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 |