| 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 optparse | 13 import optparse |
| 14 import os | 14 import os |
| 15 import re | 15 import re |
| 16 import shutil |
| 17 import sys |
| 16 import zipfile | 18 import zipfile |
| 17 | 19 |
| 18 import generate_v14_compatible_resources | 20 import generate_v14_compatible_resources |
| 19 | 21 |
| 20 from util import build_utils | 22 from util import build_utils |
| 21 | 23 |
| 22 def ParseArgs(): | 24 def ParseArgs(args): |
| 23 """Parses command line options. | 25 """Parses command line options. |
| 24 | 26 |
| 25 Returns: | 27 Returns: |
| 26 An options object as from optparse.OptionsParser.parse_args() | 28 An options object as from optparse.OptionsParser.parse_args() |
| 27 """ | 29 """ |
| 28 parser = optparse.OptionParser() | 30 parser = optparse.OptionParser() |
| 31 build_utils.AddDepfileOption(parser) |
| 29 | 32 |
| 30 parser.add_option('--android-sdk', help='path to the Android SDK folder') | 33 parser.add_option('--android-sdk', help='path to the Android SDK folder') |
| 31 parser.add_option('--android-sdk-tools', | 34 parser.add_option('--android-sdk-tools', |
| 32 help='path to the Android SDK build tools folder') | 35 help='path to the Android SDK build tools folder') |
| 33 parser.add_option('--non-constant-id', action='store_true') | 36 parser.add_option('--non-constant-id', action='store_true') |
| 34 | 37 |
| 35 parser.add_option('--android-manifest', help='AndroidManifest.xml path') | 38 parser.add_option('--android-manifest', help='AndroidManifest.xml path') |
| 36 parser.add_option('--custom-package', help='Java package for R.java') | 39 parser.add_option('--custom-package', help='Java package for R.java') |
| 37 | 40 |
| 38 parser.add_option('--resource-dirs', | 41 parser.add_option('--resource-dirs', |
| 39 help='Directories containing resources of this target.') | 42 help='Directories containing resources of this target.') |
| 40 parser.add_option('--dependencies-res-zips', | 43 parser.add_option('--dependencies-res-zips', |
| 41 help='Resources from dependents.') | 44 help='Resources from dependents.') |
| 42 | 45 |
| 43 parser.add_option('--R-dir', help='directory to hold generated R.java') | |
| 44 parser.add_option('--resource-zip-out', | 46 parser.add_option('--resource-zip-out', |
| 45 help='Path for output zipped resources.') | 47 help='Path for output zipped resources.') |
| 46 | 48 |
| 49 parser.add_option('--R-dir', |
| 50 help='directory to hold generated R.java.') |
| 51 parser.add_option('--srcjar-out', |
| 52 help='Path to srcjar to contain generated R.java.') |
| 53 |
| 47 parser.add_option('--proguard-file', | 54 parser.add_option('--proguard-file', |
| 48 help='Path to proguard.txt generated file') | 55 help='Path to proguard.txt generated file') |
| 49 | 56 |
| 50 parser.add_option( | 57 parser.add_option( |
| 51 '--v14-verify-only', | 58 '--v14-verify-only', |
| 52 action='store_true', | 59 action='store_true', |
| 53 help='Do not generate v14 resources. Instead, just verify that the ' | 60 help='Do not generate v14 resources. Instead, just verify that the ' |
| 54 'resources are already compatible with v14, i.e. they don\'t use ' | 61 'resources are already compatible with v14, i.e. they don\'t use ' |
| 55 'attributes that cause crashes on certain devices.') | 62 'attributes that cause crashes on certain devices.') |
| 56 | 63 |
| 57 parser.add_option( | 64 parser.add_option( |
| 58 '--extra-res-packages', | 65 '--extra-res-packages', |
| 59 help='Additional package names to generate R.java files for') | 66 help='Additional package names to generate R.java files for') |
| 60 parser.add_option( | 67 parser.add_option( |
| 61 '--extra-r-text-files', | 68 '--extra-r-text-files', |
| 62 help='For each additional package, the R.txt file should contain a ' | 69 help='For each additional package, the R.txt file should contain a ' |
| 63 'list of resources to be included in the R.java file in the format ' | 70 'list of resources to be included in the R.java file in the format ' |
| 64 'generated by aapt') | 71 'generated by aapt') |
| 65 | 72 |
| 66 parser.add_option('--stamp', help='File to touch on success') | 73 parser.add_option('--stamp', help='File to touch on success') |
| 67 | 74 |
| 68 (options, args) = parser.parse_args() | 75 (options, args) = parser.parse_args(args) |
| 69 | 76 |
| 70 if args: | 77 if args: |
| 71 parser.error('No positional arguments should be given.') | 78 parser.error('No positional arguments should be given.') |
| 72 | 79 |
| 73 # Check that required options have been provided. | 80 # Check that required options have been provided. |
| 74 required_options = ( | 81 required_options = ( |
| 75 'android_sdk', | 82 'android_sdk', |
| 76 'android_sdk_tools', | 83 'android_sdk_tools', |
| 77 'android_manifest', | 84 'android_manifest', |
| 78 'dependencies_res_zips', | 85 'dependencies_res_zips', |
| 79 'resource_dirs', | 86 'resource_dirs', |
| 80 'resource_zip_out', | 87 'resource_zip_out', |
| 81 'R_dir', | |
| 82 ) | 88 ) |
| 83 build_utils.CheckOptions(options, parser, required=required_options) | 89 build_utils.CheckOptions(options, parser, required=required_options) |
| 84 | 90 |
| 91 if (options.R_dir is None) == (options.srcjar_out is None): |
| 92 raise Exception('Exactly one of --R-dir or --srcjar-out must be specified.') |
| 93 |
| 85 return options | 94 return options |
| 86 | 95 |
| 87 | 96 |
| 88 def CreateExtraRJavaFiles( | 97 def CreateExtraRJavaFiles( |
| 89 r_dir, extra_packages, extra_r_text_files): | 98 r_dir, extra_packages, extra_r_text_files): |
| 90 if len(extra_packages) != len(extra_r_text_files): | 99 if len(extra_packages) != len(extra_r_text_files): |
| 91 raise Exception('--extra-res-packages and --extra-r-text-files' | 100 raise Exception('--extra-res-packages and --extra-r-text-files' |
| 92 'should have the same length') | 101 'should have the same length') |
| 93 | 102 |
| 94 java_files = build_utils.FindInDirectory(r_dir, "R.java") | 103 java_files = build_utils.FindInDirectory(r_dir, "R.java") |
| 95 if len(java_files) != 1: | 104 if len(java_files) != 1: |
| 96 return | 105 return |
| 97 r_java_file = java_files[0] | 106 r_java_file = java_files[0] |
| 98 r_java_contents = open(r_java_file).read() | 107 r_java_contents = open(r_java_file).read() |
| 99 | 108 |
| 100 for package in extra_packages: | 109 for package in extra_packages: |
| 101 package_r_java_dir = os.path.join(r_dir, *package.split('.')) | 110 package_r_java_dir = os.path.join(r_dir, *package.split('.')) |
| 102 build_utils.MakeDirectory(package_r_java_dir) | 111 build_utils.MakeDirectory(package_r_java_dir) |
| 103 package_r_java_path = os.path.join(package_r_java_dir, 'R.java') | 112 package_r_java_path = os.path.join(package_r_java_dir, 'R.java') |
| 104 open(package_r_java_path, 'w').write( | 113 open(package_r_java_path, 'w').write( |
| 105 re.sub(r'package [.\w]*;', 'package %s;' % package, r_java_contents)) | 114 re.sub(r'package [.\w]*;', 'package %s;' % package, r_java_contents)) |
| 106 # TODO(cjhopman): These extra package's R.java files should be filtered to | 115 # TODO(cjhopman): These extra package's R.java files should be filtered to |
| 107 # only contain the resources listed in their R.txt files. At this point, we | 116 # only contain the resources listed in their R.txt files. At this point, we |
| 108 # have already compiled those other libraries, so doing this would only | 117 # have already compiled those other libraries, so doing this would only |
| 109 # affect how the code in this .apk target could refer to the resources. | 118 # affect how the code in this .apk target could refer to the resources. |
| 110 | 119 |
| 111 | 120 |
| 112 | |
| 113 | |
| 114 | |
| 115 | |
| 116 def DidCrunchFail(returncode, stderr): | 121 def DidCrunchFail(returncode, stderr): |
| 117 """Determines whether aapt crunch failed from its return code and output. | 122 """Determines whether aapt crunch failed from its return code and output. |
| 118 | 123 |
| 119 Because aapt's return code cannot be trusted, any output to stderr is | 124 Because aapt's return code cannot be trusted, any output to stderr is |
| 120 an indication that aapt has failed (http://crbug.com/314885), except | 125 an indication that aapt has failed (http://crbug.com/314885), except |
| 121 lines that contain "libpng warning", which is a known non-error condition | 126 lines that contain "libpng warning", which is a known non-error condition |
| 122 (http://crbug.com/364355). | 127 (http://crbug.com/364355). |
| 123 """ | 128 """ |
| 124 if returncode != 0: | 129 if returncode != 0: |
| 125 return True | 130 return True |
| 126 for line in stderr.splitlines(): | 131 for line in stderr.splitlines(): |
| 127 if line and not 'libpng warning' in line: | 132 if line and not 'libpng warning' in line: |
| 128 return True | 133 return True |
| 129 return False | 134 return False |
| 130 | 135 |
| 131 | 136 |
| 137 def ZipResources(resource_dirs, zip_path): |
| 138 # Python zipfile does not provide a way to replace a file (it just writes |
| 139 # another file with the same name). So, first collect all the files to put |
| 140 # in the zip (with proper overriding), and then zip them. |
| 141 files_to_zip = dict() |
| 142 for d in resource_dirs: |
| 143 for root, _, files in os.walk(d): |
| 144 for f in files: |
| 145 archive_path = os.path.join(os.path.relpath(root, d), f) |
| 146 path = os.path.join(root, f) |
| 147 files_to_zip[archive_path] = path |
| 148 with zipfile.ZipFile(zip_path, 'w') as outzip: |
| 149 for archive_path, path in files_to_zip.iteritems(): |
| 150 outzip.write(path, archive_path) |
| 151 |
| 152 |
| 132 def main(): | 153 def main(): |
| 133 options = ParseArgs() | 154 args = build_utils.ExpandFileArgs(sys.argv[1:]) |
| 155 |
| 156 options = ParseArgs(args) |
| 134 android_jar = os.path.join(options.android_sdk, 'android.jar') | 157 android_jar = os.path.join(options.android_sdk, 'android.jar') |
| 135 aapt = os.path.join(options.android_sdk_tools, 'aapt') | 158 aapt = os.path.join(options.android_sdk_tools, 'aapt') |
| 136 | 159 |
| 137 build_utils.DeleteDirectory(options.R_dir) | 160 input_files = [] |
| 138 build_utils.MakeDirectory(options.R_dir) | |
| 139 | 161 |
| 140 with build_utils.TempDir() as temp_dir: | 162 with build_utils.TempDir() as temp_dir: |
| 141 deps_dir = os.path.join(temp_dir, 'deps') | 163 deps_dir = os.path.join(temp_dir, 'deps') |
| 142 build_utils.MakeDirectory(deps_dir) | 164 build_utils.MakeDirectory(deps_dir) |
| 143 v14_dir = os.path.join(temp_dir, 'v14') | 165 v14_dir = os.path.join(temp_dir, 'v14') |
| 144 build_utils.MakeDirectory(v14_dir) | 166 build_utils.MakeDirectory(v14_dir) |
| 145 | 167 |
| 168 gen_dir = os.path.join(temp_dir, 'gen') |
| 169 build_utils.MakeDirectory(gen_dir) |
| 170 |
| 146 input_resource_dirs = build_utils.ParseGypList(options.resource_dirs) | 171 input_resource_dirs = build_utils.ParseGypList(options.resource_dirs) |
| 147 | 172 |
| 148 for resource_dir in input_resource_dirs: | 173 for resource_dir in input_resource_dirs: |
| 149 generate_v14_compatible_resources.GenerateV14Resources( | 174 generate_v14_compatible_resources.GenerateV14Resources( |
| 150 resource_dir, | 175 resource_dir, |
| 151 v14_dir, | 176 v14_dir, |
| 152 options.v14_verify_only) | 177 options.v14_verify_only) |
| 153 | 178 |
| 179 dep_zips = build_utils.ParseGypList(options.dependencies_res_zips) |
| 180 input_files += dep_zips |
| 181 dep_subdirs = [] |
| 182 for z in dep_zips: |
| 183 subdir = os.path.join(deps_dir, os.path.basename(z)) |
| 184 if os.path.exists(subdir): |
| 185 raise Exception('Resource zip name conflict: ' + os.path.basename(z)) |
| 186 build_utils.ExtractAll(z, path=subdir) |
| 187 dep_subdirs.append(subdir) |
| 188 |
| 154 # Generate R.java. This R.java contains non-final constants and is used only | 189 # Generate R.java. This R.java contains non-final constants and is used only |
| 155 # while compiling the library jar (e.g. chromium_content.jar). When building | 190 # while compiling the library jar (e.g. chromium_content.jar). When building |
| 156 # an apk, a new R.java file with the correct resource -> ID mappings will be | 191 # an apk, a new R.java file with the correct resource -> ID mappings will be |
| 157 # generated by merging the resources from all libraries and the main apk | 192 # generated by merging the resources from all libraries and the main apk |
| 158 # project. | 193 # project. |
| 159 package_command = [aapt, | 194 package_command = [aapt, |
| 160 'package', | 195 'package', |
| 161 '-m', | 196 '-m', |
| 162 '-M', options.android_manifest, | 197 '-M', options.android_manifest, |
| 163 '--auto-add-overlay', | 198 '--auto-add-overlay', |
| 164 '-I', android_jar, | 199 '-I', android_jar, |
| 165 '--output-text-symbols', options.R_dir, | 200 '--output-text-symbols', gen_dir, |
| 166 '-J', options.R_dir] | 201 '-J', gen_dir] |
| 167 | 202 |
| 168 for d in input_resource_dirs: | 203 for d in input_resource_dirs: |
| 169 package_command += ['-S', d] | 204 package_command += ['-S', d] |
| 170 | 205 |
| 171 dep_zips = build_utils.ParseGypList(options.dependencies_res_zips) | 206 for d in dep_subdirs: |
| 172 for z in dep_zips: | 207 package_command += ['-S', d] |
| 173 subdir = os.path.join(deps_dir, os.path.basename(z)) | |
| 174 if os.path.exists(subdir): | |
| 175 raise Exception('Resource zip name conflict: ' + os.path.basename(z)) | |
| 176 build_utils.ExtractAll(z, path=subdir) | |
| 177 package_command += ['-S', subdir] | |
| 178 | 208 |
| 179 if options.non_constant_id: | 209 if options.non_constant_id: |
| 180 package_command.append('--non-constant-id') | 210 package_command.append('--non-constant-id') |
| 181 if options.custom_package: | 211 if options.custom_package: |
| 182 package_command += ['--custom-package', options.custom_package] | 212 package_command += ['--custom-package', options.custom_package] |
| 183 if options.proguard_file: | 213 if options.proguard_file: |
| 184 package_command += ['-G', options.proguard_file] | 214 package_command += ['-G', options.proguard_file] |
| 185 build_utils.CheckOutput(package_command, print_stderr=False) | 215 build_utils.CheckOutput(package_command, print_stderr=False) |
| 186 | 216 |
| 187 if options.extra_res_packages: | 217 if options.extra_res_packages: |
| 188 CreateExtraRJavaFiles( | 218 CreateExtraRJavaFiles( |
| 189 options.R_dir, | 219 gen_dir, |
| 190 build_utils.ParseGypList(options.extra_res_packages), | 220 build_utils.ParseGypList(options.extra_res_packages), |
| 191 build_utils.ParseGypList(options.extra_r_text_files)) | 221 build_utils.ParseGypList(options.extra_r_text_files)) |
| 192 | 222 |
| 193 # This is the list of directories with resources to put in the final .zip | 223 # This is the list of directories with resources to put in the final .zip |
| 194 # file. The order of these is important so that crunched/v14 resources | 224 # file. The order of these is important so that crunched/v14 resources |
| 195 # override the normal ones. | 225 # override the normal ones. |
| 196 zip_resource_dirs = input_resource_dirs + [v14_dir] | 226 zip_resource_dirs = input_resource_dirs + [v14_dir] |
| 197 | 227 |
| 198 base_crunch_dir = os.path.join(temp_dir, 'crunch') | 228 base_crunch_dir = os.path.join(temp_dir, 'crunch') |
| 199 | 229 |
| 200 # Crunch image resources. This shrinks png files and is necessary for | 230 # Crunch image resources. This shrinks png files and is necessary for |
| 201 # 9-patch images to display correctly. 'aapt crunch' accepts only a single | 231 # 9-patch images to display correctly. 'aapt crunch' accepts only a single |
| 202 # directory at a time and deletes everything in the output directory. | 232 # directory at a time and deletes everything in the output directory. |
| 203 for idx, d in enumerate(input_resource_dirs): | 233 for idx, d in enumerate(input_resource_dirs): |
| 204 crunch_dir = os.path.join(base_crunch_dir, str(idx)) | 234 crunch_dir = os.path.join(base_crunch_dir, str(idx)) |
| 205 build_utils.MakeDirectory(crunch_dir) | 235 build_utils.MakeDirectory(crunch_dir) |
| 206 zip_resource_dirs.append(crunch_dir) | 236 zip_resource_dirs.append(crunch_dir) |
| 207 aapt_cmd = [aapt, | 237 aapt_cmd = [aapt, |
| 208 'crunch', | 238 'crunch', |
| 209 '-C', crunch_dir, | 239 '-C', crunch_dir, |
| 210 '-S', d] | 240 '-S', d] |
| 211 build_utils.CheckOutput(aapt_cmd, fail_func=DidCrunchFail) | 241 build_utils.CheckOutput(aapt_cmd, fail_func=DidCrunchFail) |
| 212 | 242 |
| 213 # Python zipfile does not provide a way to replace a file (it just writes | 243 ZipResources(zip_resource_dirs, options.resource_zip_out) |
| 214 # another file with the same name). So, first collect all the files to put | |
| 215 # in the zip (with proper overriding), and then zip them. | |
| 216 files_to_zip = dict() | |
| 217 for d in zip_resource_dirs: | |
| 218 for root, _, files in os.walk(d): | |
| 219 for f in files: | |
| 220 archive_path = os.path.join(os.path.relpath(root, d), f) | |
| 221 path = os.path.join(root, f) | |
| 222 files_to_zip[archive_path] = path | |
| 223 with zipfile.ZipFile(options.resource_zip_out, 'w') as outzip: | |
| 224 for archive_path, path in files_to_zip.iteritems(): | |
| 225 outzip.write(path, archive_path) | |
| 226 | 244 |
| 227 if options.stamp: | 245 if options.R_dir: |
| 228 build_utils.Touch(options.stamp) | 246 build_utils.DeleteDirectory(options.R_dir) |
| 247 shutil.copytree(gen_dir, options.R_dir) |
| 248 else: |
| 249 build_utils.ZipDir(options.srcjar_out, gen_dir) |
| 250 |
| 251 if options.depfile: |
| 252 input_files += build_utils.GetPythonDependencies() |
| 253 build_utils.WriteDepfile(options.depfile, input_files) |
| 254 |
| 255 if options.stamp: |
| 256 build_utils.Touch(options.stamp) |
| 229 | 257 |
| 230 | 258 |
| 231 if __name__ == '__main__': | 259 if __name__ == '__main__': |
| 232 main() | 260 main() |
| OLD | NEW |