| 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 |