blob: 55c8c6fb5a23ef456a235e7e095712886b1f8247 [file] [log] [blame]
Joe Onoratocccf2ca2022-09-28 22:52:56 -07001#!/usr/bin/env python3
2
3import argparse
Fabián Cañasc1f344e2024-04-24 16:23:41 -04004import itertools
Joe Onoratocccf2ca2022-09-28 22:52:56 -07005import 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
Fabián Cañasc1f344e2024-04-24 16:23:41 -040014def 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
Joe Onoratocccf2ca2022-09-28 22:52:56 -070030def get_sources(modules):
Fabián Cañasc1f344e2024-04-24 16:23:41 -040031 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 Onoratocccf2ca2022-09-28 22:52:56 -070042
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
Fabián Cañas97ea68a2024-04-23 20:36:14 -040073 return referenced_dirs
Joe Onoratocccf2ca2022-09-28 22:52:56 -070074
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="*",
Fabián Cañasc1f344e2024-04-24 16:23:41 -040080 help="One or more TARGET_PRODUCT to check, or \"*\" for all. If not provided"
81 + "just uses whatever has already been built")
Joe Onoratocccf2ca2022-09-28 22:52:56 -070082 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ñasc1f344e2024-04-24 16:23:41 -040086 help="The build modules to check, or \"*\" for all, or droid if not supplied")
Joe Onoratocccf2ca2022-09-28 22:52:56 -070087 ap.add_argument("--why", nargs="*",
88 help="Also print the input files used in these projects, or \"*\" for all")
Fabián Cañas97ea68a2024-04-23 20:36:14 -040089 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 Onoratocccf2ca2022-09-28 22:52:56 -070091 args = ap.parse_args(argv[1:])
92
93 modules = args.modules if args.modules else ["droid"]
94
Fabián Cañasc1f344e2024-04-24 16:23:41 -040095 match args.products:
96 case ["*"]:
97 products = get_build_var("all_named_products").split(" ")
98 case _:
99 products = args.products
100
Joe Onoratocccf2ca2022-09-28 22:52:56 -0700101 # Get the list of sources for all of the requested build combos
Fabián Cañasc1f344e2024-04-24 16:23:41 -0400102 if not products and not args.variants:
103 m_nothing()
104 if args.modules == ["*"]:
105 modules = get_all_modules()
Joe Onoratocccf2ca2022-09-28 22:52:56 -0700106 sources = get_sources(modules)
107 else:
Fabián Cañasc1f344e2024-04-24 16:23:41 -0400108 if not products:
Joe Onoratocccf2ca2022-09-28 22:52:56 -0700109 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ñasc1f344e2024-04-24 16:23:41 -0400113 for product in products:
Joe Onoratocccf2ca2022-09-28 22:52:56 -0700114 os.environ["TARGET_PRODUCT"] = product
115 variants = args.variants if args.variants else ["user", "userdebug", "eng"]
116 for variant in variants:
Fabián Cañasc1f344e2024-04-24 16:23:41 -0400117 sys.stderr.write(f"Analyzing build {build_num} of {len(products)*len(variants)}\r")
Joe Onoratocccf2ca2022-09-28 22:52:56 -0700118 os.environ["TARGET_BUILD_VARIANT"] = variant
119 m_nothing()
Fabián Cañasc1f344e2024-04-24 16:23:41 -0400120 if args.modules == ["*"]:
121 modules = get_all_modules()
Joe Onoratocccf2ca2022-09-28 22:52:56 -0700122 sources.update(get_sources(modules))
123 build_num += 1
124 sys.stderr.write("\n\n")
125
126 sources = sorted(sources)
127
Fabián Cañas97ea68a2024-04-23 20:36:14 -0400128 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 Onoratocccf2ca2022-09-28 22:52:56 -0700143
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: