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 |