| #!/usr/bin/env python3 |
| # Copyright 2020 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| # Lint as: python3 |
| """Prints out available java targets. |
| |
| Examples: |
| # List GN target for bundles: |
| build/android/list_java_targets.py -C out/Default --type android_app_bundle \ |
| --gn-labels |
| |
| # List all android targets with types: |
| build/android/list_java_targets.py -C out/Default --print-types |
| |
| # Build all apk targets: |
| build/android/list_java_targets.py -C out/Default --type android_apk | xargs \ |
| autoninja -C out/Default |
| |
| # Show how many of each target type exist: |
| build/android/list_java_targets.py -C out/Default --stats |
| |
| """ |
| |
| import argparse |
| import collections |
| import json |
| import logging |
| import os |
| import shlex |
| import shutil |
| import subprocess |
| import sys |
| |
| _SRC_ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), '..', |
| '..')) |
| sys.path.append(os.path.join(_SRC_ROOT, 'build')) |
| import gn_helpers |
| |
| sys.path.append(os.path.join(_SRC_ROOT, 'build', 'android')) |
| from pylib import constants |
| |
| _VALID_TYPES = ( |
| 'android_apk', |
| 'android_app_bundle', |
| 'android_app_bundle_module', |
| 'android_assets', |
| 'android_resources', |
| 'dist_aar', |
| 'dist_jar', |
| 'group', |
| 'java_annotation_processor', |
| 'java_binary', |
| 'java_library', |
| 'robolectric_binary', |
| 'system_java_library', |
| ) |
| |
| |
| def _resolve_ninja(): |
| # Prefer the version on PATH, but fallback to known version if PATH doesn't |
| # have one (e.g. on bots). |
| if shutil.which('ninja') is None: |
| return os.path.join(_SRC_ROOT, 'third_party', 'ninja', 'ninja') |
| return 'ninja' |
| |
| |
| def _compile(output_dir, args, quiet=False): |
| cmd = gn_helpers.CreateBuildCommand(output_dir) + args |
| logging.info('Running: %s', shlex.join(cmd)) |
| if quiet: |
| subprocess.run(cmd, check=True, capture_output=True) |
| else: |
| subprocess.run(cmd, check=True, stdout=sys.stderr) |
| |
| |
| def _query_for_build_config_targets(output_dir): |
| # Query ninja rather than GN since it's faster. |
| # Use ninja rather than autoninja to avoid extra output if user has set the |
| # NINJA_SUMMARIZE_BUILD environment variable. |
| cmd = [_resolve_ninja(), '-C', output_dir, '-t', 'targets'] |
| logging.info('Running: %r', cmd) |
| ninja_output = subprocess.run(cmd, |
| check=True, |
| capture_output=True, |
| encoding='ascii').stdout |
| ret = [] |
| SUFFIX = '__build_config_crbug_908819' |
| SUFFIX_LEN = len(SUFFIX) |
| for line in ninja_output.splitlines(): |
| ninja_target = line.rsplit(':', 1)[0] |
| # Ignore root aliases by ensuring a : exists. |
| if ':' in ninja_target and ninja_target.endswith(SUFFIX): |
| ret.append(f'//{ninja_target[:-SUFFIX_LEN]}') |
| return ret |
| |
| |
| def _query_json(*, json_dict: dict, query: str, path: str): |
| """Traverses through the json dictionary according to the query. |
| |
| If at any point a key does not exist, return the empty string, but raise an |
| error if a key exists but is the wrong type. |
| |
| This is roughly equivalent to returning |
| json_dict[queries[0]]?[queries[1]]?...[queries[N]]? where the ? means that if |
| the key doesn't exist, the empty string is returned. |
| |
| Example: |
| Given json_dict = {'a': {'b': 'c'}} |
| - If queries = ['a', 'b'] |
| Return: 'c' |
| - If queries = ['a', 'd'] |
| Return '' |
| - If queries = ['x'] |
| Return '' |
| - If queries = ['a', 'b', 'x'] |
| Raise an error since json_dict['a']['b'] is the string 'c' instead of an |
| expected dict that can be indexed into. |
| |
| Returns the final result after exhausting all the queries. |
| """ |
| queries = query.split('.') |
| value = json_dict |
| try: |
| for key in queries: |
| value = value.get(key) |
| if value is None: |
| return '' |
| except AttributeError as e: |
| raise Exception( |
| f'Failed when attempting to get {queries} from {path}') from e |
| return value |
| |
| |
| class _TargetEntry: |
| |
| def __init__(self, gn_target): |
| assert gn_target.startswith('//'), f'{gn_target} does not start with //' |
| assert ':' in gn_target, f'Non-root {gn_target} required' |
| self.gn_target = gn_target |
| self._build_config = None |
| |
| @property |
| def ninja_target(self): |
| return self.gn_target[2:] |
| |
| @property |
| def ninja_build_config_target(self): |
| return self.ninja_target + '__build_config_crbug_908819' |
| |
| @property |
| def build_config_path(self): |
| """Returns the filepath of the project's .build_config.json.""" |
| ninja_target = self.ninja_target |
| # Support targets at the root level. e.g. //:foo |
| if ninja_target[0] == ':': |
| ninja_target = ninja_target[1:] |
| subpath = ninja_target.replace(':', os.path.sep) + '.build_config.json' |
| return os.path.join(constants.GetOutDirectory(), 'gen', subpath) |
| |
| def build_config(self): |
| """Reads and returns the project's .build_config.json JSON.""" |
| if not self._build_config: |
| with open(self.build_config_path) as jsonfile: |
| self._build_config = json.load(jsonfile) |
| return self._build_config |
| |
| def get_type(self): |
| """Returns the target type from its .build_config.json.""" |
| return self.build_config()['deps_info']['type'] |
| |
| def proguard_enabled(self): |
| """Returns whether proguard runs for this target.""" |
| # Modules set proguard_enabled, but the proguarding happens only once at the |
| # bundle level. |
| if self.get_type() == 'android_app_bundle_module': |
| return False |
| return self.build_config()['deps_info'].get('proguard_enabled', False) |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser( |
| description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) |
| parser.add_argument('-C', |
| '--output-directory', |
| help='If outdir is not provided, will attempt to guess.') |
| parser.add_argument('--gn-labels', |
| action='store_true', |
| help='Print GN labels rather than ninja targets') |
| parser.add_argument( |
| '--nested', |
| action='store_true', |
| help='Do not convert nested targets to their top-level equivalents. ' |
| 'E.g. Without this, foo_test__apk -> foo_test') |
| parser.add_argument('--print-types', |
| action='store_true', |
| help='Print type of each target') |
| parser.add_argument( |
| '--print-build-config-paths', |
| action='store_true', |
| help='Print path to the .build_config.json of each target') |
| parser.add_argument('--build', |
| action='store_true', |
| help='Build all .build_config.json files.') |
| parser.add_argument('--type', |
| action='append', |
| help='Restrict to targets of given type', |
| choices=_VALID_TYPES) |
| parser.add_argument('--stats', |
| action='store_true', |
| help='Print counts of each target type.') |
| parser.add_argument('--proguard-enabled', |
| action='store_true', |
| help='Restrict to targets that have proguard enabled.') |
| parser.add_argument('--query', |
| help='A dot separated string specifying a query for a ' |
| 'build config json value of each target. Example: Use ' |
| '--query deps_info.unprocessed_jar_path to show a list ' |
| 'of all targets that have a non-empty deps_info dict and ' |
| 'non-empty "unprocessed_jar_path" value in that dict.') |
| parser.add_argument('-v', '--verbose', default=0, action='count') |
| parser.add_argument('-q', '--quiet', default=0, action='count') |
| args = parser.parse_args() |
| |
| args.build |= bool(args.type or args.proguard_enabled or args.print_types |
| or args.stats or args.query) |
| |
| logging.basicConfig(level=logging.WARNING + 10 * (args.quiet - args.verbose), |
| format='%(levelname).1s %(relativeCreated)6d %(message)s') |
| |
| if args.output_directory: |
| constants.SetOutputDirectory(args.output_directory) |
| constants.CheckOutputDirectory() |
| output_dir = constants.GetOutDirectory() |
| |
| # Query ninja for all __build_config_crbug_908819 targets. |
| targets = _query_for_build_config_targets(output_dir) |
| entries = [_TargetEntry(t) for t in targets] |
| |
| if args.build: |
| logging.warning('Building %d .build_config.json files...', len(entries)) |
| _compile(output_dir, [e.ninja_build_config_target for e in entries], |
| quiet=args.quiet) |
| |
| if args.type: |
| entries = [e for e in entries if e.get_type() in args.type] |
| |
| if args.proguard_enabled: |
| entries = [e for e in entries if e.proguard_enabled()] |
| |
| if args.stats: |
| counts = collections.Counter(e.get_type() for e in entries) |
| for entry_type, count in sorted(counts.items()): |
| print(f'{entry_type}: {count}') |
| else: |
| for e in entries: |
| if args.gn_labels: |
| to_print = e.gn_target |
| else: |
| to_print = e.ninja_target |
| |
| # Convert to top-level target |
| if not args.nested: |
| to_print = to_print.replace('__test_apk', '').replace('__apk', '') |
| |
| if args.print_types: |
| to_print = f'{to_print}: {e.get_type()}' |
| elif args.print_build_config_paths: |
| to_print = f'{to_print}: {e.build_config_path}' |
| elif args.query: |
| value = _query_json(json_dict=e.build_config(), |
| query=args.query, |
| path=e.build_config_path) |
| if not value: |
| continue |
| to_print = f'{to_print}: {value}' |
| |
| print(to_print) |
| |
| |
| if __name__ == '__main__': |
| main() |