tor-browser

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

aar.py (7341B)


      1 #!/usr/bin/env python3
      2 #
      3 # Copyright 2016 The Chromium Authors
      4 # Use of this source code is governed by a BSD-style license that can be
      5 # found in the LICENSE file.
      6 
      7 """Processes an Android AAR file."""
      8 
      9 import argparse
     10 import os
     11 import posixpath
     12 import re
     13 import shutil
     14 import sys
     15 from xml.etree import ElementTree
     16 import zipfile
     17 
     18 from util import build_utils
     19 import action_helpers  # build_utils adds //build to sys.path.
     20 import gn_helpers
     21 
     22 
     23 _PROGUARD_TXT = 'proguard.txt'
     24 
     25 
     26 def _GetManifestPackage(doc):
     27  """Returns the package specified in the manifest.
     28 
     29  Args:
     30    doc: an XML tree parsed by ElementTree
     31 
     32  Returns:
     33    String representing the package name.
     34  """
     35  return doc.attrib['package']
     36 
     37 
     38 def _IsManifestEmpty(doc):
     39  """Decides whether the given manifest has merge-worthy elements.
     40 
     41  E.g.: <activity>, <service>, etc.
     42 
     43  Args:
     44    doc: an XML tree parsed by ElementTree
     45 
     46  Returns:
     47    Whether the manifest has merge-worthy elements.
     48  """
     49  for node in doc:
     50    if node.tag == 'application':
     51      if list(node):
     52        return False
     53    elif node.tag != 'uses-sdk':
     54      return False
     55 
     56  return True
     57 
     58 
     59 def _CreateInfo(aar_file, resource_exclusion_globs):
     60  """Extracts and return .info data from an .aar file.
     61 
     62  Args:
     63    aar_file: Path to an input .aar file.
     64    resource_exclusion_globs: List of globs that exclude res/ files.
     65 
     66  Returns:
     67    A dict containing .info data.
     68  """
     69  data = {}
     70  data['aidl'] = []
     71  data['assets'] = []
     72  data['resources'] = []
     73  data['subjars'] = []
     74  data['subjar_tuples'] = []
     75  data['has_classes_jar'] = False
     76  data['has_proguard_flags'] = False
     77  data['has_native_libraries'] = False
     78  data['has_r_text_file'] = False
     79  prefab_headers = []
     80  prefab_include_dirs = []
     81  with zipfile.ZipFile(aar_file) as z:
     82    manifest_xml = ElementTree.fromstring(z.read('AndroidManifest.xml'))
     83    data['is_manifest_empty'] = _IsManifestEmpty(manifest_xml)
     84    manifest_package = _GetManifestPackage(manifest_xml)
     85    if manifest_package:
     86      data['manifest_package'] = manifest_package
     87 
     88    for name in z.namelist():
     89      if name.endswith('/'):
     90        continue
     91      if name.startswith('aidl/'):
     92        data['aidl'].append(name)
     93      elif name.startswith('res/'):
     94        if not build_utils.MatchesGlob(name, resource_exclusion_globs):
     95          data['resources'].append(name)
     96      elif name.startswith('libs/') and name.endswith('.jar'):
     97        label = posixpath.basename(name)[:-4]
     98        label = re.sub(r'[^a-zA-Z0-9._]', '_', label)
     99        data['subjars'].append(name)
    100        data['subjar_tuples'].append([label, name])
    101      elif name.startswith('assets/'):
    102        data['assets'].append(name)
    103      elif name.startswith('jni/'):
    104        data['has_native_libraries'] = True
    105        if 'native_libraries' in data:
    106          data['native_libraries'].append(name)
    107        else:
    108          data['native_libraries'] = [name]
    109      elif name == 'classes.jar':
    110        data['has_classes_jar'] = True
    111      elif name == _PROGUARD_TXT:
    112        data['has_proguard_flags'] = True
    113      elif name == 'R.txt':
    114        # Some AARs, e.g. gvr_controller_java, have empty R.txt. Such AARs
    115        # have no resources as well. We treat empty R.txt as having no R.txt.
    116        data['has_r_text_file'] = bool(z.read('R.txt').strip())
    117      elif name.startswith('prefab/modules') and '/include/' in name:
    118        prefab_headers.append(name)
    119        subdir = name[:name.index('/include/')] + '/include'
    120        if subdir not in prefab_include_dirs:
    121          prefab_include_dirs.append(subdir)
    122 
    123  if prefab_include_dirs:
    124    data['prefab_headers'] = prefab_headers
    125    data['prefab_include_dirs'] = prefab_include_dirs
    126  return data
    127 
    128 
    129 def _PerformExtract(aar_file, output_dir, name_allowlist):
    130  with build_utils.TempDir() as tmp_dir:
    131    tmp_dir = os.path.join(tmp_dir, 'staging')
    132    os.mkdir(tmp_dir)
    133    build_utils.ExtractAll(
    134        aar_file, path=tmp_dir, predicate=name_allowlist.__contains__)
    135    # Write a breadcrumb so that SuperSize can attribute files back to the .aar.
    136    with open(os.path.join(tmp_dir, 'source.info'), 'w') as f:
    137      f.write('source={}\n'.format(aar_file))
    138 
    139    shutil.rmtree(output_dir, ignore_errors=True)
    140    shutil.move(tmp_dir, output_dir)
    141 
    142 
    143 def _AddCommonArgs(parser):
    144  parser.add_argument(
    145      'aar_file', help='Path to the AAR file.', type=os.path.normpath)
    146  parser.add_argument('--ignore-resources',
    147                      action='store_true',
    148                      help='Whether to skip extraction of res/')
    149  parser.add_argument('--resource-exclusion-globs',
    150                      help='GN list of globs for res/ files to ignore')
    151 
    152 
    153 def main():
    154  parser = argparse.ArgumentParser(description=__doc__)
    155  command_parsers = parser.add_subparsers(dest='command')
    156  subp = command_parsers.add_parser(
    157      'list', help='Output a GN scope describing the contents of the .aar.')
    158  _AddCommonArgs(subp)
    159  subp.add_argument('--output', help='Output file.', default='-')
    160 
    161  subp = command_parsers.add_parser('extract', help='Extracts the .aar')
    162  _AddCommonArgs(subp)
    163  subp.add_argument(
    164      '--output-dir',
    165      help='Output directory for the extracted files.',
    166      required=True,
    167      type=os.path.normpath)
    168  subp.add_argument(
    169      '--assert-info-file',
    170      help='Path to .info file. Asserts that it matches what '
    171      '"list" would output.',
    172      type=argparse.FileType('r'))
    173 
    174  args = parser.parse_args()
    175 
    176  args.resource_exclusion_globs = action_helpers.parse_gn_list(
    177      args.resource_exclusion_globs)
    178  if args.ignore_resources:
    179    args.resource_exclusion_globs.append('res/*')
    180 
    181  aar_info = _CreateInfo(args.aar_file, args.resource_exclusion_globs)
    182  formatted_info = """\
    183 # Generated by //build/android/gyp/aar.py
    184 # To regenerate, use "update_android_aar_prebuilts = true" and run "gn gen".
    185 
    186 """ + gn_helpers.ToGNString(aar_info, pretty=True)
    187 
    188  if args.command == 'extract':
    189    if args.assert_info_file:
    190      cached_info = args.assert_info_file.read()
    191      if formatted_info != cached_info:
    192        raise Exception('android_aar_prebuilt() cached .info file is '
    193                        'out-of-date. Run gn gen with '
    194                        'update_android_aar_prebuilts=true to update it.')
    195 
    196    # Extract all files except for filtered res/ files.
    197    with zipfile.ZipFile(args.aar_file) as zf:
    198      names = {n for n in zf.namelist() if not n.startswith('res/')}
    199    names.update(aar_info['resources'])
    200 
    201    _PerformExtract(args.aar_file, args.output_dir, names)
    202 
    203  elif args.command == 'list':
    204    aar_output_present = args.output != '-' and os.path.isfile(args.output)
    205    if aar_output_present:
    206      # Some .info files are read-only, for examples the cipd-controlled ones
    207      # under third_party/android_deps/repository. To deal with these, first
    208      # that its content is correct, and if it is, exit without touching
    209      # the file system.
    210      file_info = open(args.output, 'r').read()
    211      if file_info == formatted_info:
    212        return
    213 
    214    # Try to write the file. This may fail for read-only ones that were
    215    # not updated.
    216    try:
    217      with open(args.output, 'w') as f:
    218        f.write(formatted_info)
    219    except IOError as e:
    220      if not aar_output_present:
    221        raise e
    222      raise Exception('Could not update output file: %s\n' % args.output) from e
    223 
    224 
    225 if __name__ == '__main__':
    226  sys.exit(main())