def _XMLTransform(source_pairs, outputs_zip): """ Apply module codes to input sources. """ with build_utils.TempDir() as temp_dir: path_info = resource_utils.ResourceInfoFile() for source, module in source_pairs: with codecs.open(source, 'r', 'utf-8') as f: xml_content = f.read() loaded_module = _ImportModuleByPath(module) if not xml_content or not loaded_module: continue root = ET.XML(xml_content) result = loaded_module._ProcessXML(root) output = ET.tostring(result, encoding='utf-8', xml_declaration=True) # Parse output path # For simplicity, we assume input path will always has java/res in it if not (relpath := _UnderJavaRes(os.path.abspath(source))): raise Exception('input file %s is not under java/res' % source) # resource_overlay doesn't seem to work from android_generated_resources relpath = _AddBravePrefix(relpath) output_filename = os.path.join(temp_dir, relpath) parent_dir = os.path.dirname(output_filename) build_utils.MakeDirectory(parent_dir) _ProcessFile(output_filename, output) path_info.AddMapping(relpath, source) path_info.Write(outputs_zip + '.info') build_utils.ZipDir(outputs_zip, temp_dir)
def _ProcessFiles(processor, input_filenames, inputs_base_dir, outputs_zip): with build_utils.TempDir() as temp_dir: path_info = resource_utils.ResourceInfoFile() for input_filename in input_filenames: relpath = os.path.relpath(os.path.abspath(input_filename), os.path.abspath(inputs_base_dir)) if relpath.startswith(os.pardir): raise Exception( 'input file %s is not contained in inputs base dir %s' % (input_filename, inputs_base_dir)) output_filename = os.path.join(temp_dir, relpath) parent_dir = os.path.dirname(output_filename) build_utils.MakeDirectory(parent_dir) _ProcessFile(processor, input_filename, output_filename) path_info.AddMapping(relpath, input_filename) path_info.Write(outputs_zip + '.info') build_utils.ZipDir(outputs_zip, temp_dir)
def _ZipResources(resource_dirs, zip_path, ignore_pattern): # ignore_pattern is a string of ':' delimited list of globs used to ignore # files that should not be part of the final resource zip. files_to_zip = [] path_info = resource_utils.ResourceInfoFile() for index, resource_dir in enumerate(resource_dirs): attributed_aar = None if not resource_dir.startswith('..'): aar_source_info_path = os.path.join(os.path.dirname(resource_dir), 'source.info') if os.path.exists(aar_source_info_path): attributed_aar = jar_info_utils.ReadAarSourceInfo( aar_source_info_path) for path, archive_path in resource_utils.IterResourceFilesInDirectories( [resource_dir], ignore_pattern): attributed_path = path if attributed_aar: attributed_path = os.path.join(attributed_aar, 'res', path[len(resource_dir) + 1:]) # Use the non-prefixed archive_path in the .info file. path_info.AddMapping(archive_path, attributed_path) resource_dir_name = os.path.basename(resource_dir) archive_path = '{}_{}/{}'.format(index, resource_dir_name, archive_path) files_to_zip.append((archive_path, path)) path_info.Write(zip_path + '.info') with zipfile.ZipFile(zip_path, 'w') as z: # This magic comment signals to resource_utils.ExtractDeps that this zip is # not just the contents of a single res dir, without the encapsulating res/ # (like the outputs of android_generated_resources targets), but instead has # the contents of possibly multiple res/ dirs each within an encapsulating # directory within the zip. z.comment = resource_utils.MULTIPLE_RES_MAGIC_STRING build_utils.DoZip(files_to_zip, z)
def _PackageApk(options, build): """Compile and link resources with aapt2. Args: options: The command-line options. build: BuildContext object. Returns: The manifest package name for the APK. """ logging.debug('Extracting resource .zips') dep_subdirs = resource_utils.ExtractDeps(options.dependencies_res_zips, build.deps_dir) logging.debug('Applying locale transformations') path_info = resource_utils.ResourceInfoFile() if options.support_zh_hk: _DuplicateZhResources(dep_subdirs, path_info) _RenameLocaleResourceDirs(dep_subdirs, path_info) _RemoveUnwantedLocalizedStrings(dep_subdirs, options) # Create a function that selects which resource files should be packaged # into the final output. Any file that does not pass the predicate will # be removed below. logging.debug('Applying file-based blacklist') keep_predicate = _CreateKeepPredicate( options.resource_blacklist_regex, options.resource_blacklist_exceptions) png_paths = [] for directory in dep_subdirs: for f in _IterFiles(directory): if not keep_predicate(f): os.remove(f) elif f.endswith('.png'): png_paths.append((f, directory)) if png_paths and options.png_to_webp: logging.debug('Converting png->webp') _ConvertToWebP(options.webp_binary, png_paths, path_info) logging.debug('Applying drawable transformations') for directory in dep_subdirs: _MoveImagesToNonMdpiFolders(directory, path_info) _RemoveImageExtensions(directory, path_info) link_command = [ options.aapt2_path, 'link', '--auto-add-overlay', '--no-version-vectors', # Set SDK versions in case they are not set in the Android manifest. '--min-sdk-version', options.min_sdk_version, '--target-sdk-version', options.target_sdk_version, ] for j in options.include_resources: link_command += ['-I', j] if options.version_code: link_command += ['--version-code', options.version_code] if options.version_name: link_command += ['--version-name', options.version_name] if options.proguard_file: link_command += ['--proguard', build.proguard_path] link_command += ['--proguard-minimal-keep-rules'] if options.proguard_file_main_dex: link_command += ['--proguard-main-dex', build.proguard_main_dex_path] if options.emit_ids_out: link_command += ['--emit-ids', build.emit_ids_path] if options.r_text_in: shutil.copyfile(options.r_text_in, build.r_txt_path) else: link_command += ['--output-text-symbols', build.r_txt_path] # Note: only one of --proto-format, --shared-lib or --app-as-shared-lib # can be used with recent versions of aapt2. if options.shared_resources: link_command.append('--shared-lib') if options.no_xml_namespaces: link_command.append('--no-xml-namespaces') if options.package_id: link_command += [ '--package-id', hex(options.package_id), '--allow-reserved-package-id', ] fixed_manifest, desired_manifest_package_name = _FixManifest( options, build.temp_dir) if options.rename_manifest_package: desired_manifest_package_name = options.rename_manifest_package if options.android_manifest_expected: _VerifyManifest(fixed_manifest, options.android_manifest_expected, options.android_manifest_normalized, options.android_manifest_expectations_failure_file) link_command += [ '--manifest', fixed_manifest, '--rename-manifest-package', desired_manifest_package_name ] # Creates a .zip with AndroidManifest.xml, resources.arsc, res/* # Also creates R.txt if options.use_resource_ids_path: _CreateStableIdsFile(options.use_resource_ids_path, build.stable_ids_path, desired_manifest_package_name) link_command += ['--stable-ids', build.stable_ids_path] partials = _CompileDeps(options.aapt2_path, dep_subdirs, build.temp_dir) for partial in partials: link_command += ['-R', partial] # We always create a binary arsc file first, then convert to proto, so flags # such as --shared-lib can be supported. arsc_path = build.arsc_path if arsc_path is None: _, arsc_path = tempfile.mkstmp() link_command += ['-o', build.arsc_path] logging.debug('Starting: aapt2 link') link_proc = subprocess.Popen(link_command) # Create .res.info file in parallel. _CreateResourceInfoFile(path_info, build.info_path, options.dependencies_res_zips) logging.debug('Created .res.info file') exit_code = link_proc.wait() logging.debug('Finished: aapt2 link') if exit_code: raise subprocess.CalledProcessError(exit_code, link_command) if options.proguard_file and (options.shared_resources or options.app_as_shared_lib): # Make sure the R class associated with the manifest package does not have # its onResourcesLoaded method obfuscated or removed, so that the framework # can call it in the case where the APK is being loaded as a library. with open(build.proguard_path, 'a') as proguard_file: keep_rule = ''' -keep class {package}.R {{ public static void onResourcesLoaded(int); }} '''.format(package=desired_manifest_package_name) proguard_file.write(textwrap.dedent(keep_rule)) logging.debug('Running aapt2 convert') build_utils.CheckOutput([ options.aapt2_path, 'convert', '--output-format', 'proto', '-o', build.proto_path, build.arsc_path ]) # Workaround for b/147674078. This is only needed for WebLayer and does not # affect WebView usage, since WebView does not used dynamic attributes. if options.shared_resources: logging.debug('Hardcoding dynamic attributes') _HardcodeSharedLibraryDynamicAttributes(build.proto_path) build_utils.CheckOutput([ options.aapt2_path, 'convert', '--output-format', 'binary', '-o', build.arsc_path, build.proto_path ]) if build.arsc_path is None: os.remove(arsc_path) if options.optimized_proto_path: _OptimizeApk(build.optimized_proto_path, options, build.temp_dir, build.proto_path, build.r_txt_path) elif options.optimized_arsc_path: _OptimizeApk(build.optimized_arsc_path, options, build.temp_dir, build.arsc_path, build.r_txt_path) return desired_manifest_package_name
def _PackageApk(options, build): """Compile and link resources with aapt2. Args: options: The command-line options. build: BuildContext object. Returns: The manifest package name for the APK. """ logging.debug('Extracting resource .zips') dep_subdirs = [] dep_subdir_overlay_set = set() for dependency_res_zip in options.dependencies_res_zips: extracted_dep_subdirs = resource_utils.ExtractDeps( [dependency_res_zip], build.deps_dir) dep_subdirs += extracted_dep_subdirs if dependency_res_zip in options.dependencies_res_zip_overlays: dep_subdir_overlay_set.update(extracted_dep_subdirs) logging.debug('Applying locale transformations') path_info = resource_utils.ResourceInfoFile() _RenameLocaleResourceDirs(dep_subdirs, path_info) logging.debug('Applying file-based exclusions') keep_predicate = _CreateKeepPredicate( options.resource_exclusion_regex, options.resource_exclusion_exceptions) png_paths = _FilterResourceFiles(dep_subdirs, keep_predicate) if options.locale_allowlist or options.shared_resources_allowlist_locales: logging.debug('Applying locale-based string exclusions') _RemoveUnwantedLocalizedStrings(dep_subdirs, options) if png_paths and options.png_to_webp: logging.debug('Converting png->webp') _ConvertToWebP(options.webp_binary, png_paths, path_info, options.webp_cache_dir) logging.debug('Applying drawable transformations') for directory in dep_subdirs: _MoveImagesToNonMdpiFolders(directory, path_info) _RemoveImageExtensions(directory, path_info) logging.debug('Running aapt2 compile') exclusion_rules = [x.split(':', 1) for x in options.values_filter_rules] partials = _CompileDeps(options.aapt2_path, dep_subdirs, dep_subdir_overlay_set, build.temp_dir, exclusion_rules) link_command = [ options.aapt2_path, 'link', '--auto-add-overlay', '--no-version-vectors', # Set SDK versions in case they are not set in the Android manifest. '--min-sdk-version', options.min_sdk_version, '--target-sdk-version', options.target_sdk_version, '--output-text-symbols', build.r_txt_path, ] for j in options.include_resources: link_command += ['-I', j] if options.version_code: link_command += ['--version-code', options.version_code] if options.version_name: link_command += ['--version-name', options.version_name] if options.proguard_file: link_command += ['--proguard', build.proguard_path] link_command += ['--proguard-minimal-keep-rules'] if options.proguard_file_main_dex: link_command += ['--proguard-main-dex', build.proguard_main_dex_path] if options.emit_ids_out: link_command += ['--emit-ids', build.emit_ids_path] # Note: only one of --proto-format, --shared-lib or --app-as-shared-lib # can be used with recent versions of aapt2. if options.shared_resources: link_command.append('--shared-lib') if options.no_xml_namespaces: link_command.append('--no-xml-namespaces') if options.package_id: link_command += [ '--package-id', hex(options.package_id), '--allow-reserved-package-id', ] fixed_manifest, desired_manifest_package_name, fixed_manifest_package = ( _FixManifest(options, build.temp_dir)) if options.rename_manifest_package: desired_manifest_package_name = options.rename_manifest_package link_command += [ '--manifest', fixed_manifest, '--rename-manifest-package', desired_manifest_package_name ] # Creates a .zip with AndroidManifest.xml, resources.arsc, res/* # Also creates R.txt if options.use_resource_ids_path: _CreateStableIdsFile(options.use_resource_ids_path, build.stable_ids_path, fixed_manifest_package) link_command += ['--stable-ids', build.stable_ids_path] link_command += partials # We always create a binary arsc file first, then convert to proto, so flags # such as --shared-lib can be supported. link_command += ['-o', build.arsc_path] logging.debug('Starting: aapt2 link') link_proc = subprocess.Popen(link_command) # Create .res.info file in parallel. _CreateResourceInfoFile(path_info, build.info_path, options.dependencies_res_zips) logging.debug('Created .res.info file') exit_code = link_proc.wait() logging.debug('Finished: aapt2 link') if exit_code: raise subprocess.CalledProcessError(exit_code, link_command) if options.proguard_file and (options.shared_resources or options.app_as_shared_lib): # Make sure the R class associated with the manifest package does not have # its onResourcesLoaded method obfuscated or removed, so that the framework # can call it in the case where the APK is being loaded as a library. with open(build.proguard_path, 'a') as proguard_file: keep_rule = ''' -keep,allowoptimization class {package}.R {{ public static void onResourcesLoaded(int); }} '''.format(package=desired_manifest_package_name) proguard_file.write(textwrap.dedent(keep_rule)) logging.debug('Running aapt2 convert') build_utils.CheckOutput([ options.aapt2_path, 'convert', '--output-format', 'proto', '-o', build.proto_path, build.arsc_path ]) # Workaround for b/147674078. This is only needed for WebLayer and does not # affect WebView usage, since WebView does not used dynamic attributes. if options.shared_resources: logging.debug('Hardcoding dynamic attributes') protoresources.HardcodeSharedLibraryDynamicAttributes( build.proto_path, options.is_bundle_module, options.shared_resources_allowlist) build_utils.CheckOutput([ options.aapt2_path, 'convert', '--output-format', 'binary', '-o', build.arsc_path, build.proto_path ]) return desired_manifest_package_name