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.
7 """Writes a build_config file.
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.
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)
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
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
32 import xml
.dom
.minidom
34 from util
import build_utils
36 import write_ordered_libraries
38 class AndroidManifest(object):
39 def __init__(self
, path
):
41 dom
= xml
.dom
.minidom
.parse(path
)
42 manifests
= dom
.getElementsByTagName('manifest')
43 assert len(manifests
) == 1
44 self
.manifest
= manifests
[0]
46 def GetInstrumentation(self
):
47 instrumentation_els
= self
.manifest
.getElementsByTagName('instrumentation')
48 if len(instrumentation_els
) == 0:
50 if len(instrumentation_els
) != 1:
52 'More than one <instrumentation> element found in %s' % self
.path
)
53 return instrumentation_els
[0]
55 def CheckInstrumentation(self
, expected_package
):
56 instr
= self
.GetInstrumentation()
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
:
63 'Wrong instrumented package. Expected %s, got %s'
64 % (expected_package
, instrumented_package
))
66 def GetPackageName(self
):
67 return self
.manifest
.getAttribute('package')
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
]
77 def DepsOfType(wanted_type
, configs
):
78 return [c
for c
in configs
if c
['type'] == wanted_type
]
81 def GetAllDepsConfigsInOrder(deps_config_paths
):
83 return set(GetDepConfig(path
)['deps_configs'])
84 return build_utils
.GetSortedTransitiveDependencies(deps_config_paths
, GetDeps
)
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
]
96 def All(self
, wanted_type
=None):
98 return self
.all_deps_configs
99 return DepsOfType(wanted_type
, self
.all_deps_configs
)
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
)
106 def AllConfigPaths(self
):
107 return self
.all_deps_config_paths
111 parser
= optparse
.OptionParser()
112 build_utils
.AddDepfileOption(parser
)
113 parser
.add_option('--build-config', help='Path to build_config output.')
116 help='Type of this target (e.g. android_library).')
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.')
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.')
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.')
140 # android library options
141 parser
.add_option('--dex-path', help='Path to target\'s dex output.')
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.')
147 parser
.add_option('--tested-apk-config',
148 help='Path to the build config of the tested apk (for an instrumentation '
151 options
, args
= parser
.parse_args(argv
)
154 parser
.error('No positional arguments should be given.')
157 if not options
.type in [
158 'java_library', 'android_resources', 'android_apk', 'deps_dex']:
159 raise Exception('Unknown type: <%s>' % options
.type)
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']
168 if options
.native_libs
:
169 required_options
.append('readelf_path')
171 build_utils
.CheckOptions(options
, parser
, required_options
)
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.')
177 if options
.requires_android
and not options
.supports_android
:
179 '--supports-android is required when using --requires-android')
181 possible_deps_config_paths
= build_utils
.ParseGypList(
182 options
.possible_deps_configs
)
184 allow_unknown_deps
= (options
.type == 'android_apk' or
185 options
.type == 'android_resources')
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
))
191 direct_deps_config_paths
= [
192 c
for c
in possible_deps_config_paths
if not c
in unknown_deps
]
194 deps
= Deps(direct_deps_config_paths
)
195 direct_library_deps
= deps
.Direct('java_library')
196 all_library_deps
= deps
.All('java_library')
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()
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
]
210 # Initialize some common config.
213 'name': os
.path
.basename(options
.build_config
),
214 'path': options
.build_config
,
215 'type': options
.type,
216 'deps_configs': direct_deps_config_paths
,
219 deps_info
= config
['deps_info']
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
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']])
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
))
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
))
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
246 'classpath': javac_classpath
,
249 'full_classpath': java_full_classpath
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
]
260 if options
.type == 'android_apk':
261 # Apks will get their resources srcjar explicitly passed to the java step.
262 config
['javac']['srcjars'] = []
264 if options
.type == 'android_resources':
265 deps_info
['resources_zip'] = options
.resources_zip
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
274 deps_info
['r_text'] = options
.r_text
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'] = []
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
]
289 if options
.type in ['android_apk', 'deps_dex']:
290 deps_dex_files
= [c
['dex_path'] for c
in all_library_deps
]
292 # An instrumentation test apk should exclude the dex files that are in the apk
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
]
299 p
for p
in deps_dex_files
if not p
in tested_apk_deps_dex_files
]
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
)
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
313 if options
.type == 'android_apk':
314 config
['dist_jar'] = {
316 c
['jar_path'] for c
in all_library_deps
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())
326 java_libraries_list
= []
327 if options
.native_libs
:
328 libraries
= build_utils
.ParseGypList(options
.native_libs
)
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(
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
])
341 write_ordered_libraries
.FullLibraryPath
, all_native_library_deps
)
344 'libraries': library_paths
,
345 'java_libraries_list': java_libraries_list
348 build_utils
.WriteJson(config
, options
.build_config
, only_if_changed
=True)
351 build_utils
.WriteDepfile(
353 deps
.AllConfigPaths() + build_utils
.GetPythonDependencies())
356 if __name__
== '__main__':
357 sys
.exit(main(sys
.argv
[1:]))