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 """ 125 remove_projects: Dict[str, str] 126 add_projects: Dict[str, str] 127 path_mappings: List[PathMappingConfig] 128 129 @classmethod 130 def from_config_files(cls, config_files: List[str]): 131 """Reads from a list of config XML files. 132 133 Args: 134 config_files: A list of config XML filenames. 135 136 Returns: 137 A ManifestSplitConfig from the files. 138 """ 139 remove_projects: Dict[str, str] = {} 140 add_projects: Dict[str, str] = {} 141 path_mappings = [] 142 for config_file in config_files: 143 root = ET.parse(config_file).getroot() 144 145 remove_projects.update({ 146 c.attrib["name"]: config_file for c in root.findall("remove_project") 147 }) 148 149 add_projects.update( 150 {c.attrib["name"]: config_file for c in root.findall("add_project")}) 151 152 path_mappings.extend([ 153 PathMappingConfig( 154 re.compile(child.attrib["pattern"]), child.attrib["sub"]) 155 for child in root.findall("path_mapping") 156 ]) 157 158 return cls(remove_projects, add_projects, path_mappings) 159 160 161def get_repo_projects(repo_list_file, manifest, path_mappings): 162 """Returns a dict of { project path : project name } using the manifest. 163 164 The path_mappings stop on the first match mapping. If the mapping results in 165 an empty string, that entry is removed. 166 167 Args: 168 repo_list_file: An optional filename to read instead of parsing the manifest. 169 manifest: The manifest object to scan for projects. 170 path_mappings: A list of PathMappingConfigs to modify a path in the build 171 sandbox to the path in the manifest. 172 """ 173 repo_list = [] 174 175 if repo_list_file: 176 with open(repo_list_file) as repo_list_lines: 177 repo_list = [line.strip().split(" : ") for line in repo_list_lines if line.strip()] 178 else: 179 root = manifest.getroot() 180 repo_list = [(p.get("path", p.get("name")), p.get("name")) for p in root.findall("project")] 181 182 repo_dict = {} 183 for entry in repo_list: 184 path, project = entry 185 for mapping in path_mappings: 186 if mapping.pattern.fullmatch(path): 187 path = mapping.pattern.sub(mapping.sub, path) 188 break 189 # If the resulting path mapping is empty, then don't add entry 190 if path: 191 repo_dict[path] = project 192 return repo_dict 193 194 195class ModuleInfo: 196 """Contains various mappings to/from module/project""" 197 198 def __init__(self, module_info_file, repo_projects): 199 """Initialize a module info instance. 200 201 Builds various maps related to platform build system modules and how they 202 relate to each other and projects. 203 204 Args: 205 module_info_file: The path to a module-info.json file from a build. 206 repo_projects: The output of the get_repo_projects function. 207 208 Raises: 209 ValueError: A module from module-info.json belongs to a path not 210 known by the repo projects output. 211 """ 212 # Maps a project to the set of modules it contains. 213 self.project_modules = {} 214 # Maps a module to the project that contains it. 215 self.module_project = {} 216 # Maps a module to its class. 217 self.module_class = {} 218 # Maps a module to modules it depends on. 219 self.module_deps = {} 220 221 with open(module_info_file) as module_info_file: 222 module_info = json.load(module_info_file) 223 224 def module_has_valid_path(module): 225 return ("path" in module_info[module] and module_info[module]["path"] and 226 not module_info[module]["path"][0].startswith("out/")) 227 228 module_paths = { 229 module: module_info[module]["path"][0] 230 for module in module_info 231 if module_has_valid_path(module) 232 } 233 module_project_paths = { 234 module: scan_repo_projects(repo_projects, module_paths[module]) 235 for module in module_paths 236 } 237 238 for module, project_path in module_project_paths.items(): 239 if not project_path: 240 raise ValueError("Unknown module path for module %s: %s" % 241 (module, module_info[module])) 242 repo_project = repo_projects[project_path] 243 self.project_modules.setdefault(repo_project, set()).add(module) 244 self.module_project[module] = repo_project 245 246 def dep_from_raw_dep(raw_dep): 247 match = re.search(_JAVA_LIB_PATTERN, raw_dep) 248 return match.group(1) if match else raw_dep 249 250 def deps_from_raw_deps(raw_deps): 251 return [dep_from_raw_dep(raw_dep) for raw_dep in raw_deps] 252 253 self.module_class = { 254 module: module_info[module]["class"][0] 255 for module in module_info 256 } 257 self.module_deps = { 258 module: deps_from_raw_deps(module_info[module]["dependencies"]) 259 for module in module_info 260 } 261 262 263def get_ninja_inputs(ninja_binary, ninja_build_file, modules): 264 """Returns the set of input file path strings for the given modules. 265 266 Uses the `ninja -t inputs` tool. 267 268 Args: 269 ninja_binary: The path to a ninja binary. 270 ninja_build_file: The path to a .ninja file from a build. 271 modules: The list of modules to scan for inputs. 272 """ 273 inputs = set() 274 NINJA_SHARD_LIMIT = 20000 275 for i in range(0, len(modules), NINJA_SHARD_LIMIT): 276 modules_shard = modules[i:i + NINJA_SHARD_LIMIT] 277 inputs = inputs.union(set( 278 subprocess.check_output([ 279 ninja_binary, 280 "-f", 281 ninja_build_file, 282 "-t", 283 "inputs", 284 "-d", 285 ] + list(modules_shard)).decode().strip("\n").split("\n"))) 286 287 def input_allowed(path): 288 path = path.strip() 289 if path.endswith("TEST_MAPPING") and "test_mapping" not in modules: 290 # Exclude projects that are only needed for TEST_MAPPING files, unless the 291 # user is asking to build 'test_mapping'. 292 return False 293 if path.endswith("MODULE_LICENSE_GPL"): 294 # Exclude projects that are included only due to having a 295 # MODULE_LICENSE_GPL file, if no other inputs from that project are used. 296 return False 297 return path 298 299 return {path.strip() for path in inputs if input_allowed(path)} 300 301 302def get_kati_makefiles(kati_stamp_file, overlays): 303 """Returns the set of makefile paths from the kati stamp file. 304 305 Uses the ckati_stamp_dump prebuilt binary. 306 Also includes symlink sources in the resulting set for any 307 makefiles that are symlinks. 308 309 Args: 310 kati_stamp_file: The path to a .kati_stamp file from a build. 311 overlays: A list of paths to treat as overlays when parsing the kati stamp 312 file. 313 """ 314 # Get a set of all makefiles that were parsed by Kati during the build. 315 makefiles = set( 316 subprocess.check_output([ 317 "prebuilts/build-tools/linux-x86/bin/ckati_stamp_dump", 318 "--files", 319 kati_stamp_file, 320 ]).decode().strip("\n").split("\n")) 321 322 def is_product_makefile(makefile): 323 """Returns True if the makefile path meets certain criteria.""" 324 banned_prefixes = [ 325 "out/", 326 # Ignore product makefiles for sample AOSP boards. 327 "device/amlogic", 328 "device/generic", 329 "device/google", 330 "device/linaro", 331 "device/sample", 332 ] 333 banned_suffixes = [ 334 # All Android.mk files in the source are always parsed by Kati, 335 # so including them here would bring in lots of unnecessary projects. 336 "Android.mk", 337 # The ckati stamp file always includes a line for the ckati bin at 338 # the beginnning. 339 "bin/ckati", 340 ] 341 return (all([not makefile.startswith(p) for p in banned_prefixes]) and 342 all([not makefile.endswith(s) for s in banned_suffixes])) 343 344 # Limit the makefiles to only product makefiles. 345 product_makefiles = { 346 os.path.normpath(path) for path in makefiles if is_product_makefile(path) 347 } 348 349 def strip_overlay(makefile): 350 """Remove any overlays from a makefile path.""" 351 for overlay in overlays: 352 if makefile.startswith(overlay): 353 return makefile[len(overlay):] 354 return makefile 355 356 makefiles_and_symlinks = set() 357 for makefile in product_makefiles: 358 # Search for the makefile, possibly scanning overlays as well. 359 for overlay in [""] + overlays: 360 makefile_with_overlay = os.path.join(overlay, makefile) 361 if os.path.exists(makefile_with_overlay): 362 makefile = makefile_with_overlay 363 break 364 365 if not os.path.exists(makefile): 366 logger.warning("Unknown kati makefile: %s" % makefile) 367 continue 368 369 # Ensure the project that contains the makefile is included, as well as 370 # the project that any makefile symlinks point to. 371 makefiles_and_symlinks.add(strip_overlay(makefile)) 372 if os.path.islink(makefile): 373 makefiles_and_symlinks.add( 374 strip_overlay(os.path.relpath(os.path.realpath(makefile)))) 375 376 return makefiles_and_symlinks 377 378 379def scan_repo_projects(repo_projects, input_path): 380 """Returns the project path of the given input path if it exists. 381 382 Args: 383 repo_projects: The output of the get_repo_projects function. 384 input_path: The path of an input file used in the build, as given by the 385 ninja inputs tool. 386 387 Returns: 388 The path string, or None if not found. 389 """ 390 parts = input_path.split("/") 391 392 for index in reversed(range(0, len(parts))): 393 project_path = os.path.join(*parts[:index + 1]) 394 if project_path in repo_projects: 395 return project_path 396 397 return None 398 399 400def get_input_projects(repo_projects, inputs): 401 """Returns the collection of project names that contain the given input paths. 402 403 Args: 404 repo_projects: The output of the get_repo_projects function. 405 inputs: The paths of input files used in the build, as given by the ninja 406 inputs tool. 407 """ 408 input_project_paths = {} 409 for input_path in inputs: 410 if not input_path.startswith("out/") and not input_path.startswith("/"): 411 input_project_paths.setdefault( 412 scan_repo_projects(repo_projects, input_path), []).append(input_path) 413 414 return { 415 repo_projects[project_path]: inputs 416 for project_path, inputs in input_project_paths.items() 417 if project_path is not None 418 } 419 420 421def update_manifest(manifest, input_projects, remove_projects): 422 """Modifies and returns a manifest ElementTree by modifying its projects. 423 424 Args: 425 manifest: The manifest object to modify. 426 input_projects: A set of projects that should stay in the manifest. 427 remove_projects: A set of projects that should be removed from the manifest. 428 Projects in this set override input_projects. 429 430 Returns: 431 The modified manifest object. 432 """ 433 projects_to_keep = input_projects.difference(remove_projects) 434 root = manifest.getroot() 435 for child in root.findall("project"): 436 if child.attrib["name"] not in projects_to_keep: 437 root.remove(child) 438 return manifest 439 440 441@dataclasses.dataclass 442class DebugInfo: 443 """Simple class to store structured debug info for a project.""" 444 direct_input: bool = False 445 adjacent_input: bool = False 446 deps_input: bool = False 447 kati_makefiles: List[str] = dataclasses.field(default_factory=list) 448 manual_add_config: str = "" 449 manual_remove_config: str = "" 450 451 452def create_split_manifest(targets, manifest_file, split_manifest_file, 453 config_files, repo_list_file, ninja_build_file, 454 ninja_binary, module_info_file, kati_stamp_file, 455 overlays, installed_prebuilts, debug_file): 456 """Creates and writes a split manifest by inspecting build inputs. 457 458 Args: 459 targets: List of targets that should be buildable using the split manifest. 460 manifest_file: Path to the repo manifest to split. 461 split_manifest_file: Path to write the resulting split manifest. 462 config_files: Paths to a config XML file containing projects to add or 463 remove. See default_config.xml for an example. This flag can be passed 464 more than once to use multiple config files. 465 repo_list_file: Path to the output of the 'repo list' command. 466 ninja_build_file: Path to the combined-<target>.ninja file found in an out 467 dir. 468 ninja_binary: Path to the ninja binary. 469 module_info_file: Path to the module-info.json file found in an out dir. 470 kati_stamp_file: The path to a .kati_stamp file from a build. 471 overlays: A list of paths to treat as overlays when parsing the kati stamp 472 file. 473 installed_prebuilts: A list of paths for which to create "fake" repo 474 entries. These entries allow the tool to recognize modules that installed 475 rather than being sync'd via a manifest. 476 debug_file: If not None, the path to write JSON debug info. 477 """ 478 debug_info = {} 479 480 config = ManifestSplitConfig.from_config_files(config_files) 481 original_manifest = ET.parse(manifest_file) 482 483 484 repo_projects = get_repo_projects(repo_list_file, original_manifest, 485 config.path_mappings) 486 repo_projects.update({ip: ip for ip in installed_prebuilts}) 487 488 inputs = get_ninja_inputs(ninja_binary, ninja_build_file, targets) 489 input_projects = set(get_input_projects(repo_projects, inputs).keys()) 490 for project in input_projects: 491 debug_info.setdefault(project, DebugInfo()).direct_input = True 492 logger.info( 493 "%s projects needed for Ninja-graph direct dependencies of targets \"%s\"", 494 len(input_projects), " ".join(targets)) 495 496 if kati_stamp_file: 497 kati_makefiles = get_kati_makefiles(kati_stamp_file, overlays) 498 kati_makefiles_projects = get_input_projects(repo_projects, kati_makefiles) 499 for project, makefiles in kati_makefiles_projects.items(): 500 debug_info.setdefault(project, DebugInfo()).kati_makefiles = makefiles 501 input_projects = input_projects.union(kati_makefiles_projects.keys()) 502 logger.info("%s projects after including Kati makefiles projects.", 503 len(input_projects)) 504 else: 505 logger.info("Kati makefiles projects skipped.") 506 507 for project, cfile in config.add_projects.items(): 508 debug_info.setdefault(project, DebugInfo()).manual_add_config = cfile 509 for project, cfile in config.remove_projects.items(): 510 debug_info.setdefault(project, DebugInfo()).manual_remove_config = cfile 511 input_projects = input_projects.union(config.add_projects.keys()) 512 logger.info("%s projects after including manual additions.", 513 len(input_projects)) 514 515 # Remove projects from our set of input projects before adding adjacent 516 # modules, so that no project is added only because of an adjacent 517 # dependency in a to-be-removed project. 518 input_projects = input_projects.difference(config.remove_projects.keys()) 519 520 # While we still have projects whose modules we haven't checked yet, 521 if module_info_file: 522 module_info = ModuleInfo(module_info_file, repo_projects) 523 checked_projects = set() 524 projects_to_check = input_projects.difference(checked_projects) 525 logger.info("Checking module-info dependencies for direct and adjacent modules...") 526 else: 527 logging.info("Direct and adjacent modules skipped.") 528 projects_to_check = None 529 530 iteration = 0 531 532 while projects_to_check: 533 iteration += 1 534 # check all modules in each project, 535 modules = [] 536 deps_additions = set() 537 538 def process_deps(module): 539 for d in module_info.module_deps[module]: 540 if d in module_info.module_class: 541 if module_info.module_class[d] == "HEADER_LIBRARIES": 542 hla = module_info.module_project[d] 543 if hla not in input_projects: 544 deps_additions.add(hla) 545 546 for project in projects_to_check: 547 checked_projects.add(project) 548 if project not in module_info.project_modules: 549 continue 550 for module in module_info.project_modules[project]: 551 modules.append(module) 552 process_deps(module) 553 554 for project in deps_additions: 555 debug_info.setdefault(project, DebugInfo()).deps_input = True 556 input_projects = input_projects.union(deps_additions) 557 logger.info( 558 "pass %d - %d projects after including HEADER_LIBRARIES dependencies", 559 iteration, len(input_projects)) 560 561 # adding those modules' input projects to our list of projects. 562 inputs = get_ninja_inputs(ninja_binary, ninja_build_file, modules) 563 adjacent_module_additions = set( 564 get_input_projects(repo_projects, inputs).keys()) 565 for project in adjacent_module_additions: 566 debug_info.setdefault(project, DebugInfo()).adjacent_input = True 567 input_projects = input_projects.union(adjacent_module_additions) 568 logger.info( 569 "pass %d - %d projects after including adjacent-module Ninja-graph dependencies", 570 iteration, len(input_projects)) 571 572 projects_to_check = input_projects.difference(checked_projects) 573 574 logger.info("%s projects - complete", len(input_projects)) 575 576 split_manifest = update_manifest(original_manifest, input_projects, 577 config.remove_projects.keys()) 578 split_manifest.write(split_manifest_file) 579 580 if debug_file: 581 with open(debug_file, "w") as debug_fp: 582 logger.info("Writing debug info to %s", debug_file) 583 json.dump( 584 debug_info, 585 fp=debug_fp, 586 sort_keys=True, 587 indent=2, 588 default=lambda info: info.__dict__) 589 590 591def main(argv): 592 try: 593 opts, args = getopt.getopt(argv, "h", [ 594 "help", 595 "debug-file=", 596 "manifest=", 597 "split-manifest=", 598 "config=", 599 "ignore-default-config", 600 "repo-list=", 601 "ninja-build=", 602 "ninja-binary=", 603 "module-info=", 604 "skip-module-info", 605 "kati-stamp=", 606 "skip-kati", 607 "overlay=", 608 "installed-prebuilt=", 609 ]) 610 except getopt.GetoptError as err: 611 print(__doc__, file=sys.stderr) 612 print("**%s**" % str(err), file=sys.stderr) 613 sys.exit(2) 614 615 debug_file = None 616 manifest_file = None 617 split_manifest_file = None 618 config_files = [] 619 repo_list_file = None 620 ninja_build_file = None 621 module_info_file = None 622 ninja_binary = "prebuilts/build-tools/linux-x86/bin/ninja" 623 kati_stamp_file = None 624 overlays = [] 625 installed_prebuilts = [] 626 ignore_default_config = False 627 skip_kati = False 628 skip_module_info = False 629 630 for o, a in opts: 631 if o in ("-h", "--help"): 632 print(__doc__, file=sys.stderr) 633 sys.exit() 634 elif o in ("--debug-file"): 635 debug_file = a 636 elif o in ("--manifest"): 637 manifest_file = a 638 elif o in ("--split-manifest"): 639 split_manifest_file = a 640 elif o in ("--config"): 641 config_files.append(a) 642 elif o == "--ignore-default-config": 643 ignore_default_config = True 644 elif o in ("--repo-list"): 645 repo_list_file = a 646 elif o in ("--ninja-build"): 647 ninja_build_file = a 648 elif o in ("--ninja-binary"): 649 ninja_binary = a 650 elif o in ("--module-info"): 651 module_info_file = a 652 elif o == "--skip-module-info": 653 skip_module_info = True 654 elif o in ("--kati-stamp"): 655 kati_stamp_file = a 656 elif o == "--skip-kati": 657 skip_kati = True 658 elif o in ("--overlay"): 659 overlays.append(a) 660 elif o in ("--installed-prebuilt"): 661 installed_prebuilts.append(a) 662 else: 663 assert False, "unknown option \"%s\"" % o 664 665 if not args: 666 print(__doc__, file=sys.stderr) 667 print("**Missing targets**", file=sys.stderr) 668 sys.exit(2) 669 if not manifest_file: 670 print(__doc__, file=sys.stderr) 671 print("**Missing required flag --manifest**", file=sys.stderr) 672 sys.exit(2) 673 if not split_manifest_file: 674 print(__doc__, file=sys.stderr) 675 print("**Missing required flag --split-manifest**", file=sys.stderr) 676 sys.exit(2) 677 678 if skip_module_info: 679 if module_info_file: 680 logging.warning("User provided both --skip-module-info and --module-info args. Arg --module-info ignored.") 681 module_info_file = None 682 elif not module_info_file: 683 module_info_file = os.path.join(os.environ["ANDROID_PRODUCT_OUT"], 684 "module-info.json") 685 if skip_kati: 686 if kati_stamp_file: 687 logging.warning("User provided both --skip-kati and --kati-stamp args. Arg --kati-stamp ignored.") 688 kati_stamp_file = None 689 elif not kati_stamp_file: 690 kati_stamp_file = os.path.join( 691 os.environ["ANDROID_BUILD_TOP"], "out", 692 ".kati_stamp-%s" % os.environ["TARGET_PRODUCT"]) 693 694 if not ninja_build_file: 695 ninja_build_file = os.path.join( 696 os.environ["ANDROID_BUILD_TOP"], "out", 697 "combined-%s.ninja" % os.environ["TARGET_PRODUCT"]) 698 699 with tempfile.NamedTemporaryFile() as default_config_file: 700 if not ignore_default_config: 701 default_config_file.write(pkgutil.get_data(__name__, DEFAULT_CONFIG_XML)) 702 default_config_file.flush() 703 config_files.insert(0, default_config_file.name) 704 705 create_split_manifest( 706 targets=args, 707 manifest_file=manifest_file, 708 split_manifest_file=split_manifest_file, 709 config_files=config_files, 710 repo_list_file=repo_list_file, 711 ninja_build_file=ninja_build_file, 712 ninja_binary=ninja_binary, 713 module_info_file=module_info_file, 714 kati_stamp_file=kati_stamp_file, 715 overlays=overlays, 716 installed_prebuilts=installed_prebuilts, 717 debug_file=debug_file) 718 719 720if __name__ == "__main__": 721 main(sys.argv[1:]) 722