722 lines
25 KiB
Python
722 lines
25 KiB
Python
# 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.
|
|
"""
|
|
remove_projects: Dict[str, str]
|
|
add_projects: Dict[str, str]
|
|
path_mappings: List[PathMappingConfig]
|
|
|
|
@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 = []
|
|
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")
|
|
])
|
|
|
|
return cls(remove_projects, add_projects, path_mappings)
|
|
|
|
|
|
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):
|
|
"""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.
|
|
|
|
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)
|
|
|
|
def module_has_valid_path(module):
|
|
return ("path" in module_info[module] and module_info[module]["path"] and
|
|
not module_info[module]["path"][0].startswith("out/"))
|
|
|
|
module_paths = {
|
|
module: module_info[module]["path"][0]
|
|
for module in module_info
|
|
if module_has_valid_path(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)
|
|
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:])
|