xref: /aosp_15_r20/build/make/tools/whichgit (revision 9e94795a3d4ef5c1d47486f9a02bb378756cea8a)
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