OLD | NEW |
| (Empty) |
1 #!/usr/bin/env python | |
2 # | |
3 # Copyright 2014 The Chromium Authors. All rights reserved. | |
4 # Use of this source code is governed by a BSD-style license that can be | |
5 # found in the LICENSE file. | |
6 | |
7 """Writes a build_config file. | |
8 | |
9 The build_config file for a target is a json file containing information about | |
10 how to build that target based on the target's dependencies. This includes | |
11 things like: the javac classpath, the list of android resources dependencies, | |
12 etc. It also includes the information needed to create the build_config for | |
13 other targets that depend on that one. | |
14 | |
15 Android build scripts should not refer to the build_config directly, and the | |
16 build specification should instead pass information in using the special | |
17 file-arg syntax (see build_utils.py:ExpandFileArgs). That syntax allows passing | |
18 of values in a json dict in a file and looks like this: | |
19 --python-arg=@FileArg(build_config_path:javac:classpath) | |
20 | |
21 Note: If paths to input files are passed in this way, it is important that: | |
22 1. inputs/deps of the action ensure that the files are available the first | |
23 time the action runs. | |
24 2. Either (a) or (b) | |
25 a. inputs/deps ensure that the action runs whenever one of the files changes | |
26 b. the files are added to the action's depfile | |
27 """ | |
28 | |
29 import optparse | |
30 import os | |
31 import sys | |
32 import xml.dom.minidom | |
33 | |
34 from util import build_utils | |
35 | |
36 import write_ordered_libraries | |
37 | |
38 class AndroidManifest(object): | |
39 def __init__(self, path): | |
40 self.path = path | |
41 dom = xml.dom.minidom.parse(path) | |
42 manifests = dom.getElementsByTagName('manifest') | |
43 assert len(manifests) == 1 | |
44 self.manifest = manifests[0] | |
45 | |
46 def GetInstrumentation(self): | |
47 instrumentation_els = self.manifest.getElementsByTagName('instrumentation') | |
48 if len(instrumentation_els) == 0: | |
49 return None | |
50 if len(instrumentation_els) != 1: | |
51 raise Exception( | |
52 'More than one <instrumentation> element found in %s' % self.path) | |
53 return instrumentation_els[0] | |
54 | |
55 def CheckInstrumentation(self, expected_package): | |
56 instr = self.GetInstrumentation() | |
57 if not instr: | |
58 raise Exception('No <instrumentation> elements found in %s' % self.path) | |
59 instrumented_package = instr.getAttributeNS( | |
60 'http://schemas.android.com/apk/res/android', 'targetPackage') | |
61 if instrumented_package != expected_package: | |
62 raise Exception( | |
63 'Wrong instrumented package. Expected %s, got %s' | |
64 % (expected_package, instrumented_package)) | |
65 | |
66 def GetPackageName(self): | |
67 return self.manifest.getAttribute('package') | |
68 | |
69 | |
70 dep_config_cache = {} | |
71 def GetDepConfig(path): | |
72 if not path in dep_config_cache: | |
73 dep_config_cache[path] = build_utils.ReadJson(path)['deps_info'] | |
74 return dep_config_cache[path] | |
75 | |
76 | |
77 def DepsOfType(wanted_type, configs): | |
78 return [c for c in configs if c['type'] == wanted_type] | |
79 | |
80 | |
81 def GetAllDepsConfigsInOrder(deps_config_paths): | |
82 def GetDeps(path): | |
83 return set(GetDepConfig(path)['deps_configs']) | |
84 return build_utils.GetSortedTransitiveDependencies(deps_config_paths, GetDeps) | |
85 | |
86 | |
87 class Deps(object): | |
88 def __init__(self, direct_deps_config_paths): | |
89 self.all_deps_config_paths = GetAllDepsConfigsInOrder( | |
90 direct_deps_config_paths) | |
91 self.direct_deps_configs = [ | |
92 GetDepConfig(p) for p in direct_deps_config_paths] | |
93 self.all_deps_configs = [ | |
94 GetDepConfig(p) for p in self.all_deps_config_paths] | |
95 | |
96 def All(self, wanted_type=None): | |
97 if type is None: | |
98 return self.all_deps_configs | |
99 return DepsOfType(wanted_type, self.all_deps_configs) | |
100 | |
101 def Direct(self, wanted_type=None): | |
102 if wanted_type is None: | |
103 return self.direct_deps_configs | |
104 return DepsOfType(wanted_type, self.direct_deps_configs) | |
105 | |
106 def AllConfigPaths(self): | |
107 return self.all_deps_config_paths | |
108 | |
109 | |
110 def main(argv): | |
111 parser = optparse.OptionParser() | |
112 build_utils.AddDepfileOption(parser) | |
113 parser.add_option('--build-config', help='Path to build_config output.') | |
114 parser.add_option( | |
115 '--type', | |
116 help='Type of this target (e.g. android_library).') | |
117 parser.add_option( | |
118 '--possible-deps-configs', | |
119 help='List of paths for dependency\'s build_config files. Some ' | |
120 'dependencies may not write build_config files. Missing build_config ' | |
121 'files are handled differently based on the type of this target.') | |
122 | |
123 # android_resources options | |
124 parser.add_option('--srcjar', help='Path to target\'s resources srcjar.') | |
125 parser.add_option('--resources-zip', help='Path to target\'s resources zip.') | |
126 parser.add_option('--r-text', help='Path to target\'s R.txt file.') | |
127 parser.add_option('--package-name', | |
128 help='Java package name for these resources.') | |
129 parser.add_option('--android-manifest', help='Path to android manifest.') | |
130 | |
131 # java library options | |
132 parser.add_option('--jar-path', help='Path to target\'s jar output.') | |
133 parser.add_option('--supports-android', action='store_true', | |
134 help='Whether this library supports running on the Android platform.') | |
135 parser.add_option('--requires-android', action='store_true', | |
136 help='Whether this library requires running on the Android platform.') | |
137 parser.add_option('--bypass-platform-checks', action='store_true', | |
138 help='Bypass checks for support/require Android platform.') | |
139 | |
140 # android library options | |
141 parser.add_option('--dex-path', help='Path to target\'s dex output.') | |
142 | |
143 # native library options | |
144 parser.add_option('--native-libs', help='List of top-level native libs.') | |
145 parser.add_option('--readelf-path', help='Path to toolchain\'s readelf.') | |
146 | |
147 parser.add_option('--tested-apk-config', | |
148 help='Path to the build config of the tested apk (for an instrumentation ' | |
149 'test apk).') | |
150 | |
151 options, args = parser.parse_args(argv) | |
152 | |
153 if args: | |
154 parser.error('No positional arguments should be given.') | |
155 | |
156 | |
157 if not options.type in [ | |
158 'java_library', 'android_resources', 'android_apk', 'deps_dex']: | |
159 raise Exception('Unknown type: <%s>' % options.type) | |
160 | |
161 required_options = ['build_config'] + { | |
162 'java_library': ['jar_path'], | |
163 'android_resources': ['resources_zip'], | |
164 'android_apk': ['jar_path', 'dex_path', 'resources_zip'], | |
165 'deps_dex': ['dex_path'] | |
166 }[options.type] | |
167 | |
168 if options.native_libs: | |
169 required_options.append('readelf_path') | |
170 | |
171 build_utils.CheckOptions(options, parser, required_options) | |
172 | |
173 if options.type == 'java_library': | |
174 if options.supports_android and not options.dex_path: | |
175 raise Exception('java_library that supports Android requires a dex path.') | |
176 | |
177 if options.requires_android and not options.supports_android: | |
178 raise Exception( | |
179 '--supports-android is required when using --requires-android') | |
180 | |
181 possible_deps_config_paths = build_utils.ParseGypList( | |
182 options.possible_deps_configs) | |
183 | |
184 allow_unknown_deps = (options.type == 'android_apk' or | |
185 options.type == 'android_resources') | |
186 unknown_deps = [ | |
187 c for c in possible_deps_config_paths if not os.path.exists(c)] | |
188 if unknown_deps and not allow_unknown_deps: | |
189 raise Exception('Unknown deps: ' + str(unknown_deps)) | |
190 | |
191 direct_deps_config_paths = [ | |
192 c for c in possible_deps_config_paths if not c in unknown_deps] | |
193 | |
194 deps = Deps(direct_deps_config_paths) | |
195 direct_library_deps = deps.Direct('java_library') | |
196 all_library_deps = deps.All('java_library') | |
197 | |
198 direct_resources_deps = deps.Direct('android_resources') | |
199 all_resources_deps = deps.All('android_resources') | |
200 # Resources should be ordered with the highest-level dependency first so that | |
201 # overrides are done correctly. | |
202 all_resources_deps.reverse() | |
203 | |
204 if options.type == 'android_apk' and options.tested_apk_config: | |
205 tested_apk_deps = Deps([options.tested_apk_config]) | |
206 tested_apk_resources_deps = tested_apk_deps.All('android_resources') | |
207 all_resources_deps = [ | |
208 d for d in all_resources_deps if not d in tested_apk_resources_deps] | |
209 | |
210 # Initialize some common config. | |
211 config = { | |
212 'deps_info': { | |
213 'name': os.path.basename(options.build_config), | |
214 'path': options.build_config, | |
215 'type': options.type, | |
216 'deps_configs': direct_deps_config_paths, | |
217 } | |
218 } | |
219 deps_info = config['deps_info'] | |
220 | |
221 if options.type == 'java_library' and not options.bypass_platform_checks: | |
222 deps_info['requires_android'] = options.requires_android | |
223 deps_info['supports_android'] = options.supports_android | |
224 | |
225 deps_require_android = (all_resources_deps + | |
226 [d['name'] for d in all_library_deps if d['requires_android']]) | |
227 deps_not_support_android = ( | |
228 [d['name'] for d in all_library_deps if not d['supports_android']]) | |
229 | |
230 if deps_require_android and not options.requires_android: | |
231 raise Exception('Some deps require building for the Android platform: ' + | |
232 str(deps_require_android)) | |
233 | |
234 if deps_not_support_android and options.supports_android: | |
235 raise Exception('Not all deps support the Android platform: ' + | |
236 str(deps_not_support_android)) | |
237 | |
238 if options.type in ['java_library', 'android_apk']: | |
239 javac_classpath = [c['jar_path'] for c in direct_library_deps] | |
240 java_full_classpath = [c['jar_path'] for c in all_library_deps] | |
241 deps_info['resources_deps'] = [c['path'] for c in all_resources_deps] | |
242 deps_info['jar_path'] = options.jar_path | |
243 if options.type == 'android_apk' or options.supports_android: | |
244 deps_info['dex_path'] = options.dex_path | |
245 config['javac'] = { | |
246 'classpath': javac_classpath, | |
247 } | |
248 config['java'] = { | |
249 'full_classpath': java_full_classpath | |
250 } | |
251 | |
252 if options.type == 'java_library': | |
253 # Only resources might have srcjars (normal srcjar targets are listed in | |
254 # srcjar_deps). A resource's srcjar contains the R.java file for those | |
255 # resources, and (like Android's default build system) we allow a library to | |
256 # refer to the resources in any of its dependents. | |
257 config['javac']['srcjars'] = [ | |
258 c['srcjar'] for c in direct_resources_deps if 'srcjar' in c] | |
259 | |
260 if options.type == 'android_apk': | |
261 # Apks will get their resources srcjar explicitly passed to the java step. | |
262 config['javac']['srcjars'] = [] | |
263 | |
264 if options.type == 'android_resources': | |
265 deps_info['resources_zip'] = options.resources_zip | |
266 if options.srcjar: | |
267 deps_info['srcjar'] = options.srcjar | |
268 if options.android_manifest: | |
269 manifest = AndroidManifest(options.android_manifest) | |
270 deps_info['package_name'] = manifest.GetPackageName() | |
271 if options.package_name: | |
272 deps_info['package_name'] = options.package_name | |
273 if options.r_text: | |
274 deps_info['r_text'] = options.r_text | |
275 | |
276 if options.type == 'android_resources' or options.type == 'android_apk': | |
277 config['resources'] = {} | |
278 config['resources']['dependency_zips'] = [ | |
279 c['resources_zip'] for c in all_resources_deps] | |
280 config['resources']['extra_package_names'] = [] | |
281 config['resources']['extra_r_text_files'] = [] | |
282 | |
283 if options.type == 'android_apk': | |
284 config['resources']['extra_package_names'] = [ | |
285 c['package_name'] for c in all_resources_deps if 'package_name' in c] | |
286 config['resources']['extra_r_text_files'] = [ | |
287 c['r_text'] for c in all_resources_deps if 'r_text' in c] | |
288 | |
289 if options.type in ['android_apk', 'deps_dex']: | |
290 deps_dex_files = [c['dex_path'] for c in all_library_deps] | |
291 | |
292 # An instrumentation test apk should exclude the dex files that are in the apk | |
293 # under test. | |
294 if options.type == 'android_apk' and options.tested_apk_config: | |
295 tested_apk_deps = Deps([options.tested_apk_config]) | |
296 tested_apk_library_deps = tested_apk_deps.All('java_library') | |
297 tested_apk_deps_dex_files = [c['dex_path'] for c in tested_apk_library_deps] | |
298 deps_dex_files = [ | |
299 p for p in deps_dex_files if not p in tested_apk_deps_dex_files] | |
300 | |
301 tested_apk_config = GetDepConfig(options.tested_apk_config) | |
302 expected_tested_package = tested_apk_config['package_name'] | |
303 AndroidManifest(options.android_manifest).CheckInstrumentation( | |
304 expected_tested_package) | |
305 | |
306 # Dependencies for the final dex file of an apk or a 'deps_dex'. | |
307 if options.type in ['android_apk', 'deps_dex']: | |
308 config['final_dex'] = {} | |
309 dex_config = config['final_dex'] | |
310 # TODO(cjhopman): proguard version | |
311 dex_config['dependency_dex_files'] = deps_dex_files | |
312 | |
313 if options.type == 'android_apk': | |
314 config['dist_jar'] = { | |
315 'dependency_jars': [ | |
316 c['jar_path'] for c in all_library_deps | |
317 ] | |
318 } | |
319 manifest = AndroidManifest(options.android_manifest) | |
320 deps_info['package_name'] = manifest.GetPackageName() | |
321 if not options.tested_apk_config and manifest.GetInstrumentation(): | |
322 # This must then have instrumentation only for itself. | |
323 manifest.CheckInstrumentation(manifest.GetPackageName()) | |
324 | |
325 library_paths = [] | |
326 java_libraries_list = [] | |
327 if options.native_libs: | |
328 libraries = build_utils.ParseGypList(options.native_libs) | |
329 if libraries: | |
330 libraries_dir = os.path.dirname(libraries[0]) | |
331 write_ordered_libraries.SetReadelfPath(options.readelf_path) | |
332 write_ordered_libraries.SetLibraryDirs([libraries_dir]) | |
333 all_native_library_deps = ( | |
334 write_ordered_libraries.GetSortedTransitiveDependenciesForBinaries( | |
335 libraries)) | |
336 # Create a java literal array with the "base" library names: | |
337 # e.g. libfoo.so -> foo | |
338 java_libraries_list = '{%s}' % ','.join( | |
339 ['"%s"' % s[3:-3] for s in all_native_library_deps]) | |
340 library_paths = map( | |
341 write_ordered_libraries.FullLibraryPath, all_native_library_deps) | |
342 | |
343 config['native'] = { | |
344 'libraries': library_paths, | |
345 'java_libraries_list': java_libraries_list | |
346 } | |
347 | |
348 build_utils.WriteJson(config, options.build_config, only_if_changed=True) | |
349 | |
350 if options.depfile: | |
351 build_utils.WriteDepfile( | |
352 options.depfile, | |
353 deps.AllConfigPaths() + build_utils.GetPythonDependencies()) | |
354 | |
355 | |
356 if __name__ == '__main__': | |
357 sys.exit(main(sys.argv[1:])) | |
OLD | NEW |