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