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 | |
21 | 20 |
22 import generate_v14_compatible_resources | 21 import generate_v14_compatible_resources |
23 | 22 |
24 from util import build_utils | 23 from util import build_utils |
25 | 24 |
26 # Import jinja2 from third_party/jinja2 | 25 # Import jinja2 from third_party/jinja2 |
27 sys.path.insert(1, | 26 sys.path.insert(1, |
28 os.path.join(os.path.dirname(__file__), '../../../third_party')) | 27 os.path.join(os.path.dirname(__file__), '../../../third_party')) |
29 from jinja2 import Template # pylint: disable=F0401 | 28 from jinja2 import Template # pylint: disable=F0401 |
30 | 29 |
(...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
137 | 136 |
138 if options.extra_r_text_files: | 137 if options.extra_r_text_files: |
139 options.extra_r_text_files = ( | 138 options.extra_r_text_files = ( |
140 build_utils.ParseGypList(options.extra_r_text_files)) | 139 build_utils.ParseGypList(options.extra_r_text_files)) |
141 else: | 140 else: |
142 options.extra_r_text_files = [] | 141 options.extra_r_text_files = [] |
143 | 142 |
144 return options | 143 return options |
145 | 144 |
146 | 145 |
147 def CreateRJavaFiles(srcjar_dir, main_r_txt_file, packages, r_txt_files, | 146 def CreateExtraRJavaFiles( |
148 shared_resources): | 147 r_dir, extra_packages, extra_r_text_files, shared_resources, include_all): |
149 assert len(packages) == len(r_txt_files), 'Need one R.txt file per package' | 148 if include_all: |
| 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() |
150 | 154 |
151 # Map of (resource_type, name) -> Entry. | 155 for package in extra_packages: |
152 # Contains the correct values for resources. | 156 package_r_java_dir = os.path.join(r_dir, *package.split('.')) |
153 all_resources = {} | 157 build_utils.MakeDirectory(package_r_java_dir) |
154 for entry in _ParseTextSymbolsFile(main_r_txt_file): | 158 package_r_java_path = os.path.join(package_r_java_dir, 'R.java') |
155 all_resources[(entry.resource_type, entry.name)] = entry | 159 new_r_java = re.sub(r'package [.\w]*;', u'package %s;' % package, |
| 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') |
156 | 165 |
157 # Map of package_name->resource_type->entry | 166 r_txt_file = os.path.join(r_dir, 'R.txt') |
158 resources_by_package = ( | 167 if not os.path.exists(r_txt_file): |
159 collections.defaultdict(lambda: collections.defaultdict(list))) | 168 return |
160 # Build the R.java files using each package's R.txt file, but replacing | 169 |
161 # each entry's placeholder value with correct values from all_resources. | 170 # Map of (resource_type, name) -> Entry. |
162 for package, r_txt_file in zip(packages, r_txt_files): | 171 # Contains the correct values for resources. |
163 if package in resources_by_package: | 172 all_resources = {} |
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): | 173 for entry in _ParseTextSymbolsFile(r_txt_file): |
172 entry = all_resources[(entry.resource_type, entry.name)] | 174 all_resources[(entry.resource_type, entry.name)] = entry |
173 resources_by_type[entry.resource_type].append(entry) | |
174 | 175 |
175 for package, resources_by_type in resources_by_package.iteritems(): | 176 # Map of package_name->resource_type->entry |
176 package_r_java_dir = os.path.join(srcjar_dir, *package.split('.')) | 177 resources_by_package = ( |
177 build_utils.MakeDirectory(package_r_java_dir) | 178 collections.defaultdict(lambda: collections.defaultdict(list))) |
178 package_r_java_path = os.path.join(package_r_java_dir, 'R.java') | 179 # Build the R.java files using each package's R.txt file, but replacing |
179 java_file_contents = _CreateExtraRJavaFile( | 180 # each entry's placeholder value with correct values from all_resources. |
180 package, resources_by_type, shared_resources) | 181 for package, r_text_file in zip(extra_packages, extra_r_text_files): |
181 with open(package_r_java_path, 'w') as f: | 182 if not os.path.exists(r_text_file): |
182 f.write(java_file_contents) | 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) |
183 | 204 |
184 | 205 |
185 def _ParseTextSymbolsFile(path): | 206 def _ParseTextSymbolsFile(path): |
186 """Given an R.txt file, returns a list of TextSymbolsEntry.""" | 207 """Given an R.txt file, returns a list of TextSymbolsEntry.""" |
187 ret = [] | 208 ret = [] |
188 with open(path) as f: | 209 with open(path) as f: |
189 for line in f: | 210 for line in f: |
190 m = re.match(r'(int(?:\[\])?) (\w+) (\w+) (.+)$', line) | 211 m = re.match(r'(int(?:\[\])?) (\w+) (\w+) (.+)$', line) |
191 if not m: | 212 if not m: |
192 raise Exception('Unexpected line in R.txt: %s' % line) | 213 raise Exception('Unexpected line in R.txt: %s' % line) |
(...skipping 120 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
313 # of the form 0, 1, ..., then each subdirectory will be passed to aapt as a | 334 # of the form 0, 1, ..., then each subdirectory will be passed to aapt as a |
314 # resources directory. While some resources just clobber others (image files, | 335 # resources directory. While some resources just clobber others (image files, |
315 # etc), other resources (particularly .xml files) need to be more | 336 # etc), other resources (particularly .xml files) need to be more |
316 # intelligently merged. That merging is left up to aapt. | 337 # intelligently merged. That merging is left up to aapt. |
317 def path_transform(name, src_zip): | 338 def path_transform(name, src_zip): |
318 return '%d/%s' % (zip_files.index(src_zip), name) | 339 return '%d/%s' % (zip_files.index(src_zip), name) |
319 | 340 |
320 build_utils.MergeZips(output_path, zip_files, path_transform=path_transform) | 341 build_utils.MergeZips(output_path, zip_files, path_transform=path_transform) |
321 | 342 |
322 | 343 |
323 def _ExtractPackageFromManifest(manifest_path): | |
324 doc = xml.etree.ElementTree.parse(manifest_path) | |
325 return doc.getroot().get('package') | |
326 | |
327 | |
328 def _OnStaleMd5(options): | 344 def _OnStaleMd5(options): |
329 aapt = options.aapt_path | 345 aapt = options.aapt_path |
330 with build_utils.TempDir() as temp_dir: | 346 with build_utils.TempDir() as temp_dir: |
331 deps_dir = os.path.join(temp_dir, 'deps') | 347 deps_dir = os.path.join(temp_dir, 'deps') |
332 build_utils.MakeDirectory(deps_dir) | 348 build_utils.MakeDirectory(deps_dir) |
333 v14_dir = os.path.join(temp_dir, 'v14') | 349 v14_dir = os.path.join(temp_dir, 'v14') |
334 build_utils.MakeDirectory(v14_dir) | 350 build_utils.MakeDirectory(v14_dir) |
335 | 351 |
336 gen_dir = os.path.join(temp_dir, 'gen') | 352 gen_dir = os.path.join(temp_dir, 'gen') |
337 build_utils.MakeDirectory(gen_dir) | 353 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') | |
340 | 354 |
341 input_resource_dirs = options.resource_dirs | 355 input_resource_dirs = options.resource_dirs |
342 | 356 |
343 if not options.v14_skip: | 357 if not options.v14_skip: |
344 for resource_dir in input_resource_dirs: | 358 for resource_dir in input_resource_dirs: |
345 generate_v14_compatible_resources.GenerateV14Resources( | 359 generate_v14_compatible_resources.GenerateV14Resources( |
346 resource_dir, | 360 resource_dir, |
347 v14_dir) | 361 v14_dir) |
348 | 362 |
349 dep_zips = options.dependencies_res_zips | 363 dep_zips = options.dependencies_res_zips |
(...skipping 11 matching lines...) Expand all Loading... |
361 # generated by merging the resources from all libraries and the main apk | 375 # generated by merging the resources from all libraries and the main apk |
362 # project. | 376 # project. |
363 package_command = [aapt, | 377 package_command = [aapt, |
364 'package', | 378 'package', |
365 '-m', | 379 '-m', |
366 '-M', options.android_manifest, | 380 '-M', options.android_manifest, |
367 '--auto-add-overlay', | 381 '--auto-add-overlay', |
368 '--no-version-vectors', | 382 '--no-version-vectors', |
369 '-I', options.android_sdk_jar, | 383 '-I', options.android_sdk_jar, |
370 '--output-text-symbols', gen_dir, | 384 '--output-text-symbols', gen_dir, |
371 '-J', gen_dir, # Required for R.txt generation. | 385 '-J', gen_dir, |
372 '--ignore-assets', build_utils.AAPT_IGNORE_PATTERN] | 386 '--ignore-assets', build_utils.AAPT_IGNORE_PATTERN] |
373 | 387 |
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 | |
396 for d in input_resource_dirs: | 388 for d in input_resource_dirs: |
397 package_command += ['-S', d] | 389 package_command += ['-S', d] |
398 | 390 |
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. | |
405 for d in dep_subdirs: | 391 for d in dep_subdirs: |
406 package_command += ['-S', d] | 392 package_command += ['-S', d] |
407 | 393 |
| 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] |
408 if options.proguard_file: | 398 if options.proguard_file: |
409 package_command += ['-G', options.proguard_file] | 399 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') |
410 build_utils.CheckOutput(package_command, print_stderr=False) | 404 build_utils.CheckOutput(package_command, print_stderr=False) |
411 | 405 |
412 # When an empty res/ directory is passed, aapt does not write an R.txt. | 406 if options.extra_res_packages: |
413 if not os.path.exists(r_txt_path): | 407 CreateExtraRJavaFiles( |
414 build_utils.Touch(r_txt_path) | 408 gen_dir, |
415 | 409 options.extra_res_packages, |
416 if not options.include_all_resources: | 410 options.extra_r_text_files, |
417 packages = list(options.extra_res_packages) | 411 options.shared_resources or options.app_as_shared_lib, |
418 r_txt_files = list(options.extra_r_text_files) | 412 options.include_all_resources) |
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) | |
437 | 413 |
438 # This is the list of directories with resources to put in the final .zip | 414 # This is the list of directories with resources to put in the final .zip |
439 # file. The order of these is important so that crunched/v14 resources | 415 # file. The order of these is important so that crunched/v14 resources |
440 # override the normal ones. | 416 # override the normal ones. |
441 zip_resource_dirs = input_resource_dirs + [v14_dir] | 417 zip_resource_dirs = input_resource_dirs + [v14_dir] |
442 | 418 |
443 base_crunch_dir = os.path.join(temp_dir, 'crunch') | 419 base_crunch_dir = os.path.join(temp_dir, 'crunch') |
444 | 420 |
445 # Crunch image resources. This shrinks png files and is necessary for | 421 # Crunch image resources. This shrinks png files and is necessary for |
446 # 9-patch images to display correctly. 'aapt crunch' accepts only a single | 422 # 9-patch images to display correctly. 'aapt crunch' accepts only a single |
447 # directory at a time and deletes everything in the output directory. | 423 # directory at a time and deletes everything in the output directory. |
448 for idx, input_dir in enumerate(input_resource_dirs): | 424 for idx, input_dir in enumerate(input_resource_dirs): |
449 crunch_dir = os.path.join(base_crunch_dir, str(idx)) | 425 crunch_dir = os.path.join(base_crunch_dir, str(idx)) |
450 build_utils.MakeDirectory(crunch_dir) | 426 build_utils.MakeDirectory(crunch_dir) |
451 zip_resource_dirs.append(crunch_dir) | 427 zip_resource_dirs.append(crunch_dir) |
452 CrunchDirectory(aapt, input_dir, crunch_dir) | 428 CrunchDirectory(aapt, input_dir, crunch_dir) |
453 | 429 |
454 ZipResources(zip_resource_dirs, options.resource_zip_out) | 430 ZipResources(zip_resource_dirs, options.resource_zip_out) |
455 | 431 |
456 if options.all_resources_zip_out: | 432 if options.all_resources_zip_out: |
457 CombineZips([options.resource_zip_out] + dep_zips, | 433 CombineZips([options.resource_zip_out] + dep_zips, |
458 options.all_resources_zip_out) | 434 options.all_resources_zip_out) |
459 | 435 |
460 if options.R_dir: | 436 if options.R_dir: |
461 build_utils.DeleteDirectory(options.R_dir) | 437 build_utils.DeleteDirectory(options.R_dir) |
462 shutil.copytree(srcjar_dir, options.R_dir) | 438 shutil.copytree(gen_dir, options.R_dir) |
463 else: | 439 else: |
464 build_utils.ZipDir(options.srcjar_out, srcjar_dir) | 440 build_utils.ZipDir(options.srcjar_out, gen_dir) |
465 | 441 |
466 if options.r_text_out: | 442 if options.r_text_out: |
467 shutil.copyfile(r_txt_path, options.r_text_out) | 443 r_text_path = os.path.join(gen_dir, 'R.txt') |
| 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() |
468 | 448 |
469 | 449 |
470 def main(args): | 450 def main(args): |
471 args = build_utils.ExpandFileArgs(args) | 451 args = build_utils.ExpandFileArgs(args) |
472 options = _ParseArgs(args) | 452 options = _ParseArgs(args) |
473 | 453 |
474 possible_output_paths = [ | 454 possible_output_paths = [ |
475 options.resource_zip_out, | 455 options.resource_zip_out, |
476 options.all_resources_zip_out, | 456 options.all_resources_zip_out, |
477 options.proguard_file, | 457 options.proguard_file, |
(...skipping 12 matching lines...) Expand all Loading... |
490 options.shared_resources, | 470 options.shared_resources, |
491 options.v14_skip, | 471 options.v14_skip, |
492 ] | 472 ] |
493 | 473 |
494 input_paths = [ | 474 input_paths = [ |
495 options.aapt_path, | 475 options.aapt_path, |
496 options.android_manifest, | 476 options.android_manifest, |
497 options.android_sdk_jar, | 477 options.android_sdk_jar, |
498 ] | 478 ] |
499 input_paths.extend(options.dependencies_res_zips) | 479 input_paths.extend(options.dependencies_res_zips) |
500 input_paths.extend(options.extra_r_text_files) | 480 input_paths.extend(p for p in options.extra_r_text_files if os.path.exists(p)) |
501 | 481 |
502 resource_names = [] | 482 resource_names = [] |
503 for resource_dir in options.resource_dirs: | 483 for resource_dir in options.resource_dirs: |
504 for resource_file in build_utils.FindInDirectory(resource_dir, '*'): | 484 for resource_file in build_utils.FindInDirectory(resource_dir, '*'): |
505 input_paths.append(resource_file) | 485 input_paths.append(resource_file) |
506 resource_names.append(os.path.relpath(resource_file, resource_dir)) | 486 resource_names.append(os.path.relpath(resource_file, resource_dir)) |
507 | 487 |
508 # Resource filenames matter to the output, so add them to strings as well. | 488 # Resource filenames matter to the output, so add them to strings as well. |
509 # This matters if a file is renamed but not changed (http://crbug.com/597126). | 489 # This matters if a file is renamed but not changed (http://crbug.com/597126). |
510 input_strings.extend(sorted(resource_names)) | 490 input_strings.extend(sorted(resource_names)) |
511 | 491 |
512 build_utils.CallAndWriteDepfileIfStale( | 492 build_utils.CallAndWriteDepfileIfStale( |
513 lambda: _OnStaleMd5(options), | 493 lambda: _OnStaleMd5(options), |
514 options, | 494 options, |
515 input_paths=input_paths, | 495 input_paths=input_paths, |
516 input_strings=input_strings, | 496 input_strings=input_strings, |
517 output_paths=output_paths, | 497 output_paths=output_paths, |
518 # TODO(agrieve): Remove R_dir when it's no longer used (used only by GYP). | 498 # TODO(agrieve): Remove R_dir when it's no longer used (used only by GYP). |
519 force=options.R_dir) | 499 force=options.R_dir) |
520 | 500 |
521 | 501 |
522 if __name__ == '__main__': | 502 if __name__ == '__main__': |
523 main(sys.argv[1:]) | 503 main(sys.argv[1:]) |
OLD | NEW |