Joe Onorato | cccf2ca | 2022-09-28 22:52:56 -0700 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | |
| 3 | import argparse |
Fabián Cañas | c1f344e | 2024-04-24 16:23:41 -0400 | [diff] [blame] | 4 | import itertools |
Joe Onorato | cccf2ca | 2022-09-28 22:52:56 -0700 | [diff] [blame] | 5 | import os |
| 6 | import subprocess |
| 7 | import sys |
| 8 | |
| 9 | def 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 | |
Fabián Cañas | c1f344e | 2024-04-24 16:23:41 -0400 | [diff] [blame] | 14 | def 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 | |
| 21 | def 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 | |
Joe Onorato | cccf2ca | 2022-09-28 22:52:56 -0700 | [diff] [blame] | 30 | def get_sources(modules): |
Fabián Cañas | c1f344e | 2024-04-24 16:23:41 -0400 | [diff] [blame] | 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 |
Joe Onorato | cccf2ca | 2022-09-28 22:52:56 -0700 | [diff] [blame] | 42 | |
| 43 | |
| 44 | def 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 | |
| 53 | def 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 | |
| 58 | def 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 |
Fabián Cañas | 97ea68a | 2024-04-23 20:36:14 -0400 | [diff] [blame] | 73 | return referenced_dirs |
Joe Onorato | cccf2ca | 2022-09-28 22:52:56 -0700 | [diff] [blame] | 74 | |
| 75 | |
| 76 | def 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="*", |
Fabián Cañas | c1f344e | 2024-04-24 16:23:41 -0400 | [diff] [blame] | 80 | help="One or more TARGET_PRODUCT to check, or \"*\" for all. If not provided" |
| 81 | + "just uses whatever has already been built") |
Joe Onorato | cccf2ca | 2022-09-28 22:52:56 -0700 | [diff] [blame] | 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="*", |
Fabián Cañas | c1f344e | 2024-04-24 16:23:41 -0400 | [diff] [blame] | 86 | help="The build modules to check, or \"*\" for all, or droid if not supplied") |
Joe Onorato | cccf2ca | 2022-09-28 22:52:56 -0700 | [diff] [blame] | 87 | ap.add_argument("--why", nargs="*", |
| 88 | help="Also print the input files used in these projects, or \"*\" for all") |
Fabián Cañas | 97ea68a | 2024-04-23 20:36:14 -0400 | [diff] [blame] | 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") |
Joe Onorato | cccf2ca | 2022-09-28 22:52:56 -0700 | [diff] [blame] | 91 | args = ap.parse_args(argv[1:]) |
| 92 | |
| 93 | modules = args.modules if args.modules else ["droid"] |
| 94 | |
Fabián Cañas | c1f344e | 2024-04-24 16:23:41 -0400 | [diff] [blame] | 95 | match args.products: |
| 96 | case ["*"]: |
| 97 | products = get_build_var("all_named_products").split(" ") |
| 98 | case _: |
| 99 | products = args.products |
| 100 | |
Joe Onorato | cccf2ca | 2022-09-28 22:52:56 -0700 | [diff] [blame] | 101 | # Get the list of sources for all of the requested build combos |
Fabián Cañas | c1f344e | 2024-04-24 16:23:41 -0400 | [diff] [blame] | 102 | if not products and not args.variants: |
| 103 | m_nothing() |
| 104 | if args.modules == ["*"]: |
| 105 | modules = get_all_modules() |
Joe Onorato | cccf2ca | 2022-09-28 22:52:56 -0700 | [diff] [blame] | 106 | sources = get_sources(modules) |
| 107 | else: |
Fabián Cañas | c1f344e | 2024-04-24 16:23:41 -0400 | [diff] [blame] | 108 | if not products: |
Joe Onorato | cccf2ca | 2022-09-28 22:52:56 -0700 | [diff] [blame] | 109 | sys.stderr.write("Error: --products must be supplied if --variants is supplied") |
| 110 | sys.exit(1) |
| 111 | sources = set() |
| 112 | build_num = 1 |
Fabián Cañas | c1f344e | 2024-04-24 16:23:41 -0400 | [diff] [blame] | 113 | for product in products: |
Joe Onorato | cccf2ca | 2022-09-28 22:52:56 -0700 | [diff] [blame] | 114 | os.environ["TARGET_PRODUCT"] = product |
| 115 | variants = args.variants if args.variants else ["user", "userdebug", "eng"] |
| 116 | for variant in variants: |
Fabián Cañas | c1f344e | 2024-04-24 16:23:41 -0400 | [diff] [blame] | 117 | sys.stderr.write(f"Analyzing build {build_num} of {len(products)*len(variants)}\r") |
Joe Onorato | cccf2ca | 2022-09-28 22:52:56 -0700 | [diff] [blame] | 118 | os.environ["TARGET_BUILD_VARIANT"] = variant |
| 119 | m_nothing() |
Fabián Cañas | c1f344e | 2024-04-24 16:23:41 -0400 | [diff] [blame] | 120 | if args.modules == ["*"]: |
| 121 | modules = get_all_modules() |
Joe Onorato | cccf2ca | 2022-09-28 22:52:56 -0700 | [diff] [blame] | 122 | sources.update(get_sources(modules)) |
| 123 | build_num += 1 |
| 124 | sys.stderr.write("\n\n") |
| 125 | |
| 126 | sources = sorted(sources) |
| 127 | |
Fabián Cañas | 97ea68a | 2024-04-23 20:36:14 -0400 | [diff] [blame] | 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) |
Joe Onorato | cccf2ca | 2022-09-28 22:52:56 -0700 | [diff] [blame] | 143 | |
| 144 | |
| 145 | if __name__ == "__main__": |
| 146 | sys.exit(main(sys.argv)) |
| 147 | |
| 148 | |
| 149 | # vim: set ts=2 sw=2 sts=2 expandtab nocindent tw=100: |