tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

list_java_targets.py (9730B)


      1 #!/usr/bin/env python3
      2 # Copyright 2020 The Chromium Authors
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 
      6 # Lint as: python3
      7 """Prints out available java targets.
      8 
      9 Examples:
     10 # List GN target for bundles:
     11 build/android/list_java_targets.py -C out/Default --type android_app_bundle \
     12 --gn-labels
     13 
     14 # List all android targets with types:
     15 build/android/list_java_targets.py -C out/Default --print-types
     16 
     17 # Build all apk targets:
     18 build/android/list_java_targets.py -C out/Default --type android_apk | xargs \
     19 autoninja -C out/Default
     20 
     21 # Show how many of each target type exist:
     22 build/android/list_java_targets.py -C out/Default --stats
     23 
     24 """
     25 
     26 import argparse
     27 import collections
     28 import json
     29 import logging
     30 import os
     31 import shlex
     32 import shutil
     33 import subprocess
     34 import sys
     35 
     36 _SRC_ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), '..',
     37                                          '..'))
     38 sys.path.append(os.path.join(_SRC_ROOT, 'build'))
     39 import gn_helpers
     40 
     41 sys.path.append(os.path.join(_SRC_ROOT, 'build', 'android'))
     42 from pylib import constants
     43 
     44 _VALID_TYPES = (
     45    'android_apk',
     46    'android_app_bundle',
     47    'android_app_bundle_module',
     48    'android_assets',
     49    'android_resources',
     50    'dist_aar',
     51    'dist_jar',
     52    'group',
     53    'java_annotation_processor',
     54    'java_binary',
     55    'java_library',
     56    'robolectric_binary',
     57    'system_java_library',
     58 )
     59 
     60 
     61 def _resolve_ninja():
     62  # Prefer the version on PATH, but fallback to known version if PATH doesn't
     63  # have one (e.g. on bots).
     64  if shutil.which('ninja') is None:
     65    return os.path.join(_SRC_ROOT, 'third_party', 'ninja', 'ninja')
     66  return 'ninja'
     67 
     68 
     69 def _compile(output_dir, args, quiet=False):
     70  cmd = gn_helpers.CreateBuildCommand(output_dir) + args
     71  logging.info('Running: %s', shlex.join(cmd))
     72  if quiet:
     73    subprocess.run(cmd, check=True, capture_output=True)
     74  else:
     75    subprocess.run(cmd, check=True, stdout=sys.stderr)
     76 
     77 
     78 def _query_for_build_config_targets(output_dir):
     79  # Query ninja rather than GN since it's faster.
     80  # Use ninja rather than autoninja to avoid extra output if user has set the
     81  # NINJA_SUMMARIZE_BUILD environment variable.
     82  cmd = [_resolve_ninja(), '-C', output_dir, '-t', 'targets']
     83  logging.info('Running: %r', cmd)
     84  ninja_output = subprocess.run(cmd,
     85                                check=True,
     86                                capture_output=True,
     87                                encoding='ascii').stdout
     88  ret = []
     89  SUFFIX = '__build_config_crbug_908819'
     90  SUFFIX_LEN = len(SUFFIX)
     91  for line in ninja_output.splitlines():
     92    ninja_target = line.rsplit(':', 1)[0]
     93    # Ignore root aliases by ensuring a : exists.
     94    if ':' in ninja_target and ninja_target.endswith(SUFFIX):
     95      ret.append(f'//{ninja_target[:-SUFFIX_LEN]}')
     96  return ret
     97 
     98 
     99 def _query_json(*, json_dict: dict, query: str, path: str):
    100  """Traverses through the json dictionary according to the query.
    101 
    102  If at any point a key does not exist, return the empty string, but raise an
    103  error if a key exists but is the wrong type.
    104 
    105  This is roughly equivalent to returning
    106  json_dict[queries[0]]?[queries[1]]?...[queries[N]]? where the ? means that if
    107  the key doesn't exist, the empty string is returned.
    108 
    109  Example:
    110  Given json_dict = {'a': {'b': 'c'}}
    111  - If queries = ['a', 'b']
    112    Return: 'c'
    113  - If queries = ['a', 'd']
    114    Return ''
    115  - If queries = ['x']
    116    Return ''
    117  - If queries = ['a', 'b', 'x']
    118    Raise an error since json_dict['a']['b'] is the string 'c' instead of an
    119    expected dict that can be indexed into.
    120 
    121  Returns the final result after exhausting all the queries.
    122  """
    123  queries = query.split('.')
    124  value = json_dict
    125  try:
    126    for key in queries:
    127      value = value.get(key)
    128      if value is None:
    129        return ''
    130  except AttributeError as e:
    131    raise Exception(
    132        f'Failed when attempting to get {queries} from {path}') from e
    133  return value
    134 
    135 
    136 class _TargetEntry:
    137 
    138  def __init__(self, gn_target):
    139    assert gn_target.startswith('//'), f'{gn_target} does not start with //'
    140    assert ':' in gn_target, f'Non-root {gn_target} required'
    141    self.gn_target = gn_target
    142    self._build_config = None
    143 
    144  @property
    145  def ninja_target(self):
    146    return self.gn_target[2:]
    147 
    148  @property
    149  def ninja_build_config_target(self):
    150    return self.ninja_target + '__build_config_crbug_908819'
    151 
    152  @property
    153  def build_config_path(self):
    154    """Returns the filepath of the project's .build_config.json."""
    155    ninja_target = self.ninja_target
    156    # Support targets at the root level. e.g. //:foo
    157    if ninja_target[0] == ':':
    158      ninja_target = ninja_target[1:]
    159    subpath = ninja_target.replace(':', os.path.sep) + '.build_config.json'
    160    return os.path.join(constants.GetOutDirectory(), 'gen', subpath)
    161 
    162  def build_config(self):
    163    """Reads and returns the project's .build_config.json JSON."""
    164    if not self._build_config:
    165      with open(self.build_config_path) as jsonfile:
    166        self._build_config = json.load(jsonfile)
    167    return self._build_config
    168 
    169  def get_type(self):
    170    """Returns the target type from its .build_config.json."""
    171    return self.build_config()['deps_info']['type']
    172 
    173  def proguard_enabled(self):
    174    """Returns whether proguard runs for this target."""
    175    # Modules set proguard_enabled, but the proguarding happens only once at the
    176    # bundle level.
    177    if self.get_type() == 'android_app_bundle_module':
    178      return False
    179    return self.build_config()['deps_info'].get('proguard_enabled', False)
    180 
    181 
    182 def main():
    183  parser = argparse.ArgumentParser(
    184      description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
    185  parser.add_argument('-C',
    186                      '--output-directory',
    187                      help='If outdir is not provided, will attempt to guess.')
    188  parser.add_argument('--gn-labels',
    189                      action='store_true',
    190                      help='Print GN labels rather than ninja targets')
    191  parser.add_argument(
    192      '--nested',
    193      action='store_true',
    194      help='Do not convert nested targets to their top-level equivalents. '
    195      'E.g. Without this, foo_test__apk -> foo_test')
    196  parser.add_argument('--print-types',
    197                      action='store_true',
    198                      help='Print type of each target')
    199  parser.add_argument(
    200      '--print-build-config-paths',
    201      action='store_true',
    202      help='Print path to the .build_config.json of each target')
    203  parser.add_argument('--build',
    204                      action='store_true',
    205                      help='Build all .build_config.json files.')
    206  parser.add_argument('--type',
    207                      action='append',
    208                      help='Restrict to targets of given type',
    209                      choices=_VALID_TYPES)
    210  parser.add_argument('--stats',
    211                      action='store_true',
    212                      help='Print counts of each target type.')
    213  parser.add_argument('--proguard-enabled',
    214                      action='store_true',
    215                      help='Restrict to targets that have proguard enabled.')
    216  parser.add_argument('--query',
    217                      help='A dot separated string specifying a query for a '
    218                      'build config json value of each target. Example: Use '
    219                      '--query deps_info.unprocessed_jar_path to show a list '
    220                      'of all targets that have a non-empty deps_info dict and '
    221                      'non-empty "unprocessed_jar_path" value in that dict.')
    222  parser.add_argument('-v', '--verbose', default=0, action='count')
    223  parser.add_argument('-q', '--quiet', default=0, action='count')
    224  args = parser.parse_args()
    225 
    226  args.build |= bool(args.type or args.proguard_enabled or args.print_types
    227                     or args.stats or args.query)
    228 
    229  logging.basicConfig(level=logging.WARNING + 10 * (args.quiet - args.verbose),
    230                      format='%(levelname).1s %(relativeCreated)6d %(message)s')
    231 
    232  if args.output_directory:
    233    constants.SetOutputDirectory(args.output_directory)
    234  constants.CheckOutputDirectory()
    235  output_dir = constants.GetOutDirectory()
    236 
    237  if args.build:
    238    _compile(output_dir, ['build.ninja'])
    239 
    240  # Query ninja for all __build_config_crbug_908819 targets.
    241  targets = _query_for_build_config_targets(output_dir)
    242  entries = [_TargetEntry(t) for t in targets]
    243 
    244  if not entries:
    245    logging.warning('No targets found. Run with --build')
    246    sys.exit(1)
    247 
    248  if args.build:
    249    logging.warning('Building %d .build_config.json files...', len(entries))
    250    _compile(output_dir, [e.ninja_build_config_target for e in entries],
    251             quiet=args.quiet)
    252 
    253  if args.type:
    254    entries = [e for e in entries if e.get_type() in args.type]
    255 
    256  if args.proguard_enabled:
    257    entries = [e for e in entries if e.proguard_enabled()]
    258 
    259  if args.stats:
    260    counts = collections.Counter(e.get_type() for e in entries)
    261    for entry_type, count in sorted(counts.items()):
    262      print(f'{entry_type}: {count}')
    263  else:
    264    for e in entries:
    265      if args.gn_labels:
    266        to_print = e.gn_target
    267      else:
    268        to_print = e.ninja_target
    269 
    270      # Convert to top-level target
    271      if not args.nested:
    272        to_print = to_print.replace('__test_apk', '').replace('__apk', '')
    273 
    274      if args.print_types:
    275        to_print = f'{to_print}: {e.get_type()}'
    276      elif args.print_build_config_paths:
    277        to_print = f'{to_print}: {e.build_config_path}'
    278      elif args.query:
    279        value = _query_json(json_dict=e.build_config(),
    280                            query=args.query,
    281                            path=e.build_config_path)
    282        if not value:
    283          continue
    284        to_print = f'{to_print}: {value}'
    285 
    286      print(to_print)
    287 
    288 
    289 if __name__ == '__main__':
    290  main()