Andrew Grieve | 7dd4aaf | 2021-04-27 21:47:08 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
David 'Digit' Turner | 6694004 | 2018-03-23 09:09:42 | [diff] [blame] | 2 | # |
Avi Drissman | 7519809 | 2022-09-08 20:33:38 | [diff] [blame] | 3 | # Copyright 2012 The Chromium Authors |
David 'Digit' Turner | 6694004 | 2018-03-23 09:09:42 | [diff] [blame] | 4 | # Use of this source code is governed by a BSD-style license that can be |
| 5 | # found in the LICENSE file. |
| 6 | |
Mohamed Heikal | d580658 | 2020-09-08 18:07:41 | [diff] [blame] | 7 | """Process Android resource directories to generate .resources.zip and R.txt |
| 8 | files.""" |
David 'Digit' Turner | 6694004 | 2018-03-23 09:09:42 | [diff] [blame] | 9 | |
| 10 | import argparse |
David 'Digit' Turner | 6694004 | 2018-03-23 09:09:42 | [diff] [blame] | 11 | import os |
David 'Digit' Turner | 6694004 | 2018-03-23 09:09:42 | [diff] [blame] | 12 | import shutil |
| 13 | import sys |
Mohamed Heikal | 4a770c7 | 2019-11-26 03:28:42 | [diff] [blame] | 14 | import zipfile |
David 'Digit' Turner | 6694004 | 2018-03-23 09:09:42 | [diff] [blame] | 15 | |
David 'Digit' Turner | 6694004 | 2018-03-23 09:09:42 | [diff] [blame] | 16 | from util import build_utils |
Andrew Grieve | cdfb714 | 2020-01-03 02:40:45 | [diff] [blame] | 17 | from util import jar_info_utils |
Peter Wen | b94b58e | 2021-02-25 22:51:50 | [diff] [blame] | 18 | from util import md5_check |
Mohamed Heikal | 0015f8b | 2020-01-30 15:57:27 | [diff] [blame] | 19 | from util import resources_parser |
David 'Digit' Turner | 6694004 | 2018-03-23 09:09:42 | [diff] [blame] | 20 | from util import resource_utils |
Andrew Grieve | 0cdf15d | 2023-03-31 02:25:57 | [diff] [blame] | 21 | import action_helpers # build_utils adds //build to sys.path. |
Andrew Grieve | 40071f4 | 2023-03-31 20:31:29 | [diff] [blame] | 22 | import zip_helpers |
David 'Digit' Turner | 6694004 | 2018-03-23 09:09:42 | [diff] [blame] | 23 | |
| 24 | |
| 25 | def _ParseArgs(args): |
| 26 | """Parses command line options. |
| 27 | |
| 28 | Returns: |
| 29 | An options object as from argparse.ArgumentParser.parse_args() |
| 30 | """ |
Mohamed Heikal | 31a5678 | 2021-02-01 23:32:09 | [diff] [blame] | 31 | parser = argparse.ArgumentParser(description=__doc__) |
Andrew Grieve | 0cdf15d | 2023-03-31 02:25:57 | [diff] [blame] | 32 | action_helpers.add_depfile_arg(parser) |
David 'Digit' Turner | 6694004 | 2018-03-23 09:09:42 | [diff] [blame] | 33 | |
Mohamed Heikal | 31a5678 | 2021-02-01 23:32:09 | [diff] [blame] | 34 | parser.add_argument('--res-sources-path', |
| 35 | required=True, |
| 36 | help='Path to a list of input resources for this target.') |
David 'Digit' Turner | 6694004 | 2018-03-23 09:09:42 | [diff] [blame] | 37 | |
Mohamed Heikal | 31a5678 | 2021-02-01 23:32:09 | [diff] [blame] | 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( |
Mohamed Heikal | 67f15e4 | 2021-06-11 14:58:13 | [diff] [blame] | 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( |
David 'Digit' Turner | 6694004 | 2018-03-23 09:09:42 | [diff] [blame] | 50 | '--resource-zip-out', |
| 51 | help='Path to a zip archive containing all resources from ' |
Patrick Noland | 897c550 | 2019-06-12 20:15:15 | [diff] [blame] | 52 | '--resource-dirs, merged into a single directory tree.') |
David 'Digit' Turner | 6694004 | 2018-03-23 09:09:42 | [diff] [blame] | 53 | |
Mohamed Heikal | 31a5678 | 2021-02-01 23:32:09 | [diff] [blame] | 54 | parser.add_argument('--r-text-out', |
| 55 | help='Path to store the generated R.txt file.') |
David 'Digit' Turner | 6694004 | 2018-03-23 09:09:42 | [diff] [blame] | 56 | |
Mohamed Heikal | 31a5678 | 2021-02-01 23:32:09 | [diff] [blame] | 57 | parser.add_argument('--strip-drawables', |
| 58 | action="store_true", |
| 59 | help='Remove drawables from the resources.') |
Sam Maier | f2a3475 | 2019-03-18 14:39:36 | [diff] [blame] | 60 | |
David 'Digit' Turner | 6694004 | 2018-03-23 09:09:42 | [diff] [blame] | 61 | options = parser.parse_args(args) |
| 62 | |
Andrew Grieve | a622c09 | 2025-06-23 15:29:14 | [diff] [blame] | 63 | with open(options.res_sources_path, encoding='utf-8') as f: |
Andrew Grieve | 412eb61 | 2020-04-24 21:25:41 | [diff] [blame] | 64 | options.sources = f.read().splitlines() |
| 65 | options.resource_dirs = resource_utils.DeduceResourceDirsFromFileList( |
Mohamed Heikal | edddd48 | 2019-11-22 16:12:17 | [diff] [blame] | 66 | options.sources) |
David 'Digit' Turner | 6694004 | 2018-03-23 09:09:42 | [diff] [blame] | 67 | |
| 68 | return options |
| 69 | |
| 70 | |
Mohamed Heikal | edddd48 | 2019-11-22 16:12:17 | [diff] [blame] | 71 | def _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) |
David 'Digit' Turner | 6694004 | 2018-03-23 09:09:42 | [diff] [blame] | 84 | |
| 85 | |
| 86 | def _ZipResources(resource_dirs, zip_path, ignore_pattern): |
David 'Digit' Turner | 6694004 | 2018-03-23 09:09:42 | [diff] [blame] | 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. |
Andrew Grieve | 6395d35 | 2019-12-17 19:44:23 | [diff] [blame] | 89 | files_to_zip = [] |
| 90 | path_info = resource_utils.ResourceInfoFile() |
Mohamed Heikal | 4a770c7 | 2019-11-26 03:28:42 | [diff] [blame] | 91 | for index, resource_dir in enumerate(resource_dirs): |
Andrew Grieve | cdfb714 | 2020-01-03 02:40:45 | [diff] [blame] | 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 | |
Mohamed Heikal | 4a770c7 | 2019-11-26 03:28:42 | [diff] [blame] | 99 | for path, archive_path in resource_utils.IterResourceFilesInDirectories( |
| 100 | [resource_dir], ignore_pattern): |
Andrew Grieve | cdfb714 | 2020-01-03 02:40:45 | [diff] [blame] | 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) |
Andrew Grieve | 6395d35 | 2019-12-17 19:44:23 | [diff] [blame] | 107 | |
Mohamed Heikal | 4a770c7 | 2019-11-26 03:28:42 | [diff] [blame] | 108 | resource_dir_name = os.path.basename(resource_dir) |
| 109 | archive_path = '{}_{}/{}'.format(index, resource_dir_name, archive_path) |
Andrew Grieve | 6395d35 | 2019-12-17 19:44:23 | [diff] [blame] | 110 | files_to_zip.append((archive_path, path)) |
| 111 | |
| 112 | path_info.Write(zip_path + '.info') |
| 113 | |
Mohamed Heikal | 4a770c7 | 2019-11-26 03:28:42 | [diff] [blame] | 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 |
Andrew Grieve | 40071f4 | 2023-03-31 20:31:29 | [diff] [blame] | 121 | zip_helpers.add_files_to_zip(files_to_zip, z) |
David 'Digit' Turner | 6694004 | 2018-03-23 09:09:42 | [diff] [blame] | 122 | |
| 123 | |
Mohamed Heikal | 31a5678 | 2021-02-01 23:32:09 | [diff] [blame] | 124 | def _GenerateRTxt(options, r_txt_path): |
David 'Digit' Turner | 6694004 | 2018-03-23 09:09:42 | [diff] [blame] | 125 | """Generate R.txt file. |
| 126 | |
| 127 | Args: |
| 128 | options: The command-line options tuple. |
Mohamed Heikal | 31a5678 | 2021-02-01 23:32:09 | [diff] [blame] | 129 | r_txt_path: Locates where the R.txt file goes. |
David 'Digit' Turner | 6694004 | 2018-03-23 09:09:42 | [diff] [blame] | 130 | """ |
Mohamed Heikal | edddd48 | 2019-11-22 16:12:17 | [diff] [blame] | 131 | ignore_pattern = resource_utils.AAPT_IGNORE_PATTERN |
Sam Maier | f2a3475 | 2019-03-18 14:39:36 | [diff] [blame] | 132 | if options.strip_drawables: |
| 133 | ignore_pattern += ':*drawable*' |
David 'Digit' Turner | 6694004 | 2018-03-23 09:09:42 | [diff] [blame] | 134 | |
Mohamed Heikal | 31a5678 | 2021-02-01 23:32:09 | [diff] [blame] | 135 | resources_parser.RTxtGenerator(options.resource_dirs, |
| 136 | ignore_pattern).WriteRTxtFile(r_txt_path) |
David 'Digit' Turner | 6694004 | 2018-03-23 09:09:42 | [diff] [blame] | 137 | |
| 138 | |
Peter Wen | b94b58e | 2021-02-25 22:51:50 | [diff] [blame] | 139 | def _OnStaleMd5(options): |
David 'Digit' Turner | 6694004 | 2018-03-23 09:09:42 | [diff] [blame] | 140 | with resource_utils.BuildContext() as build: |
Mohamed Heikal | 67f15e4 | 2021-06-11 14:58:13 | [diff] [blame] | 141 | if options.sources and not options.allow_missing_resources: |
Mohamed Heikal | edddd48 | 2019-11-22 16:12:17 | [diff] [blame] | 142 | _CheckAllFilesListed(options.sources, options.resource_dirs) |
David 'Digit' Turner | 6694004 | 2018-03-23 09:09:42 | [diff] [blame] | 143 | if options.r_text_in: |
| 144 | r_txt_path = options.r_text_in |
| 145 | else: |
Mohamed Heikal | 31a5678 | 2021-02-01 23:32:09 | [diff] [blame] | 146 | _GenerateRTxt(options, build.r_txt_path) |
David 'Digit' Turner | 6694004 | 2018-03-23 09:09:42 | [diff] [blame] | 147 | r_txt_path = build.r_txt_path |
| 148 | |
David 'Digit' Turner | 6694004 | 2018-03-23 09:09:42 | [diff] [blame] | 149 | if options.r_text_out: |
| 150 | shutil.copyfile(r_txt_path, options.r_text_out) |
| 151 | |
David 'Digit' Turner | 6694004 | 2018-03-23 09:09:42 | [diff] [blame] | 152 | if options.resource_zip_out: |
Andrew Grieve | 6395d35 | 2019-12-17 19:44:23 | [diff] [blame] | 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) |
David 'Digit' Turner | 6694004 | 2018-03-23 09:09:42 | [diff] [blame] | 158 | |
Peter Wen | b94b58e | 2021-02-25 22:51:50 | [diff] [blame] | 159 | |
| 160 | def 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) + [ |
Peter Wen | b94b58e | 2021-02-25 22:51:50 | [diff] [blame] | 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) |
David 'Digit' Turner | 6694004 | 2018-03-23 09:09:42 | [diff] [blame] | 206 | |
| 207 | |
| 208 | if __name__ == '__main__': |
| 209 | main(sys.argv[1:]) |