OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # | 2 # |
3 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 4 # Use of this source code is governed by a BSD-style license that can be |
5 # found in the LICENSE file. | 5 # found in the LICENSE file. |
6 | 6 |
7 """Process Android resources to generate R.java, and prepare for packaging. | 7 """Process Android resources to generate R.java, and prepare for packaging. |
8 | 8 |
9 This will crunch images and generate v14 compatible resources | 9 This will crunch images and generate v14 compatible resources |
10 (see generate_v14_compatible_resources.py). | 10 (see generate_v14_compatible_resources.py). |
11 """ | 11 """ |
12 | 12 |
13 import codecs | 13 import codecs |
14 import collections | 14 import collections |
15 import optparse | 15 import optparse |
16 import os | 16 import os |
17 import re | 17 import re |
18 import shutil | 18 import shutil |
19 import sys | 19 import sys |
| 20 import xml.etree.ElementTree |
20 | 21 |
21 import generate_v14_compatible_resources | 22 import generate_v14_compatible_resources |
22 | 23 |
23 from util import build_utils | 24 from util import build_utils |
24 | 25 |
25 # Import jinja2 from third_party/jinja2 | 26 # Import jinja2 from third_party/jinja2 |
26 sys.path.insert(1, | 27 sys.path.insert(1, |
27 os.path.join(os.path.dirname(__file__), '../../../third_party')) | 28 os.path.join(os.path.dirname(__file__), '../../../third_party')) |
28 from jinja2 import Template # pylint: disable=F0401 | 29 from jinja2 import Template # pylint: disable=F0401 |
29 | 30 |
(...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
136 | 137 |
137 if options.extra_r_text_files: | 138 if options.extra_r_text_files: |
138 options.extra_r_text_files = ( | 139 options.extra_r_text_files = ( |
139 build_utils.ParseGypList(options.extra_r_text_files)) | 140 build_utils.ParseGypList(options.extra_r_text_files)) |
140 else: | 141 else: |
141 options.extra_r_text_files = [] | 142 options.extra_r_text_files = [] |
142 | 143 |
143 return options | 144 return options |
144 | 145 |
145 | 146 |
146 def CreateExtraRJavaFiles( | 147 def CreateRJavaFiles(srcjar_dir, main_r_txt_file, packages, r_txt_files, |
147 r_dir, extra_packages, extra_r_text_files, shared_resources, include_all): | 148 shared_resources): |
148 if include_all: | 149 assert len(packages) == len(r_txt_files), 'Need one R.txt file per package' |
149 java_files = build_utils.FindInDirectory(r_dir, "R.java") | |
150 if len(java_files) != 1: | |
151 return | |
152 r_java_file = java_files[0] | |
153 r_java_contents = codecs.open(r_java_file, encoding='utf-8').read() | |
154 | 150 |
155 for package in extra_packages: | 151 # Map of (resource_type, name) -> Entry. |
156 package_r_java_dir = os.path.join(r_dir, *package.split('.')) | 152 # Contains the correct values for resources. |
157 build_utils.MakeDirectory(package_r_java_dir) | 153 all_resources = {} |
158 package_r_java_path = os.path.join(package_r_java_dir, 'R.java') | 154 for entry in _ParseTextSymbolsFile(main_r_txt_file): |
159 new_r_java = re.sub(r'package [.\w]*;', u'package %s;' % package, | 155 all_resources[(entry.resource_type, entry.name)] = entry |
160 r_java_contents) | |
161 codecs.open(package_r_java_path, 'w', encoding='utf-8').write(new_r_java) | |
162 else: | |
163 if len(extra_packages) != len(extra_r_text_files): | |
164 raise Exception('Need one R.txt file per extra package') | |
165 | 156 |
166 r_txt_file = os.path.join(r_dir, 'R.txt') | 157 # Map of package_name->resource_type->entry |
167 if not os.path.exists(r_txt_file): | 158 resources_by_package = ( |
168 return | 159 collections.defaultdict(lambda: collections.defaultdict(list))) |
| 160 # Build the R.java files using each package's R.txt file, but replacing |
| 161 # each entry's placeholder value with correct values from all_resources. |
| 162 for package, r_txt_file in zip(packages, r_txt_files): |
| 163 if package in resources_by_package: |
| 164 raise Exception(('Package name "%s" appeared twice. All ' |
| 165 'android_resources() targets must use unique package ' |
| 166 'names, or no package name at all.') % package) |
| 167 resources_by_type = resources_by_package[package] |
| 168 # The sub-R.txt files have the wrong values at this point. Read them to |
| 169 # figure out which entries belong to them, but use the values from the |
| 170 # main R.txt file. |
| 171 for entry in _ParseTextSymbolsFile(r_txt_file): |
| 172 entry = all_resources[(entry.resource_type, entry.name)] |
| 173 resources_by_type[entry.resource_type].append(entry) |
169 | 174 |
170 # Map of (resource_type, name) -> Entry. | 175 for package, resources_by_type in resources_by_package.iteritems(): |
171 # Contains the correct values for resources. | 176 package_r_java_dir = os.path.join(srcjar_dir, *package.split('.')) |
172 all_resources = {} | 177 build_utils.MakeDirectory(package_r_java_dir) |
173 for entry in _ParseTextSymbolsFile(r_txt_file): | 178 package_r_java_path = os.path.join(package_r_java_dir, 'R.java') |
174 all_resources[(entry.resource_type, entry.name)] = entry | 179 java_file_contents = _CreateExtraRJavaFile( |
175 | 180 package, resources_by_type, shared_resources) |
176 # Map of package_name->resource_type->entry | 181 with open(package_r_java_path, 'w') as f: |
177 resources_by_package = ( | 182 f.write(java_file_contents) |
178 collections.defaultdict(lambda: collections.defaultdict(list))) | |
179 # Build the R.java files using each package's R.txt file, but replacing | |
180 # each entry's placeholder value with correct values from all_resources. | |
181 for package, r_text_file in zip(extra_packages, extra_r_text_files): | |
182 if not os.path.exists(r_text_file): | |
183 continue | |
184 if package in resources_by_package: | |
185 raise Exception(('Package name "%s" appeared twice. All ' | |
186 'android_resources() targets must use unique package ' | |
187 'names, or no package name at all.') % package) | |
188 resources_by_type = resources_by_package[package] | |
189 # The sub-R.txt files have the wrong values at this point. Read them to | |
190 # figure out which entries belong to them, but use the values from the | |
191 # main R.txt file. | |
192 for entry in _ParseTextSymbolsFile(r_text_file): | |
193 entry = all_resources[(entry.resource_type, entry.name)] | |
194 resources_by_type[entry.resource_type].append(entry) | |
195 | |
196 for package, resources_by_type in resources_by_package.iteritems(): | |
197 package_r_java_dir = os.path.join(r_dir, *package.split('.')) | |
198 build_utils.MakeDirectory(package_r_java_dir) | |
199 package_r_java_path = os.path.join(package_r_java_dir, 'R.java') | |
200 java_file_contents = _CreateExtraRJavaFile( | |
201 package, resources_by_type, shared_resources) | |
202 with open(package_r_java_path, 'w') as f: | |
203 f.write(java_file_contents) | |
204 | 183 |
205 | 184 |
206 def _ParseTextSymbolsFile(path): | 185 def _ParseTextSymbolsFile(path): |
207 """Given an R.txt file, returns a list of TextSymbolsEntry.""" | 186 """Given an R.txt file, returns a list of TextSymbolsEntry.""" |
208 ret = [] | 187 ret = [] |
209 with open(path) as f: | 188 with open(path) as f: |
210 for line in f: | 189 for line in f: |
211 m = re.match(r'(int(?:\[\])?) (\w+) (\w+) (.+)$', line) | 190 m = re.match(r'(int(?:\[\])?) (\w+) (\w+) (.+)$', line) |
212 if not m: | 191 if not m: |
213 raise Exception('Unexpected line in R.txt: %s' % line) | 192 raise Exception('Unexpected line in R.txt: %s' % line) |
(...skipping 120 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
334 # of the form 0, 1, ..., then each subdirectory will be passed to aapt as a | 313 # of the form 0, 1, ..., then each subdirectory will be passed to aapt as a |
335 # resources directory. While some resources just clobber others (image files, | 314 # resources directory. While some resources just clobber others (image files, |
336 # etc), other resources (particularly .xml files) need to be more | 315 # etc), other resources (particularly .xml files) need to be more |
337 # intelligently merged. That merging is left up to aapt. | 316 # intelligently merged. That merging is left up to aapt. |
338 def path_transform(name, src_zip): | 317 def path_transform(name, src_zip): |
339 return '%d/%s' % (zip_files.index(src_zip), name) | 318 return '%d/%s' % (zip_files.index(src_zip), name) |
340 | 319 |
341 build_utils.MergeZips(output_path, zip_files, path_transform=path_transform) | 320 build_utils.MergeZips(output_path, zip_files, path_transform=path_transform) |
342 | 321 |
343 | 322 |
| 323 def _ExtractPackageFromManifest(manifest_path): |
| 324 doc = xml.etree.ElementTree.parse(manifest_path) |
| 325 return doc.getroot().get('package') |
| 326 |
| 327 |
344 def _OnStaleMd5(options): | 328 def _OnStaleMd5(options): |
345 aapt = options.aapt_path | 329 aapt = options.aapt_path |
346 with build_utils.TempDir() as temp_dir: | 330 with build_utils.TempDir() as temp_dir: |
347 deps_dir = os.path.join(temp_dir, 'deps') | 331 deps_dir = os.path.join(temp_dir, 'deps') |
348 build_utils.MakeDirectory(deps_dir) | 332 build_utils.MakeDirectory(deps_dir) |
349 v14_dir = os.path.join(temp_dir, 'v14') | 333 v14_dir = os.path.join(temp_dir, 'v14') |
350 build_utils.MakeDirectory(v14_dir) | 334 build_utils.MakeDirectory(v14_dir) |
351 | 335 |
352 gen_dir = os.path.join(temp_dir, 'gen') | 336 gen_dir = os.path.join(temp_dir, 'gen') |
353 build_utils.MakeDirectory(gen_dir) | 337 build_utils.MakeDirectory(gen_dir) |
| 338 r_txt_path = os.path.join(gen_dir, 'R.txt') |
| 339 srcjar_dir = os.path.join(temp_dir, 'java') |
354 | 340 |
355 input_resource_dirs = options.resource_dirs | 341 input_resource_dirs = options.resource_dirs |
356 | 342 |
357 if not options.v14_skip: | 343 if not options.v14_skip: |
358 for resource_dir in input_resource_dirs: | 344 for resource_dir in input_resource_dirs: |
359 generate_v14_compatible_resources.GenerateV14Resources( | 345 generate_v14_compatible_resources.GenerateV14Resources( |
360 resource_dir, | 346 resource_dir, |
361 v14_dir) | 347 v14_dir) |
362 | 348 |
363 dep_zips = options.dependencies_res_zips | 349 dep_zips = options.dependencies_res_zips |
(...skipping 11 matching lines...) Expand all Loading... |
375 # generated by merging the resources from all libraries and the main apk | 361 # generated by merging the resources from all libraries and the main apk |
376 # project. | 362 # project. |
377 package_command = [aapt, | 363 package_command = [aapt, |
378 'package', | 364 'package', |
379 '-m', | 365 '-m', |
380 '-M', options.android_manifest, | 366 '-M', options.android_manifest, |
381 '--auto-add-overlay', | 367 '--auto-add-overlay', |
382 '--no-version-vectors', | 368 '--no-version-vectors', |
383 '-I', options.android_sdk_jar, | 369 '-I', options.android_sdk_jar, |
384 '--output-text-symbols', gen_dir, | 370 '--output-text-symbols', gen_dir, |
385 '-J', gen_dir, | 371 '-J', gen_dir, # Required for R.txt generation. |
386 '--ignore-assets', build_utils.AAPT_IGNORE_PATTERN] | 372 '--ignore-assets', build_utils.AAPT_IGNORE_PATTERN] |
387 | 373 |
| 374 # aapt supports only the "--include-all-resources" mode, where each R.java |
| 375 # file ends up with all symbols, rather than only those that it had at the |
| 376 # time it was originally generated. This subtle difference makes no |
| 377 # difference when compiling, but can lead to increased unused symbols in the |
| 378 # resulting R.class files. |
| 379 # TODO(agrieve): See if proguard makes this difference actually translate |
| 380 # into a size difference. If not, we can delete all of our custom R.java |
| 381 # template code above (and make include_all_resources the default). |
| 382 if options.include_all_resources: |
| 383 srcjar_dir = gen_dir |
| 384 if options.extra_res_packages: |
| 385 colon_separated = ':'.join(options.extra_res_packages) |
| 386 package_command += ['--extra-packages', colon_separated] |
| 387 if options.non_constant_id: |
| 388 package_command.append('--non-constant-id') |
| 389 if options.custom_package: |
| 390 package_command += ['--custom-package', options.custom_package] |
| 391 if options.shared_resources: |
| 392 package_command.append('--shared-lib') |
| 393 if options.app_as_shared_lib: |
| 394 package_command.append('--app-as-shared-lib') |
| 395 |
388 for d in input_resource_dirs: | 396 for d in input_resource_dirs: |
389 package_command += ['-S', d] | 397 package_command += ['-S', d] |
390 | 398 |
| 399 # Adding all dependencies as sources is necessary for @type/foo references |
| 400 # to symbols within dependencies to resolve. However, it has the side-effect |
| 401 # that all Java symbols from dependencies are copied into the new R.java. |
| 402 # E.g.: It enables an arguably incorrect usage of |
| 403 # "mypackage.R.id.lib_symbol" where "libpackage.R.id.lib_symbol" would be |
| 404 # more correct. This is just how Android works. |
391 for d in dep_subdirs: | 405 for d in dep_subdirs: |
392 package_command += ['-S', d] | 406 package_command += ['-S', d] |
393 | 407 |
394 if options.non_constant_id: | |
395 package_command.append('--non-constant-id') | |
396 if options.custom_package: | |
397 package_command += ['--custom-package', options.custom_package] | |
398 if options.proguard_file: | 408 if options.proguard_file: |
399 package_command += ['-G', options.proguard_file] | 409 package_command += ['-G', options.proguard_file] |
400 if options.shared_resources: | |
401 package_command.append('--shared-lib') | |
402 if options.app_as_shared_lib: | |
403 package_command.append('--app-as-shared-lib') | |
404 build_utils.CheckOutput(package_command, print_stderr=False) | 410 build_utils.CheckOutput(package_command, print_stderr=False) |
405 | 411 |
406 if options.extra_res_packages: | 412 # When an empty res/ directory is passed, aapt does not write an R.txt. |
407 CreateExtraRJavaFiles( | 413 if not os.path.exists(r_txt_path): |
408 gen_dir, | 414 build_utils.Touch(r_txt_path) |
409 options.extra_res_packages, | 415 |
410 options.extra_r_text_files, | 416 if not options.include_all_resources: |
411 options.shared_resources or options.app_as_shared_lib, | 417 packages = list(options.extra_res_packages) |
412 options.include_all_resources) | 418 r_txt_files = list(options.extra_r_text_files) |
| 419 |
| 420 cur_package = options.custom_package |
| 421 if not options.custom_package: |
| 422 cur_package = _ExtractPackageFromManifest(options.android_manifest) |
| 423 |
| 424 # Don't create a .java file for the current resource target when: |
| 425 # - no package name was provided (either by manifest or build rules), |
| 426 # - there was already a dependent android_resources() with the same |
| 427 # package (occurs mostly when an apk target and resources target share |
| 428 # an AndroidManifest.xml) |
| 429 if cur_package != 'dummy.package' and cur_package not in packages: |
| 430 packages.append(cur_package) |
| 431 r_txt_files.append(r_txt_path) |
| 432 |
| 433 if packages: |
| 434 shared_resources = options.shared_resources or options.app_as_shared_lib |
| 435 CreateRJavaFiles(srcjar_dir, r_txt_path, packages, r_txt_files, |
| 436 shared_resources) |
413 | 437 |
414 # This is the list of directories with resources to put in the final .zip | 438 # This is the list of directories with resources to put in the final .zip |
415 # file. The order of these is important so that crunched/v14 resources | 439 # file. The order of these is important so that crunched/v14 resources |
416 # override the normal ones. | 440 # override the normal ones. |
417 zip_resource_dirs = input_resource_dirs + [v14_dir] | 441 zip_resource_dirs = input_resource_dirs + [v14_dir] |
418 | 442 |
419 base_crunch_dir = os.path.join(temp_dir, 'crunch') | 443 base_crunch_dir = os.path.join(temp_dir, 'crunch') |
420 | 444 |
421 # Crunch image resources. This shrinks png files and is necessary for | 445 # Crunch image resources. This shrinks png files and is necessary for |
422 # 9-patch images to display correctly. 'aapt crunch' accepts only a single | 446 # 9-patch images to display correctly. 'aapt crunch' accepts only a single |
423 # directory at a time and deletes everything in the output directory. | 447 # directory at a time and deletes everything in the output directory. |
424 for idx, input_dir in enumerate(input_resource_dirs): | 448 for idx, input_dir in enumerate(input_resource_dirs): |
425 crunch_dir = os.path.join(base_crunch_dir, str(idx)) | 449 crunch_dir = os.path.join(base_crunch_dir, str(idx)) |
426 build_utils.MakeDirectory(crunch_dir) | 450 build_utils.MakeDirectory(crunch_dir) |
427 zip_resource_dirs.append(crunch_dir) | 451 zip_resource_dirs.append(crunch_dir) |
428 CrunchDirectory(aapt, input_dir, crunch_dir) | 452 CrunchDirectory(aapt, input_dir, crunch_dir) |
429 | 453 |
430 ZipResources(zip_resource_dirs, options.resource_zip_out) | 454 ZipResources(zip_resource_dirs, options.resource_zip_out) |
431 | 455 |
432 if options.all_resources_zip_out: | 456 if options.all_resources_zip_out: |
433 CombineZips([options.resource_zip_out] + dep_zips, | 457 CombineZips([options.resource_zip_out] + dep_zips, |
434 options.all_resources_zip_out) | 458 options.all_resources_zip_out) |
435 | 459 |
436 if options.R_dir: | 460 if options.R_dir: |
437 build_utils.DeleteDirectory(options.R_dir) | 461 build_utils.DeleteDirectory(options.R_dir) |
438 shutil.copytree(gen_dir, options.R_dir) | 462 shutil.copytree(srcjar_dir, options.R_dir) |
439 else: | 463 else: |
440 build_utils.ZipDir(options.srcjar_out, gen_dir) | 464 build_utils.ZipDir(options.srcjar_out, srcjar_dir) |
441 | 465 |
442 if options.r_text_out: | 466 if options.r_text_out: |
443 r_text_path = os.path.join(gen_dir, 'R.txt') | 467 shutil.copyfile(r_txt_path, options.r_text_out) |
444 if os.path.exists(r_text_path): | |
445 shutil.copyfile(r_text_path, options.r_text_out) | |
446 else: | |
447 open(options.r_text_out, 'w').close() | |
448 | 468 |
449 | 469 |
450 def main(args): | 470 def main(args): |
451 args = build_utils.ExpandFileArgs(args) | 471 args = build_utils.ExpandFileArgs(args) |
452 options = _ParseArgs(args) | 472 options = _ParseArgs(args) |
453 | 473 |
454 possible_output_paths = [ | 474 possible_output_paths = [ |
455 options.resource_zip_out, | 475 options.resource_zip_out, |
456 options.all_resources_zip_out, | 476 options.all_resources_zip_out, |
457 options.proguard_file, | 477 options.proguard_file, |
(...skipping 12 matching lines...) Expand all Loading... |
470 options.shared_resources, | 490 options.shared_resources, |
471 options.v14_skip, | 491 options.v14_skip, |
472 ] | 492 ] |
473 | 493 |
474 input_paths = [ | 494 input_paths = [ |
475 options.aapt_path, | 495 options.aapt_path, |
476 options.android_manifest, | 496 options.android_manifest, |
477 options.android_sdk_jar, | 497 options.android_sdk_jar, |
478 ] | 498 ] |
479 input_paths.extend(options.dependencies_res_zips) | 499 input_paths.extend(options.dependencies_res_zips) |
480 input_paths.extend(p for p in options.extra_r_text_files if os.path.exists(p)) | 500 input_paths.extend(options.extra_r_text_files) |
481 | 501 |
482 resource_names = [] | 502 resource_names = [] |
483 for resource_dir in options.resource_dirs: | 503 for resource_dir in options.resource_dirs: |
484 for resource_file in build_utils.FindInDirectory(resource_dir, '*'): | 504 for resource_file in build_utils.FindInDirectory(resource_dir, '*'): |
485 input_paths.append(resource_file) | 505 input_paths.append(resource_file) |
486 resource_names.append(os.path.relpath(resource_file, resource_dir)) | 506 resource_names.append(os.path.relpath(resource_file, resource_dir)) |
487 | 507 |
488 # Resource filenames matter to the output, so add them to strings as well. | 508 # Resource filenames matter to the output, so add them to strings as well. |
489 # This matters if a file is renamed but not changed (http://crbug.com/597126). | 509 # This matters if a file is renamed but not changed (http://crbug.com/597126). |
490 input_strings.extend(sorted(resource_names)) | 510 input_strings.extend(sorted(resource_names)) |
491 | 511 |
492 build_utils.CallAndWriteDepfileIfStale( | 512 build_utils.CallAndWriteDepfileIfStale( |
493 lambda: _OnStaleMd5(options), | 513 lambda: _OnStaleMd5(options), |
494 options, | 514 options, |
495 input_paths=input_paths, | 515 input_paths=input_paths, |
496 input_strings=input_strings, | 516 input_strings=input_strings, |
497 output_paths=output_paths, | 517 output_paths=output_paths, |
498 # TODO(agrieve): Remove R_dir when it's no longer used (used only by GYP). | 518 # TODO(agrieve): Remove R_dir when it's no longer used (used only by GYP). |
499 force=options.R_dir) | 519 force=options.R_dir) |
500 | 520 |
501 | 521 |
502 if __name__ == '__main__': | 522 if __name__ == '__main__': |
503 main(sys.argv[1:]) | 523 main(sys.argv[1:]) |
OLD | NEW |