xref: /aosp_15_r20/build/make/tools/find_static_candidates.py (revision 9e94795a3d4ef5c1d47486f9a02bb378756cea8a)
1*9e94795aSAndroid Build Coastguard Worker#!/usr/bin/env python3
2*9e94795aSAndroid Build Coastguard Worker
3*9e94795aSAndroid Build Coastguard Worker"""Tool to find static libraries that maybe should be shared libraries and shared libraries that maybe should be static libraries.
4*9e94795aSAndroid Build Coastguard Worker
5*9e94795aSAndroid Build Coastguard WorkerThis tool only looks at the module-info.json for the current target.
6*9e94795aSAndroid Build Coastguard Worker
7*9e94795aSAndroid Build Coastguard WorkerExample of "class" types for each of the modules in module-info.json
8*9e94795aSAndroid Build Coastguard Worker  "EXECUTABLES": 2307,
9*9e94795aSAndroid Build Coastguard Worker  "ETC": 9094,
10*9e94795aSAndroid Build Coastguard Worker  "NATIVE_TESTS": 10461,
11*9e94795aSAndroid Build Coastguard Worker  "APPS": 2885,
12*9e94795aSAndroid Build Coastguard Worker  "JAVA_LIBRARIES": 5205,
13*9e94795aSAndroid Build Coastguard Worker  "EXECUTABLES/JAVA_LIBRARIES": 119,
14*9e94795aSAndroid Build Coastguard Worker  "FAKE": 553,
15*9e94795aSAndroid Build Coastguard Worker  "SHARED_LIBRARIES/STATIC_LIBRARIES": 7591,
16*9e94795aSAndroid Build Coastguard Worker  "STATIC_LIBRARIES": 11535,
17*9e94795aSAndroid Build Coastguard Worker  "SHARED_LIBRARIES": 10852,
18*9e94795aSAndroid Build Coastguard Worker  "HEADER_LIBRARIES": 1897,
19*9e94795aSAndroid Build Coastguard Worker  "DYLIB_LIBRARIES": 1262,
20*9e94795aSAndroid Build Coastguard Worker  "RLIB_LIBRARIES": 3413,
21*9e94795aSAndroid Build Coastguard Worker  "ROBOLECTRIC": 39,
22*9e94795aSAndroid Build Coastguard Worker  "PACKAGING": 5,
23*9e94795aSAndroid Build Coastguard Worker  "PROC_MACRO_LIBRARIES": 36,
24*9e94795aSAndroid Build Coastguard Worker  "RENDERSCRIPT_BITCODE": 17,
25*9e94795aSAndroid Build Coastguard Worker  "DYLIB_LIBRARIES/RLIB_LIBRARIES": 8,
26*9e94795aSAndroid Build Coastguard Worker  "ETC/FAKE": 1
27*9e94795aSAndroid Build Coastguard Worker
28*9e94795aSAndroid Build Coastguard WorkerNone of the "SHARED_LIBRARIES/STATIC_LIBRARIES" are double counted in the
29*9e94795aSAndroid Build Coastguard Workermodules with one class
30*9e94795aSAndroid Build Coastguard WorkerRLIB/
31*9e94795aSAndroid Build Coastguard Worker
32*9e94795aSAndroid Build Coastguard WorkerAll of these classes have shared_libs and/or static_libs
33*9e94795aSAndroid Build Coastguard Worker    "EXECUTABLES",
34*9e94795aSAndroid Build Coastguard Worker    "SHARED_LIBRARIES",
35*9e94795aSAndroid Build Coastguard Worker    "STATIC_LIBRARIES",
36*9e94795aSAndroid Build Coastguard Worker    "SHARED_LIBRARIES/STATIC_LIBRARIES", # cc_library
37*9e94795aSAndroid Build Coastguard Worker    "HEADER_LIBRARIES",
38*9e94795aSAndroid Build Coastguard Worker    "NATIVE_TESTS", # test modules
39*9e94795aSAndroid Build Coastguard Worker    "DYLIB_LIBRARIES", # rust
40*9e94795aSAndroid Build Coastguard Worker    "RLIB_LIBRARIES", # rust
41*9e94795aSAndroid Build Coastguard Worker    "ETC", # rust_bindgen
42*9e94795aSAndroid Build Coastguard Worker"""
43*9e94795aSAndroid Build Coastguard Worker
44*9e94795aSAndroid Build Coastguard Workerfrom collections import defaultdict
45*9e94795aSAndroid Build Coastguard Worker
46*9e94795aSAndroid Build Coastguard Workerimport json, os, argparse
47*9e94795aSAndroid Build Coastguard Worker
48*9e94795aSAndroid Build Coastguard WorkerANDROID_PRODUCT_OUT = os.environ.get("ANDROID_PRODUCT_OUT")
49*9e94795aSAndroid Build Coastguard Worker# If a shared library is used less than MAX_SHARED_INCLUSIONS times in a target,
50*9e94795aSAndroid Build Coastguard Worker# then it will likely save memory by changing it to a static library
51*9e94795aSAndroid Build Coastguard Worker# This move will also use less storage
52*9e94795aSAndroid Build Coastguard WorkerMAX_SHARED_INCLUSIONS = 2
53*9e94795aSAndroid Build Coastguard Worker# If a static library is used more than MAX_STATIC_INCLUSIONS times in a target,
54*9e94795aSAndroid Build Coastguard Worker# then it will likely save memory by changing it to a shared library
55*9e94795aSAndroid Build Coastguard Worker# This move will also likely use less storage
56*9e94795aSAndroid Build Coastguard WorkerMIN_STATIC_INCLUSIONS = 3
57*9e94795aSAndroid Build Coastguard Worker
58*9e94795aSAndroid Build Coastguard Worker
59*9e94795aSAndroid Build Coastguard Workerdef parse_args():
60*9e94795aSAndroid Build Coastguard Worker  parser = argparse.ArgumentParser(
61*9e94795aSAndroid Build Coastguard Worker      description=(
62*9e94795aSAndroid Build Coastguard Worker          "Parse module-info.jso and display information about static and"
63*9e94795aSAndroid Build Coastguard Worker          " shared library dependencies."
64*9e94795aSAndroid Build Coastguard Worker      )
65*9e94795aSAndroid Build Coastguard Worker  )
66*9e94795aSAndroid Build Coastguard Worker  parser.add_argument(
67*9e94795aSAndroid Build Coastguard Worker      "--module", dest="module", help="Print the info for the module."
68*9e94795aSAndroid Build Coastguard Worker  )
69*9e94795aSAndroid Build Coastguard Worker  parser.add_argument(
70*9e94795aSAndroid Build Coastguard Worker      "--shared",
71*9e94795aSAndroid Build Coastguard Worker      dest="print_shared",
72*9e94795aSAndroid Build Coastguard Worker      action=argparse.BooleanOptionalAction,
73*9e94795aSAndroid Build Coastguard Worker      help=(
74*9e94795aSAndroid Build Coastguard Worker          "Print the list of libraries that are shared_libs for fewer than {}"
75*9e94795aSAndroid Build Coastguard Worker          " modules.".format(MAX_SHARED_INCLUSIONS)
76*9e94795aSAndroid Build Coastguard Worker      ),
77*9e94795aSAndroid Build Coastguard Worker  )
78*9e94795aSAndroid Build Coastguard Worker  parser.add_argument(
79*9e94795aSAndroid Build Coastguard Worker      "--static",
80*9e94795aSAndroid Build Coastguard Worker      dest="print_static",
81*9e94795aSAndroid Build Coastguard Worker      action=argparse.BooleanOptionalAction,
82*9e94795aSAndroid Build Coastguard Worker      help=(
83*9e94795aSAndroid Build Coastguard Worker          "Print the list of libraries that are static_libs for more than {}"
84*9e94795aSAndroid Build Coastguard Worker          " modules.".format(MIN_STATIC_INCLUSIONS)
85*9e94795aSAndroid Build Coastguard Worker      ),
86*9e94795aSAndroid Build Coastguard Worker  )
87*9e94795aSAndroid Build Coastguard Worker  parser.add_argument(
88*9e94795aSAndroid Build Coastguard Worker      "--recursive",
89*9e94795aSAndroid Build Coastguard Worker      dest="recursive",
90*9e94795aSAndroid Build Coastguard Worker      action=argparse.BooleanOptionalAction,
91*9e94795aSAndroid Build Coastguard Worker      default=True,
92*9e94795aSAndroid Build Coastguard Worker      help=(
93*9e94795aSAndroid Build Coastguard Worker          "Gather all dependencies of EXECUTABLES recursvily before calculating"
94*9e94795aSAndroid Build Coastguard Worker          " the stats. This eliminates duplicates from multiple libraries"
95*9e94795aSAndroid Build Coastguard Worker          " including the same dependencies in a single binary."
96*9e94795aSAndroid Build Coastguard Worker      ),
97*9e94795aSAndroid Build Coastguard Worker  )
98*9e94795aSAndroid Build Coastguard Worker  parser.add_argument(
99*9e94795aSAndroid Build Coastguard Worker      "--both",
100*9e94795aSAndroid Build Coastguard Worker      dest="both",
101*9e94795aSAndroid Build Coastguard Worker      action=argparse.BooleanOptionalAction,
102*9e94795aSAndroid Build Coastguard Worker      default=False,
103*9e94795aSAndroid Build Coastguard Worker      help=(
104*9e94795aSAndroid Build Coastguard Worker          "Print a list of libraries that are including libraries as both"
105*9e94795aSAndroid Build Coastguard Worker          " static and shared"
106*9e94795aSAndroid Build Coastguard Worker      ),
107*9e94795aSAndroid Build Coastguard Worker  )
108*9e94795aSAndroid Build Coastguard Worker  return parser.parse_args()
109*9e94795aSAndroid Build Coastguard Worker
110*9e94795aSAndroid Build Coastguard Worker
111*9e94795aSAndroid Build Coastguard Workerclass TransitiveHelper:
112*9e94795aSAndroid Build Coastguard Worker
113*9e94795aSAndroid Build Coastguard Worker  def __init__(self):
114*9e94795aSAndroid Build Coastguard Worker    # keep a list of already expanded libraries so we don't end up in a cycle
115*9e94795aSAndroid Build Coastguard Worker    self.visited = defaultdict(lambda: defaultdict(set))
116*9e94795aSAndroid Build Coastguard Worker
117*9e94795aSAndroid Build Coastguard Worker  # module is an object from the module-info dictionary
118*9e94795aSAndroid Build Coastguard Worker  # module_info is the dictionary from module-info.json
119*9e94795aSAndroid Build Coastguard Worker  # modify the module's shared_libs and static_libs with all of the transient
120*9e94795aSAndroid Build Coastguard Worker  # dependencies required from all of the explicit dependencies
121*9e94795aSAndroid Build Coastguard Worker  def flattenDeps(self, module, module_info):
122*9e94795aSAndroid Build Coastguard Worker    libs_snapshot = dict(shared_libs = set(module.get("shared_libs",{})), static_libs = set(module.get("static_libs",{})))
123*9e94795aSAndroid Build Coastguard Worker
124*9e94795aSAndroid Build Coastguard Worker    for lib_class in ["shared_libs", "static_libs"]:
125*9e94795aSAndroid Build Coastguard Worker      for lib in libs_snapshot[lib_class]:
126*9e94795aSAndroid Build Coastguard Worker        if not lib or lib not in module_info or lib_class not in module:
127*9e94795aSAndroid Build Coastguard Worker          continue
128*9e94795aSAndroid Build Coastguard Worker        if lib in self.visited:
129*9e94795aSAndroid Build Coastguard Worker          module[lib_class].update(self.visited[lib][lib_class])
130*9e94795aSAndroid Build Coastguard Worker        else:
131*9e94795aSAndroid Build Coastguard Worker          res = self.flattenDeps(module_info[lib], module_info)
132*9e94795aSAndroid Build Coastguard Worker          module[lib_class].update(res.get(lib_class, {}))
133*9e94795aSAndroid Build Coastguard Worker          self.visited[lib][lib_class].update(res.get(lib_class, {}))
134*9e94795aSAndroid Build Coastguard Worker
135*9e94795aSAndroid Build Coastguard Worker    return module
136*9e94795aSAndroid Build Coastguard Worker
137*9e94795aSAndroid Build Coastguard Workerdef main():
138*9e94795aSAndroid Build Coastguard Worker  module_info = json.load(open(ANDROID_PRODUCT_OUT + "/module-info.json"))
139*9e94795aSAndroid Build Coastguard Worker
140*9e94795aSAndroid Build Coastguard Worker  args = parse_args()
141*9e94795aSAndroid Build Coastguard Worker
142*9e94795aSAndroid Build Coastguard Worker  if args.module:
143*9e94795aSAndroid Build Coastguard Worker    if args.module not in module_info:
144*9e94795aSAndroid Build Coastguard Worker      print("Module {} does not exist".format(args.module))
145*9e94795aSAndroid Build Coastguard Worker      exit(1)
146*9e94795aSAndroid Build Coastguard Worker
147*9e94795aSAndroid Build Coastguard Worker  # turn all of the static_libs and shared_libs lists into sets to make them
148*9e94795aSAndroid Build Coastguard Worker  # easier to update
149*9e94795aSAndroid Build Coastguard Worker  for _, module in module_info.items():
150*9e94795aSAndroid Build Coastguard Worker    module["shared_libs"] = set(module.get("shared_libs", {}))
151*9e94795aSAndroid Build Coastguard Worker    module["static_libs"] = set(module.get("static_libs", {}))
152*9e94795aSAndroid Build Coastguard Worker
153*9e94795aSAndroid Build Coastguard Worker  includedStatically = defaultdict(set)
154*9e94795aSAndroid Build Coastguard Worker  includedSharedly = defaultdict(set)
155*9e94795aSAndroid Build Coastguard Worker  includedBothly = defaultdict(set)
156*9e94795aSAndroid Build Coastguard Worker  transitive = TransitiveHelper()
157*9e94795aSAndroid Build Coastguard Worker  for name, module in module_info.items():
158*9e94795aSAndroid Build Coastguard Worker    if args.recursive:
159*9e94795aSAndroid Build Coastguard Worker      # in this recursive mode we only want to see what is included by the executables
160*9e94795aSAndroid Build Coastguard Worker      if "EXECUTABLES" not in module["class"]:
161*9e94795aSAndroid Build Coastguard Worker        continue
162*9e94795aSAndroid Build Coastguard Worker      module = transitive.flattenDeps(module, module_info)
163*9e94795aSAndroid Build Coastguard Worker      # filter out fuzzers by their dependency on clang
164*9e94795aSAndroid Build Coastguard Worker      if "static_libs" in module:
165*9e94795aSAndroid Build Coastguard Worker        if "libclang_rt.fuzzer" in module["static_libs"]:
166*9e94795aSAndroid Build Coastguard Worker          continue
167*9e94795aSAndroid Build Coastguard Worker    else:
168*9e94795aSAndroid Build Coastguard Worker      if "NATIVE_TESTS" in module["class"]:
169*9e94795aSAndroid Build Coastguard Worker        # We don't care about how tests are including libraries
170*9e94795aSAndroid Build Coastguard Worker        continue
171*9e94795aSAndroid Build Coastguard Worker
172*9e94795aSAndroid Build Coastguard Worker    # count all of the shared and static libs included in this module
173*9e94795aSAndroid Build Coastguard Worker    if "shared_libs" in module:
174*9e94795aSAndroid Build Coastguard Worker      for lib in module["shared_libs"]:
175*9e94795aSAndroid Build Coastguard Worker        includedSharedly[lib].add(name)
176*9e94795aSAndroid Build Coastguard Worker    if "static_libs" in module:
177*9e94795aSAndroid Build Coastguard Worker      for lib in module["static_libs"]:
178*9e94795aSAndroid Build Coastguard Worker        includedStatically[lib].add(name)
179*9e94795aSAndroid Build Coastguard Worker
180*9e94795aSAndroid Build Coastguard Worker    if "shared_libs" in module and  "static_libs" in module:
181*9e94795aSAndroid Build Coastguard Worker      intersection = set(module["shared_libs"]).intersection(
182*9e94795aSAndroid Build Coastguard Worker          module["static_libs"]
183*9e94795aSAndroid Build Coastguard Worker      )
184*9e94795aSAndroid Build Coastguard Worker      if intersection:
185*9e94795aSAndroid Build Coastguard Worker        includedBothly[name] = intersection
186*9e94795aSAndroid Build Coastguard Worker
187*9e94795aSAndroid Build Coastguard Worker  if args.print_shared:
188*9e94795aSAndroid Build Coastguard Worker    print(
189*9e94795aSAndroid Build Coastguard Worker        "Shared libraries that are included by fewer than {} modules on a"
190*9e94795aSAndroid Build Coastguard Worker        " device:".format(MAX_SHARED_INCLUSIONS)
191*9e94795aSAndroid Build Coastguard Worker    )
192*9e94795aSAndroid Build Coastguard Worker    for name, libs in includedSharedly.items():
193*9e94795aSAndroid Build Coastguard Worker      if len(libs) < MAX_SHARED_INCLUSIONS:
194*9e94795aSAndroid Build Coastguard Worker        print("{}: {} included by: {}".format(name, len(libs), libs))
195*9e94795aSAndroid Build Coastguard Worker
196*9e94795aSAndroid Build Coastguard Worker  if args.print_static:
197*9e94795aSAndroid Build Coastguard Worker    print(
198*9e94795aSAndroid Build Coastguard Worker        "Libraries that are included statically by more than {} modules on a"
199*9e94795aSAndroid Build Coastguard Worker        " device:".format(MIN_STATIC_INCLUSIONS)
200*9e94795aSAndroid Build Coastguard Worker    )
201*9e94795aSAndroid Build Coastguard Worker    for name, libs in includedStatically.items():
202*9e94795aSAndroid Build Coastguard Worker      if len(libs) > MIN_STATIC_INCLUSIONS:
203*9e94795aSAndroid Build Coastguard Worker        print("{}: {} included by: {}".format(name, len(libs), libs))
204*9e94795aSAndroid Build Coastguard Worker
205*9e94795aSAndroid Build Coastguard Worker  if args.both:
206*9e94795aSAndroid Build Coastguard Worker    allIncludedBothly = set()
207*9e94795aSAndroid Build Coastguard Worker    for name, libs in includedBothly.items():
208*9e94795aSAndroid Build Coastguard Worker      allIncludedBothly.update(libs)
209*9e94795aSAndroid Build Coastguard Worker
210*9e94795aSAndroid Build Coastguard Worker    print(
211*9e94795aSAndroid Build Coastguard Worker        "List of libraries used both statically and shared in the same"
212*9e94795aSAndroid Build Coastguard Worker        " processes:\n {}\n\n".format("\n".join(sorted(allIncludedBothly)))
213*9e94795aSAndroid Build Coastguard Worker    )
214*9e94795aSAndroid Build Coastguard Worker    print(
215*9e94795aSAndroid Build Coastguard Worker        "List of libraries used both statically and shared in any processes:\n {}".format("\n".join(sorted(includedStatically.keys() & includedSharedly.keys()))))
216*9e94795aSAndroid Build Coastguard Worker
217*9e94795aSAndroid Build Coastguard Worker  if args.module:
218*9e94795aSAndroid Build Coastguard Worker    print(json.dumps(module_info[args.module], default=list, indent=2))
219*9e94795aSAndroid Build Coastguard Worker    print(
220*9e94795aSAndroid Build Coastguard Worker        "{} is included in shared_libs {} times by these modules: {}".format(
221*9e94795aSAndroid Build Coastguard Worker            args.module, len(includedSharedly[args.module]),
222*9e94795aSAndroid Build Coastguard Worker            includedSharedly[args.module]
223*9e94795aSAndroid Build Coastguard Worker        )
224*9e94795aSAndroid Build Coastguard Worker    )
225*9e94795aSAndroid Build Coastguard Worker    print(
226*9e94795aSAndroid Build Coastguard Worker        "{} is included in static_libs {} times by these modules: {}".format(
227*9e94795aSAndroid Build Coastguard Worker            args.module, len(includedStatically[args.module]),
228*9e94795aSAndroid Build Coastguard Worker            includedStatically[args.module]
229*9e94795aSAndroid Build Coastguard Worker        )
230*9e94795aSAndroid Build Coastguard Worker    )
231*9e94795aSAndroid Build Coastguard Worker    print("Shared libs included by this module that are used in fewer than {} processes:\n{}".format(
232*9e94795aSAndroid Build Coastguard Worker        MAX_SHARED_INCLUSIONS, [x for x in module_info[args.module]["shared_libs"] if len(includedSharedly[x]) < MAX_SHARED_INCLUSIONS]))
233*9e94795aSAndroid Build Coastguard Worker
234*9e94795aSAndroid Build Coastguard Worker
235*9e94795aSAndroid Build Coastguard Worker
236*9e94795aSAndroid Build Coastguard Workerif __name__ == "__main__":
237*9e94795aSAndroid Build Coastguard Worker  main()
238