| #!/usr/bin/env python3 |
| # |
| # Copyright (c) 2012 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. |
| |
| """Process Android resource directories to generate .resources.zip and R.txt |
| files.""" |
| |
| import argparse |
| import os |
| import shutil |
| import sys |
| import zipfile |
| |
| from util import build_utils |
| from util import jar_info_utils |
| from util import md5_check |
| from util import resources_parser |
| from util import resource_utils |
| |
| |
| def _ParseArgs(args): |
| """Parses command line options. |
| |
| Returns: |
| An options object as from argparse.ArgumentParser.parse_args() |
| """ |
| parser = argparse.ArgumentParser(description=__doc__) |
| build_utils.AddDepfileOption(parser) |
| |
| parser.add_argument('--res-sources-path', |
| required=True, |
| help='Path to a list of input resources for this target.') |
| |
| parser.add_argument( |
| '--r-text-in', |
| help='Path to pre-existing R.txt. Its resource IDs override those found ' |
| 'in the generated R.txt when generating R.java.') |
| |
| parser.add_argument( |
| '--resource-zip-out', |
| help='Path to a zip archive containing all resources from ' |
| '--resource-dirs, merged into a single directory tree.') |
| |
| parser.add_argument('--r-text-out', |
| help='Path to store the generated R.txt file.') |
| |
| parser.add_argument('--strip-drawables', |
| action="store_true", |
| help='Remove drawables from the resources.') |
| |
| options = parser.parse_args(args) |
| |
| with open(options.res_sources_path) as f: |
| options.sources = f.read().splitlines() |
| options.resource_dirs = resource_utils.DeduceResourceDirsFromFileList( |
| options.sources) |
| |
| return options |
| |
| |
| def _CheckAllFilesListed(resource_files, resource_dirs): |
| resource_files = set(resource_files) |
| missing_files = [] |
| for path, _ in resource_utils.IterResourceFilesInDirectories(resource_dirs): |
| if path not in resource_files: |
| missing_files.append(path) |
| |
| if missing_files: |
| sys.stderr.write('Error: Found files not listed in the sources list of ' |
| 'the BUILD.gn target:\n') |
| for path in missing_files: |
| sys.stderr.write('{}\n'.format(path)) |
| sys.exit(1) |
| |
| |
| 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 _GenerateRTxt(options, r_txt_path): |
| """Generate R.txt file. |
| |
| Args: |
| options: The command-line options tuple. |
| r_txt_path: Locates where the R.txt file goes. |
| """ |
| ignore_pattern = resource_utils.AAPT_IGNORE_PATTERN |
| if options.strip_drawables: |
| ignore_pattern += ':*drawable*' |
| |
| resources_parser.RTxtGenerator(options.resource_dirs, |
| ignore_pattern).WriteRTxtFile(r_txt_path) |
| |
| |
| def _OnStaleMd5(options): |
| with resource_utils.BuildContext() as build: |
| if options.sources: |
| _CheckAllFilesListed(options.sources, options.resource_dirs) |
| if options.r_text_in: |
| r_txt_path = options.r_text_in |
| else: |
| _GenerateRTxt(options, build.r_txt_path) |
| r_txt_path = build.r_txt_path |
| |
| if options.r_text_out: |
| shutil.copyfile(r_txt_path, options.r_text_out) |
| |
| if options.resource_zip_out: |
| ignore_pattern = resource_utils.AAPT_IGNORE_PATTERN |
| if options.strip_drawables: |
| ignore_pattern += ':*drawable*' |
| _ZipResources(options.resource_dirs, options.resource_zip_out, |
| ignore_pattern) |
| |
| |
| def main(args): |
| args = build_utils.ExpandFileArgs(args) |
| options = _ParseArgs(args) |
| |
| # Order of these must match order specified in GN so that the correct one |
| # appears first in the depfile. |
| output_paths = [ |
| options.resource_zip_out, |
| options.resource_zip_out + '.info', |
| options.r_text_out, |
| ] |
| |
| input_paths = [options.res_sources_path] |
| if options.r_text_in: |
| input_paths += [options.r_text_in] |
| |
| # Resource files aren't explicitly listed in GN. Listing them in the depfile |
| # ensures the target will be marked stale when resource files are removed. |
| depfile_deps = [] |
| resource_names = [] |
| for resource_dir in options.resource_dirs: |
| for resource_file in build_utils.FindInDirectory(resource_dir, '*'): |
| # Don't list the empty .keep file in depfile. Since it doesn't end up |
| # included in the .zip, it can lead to -w 'dupbuild=err' ninja errors |
| # if ever moved. |
| if not resource_file.endswith(os.path.join('empty', '.keep')): |
| input_paths.append(resource_file) |
| depfile_deps.append(resource_file) |
| resource_names.append(os.path.relpath(resource_file, resource_dir)) |
| |
| # Resource filenames matter to the output, so add them to strings as well. |
| # This matters if a file is renamed but not changed (http://crbug.com/597126). |
| input_strings = sorted(resource_names) + [ |
| options.strip_drawables, |
| ] |
| |
| # Since android_resources targets like *__all_dfm_resources depend on java |
| # targets that they do not need (in reality it only needs the transitive |
| # resource targets that those java targets depend on), md5_check is used to |
| # prevent outputs from being re-written when real inputs have not changed. |
| md5_check.CallAndWriteDepfileIfStale(lambda: _OnStaleMd5(options), |
| options, |
| input_paths=input_paths, |
| input_strings=input_strings, |
| output_paths=output_paths, |
| depfile_deps=depfile_deps) |
| |
| |
| if __name__ == '__main__': |
| main(sys.argv[1:]) |