blob: 888d16e87f18e421202b782a4bb54d41a1978d65 [file] [log] [blame]
Andrew Grieve7dd4aaf2021-04-27 21:47:081#!/usr/bin/env python3
David 'Digit' Turner66940042018-03-23 09:09:422#
Avi Drissman75198092022-09-08 20:33:383# Copyright 2012 The Chromium Authors
David 'Digit' Turner66940042018-03-23 09:09:424# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
Mohamed Heikald5806582020-09-08 18:07:417"""Process Android resource directories to generate .resources.zip and R.txt
8files."""
David 'Digit' Turner66940042018-03-23 09:09:429
10import argparse
David 'Digit' Turner66940042018-03-23 09:09:4211import os
David 'Digit' Turner66940042018-03-23 09:09:4212import shutil
13import sys
Mohamed Heikal4a770c72019-11-26 03:28:4214import zipfile
David 'Digit' Turner66940042018-03-23 09:09:4215
David 'Digit' Turner66940042018-03-23 09:09:4216from util import build_utils
Andrew Grievecdfb7142020-01-03 02:40:4517from util import jar_info_utils
Peter Wenb94b58e2021-02-25 22:51:5018from util import md5_check
Mohamed Heikal0015f8b2020-01-30 15:57:2719from util import resources_parser
David 'Digit' Turner66940042018-03-23 09:09:4220from util import resource_utils
Andrew Grieve0cdf15d2023-03-31 02:25:5721import action_helpers # build_utils adds //build to sys.path.
Andrew Grieve40071f42023-03-31 20:31:2922import zip_helpers
David 'Digit' Turner66940042018-03-23 09:09:4223
24
25def _ParseArgs(args):
26 """Parses command line options.
27
28 Returns:
29 An options object as from argparse.ArgumentParser.parse_args()
30 """
Mohamed Heikal31a56782021-02-01 23:32:0931 parser = argparse.ArgumentParser(description=__doc__)
Andrew Grieve0cdf15d2023-03-31 02:25:5732 action_helpers.add_depfile_arg(parser)
David 'Digit' Turner66940042018-03-23 09:09:4233
Mohamed Heikal31a56782021-02-01 23:32:0934 parser.add_argument('--res-sources-path',
35 required=True,
36 help='Path to a list of input resources for this target.')
David 'Digit' Turner66940042018-03-23 09:09:4237
Mohamed Heikal31a56782021-02-01 23:32:0938 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 Heikal67f15e42021-06-11 14:58:1344 '--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' Turner66940042018-03-23 09:09:4250 '--resource-zip-out',
51 help='Path to a zip archive containing all resources from '
Patrick Noland897c5502019-06-12 20:15:1552 '--resource-dirs, merged into a single directory tree.')
David 'Digit' Turner66940042018-03-23 09:09:4253
Mohamed Heikal31a56782021-02-01 23:32:0954 parser.add_argument('--r-text-out',
55 help='Path to store the generated R.txt file.')
David 'Digit' Turner66940042018-03-23 09:09:4256
Mohamed Heikal31a56782021-02-01 23:32:0957 parser.add_argument('--strip-drawables',
58 action="store_true",
59 help='Remove drawables from the resources.')
Sam Maierf2a34752019-03-18 14:39:3660
David 'Digit' Turner66940042018-03-23 09:09:4261 options = parser.parse_args(args)
62
Andrew Grievea622c092025-06-23 15:29:1463 with open(options.res_sources_path, encoding='utf-8') as f:
Andrew Grieve412eb612020-04-24 21:25:4164 options.sources = f.read().splitlines()
65 options.resource_dirs = resource_utils.DeduceResourceDirsFromFileList(
Mohamed Heikaledddd482019-11-22 16:12:1766 options.sources)
David 'Digit' Turner66940042018-03-23 09:09:4267
68 return options
69
70
Mohamed Heikaledddd482019-11-22 16:12:1771def _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' Turner66940042018-03-23 09:09:4284
85
86def _ZipResources(resource_dirs, zip_path, ignore_pattern):
David 'Digit' Turner66940042018-03-23 09:09:4287 # 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 Grieve6395d352019-12-17 19:44:2389 files_to_zip = []
90 path_info = resource_utils.ResourceInfoFile()
Mohamed Heikal4a770c72019-11-26 03:28:4291 for index, resource_dir in enumerate(resource_dirs):
Andrew Grievecdfb7142020-01-03 02:40:4592 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 Heikal4a770c72019-11-26 03:28:4299 for path, archive_path in resource_utils.IterResourceFilesInDirectories(
100 [resource_dir], ignore_pattern):
Andrew Grievecdfb7142020-01-03 02:40:45101 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 Grieve6395d352019-12-17 19:44:23107
Mohamed Heikal4a770c72019-11-26 03:28:42108 resource_dir_name = os.path.basename(resource_dir)
109 archive_path = '{}_{}/{}'.format(index, resource_dir_name, archive_path)
Andrew Grieve6395d352019-12-17 19:44:23110 files_to_zip.append((archive_path, path))
111
112 path_info.Write(zip_path + '.info')
113
Mohamed Heikal4a770c72019-11-26 03:28:42114 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 Grieve40071f42023-03-31 20:31:29121 zip_helpers.add_files_to_zip(files_to_zip, z)
David 'Digit' Turner66940042018-03-23 09:09:42122
123
Mohamed Heikal31a56782021-02-01 23:32:09124def _GenerateRTxt(options, r_txt_path):
David 'Digit' Turner66940042018-03-23 09:09:42125 """Generate R.txt file.
126
127 Args:
128 options: The command-line options tuple.
Mohamed Heikal31a56782021-02-01 23:32:09129 r_txt_path: Locates where the R.txt file goes.
David 'Digit' Turner66940042018-03-23 09:09:42130 """
Mohamed Heikaledddd482019-11-22 16:12:17131 ignore_pattern = resource_utils.AAPT_IGNORE_PATTERN
Sam Maierf2a34752019-03-18 14:39:36132 if options.strip_drawables:
133 ignore_pattern += ':*drawable*'
David 'Digit' Turner66940042018-03-23 09:09:42134
Mohamed Heikal31a56782021-02-01 23:32:09135 resources_parser.RTxtGenerator(options.resource_dirs,
136 ignore_pattern).WriteRTxtFile(r_txt_path)
David 'Digit' Turner66940042018-03-23 09:09:42137
138
Peter Wenb94b58e2021-02-25 22:51:50139def _OnStaleMd5(options):
David 'Digit' Turner66940042018-03-23 09:09:42140 with resource_utils.BuildContext() as build:
Mohamed Heikal67f15e42021-06-11 14:58:13141 if options.sources and not options.allow_missing_resources:
Mohamed Heikaledddd482019-11-22 16:12:17142 _CheckAllFilesListed(options.sources, options.resource_dirs)
David 'Digit' Turner66940042018-03-23 09:09:42143 if options.r_text_in:
144 r_txt_path = options.r_text_in
145 else:
Mohamed Heikal31a56782021-02-01 23:32:09146 _GenerateRTxt(options, build.r_txt_path)
David 'Digit' Turner66940042018-03-23 09:09:42147 r_txt_path = build.r_txt_path
148
David 'Digit' Turner66940042018-03-23 09:09:42149 if options.r_text_out:
150 shutil.copyfile(r_txt_path, options.r_text_out)
151
David 'Digit' Turner66940042018-03-23 09:09:42152 if options.resource_zip_out:
Andrew Grieve6395d352019-12-17 19:44:23153 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' Turner66940042018-03-23 09:09:42158
Peter Wenb94b58e2021-02-25 22:51:50159
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) + [
Peter Wenb94b58e2021-02-25 22:51:50193 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' Turner66940042018-03-23 09:09:42206
207
208if __name__ == '__main__':
209 main(sys.argv[1:])