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 311# get all modules in $(PRODUCT_PACKAGE) and the corresponding deps 312def get_module_product_packages_plus_deps(initial_modules, result, soong_data): 313 for module in initial_modules: 314 if module in result: 315 continue 316 result.add(module) 317 if module in soong_data.deps: 318 get_module_product_packages_plus_deps(soong_data.deps[module], result, soong_data) 319 320def main(): 321 parser = argparse.ArgumentParser(description="Info about remaining Android.mk files.") 322 parser.add_argument("--device", type=str, required=True, 323 help="TARGET_DEVICE") 324 parser.add_argument("--product-packages", type=argparse.FileType('r'), 325 default=None, 326 help="PRODUCT_PACKAGES") 327 parser.add_argument("--title", type=str, 328 help="page title") 329 parser.add_argument("--codesearch", type=str, 330 default="https://cs.android.com/android/platform/superproject/+/master:", 331 help="page title") 332 parser.add_argument("--out-dir", type=str, 333 default=None, 334 help="Equivalent of $OUT_DIR, which will also be checked if" 335 + " --out-dir is unset. If neither is set, default is" 336 + " 'out'.") 337 parser.add_argument("--mode", type=str, 338 default="html", 339 help="output format: csv or html") 340 341 args = parser.parse_args() 342 343 # Guess out directory name 344 if not args.out_dir: 345 args.out_dir = os.getenv("OUT_DIR", "out") 346 while args.out_dir.endswith("/") and len(args.out_dir) > 1: 347 args.out_dir = args.out_dir[:-1] 348 349 TARGET_DEVICE = args.device 350 global HOST_OUT_ROOT 351 HOST_OUT_ROOT = args.out_dir + "/host" 352 global PRODUCT_OUT 353 PRODUCT_OUT = args.out_dir + "/target/product/%s" % TARGET_DEVICE 354 355 # Read target information 356 # TODO: Pull from configurable location. This is also slightly different because it's 357 # only a single build, where as the tree scanning we do below is all Android.mk files. 358 with open("%s/obj/PACKAGING/soong_conversion_intermediates/soong_conv_data" 359 % PRODUCT_OUT, "r", errors="ignore") as csvfile: 360 soong = SoongData(csv.reader(csvfile)) 361 362 # Read the makefiles 363 all_makefiles = dict() 364 for filename, modules in soong.reverse_makefiles.items(): 365 if filename.startswith(args.out_dir + "/"): 366 continue 367 all_makefiles[filename] = Makefile(filename) 368 369 # Get all the modules in $(PRODUCT_PACKAGES) and the correspoding deps 370 product_package_modules_plus_deps = set() 371 if args.product_packages: 372 product_package_top_modules = args.product_packages.read().strip().split('\n') 373 get_module_product_packages_plus_deps(product_package_top_modules, product_package_modules_plus_deps, soong) 374 375 if args.mode == "html": 376 HtmlProcessor(args=args, soong=soong, all_makefiles=all_makefiles, 377 product_packages_modules=product_package_modules_plus_deps).execute() 378 elif args.mode == "csv": 379 CsvProcessor(args=args, soong=soong, all_makefiles=all_makefiles, 380 product_packages_modules=product_package_modules_plus_deps).execute() 381 382class HtmlProcessor(object): 383 def __init__(self, args, soong, all_makefiles, product_packages_modules): 384 self.args = args 385 self.soong = soong 386 self.all_makefiles = all_makefiles 387 self.product_packages_modules = product_packages_modules 388 self.annotations = Annotations() 389 390 def execute(self): 391 if self.args.title: 392 page_title = self.args.title 393 else: 394 page_title = "Remaining Android.mk files" 395 396 # Which modules are installed where 397 modules_by_partition = dict() 398 partitions = set() 399 for installed, module in self.soong.installed.items(): 400 if len(self.product_packages_modules) > 0 and module not in self.product_packages_modules: 401 continue 402 partition = get_partition_from_installed(HOST_OUT_ROOT, PRODUCT_OUT, installed) 403 modules_by_partition.setdefault(partition, []).append(module) 404 partitions.add(partition) 405 406 print(""" 407 <html> 408 <head> 409 <title>%(page_title)s</title> 410 <style type="text/css"> 411 body, table { 412 font-family: Roboto, sans-serif; 413 font-size: 9pt; 414 } 415 body { 416 margin: 0; 417 padding: 0; 418 display: flex; 419 flex-direction: column; 420 height: 100vh; 421 } 422 #container { 423 flex: 1; 424 display: flex; 425 flex-direction: row; 426 overflow: hidden; 427 } 428 #tables { 429 padding: 0 20px 40px 20px; 430 overflow: scroll; 431 flex: 2 2 600px; 432 } 433 #details { 434 display: none; 435 overflow: scroll; 436 flex: 1 1 650px; 437 padding: 0 20px 0 20px; 438 } 439 h1 { 440 margin: 16px 0 16px 20px; 441 } 442 h2 { 443 margin: 12px 0 4px 0; 444 } 445 .RowTitle { 446 text-align: left; 447 width: 200px; 448 min-width: 200px; 449 } 450 .Count { 451 text-align: center; 452 width: 60px; 453 min-width: 60px; 454 max-width: 60px; 455 } 456 th.Clean, 457 th.Unblocked { 458 background-color: #1e8e3e; 459 } 460 th.Blocked { 461 background-color: #d93025; 462 } 463 th.Warning { 464 background-color: #e8710a; 465 } 466 th { 467 background-color: #1a73e8; 468 color: white; 469 font-weight: bold; 470 } 471 td.Unblocked { 472 background-color: #81c995; 473 } 474 td.Blocked { 475 background-color: #f28b82; 476 } 477 td, th { 478 padding: 2px 4px; 479 border-right: 2px solid white; 480 } 481 tr.TotalRow td { 482 background-color: white; 483 border-right-color: white; 484 } 485 tr.AospDir td { 486 background-color: #e6f4ea; 487 border-right-color: #e6f4ea; 488 } 489 tr.GoogleDir td { 490 background-color: #e8f0fe; 491 border-right-color: #e8f0fe; 492 } 493 tr.PartnerDir td { 494 background-color: #fce8e6; 495 border-right-color: #fce8e6; 496 } 497 table { 498 border-spacing: 0; 499 border-collapse: collapse; 500 } 501 div.Makefile { 502 margin: 12px 0 0 0; 503 } 504 div.Makefile:first { 505 margin-top: 0; 506 } 507 div.FileModules { 508 padding: 4px 0 0 20px; 509 } 510 td.LineNo { 511 vertical-align: baseline; 512 padding: 6px 0 0 20px; 513 width: 50px; 514 vertical-align: baseline; 515 } 516 td.LineText { 517 vertical-align: baseline; 518 font-family: monospace; 519 padding: 6px 0 0 0; 520 } 521 a.CsLink { 522 font-family: monospace; 523 } 524 div.Help { 525 width: 550px; 526 } 527 table.HelpColumns tr { 528 border-bottom: 2px solid white; 529 } 530 .ModuleName { 531 vertical-align: baseline; 532 padding: 6px 0 0 20px; 533 width: 275px; 534 } 535 .ModuleDeps { 536 vertical-align: baseline; 537 padding: 6px 0 0 0; 538 } 539 table#Modules td { 540 vertical-align: baseline; 541 } 542 tr.Alt { 543 background-color: #ececec; 544 } 545 tr.Alt td { 546 border-right-color: #ececec; 547 } 548 .AnalysisCol { 549 width: 300px; 550 padding: 2px; 551 line-height: 21px; 552 } 553 .Analysis { 554 color: white; 555 font-weight: bold; 556 background-color: #e8710a; 557 border-radius: 6px; 558 margin: 4px; 559 padding: 2px 6px; 560 white-space: nowrap; 561 } 562 .Nav { 563 margin: 4px 0 16px 20px; 564 } 565 .NavSpacer { 566 display: inline-block; 567 width: 6px; 568 } 569 .ModuleDetails { 570 margin-top: 20px; 571 } 572 .ModuleDetails td { 573 vertical-align: baseline; 574 } 575 </style> 576 </head> 577 <body> 578 <h1>%(page_title)s</h1> 579 <div class="Nav"> 580 <a href='#help'>Help</a> 581 <span class='NavSpacer'></span><span class='NavSpacer'> </span> 582 Partitions: 583 """ % { 584 "page_title": page_title, 585 }) 586 for partition in sorted(partitions): 587 print("<a href='#partition_%s'>%s</a><span class='NavSpacer'></span>" % (partition, partition)) 588 589 print(""" 590 <span class='NavSpacer'></span><span class='NavSpacer'> </span> 591 <a href='#summary'>Overall Summary</a> 592 </div> 593 <div id="container"> 594 <div id="tables"> 595 <a name="help"></a> 596 <div class="Help"> 597 <p> 598 This page analyzes the remaining Android.mk files in the Android Source tree. 599 <p> 600 The modules are first broken down by which of the device filesystem partitions 601 they are installed to. This also includes host tools and testcases which don't 602 actually reside in their own partition but convenitely group together. 603 <p> 604 The makefiles for each partition are further are grouped into a set of directories 605 aritrarily picked to break down the problem size by owners. 606 <ul style="width: 300px"> 607 <li style="background-color: #e6f4ea">AOSP directories are colored green.</li> 608 <li style="background-color: #e8f0fe">Google directories are colored blue.</li> 609 <li style="background-color: #fce8e6">Other partner directories are colored red.</li> 610 </ul> 611 Each of the makefiles are scanned for issues that are likely to come up during 612 conversion to soong. Clicking the number in each cell shows additional information, 613 including the line that triggered the warning. 614 <p> 615 <table class="HelpColumns"> 616 <tr> 617 <th>Total</th> 618 <td>The total number of makefiles in this each directory.</td> 619 </tr> 620 <tr> 621 <th class="Clean">Easy</th> 622 <td>The number of makefiles that have no warnings themselves, and also 623 none of their dependencies have warnings either.</td> 624 </tr> 625 <tr> 626 <th class="Clean">Unblocked Clean</th> 627 <td>The number of makefiles that are both Unblocked and Clean.</td> 628 </tr> 629 630 <tr> 631 <th class="Unblocked">Unblocked</th> 632 <td>Makefiles containing one or more modules that don't have any 633 additional dependencies pending before conversion.</td> 634 </tr> 635 <tr> 636 <th class="Blocked">Blocked</th> 637 <td>Makefiles containiong one or more modules which <i>do</i> have 638 additional prerequesite depenedencies that are not yet converted.</td> 639 </tr> 640 <tr> 641 <th class="Clean">Clean</th> 642 <td>The number of makefiles that have none of the following warnings.</td> 643 </tr> 644 <tr> 645 <th class="Warning">ifeq / ifneq</th> 646 <td>Makefiles that use <code>ifeq</code> or <code>ifneq</code>. i.e. 647 conditionals.</td> 648 </tr> 649 <tr> 650 <th class="Warning">Wacky Includes</th> 651 <td>Makefiles that <code>include</code> files other than the standard build-system 652 defined template and macros.</td> 653 </tr> 654 <tr> 655 <th class="Warning">Calls base_rules</th> 656 <td>Makefiles that include base_rules.mk directly.</td> 657 </tr> 658 <tr> 659 <th class="Warning">Calls define</th> 660 <td>Makefiles that define their own macros. Some of these are easy to convert 661 to soong <code>defaults</code>, but others are complex.</td> 662 </tr> 663 <tr> 664 <th class="Warning">Has ../</th> 665 <td>Makefiles containing the string "../" outside of a comment. These likely 666 access files outside their directories.</td> 667 </tr> 668 <tr> 669 <th class="Warning">dist-for-goals</th> 670 <td>Makefiles that call <code>dist-for-goals</code> directly.</td> 671 </tr> 672 <tr> 673 <th class="Warning">.PHONY</th> 674 <td>Makefiles that declare .PHONY targets.</td> 675 </tr> 676 <tr> 677 <th class="Warning">renderscript</th> 678 <td>Makefiles defining targets that depend on <code>.rscript</code> source files.</td> 679 </tr> 680 <tr> 681 <th class="Warning">vts src</th> 682 <td>Makefiles defining targets that depend on <code>.vts</code> source files.</td> 683 </tr> 684 <tr> 685 <th class="Warning">COPY_HEADERS</th> 686 <td>Makefiles using LOCAL_COPY_HEADERS.</td> 687 </tr> 688 </table> 689 <p> 690 Following the list of directories is a list of the modules that are installed on 691 each partition. Potential issues from their makefiles are listed, as well as the 692 total number of dependencies (both blocking that module and blocked by that module) 693 and the list of direct dependencies. Note: The number is the number of all transitive 694 dependencies and the list of modules is only the direct dependencies. 695 </div> 696 """) 697 698 overall_summary = Summary() 699 700 # For each partition 701 for partition in sorted(partitions): 702 modules = modules_by_partition[partition] 703 704 makefiles = set(itertools.chain.from_iterable( 705 [self.soong.makefiles[module] for module in modules])) 706 707 # Read makefiles 708 summary = Summary() 709 for filename in makefiles: 710 makefile = self.all_makefiles.get(filename) 711 if makefile: 712 summary.Add(makefile) 713 overall_summary.Add(makefile) 714 715 # Categorize directories by who is responsible 716 aosp_dirs = [] 717 google_dirs = [] 718 partner_dirs = [] 719 for dirname in sorted(summary.directories.keys()): 720 if is_aosp(dirname): 721 aosp_dirs.append(dirname) 722 elif is_google(dirname): 723 google_dirs.append(dirname) 724 else: 725 partner_dirs.append(dirname) 726 727 print_analysis_header("partition_" + partition, partition) 728 729 for dirgroup, rowclass in [(aosp_dirs, "AospDir"), 730 (google_dirs, "GoogleDir"), 731 (partner_dirs, "PartnerDir"),]: 732 for dirname in dirgroup: 733 self.print_analysis_row(summary, modules, 734 dirname, rowclass, summary.directories[dirname]) 735 736 self.print_analysis_row(summary, modules, 737 "Total", "TotalRow", 738 set(itertools.chain.from_iterable(summary.directories.values()))) 739 print(""" 740 </table> 741 """) 742 743 module_details = [(count_deps(self.soong.deps, m, []), 744 -count_deps(self.soong.reverse_deps, m, []), m) 745 for m in modules] 746 module_details.sort() 747 module_details = [m[2] for m in module_details] 748 print(""" 749 <table class="ModuleDetails">""") 750 print("<tr>") 751 print(" <th>Module Name</th>") 752 print(" <th>Issues</th>") 753 print(" <th colspan='2'>Blocked By</th>") 754 print(" <th colspan='2'>Blocking</th>") 755 print("</tr>") 756 altRow = True 757 for module in module_details: 758 analyses = set() 759 for filename in self.soong.makefiles[module]: 760 makefile = summary.makefiles.get(filename) 761 if makefile: 762 for analyzer, analysis in makefile.analyses.items(): 763 if analysis: 764 analyses.add(analyzer.title) 765 766 altRow = not altRow 767 print("<tr class='%s'>" % ("Alt" if altRow else "",)) 768 print(" <td><a name='module_%s'></a>%s</td>" % (module, module)) 769 print(" <td class='AnalysisCol'>%s</td>" % " ".join(["<span class='Analysis'>%s</span>" % title 770 for title in analyses])) 771 print(" <td>%s</td>" % count_deps(self.soong.deps, module, [])) 772 print(" <td>%s</td>" % format_module_list(self.soong.deps.get(module, []))) 773 print(" <td>%s</td>" % count_deps(self.soong.reverse_deps, module, [])) 774 print(" <td>%s</td>" % format_module_list(self.soong.reverse_deps.get(module, []))) 775 print("</tr>") 776 print("""</table>""") 777 778 print_analysis_header("summary", "Overall Summary") 779 780 modules = [module for installed, module in self.soong.installed.items()] 781 self.print_analysis_row(overall_summary, modules, 782 "All Makefiles", "TotalRow", 783 set(itertools.chain.from_iterable(overall_summary.directories.values()))) 784 print(""" 785 </table> 786 """) 787 788 print(""" 789 <script type="text/javascript"> 790 function close_details() { 791 document.getElementById('details').style.display = 'none'; 792 } 793 794 class LineMatch { 795 constructor(lineno, text) { 796 this.lineno = lineno; 797 this.text = text; 798 } 799 } 800 801 class Analysis { 802 constructor(filename, modules, line_matches) { 803 this.filename = filename; 804 this.modules = modules; 805 this.line_matches = line_matches; 806 } 807 } 808 809 class Module { 810 constructor(deps) { 811 this.deps = deps; 812 } 813 } 814 815 function make_module_link(module) { 816 var a = document.createElement('a'); 817 a.className = 'ModuleLink'; 818 a.innerText = module; 819 a.href = '#module_' + module; 820 return a; 821 } 822 823 function update_details(id) { 824 document.getElementById('details').style.display = 'block'; 825 826 var analyses = ANALYSIS[id]; 827 828 var details = document.getElementById("details_data"); 829 while (details.firstChild) { 830 details.removeChild(details.firstChild); 831 } 832 833 for (var i=0; i<analyses.length; i++) { 834 var analysis = analyses[i]; 835 836 var makefileDiv = document.createElement('div'); 837 makefileDiv.className = 'Makefile'; 838 details.appendChild(makefileDiv); 839 840 var fileA = document.createElement('a'); 841 makefileDiv.appendChild(fileA); 842 fileA.className = 'CsLink'; 843 fileA.href = '%(codesearch)s' + analysis.filename; 844 fileA.innerText = analysis.filename; 845 fileA.target = "_blank"; 846 847 if (analysis.modules.length > 0) { 848 var moduleTable = document.createElement('table'); 849 details.appendChild(moduleTable); 850 851 for (var j=0; j<analysis.modules.length; j++) { 852 var moduleRow = document.createElement('tr'); 853 moduleTable.appendChild(moduleRow); 854 855 var moduleNameCell = document.createElement('td'); 856 moduleRow.appendChild(moduleNameCell); 857 moduleNameCell.className = 'ModuleName'; 858 moduleNameCell.appendChild(make_module_link(analysis.modules[j])); 859 860 var moduleData = MODULE_DATA[analysis.modules[j]]; 861 console.log(moduleData); 862 863 var depCell = document.createElement('td'); 864 moduleRow.appendChild(depCell); 865 866 if (moduleData.deps.length == 0) { 867 depCell.className = 'ModuleDeps Unblocked'; 868 depCell.innerText = 'UNBLOCKED'; 869 } else { 870 depCell.className = 'ModuleDeps Blocked'; 871 872 for (var k=0; k<moduleData.deps.length; k++) { 873 depCell.appendChild(make_module_link(moduleData.deps[k])); 874 depCell.appendChild(document.createElement('br')); 875 } 876 } 877 } 878 } 879 880 if (analysis.line_matches.length > 0) { 881 var lineTable = document.createElement('table'); 882 details.appendChild(lineTable); 883 884 for (var j=0; j<analysis.line_matches.length; j++) { 885 var line_match = analysis.line_matches[j]; 886 887 var lineRow = document.createElement('tr'); 888 lineTable.appendChild(lineRow); 889 890 var linenoCell = document.createElement('td'); 891 lineRow.appendChild(linenoCell); 892 linenoCell.className = 'LineNo'; 893 894 var linenoA = document.createElement('a'); 895 linenoCell.appendChild(linenoA); 896 linenoA.className = 'CsLink'; 897 linenoA.href = '%(codesearch)s' + analysis.filename 898 + ';l=' + line_match.lineno; 899 linenoA.innerText = line_match.lineno; 900 linenoA.target = "_blank"; 901 902 var textCell = document.createElement('td'); 903 lineRow.appendChild(textCell); 904 textCell.className = 'LineText'; 905 textCell.innerText = line_match.text; 906 } 907 } 908 } 909 } 910 911 var ANALYSIS = [ 912 """ % { 913 "codesearch": self.args.codesearch, 914 }) 915 for entry, mods in self.annotations.entries: 916 print(" [") 917 for analysis in entry: 918 print(" new Analysis('%(filename)s', %(modules)s, [%(line_matches)s])," % { 919 "filename": analysis.filename, 920 #"modules": json.dumps([m for m in mods if m in filename in self.soong.makefiles[m]]), 921 "modules": json.dumps( 922 [m for m in self.soong.reverse_makefiles[analysis.filename] if m in mods]), 923 "line_matches": ", ".join([ 924 "new LineMatch(%d, %s)" % (lineno, json.dumps(text)) 925 for lineno, text in analysis.line_matches]), 926 }) 927 print(" ],") 928 print(""" 929 ]; 930 var MODULE_DATA = { 931 """) 932 for module in self.soong.modules: 933 print(" '%(name)s': new Module(%(deps)s)," % { 934 "name": module, 935 "deps": json.dumps(self.soong.deps[module]), 936 }) 937 print(""" 938 }; 939 </script> 940 941 """) 942 943 print(""" 944 </div> <!-- id=tables --> 945 <div id="details"> 946 <div style="text-align: right;"> 947 <a href="javascript:close_details();"> 948 <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> 949 </a> 950 </div> 951 <div id="details_data"></div> 952 </div> 953 </body> 954 </html> 955 """) 956 957 def traverse_ready_makefiles(self, summary, makefiles): 958 return [Analysis(makefile.filename, []) for makefile in makefiles 959 if clean_and_only_blocked_by_clean(self.soong, self.all_makefiles, makefile)] 960 961 def print_analysis_row(self, summary, modules, rowtitle, rowclass, makefiles): 962 all_makefiles = [Analysis(makefile.filename, []) for makefile in makefiles] 963 clean_makefiles = [Analysis(makefile.filename, []) for makefile in makefiles 964 if is_clean(makefile)] 965 easy_makefiles = self.traverse_ready_makefiles(summary, makefiles) 966 unblocked_clean_makefiles = [Analysis(makefile.filename, []) for makefile in makefiles 967 if (self.soong.contains_unblocked_modules(makefile.filename) 968 and is_clean(makefile))] 969 unblocked_makefiles = [Analysis(makefile.filename, []) for makefile in makefiles 970 if self.soong.contains_unblocked_modules(makefile.filename)] 971 blocked_makefiles = [Analysis(makefile.filename, []) for makefile in makefiles 972 if self.soong.contains_blocked_modules(makefile.filename)] 973 974 print(""" 975 <tr class="%(rowclass)s"> 976 <td class="RowTitle">%(rowtitle)s</td> 977 <td class="Count">%(makefiles)s</td> 978 <td class="Count">%(easy)s</td> 979 <td class="Count">%(unblocked_clean)s</td> 980 <td class="Count">%(unblocked)s</td> 981 <td class="Count">%(blocked)s</td> 982 <td class="Count">%(clean)s</td> 983 """ % { 984 "rowclass": rowclass, 985 "rowtitle": rowtitle, 986 "makefiles": self.make_annotation_link(all_makefiles, modules), 987 "unblocked": self.make_annotation_link(unblocked_makefiles, modules), 988 "blocked": self.make_annotation_link(blocked_makefiles, modules), 989 "clean": self.make_annotation_link(clean_makefiles, modules), 990 "unblocked_clean": self.make_annotation_link(unblocked_clean_makefiles, modules), 991 "easy": self.make_annotation_link(easy_makefiles, modules), 992 }) 993 994 for analyzer in ANALYZERS: 995 analyses = [m.analyses.get(analyzer) for m in makefiles if m.analyses.get(analyzer)] 996 print("""<td class="Count">%s</td>""" 997 % self.make_annotation_link(analyses, modules)) 998 999 print(" </tr>") 1000 1001 def make_annotation_link(self, analysis, modules): 1002 if analysis: 1003 return "<a href='javascript:update_details(%d)'>%s</a>" % ( 1004 self.annotations.Add(analysis, modules), 1005 len(analysis) 1006 ) 1007 else: 1008 return ""; 1009 1010class CsvProcessor(object): 1011 def __init__(self, args, soong, all_makefiles, product_packages_modules): 1012 self.args = args 1013 self.soong = soong 1014 self.all_makefiles = all_makefiles 1015 self.product_packages_modules = product_packages_modules 1016 1017 def execute(self): 1018 csvout = csv.writer(sys.stdout) 1019 1020 # Title row 1021 row = ["Filename", "Module", "Partitions", "Easy", "Unblocked Clean", "Unblocked", 1022 "Blocked", "Clean"] 1023 for analyzer in ANALYZERS: 1024 row.append(analyzer.title) 1025 csvout.writerow(row) 1026 1027 # Makefile & module data 1028 for filename in sorted(self.all_makefiles.keys()): 1029 makefile = self.all_makefiles[filename] 1030 for module in self.soong.reverse_makefiles[filename]: 1031 if len(self.product_packages_modules) > 0 and module not in self.product_packages_modules: 1032 continue 1033 row = [filename, module] 1034 # Partitions 1035 row.append(";".join(sorted(set([get_partition_from_installed(HOST_OUT_ROOT, PRODUCT_OUT, 1036 installed) 1037 for installed 1038 in self.soong.reverse_installed.get(module, [])])))) 1039 # Easy 1040 row.append(1 1041 if clean_and_only_blocked_by_clean(self.soong, self.all_makefiles, makefile) 1042 else "") 1043 # Unblocked Clean 1044 row.append(1 1045 if (self.soong.contains_unblocked_modules(makefile.filename) and is_clean(makefile)) 1046 else "") 1047 # Unblocked 1048 row.append(1 if self.soong.contains_unblocked_modules(makefile.filename) else "") 1049 # Blocked 1050 row.append(1 if self.soong.contains_blocked_modules(makefile.filename) else "") 1051 # Clean 1052 row.append(1 if is_clean(makefile) else "") 1053 # Analysis 1054 for analyzer in ANALYZERS: 1055 row.append(1 if makefile.analyses.get(analyzer) else "") 1056 # Write results 1057 csvout.writerow(row) 1058 1059if __name__ == "__main__": 1060 main() 1061 1062