blob: cec232701fd202d23f5578d1612d7adb527050e0 [file] [log] [blame]
David 'Digit' Turner66940042018-03-23 09:09:421#!/usr/bin/env python
2#
3# Copyright (c) 2012 The Chromium Authors. All rights reserved.
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, R.txt and
8.srcjar files."""
9
10import argparse
11import collections
12import os
13import re
14import shutil
15import sys
16
David 'Digit' Turner66940042018-03-23 09:09:4217from util import build_utils
18from util import resource_utils
19
Andrew Grieve14f62b42018-06-26 15:16:0420_AAPT_IGNORE_PATTERN = ':'.join([
21 'OWNERS', # Allow OWNERS files within res/
22 '*.py', # PRESUBMIT.py sometimes exist.
23 '*.pyc',
24 '*~', # Some editors create these as temp files.
25 '.*', # Never makes sense to include dot(files/dirs).
26 '*.d.stamp', # Ignore stamp files
27 ])
David 'Digit' Turner66940042018-03-23 09:09:4228
29def _ParseArgs(args):
30 """Parses command line options.
31
32 Returns:
33 An options object as from argparse.ArgumentParser.parse_args()
34 """
35 parser, input_opts, output_opts = resource_utils.ResourceArgsParser()
36
Andrew Grievef2023252019-04-16 01:03:4137 input_opts.add_argument(
38 '--aapt-path', required=True, help='Path to the Android aapt tool')
39
David 'Digit' Turner66940042018-03-23 09:09:4240 input_opts.add_argument('--resource-dirs',
41 default='[]',
42 help='A list of input directories containing resources '
43 'for this target.')
44
45 input_opts.add_argument(
46 '--shared-resources',
47 action='store_true',
48 help='Make resources shareable by generating an onResourcesLoaded() '
49 'method in the R.java source file.')
50
51 input_opts.add_argument('--custom-package',
52 help='Optional Java package for main R.java.')
53
54 input_opts.add_argument(
55 '--android-manifest',
56 help='Optional AndroidManifest.xml path. Only used to extract a package '
57 'name for R.java if a --custom-package is not provided.')
58
59 output_opts.add_argument(
60 '--resource-zip-out',
61 help='Path to a zip archive containing all resources from '
Patrick Noland897c5502019-06-12 20:15:1562 '--resource-dirs, merged into a single directory tree.')
David 'Digit' Turner66940042018-03-23 09:09:4263
64 output_opts.add_argument('--srcjar-out',
65 help='Path to .srcjar to contain the generated R.java.')
66
67 output_opts.add_argument('--r-text-out',
68 help='Path to store the generated R.txt file.')
69
70 input_opts.add_argument(
Sam Maierf2a34752019-03-18 14:39:3671 '--strip-drawables',
72 action="store_true",
73 help='Remove drawables from the resources.')
74
David 'Digit' Turner66940042018-03-23 09:09:4275 options = parser.parse_args(args)
76
77 resource_utils.HandleCommonOptions(options)
78
79 options.resource_dirs = build_utils.ParseGnList(options.resource_dirs)
80
81 return options
82
83
84def _GenerateGlobs(pattern):
85 # This function processes the aapt ignore assets pattern into a list of globs
86 # to be used to exclude files on the python side. It removes the '!', which is
87 # used by aapt to mean 'not chatty' so it does not output if the file is
88 # ignored (we dont output anyways, so it is not required). This function does
89 # not handle the <dir> and <file> prefixes used by aapt and are assumed not to
90 # be included in the pattern string.
91 return pattern.replace('!', '').split(':')
92
93
94def _ZipResources(resource_dirs, zip_path, ignore_pattern):
95 # Python zipfile does not provide a way to replace a file (it just writes
96 # another file with the same name). So, first collect all the files to put
97 # in the zip (with proper overriding), and then zip them.
98 # ignore_pattern is a string of ':' delimited list of globs used to ignore
99 # files that should not be part of the final resource zip.
100 files_to_zip = dict()
Peter Wen568647d2018-04-19 17:57:10101 files_to_zip_without_generated = dict()
David 'Digit' Turner66940042018-03-23 09:09:42102 globs = _GenerateGlobs(ignore_pattern)
103 for d in resource_dirs:
104 for root, _, files in os.walk(d):
105 for f in files:
106 archive_path = f
107 parent_dir = os.path.relpath(root, d)
108 if parent_dir != '.':
109 archive_path = os.path.join(parent_dir, f)
110 path = os.path.join(root, f)
111 if build_utils.MatchesGlob(archive_path, globs):
112 continue
Peter Wen568647d2018-04-19 17:57:10113 # We want the original resource dirs in the .info file rather than the
114 # generated overridden path.
115 if not path.startswith('/tmp'):
116 files_to_zip_without_generated[archive_path] = path
David 'Digit' Turner66940042018-03-23 09:09:42117 files_to_zip[archive_path] = path
Peter Wen568647d2018-04-19 17:57:10118 resource_utils.CreateResourceInfoFile(files_to_zip_without_generated,
119 zip_path)
David 'Digit' Turner66940042018-03-23 09:09:42120 build_utils.DoZip(files_to_zip.iteritems(), zip_path)
121
122
123def _GenerateRTxt(options, dep_subdirs, gen_dir):
124 """Generate R.txt file.
125
126 Args:
127 options: The command-line options tuple.
128 dep_subdirs: List of directories containing extracted dependency resources.
129 gen_dir: Locates where the aapt-generated files will go. In particular
130 the output file is always generated as |{gen_dir}/R.txt|.
131 """
132 # NOTE: This uses aapt rather than aapt2 because 'aapt2 compile' does not
133 # support the --output-text-symbols option yet (https://crbug.com/820460).
134 package_command = [options.aapt_path,
135 'package',
136 '-m',
137 '-M', resource_utils.EMPTY_ANDROID_MANIFEST_PATH,
138 '--no-crunch',
139 '--auto-add-overlay',
140 '--no-version-vectors',
John Budorick400c7082018-06-21 16:34:52141 ]
Tibor Goldschwendt0a9439a2018-09-06 21:01:04142 for j in options.include_resources:
John Budorick400c7082018-06-21 16:34:52143 package_command += ['-I', j]
144
Sam Maierf2a34752019-03-18 14:39:36145 ignore_pattern = _AAPT_IGNORE_PATTERN
146 if options.strip_drawables:
147 ignore_pattern += ':*drawable*'
John Budorick400c7082018-06-21 16:34:52148 package_command += [
Sam Maierf2a34752019-03-18 14:39:36149 '--output-text-symbols',
150 gen_dir,
151 '-J',
152 gen_dir, # Required for R.txt generation.
153 '--ignore-assets',
154 ignore_pattern
155 ]
David 'Digit' Turner66940042018-03-23 09:09:42156
157 # Adding all dependencies as sources is necessary for @type/foo references
158 # to symbols within dependencies to resolve. However, it has the side-effect
159 # that all Java symbols from dependencies are copied into the new R.java.
160 # E.g.: It enables an arguably incorrect usage of
161 # "mypackage.R.id.lib_symbol" where "libpackage.R.id.lib_symbol" would be
162 # more correct. This is just how Android works.
163 for d in dep_subdirs:
164 package_command += ['-S', d]
165
166 for d in options.resource_dirs:
167 package_command += ['-S', d]
168
169 # Only creates an R.txt
170 build_utils.CheckOutput(
171 package_command, print_stdout=False, print_stderr=False)
172
173
Patrick Noland897c5502019-06-12 20:15:15174def _GenerateResourcesZip(output_resource_zip, input_resource_dirs,
175 strip_drawables):
David 'Digit' Turner66940042018-03-23 09:09:42176 """Generate a .resources.zip file fron a list of input resource dirs.
177
178 Args:
179 output_resource_zip: Path to the output .resources.zip file.
180 input_resource_dirs: A list of input resource directories.
David 'Digit' Turner66940042018-03-23 09:09:42181 """
David 'Digit' Turner66940042018-03-23 09:09:42182
Sam Maierf2a34752019-03-18 14:39:36183 ignore_pattern = _AAPT_IGNORE_PATTERN
184 if strip_drawables:
185 ignore_pattern += ':*drawable*'
186 _ZipResources(input_resource_dirs, output_resource_zip, ignore_pattern)
David 'Digit' Turner66940042018-03-23 09:09:42187
188
189def _OnStaleMd5(options):
190 with resource_utils.BuildContext() as build:
191 if options.r_text_in:
192 r_txt_path = options.r_text_in
193 else:
194 # Extract dependencies to resolve @foo/type references into
195 # dependent packages.
196 dep_subdirs = resource_utils.ExtractDeps(options.dependencies_res_zips,
197 build.deps_dir)
198
199 _GenerateRTxt(options, dep_subdirs, build.gen_dir)
200 r_txt_path = build.r_txt_path
201
202 # 'aapt' doesn't generate any R.txt file if res/ was empty.
203 if not os.path.exists(r_txt_path):
204 build_utils.Touch(r_txt_path)
205
206 if options.r_text_out:
207 shutil.copyfile(r_txt_path, options.r_text_out)
208
209 if options.srcjar_out:
210 package = options.custom_package
211 if not package and options.android_manifest:
212 package = resource_utils.ExtractPackageFromManifest(
213 options.android_manifest)
214
215 # Don't create a .java file for the current resource target when no
216 # package name was provided (either by manifest or build rules).
217 if package:
218 # All resource IDs should be non-final here, but the
219 # onResourcesLoaded() method should only be generated if
220 # --shared-resources is used.
221 rjava_build_options = resource_utils.RJavaBuildOptions()
222 rjava_build_options.ExportAllResources()
223 rjava_build_options.ExportAllStyleables()
224 if options.shared_resources:
225 rjava_build_options.GenerateOnResourcesLoaded()
226
227 resource_utils.CreateRJavaFiles(
Sam Maier97564df2019-05-22 22:09:30228 build.srcjar_dir, package, r_txt_path, options.extra_res_packages,
Sam Maier9585f5e2019-05-24 21:47:16229 options.extra_r_text_files, rjava_build_options, options.srcjar_out)
David 'Digit' Turner66940042018-03-23 09:09:42230
231 build_utils.ZipDir(options.srcjar_out, build.srcjar_dir)
232
233 if options.resource_zip_out:
234 _GenerateResourcesZip(options.resource_zip_out, options.resource_dirs,
Patrick Noland897c5502019-06-12 20:15:15235 options.strip_drawables)
David 'Digit' Turner66940042018-03-23 09:09:42236
237
238def main(args):
239 args = build_utils.ExpandFileArgs(args)
240 options = _ParseArgs(args)
241
242 # Order of these must match order specified in GN so that the correct one
243 # appears first in the depfile.
244 possible_output_paths = [
245 options.resource_zip_out,
246 options.r_text_out,
247 options.srcjar_out,
248 ]
249 output_paths = [x for x in possible_output_paths if x]
250
251 # List python deps in input_strings rather than input_paths since the contents
252 # of them does not change what gets written to the depsfile.
253 input_strings = options.extra_res_packages + [
Sam Maierf2a34752019-03-18 14:39:36254 options.custom_package,
255 options.shared_resources,
Sam Maierf2a34752019-03-18 14:39:36256 options.strip_drawables,
David 'Digit' Turner66940042018-03-23 09:09:42257 ]
258
259 possible_input_paths = [
260 options.aapt_path,
261 options.android_manifest,
David 'Digit' Turner66940042018-03-23 09:09:42262 ]
Tibor Goldschwendt0a9439a2018-09-06 21:01:04263 possible_input_paths += options.include_resources
David 'Digit' Turner66940042018-03-23 09:09:42264 input_paths = [x for x in possible_input_paths if x]
265 input_paths.extend(options.dependencies_res_zips)
266 input_paths.extend(options.extra_r_text_files)
267
268 # Resource files aren't explicitly listed in GN. Listing them in the depfile
269 # ensures the target will be marked stale when resource files are removed.
270 depfile_deps = []
271 resource_names = []
272 for resource_dir in options.resource_dirs:
273 for resource_file in build_utils.FindInDirectory(resource_dir, '*'):
274 # Don't list the empty .keep file in depfile. Since it doesn't end up
275 # included in the .zip, it can lead to -w 'dupbuild=err' ninja errors
276 # if ever moved.
277 if not resource_file.endswith(os.path.join('empty', '.keep')):
278 input_paths.append(resource_file)
279 depfile_deps.append(resource_file)
280 resource_names.append(os.path.relpath(resource_file, resource_dir))
281
282 # Resource filenames matter to the output, so add them to strings as well.
283 # This matters if a file is renamed but not changed (http://crbug.com/597126).
284 input_strings.extend(sorted(resource_names))
285
286 build_utils.CallAndWriteDepfileIfStale(
287 lambda: _OnStaleMd5(options),
288 options,
289 input_paths=input_paths,
290 input_strings=input_strings,
291 output_paths=output_paths,
David 'Digit' Turnerf24ca382018-08-07 07:12:36292 depfile_deps=depfile_deps,
293 add_pydeps=False)
David 'Digit' Turner66940042018-03-23 09:09:42294
295
296if __name__ == '__main__':
297 main(sys.argv[1:])