• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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