Validate monolithic and modular hidden API flags are consistent

This makes sure that where there is overlap between the hidden API
flags generated for a module and the monolithic flags that they are
identical. That ensures that the modular hidden API flags will be
compatible with previous releases that relied on the monolithic flags.

Bug: 179354495
Test: m out/soong/.intermediates/art/build/boot/art-bootclasspath-fragment/android_common_apex10000/modular-hiddenapi/all-flags.csv
      m out/soong/hiddenapi/hiddenapi-flags.csv
      - Create some inconsistencies between the above two files.
      m out/soong/hiddenapi/hiddenapi-flags.csv.valid
Change-Id: Iaf9e23cef63e221608955d89dc8d496bcc70c86e
diff --git a/java/hiddenapi_modular.go b/java/hiddenapi_modular.go
index 8174524..d1d9a75 100644
--- a/java/hiddenapi_modular.go
+++ b/java/hiddenapi_modular.go
@@ -15,6 +15,8 @@
 package java
 
 import (
+	"strings"
+
 	"android/soong/android"
 	"github.com/google/blueprint"
 )
@@ -378,6 +380,16 @@
 
 var hiddenAPIFlagFileInfoProvider = blueprint.NewProvider(hiddenAPIFlagFileInfo{})
 
+// pathForValidation creates a path of the same type as the supplied type but with a name of
+// <path>.valid.
+//
+// e.g. If path is an OutputPath for out/soong/hiddenapi/hiddenapi-flags.csv then this will return
+// an OutputPath for out/soong/hiddenapi/hiddenapi-flags.csv.valid
+func pathForValidation(ctx android.PathContext, path android.WritablePath) android.WritablePath {
+	extWithoutLeadingDot := strings.TrimPrefix(path.Ext(), ".")
+	return path.ReplaceExtension(ctx, extWithoutLeadingDot+".valid")
+}
+
 // buildRuleToGenerateHiddenApiFlags creates a rule to create the monolithic hidden API flags from
 // the flags from all the modules, the stub flags, augmented with some additional configuration
 // files.
@@ -392,6 +404,30 @@
 // augmentationInfo is a struct containing paths to files that augment the information provided by
 // the moduleSpecificFlagsPaths.
 func buildRuleToGenerateHiddenApiFlags(ctx android.BuilderContext, name, desc string, outputPath android.WritablePath, baseFlagsPath android.Path, moduleSpecificFlagsPaths android.Paths, flagFileInfo *hiddenAPIFlagFileInfo) {
+
+	// The file which is used to record that the flags file is valid.
+	var validFile android.WritablePath
+
+	// If there are flag files that have been generated by fragments on which this depends then use
+	// them to validate the flag file generated by the rules created by this method.
+	if allFlagsPaths := flagFileInfo.AllFlagsPaths; len(allFlagsPaths) > 0 {
+		// The flags file generated by the rule created by this method needs to be validated to ensure
+		// that it is consistent with the flag files generated by the individual fragments.
+
+		validFile = pathForValidation(ctx, outputPath)
+
+		// Create a rule to validate the output from the following rule.
+		rule := android.NewRuleBuilder(pctx, ctx)
+		rule.Command().
+			BuiltTool("verify_overlaps").
+			Input(outputPath).
+			Inputs(allFlagsPaths).
+			// If validation passes then update the file that records that.
+			Text("&& touch").Output(validFile)
+		rule.Build(name+"Validation", desc+" validation")
+	}
+
+	// Create the rule that will generate the flag files.
 	tempPath := tempPathForRestat(ctx, outputPath)
 	rule := android.NewRuleBuilder(pctx, ctx)
 	command := rule.Command().
@@ -410,6 +446,14 @@
 
 	commitChangeForRestat(rule, tempPath, outputPath)
 
+	if validFile != nil {
+		// Add the file that indicates that the file generated by this is valid.
+		//
+		// This will cause the validation rule above to be run any time that the output of this rule
+		// changes but the validation will run in parallel with other rules that depend on this file.
+		command.Validation(validFile)
+	}
+
 	rule.Build(name, desc)
 }
 
diff --git a/scripts/hiddenapi/Android.bp b/scripts/hiddenapi/Android.bp
index af7e7fe..7472f52 100644
--- a/scripts/hiddenapi/Android.bp
+++ b/scripts/hiddenapi/Android.bp
@@ -47,3 +47,18 @@
         },
     },
 }
+
+python_binary_host {
+    name: "verify_overlaps",
+    main: "verify_overlaps.py",
+    srcs: ["verify_overlaps.py"],
+    version: {
+        py2: {
+            enabled: false,
+        },
+        py3: {
+            enabled: true,
+            embedded_launcher: true,
+        },
+    },
+}
diff --git a/scripts/hiddenapi/verify_overlaps.py b/scripts/hiddenapi/verify_overlaps.py
new file mode 100755
index 0000000..c8e3879
--- /dev/null
+++ b/scripts/hiddenapi/verify_overlaps.py
@@ -0,0 +1,69 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""
+Verify that one set of hidden API flags is a subset of another.
+"""
+
+import argparse
+import csv
+
+args_parser = argparse.ArgumentParser(description='Verify that one set of hidden API flags is a subset of another.')
+args_parser.add_argument('all', help='All the flags')
+args_parser.add_argument('subsets', nargs=argparse.REMAINDER, help='Subsets of the flags')
+args = args_parser.parse_args()
+
+
+def dict_reader(input):
+    return csv.DictReader(input, delimiter=',', quotechar='|', fieldnames=['signature'])
+
+# Read in all the flags into a dict indexed by signature
+allFlagsBySignature = {}
+with open(args.all, 'r') as allFlagsFile:
+    allFlagsReader = dict_reader(allFlagsFile)
+    for row in allFlagsReader:
+        signature = row['signature']
+        allFlagsBySignature[signature]=row
+
+failed = False
+for subsetPath in args.subsets:
+    mismatchingSignatures = []
+    with open(subsetPath, 'r') as subsetFlagsFile:
+        subsetReader = dict_reader(subsetFlagsFile)
+        for row in subsetReader:
+            signature = row['signature']
+            if signature in allFlagsBySignature:
+                allFlags = allFlagsBySignature.get(signature)
+                if allFlags != row:
+                    mismatchingSignatures.append((signature, row[None], allFlags[None]))
+            else:
+                mismatchingSignatures.append((signature, row[None], []))
+
+
+    if mismatchingSignatures:
+        failed = True
+        print("ERROR: Hidden API flags are inconsistent:")
+        print("< " + subsetPath)
+        print("> " + args.all)
+        for mismatch in mismatchingSignatures:
+            print()
+            print("< " + mismatch[0] + "," + ",".join(mismatch[1]))
+            if mismatch[2] != None:
+                print("> " + mismatch[0] + "," + ",".join(mismatch[2]))
+            else:
+                print("> " + mismatch[0] + " - missing")
+
+if failed:
+    sys.exit(1)