blob: e85ae18faa750445fef29796c2ac392eb0b06f43 [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
Joe Onorato934bd8d2020-07-16 18:10:48 -0700122 def IsClean(self, filename):
123 """Also returns true if filename isn't present, with the assumption that it's already
124 converted.
125 """
126 makefile = self.makefiles.get(filename)
127 if not makefile:
128 return True
129 return is_clean(makefile)
130
Joe Onorato02fb89a2020-06-27 00:10:23 -0700131class Makefile(object):
132 def __init__(self, filename):
133 self.filename = filename
134
135 # Analyze the file
136 with open(filename, "r", errors="ignore") as f:
137 try:
138 lines = f.readlines()
139 except UnicodeDecodeError as ex:
140 sys.stderr.write("Filename: %s\n" % filename)
141 raise ex
142 lines = [line.strip() for line in lines]
143
144 self.analyses = dict([(analyzer, analyze_lines(filename, lines, analyzer.func)) for analyzer
145 in ANALYZERS])
146
147def find_android_mk():
148 cwd = os.getcwd()
149 for root, dirs, files in os.walk(cwd):
150 for filename in files:
151 if filename == "Android.mk":
152 yield os.path.join(root, filename)[len(cwd) + 1:]
153 for ignore in (".git", ".repo"):
154 if ignore in dirs:
155 dirs.remove(ignore)
156
157def is_aosp(dirname):
158 for d in ("device/sample", "hardware/interfaces", "hardware/libhardware",
159 "hardware/ril"):
160 if dirname.startswith(d):
161 return True
162 for d in ("device/", "hardware/", "vendor/"):
163 if dirname.startswith(d):
164 return False
165 return True
166
167def is_google(dirname):
168 for d in ("device/google",
169 "hardware/google",
170 "test/sts",
171 "vendor/auto",
172 "vendor/google",
173 "vendor/unbundled_google",
174 "vendor/widevine",
175 "vendor/xts"):
176 if dirname.startswith(d):
177 return True
178 return False
179
180def make_annotation_link(annotations, analysis, modules):
181 if analysis:
182 return "<a href='javascript:update_details(%d)'>%s</a>" % (
183 annotations.Add(analysis, modules),
184 len(analysis)
185 )
186 else:
187 return "";
188
189
190def is_clean(makefile):
191 for analysis in makefile.analyses.values():
192 if analysis:
193 return False
194 return True
195
196class Annotations(object):
197 def __init__(self):
198 self.entries = []
199 self.count = 0
200
201 def Add(self, makefiles, modules):
202 self.entries.append((makefiles, modules))
203 self.count += 1
204 return self.count-1
205
206class SoongData(object):
207 def __init__(self, reader):
208 """Read the input file and store the modules and dependency mappings.
209 """
210 self.problems = dict()
211 self.deps = dict()
212 self.reverse_deps = dict()
213 self.module_types = dict()
214 self.makefiles = dict()
215 self.reverse_makefiles = dict()
216 self.installed = dict()
217 self.modules = set()
218
219 for (module, module_type, problem, dependencies, makefiles, installed) in reader:
220 self.modules.add(module)
221 makefiles = [f for f in makefiles.strip().split(' ') if f != ""]
222 self.module_types[module] = module_type
223 self.problems[module] = problem
224 self.deps[module] = [d for d in dependencies.strip().split(' ') if d != ""]
225 for dep in self.deps[module]:
226 if not dep in self.reverse_deps:
227 self.reverse_deps[dep] = []
228 self.reverse_deps[dep].append(module)
229 self.makefiles[module] = makefiles
230 for f in makefiles:
231 self.reverse_makefiles.setdefault(f, []).append(module)
232 for f in installed.strip().split(' '):
233 self.installed[f] = module
234
Joe Onorato934bd8d2020-07-16 18:10:48 -0700235 def transitive_deps(self, module):
236 results = set()
237 def traverse(module):
238 for dep in self.deps.get(module, []):
239 if not dep in results:
240 results.add(dep)
241 traverse(module)
242 traverse(module)
243 return results
244
Joe Onorato02fb89a2020-06-27 00:10:23 -0700245def count_deps(depsdb, module, seen):
246 """Based on the depsdb, count the number of transitive dependencies.
247
248 You can pass in an reversed dependency graph to count the number of
249 modules that depend on the module."""
250 count = 0
251 seen.append(module)
252 if module in depsdb:
253 for dep in depsdb[module]:
254 if dep in seen:
255 continue
256 count += 1 + count_deps(depsdb, dep, seen)
257 return count
258
259def contains_unblocked_modules(soong, modules):
260 for m in modules:
261 if len(soong.deps[m]) == 0:
262 return True
263 return False
264
265def contains_blocked_modules(soong, modules):
266 for m in modules:
267 if len(soong.deps[m]) > 0:
268 return True
269 return False
270
271OTHER_PARTITON = "_other"
272HOST_PARTITON = "_host"
273
274def get_partition_from_installed(HOST_OUT_ROOT, PRODUCT_OUT, filename):
275 host_prefix = HOST_OUT_ROOT + "/"
276 device_prefix = PRODUCT_OUT + "/"
277
278 if filename.startswith(host_prefix):
279 return HOST_PARTITON
280
281 elif filename.startswith(device_prefix):
282 index = filename.find("/", len(device_prefix))
283 if index < 0:
284 return OTHER_PARTITON
285 return filename[len(device_prefix):index]
286
287 return OTHER_PARTITON
288
289def format_module_link(module):
290 return "<a class='ModuleLink' href='#module_%s'>%s</a>" % (module, module)
291
292def format_module_list(modules):
293 return "".join(["<div>%s</div>" % format_module_link(m) for m in modules])
294
Joe Onorato934bd8d2020-07-16 18:10:48 -0700295def traverse_ready_makefiles(soong, summary, makefiles):
296 def clean_and_only_blocked_by_clean(makefile):
297 if not is_clean(makefile):
298 return False
299 modules = soong.reverse_makefiles[makefile.filename]
300 for module in modules:
301 for dep in soong.transitive_deps(module):
302 for m in soong.makefiles.get(dep, []):
303 if not summary.IsClean(m):
304 return False
305 return True
306
307 return [Analysis(makefile.filename, []) for makefile in makefiles
308 if clean_and_only_blocked_by_clean(makefile)]
309
310def print_analysis_header(link, title):
311 print("""
312 <a name="%(link)s"></a>
313 <h2>%(title)s</h2>
314 <table>
315 <tr>
316 <th class="RowTitle">Directory</th>
317 <th class="Count">Total</th>
318 <th class="Count Clean">Easy</th>
319 <th class="Count Clean">Unblocked Clean</th>
320 <th class="Count Unblocked">Unblocked</th>
321 <th class="Count Blocked">Blocked</th>
322 <th class="Count Clean">Clean</th>
323 """ % {
324 "link": link,
325 "title": title
326 })
327 for analyzer in ANALYZERS:
328 print("""<th class="Count Warning">%s</th>""" % analyzer.title)
329 print(" </tr>")
330
331
332def print_analysis_row(soong, summary, annotations, modules, rowtitle, rowclass, makefiles):
333 all_makefiles = [Analysis(makefile.filename, []) for makefile in makefiles]
334 clean_makefiles = [Analysis(makefile.filename, []) for makefile in makefiles
335 if is_clean(makefile)]
336 easy_makefiles = traverse_ready_makefiles(soong, summary, makefiles)
337 unblocked_clean_makefiles = [Analysis(makefile.filename, []) for makefile in makefiles
338 if (contains_unblocked_modules(soong, soong.reverse_makefiles[makefile.filename])
339 and is_clean(makefile))]
340 unblocked_makefiles = [Analysis(makefile.filename, []) for makefile in makefiles
341 if contains_unblocked_modules(soong,
342 soong.reverse_makefiles[makefile.filename])]
343 blocked_makefiles = [Analysis(makefile.filename, []) for makefile in makefiles
344 if contains_blocked_modules(soong, soong.reverse_makefiles[makefile.filename])]
345
346 print("""
347 <tr class="%(rowclass)s">
348 <td class="RowTitle">%(rowtitle)s</td>
349 <td class="Count">%(makefiles)s</td>
350 <td class="Count">%(easy)s</td>
351 <td class="Count">%(unblocked_clean)s</td>
352 <td class="Count">%(unblocked)s</td>
353 <td class="Count">%(blocked)s</td>
354 <td class="Count">%(clean)s</td>
355 """ % {
356 "rowclass": rowclass,
357 "rowtitle": rowtitle,
358 "makefiles": make_annotation_link(annotations, all_makefiles, modules),
359 "unblocked": make_annotation_link(annotations, unblocked_makefiles, modules),
360 "blocked": make_annotation_link(annotations, blocked_makefiles, modules),
361 "clean": make_annotation_link(annotations, clean_makefiles, modules),
362 "unblocked_clean": make_annotation_link(annotations, unblocked_clean_makefiles, modules),
363 "easy": make_annotation_link(annotations, easy_makefiles, modules),
364 })
365
366 for analyzer in ANALYZERS:
367 analyses = [m.analyses.get(analyzer) for m in makefiles if m.analyses.get(analyzer)]
368 print("""<td class="Count">%s</td>"""
369 % make_annotation_link(annotations, analyses, modules))
370
371 print(" </tr>")
372
Joe Onorato02fb89a2020-06-27 00:10:23 -0700373def main():
374 parser = argparse.ArgumentParser(description="Info about remaining Android.mk files.")
375 parser.add_argument("--device", type=str, required=True,
376 help="TARGET_DEVICE")
377 parser.add_argument("--title", type=str,
378 help="page title")
379 parser.add_argument("--codesearch", type=str,
380 default="https://cs.android.com/android/platform/superproject/+/master:",
381 help="page title")
382 parser.add_argument("--out_dir", type=str,
383 default=None,
384 help="Equivalent of $OUT_DIR, which will also be checked if"
385 + " --out_dir is unset. If neither is set, default is"
386 + " 'out'.")
387
388 args = parser.parse_args()
389
390 # Guess out directory name
391 if not args.out_dir:
392 args.out_dir = os.getenv("OUT_DIR", "out")
393 while args.out_dir.endswith("/") and len(args.out_dir) > 1:
394 args.out_dir = args.out_dir[:-1]
395
396 TARGET_DEVICE = args.device
Joe Onorato934bd8d2020-07-16 18:10:48 -0700397 HOST_OUT_ROOT = args.out_dir + "/host"
Joe Onorato02fb89a2020-06-27 00:10:23 -0700398 PRODUCT_OUT = args.out_dir + "/target/product/%s" % TARGET_DEVICE
399
400 if args.title:
401 page_title = args.title
402 else:
403 page_title = "Remaining Android.mk files"
404
405 # Read target information
406 # TODO: Pull from configurable location. This is also slightly different because it's
407 # only a single build, where as the tree scanning we do below is all Android.mk files.
408 with open("%s/obj/PACKAGING/soong_conversion_intermediates/soong_conv_data"
409 % PRODUCT_OUT, "r", errors="ignore") as csvfile:
410 soong = SoongData(csv.reader(csvfile))
411
412 # Which modules are installed where
413 modules_by_partition = dict()
414 partitions = set()
415 for installed, module in soong.installed.items():
416 partition = get_partition_from_installed(HOST_OUT_ROOT, PRODUCT_OUT, installed)
417 modules_by_partition.setdefault(partition, []).append(module)
418 partitions.add(partition)
419
420 print("""
421 <html>
422 <head>
423 <title>%(page_title)s</title>
424 <style type="text/css">
425 body, table {
426 font-family: Roboto, sans-serif;
427 font-size: 9pt;
428 }
429 body {
430 margin: 0;
431 padding: 0;
432 display: flex;
433 flex-direction: column;
434 height: 100vh;
435 }
436 #container {
437 flex: 1;
438 display: flex;
439 flex-direction: row;
440 overflow: hidden;
441 }
442 #tables {
Joe Onorato934bd8d2020-07-16 18:10:48 -0700443 padding: 0 20px 40px 20px;
Joe Onorato02fb89a2020-06-27 00:10:23 -0700444 overflow: scroll;
445 flex: 2 2 600px;
446 }
447 #details {
448 display: none;
449 overflow: scroll;
450 flex: 1 1 650px;
451 padding: 0 20px 0 20px;
452 }
453 h1 {
454 margin: 16px 0 16px 20px;
455 }
456 h2 {
457 margin: 12px 0 4px 0;
458 }
Joe Onorato934bd8d2020-07-16 18:10:48 -0700459 .RowTitle {
Joe Onorato02fb89a2020-06-27 00:10:23 -0700460 text-align: left;
461 width: 200px;
462 min-width: 200px;
463 }
464 .Count {
465 text-align: center;
466 width: 60px;
467 min-width: 60px;
468 max-width: 60px;
469 }
470 th.Clean,
471 th.Unblocked {
472 background-color: #1e8e3e;
473 }
474 th.Blocked {
475 background-color: #d93025;
476 }
477 th.Warning {
478 background-color: #e8710a;
479 }
480 th {
481 background-color: #1a73e8;
482 color: white;
483 font-weight: bold;
484 }
485 td.Unblocked {
486 background-color: #81c995;
487 }
488 td.Blocked {
489 background-color: #f28b82;
490 }
491 td, th {
492 padding: 2px 4px;
493 border-right: 2px solid white;
494 }
Joe Onorato934bd8d2020-07-16 18:10:48 -0700495 tr.TotalRow td {
496 background-color: white;
497 border-right-color: white;
498 }
Joe Onorato02fb89a2020-06-27 00:10:23 -0700499 tr.AospDir td {
500 background-color: #e6f4ea;
501 border-right-color: #e6f4ea;
502 }
503 tr.GoogleDir td {
504 background-color: #e8f0fe;
505 border-right-color: #e8f0fe;
506 }
507 tr.PartnerDir td {
508 background-color: #fce8e6;
509 border-right-color: #fce8e6;
510 }
511 table {
512 border-spacing: 0;
513 border-collapse: collapse;
514 }
515 div.Makefile {
516 margin: 12px 0 0 0;
517 }
518 div.Makefile:first {
519 margin-top: 0;
520 }
521 div.FileModules {
522 padding: 4px 0 0 20px;
523 }
524 td.LineNo {
525 vertical-align: baseline;
526 padding: 6px 0 0 20px;
527 width: 50px;
528 vertical-align: baseline;
529 }
530 td.LineText {
531 vertical-align: baseline;
532 font-family: monospace;
533 padding: 6px 0 0 0;
534 }
535 a.CsLink {
536 font-family: monospace;
537 }
538 div.Help {
539 width: 550px;
540 }
541 table.HelpColumns tr {
542 border-bottom: 2px solid white;
543 }
544 .ModuleName {
545 vertical-align: baseline;
546 padding: 6px 0 0 20px;
547 width: 275px;
548 }
549 .ModuleDeps {
550 vertical-align: baseline;
551 padding: 6px 0 0 0;
552 }
553 table#Modules td {
554 vertical-align: baseline;
555 }
556 tr.Alt {
557 background-color: #ececec;
558 }
559 tr.Alt td {
560 border-right-color: #ececec;
561 }
562 .AnalysisCol {
563 width: 300px;
564 padding: 2px;
565 line-height: 21px;
566 }
567 .Analysis {
568 color: white;
569 font-weight: bold;
570 background-color: #e8710a;
571 border-radius: 6px;
572 margin: 4px;
573 padding: 2px 6px;
574 white-space: nowrap;
575 }
576 .Nav {
577 margin: 4px 0 16px 20px;
578 }
579 .NavSpacer {
580 display: inline-block;
581 width: 6px;
582 }
583 .ModuleDetails {
584 margin-top: 20px;
585 }
586 .ModuleDetails td {
587 vertical-align: baseline;
588 }
589 </style>
590 </head>
591 <body>
592 <h1>%(page_title)s</h1>
593 <div class="Nav">
594 <a href='#help'>Help</a>
595 <span class='NavSpacer'></span><span class='NavSpacer'> </span>
596 Partitions:
597 """ % {
598 "page_title": page_title,
599 })
600 for partition in sorted(partitions):
601 print("<a href='#partition_%s'>%s</a><span class='NavSpacer'></span>" % (partition, partition))
602
603 print("""
604 <span class='NavSpacer'></span><span class='NavSpacer'> </span>
Joe Onorato934bd8d2020-07-16 18:10:48 -0700605 <a href='#summary'>Overall Summary</a>
Joe Onorato02fb89a2020-06-27 00:10:23 -0700606 </div>
607 <div id="container">
608 <div id="tables">
609 <a name="help"></a>
610 <div class="Help">
611 <p>
612 This page analyzes the remaining Android.mk files in the Android Source tree.
613 <p>
614 The modules are first broken down by which of the device filesystem partitions
615 they are installed to. This also includes host tools and testcases which don't
616 actually reside in their own partition but convenitely group together.
617 <p>
618 The makefiles for each partition are further are grouped into a set of directories
619 aritrarily picked to break down the problem size by owners.
620 <ul style="width: 300px">
621 <li style="background-color: #e6f4ea">AOSP directories are colored green.</li>
622 <li style="background-color: #e8f0fe">Google directories are colored blue.</li>
623 <li style="background-color: #fce8e6">Other partner directories are colored red.</li>
624 </ul>
625 Each of the makefiles are scanned for issues that are likely to come up during
626 conversion to soong. Clicking the number in each cell shows additional information,
627 including the line that triggered the warning.
628 <p>
629 <table class="HelpColumns">
630 <tr>
631 <th>Total</th>
632 <td>The total number of makefiles in this each directory.</td>
633 </tr>
634 <tr>
Joe Onorato934bd8d2020-07-16 18:10:48 -0700635 <th class="Clean">Easy</th>
636 <td>The number of makefiles that have no warnings themselves, and also
637 none of their dependencies have warnings either.</td>
638 </tr>
639 <tr>
640 <th class="Clean">Unblocked Clean</th>
641 <td>The number of makefiles that are both Unblocked and Clean.</td>
642 </tr>
643
644 <tr>
Joe Onorato02fb89a2020-06-27 00:10:23 -0700645 <th class="Unblocked">Unblocked</th>
646 <td>Makefiles containing one or more modules that don't have any
647 additional dependencies pending before conversion.</td>
648 </tr>
649 <tr>
650 <th class="Blocked">Blocked</th>
651 <td>Makefiles containiong one or more modules which <i>do</i> have
652 additional prerequesite depenedencies that are not yet converted.</td>
653 </tr>
654 <tr>
655 <th class="Clean">Clean</th>
656 <td>The number of makefiles that have none of the following warnings.</td>
657 </tr>
658 <tr>
659 <th class="Warning">ifeq / ifneq</th>
660 <td>Makefiles that use <code>ifeq</code> or <code>ifneq</code>. i.e.
661 conditionals.</td>
662 </tr>
663 <tr>
664 <th class="Warning">Wacky Includes</th>
665 <td>Makefiles that <code>include</code> files other than the standard build-system
666 defined template and macros.</td>
667 </tr>
668 <tr>
669 <th class="Warning">Calls base_rules</th>
670 <td>Makefiles that include base_rules.mk directly.</td>
671 </tr>
672 <tr>
673 <th class="Warning">Calls define</th>
674 <td>Makefiles that define their own macros. Some of these are easy to convert
675 to soong <code>defaults</code>, but others are complex.</td>
676 </tr>
677 <tr>
678 <th class="Warning">Has ../</th>
679 <td>Makefiles containing the string "../" outside of a comment. These likely
680 access files outside their directories.</td>
681 </tr>
682 <tr>
683 <th class="Warning">dist-for-goals</th>
684 <td>Makefiles that call <code>dist-for-goals</code> directly.</td>
685 </tr>
686 <tr>
687 <th class="Warning">.PHONY</th>
688 <td>Makefiles that declare .PHONY targets.</td>
689 </tr>
690 <tr>
691 <th class="Warning">renderscript</th>
692 <td>Makefiles defining targets that depend on <code>.rscript</code> source files.</td>
693 </tr>
694 <tr>
695 <th class="Warning">vts src</th>
696 <td>Makefiles defining targets that depend on <code>.vts</code> source files.</td>
697 </tr>
698 <tr>
699 <th class="Warning">COPY_HEADERS</th>
700 <td>Makefiles using LOCAL_COPY_HEADERS.</td>
701 </tr>
702 </table>
703 <p>
704 Following the list of directories is a list of the modules that are installed on
705 each partition. Potential issues from their makefiles are listed, as well as the
706 total number of dependencies (both blocking that module and blocked by that module)
707 and the list of direct dependencies. Note: The number is the number of all transitive
708 dependencies and the list of modules is only the direct dependencies.
709 </div>
710 """)
711
712 annotations = Annotations()
Joe Onorato934bd8d2020-07-16 18:10:48 -0700713 overall_summary = Summary()
Joe Onorato02fb89a2020-06-27 00:10:23 -0700714
715 # For each partition
716 makefiles_for_partitions = dict()
717 for partition in sorted(partitions):
718 modules = modules_by_partition[partition]
719
720 makefiles = set(itertools.chain.from_iterable(
721 [soong.makefiles[module] for module in modules]))
722
723 # Read makefiles
724 summary = Summary()
725 for filename in makefiles:
726 if not filename.startswith(args.out_dir + "/"):
727 summary.Add(Makefile(filename))
Joe Onorato934bd8d2020-07-16 18:10:48 -0700728 overall_summary.Add(Makefile(filename))
Joe Onorato02fb89a2020-06-27 00:10:23 -0700729
730 # Categorize directories by who is responsible
731 aosp_dirs = []
732 google_dirs = []
733 partner_dirs = []
734 for dirname in sorted(summary.directories.keys()):
735 if is_aosp(dirname):
736 aosp_dirs.append(dirname)
737 elif is_google(dirname):
738 google_dirs.append(dirname)
739 else:
740 partner_dirs.append(dirname)
741
Joe Onorato934bd8d2020-07-16 18:10:48 -0700742 print_analysis_header("partition_" + partition, partition)
Joe Onorato02fb89a2020-06-27 00:10:23 -0700743
Joe Onorato02fb89a2020-06-27 00:10:23 -0700744 for dirgroup, rowclass in [(aosp_dirs, "AospDir"),
745 (google_dirs, "GoogleDir"),
746 (partner_dirs, "PartnerDir"),]:
747 for dirname in dirgroup:
Joe Onorato934bd8d2020-07-16 18:10:48 -0700748 print_analysis_row(soong, summary, annotations, modules,
749 dirname, rowclass, summary.directories[dirname])
Joe Onorato02fb89a2020-06-27 00:10:23 -0700750
Joe Onorato934bd8d2020-07-16 18:10:48 -0700751 print_analysis_row(soong, summary, annotations, modules,
752 "Total", "TotalRow",
753 set(itertools.chain.from_iterable(summary.directories.values())))
Joe Onorato02fb89a2020-06-27 00:10:23 -0700754 print("""
755 </table>
756 """)
757
758 module_details = [(count_deps(soong.deps, m, []), -count_deps(soong.reverse_deps, m, []), m)
759 for m in modules]
760 module_details.sort()
761 module_details = [m[2] for m in module_details]
762 print("""
763 <table class="ModuleDetails">""")
764 print("<tr>")
765 print(" <th>Module Name</th>")
766 print(" <th>Issues</th>")
767 print(" <th colspan='2'>Blocked By</th>")
768 print(" <th colspan='2'>Blocking</th>")
769 print("</tr>")
770 altRow = True
771 for module in module_details:
772 analyses = set()
773 for filename in soong.makefiles[module]:
774 makefile = summary.makefiles.get(filename)
775 if makefile:
776 for analyzer, analysis in makefile.analyses.items():
777 if analysis:
778 analyses.add(analyzer.title)
779
780 altRow = not altRow
781 print("<tr class='%s'>" % ("Alt" if altRow else "",))
782 print(" <td><a name='module_%s'></a>%s</td>" % (module, module))
783 print(" <td class='AnalysisCol'>%s</td>" % " ".join(["<span class='Analysis'>%s</span>" % title
784 for title in analyses]))
785 print(" <td>%s</td>" % count_deps(soong.deps, module, []))
786 print(" <td>%s</td>" % format_module_list(soong.deps.get(module, [])))
787 print(" <td>%s</td>" % count_deps(soong.reverse_deps, module, []))
788 print(" <td>%s</td>" % format_module_list(soong.reverse_deps.get(module, [])))
789 print("</tr>")
790 print("""</table>""")
791
Joe Onorato934bd8d2020-07-16 18:10:48 -0700792 print_analysis_header("summary", "Overall Summary")
793
794 modules = [module for installed, module in soong.installed.items()]
795 print_analysis_row(soong, overall_summary, annotations, modules,
796 "All Makefiles", "TotalRow",
797 set(itertools.chain.from_iterable(overall_summary.directories.values())))
798 print("""
799 </table>
800 """)
801
Joe Onorato02fb89a2020-06-27 00:10:23 -0700802 print("""
803 <script type="text/javascript">
804 function close_details() {
805 document.getElementById('details').style.display = 'none';
806 }
807
808 class LineMatch {
809 constructor(lineno, text) {
810 this.lineno = lineno;
811 this.text = text;
812 }
813 }
814
815 class Analysis {
816 constructor(filename, modules, line_matches) {
817 this.filename = filename;
818 this.modules = modules;
819 this.line_matches = line_matches;
820 }
821 }
822
823 class Module {
824 constructor(deps) {
825 this.deps = deps;
826 }
827 }
828
829 function make_module_link(module) {
830 var a = document.createElement('a');
831 a.className = 'ModuleLink';
832 a.innerText = module;
833 a.href = '#module_' + module;
834 return a;
835 }
836
837 function update_details(id) {
838 document.getElementById('details').style.display = 'block';
839
840 var analyses = ANALYSIS[id];
841
842 var details = document.getElementById("details_data");
843 while (details.firstChild) {
844 details.removeChild(details.firstChild);
845 }
846
847 for (var i=0; i<analyses.length; i++) {
848 var analysis = analyses[i];
849
850 var makefileDiv = document.createElement('div');
851 makefileDiv.className = 'Makefile';
852 details.appendChild(makefileDiv);
853
854 var fileA = document.createElement('a');
855 makefileDiv.appendChild(fileA);
856 fileA.className = 'CsLink';
857 fileA.href = '%(codesearch)s' + analysis.filename;
858 fileA.innerText = analysis.filename;
859 fileA.target = "_blank";
860
861 if (analysis.modules.length > 0) {
862 var moduleTable = document.createElement('table');
863 details.appendChild(moduleTable);
864
865 for (var j=0; j<analysis.modules.length; j++) {
866 var moduleRow = document.createElement('tr');
867 moduleTable.appendChild(moduleRow);
868
869 var moduleNameCell = document.createElement('td');
870 moduleRow.appendChild(moduleNameCell);
871 moduleNameCell.className = 'ModuleName';
872 moduleNameCell.appendChild(make_module_link(analysis.modules[j]));
873
874 var moduleData = MODULE_DATA[analysis.modules[j]];
875 console.log(moduleData);
876
877 var depCell = document.createElement('td');
878 moduleRow.appendChild(depCell);
879
880 if (moduleData.deps.length == 0) {
881 depCell.className = 'ModuleDeps Unblocked';
882 depCell.innerText = 'UNBLOCKED';
883 } else {
884 depCell.className = 'ModuleDeps Blocked';
885
886 for (var k=0; k<moduleData.deps.length; k++) {
887 depCell.appendChild(make_module_link(moduleData.deps[k]));
888 depCell.appendChild(document.createElement('br'));
889 }
890 }
891 }
892 }
893
894 if (analysis.line_matches.length > 0) {
895 var lineTable = document.createElement('table');
896 details.appendChild(lineTable);
897
898 for (var j=0; j<analysis.line_matches.length; j++) {
899 var line_match = analysis.line_matches[j];
900
901 var lineRow = document.createElement('tr');
902 lineTable.appendChild(lineRow);
903
904 var linenoCell = document.createElement('td');
905 lineRow.appendChild(linenoCell);
906 linenoCell.className = 'LineNo';
907
908 var linenoA = document.createElement('a');
909 linenoCell.appendChild(linenoA);
910 linenoA.className = 'CsLink';
911 linenoA.href = '%(codesearch)s' + analysis.filename
912 + ';l=' + line_match.lineno;
913 linenoA.innerText = line_match.lineno;
914 linenoA.target = "_blank";
915
916 var textCell = document.createElement('td');
917 lineRow.appendChild(textCell);
918 textCell.className = 'LineText';
919 textCell.innerText = line_match.text;
920 }
921 }
922 }
923 }
924
925 var ANALYSIS = [
926 """ % {
927 "codesearch": args.codesearch,
928 })
929 for entry, mods in annotations.entries:
930 print(" [")
931 for analysis in entry:
932 print(" new Analysis('%(filename)s', %(modules)s, [%(line_matches)s])," % {
933 "filename": analysis.filename,
934 #"modules": json.dumps([m for m in mods if m in filename in soong.makefiles[m]]),
935 "modules": json.dumps(
936 [m for m in soong.reverse_makefiles[analysis.filename] if m in mods]),
937 "line_matches": ", ".join([
938 "new LineMatch(%d, %s)" % (lineno, json.dumps(text))
939 for lineno, text in analysis.line_matches]),
940 })
941 print(" ],")
942 print("""
943 ];
944 var MODULE_DATA = {
945 """)
946 for module in soong.modules:
947 print(" '%(name)s': new Module(%(deps)s)," % {
948 "name": module,
949 "deps": json.dumps(soong.deps[module]),
950 })
951 print("""
952 };
953 </script>
954
955 """)
956
957 print("""
958 </div> <!-- id=tables -->
959 <div id="details">
960 <div style="text-align: right;">
961 <a href="javascript:close_details();">
962 <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>
963 </a>
964 </div>
965 <div id="details_data"></div>
966 </div>
967 </body>
968 </html>
969 """)
970
971if __name__ == "__main__":
972 main()
973