196 lines
7.1 KiB
Python
196 lines
7.1 KiB
Python
# Copyright 2018 The Chromium Authors
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
import json
|
|
import logging
|
|
import os
|
|
import pathlib
|
|
import re
|
|
import shutil
|
|
import sys
|
|
import zipfile
|
|
|
|
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'gyp'))
|
|
|
|
from util import build_utils
|
|
from util import md5_check
|
|
from util import resource_utils
|
|
import bundletool
|
|
|
|
# "system_apks" is "default", but with locale list and compressed dex.
|
|
_SYSTEM_MODES = ('system', 'system_apks')
|
|
BUILD_APKS_MODES = _SYSTEM_MODES + ('default', 'universal')
|
|
OPTIMIZE_FOR_OPTIONS = ('ABI', 'SCREEN_DENSITY', 'LANGUAGE',
|
|
'TEXTURE_COMPRESSION_FORMAT')
|
|
|
|
_ALL_ABIS = ['armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64']
|
|
|
|
|
|
def _BundleMinSdkVersion(bundle_path):
|
|
manifest_data = bundletool.RunBundleTool(
|
|
['dump', 'manifest', '--bundle', bundle_path])
|
|
return int(re.search(r'minSdkVersion.*?(\d+)', manifest_data).group(1))
|
|
|
|
|
|
def _CreateDeviceSpec(bundle_path, sdk_version, locales):
|
|
if not sdk_version:
|
|
sdk_version = _BundleMinSdkVersion(bundle_path)
|
|
|
|
# Setting sdkVersion=minSdkVersion prevents multiple per-minSdkVersion .apk
|
|
# files from being created within the .apks file.
|
|
return {
|
|
'screenDensity': 1000, # Ignored since we don't split on density.
|
|
'sdkVersion': sdk_version,
|
|
'supportedAbis': _ALL_ABIS, # Our .aab files are already split on abi.
|
|
'supportedLocales': locales,
|
|
}
|
|
|
|
|
|
def _FixBundleDexCompressionGlob(src_bundle, dst_bundle):
|
|
# Modifies the BundleConfig.pb of the given .aab to add "classes*.dex" to the
|
|
# "uncompressedGlob" list.
|
|
with zipfile.ZipFile(src_bundle) as src, \
|
|
zipfile.ZipFile(dst_bundle, 'w') as dst:
|
|
for info in src.infolist():
|
|
data = src.read(info)
|
|
if info.filename == 'BundleConfig.pb':
|
|
# A classesX.dex entry is added by create_app_bundle.py so that we can
|
|
# modify it here in order to have it take effect. b/176198991
|
|
data = data.replace(b'classesX.dex', b'classes*.dex')
|
|
dst.writestr(info, data)
|
|
|
|
|
|
def GenerateBundleApks(bundle_path,
|
|
bundle_apks_path,
|
|
aapt2_path,
|
|
keystore_path,
|
|
keystore_password,
|
|
keystore_alias,
|
|
mode=None,
|
|
local_testing=False,
|
|
minimal=False,
|
|
minimal_sdk_version=None,
|
|
check_for_noop=True,
|
|
system_image_locales=None,
|
|
optimize_for=None):
|
|
"""Generate an .apks archive from a an app bundle if needed.
|
|
|
|
Args:
|
|
bundle_path: Input bundle file path.
|
|
bundle_apks_path: Output bundle .apks archive path. Name must end with
|
|
'.apks' or this operation will fail.
|
|
aapt2_path: Path to aapt2 build tool.
|
|
keystore_path: Path to keystore.
|
|
keystore_password: Keystore password, as a string.
|
|
keystore_alias: Keystore signing key alias.
|
|
mode: Build mode, which must be either None or one of BUILD_APKS_MODES.
|
|
minimal: Create the minimal set of apks possible (english-only).
|
|
minimal_sdk_version: Use this sdkVersion when |minimal| or
|
|
|system_image_locales| args are present.
|
|
check_for_noop: Use md5_check to short-circuit when inputs have not changed.
|
|
system_image_locales: Locales to package in the APK when mode is "system"
|
|
or "system_compressed".
|
|
optimize_for: Overrides split configuration, which must be None or
|
|
one of OPTIMIZE_FOR_OPTIONS.
|
|
"""
|
|
device_spec = None
|
|
if minimal_sdk_version:
|
|
assert minimal or system_image_locales, (
|
|
'minimal_sdk_version is only used when minimal or system_image_locales '
|
|
'is specified')
|
|
if minimal:
|
|
# Measure with one language split installed. Use Hindi because it is
|
|
# popular. resource_size.py looks for splits/base-hi.apk.
|
|
# Note: English is always included since it's in base-master.apk.
|
|
device_spec = _CreateDeviceSpec(bundle_path, minimal_sdk_version, ['hi'])
|
|
elif mode in _SYSTEM_MODES:
|
|
if not system_image_locales:
|
|
raise Exception('system modes require system_image_locales')
|
|
# Bundletool doesn't seem to understand device specs with locales in the
|
|
# form of "<lang>-r<region>", so just provide the language code instead.
|
|
locales = [
|
|
resource_utils.ToAndroidLocaleName(l).split('-')[0]
|
|
for l in system_image_locales
|
|
]
|
|
device_spec = _CreateDeviceSpec(bundle_path, minimal_sdk_version, locales)
|
|
|
|
def rebuild():
|
|
logging.info('Building %s', bundle_apks_path)
|
|
with build_utils.TempDir() as tmp_dir:
|
|
tmp_apks_file = os.path.join(tmp_dir, 'output.apks')
|
|
cmd_args = [
|
|
'build-apks',
|
|
'--aapt2=%s' % aapt2_path,
|
|
'--output=%s' % tmp_apks_file,
|
|
'--ks=%s' % keystore_path,
|
|
'--ks-pass=pass:%s' % keystore_password,
|
|
'--ks-key-alias=%s' % keystore_alias,
|
|
'--overwrite',
|
|
]
|
|
input_bundle_path = bundle_path
|
|
# Work around bundletool not respecting uncompressDexFiles setting.
|
|
# b/176198991
|
|
if mode not in _SYSTEM_MODES and _BundleMinSdkVersion(bundle_path) >= 27:
|
|
input_bundle_path = os.path.join(tmp_dir, 'system.aab')
|
|
_FixBundleDexCompressionGlob(bundle_path, input_bundle_path)
|
|
|
|
cmd_args += ['--bundle=%s' % input_bundle_path]
|
|
|
|
if local_testing:
|
|
cmd_args += ['--local-testing']
|
|
|
|
if mode is not None:
|
|
if mode not in BUILD_APKS_MODES:
|
|
raise Exception('Invalid mode parameter %s (should be in %s)' %
|
|
(mode, BUILD_APKS_MODES))
|
|
if mode != 'system_apks':
|
|
cmd_args += ['--mode=' + mode]
|
|
else:
|
|
# Specify --optimize-for to prevent language splits being created.
|
|
cmd_args += ['--optimize-for=device_tier']
|
|
|
|
if optimize_for:
|
|
if optimize_for not in OPTIMIZE_FOR_OPTIONS:
|
|
raise Exception('Invalid optimize_for parameter %s '
|
|
'(should be in %s)' %
|
|
(mode, OPTIMIZE_FOR_OPTIONS))
|
|
cmd_args += ['--optimize-for=' + optimize_for]
|
|
|
|
if device_spec:
|
|
data = json.dumps(device_spec)
|
|
logging.debug('Device Spec: %s', data)
|
|
spec_file = pathlib.Path(tmp_dir) / 'device.json'
|
|
spec_file.write_text(data)
|
|
cmd_args += ['--device-spec=' + str(spec_file)]
|
|
|
|
bundletool.RunBundleTool(cmd_args)
|
|
|
|
shutil.move(tmp_apks_file, bundle_apks_path)
|
|
|
|
if check_for_noop:
|
|
input_paths = [
|
|
bundle_path,
|
|
bundletool.BUNDLETOOL_JAR_PATH,
|
|
aapt2_path,
|
|
keystore_path,
|
|
]
|
|
input_strings = [
|
|
keystore_password,
|
|
keystore_alias,
|
|
device_spec,
|
|
]
|
|
if mode is not None:
|
|
input_strings.append(mode)
|
|
|
|
# Avoid rebuilding (saves ~20s) when the input files have not changed. This
|
|
# is essential when calling the apk_operations.py script multiple times with
|
|
# the same bundle (e.g. out/Debug/bin/monochrome_public_bundle run).
|
|
md5_check.CallAndRecordIfStale(
|
|
rebuild,
|
|
input_paths=input_paths,
|
|
input_strings=input_strings,
|
|
output_paths=[bundle_apks_path])
|
|
else:
|
|
rebuild()
|