1#!/usr/bin/env python3 2# 3# Copyright 2012 The Chromium Authors 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6 7"""Process Android resource directories to generate .resources.zip and R.txt 8files.""" 9 10import argparse 11import os 12import shutil 13import sys 14import zipfile 15 16from util import build_utils 17from util import jar_info_utils 18from util import md5_check 19from util import resources_parser 20from util import resource_utils 21import action_helpers # build_utils adds //build to sys.path. 22import zip_helpers 23 24 25def _ParseArgs(args): 26 """Parses command line options. 27 28 Returns: 29 An options object as from argparse.ArgumentParser.parse_args() 30 """ 31 parser = argparse.ArgumentParser(description=__doc__) 32 action_helpers.add_depfile_arg(parser) 33 34 parser.add_argument('--res-sources-path', 35 required=True, 36 help='Path to a list of input resources for this target.') 37 38 parser.add_argument( 39 '--r-text-in', 40 help='Path to pre-existing R.txt. Its resource IDs override those found ' 41 'in the generated R.txt when generating R.java.') 42 43 parser.add_argument( 44 '--allow-missing-resources', 45 action='store_true', 46 help='Do not fail if some resources exist in the res/ dir but are not ' 47 'listed in the sources.') 48 49 parser.add_argument( 50 '--resource-zip-out', 51 help='Path to a zip archive containing all resources from ' 52 '--resource-dirs, merged into a single directory tree.') 53 54 parser.add_argument('--r-text-out', 55 help='Path to store the generated R.txt file.') 56 57 parser.add_argument('--strip-drawables', 58 action="store_true", 59 help='Remove drawables from the resources.') 60 61 options = parser.parse_args(args) 62 63 with open(options.res_sources_path) as f: 64 options.sources = f.read().splitlines() 65 options.resource_dirs = resource_utils.DeduceResourceDirsFromFileList( 66 options.sources) 67 68 return options 69 70 71def _CheckAllFilesListed(resource_files, resource_dirs): 72 resource_files = set(resource_files) 73 missing_files = [] 74 for path, _ in resource_utils.IterResourceFilesInDirectories(resource_dirs): 75 if path not in resource_files: 76 missing_files.append(path) 77 78 if missing_files: 79 sys.stderr.write('Error: Found files not listed in the sources list of ' 80 'the BUILD.gn target:\n') 81 for path in missing_files: 82 sys.stderr.write('{}\n'.format(path)) 83 sys.exit(1) 84 85 86def _ZipResources(resource_dirs, zip_path, ignore_pattern): 87 # ignore_pattern is a string of ':' delimited list of globs used to ignore 88 # files that should not be part of the final resource zip. 89 files_to_zip = [] 90 path_info = resource_utils.ResourceInfoFile() 91 for index, resource_dir in enumerate(resource_dirs): 92 attributed_aar = None 93 if not resource_dir.startswith('..'): 94 aar_source_info_path = os.path.join( 95 os.path.dirname(resource_dir), 'source.info') 96 if os.path.exists(aar_source_info_path): 97 attributed_aar = jar_info_utils.ReadAarSourceInfo(aar_source_info_path) 98 99 for path, archive_path in resource_utils.IterResourceFilesInDirectories( 100 [resource_dir], ignore_pattern): 101 attributed_path = path 102 if attributed_aar: 103 attributed_path = os.path.join(attributed_aar, 'res', 104 path[len(resource_dir) + 1:]) 105 # Use the non-prefixed archive_path in the .info file. 106 path_info.AddMapping(archive_path, attributed_path) 107 108 resource_dir_name = os.path.basename(resource_dir) 109 archive_path = '{}_{}/{}'.format(index, resource_dir_name, archive_path) 110 files_to_zip.append((archive_path, path)) 111 112 path_info.Write(zip_path + '.info') 113 114 with zipfile.ZipFile(zip_path, 'w') as z: 115 # This magic comment signals to resource_utils.ExtractDeps that this zip is 116 # not just the contents of a single res dir, without the encapsulating res/ 117 # (like the outputs of android_generated_resources targets), but instead has 118 # the contents of possibly multiple res/ dirs each within an encapsulating 119 # directory within the zip. 120 z.comment = resource_utils.MULTIPLE_RES_MAGIC_STRING 121 zip_helpers.add_files_to_zip(files_to_zip, z) 122 123 124def _GenerateRTxt(options, r_txt_path): 125 """Generate R.txt file. 126 127 Args: 128 options: The command-line options tuple. 129 r_txt_path: Locates where the R.txt file goes. 130 """ 131 ignore_pattern = resource_utils.AAPT_IGNORE_PATTERN 132 if options.strip_drawables: 133 ignore_pattern += ':*drawable*' 134 135 resources_parser.RTxtGenerator(options.resource_dirs, 136 ignore_pattern).WriteRTxtFile(r_txt_path) 137 138 139def _OnStaleMd5(options): 140 with resource_utils.BuildContext() as build: 141 if options.sources and not options.allow_missing_resources: 142 _CheckAllFilesListed(options.sources, options.resource_dirs) 143 if options.r_text_in: 144 r_txt_path = options.r_text_in 145 else: 146 _GenerateRTxt(options, build.r_txt_path) 147 r_txt_path = build.r_txt_path 148 149 if options.r_text_out: 150 shutil.copyfile(r_txt_path, options.r_text_out) 151 152 if options.resource_zip_out: 153 ignore_pattern = resource_utils.AAPT_IGNORE_PATTERN 154 if options.strip_drawables: 155 ignore_pattern += ':*drawable*' 156 _ZipResources(options.resource_dirs, options.resource_zip_out, 157 ignore_pattern) 158 159 160def main(args): 161 args = build_utils.ExpandFileArgs(args) 162 options = _ParseArgs(args) 163 164 # Order of these must match order specified in GN so that the correct one 165 # appears first in the depfile. 166 output_paths = [ 167 options.resource_zip_out, 168 options.resource_zip_out + '.info', 169 options.r_text_out, 170 ] 171 172 input_paths = [options.res_sources_path] 173 if options.r_text_in: 174 input_paths += [options.r_text_in] 175 176 # Resource files aren't explicitly listed in GN. Listing them in the depfile 177 # ensures the target will be marked stale when resource files are removed. 178 depfile_deps = [] 179 resource_names = [] 180 for resource_dir in options.resource_dirs: 181 for resource_file in build_utils.FindInDirectory(resource_dir, '*'): 182 # Don't list the empty .keep file in depfile. Since it doesn't end up 183 # included in the .zip, it can lead to -w 'dupbuild=err' ninja errors 184 # if ever moved. 185 if not resource_file.endswith(os.path.join('empty', '.keep')): 186 input_paths.append(resource_file) 187 depfile_deps.append(resource_file) 188 resource_names.append(os.path.relpath(resource_file, resource_dir)) 189 190 # Resource filenames matter to the output, so add them to strings as well. 191 # This matters if a file is renamed but not changed (http://crbug.com/597126). 192 input_strings = sorted(resource_names) + [ 193 options.strip_drawables, 194 ] 195 196 # Since android_resources targets like *__all_dfm_resources depend on java 197 # targets that they do not need (in reality it only needs the transitive 198 # resource targets that those java targets depend on), md5_check is used to 199 # prevent outputs from being re-written when real inputs have not changed. 200 md5_check.CallAndWriteDepfileIfStale(lambda: _OnStaleMd5(options), 201 options, 202 input_paths=input_paths, 203 input_strings=input_strings, 204 output_paths=output_paths, 205 depfile_deps=depfile_deps) 206 207 208if __name__ == '__main__': 209 main(sys.argv[1:]) 210