blob: e41f3eada020354300dd2fb3e790fa1a3a2e2a29 [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
Joe Onorato02fb89a2020-06-27 00:10:23 -0700180def is_clean(makefile):
181 for analysis in makefile.analyses.values():
182 if analysis:
183 return False
184 return True
185
186class Annotations(object):
187 def __init__(self):
188 self.entries = []
189 self.count = 0
190
191 def Add(self, makefiles, modules):
192 self.entries.append((makefiles, modules))
193 self.count += 1
194 return self.count-1
195
196class SoongData(object):
197 def __init__(self, reader):
198 """Read the input file and store the modules and dependency mappings.
199 """
200 self.problems = dict()
201 self.deps = dict()
202 self.reverse_deps = dict()
203 self.module_types = dict()
204 self.makefiles = dict()
205 self.reverse_makefiles = dict()
206 self.installed = dict()
207 self.modules = set()
208
209 for (module, module_type, problem, dependencies, makefiles, installed) in reader:
210 self.modules.add(module)
211 makefiles = [f for f in makefiles.strip().split(' ') if f != ""]
212 self.module_types[module] = module_type
213 self.problems[module] = problem
214 self.deps[module] = [d for d in dependencies.strip().split(' ') if d != ""]
215 for dep in self.deps[module]:
216 if not dep in self.reverse_deps:
217 self.reverse_deps[dep] = []
218 self.reverse_deps[dep].append(module)
219 self.makefiles[module] = makefiles
220 for f in makefiles:
221 self.reverse_makefiles.setdefault(f, []).append(module)
222 for f in installed.strip().split(' '):
223 self.installed[f] = module
224
Joe Onorato934bd8d2020-07-16 18:10:48 -0700225 def transitive_deps(self, module):
226 results = set()
227 def traverse(module):
228 for dep in self.deps.get(module, []):
229 if not dep in results:
230 results.add(dep)
231 traverse(module)
232 traverse(module)
233 return results
234
Joe Onorato8ade9b22020-07-20 23:19:43 -0700235 def contains_unblocked_modules(self, filename):
236 for m in self.reverse_makefiles[filename]:
237 if len(self.deps[m]) == 0:
238 return True
239 return False
240
241 def contains_blocked_modules(self, filename):
242 for m in self.reverse_makefiles[filename]:
243 if len(self.deps[m]) > 0:
244 return True
245 return False
246
Joe Onorato02fb89a2020-06-27 00:10:23 -0700247def count_deps(depsdb, module, seen):
248 """Based on the depsdb, count the number of transitive dependencies.
249
250 You can pass in an reversed dependency graph to count the number of
251 modules that depend on the module."""
252 count = 0
253 seen.append(module)
254 if module in depsdb:
255 for dep in depsdb[module]:
256 if dep in seen:
257 continue
258 count += 1 + count_deps(depsdb, dep, seen)
259 return count
260
Joe Onorato02fb89a2020-06-27 00:10:23 -0700261OTHER_PARTITON = "_other"
262HOST_PARTITON = "_host"
263
264def get_partition_from_installed(HOST_OUT_ROOT, PRODUCT_OUT, filename):
265 host_prefix = HOST_OUT_ROOT + "/"
266 device_prefix = PRODUCT_OUT + "/"
267
268 if filename.startswith(host_prefix):
269 return HOST_PARTITON
270
271 elif filename.startswith(device_prefix):
272 index = filename.find("/", len(device_prefix))
273 if index < 0:
274 return OTHER_PARTITON
275 return filename[len(device_prefix):index]
276
277 return OTHER_PARTITON
278
279def format_module_link(module):
280 return "<a class='ModuleLink' href='#module_%s'>%s</a>" % (module, module)
281
282def format_module_list(modules):
283 return "".join(["<div>%s</div>" % format_module_link(m) for m in modules])
284
Joe Onorato934bd8d2020-07-16 18:10:48 -0700285def print_analysis_header(link, title):
286 print("""
287 <a name="%(link)s"></a>
288 <h2>%(title)s</h2>
289 <table>
290 <tr>
291 <th class="RowTitle">Directory</th>
292 <th class="Count">Total</th>
293 <th class="Count Clean">Easy</th>
294 <th class="Count Clean">Unblocked Clean</th>
295 <th class="Count Unblocked">Unblocked</th>
296 <th class="Count Blocked">Blocked</th>
297 <th class="Count Clean">Clean</th>
298 """ % {
299 "link": link,
300 "title": title
301 })
302 for analyzer in ANALYZERS:
303 print("""<th class="Count Warning">%s</th>""" % analyzer.title)
304 print(" </tr>")
305
Joe Onorato02fb89a2020-06-27 00:10:23 -0700306def main():
307 parser = argparse.ArgumentParser(description="Info about remaining Android.mk files.")
308 parser.add_argument("--device", type=str, required=True,
309 help="TARGET_DEVICE")
310 parser.add_argument("--title", type=str,
311 help="page title")
312 parser.add_argument("--codesearch", type=str,
313 default="https://cs.android.com/android/platform/superproject/+/master:",
314 help="page title")
315 parser.add_argument("--out_dir", type=str,
316 default=None,
317 help="Equivalent of $OUT_DIR, which will also be checked if"
318 + " --out_dir is unset. If neither is set, default is"
319 + " 'out'.")
320
321 args = parser.parse_args()
322
323 # Guess out directory name
324 if not args.out_dir:
325 args.out_dir = os.getenv("OUT_DIR", "out")
326 while args.out_dir.endswith("/") and len(args.out_dir) > 1:
327 args.out_dir = args.out_dir[:-1]
328
329 TARGET_DEVICE = args.device
Joe Onorato8ade9b22020-07-20 23:19:43 -0700330 global HOST_OUT_ROOT
Joe Onorato934bd8d2020-07-16 18:10:48 -0700331 HOST_OUT_ROOT = args.out_dir + "/host"
Joe Onorato8ade9b22020-07-20 23:19:43 -0700332 global PRODUCT_OUT
Joe Onorato02fb89a2020-06-27 00:10:23 -0700333 PRODUCT_OUT = args.out_dir + "/target/product/%s" % TARGET_DEVICE
334
Joe Onorato02fb89a2020-06-27 00:10:23 -0700335 # Read target information
336 # TODO: Pull from configurable location. This is also slightly different because it's
337 # only a single build, where as the tree scanning we do below is all Android.mk files.
338 with open("%s/obj/PACKAGING/soong_conversion_intermediates/soong_conv_data"
339 % PRODUCT_OUT, "r", errors="ignore") as csvfile:
340 soong = SoongData(csv.reader(csvfile))
341
Joe Onorato8ade9b22020-07-20 23:19:43 -0700342 # Read the makefiles
343 all_makefiles = dict()
344 for filename, modules in soong.reverse_makefiles.items():
345 if filename.startswith(args.out_dir + "/"):
346 continue
347 all_makefiles[filename] = Makefile(filename)
Joe Onorato02fb89a2020-06-27 00:10:23 -0700348
Joe Onorato8ade9b22020-07-20 23:19:43 -0700349 HtmlProcessor(args=args, soong=soong, all_makefiles=all_makefiles).execute()
Joe Onorato02fb89a2020-06-27 00:10:23 -0700350
Joe Onorato8ade9b22020-07-20 23:19:43 -0700351class HtmlProcessor(object):
352 def __init__(self, args, soong, all_makefiles):
353 self.args = args
354 self.soong = soong
355 self.all_makefiles = all_makefiles
356 self.annotations = Annotations()
Joe Onorato934bd8d2020-07-16 18:10:48 -0700357
Joe Onorato8ade9b22020-07-20 23:19:43 -0700358 def execute(self):
359 if self.args.title:
360 page_title = self.args.title
361 else:
362 page_title = "Remaining Android.mk files"
Joe Onorato02fb89a2020-06-27 00:10:23 -0700363
Joe Onorato8ade9b22020-07-20 23:19:43 -0700364 # Which modules are installed where
365 modules_by_partition = dict()
366 partitions = set()
367 for installed, module in self.soong.installed.items():
368 partition = get_partition_from_installed(HOST_OUT_ROOT, PRODUCT_OUT, installed)
369 modules_by_partition.setdefault(partition, []).append(module)
370 partitions.add(partition)
Joe Onorato02fb89a2020-06-27 00:10:23 -0700371
Joe Onorato02fb89a2020-06-27 00:10:23 -0700372 print("""
Joe Onorato8ade9b22020-07-20 23:19:43 -0700373 <html>
374 <head>
375 <title>%(page_title)s</title>
376 <style type="text/css">
377 body, table {
378 font-family: Roboto, sans-serif;
379 font-size: 9pt;
380 }
381 body {
382 margin: 0;
383 padding: 0;
384 display: flex;
385 flex-direction: column;
386 height: 100vh;
387 }
388 #container {
389 flex: 1;
390 display: flex;
391 flex-direction: row;
392 overflow: hidden;
393 }
394 #tables {
395 padding: 0 20px 40px 20px;
396 overflow: scroll;
397 flex: 2 2 600px;
398 }
399 #details {
400 display: none;
401 overflow: scroll;
402 flex: 1 1 650px;
403 padding: 0 20px 0 20px;
404 }
405 h1 {
406 margin: 16px 0 16px 20px;
407 }
408 h2 {
409 margin: 12px 0 4px 0;
410 }
411 .RowTitle {
412 text-align: left;
413 width: 200px;
414 min-width: 200px;
415 }
416 .Count {
417 text-align: center;
418 width: 60px;
419 min-width: 60px;
420 max-width: 60px;
421 }
422 th.Clean,
423 th.Unblocked {
424 background-color: #1e8e3e;
425 }
426 th.Blocked {
427 background-color: #d93025;
428 }
429 th.Warning {
430 background-color: #e8710a;
431 }
432 th {
433 background-color: #1a73e8;
434 color: white;
435 font-weight: bold;
436 }
437 td.Unblocked {
438 background-color: #81c995;
439 }
440 td.Blocked {
441 background-color: #f28b82;
442 }
443 td, th {
444 padding: 2px 4px;
445 border-right: 2px solid white;
446 }
447 tr.TotalRow td {
448 background-color: white;
449 border-right-color: white;
450 }
451 tr.AospDir td {
452 background-color: #e6f4ea;
453 border-right-color: #e6f4ea;
454 }
455 tr.GoogleDir td {
456 background-color: #e8f0fe;
457 border-right-color: #e8f0fe;
458 }
459 tr.PartnerDir td {
460 background-color: #fce8e6;
461 border-right-color: #fce8e6;
462 }
463 table {
464 border-spacing: 0;
465 border-collapse: collapse;
466 }
467 div.Makefile {
468 margin: 12px 0 0 0;
469 }
470 div.Makefile:first {
471 margin-top: 0;
472 }
473 div.FileModules {
474 padding: 4px 0 0 20px;
475 }
476 td.LineNo {
477 vertical-align: baseline;
478 padding: 6px 0 0 20px;
479 width: 50px;
480 vertical-align: baseline;
481 }
482 td.LineText {
483 vertical-align: baseline;
484 font-family: monospace;
485 padding: 6px 0 0 0;
486 }
487 a.CsLink {
488 font-family: monospace;
489 }
490 div.Help {
491 width: 550px;
492 }
493 table.HelpColumns tr {
494 border-bottom: 2px solid white;
495 }
496 .ModuleName {
497 vertical-align: baseline;
498 padding: 6px 0 0 20px;
499 width: 275px;
500 }
501 .ModuleDeps {
502 vertical-align: baseline;
503 padding: 6px 0 0 0;
504 }
505 table#Modules td {
506 vertical-align: baseline;
507 }
508 tr.Alt {
509 background-color: #ececec;
510 }
511 tr.Alt td {
512 border-right-color: #ececec;
513 }
514 .AnalysisCol {
515 width: 300px;
516 padding: 2px;
517 line-height: 21px;
518 }
519 .Analysis {
520 color: white;
521 font-weight: bold;
522 background-color: #e8710a;
523 border-radius: 6px;
524 margin: 4px;
525 padding: 2px 6px;
526 white-space: nowrap;
527 }
528 .Nav {
529 margin: 4px 0 16px 20px;
530 }
531 .NavSpacer {
532 display: inline-block;
533 width: 6px;
534 }
535 .ModuleDetails {
536 margin-top: 20px;
537 }
538 .ModuleDetails td {
539 vertical-align: baseline;
540 }
541 </style>
542 </head>
543 <body>
544 <h1>%(page_title)s</h1>
545 <div class="Nav">
546 <a href='#help'>Help</a>
547 <span class='NavSpacer'></span><span class='NavSpacer'> </span>
548 Partitions:
549 """ % {
550 "page_title": page_title,
551 })
552 for partition in sorted(partitions):
553 print("<a href='#partition_%s'>%s</a><span class='NavSpacer'></span>" % (partition, partition))
554
555 print("""
556 <span class='NavSpacer'></span><span class='NavSpacer'> </span>
557 <a href='#summary'>Overall Summary</a>
558 </div>
559 <div id="container">
560 <div id="tables">
561 <a name="help"></a>
562 <div class="Help">
563 <p>
564 This page analyzes the remaining Android.mk files in the Android Source tree.
565 <p>
566 The modules are first broken down by which of the device filesystem partitions
567 they are installed to. This also includes host tools and testcases which don't
568 actually reside in their own partition but convenitely group together.
569 <p>
570 The makefiles for each partition are further are grouped into a set of directories
571 aritrarily picked to break down the problem size by owners.
572 <ul style="width: 300px">
573 <li style="background-color: #e6f4ea">AOSP directories are colored green.</li>
574 <li style="background-color: #e8f0fe">Google directories are colored blue.</li>
575 <li style="background-color: #fce8e6">Other partner directories are colored red.</li>
576 </ul>
577 Each of the makefiles are scanned for issues that are likely to come up during
578 conversion to soong. Clicking the number in each cell shows additional information,
579 including the line that triggered the warning.
580 <p>
581 <table class="HelpColumns">
582 <tr>
583 <th>Total</th>
584 <td>The total number of makefiles in this each directory.</td>
585 </tr>
586 <tr>
587 <th class="Clean">Easy</th>
588 <td>The number of makefiles that have no warnings themselves, and also
589 none of their dependencies have warnings either.</td>
590 </tr>
591 <tr>
592 <th class="Clean">Unblocked Clean</th>
593 <td>The number of makefiles that are both Unblocked and Clean.</td>
594 </tr>
595
596 <tr>
597 <th class="Unblocked">Unblocked</th>
598 <td>Makefiles containing one or more modules that don't have any
599 additional dependencies pending before conversion.</td>
600 </tr>
601 <tr>
602 <th class="Blocked">Blocked</th>
603 <td>Makefiles containiong one or more modules which <i>do</i> have
604 additional prerequesite depenedencies that are not yet converted.</td>
605 </tr>
606 <tr>
607 <th class="Clean">Clean</th>
608 <td>The number of makefiles that have none of the following warnings.</td>
609 </tr>
610 <tr>
611 <th class="Warning">ifeq / ifneq</th>
612 <td>Makefiles that use <code>ifeq</code> or <code>ifneq</code>. i.e.
613 conditionals.</td>
614 </tr>
615 <tr>
616 <th class="Warning">Wacky Includes</th>
617 <td>Makefiles that <code>include</code> files other than the standard build-system
618 defined template and macros.</td>
619 </tr>
620 <tr>
621 <th class="Warning">Calls base_rules</th>
622 <td>Makefiles that include base_rules.mk directly.</td>
623 </tr>
624 <tr>
625 <th class="Warning">Calls define</th>
626 <td>Makefiles that define their own macros. Some of these are easy to convert
627 to soong <code>defaults</code>, but others are complex.</td>
628 </tr>
629 <tr>
630 <th class="Warning">Has ../</th>
631 <td>Makefiles containing the string "../" outside of a comment. These likely
632 access files outside their directories.</td>
633 </tr>
634 <tr>
635 <th class="Warning">dist-for-goals</th>
636 <td>Makefiles that call <code>dist-for-goals</code> directly.</td>
637 </tr>
638 <tr>
639 <th class="Warning">.PHONY</th>
640 <td>Makefiles that declare .PHONY targets.</td>
641 </tr>
642 <tr>
643 <th class="Warning">renderscript</th>
644 <td>Makefiles defining targets that depend on <code>.rscript</code> source files.</td>
645 </tr>
646 <tr>
647 <th class="Warning">vts src</th>
648 <td>Makefiles defining targets that depend on <code>.vts</code> source files.</td>
649 </tr>
650 <tr>
651 <th class="Warning">COPY_HEADERS</th>
652 <td>Makefiles using LOCAL_COPY_HEADERS.</td>
653 </tr>
654 </table>
655 <p>
656 Following the list of directories is a list of the modules that are installed on
657 each partition. Potential issues from their makefiles are listed, as well as the
658 total number of dependencies (both blocking that module and blocked by that module)
659 and the list of direct dependencies. Note: The number is the number of all transitive
660 dependencies and the list of modules is only the direct dependencies.
661 </div>
Joe Onorato02fb89a2020-06-27 00:10:23 -0700662 """)
663
Joe Onorato8ade9b22020-07-20 23:19:43 -0700664 overall_summary = Summary()
665
666 # For each partition
667 for partition in sorted(partitions):
668 modules = modules_by_partition[partition]
669
670 makefiles = set(itertools.chain.from_iterable(
671 [self.soong.makefiles[module] for module in modules]))
672
673 # Read makefiles
674 summary = Summary()
675 for filename in makefiles:
676 makefile = self.all_makefiles.get(filename)
Joe Onorato02fb89a2020-06-27 00:10:23 -0700677 if makefile:
Joe Onorato8ade9b22020-07-20 23:19:43 -0700678 summary.Add(makefile)
679 overall_summary.Add(makefile)
Joe Onorato02fb89a2020-06-27 00:10:23 -0700680
Joe Onorato8ade9b22020-07-20 23:19:43 -0700681 # Categorize directories by who is responsible
682 aosp_dirs = []
683 google_dirs = []
684 partner_dirs = []
685 for dirname in sorted(summary.directories.keys()):
686 if is_aosp(dirname):
687 aosp_dirs.append(dirname)
688 elif is_google(dirname):
689 google_dirs.append(dirname)
690 else:
691 partner_dirs.append(dirname)
692
693 print_analysis_header("partition_" + partition, partition)
694
695 for dirgroup, rowclass in [(aosp_dirs, "AospDir"),
696 (google_dirs, "GoogleDir"),
697 (partner_dirs, "PartnerDir"),]:
698 for dirname in dirgroup:
699 self.print_analysis_row(summary, modules,
700 dirname, rowclass, summary.directories[dirname])
701
702 self.print_analysis_row(summary, modules,
703 "Total", "TotalRow",
704 set(itertools.chain.from_iterable(summary.directories.values())))
705 print("""
706 </table>
707 """)
708
709 module_details = [(count_deps(self.soong.deps, m, []),
710 -count_deps(self.soong.reverse_deps, m, []), m)
711 for m in modules]
712 module_details.sort()
713 module_details = [m[2] for m in module_details]
714 print("""
715 <table class="ModuleDetails">""")
716 print("<tr>")
717 print(" <th>Module Name</th>")
718 print(" <th>Issues</th>")
719 print(" <th colspan='2'>Blocked By</th>")
720 print(" <th colspan='2'>Blocking</th>")
Joe Onorato02fb89a2020-06-27 00:10:23 -0700721 print("</tr>")
Joe Onorato8ade9b22020-07-20 23:19:43 -0700722 altRow = True
723 for module in module_details:
724 analyses = set()
725 for filename in self.soong.makefiles[module]:
726 makefile = summary.makefiles.get(filename)
727 if makefile:
728 for analyzer, analysis in makefile.analyses.items():
729 if analysis:
730 analyses.add(analyzer.title)
Joe Onorato02fb89a2020-06-27 00:10:23 -0700731
Joe Onorato8ade9b22020-07-20 23:19:43 -0700732 altRow = not altRow
733 print("<tr class='%s'>" % ("Alt" if altRow else "",))
734 print(" <td><a name='module_%s'></a>%s</td>" % (module, module))
735 print(" <td class='AnalysisCol'>%s</td>" % " ".join(["<span class='Analysis'>%s</span>" % title
736 for title in analyses]))
737 print(" <td>%s</td>" % count_deps(self.soong.deps, module, []))
738 print(" <td>%s</td>" % format_module_list(self.soong.deps.get(module, [])))
739 print(" <td>%s</td>" % count_deps(self.soong.reverse_deps, module, []))
740 print(" <td>%s</td>" % format_module_list(self.soong.reverse_deps.get(module, [])))
741 print("</tr>")
742 print("""</table>""")
Joe Onorato934bd8d2020-07-16 18:10:48 -0700743
Joe Onorato8ade9b22020-07-20 23:19:43 -0700744 print_analysis_header("summary", "Overall Summary")
Joe Onorato934bd8d2020-07-16 18:10:48 -0700745
Joe Onorato8ade9b22020-07-20 23:19:43 -0700746 modules = [module for installed, module in self.soong.installed.items()]
747 self.print_analysis_row(overall_summary, modules,
748 "All Makefiles", "TotalRow",
749 set(itertools.chain.from_iterable(overall_summary.directories.values())))
750 print("""
751 </table>
752 """)
Joe Onorato02fb89a2020-06-27 00:10:23 -0700753
Joe Onorato8ade9b22020-07-20 23:19:43 -0700754 print("""
755 <script type="text/javascript">
756 function close_details() {
757 document.getElementById('details').style.display = 'none';
Joe Onorato02fb89a2020-06-27 00:10:23 -0700758 }
759
Joe Onorato8ade9b22020-07-20 23:19:43 -0700760 class LineMatch {
761 constructor(lineno, text) {
762 this.lineno = lineno;
763 this.text = text;
764 }
765 }
Joe Onorato02fb89a2020-06-27 00:10:23 -0700766
Joe Onorato8ade9b22020-07-20 23:19:43 -0700767 class Analysis {
768 constructor(filename, modules, line_matches) {
769 this.filename = filename;
770 this.modules = modules;
771 this.line_matches = line_matches;
772 }
773 }
Joe Onorato02fb89a2020-06-27 00:10:23 -0700774
Joe Onorato8ade9b22020-07-20 23:19:43 -0700775 class Module {
776 constructor(deps) {
777 this.deps = deps;
778 }
779 }
Joe Onorato02fb89a2020-06-27 00:10:23 -0700780
Joe Onorato8ade9b22020-07-20 23:19:43 -0700781 function make_module_link(module) {
782 var a = document.createElement('a');
783 a.className = 'ModuleLink';
784 a.innerText = module;
785 a.href = '#module_' + module;
786 return a;
787 }
Joe Onorato02fb89a2020-06-27 00:10:23 -0700788
Joe Onorato8ade9b22020-07-20 23:19:43 -0700789 function update_details(id) {
790 document.getElementById('details').style.display = 'block';
Joe Onorato02fb89a2020-06-27 00:10:23 -0700791
Joe Onorato8ade9b22020-07-20 23:19:43 -0700792 var analyses = ANALYSIS[id];
Joe Onorato02fb89a2020-06-27 00:10:23 -0700793
Joe Onorato8ade9b22020-07-20 23:19:43 -0700794 var details = document.getElementById("details_data");
795 while (details.firstChild) {
796 details.removeChild(details.firstChild);
797 }
Joe Onorato02fb89a2020-06-27 00:10:23 -0700798
Joe Onorato8ade9b22020-07-20 23:19:43 -0700799 for (var i=0; i<analyses.length; i++) {
800 var analysis = analyses[i];
Joe Onorato02fb89a2020-06-27 00:10:23 -0700801
Joe Onorato8ade9b22020-07-20 23:19:43 -0700802 var makefileDiv = document.createElement('div');
803 makefileDiv.className = 'Makefile';
804 details.appendChild(makefileDiv);
Joe Onorato02fb89a2020-06-27 00:10:23 -0700805
Joe Onorato8ade9b22020-07-20 23:19:43 -0700806 var fileA = document.createElement('a');
807 makefileDiv.appendChild(fileA);
808 fileA.className = 'CsLink';
809 fileA.href = '%(codesearch)s' + analysis.filename;
810 fileA.innerText = analysis.filename;
811 fileA.target = "_blank";
812
813 if (analysis.modules.length > 0) {
814 var moduleTable = document.createElement('table');
815 details.appendChild(moduleTable);
816
817 for (var j=0; j<analysis.modules.length; j++) {
818 var moduleRow = document.createElement('tr');
819 moduleTable.appendChild(moduleRow);
820
821 var moduleNameCell = document.createElement('td');
822 moduleRow.appendChild(moduleNameCell);
823 moduleNameCell.className = 'ModuleName';
824 moduleNameCell.appendChild(make_module_link(analysis.modules[j]));
825
826 var moduleData = MODULE_DATA[analysis.modules[j]];
827 console.log(moduleData);
828
829 var depCell = document.createElement('td');
830 moduleRow.appendChild(depCell);
831
832 if (moduleData.deps.length == 0) {
833 depCell.className = 'ModuleDeps Unblocked';
834 depCell.innerText = 'UNBLOCKED';
835 } else {
836 depCell.className = 'ModuleDeps Blocked';
837
838 for (var k=0; k<moduleData.deps.length; k++) {
839 depCell.appendChild(make_module_link(moduleData.deps[k]));
840 depCell.appendChild(document.createElement('br'));
841 }
Joe Onorato02fb89a2020-06-27 00:10:23 -0700842 }
843 }
844 }
Joe Onorato02fb89a2020-06-27 00:10:23 -0700845
Joe Onorato8ade9b22020-07-20 23:19:43 -0700846 if (analysis.line_matches.length > 0) {
847 var lineTable = document.createElement('table');
848 details.appendChild(lineTable);
Joe Onorato02fb89a2020-06-27 00:10:23 -0700849
Joe Onorato8ade9b22020-07-20 23:19:43 -0700850 for (var j=0; j<analysis.line_matches.length; j++) {
851 var line_match = analysis.line_matches[j];
Joe Onorato02fb89a2020-06-27 00:10:23 -0700852
Joe Onorato8ade9b22020-07-20 23:19:43 -0700853 var lineRow = document.createElement('tr');
854 lineTable.appendChild(lineRow);
Joe Onorato02fb89a2020-06-27 00:10:23 -0700855
Joe Onorato8ade9b22020-07-20 23:19:43 -0700856 var linenoCell = document.createElement('td');
857 lineRow.appendChild(linenoCell);
858 linenoCell.className = 'LineNo';
Joe Onorato02fb89a2020-06-27 00:10:23 -0700859
Joe Onorato8ade9b22020-07-20 23:19:43 -0700860 var linenoA = document.createElement('a');
861 linenoCell.appendChild(linenoA);
862 linenoA.className = 'CsLink';
863 linenoA.href = '%(codesearch)s' + analysis.filename
864 + ';l=' + line_match.lineno;
865 linenoA.innerText = line_match.lineno;
866 linenoA.target = "_blank";
Joe Onorato02fb89a2020-06-27 00:10:23 -0700867
Joe Onorato8ade9b22020-07-20 23:19:43 -0700868 var textCell = document.createElement('td');
869 lineRow.appendChild(textCell);
870 textCell.className = 'LineText';
871 textCell.innerText = line_match.text;
872 }
Joe Onorato02fb89a2020-06-27 00:10:23 -0700873 }
874 }
875 }
Joe Onorato02fb89a2020-06-27 00:10:23 -0700876
Joe Onorato8ade9b22020-07-20 23:19:43 -0700877 var ANALYSIS = [
878 """ % {
879 "codesearch": self.args.codesearch,
Joe Onorato02fb89a2020-06-27 00:10:23 -0700880 })
Joe Onorato8ade9b22020-07-20 23:19:43 -0700881 for entry, mods in self.annotations.entries:
882 print(" [")
883 for analysis in entry:
884 print(" new Analysis('%(filename)s', %(modules)s, [%(line_matches)s])," % {
885 "filename": analysis.filename,
886 #"modules": json.dumps([m for m in mods if m in filename in self.soong.makefiles[m]]),
887 "modules": json.dumps(
888 [m for m in self.soong.reverse_makefiles[analysis.filename] if m in mods]),
889 "line_matches": ", ".join([
890 "new LineMatch(%d, %s)" % (lineno, json.dumps(text))
891 for lineno, text in analysis.line_matches]),
892 })
893 print(" ],")
894 print("""
895 ];
896 var MODULE_DATA = {
897 """)
898 for module in self.soong.modules:
899 print(" '%(name)s': new Module(%(deps)s)," % {
900 "name": module,
901 "deps": json.dumps(self.soong.deps[module]),
902 })
903 print("""
904 };
905 </script>
Joe Onorato02fb89a2020-06-27 00:10:23 -0700906
Joe Onorato8ade9b22020-07-20 23:19:43 -0700907 """)
Joe Onorato02fb89a2020-06-27 00:10:23 -0700908
Joe Onorato8ade9b22020-07-20 23:19:43 -0700909 print("""
910 </div> <!-- id=tables -->
911 <div id="details">
912 <div style="text-align: right;">
913 <a href="javascript:close_details();">
914 <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>
915 </a>
916 </div>
917 <div id="details_data"></div>
Joe Onorato02fb89a2020-06-27 00:10:23 -0700918 </div>
Joe Onorato8ade9b22020-07-20 23:19:43 -0700919 </body>
920 </html>
921 """)
922
923 def traverse_ready_makefiles(self, summary, makefiles):
924 def clean_and_only_blocked_by_clean(makefile):
925 if not is_clean(makefile):
926 return False
927 modules = self.soong.reverse_makefiles[makefile.filename]
928 for module in modules:
929 for dep in self.soong.transitive_deps(module):
930 for m in self.soong.makefiles.get(dep, []):
931 if not summary.IsClean(m):
932 return False
933 return True
934
935 return [Analysis(makefile.filename, []) for makefile in makefiles
936 if clean_and_only_blocked_by_clean(makefile)]
937
938 def print_analysis_row(self, summary, modules, rowtitle, rowclass, makefiles):
939 all_makefiles = [Analysis(makefile.filename, []) for makefile in makefiles]
940 clean_makefiles = [Analysis(makefile.filename, []) for makefile in makefiles
941 if is_clean(makefile)]
942 easy_makefiles = self.traverse_ready_makefiles(summary, makefiles)
943 unblocked_clean_makefiles = [Analysis(makefile.filename, []) for makefile in makefiles
944 if (self.soong.contains_unblocked_modules(makefile.filename)
945 and is_clean(makefile))]
946 unblocked_makefiles = [Analysis(makefile.filename, []) for makefile in makefiles
947 if self.soong.contains_unblocked_modules(makefile.filename)]
948 blocked_makefiles = [Analysis(makefile.filename, []) for makefile in makefiles
949 if self.soong.contains_blocked_modules(makefile.filename)]
950
951 print("""
952 <tr class="%(rowclass)s">
953 <td class="RowTitle">%(rowtitle)s</td>
954 <td class="Count">%(makefiles)s</td>
955 <td class="Count">%(easy)s</td>
956 <td class="Count">%(unblocked_clean)s</td>
957 <td class="Count">%(unblocked)s</td>
958 <td class="Count">%(blocked)s</td>
959 <td class="Count">%(clean)s</td>
960 """ % {
961 "rowclass": rowclass,
962 "rowtitle": rowtitle,
963 "makefiles": self.make_annotation_link(all_makefiles, modules),
964 "unblocked": self.make_annotation_link(unblocked_makefiles, modules),
965 "blocked": self.make_annotation_link(blocked_makefiles, modules),
966 "clean": self.make_annotation_link(clean_makefiles, modules),
967 "unblocked_clean": self.make_annotation_link(unblocked_clean_makefiles, modules),
968 "easy": self.make_annotation_link(easy_makefiles, modules),
969 })
970
971 for analyzer in ANALYZERS:
972 analyses = [m.analyses.get(analyzer) for m in makefiles if m.analyses.get(analyzer)]
973 print("""<td class="Count">%s</td>"""
974 % self.make_annotation_link(analyses, modules))
975
976 print(" </tr>")
977
978 def make_annotation_link(self, analysis, modules):
979 if analysis:
980 return "<a href='javascript:update_details(%d)'>%s</a>" % (
981 self.annotations.Add(analysis, modules),
982 len(analysis)
983 )
984 else:
985 return "";
Joe Onorato02fb89a2020-06-27 00:10:23 -0700986
987if __name__ == "__main__":
988 main()
989