1#!/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-​goals", lambda line: "dist-for-goals" in line), 107 Analyzer(".PHONY", lambda line: ".PHONY" in line), 108 Analyzer("render-​script", lambda line: ".rscript" in line), 109 Analyzer("vts src", lambda line: ".vts" in line), 110 Analyzer("COPY_​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 893