blob: fee7932e6dfc06483aa32fedab47f1b23467c5a2 [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
17import generate_v14_compatible_resources
18
19from util import build_utils
20from util import resource_utils
21
22
23def _ParseArgs(args):
24 """Parses command line options.
25
26 Returns:
27 An options object as from argparse.ArgumentParser.parse_args()
28 """
29 parser, input_opts, output_opts = resource_utils.ResourceArgsParser()
30
31 input_opts.add_argument('--resource-dirs',
32 default='[]',
33 help='A list of input directories containing resources '
34 'for this target.')
35
36 input_opts.add_argument(
37 '--shared-resources',
38 action='store_true',
39 help='Make resources shareable by generating an onResourcesLoaded() '
40 'method in the R.java source file.')
41
42 input_opts.add_argument('--custom-package',
43 help='Optional Java package for main R.java.')
44
45 input_opts.add_argument(
46 '--android-manifest',
47 help='Optional AndroidManifest.xml path. Only used to extract a package '
48 'name for R.java if a --custom-package is not provided.')
49
50 output_opts.add_argument(
51 '--resource-zip-out',
52 help='Path to a zip archive containing all resources from '
53 '--resource-dirs, merged into a single directory tree. This will '
54 'also include auto-generated v14-compatible resources unless '
55 '--v14-skip is used.')
56
57 output_opts.add_argument('--srcjar-out',
58 help='Path to .srcjar to contain the generated R.java.')
59
60 output_opts.add_argument('--r-text-out',
61 help='Path to store the generated R.txt file.')
62
63 input_opts.add_argument(
64 '--v14-skip',
65 action="store_true",
66 help='Do not generate nor verify v14 resources.')
67
68 options = parser.parse_args(args)
69
70 resource_utils.HandleCommonOptions(options)
71
72 options.resource_dirs = build_utils.ParseGnList(options.resource_dirs)
73
74 return options
75
76
77def _GenerateGlobs(pattern):
78 # This function processes the aapt ignore assets pattern into a list of globs
79 # to be used to exclude files on the python side. It removes the '!', which is
80 # used by aapt to mean 'not chatty' so it does not output if the file is
81 # ignored (we dont output anyways, so it is not required). This function does
82 # not handle the <dir> and <file> prefixes used by aapt and are assumed not to
83 # be included in the pattern string.
84 return pattern.replace('!', '').split(':')
85
86
87def _ZipResources(resource_dirs, zip_path, ignore_pattern):
88 # Python zipfile does not provide a way to replace a file (it just writes
89 # another file with the same name). So, first collect all the files to put
90 # in the zip (with proper overriding), and then zip them.
91 # ignore_pattern is a string of ':' delimited list of globs used to ignore
92 # files that should not be part of the final resource zip.
93 files_to_zip = dict()
94 globs = _GenerateGlobs(ignore_pattern)
95 for d in resource_dirs:
96 for root, _, files in os.walk(d):
97 for f in files:
98 archive_path = f
99 parent_dir = os.path.relpath(root, d)
100 if parent_dir != '.':
101 archive_path = os.path.join(parent_dir, f)
102 path = os.path.join(root, f)
103 if build_utils.MatchesGlob(archive_path, globs):
104 continue
105 files_to_zip[archive_path] = path
106 build_utils.DoZip(files_to_zip.iteritems(), zip_path)
107
108
109def _GenerateRTxt(options, dep_subdirs, gen_dir):
110 """Generate R.txt file.
111
112 Args:
113 options: The command-line options tuple.
114 dep_subdirs: List of directories containing extracted dependency resources.
115 gen_dir: Locates where the aapt-generated files will go. In particular
116 the output file is always generated as |{gen_dir}/R.txt|.
117 """
118 # NOTE: This uses aapt rather than aapt2 because 'aapt2 compile' does not
119 # support the --output-text-symbols option yet (https://crbug.com/820460).
120 package_command = [options.aapt_path,
121 'package',
122 '-m',
123 '-M', resource_utils.EMPTY_ANDROID_MANIFEST_PATH,
124 '--no-crunch',
125 '--auto-add-overlay',
126 '--no-version-vectors',
127 '-I', options.android_sdk_jar,
128 '--output-text-symbols', gen_dir,
129 '-J', gen_dir, # Required for R.txt generation.
130 '--ignore-assets', build_utils.AAPT_IGNORE_PATTERN]
131
132 # Adding all dependencies as sources is necessary for @type/foo references
133 # to symbols within dependencies to resolve. However, it has the side-effect
134 # that all Java symbols from dependencies are copied into the new R.java.
135 # E.g.: It enables an arguably incorrect usage of
136 # "mypackage.R.id.lib_symbol" where "libpackage.R.id.lib_symbol" would be
137 # more correct. This is just how Android works.
138 for d in dep_subdirs:
139 package_command += ['-S', d]
140
141 for d in options.resource_dirs:
142 package_command += ['-S', d]
143
144 # Only creates an R.txt
145 build_utils.CheckOutput(
146 package_command, print_stdout=False, print_stderr=False)
147
148
149def _GenerateResourcesZip(output_resource_zip, input_resource_dirs,
150 v14_skip, temp_dir):
151 """Generate a .resources.zip file fron a list of input resource dirs.
152
153 Args:
154 output_resource_zip: Path to the output .resources.zip file.
155 input_resource_dirs: A list of input resource directories.
156 v14_skip: If False, then v14-compatible resource will also be
157 generated in |{temp_dir}/v14| and added to the final zip.
158 temp_dir: Path to temporary directory.
159 """
160 if not v14_skip:
161 # Generate v14-compatible resources in temp_dir.
162 v14_dir = os.path.join(temp_dir, 'v14')
163 build_utils.MakeDirectory(v14_dir)
164
165 for resource_dir in input_resource_dirs:
166 generate_v14_compatible_resources.GenerateV14Resources(
167 resource_dir,
168 v14_dir)
169
170 input_resource_dirs.append(v14_dir)
171
172 _ZipResources(input_resource_dirs, output_resource_zip,
173 build_utils.AAPT_IGNORE_PATTERN)
174
175
176def _OnStaleMd5(options):
177 with resource_utils.BuildContext() as build:
178 if options.r_text_in:
179 r_txt_path = options.r_text_in
180 else:
181 # Extract dependencies to resolve @foo/type references into
182 # dependent packages.
183 dep_subdirs = resource_utils.ExtractDeps(options.dependencies_res_zips,
184 build.deps_dir)
185
186 _GenerateRTxt(options, dep_subdirs, build.gen_dir)
187 r_txt_path = build.r_txt_path
188
189 # 'aapt' doesn't generate any R.txt file if res/ was empty.
190 if not os.path.exists(r_txt_path):
191 build_utils.Touch(r_txt_path)
192
193 if options.r_text_out:
194 shutil.copyfile(r_txt_path, options.r_text_out)
195
196 if options.srcjar_out:
197 package = options.custom_package
198 if not package and options.android_manifest:
199 package = resource_utils.ExtractPackageFromManifest(
200 options.android_manifest)
201
202 # Don't create a .java file for the current resource target when no
203 # package name was provided (either by manifest or build rules).
204 if package:
205 # All resource IDs should be non-final here, but the
206 # onResourcesLoaded() method should only be generated if
207 # --shared-resources is used.
208 rjava_build_options = resource_utils.RJavaBuildOptions()
209 rjava_build_options.ExportAllResources()
210 rjava_build_options.ExportAllStyleables()
211 if options.shared_resources:
212 rjava_build_options.GenerateOnResourcesLoaded()
213
214 resource_utils.CreateRJavaFiles(
215 build.srcjar_dir, package, r_txt_path,
216 options.extra_res_packages,
217 options.extra_r_text_files,
218 rjava_build_options)
219
220 build_utils.ZipDir(options.srcjar_out, build.srcjar_dir)
221
222 if options.resource_zip_out:
223 _GenerateResourcesZip(options.resource_zip_out, options.resource_dirs,
224 options.v14_skip, build.temp_dir)
225
226
227def main(args):
228 args = build_utils.ExpandFileArgs(args)
229 options = _ParseArgs(args)
230
231 # Order of these must match order specified in GN so that the correct one
232 # appears first in the depfile.
233 possible_output_paths = [
234 options.resource_zip_out,
235 options.r_text_out,
236 options.srcjar_out,
237 ]
238 output_paths = [x for x in possible_output_paths if x]
239
240 # List python deps in input_strings rather than input_paths since the contents
241 # of them does not change what gets written to the depsfile.
242 input_strings = options.extra_res_packages + [
243 options.custom_package,
244 options.shared_resources,
245 options.v14_skip,
246 ]
247
248 possible_input_paths = [
249 options.aapt_path,
250 options.android_manifest,
251 options.android_sdk_jar,
252 ]
253 input_paths = [x for x in possible_input_paths if x]
254 input_paths.extend(options.dependencies_res_zips)
255 input_paths.extend(options.extra_r_text_files)
256
257 # Resource files aren't explicitly listed in GN. Listing them in the depfile
258 # ensures the target will be marked stale when resource files are removed.
259 depfile_deps = []
260 resource_names = []
261 for resource_dir in options.resource_dirs:
262 for resource_file in build_utils.FindInDirectory(resource_dir, '*'):
263 # Don't list the empty .keep file in depfile. Since it doesn't end up
264 # included in the .zip, it can lead to -w 'dupbuild=err' ninja errors
265 # if ever moved.
266 if not resource_file.endswith(os.path.join('empty', '.keep')):
267 input_paths.append(resource_file)
268 depfile_deps.append(resource_file)
269 resource_names.append(os.path.relpath(resource_file, resource_dir))
270
271 # Resource filenames matter to the output, so add them to strings as well.
272 # This matters if a file is renamed but not changed (http://crbug.com/597126).
273 input_strings.extend(sorted(resource_names))
274
275 build_utils.CallAndWriteDepfileIfStale(
276 lambda: _OnStaleMd5(options),
277 options,
278 input_paths=input_paths,
279 input_strings=input_strings,
280 output_paths=output_paths,
281 depfile_deps=depfile_deps)
282
283
284if __name__ == '__main__':
285 main(sys.argv[1:])