| # Copyright (C) 2020 The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| """Splits a manifest to the minimum set of projects needed to build the targets. |
| |
| Usage: manifest_split [options] targets |
| |
| targets: Space-separated list of targets that should be buildable |
| using the split manifest. |
| |
| options: |
| --manifest <path> |
| Path to the repo manifest to split. [Required] |
| --split-manifest <path> |
| Path to write the resulting split manifest. [Required] |
| --config <path> |
| Optional path(s) to a config XML file containing projects to add or |
| remove. See default_config.xml for an example. This flag can be passed |
| more than once to use multiple config files. |
| Sample file my_config.xml: |
| <config> |
| <add_project name="vendor/my/needed/project" /> |
| <remove_project name="vendor/my/unused/project" /> |
| </config> |
| --ignore-default-config |
| If provided, don't include default_config.xml. |
| --installed-prebuilt |
| Specify the directory containing an installed prebuilt Android.bp file. |
| Supply this option zero or more times, once for each installed prebuilt |
| directory. |
| --repo-list <path> |
| Optional path to the output of the 'repo list' command. Used if the |
| output of 'repo list' needs pre-processing before being used by |
| this tool. |
| --ninja-build <path> |
| Optional path to the combined-<target>.ninja file found in an out dir. |
| If not provided, the default file is used based on the lunch environment. |
| --ninja-binary <path> |
| Optional path to the ninja binary. Uses the standard binary by default. |
| --module-info <path> |
| Optional path to the module-info.json file found in an out dir. |
| If not provided, the default file is used based on the lunch environment. |
| --skip-module-info |
| If provided, skip parsing module-info.json for direct and adjacent |
| dependencies. Overrides --module-info option. |
| --kati-stamp <path> |
| Optional path to the .kati_stamp file found in an out dir. |
| If not provided, the default file is used based on the lunch environment. |
| --skip-kati |
| If provided, skip Kati makefiles projects. Overrides --kati-stamp option. |
| --overlay <path> |
| Optional path(s) to treat as overlays when parsing the kati stamp file |
| and scanning for makefiles. See the tools/treble/build/sandbox directory |
| for more info about overlays. This flag can be passed more than once. |
| --debug-file <path> |
| If provided, debug info will be written to a JSON file at this path. |
| -h (--help) |
| Display this usage message and exit. |
| """ |
| |
| from __future__ import print_function |
| |
| import getopt |
| import json |
| import logging |
| import os |
| import pkgutil |
| import re |
| import subprocess |
| import sys |
| import tempfile |
| from typing import Dict, List, Pattern, Set, Tuple |
| import xml.etree.ElementTree as ET |
| |
| import dataclasses |
| |
| |
| logging.basicConfig( |
| stream=sys.stdout, |
| level=logging.INFO, |
| format="%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s", |
| datefmt="%Y-%m-%d %H:%M:%S") |
| logger = logging.getLogger(os.path.basename(__file__)) |
| |
| # Projects determined to be needed despite the dependency not being visible |
| # to ninja. |
| DEFAULT_CONFIG_XML = "default_config.xml" |
| |
| # Pattern that matches a java dependency. |
| _JAVA_LIB_PATTERN = re.compile( |
| # pylint: disable=line-too-long |
| '^out/target/common/obj/JAVA_LIBRARIES/(.+)_intermediates/classes-header.jar$' |
| ) |
| |
| |
| @dataclasses.dataclass |
| class PathMappingConfig: |
| pattern: Pattern[str] |
| sub: str |
| |
| |
| @dataclasses.dataclass |
| class ManifestSplitConfig: |
| """Holds the configuration for the split manifest tool. |
| |
| Attributes: |
| remove_projects: A Dict of project name to the config file that specified |
| this project, for projects that should be removed from the resulting |
| manifest. |
| add_projects: A Dict of project name to the config file that specified |
| this project, for projects that should be added to the resulting manifest. |
| path_mappings: A list of PathMappingConfigs to modify a path in the build |
| sandbox to the path in the manifest. |
| ignore_paths: Set of paths to ignore when parsing module_info_file |
| """ |
| remove_projects: Dict[str, str] |
| add_projects: Dict[str, str] |
| path_mappings: List[PathMappingConfig] |
| ignore_paths: Set[str] |
| |
| @classmethod |
| def from_config_files(cls, config_files: List[str]): |
| """Reads from a list of config XML files. |
| |
| Args: |
| config_files: A list of config XML filenames. |
| |
| Returns: |
| A ManifestSplitConfig from the files. |
| """ |
| remove_projects: Dict[str, str] = {} |
| add_projects: Dict[str, str] = {} |
| path_mappings = [] |
| """ Always ignore paths in out/ directory. """ |
| ignore_paths = set(["out/"]) |
| for config_file in config_files: |
| root = ET.parse(config_file).getroot() |
| |
| remove_projects.update({ |
| c.attrib["name"]: config_file for c in root.findall("remove_project") |
| }) |
| |
| add_projects.update( |
| {c.attrib["name"]: config_file for c in root.findall("add_project")}) |
| |
| path_mappings.extend([ |
| PathMappingConfig( |
| re.compile(child.attrib["pattern"]), child.attrib["sub"]) |
| for child in root.findall("path_mapping") |
| ]) |
| |
| ignore_paths.update( |
| {c.attrib["name"]: config_file for c in root.findall("ignore_path")}) |
| |
| return cls(remove_projects, add_projects, path_mappings, ignore_paths) |
| |
| |
| def get_repo_projects(repo_list_file, manifest, path_mappings): |
| """Returns a dict of { project path : project name } using the manifest. |
| |
| The path_mappings stop on the first match mapping. If the mapping results in |
| an empty string, that entry is removed. |
| |
| Args: |
| repo_list_file: An optional filename to read instead of parsing the manifest. |
| manifest: The manifest object to scan for projects. |
| path_mappings: A list of PathMappingConfigs to modify a path in the build |
| sandbox to the path in the manifest. |
| """ |
| repo_list = [] |
| |
| if repo_list_file: |
| with open(repo_list_file) as repo_list_lines: |
| repo_list = [line.strip().split(" : ") for line in repo_list_lines if line.strip()] |
| else: |
| root = manifest.getroot() |
| repo_list = [(p.get("path", p.get("name")), p.get("name")) for p in root.findall("project")] |
| |
| repo_dict = {} |
| for entry in repo_list: |
| path, project = entry |
| for mapping in path_mappings: |
| if mapping.pattern.fullmatch(path): |
| path = mapping.pattern.sub(mapping.sub, path) |
| break |
| # If the resulting path mapping is empty, then don't add entry |
| if path: |
| repo_dict[path] = project |
| return repo_dict |
| |
| |
| class ModuleInfo: |
| """Contains various mappings to/from module/project""" |
| |
| def __init__(self, module_info_file, repo_projects, ignore_paths): |
| """Initialize a module info instance. |
| |
| Builds various maps related to platform build system modules and how they |
| relate to each other and projects. |
| |
| Args: |
| module_info_file: The path to a module-info.json file from a build. |
| repo_projects: The output of the get_repo_projects function. |
| ignore_paths: Set of paths to ignore from module_info_file data |
| |
| Raises: |
| ValueError: A module from module-info.json belongs to a path not |
| known by the repo projects output. |
| """ |
| # Maps a project to the set of modules it contains. |
| self.project_modules = {} |
| # Maps a module to the project that contains it. |
| self.module_project = {} |
| # Maps a module to its class. |
| self.module_class = {} |
| # Maps a module to modules it depends on. |
| self.module_deps = {} |
| |
| with open(module_info_file) as module_info_file: |
| module_info = json.load(module_info_file) |
| |
| # Check that module contains a path and the path is not in set of |
| # ignore paths |
| def module_has_valid_path(module): |
| paths = module.get("path") |
| if not paths: |
| return False |
| return all(not paths[0].startswith(p) for p in ignore_paths) |
| |
| module_paths = { |
| module: module_info[module]["path"][0] |
| for module in module_info |
| if module_has_valid_path(module_info[module]) |
| } |
| module_project_paths = { |
| module: scan_repo_projects(repo_projects, module_paths[module]) |
| for module in module_paths |
| } |
| |
| for module, project_path in module_project_paths.items(): |
| if not project_path: |
| raise ValueError("Unknown module path for module %s: %s" % |
| (module, module_info[module])) |
| repo_project = repo_projects[project_path] |
| self.project_modules.setdefault(repo_project, set()).add(module) |
| self.module_project[module] = repo_project |
| |
| def dep_from_raw_dep(raw_dep): |
| match = re.search(_JAVA_LIB_PATTERN, raw_dep) |
| return match.group(1) if match else raw_dep |
| |
| def deps_from_raw_deps(raw_deps): |
| return [dep_from_raw_dep(raw_dep) for raw_dep in raw_deps] |
| |
| self.module_class = { |
| module: module_info[module]["class"][0] |
| for module in module_info |
| } |
| self.module_deps = { |
| module: deps_from_raw_deps(module_info[module]["dependencies"]) |
| for module in module_info |
| } |
| |
| |
| def get_ninja_inputs(ninja_binary, ninja_build_file, modules): |
| """Returns the set of input file path strings for the given modules. |
| |
| Uses the `ninja -t inputs` tool. |
| |
| Args: |
| ninja_binary: The path to a ninja binary. |
| ninja_build_file: The path to a .ninja file from a build. |
| modules: The list of modules to scan for inputs. |
| """ |
| inputs = set() |
| NINJA_SHARD_LIMIT = 20000 |
| for i in range(0, len(modules), NINJA_SHARD_LIMIT): |
| modules_shard = modules[i:i + NINJA_SHARD_LIMIT] |
| inputs = inputs.union(set( |
| subprocess.check_output([ |
| ninja_binary, |
| "-f", |
| ninja_build_file, |
| "-t", |
| "inputs", |
| "-d", |
| ] + list(modules_shard)).decode().strip("\n").split("\n"))) |
| |
| def input_allowed(path): |
| path = path.strip() |
| if path.endswith("TEST_MAPPING") and "test_mapping" not in modules: |
| # Exclude projects that are only needed for TEST_MAPPING files, unless the |
| # user is asking to build 'test_mapping'. |
| return False |
| if path.endswith("MODULE_LICENSE_GPL"): |
| # Exclude projects that are included only due to having a |
| # MODULE_LICENSE_GPL file, if no other inputs from that project are used. |
| return False |
| return path |
| |
| return {path.strip() for path in inputs if input_allowed(path)} |
| |
| |
| def get_kati_makefiles(kati_stamp_file, overlays): |
| """Returns the set of makefile paths from the kati stamp file. |
| |
| Uses the ckati_stamp_dump prebuilt binary. |
| Also includes symlink sources in the resulting set for any |
| makefiles that are symlinks. |
| |
| Args: |
| kati_stamp_file: The path to a .kati_stamp file from a build. |
| overlays: A list of paths to treat as overlays when parsing the kati stamp |
| file. |
| """ |
| # Get a set of all makefiles that were parsed by Kati during the build. |
| makefiles = set( |
| subprocess.check_output([ |
| "prebuilts/build-tools/linux-x86/bin/ckati_stamp_dump", |
| "--files", |
| kati_stamp_file, |
| ]).decode().strip("\n").split("\n")) |
| |
| def is_product_makefile(makefile): |
| """Returns True if the makefile path meets certain criteria.""" |
| banned_prefixes = [ |
| "out/", |
| # Ignore product makefiles for sample AOSP boards. |
| "device/amlogic", |
| "device/generic", |
| "device/google", |
| "device/linaro", |
| "device/sample", |
| ] |
| banned_suffixes = [ |
| # All Android.mk files in the source are always parsed by Kati, |
| # so including them here would bring in lots of unnecessary projects. |
| "Android.mk", |
| # The ckati stamp file always includes a line for the ckati bin at |
| # the beginnning. |
| "bin/ckati", |
| ] |
| return (all([not makefile.startswith(p) for p in banned_prefixes]) and |
| all([not makefile.endswith(s) for s in banned_suffixes])) |
| |
| # Limit the makefiles to only product makefiles. |
| product_makefiles = { |
| os.path.normpath(path) for path in makefiles if is_product_makefile(path) |
| } |
| |
| def strip_overlay(makefile): |
| """Remove any overlays from a makefile path.""" |
| for overlay in overlays: |
| if makefile.startswith(overlay): |
| return makefile[len(overlay):] |
| return makefile |
| |
| makefiles_and_symlinks = set() |
| for makefile in product_makefiles: |
| # Search for the makefile, possibly scanning overlays as well. |
| for overlay in [""] + overlays: |
| makefile_with_overlay = os.path.join(overlay, makefile) |
| if os.path.exists(makefile_with_overlay): |
| makefile = makefile_with_overlay |
| break |
| |
| if not os.path.exists(makefile): |
| logger.warning("Unknown kati makefile: %s" % makefile) |
| continue |
| |
| # Ensure the project that contains the makefile is included, as well as |
| # the project that any makefile symlinks point to. |
| makefiles_and_symlinks.add(strip_overlay(makefile)) |
| if os.path.islink(makefile): |
| makefiles_and_symlinks.add( |
| strip_overlay(os.path.relpath(os.path.realpath(makefile)))) |
| |
| return makefiles_and_symlinks |
| |
| |
| def scan_repo_projects(repo_projects, input_path): |
| """Returns the project path of the given input path if it exists. |
| |
| Args: |
| repo_projects: The output of the get_repo_projects function. |
| input_path: The path of an input file used in the build, as given by the |
| ninja inputs tool. |
| |
| Returns: |
| The path string, or None if not found. |
| """ |
| parts = input_path.split("/") |
| |
| for index in reversed(range(0, len(parts))): |
| project_path = os.path.join(*parts[:index + 1]) |
| if project_path in repo_projects: |
| return project_path |
| |
| return None |
| |
| |
| def get_input_projects(repo_projects, inputs): |
| """Returns the collection of project names that contain the given input paths. |
| |
| Args: |
| repo_projects: The output of the get_repo_projects function. |
| inputs: The paths of input files used in the build, as given by the ninja |
| inputs tool. |
| """ |
| input_project_paths = {} |
| for input_path in inputs: |
| if not input_path.startswith("out/") and not input_path.startswith("/"): |
| input_project_paths.setdefault( |
| scan_repo_projects(repo_projects, input_path), []).append(input_path) |
| |
| return { |
| repo_projects[project_path]: inputs |
| for project_path, inputs in input_project_paths.items() |
| if project_path is not None |
| } |
| |
| |
| def update_manifest(manifest, input_projects, remove_projects): |
| """Modifies and returns a manifest ElementTree by modifying its projects. |
| |
| Args: |
| manifest: The manifest object to modify. |
| input_projects: A set of projects that should stay in the manifest. |
| remove_projects: A set of projects that should be removed from the manifest. |
| Projects in this set override input_projects. |
| |
| Returns: |
| The modified manifest object. |
| """ |
| projects_to_keep = input_projects.difference(remove_projects) |
| root = manifest.getroot() |
| for child in root.findall("project"): |
| if child.attrib["name"] not in projects_to_keep: |
| root.remove(child) |
| return manifest |
| |
| |
| @dataclasses.dataclass |
| class DebugInfo: |
| """Simple class to store structured debug info for a project.""" |
| direct_input: bool = False |
| adjacent_input: bool = False |
| deps_input: bool = False |
| kati_makefiles: List[str] = dataclasses.field(default_factory=list) |
| manual_add_config: str = "" |
| manual_remove_config: str = "" |
| |
| |
| def create_split_manifest(targets, manifest_file, split_manifest_file, |
| config_files, repo_list_file, ninja_build_file, |
| ninja_binary, module_info_file, kati_stamp_file, |
| overlays, installed_prebuilts, debug_file): |
| """Creates and writes a split manifest by inspecting build inputs. |
| |
| Args: |
| targets: List of targets that should be buildable using the split manifest. |
| manifest_file: Path to the repo manifest to split. |
| split_manifest_file: Path to write the resulting split manifest. |
| config_files: Paths to a config XML file containing projects to add or |
| remove. See default_config.xml for an example. This flag can be passed |
| more than once to use multiple config files. |
| repo_list_file: Path to the output of the 'repo list' command. |
| ninja_build_file: Path to the combined-<target>.ninja file found in an out |
| dir. |
| ninja_binary: Path to the ninja binary. |
| module_info_file: Path to the module-info.json file found in an out dir. |
| kati_stamp_file: The path to a .kati_stamp file from a build. |
| overlays: A list of paths to treat as overlays when parsing the kati stamp |
| file. |
| installed_prebuilts: A list of paths for which to create "fake" repo |
| entries. These entries allow the tool to recognize modules that installed |
| rather than being sync'd via a manifest. |
| debug_file: If not None, the path to write JSON debug info. |
| """ |
| debug_info = {} |
| |
| config = ManifestSplitConfig.from_config_files(config_files) |
| original_manifest = ET.parse(manifest_file) |
| |
| |
| repo_projects = get_repo_projects(repo_list_file, original_manifest, |
| config.path_mappings) |
| repo_projects.update({ip: ip for ip in installed_prebuilts}) |
| |
| inputs = get_ninja_inputs(ninja_binary, ninja_build_file, targets) |
| input_projects = set(get_input_projects(repo_projects, inputs).keys()) |
| for project in input_projects: |
| debug_info.setdefault(project, DebugInfo()).direct_input = True |
| logger.info( |
| "%s projects needed for Ninja-graph direct dependencies of targets \"%s\"", |
| len(input_projects), " ".join(targets)) |
| |
| if kati_stamp_file: |
| kati_makefiles = get_kati_makefiles(kati_stamp_file, overlays) |
| kati_makefiles_projects = get_input_projects(repo_projects, kati_makefiles) |
| for project, makefiles in kati_makefiles_projects.items(): |
| debug_info.setdefault(project, DebugInfo()).kati_makefiles = makefiles |
| input_projects = input_projects.union(kati_makefiles_projects.keys()) |
| logger.info("%s projects after including Kati makefiles projects.", |
| len(input_projects)) |
| else: |
| logger.info("Kati makefiles projects skipped.") |
| |
| for project, cfile in config.add_projects.items(): |
| debug_info.setdefault(project, DebugInfo()).manual_add_config = cfile |
| for project, cfile in config.remove_projects.items(): |
| debug_info.setdefault(project, DebugInfo()).manual_remove_config = cfile |
| input_projects = input_projects.union(config.add_projects.keys()) |
| logger.info("%s projects after including manual additions.", |
| len(input_projects)) |
| |
| # Remove projects from our set of input projects before adding adjacent |
| # modules, so that no project is added only because of an adjacent |
| # dependency in a to-be-removed project. |
| input_projects = input_projects.difference(config.remove_projects.keys()) |
| |
| # While we still have projects whose modules we haven't checked yet, |
| if module_info_file: |
| module_info = ModuleInfo(module_info_file, repo_projects, |
| config.ignore_paths) |
| checked_projects = set() |
| projects_to_check = input_projects.difference(checked_projects) |
| logger.info("Checking module-info dependencies for direct and adjacent modules...") |
| else: |
| logging.info("Direct and adjacent modules skipped.") |
| projects_to_check = None |
| |
| iteration = 0 |
| |
| while projects_to_check: |
| iteration += 1 |
| # check all modules in each project, |
| modules = [] |
| deps_additions = set() |
| |
| def process_deps(module): |
| for d in module_info.module_deps[module]: |
| if d in module_info.module_class: |
| if module_info.module_class[d] == "HEADER_LIBRARIES": |
| hla = module_info.module_project[d] |
| if hla not in input_projects: |
| deps_additions.add(hla) |
| |
| for project in projects_to_check: |
| checked_projects.add(project) |
| if project not in module_info.project_modules: |
| continue |
| for module in module_info.project_modules[project]: |
| modules.append(module) |
| process_deps(module) |
| |
| for project in deps_additions: |
| debug_info.setdefault(project, DebugInfo()).deps_input = True |
| input_projects = input_projects.union(deps_additions) |
| logger.info( |
| "pass %d - %d projects after including HEADER_LIBRARIES dependencies", |
| iteration, len(input_projects)) |
| |
| # adding those modules' input projects to our list of projects. |
| inputs = get_ninja_inputs(ninja_binary, ninja_build_file, modules) |
| adjacent_module_additions = set( |
| get_input_projects(repo_projects, inputs).keys()) |
| for project in adjacent_module_additions: |
| debug_info.setdefault(project, DebugInfo()).adjacent_input = True |
| input_projects = input_projects.union(adjacent_module_additions) |
| logger.info( |
| "pass %d - %d projects after including adjacent-module Ninja-graph dependencies", |
| iteration, len(input_projects)) |
| |
| projects_to_check = input_projects.difference(checked_projects) |
| |
| logger.info("%s projects - complete", len(input_projects)) |
| |
| split_manifest = update_manifest(original_manifest, input_projects, |
| config.remove_projects.keys()) |
| split_manifest.write(split_manifest_file) |
| |
| if debug_file: |
| with open(debug_file, "w") as debug_fp: |
| logger.info("Writing debug info to %s", debug_file) |
| json.dump( |
| debug_info, |
| fp=debug_fp, |
| sort_keys=True, |
| indent=2, |
| default=lambda info: info.__dict__) |
| |
| |
| def main(argv): |
| try: |
| opts, args = getopt.getopt(argv, "h", [ |
| "help", |
| "debug-file=", |
| "manifest=", |
| "split-manifest=", |
| "config=", |
| "ignore-default-config", |
| "repo-list=", |
| "ninja-build=", |
| "ninja-binary=", |
| "module-info=", |
| "skip-module-info", |
| "kati-stamp=", |
| "skip-kati", |
| "overlay=", |
| "installed-prebuilt=", |
| ]) |
| except getopt.GetoptError as err: |
| print(__doc__, file=sys.stderr) |
| print("**%s**" % str(err), file=sys.stderr) |
| sys.exit(2) |
| |
| debug_file = None |
| manifest_file = None |
| split_manifest_file = None |
| config_files = [] |
| repo_list_file = None |
| ninja_build_file = None |
| module_info_file = None |
| ninja_binary = "prebuilts/build-tools/linux-x86/bin/ninja" |
| kati_stamp_file = None |
| overlays = [] |
| installed_prebuilts = [] |
| ignore_default_config = False |
| skip_kati = False |
| skip_module_info = False |
| |
| for o, a in opts: |
| if o in ("-h", "--help"): |
| print(__doc__, file=sys.stderr) |
| sys.exit() |
| elif o in ("--debug-file"): |
| debug_file = a |
| elif o in ("--manifest"): |
| manifest_file = a |
| elif o in ("--split-manifest"): |
| split_manifest_file = a |
| elif o in ("--config"): |
| config_files.append(a) |
| elif o == "--ignore-default-config": |
| ignore_default_config = True |
| elif o in ("--repo-list"): |
| repo_list_file = a |
| elif o in ("--ninja-build"): |
| ninja_build_file = a |
| elif o in ("--ninja-binary"): |
| ninja_binary = a |
| elif o in ("--module-info"): |
| module_info_file = a |
| elif o == "--skip-module-info": |
| skip_module_info = True |
| elif o in ("--kati-stamp"): |
| kati_stamp_file = a |
| elif o == "--skip-kati": |
| skip_kati = True |
| elif o in ("--overlay"): |
| overlays.append(a) |
| elif o in ("--installed-prebuilt"): |
| installed_prebuilts.append(a) |
| else: |
| assert False, "unknown option \"%s\"" % o |
| |
| if not args: |
| print(__doc__, file=sys.stderr) |
| print("**Missing targets**", file=sys.stderr) |
| sys.exit(2) |
| if not manifest_file: |
| print(__doc__, file=sys.stderr) |
| print("**Missing required flag --manifest**", file=sys.stderr) |
| sys.exit(2) |
| if not split_manifest_file: |
| print(__doc__, file=sys.stderr) |
| print("**Missing required flag --split-manifest**", file=sys.stderr) |
| sys.exit(2) |
| |
| if skip_module_info: |
| if module_info_file: |
| logging.warning("User provided both --skip-module-info and --module-info args. Arg --module-info ignored.") |
| module_info_file = None |
| elif not module_info_file: |
| module_info_file = os.path.join(os.environ["ANDROID_PRODUCT_OUT"], |
| "module-info.json") |
| if skip_kati: |
| if kati_stamp_file: |
| logging.warning("User provided both --skip-kati and --kati-stamp args. Arg --kati-stamp ignored.") |
| kati_stamp_file = None |
| elif not kati_stamp_file: |
| kati_stamp_file = os.path.join( |
| os.environ["ANDROID_BUILD_TOP"], "out", |
| ".kati_stamp-%s" % os.environ["TARGET_PRODUCT"]) |
| |
| if not ninja_build_file: |
| ninja_build_file = os.path.join( |
| os.environ["ANDROID_BUILD_TOP"], "out", |
| "combined-%s.ninja" % os.environ["TARGET_PRODUCT"]) |
| |
| with tempfile.NamedTemporaryFile() as default_config_file: |
| if not ignore_default_config: |
| default_config_file.write(pkgutil.get_data(__name__, DEFAULT_CONFIG_XML)) |
| default_config_file.flush() |
| config_files.insert(0, default_config_file.name) |
| |
| create_split_manifest( |
| targets=args, |
| manifest_file=manifest_file, |
| split_manifest_file=split_manifest_file, |
| config_files=config_files, |
| repo_list_file=repo_list_file, |
| ninja_build_file=ninja_build_file, |
| ninja_binary=ninja_binary, |
| module_info_file=module_info_file, |
| kati_stamp_file=kati_stamp_file, |
| overlays=overlays, |
| installed_prebuilts=installed_prebuilts, |
| debug_file=debug_file) |
| |
| |
| if __name__ == "__main__": |
| main(sys.argv[1:]) |