Support adding custom tags to APIs.
Refactor the argument parsing code to support more flexible options.
Now, instead of:
--unsupported-ignore-conflicts apis.txt
We do:
--unsupported apis.txt --ignore-conflicts
And similarly for --packages. Flags now come in groups, starting with
one of ALL_FLAGS and continuing to the next such flag. Any of
--ignore-conflicts, --package or --tag X apply to the previous one of
ALL_FLAGS.
Also add the --tag flag used to tag APIs with a custom flag. This is
used to tag removed APIs as such.
Test: m -j out/soong/hiddenapi/hiddenapi-flags.csv
Bug: 171300342
Change-Id: I59e6c365c46282f4489d71e7acac2ae43e5907d2
diff --git a/tools/hiddenapi/generate_hiddenapi_lists.py b/tools/hiddenapi/generate_hiddenapi_lists.py
index da64402..e4b96b1d 100755
--- a/tools/hiddenapi/generate_hiddenapi_lists.py
+++ b/tools/hiddenapi/generate_hiddenapi_lists.py
@@ -15,7 +15,7 @@
# limitations under the License.
"""Generate API lists for non-SDK API enforcement."""
import argparse
-from collections import defaultdict
+from collections import defaultdict, namedtuple
import functools
import os
import re
@@ -54,16 +54,21 @@
FLAGS_API_LIST_SET = set(FLAGS_API_LIST)
ALL_FLAGS_SET = set(ALL_FLAGS)
-# Suffix used in command line args to express that only known and
-# otherwise unassigned entries should be assign the given flag.
+# Option specified after one of FLAGS_API_LIST to indicate that
+# only known and otherwise unassigned entries should be assign the
+# given flag.
# For example, the max-target-P list is checked in as it was in P,
# but signatures have changes since then. The flag instructs this
# script to skip any entries which do not exist any more.
-FLAG_IGNORE_CONFLICTS_SUFFIX = "-ignore-conflicts"
+FLAG_IGNORE_CONFLICTS = "ignore-conflicts"
-# Suffix used in command line args to express that all apis within a given set
-# of packages should be assign the given flag.
-FLAG_PACKAGES_SUFFIX = "-packages"
+# Option specified after one of FLAGS_API_LIST to express that all
+# apis within a given set of packages should be assign the given flag.
+FLAG_PACKAGES = "packages"
+
+# Option specified after one of FLAGS_API_LIST to indicate an extra
+# tag that should be added to the matching APIs.
+FLAG_TAG = "tag"
# Regex patterns of fields/methods used in serialization. These are
# considered public API despite being hidden.
@@ -86,6 +91,17 @@
IS_SERIALIZATION = lambda api, flags: SERIALIZATION_REGEX.match(api)
+class StoreOrderedOptions(argparse.Action):
+ """An argparse action that stores a number of option arguments in the order that
+ they were specified.
+ """
+ def __call__(self, parser, args, values, option_string = None):
+ items = getattr(args, self.dest, None)
+ if items is None:
+ items = []
+ items.append([option_string.lstrip('-'), values])
+ setattr(args, self.dest, items)
+
def get_args():
"""Parses command line arguments.
@@ -98,17 +114,19 @@
help='CSV files to be merged into output')
for flag in ALL_FLAGS:
- ignore_conflicts_flag = flag + FLAG_IGNORE_CONFLICTS_SUFFIX
- packages_flag = flag + FLAG_PACKAGES_SUFFIX
- parser.add_argument('--' + flag, dest=flag, nargs='*', default=[], metavar='TXT_FILE',
- help='lists of entries with flag "' + flag + '"')
- parser.add_argument('--' + ignore_conflicts_flag, dest=ignore_conflicts_flag, nargs='*',
- default=[], metavar='TXT_FILE',
- help='lists of entries with flag "' + flag +
- '". skip entry if missing or flag conflict.')
- parser.add_argument('--' + packages_flag, dest=packages_flag, nargs='*',
- default=[], metavar='TXT_FILE',
- help='lists of packages to be added to ' + flag + ' list')
+ parser.add_argument('--' + flag, dest='ordered_flags', metavar='TXT_FILE',
+ action=StoreOrderedOptions, help='lists of entries with flag "' + flag + '"')
+ parser.add_argument('--' + FLAG_IGNORE_CONFLICTS, dest='ordered_flags', nargs=0,
+ action=StoreOrderedOptions, help='Indicates that only known and otherwise unassigned '
+ 'entries should be assign the given flag. Must follow a list of entries and applies '
+ 'to the preceding such list.')
+ parser.add_argument('--' + FLAG_PACKAGES, dest='ordered_flags', nargs=0,
+ action=StoreOrderedOptions, help='Indicates that the previous list of entries '
+ 'is a list of packages. All members in those packages will be given the flag. '
+ 'Must follow a list of entries and applies to the preceding such list.')
+ parser.add_argument('--' + FLAG_TAG, dest='ordered_flags', nargs=1,
+ action=StoreOrderedOptions, help='Adds an extra tag to the previous list of entries. '
+ 'Must follow a list of entries and applies to the preceding such list.')
return parser.parse_args()
@@ -258,7 +276,7 @@
flags.append(FLAG_SDK)
self._dict[csv[0]].update(flags)
- def assign_flag(self, flag, apis, source="<unknown>"):
+ def assign_flag(self, flag, apis, source="<unknown>", tag = None):
"""Assigns a flag to given subset of entries.
Args:
@@ -278,11 +296,44 @@
# Iterate over the API subset, find each entry in dict and assign the flag to it.
for api in apis:
self._dict[api].add(flag)
+ if tag:
+ self._dict[api].add(tag)
+
+
+FlagFile = namedtuple('FlagFile', ('flag', 'file', 'ignore_conflicts', 'packages', 'tag'))
+
+def parse_ordered_flags(ordered_flags):
+ r = []
+ currentflag, file, ignore_conflicts, packages, tag = None, None, False, False, None
+ for flag_value in ordered_flags:
+ flag, value = flag_value[0], flag_value[1]
+ if flag in ALL_FLAGS_SET:
+ if currentflag:
+ r.append(FlagFile(currentflag, file, ignore_conflicts, packages, tag))
+ ignore_conflicts, packages, tag = False, False, None
+ currentflag = flag
+ file = value
+ else:
+ if currentflag is None:
+ raise argparse.ArgumentError('--%s is only allowed after one of %s' % (
+ flag, ' '.join(['--%s' % f for f in ALL_FLAGS_SET])))
+ if flag == FLAG_IGNORE_CONFLICTS:
+ ignore_conflicts = True
+ elif flag == FLAG_PACKAGES:
+ packages = True
+ elif flag == FLAG_TAG:
+ tag = value[0]
+
+
+ if currentflag:
+ r.append(FlagFile(currentflag, file, ignore_conflicts, packages, tag))
+ return r
def main(argv):
# Parse arguments.
args = vars(get_args())
+ flagfiles = parse_ordered_flags(args['ordered_flags'])
# Initialize API->flags dictionary.
flags = FlagsDict()
@@ -300,28 +351,28 @@
flags.assign_flag(FLAG_SDK, flags.filter_apis(IS_SERIALIZATION))
# (2) Merge text files with a known flag into the dictionary.
- for flag in ALL_FLAGS:
- for filename in args[flag]:
- flags.assign_flag(flag, read_lines(filename), filename)
+ for info in flagfiles:
+ if (not info.ignore_conflicts) and (not info.packages):
+ flags.assign_flag(info.flag, read_lines(info.file), info.file, info.tag)
# Merge text files where conflicts should be ignored.
# This will only assign the given flag if:
# (a) the entry exists, and
# (b) it has not been assigned any other flag.
# Because of (b), this must run after all strict assignments have been performed.
- for flag in ALL_FLAGS:
- for filename in args[flag + FLAG_IGNORE_CONFLICTS_SUFFIX]:
- valid_entries = flags.get_valid_subset_of_unassigned_apis(read_lines(filename))
- flags.assign_flag(flag, valid_entries, filename)
+ for info in flagfiles:
+ if info.ignore_conflicts:
+ valid_entries = flags.get_valid_subset_of_unassigned_apis(read_lines(info.file))
+ flags.assign_flag(info.flag, valid_entries, filename, info.tag)
# All members in the specified packages will be assigned the appropriate flag.
- for flag in ALL_FLAGS:
- for filename in args[flag + FLAG_PACKAGES_SUFFIX]:
- packages_needing_list = set(read_lines(filename))
+ for info in flagfiles:
+ if info.packages:
+ packages_needing_list = set(read_lines(info.file))
should_add_signature_to_list = lambda sig,lists: extract_package(
sig) in packages_needing_list and not lists
valid_entries = flags.filter_apis(should_add_signature_to_list)
- flags.assign_flag(flag, valid_entries)
+ flags.assign_flag(info.flag, valid_entries, info.file, info.tag)
# Mark all remaining entries as blocked.
flags.assign_flag(FLAG_BLOCKED, flags.filter_apis(HAS_NO_API_LIST_ASSIGNED))