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
, Deps
)
88 parser
= optparse
.OptionParser()
89 build_utils
.AddDepfileOption(parser
)
90 parser
.add_option('--build-config', help='Path to build_config output.')
93 help='Type of this target (e.g. android_library).')
95 '--possible-deps-configs',
96 help='List of paths for dependency\'s build_config files. Some '
97 'dependencies may not write build_config files. Missing build_config '
98 'files are handled differently based on the type of this target.')
100 # android_resources options
101 parser
.add_option('--srcjar', help='Path to target\'s resources srcjar.')
102 parser
.add_option('--resources-zip', help='Path to target\'s resources zip.')
103 parser
.add_option('--r-text', help='Path to target\'s R.txt file.')
104 parser
.add_option('--package-name',
105 help='Java package name for these resources.')
106 parser
.add_option('--android-manifest', help='Path to android manifest.')
108 # java library options
109 parser
.add_option('--jar-path', help='Path to target\'s jar output.')
110 parser
.add_option('--supports-android', action
='store_true',
111 help='Whether this library supports running on the Android platform.')
112 parser
.add_option('--requires-android', action
='store_true',
113 help='Whether this library requires running on the Android platform.')
114 parser
.add_option('--bypass-platform-checks', action
='store_true',
115 help='Bypass checks for support/require Android platform.')
117 # android library options
118 parser
.add_option('--dex-path', help='Path to target\'s dex output.')
120 # native library options
121 parser
.add_option('--native-libs', help='List of top-level native libs.')
122 parser
.add_option('--readelf-path', help='Path to toolchain\'s readelf.')
124 parser
.add_option('--tested-apk-config',
125 help='Path to the build config of the tested apk (for an instrumentation '
128 options
, args
= parser
.parse_args(argv
)
131 parser
.error('No positional arguments should be given.')
134 if not options
.type in [
135 'java_library', 'android_resources', 'android_apk', 'deps_dex']:
136 raise Exception('Unknown type: <%s>' % options
.type)
138 required_options
= ['build_config'] + {
139 'java_library': ['jar_path'],
140 'android_resources': ['resources_zip'],
141 'android_apk': ['jar_path', 'dex_path', 'resources_zip'],
142 'deps_dex': ['dex_path']
145 if options
.native_libs
:
146 required_options
.append('readelf_path')
148 build_utils
.CheckOptions(options
, parser
, required_options
)
150 if options
.type == 'java_library':
151 if options
.supports_android
and not options
.dex_path
:
152 raise Exception('java_library that supports Android requires a dex path.')
154 if options
.requires_android
and not options
.supports_android
:
156 '--supports-android is required when using --requires-android')
158 possible_deps_config_paths
= build_utils
.ParseGypList(
159 options
.possible_deps_configs
)
161 allow_unknown_deps
= options
.type == 'android_apk'
163 c
for c
in possible_deps_config_paths
if not os
.path
.exists(c
)]
164 if unknown_deps
and not allow_unknown_deps
:
165 raise Exception('Unknown deps: ' + str(unknown_deps
))
167 direct_deps_config_paths
= [
168 c
for c
in possible_deps_config_paths
if not c
in unknown_deps
]
169 all_deps_config_paths
= GetAllDepsConfigsInOrder(direct_deps_config_paths
)
171 direct_deps_configs
= [GetDepConfig(p
) for p
in direct_deps_config_paths
]
172 all_deps_configs
= [GetDepConfig(p
) for p
in all_deps_config_paths
]
174 direct_library_deps
= DepsOfType('java_library', direct_deps_configs
)
175 all_library_deps
= DepsOfType('java_library', all_deps_configs
)
177 direct_resources_deps
= DepsOfType('android_resources', direct_deps_configs
)
178 all_resources_deps
= DepsOfType('android_resources', all_deps_configs
)
179 # Resources should be ordered with the highest-level dependency first so that
180 # overrides are done correctly.
181 all_resources_deps
.reverse()
183 # Initialize some common config.
186 'name': os
.path
.basename(options
.build_config
),
187 'path': options
.build_config
,
188 'type': options
.type,
189 'deps_configs': direct_deps_config_paths
,
192 deps_info
= config
['deps_info']
194 if options
.type == 'java_library' and not options
.bypass_platform_checks
:
195 deps_info
['requires_android'] = options
.requires_android
196 deps_info
['supports_android'] = options
.supports_android
198 deps_require_android
= (all_resources_deps
+
199 [d
['name'] for d
in all_library_deps
if d
['requires_android']])
200 deps_not_support_android
= (
201 [d
['name'] for d
in all_library_deps
if not d
['supports_android']])
203 if deps_require_android
and not options
.requires_android
:
204 raise Exception('Some deps require building for the Android platform: ' +
205 str(deps_require_android
))
207 if deps_not_support_android
and options
.supports_android
:
208 raise Exception('Not all deps support the Android platform: ' +
209 str(deps_not_support_android
))
211 if options
.type in ['java_library', 'android_apk']:
212 javac_classpath
= [c
['jar_path'] for c
in direct_library_deps
]
213 java_full_classpath
= [c
['jar_path'] for c
in all_library_deps
]
214 deps_info
['resources_deps'] = [c
['path'] for c
in all_resources_deps
]
215 deps_info
['jar_path'] = options
.jar_path
216 if options
.type == 'android_apk' or options
.supports_android
:
217 deps_info
['dex_path'] = options
.dex_path
219 'classpath': javac_classpath
,
222 'full_classpath': java_full_classpath
225 if options
.type == 'java_library':
226 # Only resources might have srcjars (normal srcjar targets are listed in
227 # srcjar_deps). A resource's srcjar contains the R.java file for those
228 # resources, and (like Android's default build system) we allow a library to
229 # refer to the resources in any of its dependents.
230 config
['javac']['srcjars'] = [
231 c
['srcjar'] for c
in direct_resources_deps
if 'srcjar' in c
]
233 if options
.type == 'android_apk':
234 # Apks will get their resources srcjar explicitly passed to the java step.
235 config
['javac']['srcjars'] = []
237 if options
.type == 'android_resources':
238 deps_info
['resources_zip'] = options
.resources_zip
240 deps_info
['srcjar'] = options
.srcjar
241 if options
.android_manifest
:
242 manifest
= AndroidManifest(options
.android_manifest
)
243 deps_info
['package_name'] = manifest
.GetPackageName()
244 if options
.package_name
:
245 deps_info
['package_name'] = options
.package_name
247 deps_info
['r_text'] = options
.r_text
249 if options
.type == 'android_resources' or options
.type == 'android_apk':
250 config
['resources'] = {}
251 config
['resources']['dependency_zips'] = [
252 c
['resources_zip'] for c
in all_resources_deps
]
253 config
['resources']['extra_package_names'] = []
254 config
['resources']['extra_r_text_files'] = []
256 if options
.type == 'android_apk':
257 config
['resources']['extra_package_names'] = [
258 c
['package_name'] for c
in all_resources_deps
if 'package_name' in c
]
259 config
['resources']['extra_r_text_files'] = [
260 c
['r_text'] for c
in all_resources_deps
if 'r_text' in c
]
262 if options
.type in ['android_apk', 'deps_dex']:
263 deps_dex_files
= [c
['dex_path'] for c
in all_library_deps
]
265 # An instrumentation test apk should exclude the dex files that are in the apk
267 if options
.type == 'android_apk' and options
.tested_apk_config
:
268 tested_apk_config_paths
= GetAllDepsConfigsInOrder(
269 [options
.tested_apk_config
])
270 tested_apk_configs
= [GetDepConfig(p
) for p
in tested_apk_config_paths
]
271 tested_apk_library_deps
= DepsOfType('java_library', tested_apk_configs
)
272 tested_apk_deps_dex_files
= [c
['dex_path'] for c
in tested_apk_library_deps
]
274 p
for p
in deps_dex_files
if not p
in tested_apk_deps_dex_files
]
276 tested_apk_config
= GetDepConfig(options
.tested_apk_config
)
277 expected_tested_package
= tested_apk_config
['package_name']
278 AndroidManifest(options
.android_manifest
).CheckInstrumentation(
279 expected_tested_package
)
281 # Dependencies for the final dex file of an apk or a 'deps_dex'.
282 if options
.type in ['android_apk', 'deps_dex']:
283 config
['final_dex'] = {}
284 dex_config
= config
['final_dex']
285 # TODO(cjhopman): proguard version
286 dex_config
['dependency_dex_files'] = deps_dex_files
288 if options
.type == 'android_apk':
289 config
['dist_jar'] = {
291 c
['jar_path'] for c
in all_library_deps
294 manifest
= AndroidManifest(options
.android_manifest
)
295 deps_info
['package_name'] = manifest
.GetPackageName()
296 if not options
.tested_apk_config
and manifest
.GetInstrumentation():
297 # This must then have instrumentation only for itself.
298 manifest
.CheckInstrumentation(manifest
.GetPackageName())
301 java_libraries_list
= []
302 if options
.native_libs
:
303 libraries
= build_utils
.ParseGypList(options
.native_libs
)
305 libraries_dir
= os
.path
.dirname(libraries
[0])
306 write_ordered_libraries
.SetReadelfPath(options
.readelf_path
)
307 write_ordered_libraries
.SetLibraryDirs([libraries_dir
])
308 all_native_library_deps
= (
309 write_ordered_libraries
.GetSortedTransitiveDependenciesForBinaries(
311 # Create a java literal array with the "base" library names:
312 # e.g. libfoo.so -> foo
313 java_libraries_list
= '{%s}' % ','.join(
314 ['"%s"' % s
[3:-3] for s
in all_native_library_deps
])
316 write_ordered_libraries
.FullLibraryPath
, all_native_library_deps
)
319 'libraries': library_paths
,
320 'java_libraries_list': java_libraries_list
323 build_utils
.WriteJson(config
, options
.build_config
, only_if_changed
=True)
326 build_utils
.WriteDepfile(
328 all_deps_config_paths
+ build_utils
.GetPythonDependencies())
331 if __name__
== '__main__':
332 sys
.exit(main(sys
.argv
[1:]))