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 is_clean(makefile): 172 for analysis in makefile.analyses.values(): 173 if analysis: 174 return False 175 return True 176 177def 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 189class 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() 210 self.reverse_installed = dict() 211 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 228 self.reverse_installed.setdefault(module, []).append(f) 229 230 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 240 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 252def 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 266OTHER_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 290def 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 311def 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'.") 325 parser.add_argument("--mode", type=str, 326 default="html", 327 help="output format: csv or html") 328 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 338 global HOST_OUT_ROOT 339 HOST_OUT_ROOT = args.out_dir + "/host" 340 global PRODUCT_OUT 341 PRODUCT_OUT = args.out_dir + "/target/product/%s" % TARGET_DEVICE 342 343 # 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 350 # 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) 356 357 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() 361 362class 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() 368 369 def execute(self): 370 if self.args.title: 371 page_title = self.args.title 372 else: 373 page_title = "Remaining Android.mk files" 374 375 # 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) 382 383 print(""" 384 <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> 673 """) 674 675 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) 688 if makefile: 689 summary.Add(makefile) 690 overall_summary.Add(makefile) 691 692 # 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>") 732 print("</tr>") 733 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) 742 743 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>""") 754 755 print_analysis_header("summary", "Overall Summary") 756 757 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 """) 764 765 print(""" 766 <script type="text/javascript"> 767 function close_details() { 768 document.getElementById('details').style.display = 'none'; 769 } 770 771 class LineMatch { 772 constructor(lineno, text) { 773 this.lineno = lineno; 774 this.text = text; 775 } 776 } 777 778 class Analysis { 779 constructor(filename, modules, line_matches) { 780 this.filename = filename; 781 this.modules = modules; 782 this.line_matches = line_matches; 783 } 784 } 785 786 class Module { 787 constructor(deps) { 788 this.deps = deps; 789 } 790 } 791 792 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 } 799 800 function update_details(id) { 801 document.getElementById('details').style.display = 'block'; 802 803 var analyses = ANALYSIS[id]; 804 805 var details = document.getElementById("details_data"); 806 while (details.firstChild) { 807 details.removeChild(details.firstChild); 808 } 809 810 for (var i=0; i<analyses.length; i++) { 811 var analysis = analyses[i]; 812 813 var makefileDiv = document.createElement('div'); 814 makefileDiv.className = 'Makefile'; 815 details.appendChild(makefileDiv); 816 817 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 } 853 } 854 } 855 } 856 857 if (analysis.line_matches.length > 0) { 858 var lineTable = document.createElement('table'); 859 details.appendChild(lineTable); 860 861 for (var j=0; j<analysis.line_matches.length; j++) { 862 var line_match = analysis.line_matches[j]; 863 864 var lineRow = document.createElement('tr'); 865 lineTable.appendChild(lineRow); 866 867 var linenoCell = document.createElement('td'); 868 lineRow.appendChild(linenoCell); 869 linenoCell.className = 'LineNo'; 870 871 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"; 878 879 var textCell = document.createElement('td'); 880 lineRow.appendChild(textCell); 881 textCell.className = 'LineText'; 882 textCell.innerText = line_match.text; 883 } 884 } 885 } 886 } 887 888 var ANALYSIS = [ 889 """ % { 890 "codesearch": self.args.codesearch, 891 }) 892 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> 917 918 """) 919 920 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> 929 </div> 930 </body> 931 </html> 932 """) 933 934 def traverse_ready_makefiles(self, summary, makefiles): 935 return [Analysis(makefile.filename, []) for makefile in makefiles 936 if clean_and_only_blocked_by_clean(self.soong, self.all_makefiles, 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 ""; 986 987class 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 1033if __name__ == "__main__": 1034 main() 1035 1036