
Bug: 385968364 Change-Id: Icfdc6b2a77af0accd6ef3d3a8c82be217324ef6c Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6164812 Auto-Submit: Andrew Grieve <agrieve@chromium.org> Reviewed-by: Shuhei Takahashi <nya@chromium.org> Commit-Queue: Andrew Grieve <agrieve@chromium.org> Cr-Commit-Position: refs/heads/main@{#1405862}
291 lines
9.5 KiB
Python
Executable File
291 lines
9.5 KiB
Python
Executable File
#!/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()
|
|
|
|
if args.build:
|
|
_compile(output_dir, ['build.ninja'])
|
|
|
|
# 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 not entries:
|
|
logging.warning('No targets found. Run with --build')
|
|
sys.exit(1)
|
|
|
|
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()
|