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_stamp_dump 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_stamp_dump", 330 "--files", 331 kati_stamp_file, 332 ]).decode().strip("\n").split("\n")) 333 334 def is_product_makefile(makefile): 335 """Returns True if the makefile path meets certain criteria.""" 336 banned_prefixes = [ 337 "out/", 338 # Ignore product makefiles for sample AOSP boards. 339 "device/amlogic", 340 "device/generic", 341 "device/google", 342 "device/linaro", 343 "device/sample", 344 ] 345 banned_suffixes = [ 346 # All Android.mk files in the source are always parsed by Kati, 347 # so including them here would bring in lots of unnecessary projects. 348 "Android.mk", 349 # The ckati stamp file always includes a line for the ckati bin at 350 # the beginnning. 351 "bin/ckati", 352 ] 353 return (all([not makefile.startswith(p) for p in banned_prefixes]) and 354 all([not makefile.endswith(s) for s in banned_suffixes])) 355 356 # Limit the makefiles to only product makefiles. 357 product_makefiles = { 358 os.path.normpath(path) for path in makefiles if is_product_makefile(path) 359 } 360 361 def strip_overlay(makefile): 362 """Remove any overlays from a makefile path.""" 363 for overlay in overlays: 364 if makefile.startswith(overlay): 365 return makefile[len(overlay):] 366 return makefile 367 368 makefiles_and_symlinks = set() 369 for makefile in product_makefiles: 370 # Search for the makefile, possibly scanning overlays as well. 371 for overlay in [""] + overlays: 372 makefile_with_overlay = os.path.join(overlay, makefile) 373 if os.path.exists(makefile_with_overlay): 374 makefile = makefile_with_overlay 375 break 376 377 if not os.path.exists(makefile): 378 logger.warning("Unknown kati makefile: %s" % makefile) 379 continue 380 381 # Ensure the project that contains the makefile is included, as well as 382 # the project that any makefile symlinks point to. 383 makefiles_and_symlinks.add(strip_overlay(makefile)) 384 if os.path.islink(makefile): 385 makefiles_and_symlinks.add( 386 strip_overlay(os.path.relpath(os.path.realpath(makefile)))) 387 388 return makefiles_and_symlinks 389 390 391def scan_repo_projects(repo_projects, input_path): 392 """Returns the project path of the given input path if it exists. 393 394 Args: 395 repo_projects: The output of the get_repo_projects function. 396 input_path: The path of an input file used in the build, as given by the 397 ninja inputs tool. 398 399 Returns: 400 The path string, or None if not found. 401 """ 402 parts = input_path.split("/") 403 404 for index in reversed(range(0, len(parts))): 405 project_path = os.path.join(*parts[:index + 1]) 406 if project_path in repo_projects: 407 return project_path 408 409 return None 410 411 412def get_input_projects(repo_projects, inputs): 413 """Returns the collection of project names that contain the given input paths. 414 415 Args: 416 repo_projects: The output of the get_repo_projects function. 417 inputs: The paths of input files used in the build, as given by the ninja 418 inputs tool. 419 """ 420 input_project_paths = {} 421 for input_path in inputs: 422 if not input_path.startswith("out/") and not input_path.startswith("/"): 423 input_project_paths.setdefault( 424 scan_repo_projects(repo_projects, input_path), []).append(input_path) 425 426 return { 427 repo_projects[project_path]: inputs 428 for project_path, inputs in input_project_paths.items() 429 if project_path is not None 430 } 431 432 433def update_manifest(manifest, input_projects, remove_projects): 434 """Modifies and returns a manifest ElementTree by modifying its projects. 435 436 Args: 437 manifest: The manifest object to modify. 438 input_projects: A set of projects that should stay in the manifest. 439 remove_projects: A set of projects that should be removed from the manifest. 440 Projects in this set override input_projects. 441 442 Returns: 443 The modified manifest object. 444 """ 445 projects_to_keep = input_projects.difference(remove_projects) 446 root = manifest.getroot() 447 for child in root.findall("project"): 448 if child.attrib["name"] not in projects_to_keep: 449 root.remove(child) 450 return manifest 451 452 453@dataclasses.dataclass 454class DebugInfo: 455 """Simple class to store structured debug info for a project.""" 456 direct_input: bool = False 457 adjacent_input: bool = False 458 deps_input: bool = False 459 kati_makefiles: List[str] = dataclasses.field(default_factory=list) 460 manual_add_config: str = "" 461 manual_remove_config: str = "" 462 463 464def create_split_manifest(targets, manifest_file, split_manifest_file, 465 config_files, repo_list_file, ninja_build_file, 466 ninja_binary, module_info_file, kati_stamp_file, 467 overlays, installed_prebuilts, debug_file): 468 """Creates and writes a split manifest by inspecting build inputs. 469 470 Args: 471 targets: List of targets that should be buildable using the split manifest. 472 manifest_file: Path to the repo manifest to split. 473 split_manifest_file: Path to write the resulting split manifest. 474 config_files: Paths to a config XML file containing projects to add or 475 remove. See default_config.xml for an example. This flag can be passed 476 more than once to use multiple config files. 477 repo_list_file: Path to the output of the 'repo list' command. 478 ninja_build_file: Path to the combined-<target>.ninja file found in an out 479 dir. 480 ninja_binary: Path to the ninja binary. 481 module_info_file: Path to the module-info.json file found in an out dir. 482 kati_stamp_file: The path to a .kati_stamp file from a build. 483 overlays: A list of paths to treat as overlays when parsing the kati stamp 484 file. 485 installed_prebuilts: A list of paths for which to create "fake" repo 486 entries. These entries allow the tool to recognize modules that installed 487 rather than being sync'd via a manifest. 488 debug_file: If not None, the path to write JSON debug info. 489 """ 490 debug_info = {} 491 492 config = ManifestSplitConfig.from_config_files(config_files) 493 original_manifest = ET.parse(manifest_file) 494 495 496 repo_projects = get_repo_projects(repo_list_file, original_manifest, 497 config.path_mappings) 498 repo_projects.update({ip: ip for ip in installed_prebuilts}) 499 500 inputs = get_ninja_inputs(ninja_binary, ninja_build_file, targets) 501 input_projects = set(get_input_projects(repo_projects, inputs).keys()) 502 for project in input_projects: 503 debug_info.setdefault(project, DebugInfo()).direct_input = True 504 logger.info( 505 "%s projects needed for Ninja-graph direct dependencies of targets \"%s\"", 506 len(input_projects), " ".join(targets)) 507 508 if kati_stamp_file: 509 kati_makefiles = get_kati_makefiles(kati_stamp_file, overlays) 510 kati_makefiles_projects = get_input_projects(repo_projects, kati_makefiles) 511 for project, makefiles in kati_makefiles_projects.items(): 512 debug_info.setdefault(project, DebugInfo()).kati_makefiles = makefiles 513 input_projects = input_projects.union(kati_makefiles_projects.keys()) 514 logger.info("%s projects after including Kati makefiles projects.", 515 len(input_projects)) 516 else: 517 logger.info("Kati makefiles projects skipped.") 518 519 for project, cfile in config.add_projects.items(): 520 debug_info.setdefault(project, DebugInfo()).manual_add_config = cfile 521 for project, cfile in config.remove_projects.items(): 522 debug_info.setdefault(project, DebugInfo()).manual_remove_config = cfile 523 input_projects = input_projects.union(config.add_projects.keys()) 524 logger.info("%s projects after including manual additions.", 525 len(input_projects)) 526 527 # Remove projects from our set of input projects before adding adjacent 528 # modules, so that no project is added only because of an adjacent 529 # dependency in a to-be-removed project. 530 input_projects = input_projects.difference(config.remove_projects.keys()) 531 532 # While we still have projects whose modules we haven't checked yet, 533 if module_info_file: 534 module_info = ModuleInfo(module_info_file, repo_projects, 535 config.ignore_paths) 536 checked_projects = set() 537 projects_to_check = input_projects.difference(checked_projects) 538 logger.info("Checking module-info dependencies for direct and adjacent modules...") 539 else: 540 logging.info("Direct and adjacent modules skipped.") 541 projects_to_check = None 542 543 iteration = 0 544 545 while projects_to_check: 546 iteration += 1 547 # check all modules in each project, 548 modules = [] 549 deps_additions = set() 550 551 def process_deps(module): 552 for d in module_info.module_deps[module]: 553 if d in module_info.module_class: 554 if module_info.module_class[d] == "HEADER_LIBRARIES": 555 hla = module_info.module_project[d] 556 if hla not in input_projects: 557 deps_additions.add(hla) 558 559 for project in projects_to_check: 560 checked_projects.add(project) 561 if project not in module_info.project_modules: 562 continue 563 for module in module_info.project_modules[project]: 564 modules.append(module) 565 process_deps(module) 566 567 for project in deps_additions: 568 debug_info.setdefault(project, DebugInfo()).deps_input = True 569 input_projects = input_projects.union(deps_additions) 570 logger.info( 571 "pass %d - %d projects after including HEADER_LIBRARIES dependencies", 572 iteration, len(input_projects)) 573 574 # adding those modules' input projects to our list of projects. 575 inputs = get_ninja_inputs(ninja_binary, ninja_build_file, modules) 576 adjacent_module_additions = set( 577 get_input_projects(repo_projects, inputs).keys()) 578 for project in adjacent_module_additions: 579 debug_info.setdefault(project, DebugInfo()).adjacent_input = True 580 input_projects = input_projects.union(adjacent_module_additions) 581 logger.info( 582 "pass %d - %d projects after including adjacent-module Ninja-graph dependencies", 583 iteration, len(input_projects)) 584 585 projects_to_check = input_projects.difference(checked_projects) 586 587 logger.info("%s projects - complete", len(input_projects)) 588 589 split_manifest = update_manifest(original_manifest, input_projects, 590 config.remove_projects.keys()) 591 split_manifest.write(split_manifest_file) 592 593 if debug_file: 594 with open(debug_file, "w") as debug_fp: 595 logger.info("Writing debug info to %s", debug_file) 596 json.dump( 597 debug_info, 598 fp=debug_fp, 599 sort_keys=True, 600 indent=2, 601 default=lambda info: info.__dict__) 602 603 604def main(argv): 605 try: 606 opts, args = getopt.getopt(argv, "h", [ 607 "help", 608 "debug-file=", 609 "manifest=", 610 "split-manifest=", 611 "config=", 612 "ignore-default-config", 613 "repo-list=", 614 "ninja-build=", 615 "ninja-binary=", 616 "module-info=", 617 "skip-module-info", 618 "kati-stamp=", 619 "skip-kati", 620 "overlay=", 621 "installed-prebuilt=", 622 ]) 623 except getopt.GetoptError as err: 624 print(__doc__, file=sys.stderr) 625 print("**%s**" % str(err), file=sys.stderr) 626 sys.exit(2) 627 628 debug_file = None 629 manifest_file = None 630 split_manifest_file = None 631 config_files = [] 632 repo_list_file = None 633 ninja_build_file = None 634 module_info_file = None 635 ninja_binary = "prebuilts/build-tools/linux-x86/bin/ninja" 636 kati_stamp_file = None 637 overlays = [] 638 installed_prebuilts = [] 639 ignore_default_config = False 640 skip_kati = False 641 skip_module_info = False 642 643 for o, a in opts: 644 if o in ("-h", "--help"): 645 print(__doc__, file=sys.stderr) 646 sys.exit() 647 elif o in ("--debug-file"): 648 debug_file = a 649 elif o in ("--manifest"): 650 manifest_file = a 651 elif o in ("--split-manifest"): 652 split_manifest_file = a 653 elif o in ("--config"): 654 config_files.append(a) 655 elif o == "--ignore-default-config": 656 ignore_default_config = True 657 elif o in ("--repo-list"): 658 repo_list_file = a 659 elif o in ("--ninja-build"): 660 ninja_build_file = a 661 elif o in ("--ninja-binary"): 662 ninja_binary = a 663 elif o in ("--module-info"): 664 module_info_file = a 665 elif o == "--skip-module-info": 666 skip_module_info = True 667 elif o in ("--kati-stamp"): 668 kati_stamp_file = a 669 elif o == "--skip-kati": 670 skip_kati = True 671 elif o in ("--overlay"): 672 overlays.append(a) 673 elif o in ("--installed-prebuilt"): 674 installed_prebuilts.append(a) 675 else: 676 assert False, "unknown option \"%s\"" % o 677 678 if not args: 679 print(__doc__, file=sys.stderr) 680 print("**Missing targets**", file=sys.stderr) 681 sys.exit(2) 682 if not manifest_file: 683 print(__doc__, file=sys.stderr) 684 print("**Missing required flag --manifest**", file=sys.stderr) 685 sys.exit(2) 686 if not split_manifest_file: 687 print(__doc__, file=sys.stderr) 688 print("**Missing required flag --split-manifest**", file=sys.stderr) 689 sys.exit(2) 690 691 if skip_module_info: 692 if module_info_file: 693 logging.warning("User provided both --skip-module-info and --module-info args. Arg --module-info ignored.") 694 module_info_file = None 695 elif not module_info_file: 696 module_info_file = os.path.join(os.environ["ANDROID_PRODUCT_OUT"], 697 "module-info.json") 698 if skip_kati: 699 if kati_stamp_file: 700 logging.warning("User provided both --skip-kati and --kati-stamp args. Arg --kati-stamp ignored.") 701 kati_stamp_file = None 702 elif not kati_stamp_file: 703 kati_stamp_file = os.path.join( 704 os.environ["ANDROID_BUILD_TOP"], "out", 705 ".kati_stamp-%s" % os.environ["TARGET_PRODUCT"]) 706 707 if not ninja_build_file: 708 ninja_build_file = os.path.join( 709 os.environ["ANDROID_BUILD_TOP"], "out", 710 "combined-%s.ninja" % os.environ["TARGET_PRODUCT"]) 711 712 with tempfile.NamedTemporaryFile() as default_config_file: 713 if not ignore_default_config: 714 default_config_file.write(pkgutil.get_data(__name__, DEFAULT_CONFIG_XML)) 715 default_config_file.flush() 716 config_files.insert(0, default_config_file.name) 717 718 create_split_manifest( 719 targets=args, 720 manifest_file=manifest_file, 721 split_manifest_file=split_manifest_file, 722 config_files=config_files, 723 repo_list_file=repo_list_file, 724 ninja_build_file=ninja_build_file, 725 ninja_binary=ninja_binary, 726 module_info_file=module_info_file, 727 kati_stamp_file=kati_stamp_file, 728 overlays=overlays, 729 installed_prebuilts=installed_prebuilts, 730 debug_file=debug_file) 731 732 733if __name__ == "__main__": 734 main(sys.argv[1:]) 735