diff --git a/java/hiddenapi_modular.go b/java/hiddenapi_modular.go index 817452499..d1d9a7541 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 @@ func (i *hiddenAPIFlagFileInfo) append(other hiddenAPIFlagFileInfo) { var hiddenAPIFlagFileInfoProvider = blueprint.NewProvider(hiddenAPIFlagFileInfo{}) +// pathForValidation creates a path of the same type as the supplied type but with a name of +// .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 @@ var hiddenAPIFlagFileInfoProvider = blueprint.NewProvider(hiddenAPIFlagFileInfo{ // 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 @@ func buildRuleToGenerateHiddenApiFlags(ctx android.BuilderContext, name, desc st 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 af7e7fe6b..7472f528b 100644 --- a/scripts/hiddenapi/Android.bp +++ b/scripts/hiddenapi/Android.bp @@ -47,3 +47,18 @@ python_binary_host { }, }, } + +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 000000000..c8e387931 --- /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)