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.
7 """Process Android resources to generate R.java, and prepare for packaging.
9 This will crunch images and generate v14 compatible resources
10 (see generate_v14_compatible_resources.py).
20 import generate_v14_compatible_resources
22 from util import build_utils
26 """Parses command line options.
29 An options object as from optparse.OptionsParser.parse_args()
31 parser = optparse.OptionParser()
32 build_utils.AddDepfileOption(parser)
34 parser.add_option('--android-sdk', help='path to the Android SDK folder')
35 parser.add_option('--android-sdk-tools',
36 help='path to the Android SDK build tools folder')
37 parser.add_option('--non-constant-id', action='store_true')
39 parser.add_option('--android-manifest', help='AndroidManifest.xml path')
40 parser.add_option('--custom-package', help='Java package for R.java')
42 parser.add_option('--resource-dirs',
43 help='Directories containing resources of this target.')
44 parser.add_option('--dependencies-res-zips',
45 help='Resources from dependents.')
47 parser.add_option('--resource-zip-out',
48 help='Path for output zipped resources.')
50 parser.add_option('--R-dir',
51 help='directory to hold generated R.java.')
52 parser.add_option('--srcjar-out',
53 help='Path to srcjar to contain generated R.java.')
55 parser.add_option('--proguard-file',
56 help='Path to proguard.txt generated file')
61 help='Do not generate v14 resources. Instead, just verify that the '
62 'resources are already compatible with v14, i.e. they don\'t use '
63 'attributes that cause crashes on certain devices.')
66 '--extra-res-packages',
67 help='Additional package names to generate R.java files for')
68 # TODO(cjhopman): Actually use --extra-r-text-files. We currently include all
69 # the resources in all R.java files for a particular apk.
71 '--extra-r-text-files',
72 help='For each additional package, the R.txt file should contain a '
73 'list of resources to be included in the R.java file in the format '
77 '--all-resources-zip-out',
78 help='Path for output of all resources. This includes resources in '
81 parser.add_option('--stamp', help='File to touch on success')
83 (options, args) = parser.parse_args(args)
86 parser.error('No positional arguments should be given.')
88 # Check that required options have been provided.
93 'dependencies_res_zips',
97 build_utils.CheckOptions(options, parser, required=required_options)
99 if (options.R_dir is None) == (options.srcjar_out is None):
100 raise Exception('Exactly one of --R-dir or --srcjar-out must be specified.')
105 def CreateExtraRJavaFiles(r_dir, extra_packages):
106 java_files = build_utils.FindInDirectory(r_dir, "R.java")
107 if len(java_files) != 1:
109 r_java_file = java_files[0]
110 r_java_contents = open(r_java_file).read()
112 for package in extra_packages:
113 package_r_java_dir = os.path.join(r_dir, *package.split('.'))
114 build_utils.MakeDirectory(package_r_java_dir)
115 package_r_java_path = os.path.join(package_r_java_dir, 'R.java')
116 open(package_r_java_path, 'w').write(
117 re.sub(r'package [.\w]*;', 'package %s;' % package, r_java_contents))
118 # TODO(cjhopman): These extra package's R.java files should be filtered to
119 # only contain the resources listed in their R.txt files. At this point, we
120 # have already compiled those other libraries, so doing this would only
121 # affect how the code in this .apk target could refer to the resources.
124 def FilterCrunchStderr(stderr):
125 """Filters out lines from aapt crunch's stderr that can safely be ignored."""
127 for line in stderr.splitlines(True):
128 # Ignore this libpng warning, which is a known non-error condition.
129 # http://crbug.com/364355
130 if ('libpng warning: iCCP: Not recognizing known sRGB profile that has '
131 + 'been edited' in line):
133 filtered_lines.append(line)
134 return ''.join(filtered_lines)
137 def DidCrunchFail(returncode, stderr):
138 """Determines whether aapt crunch failed from its return code and output.
140 Because aapt's return code cannot be trusted, any output to stderr is
141 an indication that aapt has failed (http://crbug.com/314885).
143 return returncode != 0 or stderr
146 def ZipResources(resource_dirs, zip_path):
147 # Python zipfile does not provide a way to replace a file (it just writes
148 # another file with the same name). So, first collect all the files to put
149 # in the zip (with proper overriding), and then zip them.
150 files_to_zip = dict()
151 for d in resource_dirs:
152 for root, _, files in os.walk(d):
154 archive_path = os.path.join(os.path.relpath(root, d), f)
155 path = os.path.join(root, f)
156 files_to_zip[archive_path] = path
157 with zipfile.ZipFile(zip_path, 'w') as outzip:
158 for archive_path, path in files_to_zip.iteritems():
159 outzip.write(path, archive_path)
162 def CombineZips(zip_files, output_path):
163 # When packaging resources, if the top-level directories in the zip file are
164 # of the form 0, 1, ..., then each subdirectory will be passed to aapt as a
165 # resources directory. While some resources just clobber others (image files,
166 # etc), other resources (particularly .xml files) need to be more
167 # intelligently merged. That merging is left up to aapt.
168 with zipfile.ZipFile(output_path, 'w') as outzip:
169 for i, z in enumerate(zip_files):
170 with zipfile.ZipFile(z, 'r') as inzip:
171 for name in inzip.namelist():
172 new_name = '%d/%s' % (i, name)
173 outzip.writestr(new_name, inzip.read(name))
177 args = build_utils.ExpandFileArgs(sys.argv[1:])
179 options = ParseArgs(args)
180 android_jar = os.path.join(options.android_sdk, 'android.jar')
181 aapt = os.path.join(options.android_sdk_tools, 'aapt')
185 with build_utils.TempDir() as temp_dir:
186 deps_dir = os.path.join(temp_dir, 'deps')
187 build_utils.MakeDirectory(deps_dir)
188 v14_dir = os.path.join(temp_dir, 'v14')
189 build_utils.MakeDirectory(v14_dir)
191 gen_dir = os.path.join(temp_dir, 'gen')
192 build_utils.MakeDirectory(gen_dir)
194 input_resource_dirs = build_utils.ParseGypList(options.resource_dirs)
196 for resource_dir in input_resource_dirs:
197 generate_v14_compatible_resources.GenerateV14Resources(
200 options.v14_verify_only)
202 dep_zips = build_utils.ParseGypList(options.dependencies_res_zips)
203 input_files += dep_zips
206 subdir = os.path.join(deps_dir, os.path.basename(z))
207 if os.path.exists(subdir):
208 raise Exception('Resource zip name conflict: ' + os.path.basename(z))
209 build_utils.ExtractAll(z, path=subdir)
210 dep_subdirs.append(subdir)
212 # Generate R.java. This R.java contains non-final constants and is used only
213 # while compiling the library jar (e.g. chromium_content.jar). When building
214 # an apk, a new R.java file with the correct resource -> ID mappings will be
215 # generated by merging the resources from all libraries and the main apk
217 package_command = [aapt,
220 '-M', options.android_manifest,
221 '--auto-add-overlay',
223 '--output-text-symbols', gen_dir,
225 '--ignore-assets', build_utils.AAPT_IGNORE_PATTERN]
227 for d in input_resource_dirs:
228 package_command += ['-S', d]
230 for d in dep_subdirs:
231 package_command += ['-S', d]
233 if options.non_constant_id:
234 package_command.append('--non-constant-id')
235 if options.custom_package:
236 package_command += ['--custom-package', options.custom_package]
237 if options.proguard_file:
238 package_command += ['-G', options.proguard_file]
239 build_utils.CheckOutput(package_command, print_stderr=False)
241 if options.extra_res_packages:
242 CreateExtraRJavaFiles(
244 build_utils.ParseGypList(options.extra_res_packages))
246 # This is the list of directories with resources to put in the final .zip
247 # file. The order of these is important so that crunched/v14 resources
248 # override the normal ones.
249 zip_resource_dirs = input_resource_dirs + [v14_dir]
251 base_crunch_dir = os.path.join(temp_dir, 'crunch')
253 # Crunch image resources. This shrinks png files and is necessary for
254 # 9-patch images to display correctly. 'aapt crunch' accepts only a single
255 # directory at a time and deletes everything in the output directory.
256 for idx, d in enumerate(input_resource_dirs):
257 crunch_dir = os.path.join(base_crunch_dir, str(idx))
258 build_utils.MakeDirectory(crunch_dir)
259 zip_resource_dirs.append(crunch_dir)
264 '--ignore-assets', build_utils.AAPT_IGNORE_PATTERN]
265 build_utils.CheckOutput(aapt_cmd, stderr_filter=FilterCrunchStderr,
266 fail_func=DidCrunchFail)
268 ZipResources(zip_resource_dirs, options.resource_zip_out)
270 if options.all_resources_zip_out:
271 CombineZips([options.resource_zip_out] + dep_zips,
272 options.all_resources_zip_out)
275 build_utils.DeleteDirectory(options.R_dir)
276 shutil.copytree(gen_dir, options.R_dir)
278 build_utils.ZipDir(options.srcjar_out, gen_dir)
281 input_files += build_utils.GetPythonDependencies()
282 build_utils.WriteDepfile(options.depfile, input_files)
285 build_utils.Touch(options.stamp)
288 if __name__ == '__main__':