def main(): parser = optparse.OptionParser() parser.add_option('--zipalign-path', help='Path to the zipalign tool.') parser.add_option('--resource-packaged-apk-path', help='Base path to input .ap_s.') parser.add_option('--base-output-path', help='Path to output .apk, minus extension.') parser.add_option('--key-path', help='Path to keystore for signing.') parser.add_option('--key-passwd', help='Keystore password') parser.add_option('--key-name', help='Keystore name') parser.add_option('--densities', help='Comma separated list of densities finalize.') parser.add_option('--languages', help='GYP list of language splits to finalize.') options, _ = parser.parse_args() options.load_library_from_zip = 0 if options.densities: for density in options.densities.split(','): options.unsigned_apk_path = ("%s_%s" % (options.resource_packaged_apk_path, density)) options.final_apk_path = ("%s-density-%s.apk" % (options.base_output_path, density)) finalize_apk.FinalizeApk(options) if options.languages: for lang in build_utils.ParseGnList(options.languages): options.unsigned_apk_path = ("%s_%s" % (options.resource_packaged_apk_path, lang)) options.final_apk_path = ("%s-lang-%s.apk" % (options.base_output_path, lang)) finalize_apk.FinalizeApk(options)
def main(): parser = argparse.ArgumentParser( description='Merge a 32-bit APK into a 64-bit APK') # Using type=os.path.abspath converts file paths to absolute paths so that # we can change working directory without affecting these paths parser.add_argument('--apk_32bit', required=True, type=os.path.abspath) parser.add_argument('--apk_64bit', required=True, type=os.path.abspath) parser.add_argument('--out_apk', required=True, type=os.path.abspath) parser.add_argument('--zipalign_path', type=os.path.abspath) parser.add_argument('--keystore_path', required=True, type=os.path.abspath) parser.add_argument('--key_name', required=True) parser.add_argument('--key_password', required=True) group = parser.add_mutually_exclusive_group(required=True) group.add_argument('--component-build', action='store_true') group.add_argument('--shared_library') parser.add_argument( '--page-align-shared-libraries', action='store_true', help='Obsolete, but remains for backwards compatibility') parser.add_argument('--uncompress-shared-libraries', action='store_true') parser.add_argument('--debug', action='store_true') # This option shall only used in debug build, see http://crbug.com/631494. parser.add_argument('--ignore-classes-dex', action='store_true') parser.add_argument('--has-unwind-cfi', action='store_true', help='Specifies if the 32-bit apk has unwind_cfi file') args = parser.parse_args() if (args.zipalign_path is not None and not os.path.isfile(args.zipalign_path)): # If given an invalid path, fall back to try the default. logging.warning('zipalign path not found: %s', args.zipalign_path) logging.warning('falling back to: %s', DEFAULT_ZIPALIGN_PATH) args.zipalign_path = None if args.zipalign_path is None: # When no path given, try the default. if not os.path.isfile(DEFAULT_ZIPALIGN_PATH): return 'ERROR: zipalign path not found: %s' % DEFAULT_ZIPALIGN_PATH args.zipalign_path = DEFAULT_ZIPALIGN_PATH tmp_dir = tempfile.mkdtemp() tmp_dir_64 = os.path.join(tmp_dir, '64_bit') tmp_dir_32 = os.path.join(tmp_dir, '32_bit') tmp_apk = os.path.join(tmp_dir, 'tmp.apk') new_apk = args.out_apk try: MergeApk(args, tmp_apk, tmp_dir_32, tmp_dir_64) apksigner_path = os.path.join(os.path.dirname(args.zipalign_path), 'apksigner') finalize_apk.FinalizeApk(apksigner_path, args.zipalign_path, tmp_apk, new_apk, args.keystore_path, args.key_password, args.key_name) finally: shutil.rmtree(tmp_dir) return 0
def main(): parser = argparse.ArgumentParser( description='Merge a 32-bit APK into a 64-bit APK') # Using type=os.path.abspath converts file paths to absolute paths so that # we can change working directory without affecting these paths parser.add_argument('--apk_32bit', required=True, type=os.path.abspath) parser.add_argument('--apk_64bit', required=True, type=os.path.abspath) parser.add_argument('--out_apk', required=True, type=os.path.abspath) parser.add_argument('--zipalign_path', type=os.path.abspath) parser.add_argument('--keystore_path', required=True, type=os.path.abspath) parser.add_argument('--key_name', required=True) parser.add_argument('--key_password', required=True) parser.add_argument('--uncompress-shared-libraries', action='store_true') parser.add_argument('--bundle', action='store_true') parser.add_argument('--debug', action='store_true') # This option shall only used in debug build, see http://crbug.com/631494. parser.add_argument('--ignore-classes-dex', action='store_true') parser.add_argument('--has-unwind-cfi', action='store_true', help='Specifies if the 32-bit apk has unwind_cfi file') args = parser.parse_args() if (args.zipalign_path is not None and not os.path.isfile(args.zipalign_path)): # If given an invalid path, fall back to try the default. logging.warning('zipalign path not found: %s', args.zipalign_path) logging.warning('falling back to: %s', DEFAULT_ZIPALIGN_PATH) args.zipalign_path = None if args.zipalign_path is None: # When no path given, try the default. if not os.path.isfile(DEFAULT_ZIPALIGN_PATH): return 'ERROR: zipalign path not found: %s' % DEFAULT_ZIPALIGN_PATH args.zipalign_path = DEFAULT_ZIPALIGN_PATH tmp_dir = tempfile.mkdtemp() tmp_dir_64 = os.path.join(tmp_dir, '64_bit') tmp_dir_32 = os.path.join(tmp_dir, '32_bit') tmp_apk = os.path.join(tmp_dir, 'tmp.apk') new_apk = args.out_apk try: MergeApk(args, tmp_apk, tmp_dir_32, tmp_dir_64) apksigner_jar = os.path.join( os.path.dirname(args.zipalign_path), 'lib', 'apksigner.jar') # Official APKs are re-signed anyways, so it is not important to figure out # the correct min_sdk_version. Use 21 since that's the lowest supported # webview version. min_sdk_version = 21 finalize_apk.FinalizeApk(apksigner_jar, args.zipalign_path, tmp_apk, new_apk, args.keystore_path, args.key_password, args.key_name, min_sdk_version) finally: shutil.rmtree(tmp_dir) return 0
def on_stale_md5(): with tempfile.NamedTemporaryFile() as tmp_apk: tmp_file = tmp_apk.name with zipfile.ZipFile(options.resource_apk) as resource_apk, \ zipfile.ZipFile(tmp_file, 'w', zipfile.ZIP_DEFLATED) as out_apk: def copy_resource(zipinfo, out_dir=''): compress = zipinfo.compress_type != zipfile.ZIP_STORED build_utils.AddToZipHermetic(out_apk, out_dir + zipinfo.filename, data=resource_apk.read( zipinfo.filename), compress=compress) # 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 assert resource_infos[0].filename == 'AndroidManifest.xml' copy_resource(resource_infos[0], out_dir=apk_manifest_dir) # 2. Assets if options.write_asset_list: data = _CreateAssetsList( itertools.chain(assets, uncompressed_assets)) build_utils.AddToZipHermetic(out_apk, 'assets/assets_list', data=data) _AddAssets(out_apk, assets, disable_compression=False) _AddAssets(out_apk, uncompressed_assets, disable_compression=True) # 3. Dex files if options.dex_file and options.dex_file.endswith('.zip'): with zipfile.ZipFile(options.dex_file, 'r') as dex_zip: for dex in (d for d in dex_zip.namelist() if d.endswith('.dex')): build_utils.AddToZipHermetic( out_apk, apk_dex_dir + dex, data=dex_zip.read(dex)) elif options.dex_file: build_utils.AddToZipHermetic(out_apk, apk_dex_dir + 'classes.dex', src_path=options.dex_file) # 4. Native libraries. _AddNativeLibraries(out_apk, native_libs, options.android_abi, options.uncompress_shared_libraries) if options.secondary_android_abi: _AddNativeLibraries(out_apk, secondary_native_libs, options.secondary_android_abi, options.uncompress_shared_libraries) for name in sorted(options.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) build_utils.AddToZipHermetic(out_apk, apk_path, data='') for name in sorted(options.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) build_utils.AddToZipHermetic(out_apk, apk_path, data='') # 5. Resources for info in resource_infos[1:]: 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. for java_resource in options.java_resources: with zipfile.ZipFile(java_resource, 'r') as java_resource_jar: for apk_path in 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 build_utils.AddToZipHermetic( out_apk, apk_root_dir + apk_path, data=java_resource_jar.read(apk_path)) if options.apk_pak_info_path: _MergePakInfoFiles(options.apk_pak_info_path, pak_infos) if options.apk_res_info_path: _MergeResInfoFiles(options.apk_res_info_path, options.resource_apk) if options.format == 'apk': finalize_apk.FinalizeApk(options.apksigner_path, options.zipalign_path, tmp_file, options.output_apk, options.key_path, options.key_passwd, options.key_name) else: shutil.move(tmp_file, options.output_apk) tmp_apk.delete = False
def main(args): args = build_utils.ExpandFileArgs(args) options = _ParseArgs(args) 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, 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, 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) # 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 = '' # 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', zipfile.ZIP_DEFLATED) as out_apk: def copy_resource(zipinfo, out_dir=''): compress = zipinfo.compress_type != zipfile.ZIP_STORED build_utils.AddToZipHermetic(out_apk, out_dir + zipinfo.filename, data=resource_apk.read( zipinfo.filename), compress=compress) # 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 copy_resource(resource_apk.getinfo('AndroidManifest.xml'), out_dir=apk_manifest_dir) # 2. Assets if options.write_asset_list: data = _CreateAssetsList( itertools.chain(assets, uncompressed_assets)) build_utils.AddToZipHermetic(out_apk, 'assets/assets_list', data=data) _AddAssets(out_apk, assets, disable_compression=False) _AddAssets(out_apk, uncompressed_assets, disable_compression=True) # 3. Dex files if options.dex_file and options.dex_file.endswith('.zip'): with zipfile.ZipFile(options.dex_file, 'r') as dex_zip: for dex in (d for d in dex_zip.namelist() if d.endswith('.dex')): build_utils.AddToZipHermetic( out_apk, apk_dex_dir + dex, data=dex_zip.read(dex), compress=not options.uncompress_dex) elif options.dex_file: build_utils.AddToZipHermetic( out_apk, apk_dex_dir + 'classes.dex', src_path=options.dex_file, compress=not options.uncompress_dex) # 4. Native libraries. _AddNativeLibraries(out_apk, native_libs, options.android_abi, options.uncompress_shared_libraries) if options.secondary_android_abi: _AddNativeLibraries(out_apk, secondary_native_libs, options.secondary_android_abi, options.uncompress_shared_libraries) for name in sorted(options.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) build_utils.AddToZipHermetic(out_apk, apk_path, data='') for name in sorted(options.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) build_utils.AddToZipHermetic(out_apk, apk_path, data='') # 5. Resources 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. 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 build_utils.AddToZipHermetic( out_apk, apk_root_dir + apk_path, data=java_resource_jar.read(apk_path)) if options.format == 'apk': finalize_apk.FinalizeApk(options.apksigner_path, options.zipalign_path, f.name, f.name, options.key_path, options.key_passwd, options.key_name) if options.depfile: build_utils.WriteDepfile(options.depfile, options.output_apk, inputs=depfile_deps, add_pydeps=False)
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)