blob: 55c8c6fb5a23ef456a235e7e095712886b1f8247 [file] [log] [blame]
#!/usr/bin/env python3
import argparse
import itertools
import os
import subprocess
import sys
def get_build_var(var):
return subprocess.run(["build/soong/soong_ui.bash","--dumpvar-mode", var],
check=True, capture_output=True, text=True).stdout.strip()
def get_all_modules():
product_out = subprocess.run(["build/soong/soong_ui.bash", "--dumpvar-mode", "--abs", "PRODUCT_OUT"],
check=True, capture_output=True, text=True).stdout.strip()
result = subprocess.run(["cat", product_out + "/all_modules.txt"], check=True, capture_output=True, text=True)
return result.stdout.strip().split("\n")
def batched(iterable, n):
# introduced in itertools 3.12, could delete once that's universally available
if n < 1:
raise ValueError('n must be at least one')
it = iter(iterable)
while batch := tuple(itertools.islice(it, n)):
yield batch
def get_sources(modules):
sources = set()
for module_group in batched(modules, 40_000):
result = subprocess.run(["./prebuilts/build-tools/linux-x86/bin/ninja", "-f",
"out/combined-" + os.environ["TARGET_PRODUCT"] + ".ninja",
"-t", "inputs", "-d", ] + list(module_group),
stderr=subprocess.STDOUT, stdout=subprocess.PIPE, check=False, text=True)
if result.returncode != 0:
sys.stderr.write(result.stdout)
sys.exit(1)
sources.update(set([f for f in result.stdout.split("\n") if not f.startswith("out/")]))
return sources
def m_nothing():
result = subprocess.run(["build/soong/soong_ui.bash", "--build-mode", "--all-modules",
"--dir=" + os.getcwd(), "nothing"],
check=False, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, text=True)
if result.returncode != 0:
sys.stderr.write(result.stdout)
sys.exit(1)
def get_git_dirs():
text = subprocess.run(["repo","list"], check=True, capture_output=True, text=True).stdout
return [line.split(" : ")[0] + "/" for line in text.split("\n")]
def get_referenced_projects(git_dirs, files):
# files must be sorted
referenced_dirs = set()
prev_dir = None
for f in files:
# Optimization is ~5x speedup for large sets of files
if prev_dir:
if f.startswith(prev_dir):
referenced_dirs.add(d)
continue
for d in git_dirs:
if f.startswith(d):
referenced_dirs.add(d)
prev_dir = d
break
return referenced_dirs
def main(argv):
# Argument parsing
ap = argparse.ArgumentParser(description="List the required git projects for the given modules")
ap.add_argument("--products", nargs="*",
help="One or more TARGET_PRODUCT to check, or \"*\" for all. If not provided"
+ "just uses whatever has already been built")
ap.add_argument("--variants", nargs="*",
help="The TARGET_BUILD_VARIANTS to check. If not provided just uses whatever has"
+ " already been built, or eng if --products is supplied")
ap.add_argument("--modules", nargs="*",
help="The build modules to check, or \"*\" for all, or droid if not supplied")
ap.add_argument("--why", nargs="*",
help="Also print the input files used in these projects, or \"*\" for all")
ap.add_argument("--unused", help="List the unused git projects for the given modules rather than"
+ "the used ones. Ignores --why", action="store_true")
args = ap.parse_args(argv[1:])
modules = args.modules if args.modules else ["droid"]
match args.products:
case ["*"]:
products = get_build_var("all_named_products").split(" ")
case _:
products = args.products
# Get the list of sources for all of the requested build combos
if not products and not args.variants:
m_nothing()
if args.modules == ["*"]:
modules = get_all_modules()
sources = get_sources(modules)
else:
if not products:
sys.stderr.write("Error: --products must be supplied if --variants is supplied")
sys.exit(1)
sources = set()
build_num = 1
for product in products:
os.environ["TARGET_PRODUCT"] = product
variants = args.variants if args.variants else ["user", "userdebug", "eng"]
for variant in variants:
sys.stderr.write(f"Analyzing build {build_num} of {len(products)*len(variants)}\r")
os.environ["TARGET_BUILD_VARIANT"] = variant
m_nothing()
if args.modules == ["*"]:
modules = get_all_modules()
sources.update(get_sources(modules))
build_num += 1
sys.stderr.write("\n\n")
sources = sorted(sources)
if args.unused:
# Print the list of git directories that don't contain sources
used_git_dirs = set(get_git_dirs())
for project in sorted(used_git_dirs.difference(set(get_referenced_projects(used_git_dirs, sources)))):
print(project[0:-1])
else:
# Print the list of git directories that has one or more of the sources in it
for project in sorted(get_referenced_projects(get_git_dirs(), sources)):
print(project[0:-1])
if args.why:
if "*" in args.why or project[0:-1] in args.why:
prefix = project
for f in sources:
if f.startswith(prefix):
print(" " + f)
if __name__ == "__main__":
sys.exit(main(sys.argv))
# vim: set ts=2 sw=2 sts=2 expandtab nocindent tw=100: