1#!/usr/bin/env python3 2 3import argparse 4import itertools 5import os 6import subprocess 7import sys 8 9def get_build_var(var): 10 return subprocess.run(["build/soong/soong_ui.bash","--dumpvar-mode", var], 11 check=True, capture_output=True, text=True).stdout.strip() 12 13 14def get_all_modules(): 15 product_out = subprocess.run(["build/soong/soong_ui.bash", "--dumpvar-mode", "--abs", "PRODUCT_OUT"], 16 check=True, capture_output=True, text=True).stdout.strip() 17 result = subprocess.run(["cat", product_out + "/all_modules.txt"], check=True, capture_output=True, text=True) 18 return result.stdout.strip().split("\n") 19 20 21def batched(iterable, n): 22 # introduced in itertools 3.12, could delete once that's universally available 23 if n < 1: 24 raise ValueError('n must be at least one') 25 it = iter(iterable) 26 while batch := tuple(itertools.islice(it, n)): 27 yield batch 28 29 30def get_sources(modules): 31 sources = set() 32 for module_group in batched(modules, 40_000): 33 result = subprocess.run(["./prebuilts/build-tools/linux-x86/bin/ninja", "-f", 34 "out/combined-" + os.environ["TARGET_PRODUCT"] + ".ninja", 35 "-t", "inputs", "-d", ] + list(module_group), 36 stderr=subprocess.STDOUT, stdout=subprocess.PIPE, check=False, text=True) 37 if result.returncode != 0: 38 sys.stderr.write(result.stdout) 39 sys.exit(1) 40 sources.update(set([f for f in result.stdout.split("\n") if not f.startswith("out/")])) 41 return sources 42 43 44def m_nothing(): 45 result = subprocess.run(["build/soong/soong_ui.bash", "--build-mode", "--all-modules", 46 "--dir=" + os.getcwd(), "nothing"], 47 check=False, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, text=True) 48 if result.returncode != 0: 49 sys.stderr.write(result.stdout) 50 sys.exit(1) 51 52 53def get_git_dirs(): 54 text = subprocess.run(["repo","list"], check=True, capture_output=True, text=True).stdout 55 return [line.split(" : ")[0] + "/" for line in text.split("\n")] 56 57 58def get_referenced_projects(git_dirs, files): 59 # files must be sorted 60 referenced_dirs = set() 61 prev_dir = None 62 for f in files: 63 # Optimization is ~5x speedup for large sets of files 64 if prev_dir: 65 if f.startswith(prev_dir): 66 referenced_dirs.add(d) 67 continue 68 for d in git_dirs: 69 if f.startswith(d): 70 referenced_dirs.add(d) 71 prev_dir = d 72 break 73 return referenced_dirs 74 75 76def main(argv): 77 # Argument parsing 78 ap = argparse.ArgumentParser(description="List the required git projects for the given modules") 79 ap.add_argument("--products", nargs="*", 80 help="One or more TARGET_PRODUCT to check, or \"*\" for all. If not provided" 81 + "just uses whatever has already been built") 82 ap.add_argument("--variants", nargs="*", 83 help="The TARGET_BUILD_VARIANTS to check. If not provided just uses whatever has" 84 + " already been built, or eng if --products is supplied") 85 ap.add_argument("--modules", nargs="*", 86 help="The build modules to check, or \"*\" for all, or droid if not supplied") 87 ap.add_argument("--why", nargs="*", 88 help="Also print the input files used in these projects, or \"*\" for all") 89 ap.add_argument("--unused", help="List the unused git projects for the given modules rather than" 90 + "the used ones. Ignores --why", action="store_true") 91 args = ap.parse_args(argv[1:]) 92 93 modules = args.modules if args.modules else ["droid"] 94 95 match args.products: 96 case ["*"]: 97 products = get_build_var("all_named_products").split(" ") 98 case _: 99 products = args.products 100 101 # Get the list of sources for all of the requested build combos 102 if not products and not args.variants: 103 m_nothing() 104 if args.modules == ["*"]: 105 modules = get_all_modules() 106 sources = get_sources(modules) 107 else: 108 if not products: 109 sys.stderr.write("Error: --products must be supplied if --variants is supplied") 110 sys.exit(1) 111 sources = set() 112 build_num = 1 113 for product in products: 114 os.environ["TARGET_PRODUCT"] = product 115 variants = args.variants if args.variants else ["user", "userdebug", "eng"] 116 for variant in variants: 117 sys.stderr.write(f"Analyzing build {build_num} of {len(products)*len(variants)}\r") 118 os.environ["TARGET_BUILD_VARIANT"] = variant 119 m_nothing() 120 if args.modules == ["*"]: 121 modules = get_all_modules() 122 sources.update(get_sources(modules)) 123 build_num += 1 124 sys.stderr.write("\n\n") 125 126 sources = sorted(sources) 127 128 if args.unused: 129 # Print the list of git directories that don't contain sources 130 used_git_dirs = set(get_git_dirs()) 131 for project in sorted(used_git_dirs.difference(set(get_referenced_projects(used_git_dirs, sources)))): 132 print(project[0:-1]) 133 else: 134 # Print the list of git directories that has one or more of the sources in it 135 for project in sorted(get_referenced_projects(get_git_dirs(), sources)): 136 print(project[0:-1]) 137 if args.why: 138 if "*" in args.why or project[0:-1] in args.why: 139 prefix = project 140 for f in sources: 141 if f.startswith(prefix): 142 print(" " + f) 143 144 145if __name__ == "__main__": 146 sys.exit(main(sys.argv)) 147 148 149# vim: set ts=2 sw=2 sts=2 expandtab nocindent tw=100: 150