blob: c2afb9b94883fd8a75972467a590c8bea3556d49 [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
Joe Onorato02fb89a2020-06-27 00:10:23 -0700311def main():
312 parser = argparse.ArgumentParser(description="Info about remaining Android.mk files.")
313 parser.add_argument("--device", type=str, required=True,
314 help="TARGET_DEVICE")
315 parser.add_argument("--title", type=str,
316 help="page title")
317 parser.add_argument("--codesearch", type=str,
318 default="https://cs.android.com/android/platform/superproject/+/master:",
319 help="page title")
320 parser.add_argument("--out_dir", type=str,
321 default=None,
322 help="Equivalent of $OUT_DIR, which will also be checked if"
323 + " --out_dir is unset. If neither is set, default is"
324 + " 'out'.")
Joe Onorato31986072020-08-10 09:58:49 -0700325 parser.add_argument("--mode", type=str,
326 default="html",
327 help="output format: csv or html")
Joe Onorato02fb89a2020-06-27 00:10:23 -0700328
329 args = parser.parse_args()
330
331 # Guess out directory name
332 if not args.out_dir:
333 args.out_dir = os.getenv("OUT_DIR", "out")
334 while args.out_dir.endswith("/") and len(args.out_dir) > 1:
335 args.out_dir = args.out_dir[:-1]
336
337 TARGET_DEVICE = args.device
Joe Onorato8ade9b22020-07-20 23:19:43 -0700338 global HOST_OUT_ROOT
Joe Onorato934bd8d2020-07-16 18:10:48 -0700339 HOST_OUT_ROOT = args.out_dir + "/host"
Joe Onorato8ade9b22020-07-20 23:19:43 -0700340 global PRODUCT_OUT
Joe Onorato02fb89a2020-06-27 00:10:23 -0700341 PRODUCT_OUT = args.out_dir + "/target/product/%s" % TARGET_DEVICE
342
Joe Onorato02fb89a2020-06-27 00:10:23 -0700343 # Read target information
344 # TODO: Pull from configurable location. This is also slightly different because it's
345 # only a single build, where as the tree scanning we do below is all Android.mk files.
346 with open("%s/obj/PACKAGING/soong_conversion_intermediates/soong_conv_data"
347 % PRODUCT_OUT, "r", errors="ignore") as csvfile:
348 soong = SoongData(csv.reader(csvfile))
349
Joe Onorato8ade9b22020-07-20 23:19:43 -0700350 # Read the makefiles
351 all_makefiles = dict()
352 for filename, modules in soong.reverse_makefiles.items():
353 if filename.startswith(args.out_dir + "/"):
354 continue
355 all_makefiles[filename] = Makefile(filename)
Joe Onorato02fb89a2020-06-27 00:10:23 -0700356
Joe Onorato31986072020-08-10 09:58:49 -0700357 if args.mode == "html":
358 HtmlProcessor(args=args, soong=soong, all_makefiles=all_makefiles).execute()
359 elif args.mode == "csv":
360 CsvProcessor(args=args, soong=soong, all_makefiles=all_makefiles).execute()
Joe Onorato02fb89a2020-06-27 00:10:23 -0700361
Joe Onorato8ade9b22020-07-20 23:19:43 -0700362class HtmlProcessor(object):
363 def __init__(self, args, soong, all_makefiles):
364 self.args = args
365 self.soong = soong
366 self.all_makefiles = all_makefiles
367 self.annotations = Annotations()
Joe Onorato934bd8d2020-07-16 18:10:48 -0700368
Joe Onorato8ade9b22020-07-20 23:19:43 -0700369 def execute(self):
370 if self.args.title:
371 page_title = self.args.title
372 else:
373 page_title = "Remaining Android.mk files"
Joe Onorato02fb89a2020-06-27 00:10:23 -0700374
Joe Onorato8ade9b22020-07-20 23:19:43 -0700375 # Which modules are installed where
376 modules_by_partition = dict()
377 partitions = set()
378 for installed, module in self.soong.installed.items():
379 partition = get_partition_from_installed(HOST_OUT_ROOT, PRODUCT_OUT, installed)
380 modules_by_partition.setdefault(partition, []).append(module)
381 partitions.add(partition)
Joe Onorato02fb89a2020-06-27 00:10:23 -0700382
Joe Onorato02fb89a2020-06-27 00:10:23 -0700383 print("""
Joe Onorato8ade9b22020-07-20 23:19:43 -0700384 <html>
385 <head>
386 <title>%(page_title)s</title>
387 <style type="text/css">
388 body, table {
389 font-family: Roboto, sans-serif;
390 font-size: 9pt;
391 }
392 body {
393 margin: 0;
394 padding: 0;
395 display: flex;
396 flex-direction: column;
397 height: 100vh;
398 }
399 #container {
400 flex: 1;
401 display: flex;
402 flex-direction: row;
403 overflow: hidden;
404 }
405 #tables {
406 padding: 0 20px 40px 20px;
407 overflow: scroll;
408 flex: 2 2 600px;
409 }
410 #details {
411 display: none;
412 overflow: scroll;
413 flex: 1 1 650px;
414 padding: 0 20px 0 20px;
415 }
416 h1 {
417 margin: 16px 0 16px 20px;
418 }
419 h2 {
420 margin: 12px 0 4px 0;
421 }
422 .RowTitle {
423 text-align: left;
424 width: 200px;
425 min-width: 200px;
426 }
427 .Count {
428 text-align: center;
429 width: 60px;
430 min-width: 60px;
431 max-width: 60px;
432 }
433 th.Clean,
434 th.Unblocked {
435 background-color: #1e8e3e;
436 }
437 th.Blocked {
438 background-color: #d93025;
439 }
440 th.Warning {
441 background-color: #e8710a;
442 }
443 th {
444 background-color: #1a73e8;
445 color: white;
446 font-weight: bold;
447 }
448 td.Unblocked {
449 background-color: #81c995;
450 }
451 td.Blocked {
452 background-color: #f28b82;
453 }
454 td, th {
455 padding: 2px 4px;
456 border-right: 2px solid white;
457 }
458 tr.TotalRow td {
459 background-color: white;
460 border-right-color: white;
461 }
462 tr.AospDir td {
463 background-color: #e6f4ea;
464 border-right-color: #e6f4ea;
465 }
466 tr.GoogleDir td {
467 background-color: #e8f0fe;
468 border-right-color: #e8f0fe;
469 }
470 tr.PartnerDir td {
471 background-color: #fce8e6;
472 border-right-color: #fce8e6;
473 }
474 table {
475 border-spacing: 0;
476 border-collapse: collapse;
477 }
478 div.Makefile {
479 margin: 12px 0 0 0;
480 }
481 div.Makefile:first {
482 margin-top: 0;
483 }
484 div.FileModules {
485 padding: 4px 0 0 20px;
486 }
487 td.LineNo {
488 vertical-align: baseline;
489 padding: 6px 0 0 20px;
490 width: 50px;
491 vertical-align: baseline;
492 }
493 td.LineText {
494 vertical-align: baseline;
495 font-family: monospace;
496 padding: 6px 0 0 0;
497 }
498 a.CsLink {
499 font-family: monospace;
500 }
501 div.Help {
502 width: 550px;
503 }
504 table.HelpColumns tr {
505 border-bottom: 2px solid white;
506 }
507 .ModuleName {
508 vertical-align: baseline;
509 padding: 6px 0 0 20px;
510 width: 275px;
511 }
512 .ModuleDeps {
513 vertical-align: baseline;
514 padding: 6px 0 0 0;
515 }
516 table#Modules td {
517 vertical-align: baseline;
518 }
519 tr.Alt {
520 background-color: #ececec;
521 }
522 tr.Alt td {
523 border-right-color: #ececec;
524 }
525 .AnalysisCol {
526 width: 300px;
527 padding: 2px;
528 line-height: 21px;
529 }
530 .Analysis {
531 color: white;
532 font-weight: bold;
533 background-color: #e8710a;
534 border-radius: 6px;
535 margin: 4px;
536 padding: 2px 6px;
537 white-space: nowrap;
538 }
539 .Nav {
540 margin: 4px 0 16px 20px;
541 }
542 .NavSpacer {
543 display: inline-block;
544 width: 6px;
545 }
546 .ModuleDetails {
547 margin-top: 20px;
548 }
549 .ModuleDetails td {
550 vertical-align: baseline;
551 }
552 </style>
553 </head>
554 <body>
555 <h1>%(page_title)s</h1>
556 <div class="Nav">
557 <a href='#help'>Help</a>
558 <span class='NavSpacer'></span><span class='NavSpacer'> </span>
559 Partitions:
560 """ % {
561 "page_title": page_title,
562 })
563 for partition in sorted(partitions):
564 print("<a href='#partition_%s'>%s</a><span class='NavSpacer'></span>" % (partition, partition))
565
566 print("""
567 <span class='NavSpacer'></span><span class='NavSpacer'> </span>
568 <a href='#summary'>Overall Summary</a>
569 </div>
570 <div id="container">
571 <div id="tables">
572 <a name="help"></a>
573 <div class="Help">
574 <p>
575 This page analyzes the remaining Android.mk files in the Android Source tree.
576 <p>
577 The modules are first broken down by which of the device filesystem partitions
578 they are installed to. This also includes host tools and testcases which don't
579 actually reside in their own partition but convenitely group together.
580 <p>
581 The makefiles for each partition are further are grouped into a set of directories
582 aritrarily picked to break down the problem size by owners.
583 <ul style="width: 300px">
584 <li style="background-color: #e6f4ea">AOSP directories are colored green.</li>
585 <li style="background-color: #e8f0fe">Google directories are colored blue.</li>
586 <li style="background-color: #fce8e6">Other partner directories are colored red.</li>
587 </ul>
588 Each of the makefiles are scanned for issues that are likely to come up during
589 conversion to soong. Clicking the number in each cell shows additional information,
590 including the line that triggered the warning.
591 <p>
592 <table class="HelpColumns">
593 <tr>
594 <th>Total</th>
595 <td>The total number of makefiles in this each directory.</td>
596 </tr>
597 <tr>
598 <th class="Clean">Easy</th>
599 <td>The number of makefiles that have no warnings themselves, and also
600 none of their dependencies have warnings either.</td>
601 </tr>
602 <tr>
603 <th class="Clean">Unblocked Clean</th>
604 <td>The number of makefiles that are both Unblocked and Clean.</td>
605 </tr>
606
607 <tr>
608 <th class="Unblocked">Unblocked</th>
609 <td>Makefiles containing one or more modules that don't have any
610 additional dependencies pending before conversion.</td>
611 </tr>
612 <tr>
613 <th class="Blocked">Blocked</th>
614 <td>Makefiles containiong one or more modules which <i>do</i> have
615 additional prerequesite depenedencies that are not yet converted.</td>
616 </tr>
617 <tr>
618 <th class="Clean">Clean</th>
619 <td>The number of makefiles that have none of the following warnings.</td>
620 </tr>
621 <tr>
622 <th class="Warning">ifeq / ifneq</th>
623 <td>Makefiles that use <code>ifeq</code> or <code>ifneq</code>. i.e.
624 conditionals.</td>
625 </tr>
626 <tr>
627 <th class="Warning">Wacky Includes</th>
628 <td>Makefiles that <code>include</code> files other than the standard build-system
629 defined template and macros.</td>
630 </tr>
631 <tr>
632 <th class="Warning">Calls base_rules</th>
633 <td>Makefiles that include base_rules.mk directly.</td>
634 </tr>
635 <tr>
636 <th class="Warning">Calls define</th>
637 <td>Makefiles that define their own macros. Some of these are easy to convert
638 to soong <code>defaults</code>, but others are complex.</td>
639 </tr>
640 <tr>
641 <th class="Warning">Has ../</th>
642 <td>Makefiles containing the string "../" outside of a comment. These likely
643 access files outside their directories.</td>
644 </tr>
645 <tr>
646 <th class="Warning">dist-for-goals</th>
647 <td>Makefiles that call <code>dist-for-goals</code> directly.</td>
648 </tr>
649 <tr>
650 <th class="Warning">.PHONY</th>
651 <td>Makefiles that declare .PHONY targets.</td>
652 </tr>
653 <tr>
654 <th class="Warning">renderscript</th>
655 <td>Makefiles defining targets that depend on <code>.rscript</code> source files.</td>
656 </tr>
657 <tr>
658 <th class="Warning">vts src</th>
659 <td>Makefiles defining targets that depend on <code>.vts</code> source files.</td>
660 </tr>
661 <tr>
662 <th class="Warning">COPY_HEADERS</th>
663 <td>Makefiles using LOCAL_COPY_HEADERS.</td>
664 </tr>
665 </table>
666 <p>
667 Following the list of directories is a list of the modules that are installed on
668 each partition. Potential issues from their makefiles are listed, as well as the
669 total number of dependencies (both blocking that module and blocked by that module)
670 and the list of direct dependencies. Note: The number is the number of all transitive
671 dependencies and the list of modules is only the direct dependencies.
672 </div>
Joe Onorato02fb89a2020-06-27 00:10:23 -0700673 """)
674
Joe Onorato8ade9b22020-07-20 23:19:43 -0700675 overall_summary = Summary()
676
677 # For each partition
678 for partition in sorted(partitions):
679 modules = modules_by_partition[partition]
680
681 makefiles = set(itertools.chain.from_iterable(
682 [self.soong.makefiles[module] for module in modules]))
683
684 # Read makefiles
685 summary = Summary()
686 for filename in makefiles:
687 makefile = self.all_makefiles.get(filename)
Joe Onorato02fb89a2020-06-27 00:10:23 -0700688 if makefile:
Joe Onorato8ade9b22020-07-20 23:19:43 -0700689 summary.Add(makefile)
690 overall_summary.Add(makefile)
Joe Onorato02fb89a2020-06-27 00:10:23 -0700691
Joe Onorato8ade9b22020-07-20 23:19:43 -0700692 # Categorize directories by who is responsible
693 aosp_dirs = []
694 google_dirs = []
695 partner_dirs = []
696 for dirname in sorted(summary.directories.keys()):
697 if is_aosp(dirname):
698 aosp_dirs.append(dirname)
699 elif is_google(dirname):
700 google_dirs.append(dirname)
701 else:
702 partner_dirs.append(dirname)
703
704 print_analysis_header("partition_" + partition, partition)
705
706 for dirgroup, rowclass in [(aosp_dirs, "AospDir"),
707 (google_dirs, "GoogleDir"),
708 (partner_dirs, "PartnerDir"),]:
709 for dirname in dirgroup:
710 self.print_analysis_row(summary, modules,
711 dirname, rowclass, summary.directories[dirname])
712
713 self.print_analysis_row(summary, modules,
714 "Total", "TotalRow",
715 set(itertools.chain.from_iterable(summary.directories.values())))
716 print("""
717 </table>
718 """)
719
720 module_details = [(count_deps(self.soong.deps, m, []),
721 -count_deps(self.soong.reverse_deps, m, []), m)
722 for m in modules]
723 module_details.sort()
724 module_details = [m[2] for m in module_details]
725 print("""
726 <table class="ModuleDetails">""")
727 print("<tr>")
728 print(" <th>Module Name</th>")
729 print(" <th>Issues</th>")
730 print(" <th colspan='2'>Blocked By</th>")
731 print(" <th colspan='2'>Blocking</th>")
Joe Onorato02fb89a2020-06-27 00:10:23 -0700732 print("</tr>")
Joe Onorato8ade9b22020-07-20 23:19:43 -0700733 altRow = True
734 for module in module_details:
735 analyses = set()
736 for filename in self.soong.makefiles[module]:
737 makefile = summary.makefiles.get(filename)
738 if makefile:
739 for analyzer, analysis in makefile.analyses.items():
740 if analysis:
741 analyses.add(analyzer.title)
Joe Onorato02fb89a2020-06-27 00:10:23 -0700742
Joe Onorato8ade9b22020-07-20 23:19:43 -0700743 altRow = not altRow
744 print("<tr class='%s'>" % ("Alt" if altRow else "",))
745 print(" <td><a name='module_%s'></a>%s</td>" % (module, module))
746 print(" <td class='AnalysisCol'>%s</td>" % " ".join(["<span class='Analysis'>%s</span>" % title
747 for title in analyses]))
748 print(" <td>%s</td>" % count_deps(self.soong.deps, module, []))
749 print(" <td>%s</td>" % format_module_list(self.soong.deps.get(module, [])))
750 print(" <td>%s</td>" % count_deps(self.soong.reverse_deps, module, []))
751 print(" <td>%s</td>" % format_module_list(self.soong.reverse_deps.get(module, [])))
752 print("</tr>")
753 print("""</table>""")
Joe Onorato934bd8d2020-07-16 18:10:48 -0700754
Joe Onorato8ade9b22020-07-20 23:19:43 -0700755 print_analysis_header("summary", "Overall Summary")
Joe Onorato934bd8d2020-07-16 18:10:48 -0700756
Joe Onorato8ade9b22020-07-20 23:19:43 -0700757 modules = [module for installed, module in self.soong.installed.items()]
758 self.print_analysis_row(overall_summary, modules,
759 "All Makefiles", "TotalRow",
760 set(itertools.chain.from_iterable(overall_summary.directories.values())))
761 print("""
762 </table>
763 """)
Joe Onorato02fb89a2020-06-27 00:10:23 -0700764
Joe Onorato8ade9b22020-07-20 23:19:43 -0700765 print("""
766 <script type="text/javascript">
767 function close_details() {
768 document.getElementById('details').style.display = 'none';
Joe Onorato02fb89a2020-06-27 00:10:23 -0700769 }
770
Joe Onorato8ade9b22020-07-20 23:19:43 -0700771 class LineMatch {
772 constructor(lineno, text) {
773 this.lineno = lineno;
774 this.text = text;
775 }
776 }
Joe Onorato02fb89a2020-06-27 00:10:23 -0700777
Joe Onorato8ade9b22020-07-20 23:19:43 -0700778 class Analysis {
779 constructor(filename, modules, line_matches) {
780 this.filename = filename;
781 this.modules = modules;
782 this.line_matches = line_matches;
783 }
784 }
Joe Onorato02fb89a2020-06-27 00:10:23 -0700785
Joe Onorato8ade9b22020-07-20 23:19:43 -0700786 class Module {
787 constructor(deps) {
788 this.deps = deps;
789 }
790 }
Joe Onorato02fb89a2020-06-27 00:10:23 -0700791
Joe Onorato8ade9b22020-07-20 23:19:43 -0700792 function make_module_link(module) {
793 var a = document.createElement('a');
794 a.className = 'ModuleLink';
795 a.innerText = module;
796 a.href = '#module_' + module;
797 return a;
798 }
Joe Onorato02fb89a2020-06-27 00:10:23 -0700799
Joe Onorato8ade9b22020-07-20 23:19:43 -0700800 function update_details(id) {
801 document.getElementById('details').style.display = 'block';
Joe Onorato02fb89a2020-06-27 00:10:23 -0700802
Joe Onorato8ade9b22020-07-20 23:19:43 -0700803 var analyses = ANALYSIS[id];
Joe Onorato02fb89a2020-06-27 00:10:23 -0700804
Joe Onorato8ade9b22020-07-20 23:19:43 -0700805 var details = document.getElementById("details_data");
806 while (details.firstChild) {
807 details.removeChild(details.firstChild);
808 }
Joe Onorato02fb89a2020-06-27 00:10:23 -0700809
Joe Onorato8ade9b22020-07-20 23:19:43 -0700810 for (var i=0; i<analyses.length; i++) {
811 var analysis = analyses[i];
Joe Onorato02fb89a2020-06-27 00:10:23 -0700812
Joe Onorato8ade9b22020-07-20 23:19:43 -0700813 var makefileDiv = document.createElement('div');
814 makefileDiv.className = 'Makefile';
815 details.appendChild(makefileDiv);
Joe Onorato02fb89a2020-06-27 00:10:23 -0700816
Joe Onorato8ade9b22020-07-20 23:19:43 -0700817 var fileA = document.createElement('a');
818 makefileDiv.appendChild(fileA);
819 fileA.className = 'CsLink';
820 fileA.href = '%(codesearch)s' + analysis.filename;
821 fileA.innerText = analysis.filename;
822 fileA.target = "_blank";
823
824 if (analysis.modules.length > 0) {
825 var moduleTable = document.createElement('table');
826 details.appendChild(moduleTable);
827
828 for (var j=0; j<analysis.modules.length; j++) {
829 var moduleRow = document.createElement('tr');
830 moduleTable.appendChild(moduleRow);
831
832 var moduleNameCell = document.createElement('td');
833 moduleRow.appendChild(moduleNameCell);
834 moduleNameCell.className = 'ModuleName';
835 moduleNameCell.appendChild(make_module_link(analysis.modules[j]));
836
837 var moduleData = MODULE_DATA[analysis.modules[j]];
838 console.log(moduleData);
839
840 var depCell = document.createElement('td');
841 moduleRow.appendChild(depCell);
842
843 if (moduleData.deps.length == 0) {
844 depCell.className = 'ModuleDeps Unblocked';
845 depCell.innerText = 'UNBLOCKED';
846 } else {
847 depCell.className = 'ModuleDeps Blocked';
848
849 for (var k=0; k<moduleData.deps.length; k++) {
850 depCell.appendChild(make_module_link(moduleData.deps[k]));
851 depCell.appendChild(document.createElement('br'));
852 }
Joe Onorato02fb89a2020-06-27 00:10:23 -0700853 }
854 }
855 }
Joe Onorato02fb89a2020-06-27 00:10:23 -0700856
Joe Onorato8ade9b22020-07-20 23:19:43 -0700857 if (analysis.line_matches.length > 0) {
858 var lineTable = document.createElement('table');
859 details.appendChild(lineTable);
Joe Onorato02fb89a2020-06-27 00:10:23 -0700860
Joe Onorato8ade9b22020-07-20 23:19:43 -0700861 for (var j=0; j<analysis.line_matches.length; j++) {
862 var line_match = analysis.line_matches[j];
Joe Onorato02fb89a2020-06-27 00:10:23 -0700863
Joe Onorato8ade9b22020-07-20 23:19:43 -0700864 var lineRow = document.createElement('tr');
865 lineTable.appendChild(lineRow);
Joe Onorato02fb89a2020-06-27 00:10:23 -0700866
Joe Onorato8ade9b22020-07-20 23:19:43 -0700867 var linenoCell = document.createElement('td');
868 lineRow.appendChild(linenoCell);
869 linenoCell.className = 'LineNo';
Joe Onorato02fb89a2020-06-27 00:10:23 -0700870
Joe Onorato8ade9b22020-07-20 23:19:43 -0700871 var linenoA = document.createElement('a');
872 linenoCell.appendChild(linenoA);
873 linenoA.className = 'CsLink';
874 linenoA.href = '%(codesearch)s' + analysis.filename
875 + ';l=' + line_match.lineno;
876 linenoA.innerText = line_match.lineno;
877 linenoA.target = "_blank";
Joe Onorato02fb89a2020-06-27 00:10:23 -0700878
Joe Onorato8ade9b22020-07-20 23:19:43 -0700879 var textCell = document.createElement('td');
880 lineRow.appendChild(textCell);
881 textCell.className = 'LineText';
882 textCell.innerText = line_match.text;
883 }
Joe Onorato02fb89a2020-06-27 00:10:23 -0700884 }
885 }
886 }
Joe Onorato02fb89a2020-06-27 00:10:23 -0700887
Joe Onorato8ade9b22020-07-20 23:19:43 -0700888 var ANALYSIS = [
889 """ % {
890 "codesearch": self.args.codesearch,
Joe Onorato02fb89a2020-06-27 00:10:23 -0700891 })
Joe Onorato8ade9b22020-07-20 23:19:43 -0700892 for entry, mods in self.annotations.entries:
893 print(" [")
894 for analysis in entry:
895 print(" new Analysis('%(filename)s', %(modules)s, [%(line_matches)s])," % {
896 "filename": analysis.filename,
897 #"modules": json.dumps([m for m in mods if m in filename in self.soong.makefiles[m]]),
898 "modules": json.dumps(
899 [m for m in self.soong.reverse_makefiles[analysis.filename] if m in mods]),
900 "line_matches": ", ".join([
901 "new LineMatch(%d, %s)" % (lineno, json.dumps(text))
902 for lineno, text in analysis.line_matches]),
903 })
904 print(" ],")
905 print("""
906 ];
907 var MODULE_DATA = {
908 """)
909 for module in self.soong.modules:
910 print(" '%(name)s': new Module(%(deps)s)," % {
911 "name": module,
912 "deps": json.dumps(self.soong.deps[module]),
913 })
914 print("""
915 };
916 </script>
Joe Onorato02fb89a2020-06-27 00:10:23 -0700917
Joe Onorato8ade9b22020-07-20 23:19:43 -0700918 """)
Joe Onorato02fb89a2020-06-27 00:10:23 -0700919
Joe Onorato8ade9b22020-07-20 23:19:43 -0700920 print("""
921 </div> <!-- id=tables -->
922 <div id="details">
923 <div style="text-align: right;">
924 <a href="javascript:close_details();">
925 <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>
926 </a>
927 </div>
928 <div id="details_data"></div>
Joe Onorato02fb89a2020-06-27 00:10:23 -0700929 </div>
Joe Onorato8ade9b22020-07-20 23:19:43 -0700930 </body>
931 </html>
932 """)
933
934 def traverse_ready_makefiles(self, summary, makefiles):
Joe Onorato8ade9b22020-07-20 23:19:43 -0700935 return [Analysis(makefile.filename, []) for makefile in makefiles
Joe Onorato31986072020-08-10 09:58:49 -0700936 if clean_and_only_blocked_by_clean(self.soong, self.all_makefiles, makefile)]
Joe Onorato8ade9b22020-07-20 23:19:43 -0700937
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
Joe Onorato31986072020-08-10 09:58:49 -0700987class CsvProcessor(object):
988 def __init__(self, args, soong, all_makefiles):
989 self.args = args
990 self.soong = soong
991 self.all_makefiles = all_makefiles
992
993 def execute(self):
994 csvout = csv.writer(sys.stdout)
995
996 # Title row
997 row = ["Filename", "Module", "Partitions", "Easy", "Unblocked Clean", "Unblocked",
998 "Blocked", "Clean"]
999 for analyzer in ANALYZERS:
1000 row.append(analyzer.title)
1001 csvout.writerow(row)
1002
1003 # Makefile & module data
1004 for filename in sorted(self.all_makefiles.keys()):
1005 makefile = self.all_makefiles[filename]
1006 for module in self.soong.reverse_makefiles[filename]:
1007 row = [filename, module]
1008 # Partitions
1009 row.append(";".join(sorted(set([get_partition_from_installed(HOST_OUT_ROOT, PRODUCT_OUT,
1010 installed)
1011 for installed
1012 in self.soong.reverse_installed.get(module, [])]))))
1013 # Easy
1014 row.append(1
1015 if clean_and_only_blocked_by_clean(self.soong, self.all_makefiles, makefile)
1016 else "")
1017 # Unblocked Clean
1018 row.append(1
1019 if (self.soong.contains_unblocked_modules(makefile.filename) and is_clean(makefile))
1020 else "")
1021 # Unblocked
1022 row.append(1 if self.soong.contains_unblocked_modules(makefile.filename) else "")
1023 # Blocked
1024 row.append(1 if self.soong.contains_blocked_modules(makefile.filename) else "")
1025 # Clean
1026 row.append(1 if is_clean(makefile) else "")
1027 # Analysis
1028 for analyzer in ANALYZERS:
1029 row.append(1 if makefile.analyses.get(analyzer) else "")
1030 # Write results
1031 csvout.writerow(row)
1032
Joe Onorato02fb89a2020-06-27 00:10:23 -07001033if __name__ == "__main__":
1034 main()
1035