1# Copyright (C) 2020 The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14"""Splits a manifest to the minimum set of projects needed to build the targets. 15 16Usage: manifest_split [options] targets 17 18targets: Space-separated list of targets that should be buildable 19 using the split manifest. 20 21options: 22 --manifest <path> 23 Path to the repo manifest to split. [Required] 24 --split-manifest <path> 25 Path to write the resulting split manifest. [Required] 26 --config <path> 27 Optional path(s) to a config XML file containing projects to add or 28 remove. See default_config.xml for an example. This flag can be passed 29 more than once to use multiple config files. 30 Sample file my_config.xml: 31 <config> 32 <add_project name="vendor/my/needed/project" /> 33 <remove_project name="vendor/my/unused/project" /> 34 </config> 35 --ignore-default-config 36 If provided, don't include default_config.xml. 37 --installed-prebuilt 38 Specify the directory containing an installed prebuilt Android.bp file. 39 Supply this option zero or more times, once for each installed prebuilt 40 directory. 41 --repo-list <path> 42 Optional path to the output of the 'repo list' command. Used if the 43 output of 'repo list' needs pre-processing before being used by 44 this tool. 45 --ninja-build <path> 46 Optional path to the combined-<target>.ninja file found in an out dir. 47 If not provided, the default file is used based on the lunch environment. 48 --ninja-binary <path> 49 Optional path to the ninja binary. Uses the standard binary by default. 50 --module-info <path> 51 Optional path to the module-info.json file found in an out dir. 52 If not provided, the default file is used based on the lunch environment. 53 --skip-module-info 54 If provided, skip parsing module-info.json for direct and adjacent 55 dependencies. Overrides --module-info option. 56 --kati-stamp <path> 57 Optional path to the .kati_stamp file found in an out dir. 58 If not provided, the default file is used based on the lunch environment. 59 --skip-kati 60 If provided, skip Kati makefiles projects. Overrides --kati-stamp option. 61 --overlay <path> 62 Optional path(s) to treat as overlays when parsing the kati stamp file 63 and scanning for makefiles. See the tools/treble/build/sandbox directory 64 for more info about overlays. This flag can be passed more than once. 65 --debug-file <path> 66 If provided, debug info will be written to a JSON file at this path. 67 -h (--help) 68 Display this usage message and exit. 69""" 70 71from __future__ import print_function 72 73import getopt 74import json 75import logging 76import os 77import pkgutil 78import re 79import subprocess 80import sys 81import tempfile 82from typing import Dict, List, Pattern, Set, Tuple 83import xml.etree.ElementTree as ET 84 85import dataclasses 86 87 88logging.basicConfig( 89 stream=sys.stdout, 90 level=logging.INFO, 91 format="%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s", 92 datefmt="%Y-%m-%d %H:%M:%S") 93logger = logging.getLogger(os.path.basename(__file__)) 94 95# Projects determined to be needed despite the dependency not being visible 96# to ninja. 97DEFAULT_CONFIG_XML = "default_config.xml" 98 99# Pattern that matches a java dependency. 100_JAVA_LIB_PATTERN = re.compile( 101 # pylint: disable=line-too-long 102 '^out/target/common/obj/JAVA_LIBRARIES/(.+)_intermediates/classes-header.jar$' 103) 104 105 106@dataclasses.dataclass 107class PathMappingConfig: 108 pattern: Pattern[str] 109 sub: str 110 111 112@dataclasses.dataclass 113class ManifestSplitConfig: 114 """Holds the configuration for the split manifest tool. 115 116 Attributes: 117 remove_projects: A Dict of project name to the config file that specified 118 this project, for projects that should be removed from the resulting 119 manifest. 120 add_projects: A Dict of project name to the config file that specified 121 this project, for projects that should be added to the resulting manifest. 122 path_mappings: A list of PathMappingConfigs to modify a path in the build 123 sandbox to the path in the manifest. 124 ignore_paths: Set of paths to ignore when parsing module_info_file 125 """ 126 remove_projects: Dict[str, str] 127 add_projects: Dict[str, str] 128 path_mappings: List[PathMappingConfig] 129 ignore_paths: Set[str] 130 131 @classmethod 132 def from_config_files(cls, config_files: List[str]): 133 """Reads from a list of config XML files. 134 135 Args: 136 config_files: A list of config XML filenames. 137 138 Returns: 139 A ManifestSplitConfig from the files. 140 """ 141 remove_projects: Dict[str, str] = {} 142 add_projects: Dict[str, str] = {} 143 path_mappings = [] 144 """ Always ignore paths in out/ directory. """ 145 ignore_paths = set(["out/"]) 146 for config_file in config_files: 147 root = ET.parse(config_file).getroot() 148 149 remove_projects.update({ 150 c.attrib["name"]: config_file for c in root.findall("remove_project") 151 }) 152 153 add_projects.update( 154 {c.attrib["name"]: config_file for c in root.findall("add_project")}) 155 156 path_mappings.extend([ 157 PathMappingConfig( 158 re.compile(child.attrib["pattern"]), child.attrib["sub"]) 159 for child in root.findall("path_mapping") 160 ]) 161 162 ignore_paths.update( 163 {c.attrib["name"]: config_file for c in root.findall("ignore_path")}) 164 165 return cls(remove_projects, add_projects, path_mappings, ignore_paths) 166 167 168def get_repo_projects(repo_list_file, manifest, path_mappings): 169 """Returns a dict of { project path : project name } using the manifest. 170 171 The path_mappings stop on the first match mapping. If the mapping results in 172 an empty string, that entry is removed. 173 174 Args: 175 repo_list_file: An optional filename to read instead of parsing the manifest. 176 manifest: The manifest object to scan for projects. 177 path_mappings: A list of PathMappingConfigs to modify a path in the build 178 sandbox to the path in the manifest. 179 """ 180 repo_list = [] 181 182 if repo_list_file: 183 with open(repo_list_file) as repo_list_lines: 184 repo_list = [line.strip().split(" : ") for line in repo_list_lines if line.strip()] 185 else: 186 root = manifest.getroot() 187 repo_list = [(p.get("path", p.get("name")), p.get("name")) for p in root.findall("project")] 188 189 repo_dict = {} 190 for entry in repo_list: 191 path, project = entry 192 for mapping in path_mappings: 193 if mapping.pattern.fullmatch(path): 194 path = mapping.pattern.sub(mapping.sub, path) 195 break 196 # If the resulting path mapping is empty, then don't add entry 197 if path: 198 repo_dict[path] = project 199 return repo_dict 200 201 202class ModuleInfo: 203 """Contains various mappings to/from module/project""" 204 205 def __init__(self, module_info_file, repo_projects, ignore_paths): 206 """Initialize a module info instance. 207 208 Builds various maps related to platform build system modules and how they 209 relate to each other and projects. 210 211 Args: 212 module_info_file: The path to a module-info.json file from a build. 213 repo_projects: The output of the get_repo_projects function. 214 ignore_paths: Set of paths to ignore from module_info_file data 215 216 Raises: 217 ValueError: A module from module-info.json belongs to a path not 218 known by the repo projects output. 219 """ 220 # Maps a project to the set of modules it contains. 221 self.project_modules = {} 222 # Maps a module to the project that contains it. 223 self.module_project = {} 224 # Maps a module to its class. 225 self.module_class = {} 226 # Maps a module to modules it depends on. 227 self.module_deps = {} 228 229 with open(module_info_file) as module_info_file: 230 module_info = json.load(module_info_file) 231 232 # Check that module contains a path and the path is not in set of 233 # ignore paths 234 def module_has_valid_path(module): 235 paths = module.get("path") 236 if not paths: 237 return False 238 return all(not paths[0].startswith(p) for p in ignore_paths) 239 240 module_paths = { 241 module: module_info[module]["path"][0] 242 for module in module_info 243 if module_has_valid_path(module_info[module]) 244 } 245 module_project_paths = { 246 module: scan_repo_projects(repo_projects, module_paths[module]) 247 for module in module_paths 248 } 249 250 for module, project_path in module_project_paths.items(): 251 if not project_path: 252 raise ValueError("Unknown module path for module %s: %s" % 253 (module, module_info[module])) 254 repo_project = repo_projects[project_path] 255 self.project_modules.setdefault(repo_project, set()).add(module) 256 self.module_project[module] = repo_project 257 258 def dep_from_raw_dep(raw_dep): 259 match = re.search(_JAVA_LIB_PATTERN, raw_dep) 260 return match.group(1) if match else raw_dep 261 262 def deps_from_raw_deps(raw_deps): 263 return [dep_from_raw_dep(raw_dep) for raw_dep in raw_deps] 264 265 self.module_class = { 266 module: module_info[module]["class"][0] 267 for module in module_info 268 } 269 self.module_deps = { 270 module: deps_from_raw_deps(module_info[module]["dependencies"]) 271 for module in module_info 272 } 273 274 275def get_ninja_inputs(ninja_binary, ninja_build_file, modules): 276 """Returns the set of input file path strings for the given modules. 277 278 Uses the `ninja -t inputs` tool. 279 280 Args: 281 ninja_binary: The path to a ninja binary. 282 ninja_build_file: The path to a .ninja file from a build. 283 modules: The list of modules to scan for inputs. 284 """ 285 inputs = set() 286 NINJA_SHARD_LIMIT = 20000 287 for i in range(0, len(modules), NINJA_SHARD_LIMIT): 288 modules_shard = modules[i:i + NINJA_SHARD_LIMIT] 289 inputs = inputs.union(set( 290 subprocess.check_output([ 291 ninja_binary, 292 "-f", 293 ninja_build_file, 294 "-t", 295 "inputs", 296 "-d", 297 ] + list(modules_shard)).decode().strip("\n").split("\n"))) 298 299 def input_allowed(path): 300 path = path.strip() 301 if path.endswith("TEST_MAPPING") and "test_mapping" not in modules: 302 # Exclude projects that are only needed for TEST_MAPPING files, unless the 303 # user is asking to build 'test_mapping'. 304 return False 305 if path.endswith("MODULE_LICENSE_GPL"): 306 # Exclude projects that are included only due to having a 307 # MODULE_LICENSE_GPL file, if no other inputs from that project are used. 308 return False 309 return path 310 311 return {path.strip() for path in inputs if input_allowed(path)} 312 313 314def get_kati_makefiles(kati_stamp_file, overlays): 315 """Returns the set of makefile paths from the kati stamp file. 316 317 Uses the ckati prebuilt binary. 318 Also includes symlink sources in the resulting set for any 319 makefiles that are symlinks. 320 321 Args: 322 kati_stamp_file: The path to a .kati_stamp file from a build. 323 overlays: A list of paths to treat as overlays when parsing the kati stamp 324 file. 325 """ 326 # Get a set of all makefiles that were parsed by Kati during the build. 327 makefiles = set( 328 subprocess.check_output([ 329 "prebuilts/build-tools/linux-x86/bin/ckati", 330 "--dump_stamp_tool", 331 "--files", 332 kati_stamp_file, 333 ]).decode().strip("\n").split("\n")) 334 335 def is_product_makefile(makefile): 336 """Returns True if the makefile path meets certain criteria.""" 337 banned_prefixes = [ 338 "out/", 339 # Ignore product makefiles for sample AOSP boards. 340 "device/amlogic", 341 "device/generic", 342 "device/google", 343 "device/linaro", 344 "device/sample", 345 ] 346 banned_suffixes = [ 347 # All Android.mk files in the source are always parsed by Kati, 348 # so including them here would bring in lots of unnecessary projects. 349 "Android.mk", 350 # The ckati stamp file always includes a line for the ckati bin at 351 # the beginnning. 352 "bin/ckati", 353 ] 354 return (all([not makefile.startswith(p) for p in banned_prefixes]) and 355 all([not makefile.endswith(s) for s in banned_suffixes])) 356 357 # Limit the makefiles to only product makefiles. 358 product_makefiles = { 359 os.path.normpath(path) for path in makefiles if is_product_makefile(path) 360 } 361 362 def strip_overlay(makefile): 363 """Remove any overlays from a makefile path.""" 364 for overlay in overlays: 365 if makefile.startswith(overlay): 366 return makefile[len(overlay):] 367 return makefile 368 369 makefiles_and_symlinks = set() 370 for makefile in product_makefiles: 371 # Search for the makefile, possibly scanning overlays as well. 372 for overlay in [""] + overlays: 373 makefile_with_overlay = os.path.join(overlay, makefile) 374 if os.path.exists(makefile_with_overlay): 375 makefile = makefile_with_overlay 376 break 377 378 if not os.path.exists(makefile): 379 logger.warning("Unknown kati makefile: %s" % makefile) 380 continue 381 382 # Ensure the project that contains the makefile is included, as well as 383 # the project that any makefile symlinks point to. 384 makefiles_and_symlinks.add(strip_overlay(makefile)) 385 if os.path.islink(makefile): 386 makefiles_and_symlinks.add( 387 strip_overlay(os.path.relpath(os.path.realpath(makefile)))) 388 389 return makefiles_and_symlinks 390 391 392def scan_repo_projects(repo_projects, input_path): 393 """Returns the project path of the given input path if it exists. 394 395 Args: 396 repo_projects: The output of the get_repo_projects function. 397 input_path: The path of an input file used in the build, as given by the 398 ninja inputs tool. 399 400 Returns: 401 The path string, or None if not found. 402 """ 403 parts = input_path.split("/") 404 405 for index in reversed(range(0, len(parts))): 406 project_path = os.path.join(*parts[:index + 1]) 407 if project_path in repo_projects: 408 return project_path 409 410 return None 411 412 413def get_input_projects(repo_projects, inputs): 414 """Returns the collection of project names that contain the given input paths. 415 416 Args: 417 repo_projects: The output of the get_repo_projects function. 418 inputs: The paths of input files used in the build, as given by the ninja 419 inputs tool. 420 """ 421 input_project_paths = {} 422 for input_path in inputs: 423 if not input_path.startswith("out/") and not input_path.startswith("/"): 424 input_project_paths.setdefault( 425 scan_repo_projects(repo_projects, input_path), []).append(input_path) 426 427 return { 428 repo_projects[project_path]: inputs 429 for project_path, inputs in input_project_paths.items() 430 if project_path is not None 431 } 432 433 434def update_manifest(manifest, input_projects, remove_projects): 435 """Modifies and returns a manifest ElementTree by modifying its projects. 436 437 Args: 438 manifest: The manifest object to modify. 439 input_projects: A set of projects that should stay in the manifest. 440 remove_projects: A set of projects that should be removed from the manifest. 441 Projects in this set override input_projects. 442 443 Returns: 444 The modified manifest object. 445 """ 446 projects_to_keep = input_projects.difference(remove_projects) 447 root = manifest.getroot() 448 for child in root.findall("project"): 449 if child.attrib["name"] not in projects_to_keep: 450 root.remove(child) 451 return manifest 452 453 454@dataclasses.dataclass 455class DebugInfo: 456 """Simple class to store structured debug info for a project.""" 457 direct_input: bool = False 458 adjacent_input: bool = False 459 deps_input: bool = False 460 kati_makefiles: List[str] = dataclasses.field(default_factory=list) 461 manual_add_config: str = "" 462 manual_remove_config: str = "" 463 464 465def create_split_manifest(targets, manifest_file, split_manifest_file, 466 config_files, repo_list_file, ninja_build_file, 467 ninja_binary, module_info_file, kati_stamp_file, 468 overlays, installed_prebuilts, debug_file): 469 """Creates and writes a split manifest by inspecting build inputs. 470 471 Args: 472 targets: List of targets that should be buildable using the split manifest. 473 manifest_file: Path to the repo manifest to split. 474 split_manifest_file: Path to write the resulting split manifest. 475 config_files: Paths to a config XML file containing projects to add or 476 remove. See default_config.xml for an example. This flag can be passed 477 more than once to use multiple config files. 478 repo_list_file: Path to the output of the 'repo list' command. 479 ninja_build_file: Path to the combined-<target>.ninja file found in an out 480 dir. 481 ninja_binary: Path to the ninja binary. 482 module_info_file: Path to the module-info.json file found in an out dir. 483 kati_stamp_file: The path to a .kati_stamp file from a build. 484 overlays: A list of paths to treat as overlays when parsing the kati stamp 485 file. 486 installed_prebuilts: A list of paths for which to create "fake" repo 487 entries. These entries allow the tool to recognize modules that installed 488 rather than being sync'd via a manifest. 489 debug_file: If not None, the path to write JSON debug info. 490 """ 491 debug_info = {} 492 493 config = ManifestSplitConfig.from_config_files(config_files) 494 original_manifest = ET.parse(manifest_file) 495 496 497 repo_projects = get_repo_projects(repo_list_file, original_manifest, 498 config.path_mappings) 499 repo_projects.update({ip: ip for ip in installed_prebuilts}) 500 501 inputs = get_ninja_inputs(ninja_binary, ninja_build_file, targets) 502 input_projects = set(get_input_projects(repo_projects, inputs).keys()) 503 for project in input_projects: 504 debug_info.setdefault(project, DebugInfo()).direct_input = True 505 logger.info( 506 "%s projects needed for Ninja-graph direct dependencies of targets \"%s\"", 507 len(input_projects), " ".join(targets)) 508 509 if kati_stamp_file: 510 kati_makefiles = get_kati_makefiles(kati_stamp_file, overlays) 511 kati_makefiles_projects = get_input_projects(repo_projects, kati_makefiles) 512 for project, makefiles in kati_makefiles_projects.items(): 513 debug_info.setdefault(project, DebugInfo()).kati_makefiles = makefiles 514 input_projects = input_projects.union(kati_makefiles_projects.keys()) 515 logger.info("%s projects after including Kati makefiles projects.", 516 len(input_projects)) 517 else: 518 logger.info("Kati makefiles projects skipped.") 519 520 for project, cfile in config.add_projects.items(): 521 debug_info.setdefault(project, DebugInfo()).manual_add_config = cfile 522 for project, cfile in config.remove_projects.items(): 523 debug_info.setdefault(project, DebugInfo()).manual_remove_config = cfile 524 input_projects = input_projects.union(config.add_projects.keys()) 525 logger.info("%s projects after including manual additions.", 526 len(input_projects)) 527 528 # Remove projects from our set of input projects before adding adjacent 529 # modules, so that no project is added only because of an adjacent 530 # dependency in a to-be-removed project. 531 input_projects = input_projects.difference(config.remove_projects.keys()) 532 533 # While we still have projects whose modules we haven't checked yet, 534 if module_info_file: 535 module_info = ModuleInfo(module_info_file, repo_projects, 536 config.ignore_paths) 537 checked_projects = set() 538 projects_to_check = input_projects.difference(checked_projects) 539 logger.info("Checking module-info dependencies for direct and adjacent modules...") 540 else: 541 logging.info("Direct and adjacent modules skipped.") 542 projects_to_check = None 543 544 iteration = 0 545 546 while projects_to_check: 547 iteration += 1 548 # check all modules in each project, 549 modules = [] 550 deps_additions = set() 551 552 def process_deps(module): 553 for d in module_info.module_deps[module]: 554 if d in module_info.module_class: 555 if module_info.module_class[d] == "HEADER_LIBRARIES": 556 hla = module_info.module_project[d] 557 if hla not in input_projects: 558 deps_additions.add(hla) 559 560 for project in projects_to_check: 561 checked_projects.add(project) 562 if project not in module_info.project_modules: 563 continue 564 for module in module_info.project_modules[project]: 565 modules.append(module) 566 process_deps(module) 567 568 for project in deps_additions: 569 debug_info.setdefault(project, DebugInfo()).deps_input = True 570 input_projects = input_projects.union(deps_additions) 571 logger.info( 572 "pass %d - %d projects after including HEADER_LIBRARIES dependencies", 573 iteration, len(input_projects)) 574 575 # adding those modules' input projects to our list of projects. 576 inputs = get_ninja_inputs(ninja_binary, ninja_build_file, modules) 577 adjacent_module_additions = set( 578 get_input_projects(repo_projects, inputs).keys()) 579 for project in adjacent_module_additions: 580 debug_info.setdefault(project, DebugInfo()).adjacent_input = True 581 input_projects = input_projects.union(adjacent_module_additions) 582 logger.info( 583 "pass %d - %d projects after including adjacent-module Ninja-graph dependencies", 584 iteration, len(input_projects)) 585 586 projects_to_check = input_projects.difference(checked_projects) 587 588 logger.info("%s projects - complete", len(input_projects)) 589 590 split_manifest = update_manifest(original_manifest, input_projects, 591 config.remove_projects.keys()) 592 split_manifest.write(split_manifest_file) 593 594 if debug_file: 595 with open(debug_file, "w") as debug_fp: 596 logger.info("Writing debug info to %s", debug_file) 597 json.dump( 598 debug_info, 599 fp=debug_fp, 600 sort_keys=True, 601 indent=2, 602 default=lambda info: info.__dict__) 603 604 605def main(argv): 606 try: 607 opts, args = getopt.getopt(argv, "h", [ 608 "help", 609 "debug-file=", 610 "manifest=", 611 "split-manifest=", 612 "config=", 613 "ignore-default-config", 614 "repo-list=", 615 "ninja-build=", 616 "ninja-binary=", 617 "module-info=", 618 "skip-module-info", 619 "kati-stamp=", 620 "skip-kati", 621 "overlay=", 622 "installed-prebuilt=", 623 ]) 624 except getopt.GetoptError as err: 625 print(__doc__, file=sys.stderr) 626 print("**%s**" % str(err), file=sys.stderr) 627 sys.exit(2) 628 629 debug_file = None 630 manifest_file = None 631 split_manifest_file = None 632 config_files = [] 633 repo_list_file = None 634 ninja_build_file = None 635 module_info_file = None 636 ninja_binary = "prebuilts/build-tools/linux-x86/bin/ninja" 637 kati_stamp_file = None 638 overlays = [] 639 installed_prebuilts = [] 640 ignore_default_config = False 641 skip_kati = False 642 skip_module_info = False 643 644 for o, a in opts: 645 if o in ("-h", "--help"): 646 print(__doc__, file=sys.stderr) 647 sys.exit() 648 elif o in ("--debug-file"): 649 debug_file = a 650 elif o in ("--manifest"): 651 manifest_file = a 652 elif o in ("--split-manifest"): 653 split_manifest_file = a 654 elif o in ("--config"): 655 config_files.append(a) 656 elif o == "--ignore-default-config": 657 ignore_default_config = True 658 elif o in ("--repo-list"): 659 repo_list_file = a 660 elif o in ("--ninja-build"): 661 ninja_build_file = a 662 elif o in ("--ninja-binary"): 663 ninja_binary = a 664 elif o in ("--module-info"): 665 module_info_file = a 666 elif o == "--skip-module-info": 667 skip_module_info = True 668 elif o in ("--kati-stamp"): 669 kati_stamp_file = a 670 elif o == "--skip-kati": 671 skip_kati = True 672 elif o in ("--overlay"): 673 overlays.append(a) 674 elif o in ("--installed-prebuilt"): 675 installed_prebuilts.append(a) 676 else: 677 assert False, "unknown option \"%s\"" % o 678 679 if not args: 680 print(__doc__, file=sys.stderr) 681 print("**Missing targets**", file=sys.stderr) 682 sys.exit(2) 683 if not manifest_file: 684 print(__doc__, file=sys.stderr) 685 print("**Missing required flag --manifest**", file=sys.stderr) 686 sys.exit(2) 687 if not split_manifest_file: 688 print(__doc__, file=sys.stderr) 689 print("**Missing required flag --split-manifest**", file=sys.stderr) 690 sys.exit(2) 691 692 if skip_module_info: 693 if module_info_file: 694 logging.warning("User provided both --skip-module-info and --module-info args. Arg --module-info ignored.") 695 module_info_file = None 696 elif not module_info_file: 697 module_info_file = os.path.join(os.environ["ANDROID_PRODUCT_OUT"], 698 "module-info.json") 699 if skip_kati: 700 if kati_stamp_file: 701 logging.warning("User provided both --skip-kati and --kati-stamp args. Arg --kati-stamp ignored.") 702 kati_stamp_file = None 703 elif not kati_stamp_file: 704 kati_stamp_file = os.path.join( 705 os.environ["ANDROID_BUILD_TOP"], "out", 706 ".kati_stamp-%s" % os.environ["TARGET_PRODUCT"]) 707 708 if not ninja_build_file: 709 ninja_build_file = os.path.join( 710 os.environ["ANDROID_BUILD_TOP"], "out", 711 "combined-%s.ninja" % os.environ["TARGET_PRODUCT"]) 712 713 with tempfile.NamedTemporaryFile() as default_config_file: 714 if not ignore_default_config: 715 default_config_file.write(pkgutil.get_data(__name__, DEFAULT_CONFIG_XML)) 716 default_config_file.flush() 717 config_files.insert(0, default_config_file.name) 718 719 create_split_manifest( 720 targets=args, 721 manifest_file=manifest_file, 722 split_manifest_file=split_manifest_file, 723 config_files=config_files, 724 repo_list_file=repo_list_file, 725 ninja_build_file=ninja_build_file, 726 ninja_binary=ninja_binary, 727 module_info_file=module_info_file, 728 kati_stamp_file=kati_stamp_file, 729 overlays=overlays, 730 installed_prebuilts=installed_prebuilts, 731 debug_file=debug_file) 732 733 734if __name__ == "__main__": 735 main(sys.argv[1:]) 736