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