diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/libwebrtc/build/android/gyp/apkbuilder.py | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream.tar.xz firefox-esr-upstream.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/build/android/gyp/apkbuilder.py')
-rwxr-xr-x | third_party/libwebrtc/build/android/gyp/apkbuilder.py | 561 |
1 files changed, 561 insertions, 0 deletions
diff --git a/third_party/libwebrtc/build/android/gyp/apkbuilder.py b/third_party/libwebrtc/build/android/gyp/apkbuilder.py new file mode 100755 index 0000000000..c355fdf88f --- /dev/null +++ b/third_party/libwebrtc/build/android/gyp/apkbuilder.py @@ -0,0 +1,561 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2015 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Adds the code parts to a resource APK.""" + +import argparse +import logging +import os +import shutil +import sys +import tempfile +import zipfile +import zlib + +import finalize_apk + +from util import build_utils +from util import diff_utils +from util import zipalign + +# Input dex.jar files are zipaligned. +zipalign.ApplyZipFileZipAlignFix() + + +# Taken from aapt's Package.cpp: +_NO_COMPRESS_EXTENSIONS = ('.jpg', '.jpeg', '.png', '.gif', '.wav', '.mp2', + '.mp3', '.ogg', '.aac', '.mpg', '.mpeg', '.mid', + '.midi', '.smf', '.jet', '.rtttl', '.imy', '.xmf', + '.mp4', '.m4a', '.m4v', '.3gp', '.3gpp', '.3g2', + '.3gpp2', '.amr', '.awb', '.wma', '.wmv', '.webm') + + +def _ParseArgs(args): + parser = argparse.ArgumentParser() + build_utils.AddDepfileOption(parser) + parser.add_argument( + '--assets', + help='GYP-list of files to add as assets in the form ' + '"srcPath:zipPath", where ":zipPath" is optional.') + parser.add_argument( + '--java-resources', help='GYP-list of java_resources JARs to include.') + parser.add_argument('--write-asset-list', + action='store_true', + help='Whether to create an assets/assets_list file.') + parser.add_argument( + '--uncompressed-assets', + help='Same as --assets, except disables compression.') + parser.add_argument('--resource-apk', + help='An .ap_ file built using aapt', + required=True) + parser.add_argument('--output-apk', + help='Path to the output file', + required=True) + parser.add_argument('--format', choices=['apk', 'bundle-module'], + default='apk', help='Specify output format.') + parser.add_argument('--dex-file', + help='Path to the classes.dex to use') + parser.add_argument( + '--jdk-libs-dex-file', + help='Path to classes.dex created by dex_jdk_libs.py') + parser.add_argument('--uncompress-dex', action='store_true', + help='Store .dex files uncompressed in the APK') + parser.add_argument('--native-libs', + action='append', + help='GYP-list of native libraries to include. ' + 'Can be specified multiple times.', + default=[]) + parser.add_argument('--secondary-native-libs', + action='append', + help='GYP-list of native libraries for secondary ' + 'android-abi. Can be specified multiple times.', + default=[]) + parser.add_argument('--android-abi', + help='Android architecture to use for native libraries') + parser.add_argument('--secondary-android-abi', + help='The secondary Android architecture to use for' + 'secondary native libraries') + parser.add_argument( + '--is-multi-abi', + action='store_true', + help='Will add a placeholder for the missing ABI if no native libs or ' + 'placeholders are set for either the primary or secondary ABI. Can only ' + 'be set if both --android-abi and --secondary-android-abi are set.') + parser.add_argument( + '--native-lib-placeholders', + help='GYP-list of native library placeholders to add.') + parser.add_argument( + '--secondary-native-lib-placeholders', + help='GYP-list of native library placeholders to add ' + 'for the secondary ABI') + parser.add_argument('--uncompress-shared-libraries', default='False', + choices=['true', 'True', 'false', 'False'], + help='Whether to uncompress native shared libraries. Argument must be ' + 'a boolean value.') + parser.add_argument( + '--apksigner-jar', help='Path to the apksigner executable.') + parser.add_argument('--zipalign-path', + help='Path to the zipalign executable.') + parser.add_argument('--key-path', + help='Path to keystore for signing.') + parser.add_argument('--key-passwd', + help='Keystore password') + parser.add_argument('--key-name', + help='Keystore name') + parser.add_argument( + '--min-sdk-version', required=True, help='Value of APK\'s minSdkVersion') + parser.add_argument( + '--best-compression', + action='store_true', + help='Use zip -9 rather than zip -1') + parser.add_argument( + '--library-always-compress', + action='append', + help='The list of library files that we always compress.') + parser.add_argument( + '--library-renames', + action='append', + help='The list of library files that we prepend crazy. to their names.') + parser.add_argument('--warnings-as-errors', + action='store_true', + help='Treat all warnings as errors.') + diff_utils.AddCommandLineFlags(parser) + options = parser.parse_args(args) + options.assets = build_utils.ParseGnList(options.assets) + options.uncompressed_assets = build_utils.ParseGnList( + options.uncompressed_assets) + options.native_lib_placeholders = build_utils.ParseGnList( + options.native_lib_placeholders) + options.secondary_native_lib_placeholders = build_utils.ParseGnList( + options.secondary_native_lib_placeholders) + options.java_resources = build_utils.ParseGnList(options.java_resources) + options.native_libs = build_utils.ParseGnList(options.native_libs) + options.secondary_native_libs = build_utils.ParseGnList( + options.secondary_native_libs) + options.library_always_compress = build_utils.ParseGnList( + options.library_always_compress) + options.library_renames = build_utils.ParseGnList(options.library_renames) + + # --apksigner-jar, --zipalign-path, --key-xxx arguments are + # required when building an APK, but not a bundle module. + if options.format == 'apk': + required_args = [ + 'apksigner_jar', 'zipalign_path', 'key_path', 'key_passwd', 'key_name' + ] + for required in required_args: + if not vars(options)[required]: + raise Exception('Argument --%s is required for APKs.' % ( + required.replace('_', '-'))) + + options.uncompress_shared_libraries = \ + options.uncompress_shared_libraries in [ 'true', 'True' ] + + if not options.android_abi and (options.native_libs or + options.native_lib_placeholders): + raise Exception('Must specify --android-abi with --native-libs') + if not options.secondary_android_abi and (options.secondary_native_libs or + options.secondary_native_lib_placeholders): + raise Exception('Must specify --secondary-android-abi with' + ' --secondary-native-libs') + if options.is_multi_abi and not (options.android_abi + and options.secondary_android_abi): + raise Exception('Must specify --is-multi-abi with both --android-abi ' + 'and --secondary-android-abi.') + return options + + +def _SplitAssetPath(path): + """Returns (src, dest) given an asset path in the form src[:dest].""" + path_parts = path.split(':') + src_path = path_parts[0] + if len(path_parts) > 1: + dest_path = path_parts[1] + else: + dest_path = os.path.basename(src_path) + return src_path, dest_path + + +def _ExpandPaths(paths): + """Converts src:dst into tuples and enumerates files within directories. + + Args: + paths: Paths in the form "src_path:dest_path" + + Returns: + A list of (src_path, dest_path) tuples sorted by dest_path (for stable + ordering within output .apk). + """ + ret = [] + for path in paths: + src_path, dest_path = _SplitAssetPath(path) + if os.path.isdir(src_path): + for f in build_utils.FindInDirectory(src_path, '*'): + ret.append((f, os.path.join(dest_path, f[len(src_path) + 1:]))) + else: + ret.append((src_path, dest_path)) + ret.sort(key=lambda t:t[1]) + return ret + + +def _GetAssetsToAdd(path_tuples, + fast_align, + disable_compression=False, + allow_reads=True): + """Returns the list of file_detail tuples for assets in the apk. + + Args: + path_tuples: List of src_path, dest_path tuples to add. + fast_align: Whether to perform alignment in python zipfile (alternatively + alignment can be done using the zipalign utility out of band). + disable_compression: Whether to disable compression. + allow_reads: If false, we do not try to read the files from disk (to find + their size for example). + + Returns: A list of (src_path, apk_path, compress, alignment) tuple + representing what and how assets are added. + """ + assets_to_add = [] + + # Group all uncompressed assets together in the hope that it will increase + # locality of mmap'ed files. + for target_compress in (False, True): + for src_path, dest_path in path_tuples: + compress = not disable_compression and ( + os.path.splitext(src_path)[1] not in _NO_COMPRESS_EXTENSIONS) + + if target_compress == compress: + # AddToZipHermetic() uses this logic to avoid growing small files. + # We need it here in order to set alignment correctly. + if allow_reads and compress and os.path.getsize(src_path) < 16: + compress = False + + apk_path = 'assets/' + dest_path + alignment = 0 if compress and not fast_align else 4 + assets_to_add.append((apk_path, src_path, compress, alignment)) + return assets_to_add + + +def _AddFiles(apk, details): + """Adds files to the apk. + + Args: + apk: path to APK to add to. + details: A list of file detail tuples (src_path, apk_path, compress, + alignment) representing what and how files are added to the APK. + """ + for apk_path, src_path, compress, alignment in details: + # This check is only relevant for assets, but it should not matter if it is + # checked for the whole list of files. + try: + apk.getinfo(apk_path) + # Should never happen since write_build_config.py handles merging. + raise Exception( + 'Multiple targets specified the asset path: %s' % apk_path) + except KeyError: + zipalign.AddToZipHermetic( + apk, + apk_path, + src_path=src_path, + compress=compress, + alignment=alignment) + + +def _GetNativeLibrariesToAdd(native_libs, android_abi, uncompress, fast_align, + lib_always_compress, lib_renames): + """Returns the list of file_detail tuples for native libraries in the apk. + + Returns: A list of (src_path, apk_path, compress, alignment) tuple + representing what and how native libraries are added. + """ + libraries_to_add = [] + + + for path in native_libs: + basename = os.path.basename(path) + compress = not uncompress or any(lib_name in basename + for lib_name in lib_always_compress) + rename = any(lib_name in basename for lib_name in lib_renames) + if rename: + basename = 'crazy.' + basename + + lib_android_abi = android_abi + if path.startswith('android_clang_arm64_hwasan/'): + lib_android_abi = 'arm64-v8a-hwasan' + + apk_path = 'lib/%s/%s' % (lib_android_abi, basename) + alignment = 0 if compress and not fast_align else 0x1000 + libraries_to_add.append((apk_path, path, compress, alignment)) + + return libraries_to_add + + +def _CreateExpectationsData(native_libs, assets): + """Creates list of native libraries and assets.""" + native_libs = sorted(native_libs) + assets = sorted(assets) + + ret = [] + for apk_path, _, compress, alignment in native_libs + assets: + ret.append('apk_path=%s, compress=%s, alignment=%s\n' % + (apk_path, compress, alignment)) + return ''.join(ret) + + +def main(args): + build_utils.InitLogging('APKBUILDER_DEBUG') + args = build_utils.ExpandFileArgs(args) + options = _ParseArgs(args) + + # Until Python 3.7, there's no better way to set compression level. + # The default is 6. + if options.best_compression: + # Compresses about twice as slow as the default. + zlib.Z_DEFAULT_COMPRESSION = 9 + else: + # Compresses about twice as fast as the default. + zlib.Z_DEFAULT_COMPRESSION = 1 + + # Manually align only when alignment is necessary. + # Python's zip implementation duplicates file comments in the central + # directory, whereas zipalign does not, so use zipalign for official builds. + fast_align = options.format == 'apk' and not options.best_compression + + native_libs = sorted(options.native_libs) + + # Include native libs in the depfile_deps since GN doesn't know about the + # dependencies when is_component_build=true. + depfile_deps = list(native_libs) + + # For targets that depend on static library APKs, dex paths are created by + # the static library's dexsplitter target and GN doesn't know about these + # paths. + if options.dex_file: + depfile_deps.append(options.dex_file) + + secondary_native_libs = [] + if options.secondary_native_libs: + secondary_native_libs = sorted(options.secondary_native_libs) + depfile_deps += secondary_native_libs + + if options.java_resources: + # Included via .build_config.json, so need to write it to depfile. + depfile_deps.extend(options.java_resources) + + assets = _ExpandPaths(options.assets) + uncompressed_assets = _ExpandPaths(options.uncompressed_assets) + + # Included via .build_config.json, so need to write it to depfile. + depfile_deps.extend(x[0] for x in assets) + depfile_deps.extend(x[0] for x in uncompressed_assets) + depfile_deps.append(options.resource_apk) + + # Bundle modules have a structure similar to APKs, except that resources + # are compiled in protobuf format (instead of binary xml), and that some + # files are located into different top-level directories, e.g.: + # AndroidManifest.xml -> manifest/AndroidManifest.xml + # classes.dex -> dex/classes.dex + # res/ -> res/ (unchanged) + # assets/ -> assets/ (unchanged) + # <other-file> -> root/<other-file> + # + # Hence, the following variables are used to control the location of files in + # the final archive. + if options.format == 'bundle-module': + apk_manifest_dir = 'manifest/' + apk_root_dir = 'root/' + apk_dex_dir = 'dex/' + else: + apk_manifest_dir = '' + apk_root_dir = '' + apk_dex_dir = '' + + def _GetAssetDetails(assets, uncompressed_assets, fast_align, allow_reads): + ret = _GetAssetsToAdd(assets, + fast_align, + disable_compression=False, + allow_reads=allow_reads) + ret.extend( + _GetAssetsToAdd(uncompressed_assets, + fast_align, + disable_compression=True, + allow_reads=allow_reads)) + return ret + + libs_to_add = _GetNativeLibrariesToAdd( + native_libs, options.android_abi, options.uncompress_shared_libraries, + fast_align, options.library_always_compress, options.library_renames) + if options.secondary_android_abi: + libs_to_add.extend( + _GetNativeLibrariesToAdd( + secondary_native_libs, options.secondary_android_abi, + options.uncompress_shared_libraries, fast_align, + options.library_always_compress, options.library_renames)) + + if options.expected_file: + # We compute expectations without reading the files. This allows us to check + # expectations for different targets by just generating their build_configs + # and not have to first generate all the actual files and all their + # dependencies (for example by just passing --only-verify-expectations). + asset_details = _GetAssetDetails(assets, + uncompressed_assets, + fast_align, + allow_reads=False) + + actual_data = _CreateExpectationsData(libs_to_add, asset_details) + diff_utils.CheckExpectations(actual_data, options) + + if options.only_verify_expectations: + if options.depfile: + build_utils.WriteDepfile(options.depfile, + options.actual_file, + inputs=depfile_deps) + return + + # If we are past this point, we are going to actually create the final apk so + # we should recompute asset details again but maybe perform some optimizations + # based on the size of the files on disk. + assets_to_add = _GetAssetDetails( + assets, uncompressed_assets, fast_align, allow_reads=True) + + # Targets generally do not depend on apks, so no need for only_if_changed. + with build_utils.AtomicOutput(options.output_apk, only_if_changed=False) as f: + with zipfile.ZipFile(options.resource_apk) as resource_apk, \ + zipfile.ZipFile(f, 'w') as out_apk: + + def add_to_zip(zip_path, data, compress=True, alignment=4): + zipalign.AddToZipHermetic( + out_apk, + zip_path, + data=data, + compress=compress, + alignment=0 if compress and not fast_align else alignment) + + def copy_resource(zipinfo, out_dir=''): + add_to_zip( + out_dir + zipinfo.filename, + resource_apk.read(zipinfo.filename), + compress=zipinfo.compress_type != zipfile.ZIP_STORED) + + # Make assets come before resources in order to maintain the same file + # ordering as GYP / aapt. http://crbug.com/561862 + resource_infos = resource_apk.infolist() + + # 1. AndroidManifest.xml + logging.debug('Adding AndroidManifest.xml') + copy_resource( + resource_apk.getinfo('AndroidManifest.xml'), out_dir=apk_manifest_dir) + + # 2. Assets + logging.debug('Adding assets/') + _AddFiles(out_apk, assets_to_add) + + # 3. Dex files + logging.debug('Adding classes.dex') + if options.dex_file: + with open(options.dex_file, 'rb') as dex_file_obj: + if options.dex_file.endswith('.dex'): + max_dex_number = 1 + # This is the case for incremental_install=true. + add_to_zip( + apk_dex_dir + 'classes.dex', + dex_file_obj.read(), + compress=not options.uncompress_dex) + else: + max_dex_number = 0 + with zipfile.ZipFile(dex_file_obj) as dex_zip: + for dex in (d for d in dex_zip.namelist() if d.endswith('.dex')): + max_dex_number += 1 + add_to_zip( + apk_dex_dir + dex, + dex_zip.read(dex), + compress=not options.uncompress_dex) + + if options.jdk_libs_dex_file: + with open(options.jdk_libs_dex_file, 'rb') as dex_file_obj: + add_to_zip( + apk_dex_dir + 'classes{}.dex'.format(max_dex_number + 1), + dex_file_obj.read(), + compress=not options.uncompress_dex) + + # 4. Native libraries. + logging.debug('Adding lib/') + _AddFiles(out_apk, libs_to_add) + + # Add a placeholder lib if the APK should be multi ABI but is missing libs + # for one of the ABIs. + native_lib_placeholders = options.native_lib_placeholders + secondary_native_lib_placeholders = ( + options.secondary_native_lib_placeholders) + if options.is_multi_abi: + if ((secondary_native_libs or secondary_native_lib_placeholders) + and not native_libs and not native_lib_placeholders): + native_lib_placeholders += ['libplaceholder.so'] + if ((native_libs or native_lib_placeholders) + and not secondary_native_libs + and not secondary_native_lib_placeholders): + secondary_native_lib_placeholders += ['libplaceholder.so'] + + # Add placeholder libs. + for name in sorted(native_lib_placeholders): + # Note: Empty libs files are ignored by md5check (can cause issues + # with stale builds when the only change is adding/removing + # placeholders). + apk_path = 'lib/%s/%s' % (options.android_abi, name) + add_to_zip(apk_path, '', alignment=0x1000) + + for name in sorted(secondary_native_lib_placeholders): + # Note: Empty libs files are ignored by md5check (can cause issues + # with stale builds when the only change is adding/removing + # placeholders). + apk_path = 'lib/%s/%s' % (options.secondary_android_abi, name) + add_to_zip(apk_path, '', alignment=0x1000) + + # 5. Resources + logging.debug('Adding res/') + for info in sorted(resource_infos, key=lambda i: i.filename): + if info.filename != 'AndroidManifest.xml': + copy_resource(info) + + # 6. Java resources that should be accessible via + # Class.getResourceAsStream(), in particular parts of Emma jar. + # Prebuilt jars may contain class files which we shouldn't include. + logging.debug('Adding Java resources') + for java_resource in options.java_resources: + with zipfile.ZipFile(java_resource, 'r') as java_resource_jar: + for apk_path in sorted(java_resource_jar.namelist()): + apk_path_lower = apk_path.lower() + + if apk_path_lower.startswith('meta-inf/'): + continue + if apk_path_lower.endswith('/'): + continue + if apk_path_lower.endswith('.class'): + continue + + add_to_zip(apk_root_dir + apk_path, + java_resource_jar.read(apk_path)) + + if options.format == 'apk': + zipalign_path = None if fast_align else options.zipalign_path + finalize_apk.FinalizeApk(options.apksigner_jar, + zipalign_path, + f.name, + f.name, + options.key_path, + options.key_passwd, + options.key_name, + int(options.min_sdk_version), + warnings_as_errors=options.warnings_as_errors) + logging.debug('Moving file into place') + + if options.depfile: + build_utils.WriteDepfile(options.depfile, + options.output_apk, + inputs=depfile_deps) + + +if __name__ == '__main__': + main(sys.argv[1:]) |