blob: 3fc6236785ac0e9e6b3513d6c0aacd9a48316d00 [file] [log] [blame]
Joe Onorato02fb89a2020-06-27 00:10:23 -07001#!/usr/bin/env python3
2
3"""
4Command to print info about makefiles remaining to be converted to soong.
5
6See usage / argument parsing below for commandline options.
7"""
8
9import argparse
10import csv
11import itertools
12import json
13import os
14import re
15import sys
16
17DIRECTORY_PATTERNS = [x.split("/") for x in (
18 "device/*",
19 "frameworks/*",
20 "hardware/*",
21 "packages/*",
22 "vendor/*",
23 "*",
24)]
25
26def match_directory_group(pattern, filename):
27 match = []
28 filename = filename.split("/")
29 if len(filename) < len(pattern):
30 return None
31 for i in range(len(pattern)):
32 pattern_segment = pattern[i]
33 filename_segment = filename[i]
34 if pattern_segment == "*" or pattern_segment == filename_segment:
35 match.append(filename_segment)
36 else:
37 return None
38 if match:
39 return os.path.sep.join(match)
40 else:
41 return None
42
43def directory_group(filename):
44 for pattern in DIRECTORY_PATTERNS:
45 match = match_directory_group(pattern, filename)
46 if match:
47 return match
48 return os.path.dirname(filename)
49
50class Analysis(object):
51 def __init__(self, filename, line_matches):
52 self.filename = filename;
53 self.line_matches = line_matches
54
55def analyze_lines(filename, lines, func):
56 line_matches = []
57 for i in range(len(lines)):
58 line = lines[i]
59 stripped = line.strip()
60 if stripped.startswith("#"):
61 continue
62 if func(stripped):
63 line_matches.append((i+1, line))
64 if line_matches:
65 return Analysis(filename, line_matches);
66
67def analyze_has_conditional(line):
68 return (line.startswith("ifeq") or line.startswith("ifneq")
69 or line.startswith("ifdef") or line.startswith("ifndef"))
70
71NORMAL_INCLUDES = [re.compile(pattern) for pattern in (
72 "include \$+\(CLEAR_VARS\)", # These are in defines which are tagged separately
73 "include \$+\(BUILD_.*\)",
74 "include \$\(call first-makefiles-under, *\$\(LOCAL_PATH\)\)",
75 "include \$\(call all-subdir-makefiles\)",
76 "include \$\(all-subdir-makefiles\)",
77 "include \$\(call all-makefiles-under, *\$\(LOCAL_PATH\)\)",
78 "include \$\(call all-makefiles-under, *\$\(call my-dir\).*\)",
79 "include \$\(BUILD_SYSTEM\)/base_rules.mk", # called out separately
80 "include \$\(call all-named-subdir-makefiles,.*\)",
81 "include \$\(subdirs\)",
82)]
83def analyze_has_wacky_include(line):
84 if not (line.startswith("include") or line.startswith("-include")
85 or line.startswith("sinclude")):
86 return False
87 for matcher in NORMAL_INCLUDES:
88 if matcher.fullmatch(line):
89 return False
90 return True
91
92BASE_RULES_RE = re.compile("include \$\(BUILD_SYSTEM\)/base_rules.mk")
93
94class Analyzer(object):
95 def __init__(self, title, func):
96 self.title = title;
97 self.func = func
98
99
100ANALYZERS = (
101 Analyzer("ifeq / ifneq", analyze_has_conditional),
102 Analyzer("Wacky Includes", analyze_has_wacky_include),
103 Analyzer("Calls base_rules", lambda line: BASE_RULES_RE.fullmatch(line)),
104 Analyzer("Calls define", lambda line: line.startswith("define ")),
105 Analyzer("Has ../", lambda line: "../" in line),
106 Analyzer("dist-for-&#8203;goals", lambda line: "dist-for-goals" in line),
107 Analyzer(".PHONY", lambda line: ".PHONY" in line),
108 Analyzer("render-&#8203;script", lambda line: ".rscript" in line),
109 Analyzer("vts src", lambda line: ".vts" in line),
110 Analyzer("COPY_&#8203;HEADERS", lambda line: "LOCAL_COPY_HEADERS" in line),
111)
112
113class Summary(object):
114 def __init__(self):
115 self.makefiles = dict()
116 self.directories = dict()
117
118 def Add(self, makefile):
119 self.makefiles[makefile.filename] = makefile
120 self.directories.setdefault(directory_group(makefile.filename), []).append(makefile)
121
122class Makefile(object):
123 def __init__(self, filename):
124 self.filename = filename
125
126 # Analyze the file
127 with open(filename, "r", errors="ignore") as f:
128 try:
129 lines = f.readlines()
130 except UnicodeDecodeError as ex:
131 sys.stderr.write("Filename: %s\n" % filename)
132 raise ex
133 lines = [line.strip() for line in lines]
134
135 self.analyses = dict([(analyzer, analyze_lines(filename, lines, analyzer.func)) for analyzer
136 in ANALYZERS])
137
138def find_android_mk():
139 cwd = os.getcwd()
140 for root, dirs, files in os.walk(cwd):
141 for filename in files:
142 if filename == "Android.mk":
143 yield os.path.join(root, filename)[len(cwd) + 1:]
144 for ignore in (".git", ".repo"):
145 if ignore in dirs:
146 dirs.remove(ignore)
147
148def is_aosp(dirname):
149 for d in ("device/sample", "hardware/interfaces", "hardware/libhardware",
150 "hardware/ril"):
151 if dirname.startswith(d):
152 return True
153 for d in ("device/", "hardware/", "vendor/"):
154 if dirname.startswith(d):
155 return False
156 return True
157
158def is_google(dirname):
159 for d in ("device/google",
160 "hardware/google",
161 "test/sts",
162 "vendor/auto",
163 "vendor/google",
164 "vendor/unbundled_google",
165 "vendor/widevine",
166 "vendor/xts"):
167 if dirname.startswith(d):
168 return True
169 return False
170
Joe Onorato02fb89a2020-06-27 00:10:23 -0700171def is_clean(makefile):
172 for analysis in makefile.analyses.values():
173 if analysis:
174 return False
175 return True
176
Joe Onorato31986072020-08-10 09:58:49 -0700177def clean_and_only_blocked_by_clean(soong, all_makefiles, makefile):
178 if not is_clean(makefile):
179 return False
180 modules = soong.reverse_makefiles[makefile.filename]
181 for module in modules:
182 for dep in soong.transitive_deps(module):
183 for filename in soong.makefiles.get(dep, []):
184 m = all_makefiles.get(filename)
185 if m and not is_clean(m):
186 return False
187 return True
188
Joe Onorato02fb89a2020-06-27 00:10:23 -0700189class Annotations(object):
190 def __init__(self):
191 self.entries = []
192 self.count = 0
193
194 def Add(self, makefiles, modules):
195 self.entries.append((makefiles, modules))
196 self.count += 1
197 return self.count-1
198
199class SoongData(object):
200 def __init__(self, reader):
201 """Read the input file and store the modules and dependency mappings.
202 """
203 self.problems = dict()
204 self.deps = dict()
205 self.reverse_deps = dict()
206 self.module_types = dict()
207 self.makefiles = dict()
208 self.reverse_makefiles = dict()
209 self.installed = dict()
Joe Onorato31986072020-08-10 09:58:49 -0700210 self.reverse_installed = dict()
Joe Onorato02fb89a2020-06-27 00:10:23 -0700211 self.modules = set()
212
213 for (module, module_type, problem, dependencies, makefiles, installed) in reader:
214 self.modules.add(module)
215 makefiles = [f for f in makefiles.strip().split(' ') if f != ""]
216 self.module_types[module] = module_type
217 self.problems[module] = problem
218 self.deps[module] = [d for d in dependencies.strip().split(' ') if d != ""]
219 for dep in self.deps[module]:
220 if not dep in self.reverse_deps:
221 self.reverse_deps[dep] = []
222 self.reverse_deps[dep].append(module)
223 self.makefiles[module] = makefiles
224 for f in makefiles:
225 self.reverse_makefiles.setdefault(f, []).append(module)
226 for f in installed.strip().split(' '):
227 self.installed[f] = module
Joe Onorato31986072020-08-10 09:58:49 -0700228 self.reverse_installed.setdefault(module, []).append(f)
Joe Onorato02fb89a2020-06-27 00:10:23 -0700229
Joe Onorato934bd8d2020-07-16 18:10:48 -0700230 def transitive_deps(self, module):
231 results = set()
232 def traverse(module):
233 for dep in self.deps.get(module, []):
234 if not dep in results:
235 results.add(dep)
236 traverse(module)
237 traverse(module)
238 return results
239
Joe Onorato8ade9b22020-07-20 23:19:43 -0700240 def contains_unblocked_modules(self, filename):
241 for m in self.reverse_makefiles[filename]:
242 if len(self.deps[m]) == 0:
243 return True
244 return False
245
246 def contains_blocked_modules(self, filename):
247 for m in self.reverse_makefiles[filename]:
248 if len(self.deps[m]) > 0:
249 return True
250 return False
251
Joe Onorato02fb89a2020-06-27 00:10:23 -0700252def count_deps(depsdb, module, seen):
253 """Based on the depsdb, count the number of transitive dependencies.
254
255 You can pass in an reversed dependency graph to count the number of
256 modules that depend on the module."""
257 count = 0
258 seen.append(module)
259 if module in depsdb:
260 for dep in depsdb[module]:
261 if dep in seen:
262 continue
263 count += 1 + count_deps(depsdb, dep, seen)
264 return count
265
Joe Onorato02fb89a2020-06-27 00:10:23 -0700266OTHER_PARTITON = "_other"
267HOST_PARTITON = "_host"
268
269def get_partition_from_installed(HOST_OUT_ROOT, PRODUCT_OUT, filename):
270 host_prefix = HOST_OUT_ROOT + "/"
271 device_prefix = PRODUCT_OUT + "/"
272
273 if filename.startswith(host_prefix):
274 return HOST_PARTITON
275
276 elif filename.startswith(device_prefix):
277 index = filename.find("/", len(device_prefix))
278 if index < 0:
279 return OTHER_PARTITON
280 return filename[len(device_prefix):index]
281
282 return OTHER_PARTITON
283
284def format_module_link(module):
285 return "<a class='ModuleLink' href='#module_%s'>%s</a>" % (module, module)
286
287def format_module_list(modules):
288 return "".join(["<div>%s</div>" % format_module_link(m) for m in modules])
289
Joe Onorato934bd8d2020-07-16 18:10:48 -0700290def print_analysis_header(link, title):
291 print("""
292 <a name="%(link)s"></a>
293 <h2>%(title)s</h2>
294 <table>
295 <tr>
296 <th class="RowTitle">Directory</th>
297 <th class="Count">Total</th>
298 <th class="Count Clean">Easy</th>
299 <th class="Count Clean">Unblocked Clean</th>
300 <th class="Count Unblocked">Unblocked</th>
301 <th class="Count Blocked">Blocked</th>
302 <th class="Count Clean">Clean</th>
303 """ % {
304 "link": link,
305 "title": title
306 })
307 for analyzer in ANALYZERS:
308 print("""<th class="Count Warning">%s</th>""" % analyzer.title)
309 print(" </tr>")
310
Yuntao Xuc21dc3c2022-03-17 18:39:36 -0700311# get all modules in $(PRODUCT_PACKAGE) and the corresponding deps
312def get_module_product_packages_plus_deps(initial_modules, result, soong_data):
313 for module in initial_modules:
314 if module in result:
315 continue
316 result.add(module)
317 if module in soong_data.deps:
318 get_module_product_packages_plus_deps(soong_data.deps[module], result, soong_data)
319
Joe Onorato02fb89a2020-06-27 00:10:23 -0700320def main():
321 parser = argparse.ArgumentParser(description="Info about remaining Android.mk files.")
322 parser.add_argument("--device", type=str, required=True,
323 help="TARGET_DEVICE")
Yuntao Xuc21dc3c2022-03-17 18:39:36 -0700324 parser.add_argument("--product-packages", type=argparse.FileType('r'),
325 default=None,
326 help="PRODUCT_PACKAGES")
Joe Onorato02fb89a2020-06-27 00:10:23 -0700327 parser.add_argument("--title", type=str,
328 help="page title")
329 parser.add_argument("--codesearch", type=str,
330 default="https://cs.android.com/android/platform/superproject/+/master:",
331 help="page title")
Yuntao Xuc21dc3c2022-03-17 18:39:36 -0700332 parser.add_argument("--out-dir", type=str,
Joe Onorato02fb89a2020-06-27 00:10:23 -0700333 default=None,
334 help="Equivalent of $OUT_DIR, which will also be checked if"
Yuntao Xuc21dc3c2022-03-17 18:39:36 -0700335 + " --out-dir is unset. If neither is set, default is"
Joe Onorato02fb89a2020-06-27 00:10:23 -0700336 + " 'out'.")
Joe Onorato31986072020-08-10 09:58:49 -0700337 parser.add_argument("--mode", type=str,
338 default="html",
339 help="output format: csv or html")
Joe Onorato02fb89a2020-06-27 00:10:23 -0700340
341 args = parser.parse_args()
342
343 # Guess out directory name
344 if not args.out_dir:
345 args.out_dir = os.getenv("OUT_DIR", "out")
346 while args.out_dir.endswith("/") and len(args.out_dir) > 1:
347 args.out_dir = args.out_dir[:-1]
348
349 TARGET_DEVICE = args.device
Joe Onorato8ade9b22020-07-20 23:19:43 -0700350 global HOST_OUT_ROOT
Joe Onorato934bd8d2020-07-16 18:10:48 -0700351 HOST_OUT_ROOT = args.out_dir + "/host"
Joe Onorato8ade9b22020-07-20 23:19:43 -0700352 global PRODUCT_OUT
Joe Onorato02fb89a2020-06-27 00:10:23 -0700353 PRODUCT_OUT = args.out_dir + "/target/product/%s" % TARGET_DEVICE
354
Joe Onorato02fb89a2020-06-27 00:10:23 -0700355 # Read target information
356 # TODO: Pull from configurable location. This is also slightly different because it's
357 # only a single build, where as the tree scanning we do below is all Android.mk files.
358 with open("%s/obj/PACKAGING/soong_conversion_intermediates/soong_conv_data"
359 % PRODUCT_OUT, "r", errors="ignore") as csvfile:
360 soong = SoongData(csv.reader(csvfile))
361
Joe Onorato8ade9b22020-07-20 23:19:43 -0700362 # Read the makefiles
363 all_makefiles = dict()
364 for filename, modules in soong.reverse_makefiles.items():
365 if filename.startswith(args.out_dir + "/"):
366 continue
367 all_makefiles[filename] = Makefile(filename)
Joe Onorato02fb89a2020-06-27 00:10:23 -0700368
Yuntao Xuc21dc3c2022-03-17 18:39:36 -0700369 # Get all the modules in $(PRODUCT_PACKAGES) and the correspoding deps
370 product_package_modules_plus_deps = set()
371 if args.product_packages:
372 product_package_top_modules = args.product_packages.read().strip().split('\n')
373 get_module_product_packages_plus_deps(product_package_top_modules, product_package_modules_plus_deps, soong)
374
Joe Onorato31986072020-08-10 09:58:49 -0700375 if args.mode == "html":
Yuntao Xuc21dc3c2022-03-17 18:39:36 -0700376 HtmlProcessor(args=args, soong=soong, all_makefiles=all_makefiles,
377 product_packages_modules=product_package_modules_plus_deps).execute()
Joe Onorato31986072020-08-10 09:58:49 -0700378 elif args.mode == "csv":
Yuntao Xuc21dc3c2022-03-17 18:39:36 -0700379 CsvProcessor(args=args, soong=soong, all_makefiles=all_makefiles,
380 product_packages_modules=product_package_modules_plus_deps).execute()
Joe Onorato02fb89a2020-06-27 00:10:23 -0700381
Joe Onorato8ade9b22020-07-20 23:19:43 -0700382class HtmlProcessor(object):
Yuntao Xuc21dc3c2022-03-17 18:39:36 -0700383 def __init__(self, args, soong, all_makefiles, product_packages_modules):
Joe Onorato8ade9b22020-07-20 23:19:43 -0700384 self.args = args
385 self.soong = soong
386 self.all_makefiles = all_makefiles
Yuntao Xuc21dc3c2022-03-17 18:39:36 -0700387 self.product_packages_modules = product_packages_modules
Joe Onorato8ade9b22020-07-20 23:19:43 -0700388 self.annotations = Annotations()
Joe Onorato934bd8d2020-07-16 18:10:48 -0700389
Joe Onorato8ade9b22020-07-20 23:19:43 -0700390 def execute(self):
391 if self.args.title:
392 page_title = self.args.title
393 else:
394 page_title = "Remaining Android.mk files"
Joe Onorato02fb89a2020-06-27 00:10:23 -0700395
Joe Onorato8ade9b22020-07-20 23:19:43 -0700396 # Which modules are installed where
397 modules_by_partition = dict()
398 partitions = set()
399 for installed, module in self.soong.installed.items():
Yuntao Xuc21dc3c2022-03-17 18:39:36 -0700400 if len(self.product_packages_modules) > 0 and module not in self.product_packages_modules:
401 continue
Joe Onorato8ade9b22020-07-20 23:19:43 -0700402 partition = get_partition_from_installed(HOST_OUT_ROOT, PRODUCT_OUT, installed)
403 modules_by_partition.setdefault(partition, []).append(module)
404 partitions.add(partition)
Joe Onorato02fb89a2020-06-27 00:10:23 -0700405
Joe Onorato02fb89a2020-06-27 00:10:23 -0700406 print("""
Joe Onorato8ade9b22020-07-20 23:19:43 -0700407 <html>
408 <head>
409 <title>%(page_title)s</title>
410 <style type="text/css">
411 body, table {
412 font-family: Roboto, sans-serif;
413 font-size: 9pt;
414 }
415 body {
416 margin: 0;
417 padding: 0;
418 display: flex;
419 flex-direction: column;
420 height: 100vh;
421 }
422 #container {
423 flex: 1;
424 display: flex;
425 flex-direction: row;
426 overflow: hidden;
427 }
428 #tables {
429 padding: 0 20px 40px 20px;
430 overflow: scroll;
431 flex: 2 2 600px;
432 }
433 #details {
434 display: none;
435 overflow: scroll;
436 flex: 1 1 650px;
437 padding: 0 20px 0 20px;
438 }
439 h1 {
440 margin: 16px 0 16px 20px;
441 }
442 h2 {
443 margin: 12px 0 4px 0;
444 }
445 .RowTitle {
446 text-align: left;
447 width: 200px;
448 min-width: 200px;
449 }
450 .Count {
451 text-align: center;
452 width: 60px;
453 min-width: 60px;
454 max-width: 60px;
455 }
456 th.Clean,
457 th.Unblocked {
458 background-color: #1e8e3e;
459 }
460 th.Blocked {
461 background-color: #d93025;
462 }
463 th.Warning {
464 background-color: #e8710a;
465 }
466 th {
467 background-color: #1a73e8;
468 color: white;
469 font-weight: bold;
470 }
471 td.Unblocked {
472 background-color: #81c995;
473 }
474 td.Blocked {
475 background-color: #f28b82;
476 }
477 td, th {
478 padding: 2px 4px;
479 border-right: 2px solid white;
480 }
481 tr.TotalRow td {
482 background-color: white;
483 border-right-color: white;
484 }
485 tr.AospDir td {
486 background-color: #e6f4ea;
487 border-right-color: #e6f4ea;
488 }
489 tr.GoogleDir td {
490 background-color: #e8f0fe;
491 border-right-color: #e8f0fe;
492 }
493 tr.PartnerDir td {
494 background-color: #fce8e6;
495 border-right-color: #fce8e6;
496 }
497 table {
498 border-spacing: 0;
499 border-collapse: collapse;
500 }
501 div.Makefile {
502 margin: 12px 0 0 0;
503 }
504 div.Makefile:first {
505 margin-top: 0;
506 }
507 div.FileModules {
508 padding: 4px 0 0 20px;
509 }
510 td.LineNo {
511 vertical-align: baseline;
512 padding: 6px 0 0 20px;
513 width: 50px;
514 vertical-align: baseline;
515 }
516 td.LineText {
517 vertical-align: baseline;
518 font-family: monospace;
519 padding: 6px 0 0 0;
520 }
521 a.CsLink {
522 font-family: monospace;
523 }
524 div.Help {
525 width: 550px;
526 }
527 table.HelpColumns tr {
528 border-bottom: 2px solid white;
529 }
530 .ModuleName {
531 vertical-align: baseline;
532 padding: 6px 0 0 20px;
533 width: 275px;
534 }
535 .ModuleDeps {
536 vertical-align: baseline;
537 padding: 6px 0 0 0;
538 }
539 table#Modules td {
540 vertical-align: baseline;
541 }
542 tr.Alt {
543 background-color: #ececec;
544 }
545 tr.Alt td {
546 border-right-color: #ececec;
547 }
548 .AnalysisCol {
549 width: 300px;
550 padding: 2px;
551 line-height: 21px;
552 }
553 .Analysis {
554 color: white;
555 font-weight: bold;
556 background-color: #e8710a;
557 border-radius: 6px;
558 margin: 4px;
559 padding: 2px 6px;
560 white-space: nowrap;
561 }
562 .Nav {
563 margin: 4px 0 16px 20px;
564 }
565 .NavSpacer {
566 display: inline-block;
567 width: 6px;
568 }
569 .ModuleDetails {
570 margin-top: 20px;
571 }
572 .ModuleDetails td {
573 vertical-align: baseline;
574 }
575 </style>
576 </head>
577 <body>
578 <h1>%(page_title)s</h1>
579 <div class="Nav">
580 <a href='#help'>Help</a>
581 <span class='NavSpacer'></span><span class='NavSpacer'> </span>
582 Partitions:
583 """ % {
584 "page_title": page_title,
585 })
586 for partition in sorted(partitions):
587 print("<a href='#partition_%s'>%s</a><span class='NavSpacer'></span>" % (partition, partition))
588
589 print("""
590 <span class='NavSpacer'></span><span class='NavSpacer'> </span>
591 <a href='#summary'>Overall Summary</a>
592 </div>
593 <div id="container">
594 <div id="tables">
595 <a name="help"></a>
596 <div class="Help">
597 <p>
598 This page analyzes the remaining Android.mk files in the Android Source tree.
599 <p>
600 The modules are first broken down by which of the device filesystem partitions
601 they are installed to. This also includes host tools and testcases which don't
602 actually reside in their own partition but convenitely group together.
603 <p>
604 The makefiles for each partition are further are grouped into a set of directories
605 aritrarily picked to break down the problem size by owners.
606 <ul style="width: 300px">
607 <li style="background-color: #e6f4ea">AOSP directories are colored green.</li>
608 <li style="background-color: #e8f0fe">Google directories are colored blue.</li>
609 <li style="background-color: #fce8e6">Other partner directories are colored red.</li>
610 </ul>
611 Each of the makefiles are scanned for issues that are likely to come up during
612 conversion to soong. Clicking the number in each cell shows additional information,
613 including the line that triggered the warning.
614 <p>
615 <table class="HelpColumns">
616 <tr>
617 <th>Total</th>
618 <td>The total number of makefiles in this each directory.</td>
619 </tr>
620 <tr>
621 <th class="Clean">Easy</th>
622 <td>The number of makefiles that have no warnings themselves, and also
623 none of their dependencies have warnings either.</td>
624 </tr>
625 <tr>
626 <th class="Clean">Unblocked Clean</th>
627 <td>The number of makefiles that are both Unblocked and Clean.</td>
628 </tr>
629
630 <tr>
631 <th class="Unblocked">Unblocked</th>
632 <td>Makefiles containing one or more modules that don't have any
633 additional dependencies pending before conversion.</td>
634 </tr>
635 <tr>
636 <th class="Blocked">Blocked</th>
637 <td>Makefiles containiong one or more modules which <i>do</i> have
638 additional prerequesite depenedencies that are not yet converted.</td>
639 </tr>
640 <tr>
641 <th class="Clean">Clean</th>
642 <td>The number of makefiles that have none of the following warnings.</td>
643 </tr>
644 <tr>
645 <th class="Warning">ifeq / ifneq</th>
646 <td>Makefiles that use <code>ifeq</code> or <code>ifneq</code>. i.e.
647 conditionals.</td>
648 </tr>
649 <tr>
650 <th class="Warning">Wacky Includes</th>
651 <td>Makefiles that <code>include</code> files other than the standard build-system
652 defined template and macros.</td>
653 </tr>
654 <tr>
655 <th class="Warning">Calls base_rules</th>
656 <td>Makefiles that include base_rules.mk directly.</td>
657 </tr>
658 <tr>
659 <th class="Warning">Calls define</th>
660 <td>Makefiles that define their own macros. Some of these are easy to convert
661 to soong <code>defaults</code>, but others are complex.</td>
662 </tr>
663 <tr>
664 <th class="Warning">Has ../</th>
665 <td>Makefiles containing the string "../" outside of a comment. These likely
666 access files outside their directories.</td>
667 </tr>
668 <tr>
669 <th class="Warning">dist-for-goals</th>
670 <td>Makefiles that call <code>dist-for-goals</code> directly.</td>
671 </tr>
672 <tr>
673 <th class="Warning">.PHONY</th>
674 <td>Makefiles that declare .PHONY targets.</td>
675 </tr>
676 <tr>
677 <th class="Warning">renderscript</th>
678 <td>Makefiles defining targets that depend on <code>.rscript</code> source files.</td>
679 </tr>
680 <tr>
681 <th class="Warning">vts src</th>
682 <td>Makefiles defining targets that depend on <code>.vts</code> source files.</td>
683 </tr>
684 <tr>
685 <th class="Warning">COPY_HEADERS</th>
686 <td>Makefiles using LOCAL_COPY_HEADERS.</td>
687 </tr>
688 </table>
689 <p>
690 Following the list of directories is a list of the modules that are installed on
691 each partition. Potential issues from their makefiles are listed, as well as the
692 total number of dependencies (both blocking that module and blocked by that module)
693 and the list of direct dependencies. Note: The number is the number of all transitive
694 dependencies and the list of modules is only the direct dependencies.
695 </div>
Joe Onorato02fb89a2020-06-27 00:10:23 -0700696 """)
697
Joe Onorato8ade9b22020-07-20 23:19:43 -0700698 overall_summary = Summary()
699
700 # For each partition
701 for partition in sorted(partitions):
702 modules = modules_by_partition[partition]
703
704 makefiles = set(itertools.chain.from_iterable(
705 [self.soong.makefiles[module] for module in modules]))
706
707 # Read makefiles
708 summary = Summary()
709 for filename in makefiles:
710 makefile = self.all_makefiles.get(filename)
Joe Onorato02fb89a2020-06-27 00:10:23 -0700711 if makefile:
Joe Onorato8ade9b22020-07-20 23:19:43 -0700712 summary.Add(makefile)
713 overall_summary.Add(makefile)
Joe Onorato02fb89a2020-06-27 00:10:23 -0700714
Joe Onorato8ade9b22020-07-20 23:19:43 -0700715 # Categorize directories by who is responsible
716 aosp_dirs = []
717 google_dirs = []
718 partner_dirs = []
719 for dirname in sorted(summary.directories.keys()):
720 if is_aosp(dirname):
721 aosp_dirs.append(dirname)
722 elif is_google(dirname):
723 google_dirs.append(dirname)
724 else:
725 partner_dirs.append(dirname)
726
727 print_analysis_header("partition_" + partition, partition)
728
729 for dirgroup, rowclass in [(aosp_dirs, "AospDir"),
730 (google_dirs, "GoogleDir"),
731 (partner_dirs, "PartnerDir"),]:
732 for dirname in dirgroup:
733 self.print_analysis_row(summary, modules,
734 dirname, rowclass, summary.directories[dirname])
735
736 self.print_analysis_row(summary, modules,
737 "Total", "TotalRow",
738 set(itertools.chain.from_iterable(summary.directories.values())))
739 print("""
740 </table>
741 """)
742
743 module_details = [(count_deps(self.soong.deps, m, []),
744 -count_deps(self.soong.reverse_deps, m, []), m)
745 for m in modules]
746 module_details.sort()
747 module_details = [m[2] for m in module_details]
748 print("""
749 <table class="ModuleDetails">""")
750 print("<tr>")
751 print(" <th>Module Name</th>")
752 print(" <th>Issues</th>")
753 print(" <th colspan='2'>Blocked By</th>")
754 print(" <th colspan='2'>Blocking</th>")
Joe Onorato02fb89a2020-06-27 00:10:23 -0700755 print("</tr>")
Joe Onorato8ade9b22020-07-20 23:19:43 -0700756 altRow = True
757 for module in module_details:
758 analyses = set()
759 for filename in self.soong.makefiles[module]:
760 makefile = summary.makefiles.get(filename)
761 if makefile:
762 for analyzer, analysis in makefile.analyses.items():
763 if analysis:
764 analyses.add(analyzer.title)
Joe Onorato02fb89a2020-06-27 00:10:23 -0700765
Joe Onorato8ade9b22020-07-20 23:19:43 -0700766 altRow = not altRow
767 print("<tr class='%s'>" % ("Alt" if altRow else "",))
768 print(" <td><a name='module_%s'></a>%s</td>" % (module, module))
769 print(" <td class='AnalysisCol'>%s</td>" % " ".join(["<span class='Analysis'>%s</span>" % title
770 for title in analyses]))
771 print(" <td>%s</td>" % count_deps(self.soong.deps, module, []))
772 print(" <td>%s</td>" % format_module_list(self.soong.deps.get(module, [])))
773 print(" <td>%s</td>" % count_deps(self.soong.reverse_deps, module, []))
774 print(" <td>%s</td>" % format_module_list(self.soong.reverse_deps.get(module, [])))
775 print("</tr>")
776 print("""</table>""")
Joe Onorato934bd8d2020-07-16 18:10:48 -0700777
Joe Onorato8ade9b22020-07-20 23:19:43 -0700778 print_analysis_header("summary", "Overall Summary")
Joe Onorato934bd8d2020-07-16 18:10:48 -0700779
Joe Onorato8ade9b22020-07-20 23:19:43 -0700780 modules = [module for installed, module in self.soong.installed.items()]
781 self.print_analysis_row(overall_summary, modules,
782 "All Makefiles", "TotalRow",
783 set(itertools.chain.from_iterable(overall_summary.directories.values())))
784 print("""
785 </table>
786 """)
Joe Onorato02fb89a2020-06-27 00:10:23 -0700787
Joe Onorato8ade9b22020-07-20 23:19:43 -0700788 print("""
789 <script type="text/javascript">
790 function close_details() {
791 document.getElementById('details').style.display = 'none';
Joe Onorato02fb89a2020-06-27 00:10:23 -0700792 }
793
Joe Onorato8ade9b22020-07-20 23:19:43 -0700794 class LineMatch {
795 constructor(lineno, text) {
796 this.lineno = lineno;
797 this.text = text;
798 }
799 }
Joe Onorato02fb89a2020-06-27 00:10:23 -0700800
Joe Onorato8ade9b22020-07-20 23:19:43 -0700801 class Analysis {
802 constructor(filename, modules, line_matches) {
803 this.filename = filename;
804 this.modules = modules;
805 this.line_matches = line_matches;
806 }
807 }
Joe Onorato02fb89a2020-06-27 00:10:23 -0700808
Joe Onorato8ade9b22020-07-20 23:19:43 -0700809 class Module {
810 constructor(deps) {
811 this.deps = deps;
812 }
813 }
Joe Onorato02fb89a2020-06-27 00:10:23 -0700814
Joe Onorato8ade9b22020-07-20 23:19:43 -0700815 function make_module_link(module) {
816 var a = document.createElement('a');
817 a.className = 'ModuleLink';
818 a.innerText = module;
819 a.href = '#module_' + module;
820 return a;
821 }
Joe Onorato02fb89a2020-06-27 00:10:23 -0700822
Joe Onorato8ade9b22020-07-20 23:19:43 -0700823 function update_details(id) {
824 document.getElementById('details').style.display = 'block';
Joe Onorato02fb89a2020-06-27 00:10:23 -0700825
Joe Onorato8ade9b22020-07-20 23:19:43 -0700826 var analyses = ANALYSIS[id];
Joe Onorato02fb89a2020-06-27 00:10:23 -0700827
Joe Onorato8ade9b22020-07-20 23:19:43 -0700828 var details = document.getElementById("details_data");
829 while (details.firstChild) {
830 details.removeChild(details.firstChild);
831 }
Joe Onorato02fb89a2020-06-27 00:10:23 -0700832
Joe Onorato8ade9b22020-07-20 23:19:43 -0700833 for (var i=0; i<analyses.length; i++) {
834 var analysis = analyses[i];
Joe Onorato02fb89a2020-06-27 00:10:23 -0700835
Joe Onorato8ade9b22020-07-20 23:19:43 -0700836 var makefileDiv = document.createElement('div');
837 makefileDiv.className = 'Makefile';
838 details.appendChild(makefileDiv);
Joe Onorato02fb89a2020-06-27 00:10:23 -0700839
Joe Onorato8ade9b22020-07-20 23:19:43 -0700840 var fileA = document.createElement('a');
841 makefileDiv.appendChild(fileA);
842 fileA.className = 'CsLink';
843 fileA.href = '%(codesearch)s' + analysis.filename;
844 fileA.innerText = analysis.filename;
845 fileA.target = "_blank";
846
847 if (analysis.modules.length > 0) {
848 var moduleTable = document.createElement('table');
849 details.appendChild(moduleTable);
850
851 for (var j=0; j<analysis.modules.length; j++) {
852 var moduleRow = document.createElement('tr');
853 moduleTable.appendChild(moduleRow);
854
855 var moduleNameCell = document.createElement('td');
856 moduleRow.appendChild(moduleNameCell);
857 moduleNameCell.className = 'ModuleName';
858 moduleNameCell.appendChild(make_module_link(analysis.modules[j]));
859
860 var moduleData = MODULE_DATA[analysis.modules[j]];
861 console.log(moduleData);
862
863 var depCell = document.createElement('td');
864 moduleRow.appendChild(depCell);
865
866 if (moduleData.deps.length == 0) {
867 depCell.className = 'ModuleDeps Unblocked';
868 depCell.innerText = 'UNBLOCKED';
869 } else {
870 depCell.className = 'ModuleDeps Blocked';
871
872 for (var k=0; k<moduleData.deps.length; k++) {
873 depCell.appendChild(make_module_link(moduleData.deps[k]));
874 depCell.appendChild(document.createElement('br'));
875 }
Joe Onorato02fb89a2020-06-27 00:10:23 -0700876 }
877 }
878 }
Joe Onorato02fb89a2020-06-27 00:10:23 -0700879
Joe Onorato8ade9b22020-07-20 23:19:43 -0700880 if (analysis.line_matches.length > 0) {
881 var lineTable = document.createElement('table');
882 details.appendChild(lineTable);
Joe Onorato02fb89a2020-06-27 00:10:23 -0700883
Joe Onorato8ade9b22020-07-20 23:19:43 -0700884 for (var j=0; j<analysis.line_matches.length; j++) {
885 var line_match = analysis.line_matches[j];
Joe Onorato02fb89a2020-06-27 00:10:23 -0700886
Joe Onorato8ade9b22020-07-20 23:19:43 -0700887 var lineRow = document.createElement('tr');
888 lineTable.appendChild(lineRow);
Joe Onorato02fb89a2020-06-27 00:10:23 -0700889
Joe Onorato8ade9b22020-07-20 23:19:43 -0700890 var linenoCell = document.createElement('td');
891 lineRow.appendChild(linenoCell);
892 linenoCell.className = 'LineNo';
Joe Onorato02fb89a2020-06-27 00:10:23 -0700893
Joe Onorato8ade9b22020-07-20 23:19:43 -0700894 var linenoA = document.createElement('a');
895 linenoCell.appendChild(linenoA);
896 linenoA.className = 'CsLink';
897 linenoA.href = '%(codesearch)s' + analysis.filename
898 + ';l=' + line_match.lineno;
899 linenoA.innerText = line_match.lineno;
900 linenoA.target = "_blank";
Joe Onorato02fb89a2020-06-27 00:10:23 -0700901
Joe Onorato8ade9b22020-07-20 23:19:43 -0700902 var textCell = document.createElement('td');
903 lineRow.appendChild(textCell);
904 textCell.className = 'LineText';
905 textCell.innerText = line_match.text;
906 }
Joe Onorato02fb89a2020-06-27 00:10:23 -0700907 }
908 }
909 }
Joe Onorato02fb89a2020-06-27 00:10:23 -0700910
Joe Onorato8ade9b22020-07-20 23:19:43 -0700911 var ANALYSIS = [
912 """ % {
913 "codesearch": self.args.codesearch,
Joe Onorato02fb89a2020-06-27 00:10:23 -0700914 })
Joe Onorato8ade9b22020-07-20 23:19:43 -0700915 for entry, mods in self.annotations.entries:
916 print(" [")
917 for analysis in entry:
918 print(" new Analysis('%(filename)s', %(modules)s, [%(line_matches)s])," % {
919 "filename": analysis.filename,
920 #"modules": json.dumps([m for m in mods if m in filename in self.soong.makefiles[m]]),
921 "modules": json.dumps(
922 [m for m in self.soong.reverse_makefiles[analysis.filename] if m in mods]),
923 "line_matches": ", ".join([
924 "new LineMatch(%d, %s)" % (lineno, json.dumps(text))
925 for lineno, text in analysis.line_matches]),
926 })
927 print(" ],")
928 print("""
929 ];
930 var MODULE_DATA = {
931 """)
932 for module in self.soong.modules:
933 print(" '%(name)s': new Module(%(deps)s)," % {
934 "name": module,
935 "deps": json.dumps(self.soong.deps[module]),
936 })
937 print("""
938 };
939 </script>
Joe Onorato02fb89a2020-06-27 00:10:23 -0700940
Joe Onorato8ade9b22020-07-20 23:19:43 -0700941 """)
Joe Onorato02fb89a2020-06-27 00:10:23 -0700942
Joe Onorato8ade9b22020-07-20 23:19:43 -0700943 print("""
944 </div> <!-- id=tables -->
945 <div id="details">
946 <div style="text-align: right;">
947 <a href="javascript:close_details();">
948 <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
949 </a>
950 </div>
951 <div id="details_data"></div>
Joe Onorato02fb89a2020-06-27 00:10:23 -0700952 </div>
Joe Onorato8ade9b22020-07-20 23:19:43 -0700953 </body>
954 </html>
955 """)
956
957 def traverse_ready_makefiles(self, summary, makefiles):
Joe Onorato8ade9b22020-07-20 23:19:43 -0700958 return [Analysis(makefile.filename, []) for makefile in makefiles
Joe Onorato31986072020-08-10 09:58:49 -0700959 if clean_and_only_blocked_by_clean(self.soong, self.all_makefiles, makefile)]
Joe Onorato8ade9b22020-07-20 23:19:43 -0700960
961 def print_analysis_row(self, summary, modules, rowtitle, rowclass, makefiles):
962 all_makefiles = [Analysis(makefile.filename, []) for makefile in makefiles]
963 clean_makefiles = [Analysis(makefile.filename, []) for makefile in makefiles
964 if is_clean(makefile)]
965 easy_makefiles = self.traverse_ready_makefiles(summary, makefiles)
966 unblocked_clean_makefiles = [Analysis(makefile.filename, []) for makefile in makefiles
967 if (self.soong.contains_unblocked_modules(makefile.filename)
968 and is_clean(makefile))]
969 unblocked_makefiles = [Analysis(makefile.filename, []) for makefile in makefiles
970 if self.soong.contains_unblocked_modules(makefile.filename)]
971 blocked_makefiles = [Analysis(makefile.filename, []) for makefile in makefiles
972 if self.soong.contains_blocked_modules(makefile.filename)]
973
974 print("""
975 <tr class="%(rowclass)s">
976 <td class="RowTitle">%(rowtitle)s</td>
977 <td class="Count">%(makefiles)s</td>
978 <td class="Count">%(easy)s</td>
979 <td class="Count">%(unblocked_clean)s</td>
980 <td class="Count">%(unblocked)s</td>
981 <td class="Count">%(blocked)s</td>
982 <td class="Count">%(clean)s</td>
983 """ % {
984 "rowclass": rowclass,
985 "rowtitle": rowtitle,
986 "makefiles": self.make_annotation_link(all_makefiles, modules),
987 "unblocked": self.make_annotation_link(unblocked_makefiles, modules),
988 "blocked": self.make_annotation_link(blocked_makefiles, modules),
989 "clean": self.make_annotation_link(clean_makefiles, modules),
990 "unblocked_clean": self.make_annotation_link(unblocked_clean_makefiles, modules),
991 "easy": self.make_annotation_link(easy_makefiles, modules),
992 })
993
994 for analyzer in ANALYZERS:
995 analyses = [m.analyses.get(analyzer) for m in makefiles if m.analyses.get(analyzer)]
996 print("""<td class="Count">%s</td>"""
997 % self.make_annotation_link(analyses, modules))
998
999 print(" </tr>")
1000
1001 def make_annotation_link(self, analysis, modules):
1002 if analysis:
1003 return "<a href='javascript:update_details(%d)'>%s</a>" % (
1004 self.annotations.Add(analysis, modules),
1005 len(analysis)
1006 )
1007 else:
1008 return "";
Joe Onorato02fb89a2020-06-27 00:10:23 -07001009
Joe Onorato31986072020-08-10 09:58:49 -07001010class CsvProcessor(object):
Yuntao Xuc21dc3c2022-03-17 18:39:36 -07001011 def __init__(self, args, soong, all_makefiles, product_packages_modules):
Joe Onorato31986072020-08-10 09:58:49 -07001012 self.args = args
1013 self.soong = soong
1014 self.all_makefiles = all_makefiles
Yuntao Xuc21dc3c2022-03-17 18:39:36 -07001015 self.product_packages_modules = product_packages_modules
Joe Onorato31986072020-08-10 09:58:49 -07001016
1017 def execute(self):
1018 csvout = csv.writer(sys.stdout)
1019
1020 # Title row
1021 row = ["Filename", "Module", "Partitions", "Easy", "Unblocked Clean", "Unblocked",
1022 "Blocked", "Clean"]
1023 for analyzer in ANALYZERS:
1024 row.append(analyzer.title)
1025 csvout.writerow(row)
1026
1027 # Makefile & module data
1028 for filename in sorted(self.all_makefiles.keys()):
1029 makefile = self.all_makefiles[filename]
1030 for module in self.soong.reverse_makefiles[filename]:
Yuntao Xuc21dc3c2022-03-17 18:39:36 -07001031 if len(self.product_packages_modules) > 0 and module not in self.product_packages_modules:
1032 continue
Joe Onorato31986072020-08-10 09:58:49 -07001033 row = [filename, module]
1034 # Partitions
1035 row.append(";".join(sorted(set([get_partition_from_installed(HOST_OUT_ROOT, PRODUCT_OUT,
1036 installed)
1037 for installed
1038 in self.soong.reverse_installed.get(module, [])]))))
1039 # Easy
1040 row.append(1
1041 if clean_and_only_blocked_by_clean(self.soong, self.all_makefiles, makefile)
1042 else "")
1043 # Unblocked Clean
1044 row.append(1
1045 if (self.soong.contains_unblocked_modules(makefile.filename) and is_clean(makefile))
1046 else "")
1047 # Unblocked
1048 row.append(1 if self.soong.contains_unblocked_modules(makefile.filename) else "")
1049 # Blocked
1050 row.append(1 if self.soong.contains_blocked_modules(makefile.filename) else "")
1051 # Clean
1052 row.append(1 if is_clean(makefile) else "")
1053 # Analysis
1054 for analyzer in ANALYZERS:
1055 row.append(1 if makefile.analyses.get(analyzer) else "")
1056 # Write results
1057 csvout.writerow(row)
1058
Joe Onorato02fb89a2020-06-27 00:10:23 -07001059if __name__ == "__main__":
1060 main()
1061