tor-browser

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

app_bundle_utils.py (7297B)


      1 # Copyright 2018 The Chromium Authors
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import json
      6 import logging
      7 import os
      8 import pathlib
      9 import re
     10 import shutil
     11 import sys
     12 import zipfile
     13 
     14 sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'gyp'))
     15 
     16 from util import build_utils
     17 from util import md5_check
     18 from util import resource_utils
     19 import bundletool
     20 
     21 # "system_apks" is "default", but with locale list and compressed dex.
     22 _SYSTEM_MODES = ('system', 'system_apks')
     23 BUILD_APKS_MODES = _SYSTEM_MODES + ('default', 'universal')
     24 OPTIMIZE_FOR_OPTIONS = ('ABI', 'SCREEN_DENSITY', 'LANGUAGE',
     25                        'TEXTURE_COMPRESSION_FORMAT')
     26 
     27 _ALL_ABIS = ['armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64']
     28 
     29 
     30 def _BundleMinSdkVersion(bundle_path):
     31  manifest_data = bundletool.RunBundleTool(
     32      ['dump', 'manifest', '--bundle', bundle_path])
     33  return int(re.search(r'minSdkVersion.*?(\d+)', manifest_data).group(1))
     34 
     35 
     36 def _CreateDeviceSpec(bundle_path, sdk_version, locales):
     37  if not sdk_version:
     38    sdk_version = _BundleMinSdkVersion(bundle_path)
     39 
     40  # Setting sdkVersion=minSdkVersion prevents multiple per-minSdkVersion .apk
     41  # files from being created within the .apks file.
     42  return {
     43      'screenDensity': 1000,  # Ignored since we don't split on density.
     44      'sdkVersion': sdk_version,
     45      'supportedAbis': _ALL_ABIS,  # Our .aab files are already split on abi.
     46      'supportedLocales': locales,
     47  }
     48 
     49 
     50 def _FixBundleDexCompressionGlob(src_bundle, dst_bundle):
     51  # Modifies the BundleConfig.pb of the given .aab to add "classes*.dex" to the
     52  # "uncompressedGlob" list.
     53  with zipfile.ZipFile(src_bundle) as src, \
     54      zipfile.ZipFile(dst_bundle, 'w') as dst:
     55    for info in src.infolist():
     56      data = src.read(info)
     57      if info.filename == 'BundleConfig.pb':
     58        # A classesX.dex entry is added by create_app_bundle.py so that we can
     59        # modify it here in order to have it take effect. b/176198991
     60        data = data.replace(b'classesX.dex', b'classes*.dex')
     61      dst.writestr(info, data)
     62 
     63 
     64 def GenerateBundleApks(bundle_path,
     65                       bundle_apks_path,
     66                       aapt2_path,
     67                       keystore_path,
     68                       keystore_password,
     69                       keystore_alias,
     70                       mode=None,
     71                       local_testing=False,
     72                       minimal=False,
     73                       minimal_sdk_version=None,
     74                       check_for_noop=True,
     75                       system_image_locales=None,
     76                       optimize_for=None):
     77  """Generate an .apks archive from a an app bundle if needed.
     78 
     79  Args:
     80    bundle_path: Input bundle file path.
     81    bundle_apks_path: Output bundle .apks archive path. Name must end with
     82      '.apks' or this operation will fail.
     83    aapt2_path: Path to aapt2 build tool.
     84    keystore_path: Path to keystore.
     85    keystore_password: Keystore password, as a string.
     86    keystore_alias: Keystore signing key alias.
     87    mode: Build mode, which must be either None or one of BUILD_APKS_MODES.
     88    minimal: Create the minimal set of apks possible (english-only).
     89    minimal_sdk_version: Use this sdkVersion when |minimal| or
     90      |system_image_locales| args are present.
     91    check_for_noop: Use md5_check to short-circuit when inputs have not changed.
     92    system_image_locales: Locales to package in the APK when mode is "system"
     93      or "system_compressed".
     94    optimize_for: Overrides split configuration, which must be None or
     95      one of OPTIMIZE_FOR_OPTIONS.
     96  """
     97  device_spec = None
     98  if minimal_sdk_version:
     99    assert minimal or system_image_locales, (
    100        'minimal_sdk_version is only used when minimal or system_image_locales '
    101        'is specified')
    102  if minimal:
    103    # Measure with one language split installed. Use Hindi because it is
    104    # popular. resource_size.py looks for splits/base-hi.apk.
    105    # Note: English is always included since it's in base-master.apk.
    106    device_spec = _CreateDeviceSpec(bundle_path, minimal_sdk_version, ['hi'])
    107  elif mode in _SYSTEM_MODES:
    108    if not system_image_locales:
    109      raise Exception('system modes require system_image_locales')
    110    # Bundletool doesn't seem to understand device specs with locales in the
    111    # form of "<lang>-r<region>", so just provide the language code instead.
    112    locales = [
    113        resource_utils.ToAndroidLocaleName(l).split('-')[0]
    114        for l in system_image_locales
    115    ]
    116    device_spec = _CreateDeviceSpec(bundle_path, minimal_sdk_version, locales)
    117 
    118  def rebuild():
    119    logging.info('Building %s', bundle_apks_path)
    120    with build_utils.TempDir() as tmp_dir:
    121      tmp_apks_file = os.path.join(tmp_dir, 'output.apks')
    122      cmd_args = [
    123          'build-apks',
    124          '--aapt2=%s' % aapt2_path,
    125          '--output=%s' % tmp_apks_file,
    126          '--ks=%s' % keystore_path,
    127          '--ks-pass=pass:%s' % keystore_password,
    128          '--ks-key-alias=%s' % keystore_alias,
    129          '--overwrite',
    130      ]
    131      input_bundle_path = bundle_path
    132      # Work around bundletool not respecting uncompressDexFiles setting.
    133      # b/176198991
    134      if mode not in _SYSTEM_MODES and _BundleMinSdkVersion(bundle_path) >= 27:
    135        input_bundle_path = os.path.join(tmp_dir, 'system.aab')
    136        _FixBundleDexCompressionGlob(bundle_path, input_bundle_path)
    137 
    138      cmd_args += ['--bundle=%s' % input_bundle_path]
    139 
    140      if local_testing:
    141        cmd_args += ['--local-testing']
    142 
    143      if mode is not None:
    144        if mode not in BUILD_APKS_MODES:
    145          raise Exception('Invalid mode parameter %s (should be in %s)' %
    146                          (mode, BUILD_APKS_MODES))
    147        if mode != 'system_apks':
    148          cmd_args += ['--mode=' + mode]
    149        else:
    150          # Specify --optimize-for to prevent language splits being created.
    151          cmd_args += ['--optimize-for=device_tier']
    152 
    153      if optimize_for:
    154        if optimize_for not in OPTIMIZE_FOR_OPTIONS:
    155          raise Exception('Invalid optimize_for parameter %s '
    156                          '(should be in %s)' %
    157                          (mode, OPTIMIZE_FOR_OPTIONS))
    158        cmd_args += ['--optimize-for=' + optimize_for]
    159 
    160      if device_spec:
    161        data = json.dumps(device_spec)
    162        logging.debug('Device Spec: %s', data)
    163        spec_file = pathlib.Path(tmp_dir) / 'device.json'
    164        spec_file.write_text(data)
    165        cmd_args += ['--device-spec=' + str(spec_file)]
    166 
    167      bundletool.RunBundleTool(cmd_args)
    168 
    169      shutil.move(tmp_apks_file, bundle_apks_path)
    170 
    171  if check_for_noop:
    172    input_paths = [
    173        bundle_path,
    174        bundletool.BUNDLETOOL_JAR_PATH,
    175        aapt2_path,
    176        keystore_path,
    177    ]
    178    input_strings = [
    179        keystore_password,
    180        keystore_alias,
    181        device_spec,
    182    ]
    183    if mode is not None:
    184      input_strings.append(mode)
    185 
    186    # Avoid rebuilding (saves ~20s) when the input files have not changed. This
    187    # is essential when calling the apk_operations.py script multiple times with
    188    # the same bundle (e.g. out/Debug/bin/monochrome_public_bundle run).
    189    md5_check.CallAndRecordIfStale(
    190        rebuild,
    191        input_paths=input_paths,
    192        input_strings=input_strings,
    193        output_paths=[bundle_apks_path])
    194  else:
    195    rebuild()