| #!/usr/bin/env python3 |
| |
| """ |
| Command to print info about makefiles remaining to be converted to soong. |
| |
| See usage / argument parsing below for commandline options. |
| """ |
| |
| import argparse |
| import csv |
| import itertools |
| import json |
| import os |
| import re |
| import sys |
| |
| DIRECTORY_PATTERNS = [x.split("/") for x in ( |
| "device/*", |
| "frameworks/*", |
| "hardware/*", |
| "packages/*", |
| "vendor/*", |
| "*", |
| )] |
| |
| def match_directory_group(pattern, filename): |
| match = [] |
| filename = filename.split("/") |
| if len(filename) < len(pattern): |
| return None |
| for i in range(len(pattern)): |
| pattern_segment = pattern[i] |
| filename_segment = filename[i] |
| if pattern_segment == "*" or pattern_segment == filename_segment: |
| match.append(filename_segment) |
| else: |
| return None |
| if match: |
| return os.path.sep.join(match) |
| else: |
| return None |
| |
| def directory_group(filename): |
| for pattern in DIRECTORY_PATTERNS: |
| match = match_directory_group(pattern, filename) |
| if match: |
| return match |
| return os.path.dirname(filename) |
| |
| class Analysis(object): |
| def __init__(self, filename, line_matches): |
| self.filename = filename; |
| self.line_matches = line_matches |
| |
| def analyze_lines(filename, lines, func): |
| line_matches = [] |
| for i in range(len(lines)): |
| line = lines[i] |
| stripped = line.strip() |
| if stripped.startswith("#"): |
| continue |
| if func(stripped): |
| line_matches.append((i+1, line)) |
| if line_matches: |
| return Analysis(filename, line_matches); |
| |
| def analyze_has_conditional(line): |
| return (line.startswith("ifeq") or line.startswith("ifneq") |
| or line.startswith("ifdef") or line.startswith("ifndef")) |
| |
| NORMAL_INCLUDES = [re.compile(pattern) for pattern in ( |
| "include \$+\(CLEAR_VARS\)", # These are in defines which are tagged separately |
| "include \$+\(BUILD_.*\)", |
| "include \$\(call first-makefiles-under, *\$\(LOCAL_PATH\)\)", |
| "include \$\(call all-subdir-makefiles\)", |
| "include \$\(all-subdir-makefiles\)", |
| "include \$\(call all-makefiles-under, *\$\(LOCAL_PATH\)\)", |
| "include \$\(call all-makefiles-under, *\$\(call my-dir\).*\)", |
| "include \$\(BUILD_SYSTEM\)/base_rules.mk", # called out separately |
| "include \$\(call all-named-subdir-makefiles,.*\)", |
| "include \$\(subdirs\)", |
| )] |
| def analyze_has_wacky_include(line): |
| if not (line.startswith("include") or line.startswith("-include") |
| or line.startswith("sinclude")): |
| return False |
| for matcher in NORMAL_INCLUDES: |
| if matcher.fullmatch(line): |
| return False |
| return True |
| |
| BASE_RULES_RE = re.compile("include \$\(BUILD_SYSTEM\)/base_rules.mk") |
| |
| class Analyzer(object): |
| def __init__(self, title, func): |
| self.title = title; |
| self.func = func |
| |
| |
| ANALYZERS = ( |
| Analyzer("ifeq / ifneq", analyze_has_conditional), |
| Analyzer("Wacky Includes", analyze_has_wacky_include), |
| Analyzer("Calls base_rules", lambda line: BASE_RULES_RE.fullmatch(line)), |
| Analyzer("Calls define", lambda line: line.startswith("define ")), |
| Analyzer("Has ../", lambda line: "../" in line), |
| Analyzer("dist-for-​goals", lambda line: "dist-for-goals" in line), |
| Analyzer(".PHONY", lambda line: ".PHONY" in line), |
| Analyzer("render-​script", lambda line: ".rscript" in line), |
| Analyzer("vts src", lambda line: ".vts" in line), |
| Analyzer("COPY_​HEADERS", lambda line: "LOCAL_COPY_HEADERS" in line), |
| ) |
| |
| class Summary(object): |
| def __init__(self): |
| self.makefiles = dict() |
| self.directories = dict() |
| |
| def Add(self, makefile): |
| self.makefiles[makefile.filename] = makefile |
| self.directories.setdefault(directory_group(makefile.filename), []).append(makefile) |
| |
| class Makefile(object): |
| def __init__(self, filename): |
| self.filename = filename |
| |
| # Analyze the file |
| with open(filename, "r", errors="ignore") as f: |
| try: |
| lines = f.readlines() |
| except UnicodeDecodeError as ex: |
| sys.stderr.write("Filename: %s\n" % filename) |
| raise ex |
| lines = [line.strip() for line in lines] |
| |
| self.analyses = dict([(analyzer, analyze_lines(filename, lines, analyzer.func)) for analyzer |
| in ANALYZERS]) |
| |
| def find_android_mk(): |
| cwd = os.getcwd() |
| for root, dirs, files in os.walk(cwd): |
| for filename in files: |
| if filename == "Android.mk": |
| yield os.path.join(root, filename)[len(cwd) + 1:] |
| for ignore in (".git", ".repo"): |
| if ignore in dirs: |
| dirs.remove(ignore) |
| |
| def is_aosp(dirname): |
| for d in ("device/sample", "hardware/interfaces", "hardware/libhardware", |
| "hardware/ril"): |
| if dirname.startswith(d): |
| return True |
| for d in ("device/", "hardware/", "vendor/"): |
| if dirname.startswith(d): |
| return False |
| return True |
| |
| def is_google(dirname): |
| for d in ("device/google", |
| "hardware/google", |
| "test/sts", |
| "vendor/auto", |
| "vendor/google", |
| "vendor/unbundled_google", |
| "vendor/widevine", |
| "vendor/xts"): |
| if dirname.startswith(d): |
| return True |
| return False |
| |
| def is_clean(makefile): |
| for analysis in makefile.analyses.values(): |
| if analysis: |
| return False |
| return True |
| |
| def clean_and_only_blocked_by_clean(soong, all_makefiles, makefile): |
| if not is_clean(makefile): |
| return False |
| modules = soong.reverse_makefiles[makefile.filename] |
| for module in modules: |
| for dep in soong.transitive_deps(module): |
| for filename in soong.makefiles.get(dep, []): |
| m = all_makefiles.get(filename) |
| if m and not is_clean(m): |
| return False |
| return True |
| |
| class Annotations(object): |
| def __init__(self): |
| self.entries = [] |
| self.count = 0 |
| |
| def Add(self, makefiles, modules): |
| self.entries.append((makefiles, modules)) |
| self.count += 1 |
| return self.count-1 |
| |
| class SoongData(object): |
| def __init__(self, reader): |
| """Read the input file and store the modules and dependency mappings. |
| """ |
| self.problems = dict() |
| self.deps = dict() |
| self.reverse_deps = dict() |
| self.module_types = dict() |
| self.makefiles = dict() |
| self.reverse_makefiles = dict() |
| self.installed = dict() |
| self.reverse_installed = dict() |
| self.modules = set() |
| |
| for (module, module_type, problem, dependencies, makefiles, installed) in reader: |
| self.modules.add(module) |
| makefiles = [f for f in makefiles.strip().split(' ') if f != ""] |
| self.module_types[module] = module_type |
| self.problems[module] = problem |
| self.deps[module] = [d for d in dependencies.strip().split(' ') if d != ""] |
| for dep in self.deps[module]: |
| if not dep in self.reverse_deps: |
| self.reverse_deps[dep] = [] |
| self.reverse_deps[dep].append(module) |
| self.makefiles[module] = makefiles |
| for f in makefiles: |
| self.reverse_makefiles.setdefault(f, []).append(module) |
| for f in installed.strip().split(' '): |
| self.installed[f] = module |
| self.reverse_installed.setdefault(module, []).append(f) |
| |
| def transitive_deps(self, module): |
| results = set() |
| def traverse(module): |
| for dep in self.deps.get(module, []): |
| if not dep in results: |
| results.add(dep) |
| traverse(module) |
| traverse(module) |
| return results |
| |
| def contains_unblocked_modules(self, filename): |
| for m in self.reverse_makefiles[filename]: |
| if len(self.deps[m]) == 0: |
| return True |
| return False |
| |
| def contains_blocked_modules(self, filename): |
| for m in self.reverse_makefiles[filename]: |
| if len(self.deps[m]) > 0: |
| return True |
| return False |
| |
| def count_deps(depsdb, module, seen): |
| """Based on the depsdb, count the number of transitive dependencies. |
| |
| You can pass in an reversed dependency graph to count the number of |
| modules that depend on the module.""" |
| count = 0 |
| seen.append(module) |
| if module in depsdb: |
| for dep in depsdb[module]: |
| if dep in seen: |
| continue |
| count += 1 + count_deps(depsdb, dep, seen) |
| return count |
| |
| OTHER_PARTITON = "_other" |
| HOST_PARTITON = "_host" |
| |
| def get_partition_from_installed(HOST_OUT_ROOT, PRODUCT_OUT, filename): |
| host_prefix = HOST_OUT_ROOT + "/" |
| device_prefix = PRODUCT_OUT + "/" |
| |
| if filename.startswith(host_prefix): |
| return HOST_PARTITON |
| |
| elif filename.startswith(device_prefix): |
| index = filename.find("/", len(device_prefix)) |
| if index < 0: |
| return OTHER_PARTITON |
| return filename[len(device_prefix):index] |
| |
| return OTHER_PARTITON |
| |
| def format_module_link(module): |
| return "<a class='ModuleLink' href='#module_%s'>%s</a>" % (module, module) |
| |
| def format_module_list(modules): |
| return "".join(["<div>%s</div>" % format_module_link(m) for m in modules]) |
| |
| def print_analysis_header(link, title): |
| print(""" |
| <a name="%(link)s"></a> |
| <h2>%(title)s</h2> |
| <table> |
| <tr> |
| <th class="RowTitle">Directory</th> |
| <th class="Count">Total</th> |
| <th class="Count Clean">Easy</th> |
| <th class="Count Clean">Unblocked Clean</th> |
| <th class="Count Unblocked">Unblocked</th> |
| <th class="Count Blocked">Blocked</th> |
| <th class="Count Clean">Clean</th> |
| """ % { |
| "link": link, |
| "title": title |
| }) |
| for analyzer in ANALYZERS: |
| print("""<th class="Count Warning">%s</th>""" % analyzer.title) |
| print(" </tr>") |
| |
| # get all modules in $(PRODUCT_PACKAGE) and the corresponding deps |
| def get_module_product_packages_plus_deps(initial_modules, result, soong_data): |
| for module in initial_modules: |
| if module in result: |
| continue |
| result.add(module) |
| if module in soong_data.deps: |
| get_module_product_packages_plus_deps(soong_data.deps[module], result, soong_data) |
| |
| def main(): |
| parser = argparse.ArgumentParser(description="Info about remaining Android.mk files.") |
| parser.add_argument("--device", type=str, required=True, |
| help="TARGET_DEVICE") |
| parser.add_argument("--product-packages", type=argparse.FileType('r'), |
| default=None, |
| help="PRODUCT_PACKAGES") |
| parser.add_argument("--title", type=str, |
| help="page title") |
| parser.add_argument("--codesearch", type=str, |
| default="https://cs.android.com/android/platform/superproject/+/master:", |
| help="page title") |
| parser.add_argument("--out-dir", type=str, |
| default=None, |
| help="Equivalent of $OUT_DIR, which will also be checked if" |
| + " --out-dir is unset. If neither is set, default is" |
| + " 'out'.") |
| parser.add_argument("--mode", type=str, |
| default="html", |
| help="output format: csv or html") |
| |
| args = parser.parse_args() |
| |
| # Guess out directory name |
| if not args.out_dir: |
| args.out_dir = os.getenv("OUT_DIR", "out") |
| while args.out_dir.endswith("/") and len(args.out_dir) > 1: |
| args.out_dir = args.out_dir[:-1] |
| |
| TARGET_DEVICE = args.device |
| global HOST_OUT_ROOT |
| HOST_OUT_ROOT = args.out_dir + "/host" |
| global PRODUCT_OUT |
| PRODUCT_OUT = args.out_dir + "/target/product/%s" % TARGET_DEVICE |
| |
| # Read target information |
| # TODO: Pull from configurable location. This is also slightly different because it's |
| # only a single build, where as the tree scanning we do below is all Android.mk files. |
| with open("%s/obj/PACKAGING/soong_conversion_intermediates/soong_conv_data" |
| % PRODUCT_OUT, "r", errors="ignore") as csvfile: |
| soong = SoongData(csv.reader(csvfile)) |
| |
| # Read the makefiles |
| all_makefiles = dict() |
| for filename, modules in soong.reverse_makefiles.items(): |
| if filename.startswith(args.out_dir + "/"): |
| continue |
| all_makefiles[filename] = Makefile(filename) |
| |
| # Get all the modules in $(PRODUCT_PACKAGES) and the correspoding deps |
| product_package_modules_plus_deps = set() |
| if args.product_packages: |
| product_package_top_modules = args.product_packages.read().strip().split('\n') |
| get_module_product_packages_plus_deps(product_package_top_modules, product_package_modules_plus_deps, soong) |
| |
| if args.mode == "html": |
| HtmlProcessor(args=args, soong=soong, all_makefiles=all_makefiles, |
| product_packages_modules=product_package_modules_plus_deps).execute() |
| elif args.mode == "csv": |
| CsvProcessor(args=args, soong=soong, all_makefiles=all_makefiles, |
| product_packages_modules=product_package_modules_plus_deps).execute() |
| |
| class HtmlProcessor(object): |
| def __init__(self, args, soong, all_makefiles, product_packages_modules): |
| self.args = args |
| self.soong = soong |
| self.all_makefiles = all_makefiles |
| self.product_packages_modules = product_packages_modules |
| self.annotations = Annotations() |
| |
| def execute(self): |
| if self.args.title: |
| page_title = self.args.title |
| else: |
| page_title = "Remaining Android.mk files" |
| |
| # Which modules are installed where |
| modules_by_partition = dict() |
| partitions = set() |
| for installed, module in self.soong.installed.items(): |
| if len(self.product_packages_modules) > 0 and module not in self.product_packages_modules: |
| continue |
| partition = get_partition_from_installed(HOST_OUT_ROOT, PRODUCT_OUT, installed) |
| modules_by_partition.setdefault(partition, []).append(module) |
| partitions.add(partition) |
| |
| print(""" |
| <html> |
| <head> |
| <title>%(page_title)s</title> |
| <style type="text/css"> |
| body, table { |
| font-family: Roboto, sans-serif; |
| font-size: 9pt; |
| } |
| body { |
| margin: 0; |
| padding: 0; |
| display: flex; |
| flex-direction: column; |
| height: 100vh; |
| } |
| #container { |
| flex: 1; |
| display: flex; |
| flex-direction: row; |
| overflow: hidden; |
| } |
| #tables { |
| padding: 0 20px 40px 20px; |
| overflow: scroll; |
| flex: 2 2 600px; |
| } |
| #details { |
| display: none; |
| overflow: scroll; |
| flex: 1 1 650px; |
| padding: 0 20px 0 20px; |
| } |
| h1 { |
| margin: 16px 0 16px 20px; |
| } |
| h2 { |
| margin: 12px 0 4px 0; |
| } |
| .RowTitle { |
| text-align: left; |
| width: 200px; |
| min-width: 200px; |
| } |
| .Count { |
| text-align: center; |
| width: 60px; |
| min-width: 60px; |
| max-width: 60px; |
| } |
| th.Clean, |
| th.Unblocked { |
| background-color: #1e8e3e; |
| } |
| th.Blocked { |
| background-color: #d93025; |
| } |
| th.Warning { |
| background-color: #e8710a; |
| } |
| th { |
| background-color: #1a73e8; |
| color: white; |
| font-weight: bold; |
| } |
| td.Unblocked { |
| background-color: #81c995; |
| } |
| td.Blocked { |
| background-color: #f28b82; |
| } |
| td, th { |
| padding: 2px 4px; |
| border-right: 2px solid white; |
| } |
| tr.TotalRow td { |
| background-color: white; |
| border-right-color: white; |
| } |
| tr.AospDir td { |
| background-color: #e6f4ea; |
| border-right-color: #e6f4ea; |
| } |
| tr.GoogleDir td { |
| background-color: #e8f0fe; |
| border-right-color: #e8f0fe; |
| } |
| tr.PartnerDir td { |
| background-color: #fce8e6; |
| border-right-color: #fce8e6; |
| } |
| table { |
| border-spacing: 0; |
| border-collapse: collapse; |
| } |
| div.Makefile { |
| margin: 12px 0 0 0; |
| } |
| div.Makefile:first { |
| margin-top: 0; |
| } |
| div.FileModules { |
| padding: 4px 0 0 20px; |
| } |
| td.LineNo { |
| vertical-align: baseline; |
| padding: 6px 0 0 20px; |
| width: 50px; |
| vertical-align: baseline; |
| } |
| td.LineText { |
| vertical-align: baseline; |
| font-family: monospace; |
| padding: 6px 0 0 0; |
| } |
| a.CsLink { |
| font-family: monospace; |
| } |
| div.Help { |
| width: 550px; |
| } |
| table.HelpColumns tr { |
| border-bottom: 2px solid white; |
| } |
| .ModuleName { |
| vertical-align: baseline; |
| padding: 6px 0 0 20px; |
| width: 275px; |
| } |
| .ModuleDeps { |
| vertical-align: baseline; |
| padding: 6px 0 0 0; |
| } |
| table#Modules td { |
| vertical-align: baseline; |
| } |
| tr.Alt { |
| background-color: #ececec; |
| } |
| tr.Alt td { |
| border-right-color: #ececec; |
| } |
| .AnalysisCol { |
| width: 300px; |
| padding: 2px; |
| line-height: 21px; |
| } |
| .Analysis { |
| color: white; |
| font-weight: bold; |
| background-color: #e8710a; |
| border-radius: 6px; |
| margin: 4px; |
| padding: 2px 6px; |
| white-space: nowrap; |
| } |
| .Nav { |
| margin: 4px 0 16px 20px; |
| } |
| .NavSpacer { |
| display: inline-block; |
| width: 6px; |
| } |
| .ModuleDetails { |
| margin-top: 20px; |
| } |
| .ModuleDetails td { |
| vertical-align: baseline; |
| } |
| </style> |
| </head> |
| <body> |
| <h1>%(page_title)s</h1> |
| <div class="Nav"> |
| <a href='#help'>Help</a> |
| <span class='NavSpacer'></span><span class='NavSpacer'> </span> |
| Partitions: |
| """ % { |
| "page_title": page_title, |
| }) |
| for partition in sorted(partitions): |
| print("<a href='#partition_%s'>%s</a><span class='NavSpacer'></span>" % (partition, partition)) |
| |
| print(""" |
| <span class='NavSpacer'></span><span class='NavSpacer'> </span> |
| <a href='#summary'>Overall Summary</a> |
| </div> |
| <div id="container"> |
| <div id="tables"> |
| <a name="help"></a> |
| <div class="Help"> |
| <p> |
| This page analyzes the remaining Android.mk files in the Android Source tree. |
| <p> |
| The modules are first broken down by which of the device filesystem partitions |
| they are installed to. This also includes host tools and testcases which don't |
| actually reside in their own partition but convenitely group together. |
| <p> |
| The makefiles for each partition are further are grouped into a set of directories |
| aritrarily picked to break down the problem size by owners. |
| <ul style="width: 300px"> |
| <li style="background-color: #e6f4ea">AOSP directories are colored green.</li> |
| <li style="background-color: #e8f0fe">Google directories are colored blue.</li> |
| <li style="background-color: #fce8e6">Other partner directories are colored red.</li> |
| </ul> |
| Each of the makefiles are scanned for issues that are likely to come up during |
| conversion to soong. Clicking the number in each cell shows additional information, |
| including the line that triggered the warning. |
| <p> |
| <table class="HelpColumns"> |
| <tr> |
| <th>Total</th> |
| <td>The total number of makefiles in this each directory.</td> |
| </tr> |
| <tr> |
| <th class="Clean">Easy</th> |
| <td>The number of makefiles that have no warnings themselves, and also |
| none of their dependencies have warnings either.</td> |
| </tr> |
| <tr> |
| <th class="Clean">Unblocked Clean</th> |
| <td>The number of makefiles that are both Unblocked and Clean.</td> |
| </tr> |
| |
| <tr> |
| <th class="Unblocked">Unblocked</th> |
| <td>Makefiles containing one or more modules that don't have any |
| additional dependencies pending before conversion.</td> |
| </tr> |
| <tr> |
| <th class="Blocked">Blocked</th> |
| <td>Makefiles containiong one or more modules which <i>do</i> have |
| additional prerequesite depenedencies that are not yet converted.</td> |
| </tr> |
| <tr> |
| <th class="Clean">Clean</th> |
| <td>The number of makefiles that have none of the following warnings.</td> |
| </tr> |
| <tr> |
| <th class="Warning">ifeq / ifneq</th> |
| <td>Makefiles that use <code>ifeq</code> or <code>ifneq</code>. i.e. |
| conditionals.</td> |
| </tr> |
| <tr> |
| <th class="Warning">Wacky Includes</th> |
| <td>Makefiles that <code>include</code> files other than the standard build-system |
| defined template and macros.</td> |
| </tr> |
| <tr> |
| <th class="Warning">Calls base_rules</th> |
| <td>Makefiles that include base_rules.mk directly.</td> |
| </tr> |
| <tr> |
| <th class="Warning">Calls define</th> |
| <td>Makefiles that define their own macros. Some of these are easy to convert |
| to soong <code>defaults</code>, but others are complex.</td> |
| </tr> |
| <tr> |
| <th class="Warning">Has ../</th> |
| <td>Makefiles containing the string "../" outside of a comment. These likely |
| access files outside their directories.</td> |
| </tr> |
| <tr> |
| <th class="Warning">dist-for-goals</th> |
| <td>Makefiles that call <code>dist-for-goals</code> directly.</td> |
| </tr> |
| <tr> |
| <th class="Warning">.PHONY</th> |
| <td>Makefiles that declare .PHONY targets.</td> |
| </tr> |
| <tr> |
| <th class="Warning">renderscript</th> |
| <td>Makefiles defining targets that depend on <code>.rscript</code> source files.</td> |
| </tr> |
| <tr> |
| <th class="Warning">vts src</th> |
| <td>Makefiles defining targets that depend on <code>.vts</code> source files.</td> |
| </tr> |
| <tr> |
| <th class="Warning">COPY_HEADERS</th> |
| <td>Makefiles using LOCAL_COPY_HEADERS.</td> |
| </tr> |
| </table> |
| <p> |
| Following the list of directories is a list of the modules that are installed on |
| each partition. Potential issues from their makefiles are listed, as well as the |
| total number of dependencies (both blocking that module and blocked by that module) |
| and the list of direct dependencies. Note: The number is the number of all transitive |
| dependencies and the list of modules is only the direct dependencies. |
| </div> |
| """) |
| |
| overall_summary = Summary() |
| |
| # For each partition |
| for partition in sorted(partitions): |
| modules = modules_by_partition[partition] |
| |
| makefiles = set(itertools.chain.from_iterable( |
| [self.soong.makefiles[module] for module in modules])) |
| |
| # Read makefiles |
| summary = Summary() |
| for filename in makefiles: |
| makefile = self.all_makefiles.get(filename) |
| if makefile: |
| summary.Add(makefile) |
| overall_summary.Add(makefile) |
| |
| # Categorize directories by who is responsible |
| aosp_dirs = [] |
| google_dirs = [] |
| partner_dirs = [] |
| for dirname in sorted(summary.directories.keys()): |
| if is_aosp(dirname): |
| aosp_dirs.append(dirname) |
| elif is_google(dirname): |
| google_dirs.append(dirname) |
| else: |
| partner_dirs.append(dirname) |
| |
| print_analysis_header("partition_" + partition, partition) |
| |
| for dirgroup, rowclass in [(aosp_dirs, "AospDir"), |
| (google_dirs, "GoogleDir"), |
| (partner_dirs, "PartnerDir"),]: |
| for dirname in dirgroup: |
| self.print_analysis_row(summary, modules, |
| dirname, rowclass, summary.directories[dirname]) |
| |
| self.print_analysis_row(summary, modules, |
| "Total", "TotalRow", |
| set(itertools.chain.from_iterable(summary.directories.values()))) |
| print(""" |
| </table> |
| """) |
| |
| module_details = [(count_deps(self.soong.deps, m, []), |
| -count_deps(self.soong.reverse_deps, m, []), m) |
| for m in modules] |
| module_details.sort() |
| module_details = [m[2] for m in module_details] |
| print(""" |
| <table class="ModuleDetails">""") |
| print("<tr>") |
| print(" <th>Module Name</th>") |
| print(" <th>Issues</th>") |
| print(" <th colspan='2'>Blocked By</th>") |
| print(" <th colspan='2'>Blocking</th>") |
| print("</tr>") |
| altRow = True |
| for module in module_details: |
| analyses = set() |
| for filename in self.soong.makefiles[module]: |
| makefile = summary.makefiles.get(filename) |
| if makefile: |
| for analyzer, analysis in makefile.analyses.items(): |
| if analysis: |
| analyses.add(analyzer.title) |
| |
| altRow = not altRow |
| print("<tr class='%s'>" % ("Alt" if altRow else "",)) |
| print(" <td><a name='module_%s'></a>%s</td>" % (module, module)) |
| print(" <td class='AnalysisCol'>%s</td>" % " ".join(["<span class='Analysis'>%s</span>" % title |
| for title in analyses])) |
| print(" <td>%s</td>" % count_deps(self.soong.deps, module, [])) |
| print(" <td>%s</td>" % format_module_list(self.soong.deps.get(module, []))) |
| print(" <td>%s</td>" % count_deps(self.soong.reverse_deps, module, [])) |
| print(" <td>%s</td>" % format_module_list(self.soong.reverse_deps.get(module, []))) |
| print("</tr>") |
| print("""</table>""") |
| |
| print_analysis_header("summary", "Overall Summary") |
| |
| modules = [module for installed, module in self.soong.installed.items()] |
| self.print_analysis_row(overall_summary, modules, |
| "All Makefiles", "TotalRow", |
| set(itertools.chain.from_iterable(overall_summary.directories.values()))) |
| print(""" |
| </table> |
| """) |
| |
| print(""" |
| <script type="text/javascript"> |
| function close_details() { |
| document.getElementById('details').style.display = 'none'; |
| } |
| |
| class LineMatch { |
| constructor(lineno, text) { |
| this.lineno = lineno; |
| this.text = text; |
| } |
| } |
| |
| class Analysis { |
| constructor(filename, modules, line_matches) { |
| this.filename = filename; |
| this.modules = modules; |
| this.line_matches = line_matches; |
| } |
| } |
| |
| class Module { |
| constructor(deps) { |
| this.deps = deps; |
| } |
| } |
| |
| function make_module_link(module) { |
| var a = document.createElement('a'); |
| a.className = 'ModuleLink'; |
| a.innerText = module; |
| a.href = '#module_' + module; |
| return a; |
| } |
| |
| function update_details(id) { |
| document.getElementById('details').style.display = 'block'; |
| |
| var analyses = ANALYSIS[id]; |
| |
| var details = document.getElementById("details_data"); |
| while (details.firstChild) { |
| details.removeChild(details.firstChild); |
| } |
| |
| for (var i=0; i<analyses.length; i++) { |
| var analysis = analyses[i]; |
| |
| var makefileDiv = document.createElement('div'); |
| makefileDiv.className = 'Makefile'; |
| details.appendChild(makefileDiv); |
| |
| var fileA = document.createElement('a'); |
| makefileDiv.appendChild(fileA); |
| fileA.className = 'CsLink'; |
| fileA.href = '%(codesearch)s' + analysis.filename; |
| fileA.innerText = analysis.filename; |
| fileA.target = "_blank"; |
| |
| if (analysis.modules.length > 0) { |
| var moduleTable = document.createElement('table'); |
| details.appendChild(moduleTable); |
| |
| for (var j=0; j<analysis.modules.length; j++) { |
| var moduleRow = document.createElement('tr'); |
| moduleTable.appendChild(moduleRow); |
| |
| var moduleNameCell = document.createElement('td'); |
| moduleRow.appendChild(moduleNameCell); |
| moduleNameCell.className = 'ModuleName'; |
| moduleNameCell.appendChild(make_module_link(analysis.modules[j])); |
| |
| var moduleData = MODULE_DATA[analysis.modules[j]]; |
| console.log(moduleData); |
| |
| var depCell = document.createElement('td'); |
| moduleRow.appendChild(depCell); |
| |
| if (moduleData.deps.length == 0) { |
| depCell.className = 'ModuleDeps Unblocked'; |
| depCell.innerText = 'UNBLOCKED'; |
| } else { |
| depCell.className = 'ModuleDeps Blocked'; |
| |
| for (var k=0; k<moduleData.deps.length; k++) { |
| depCell.appendChild(make_module_link(moduleData.deps[k])); |
| depCell.appendChild(document.createElement('br')); |
| } |
| } |
| } |
| } |
| |
| if (analysis.line_matches.length > 0) { |
| var lineTable = document.createElement('table'); |
| details.appendChild(lineTable); |
| |
| for (var j=0; j<analysis.line_matches.length; j++) { |
| var line_match = analysis.line_matches[j]; |
| |
| var lineRow = document.createElement('tr'); |
| lineTable.appendChild(lineRow); |
| |
| var linenoCell = document.createElement('td'); |
| lineRow.appendChild(linenoCell); |
| linenoCell.className = 'LineNo'; |
| |
| var linenoA = document.createElement('a'); |
| linenoCell.appendChild(linenoA); |
| linenoA.className = 'CsLink'; |
| linenoA.href = '%(codesearch)s' + analysis.filename |
| + ';l=' + line_match.lineno; |
| linenoA.innerText = line_match.lineno; |
| linenoA.target = "_blank"; |
| |
| var textCell = document.createElement('td'); |
| lineRow.appendChild(textCell); |
| textCell.className = 'LineText'; |
| textCell.innerText = line_match.text; |
| } |
| } |
| } |
| } |
| |
| var ANALYSIS = [ |
| """ % { |
| "codesearch": self.args.codesearch, |
| }) |
| for entry, mods in self.annotations.entries: |
| print(" [") |
| for analysis in entry: |
| print(" new Analysis('%(filename)s', %(modules)s, [%(line_matches)s])," % { |
| "filename": analysis.filename, |
| #"modules": json.dumps([m for m in mods if m in filename in self.soong.makefiles[m]]), |
| "modules": json.dumps( |
| [m for m in self.soong.reverse_makefiles[analysis.filename] if m in mods]), |
| "line_matches": ", ".join([ |
| "new LineMatch(%d, %s)" % (lineno, json.dumps(text)) |
| for lineno, text in analysis.line_matches]), |
| }) |
| print(" ],") |
| print(""" |
| ]; |
| var MODULE_DATA = { |
| """) |
| for module in self.soong.modules: |
| print(" '%(name)s': new Module(%(deps)s)," % { |
| "name": module, |
| "deps": json.dumps(self.soong.deps[module]), |
| }) |
| print(""" |
| }; |
| </script> |
| |
| """) |
| |
| print(""" |
| </div> <!-- id=tables --> |
| <div id="details"> |
| <div style="text-align: right;"> |
| <a href="javascript:close_details();"> |
| <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> |
| </a> |
| </div> |
| <div id="details_data"></div> |
| </div> |
| </body> |
| </html> |
| """) |
| |
| def traverse_ready_makefiles(self, summary, makefiles): |
| return [Analysis(makefile.filename, []) for makefile in makefiles |
| if clean_and_only_blocked_by_clean(self.soong, self.all_makefiles, makefile)] |
| |
| def print_analysis_row(self, summary, modules, rowtitle, rowclass, makefiles): |
| all_makefiles = [Analysis(makefile.filename, []) for makefile in makefiles] |
| clean_makefiles = [Analysis(makefile.filename, []) for makefile in makefiles |
| if is_clean(makefile)] |
| easy_makefiles = self.traverse_ready_makefiles(summary, makefiles) |
| unblocked_clean_makefiles = [Analysis(makefile.filename, []) for makefile in makefiles |
| if (self.soong.contains_unblocked_modules(makefile.filename) |
| and is_clean(makefile))] |
| unblocked_makefiles = [Analysis(makefile.filename, []) for makefile in makefiles |
| if self.soong.contains_unblocked_modules(makefile.filename)] |
| blocked_makefiles = [Analysis(makefile.filename, []) for makefile in makefiles |
| if self.soong.contains_blocked_modules(makefile.filename)] |
| |
| print(""" |
| <tr class="%(rowclass)s"> |
| <td class="RowTitle">%(rowtitle)s</td> |
| <td class="Count">%(makefiles)s</td> |
| <td class="Count">%(easy)s</td> |
| <td class="Count">%(unblocked_clean)s</td> |
| <td class="Count">%(unblocked)s</td> |
| <td class="Count">%(blocked)s</td> |
| <td class="Count">%(clean)s</td> |
| """ % { |
| "rowclass": rowclass, |
| "rowtitle": rowtitle, |
| "makefiles": self.make_annotation_link(all_makefiles, modules), |
| "unblocked": self.make_annotation_link(unblocked_makefiles, modules), |
| "blocked": self.make_annotation_link(blocked_makefiles, modules), |
| "clean": self.make_annotation_link(clean_makefiles, modules), |
| "unblocked_clean": self.make_annotation_link(unblocked_clean_makefiles, modules), |
| "easy": self.make_annotation_link(easy_makefiles, modules), |
| }) |
| |
| for analyzer in ANALYZERS: |
| analyses = [m.analyses.get(analyzer) for m in makefiles if m.analyses.get(analyzer)] |
| print("""<td class="Count">%s</td>""" |
| % self.make_annotation_link(analyses, modules)) |
| |
| print(" </tr>") |
| |
| def make_annotation_link(self, analysis, modules): |
| if analysis: |
| return "<a href='javascript:update_details(%d)'>%s</a>" % ( |
| self.annotations.Add(analysis, modules), |
| len(analysis) |
| ) |
| else: |
| return ""; |
| |
| class CsvProcessor(object): |
| def __init__(self, args, soong, all_makefiles, product_packages_modules): |
| self.args = args |
| self.soong = soong |
| self.all_makefiles = all_makefiles |
| self.product_packages_modules = product_packages_modules |
| |
| def execute(self): |
| csvout = csv.writer(sys.stdout) |
| |
| # Title row |
| row = ["Filename", "Module", "Partitions", "Easy", "Unblocked Clean", "Unblocked", |
| "Blocked", "Clean"] |
| for analyzer in ANALYZERS: |
| row.append(analyzer.title) |
| csvout.writerow(row) |
| |
| # Makefile & module data |
| for filename in sorted(self.all_makefiles.keys()): |
| makefile = self.all_makefiles[filename] |
| for module in self.soong.reverse_makefiles[filename]: |
| if len(self.product_packages_modules) > 0 and module not in self.product_packages_modules: |
| continue |
| row = [filename, module] |
| # Partitions |
| row.append(";".join(sorted(set([get_partition_from_installed(HOST_OUT_ROOT, PRODUCT_OUT, |
| installed) |
| for installed |
| in self.soong.reverse_installed.get(module, [])])))) |
| # Easy |
| row.append(1 |
| if clean_and_only_blocked_by_clean(self.soong, self.all_makefiles, makefile) |
| else "") |
| # Unblocked Clean |
| row.append(1 |
| if (self.soong.contains_unblocked_modules(makefile.filename) and is_clean(makefile)) |
| else "") |
| # Unblocked |
| row.append(1 if self.soong.contains_unblocked_modules(makefile.filename) else "") |
| # Blocked |
| row.append(1 if self.soong.contains_blocked_modules(makefile.filename) else "") |
| # Clean |
| row.append(1 if is_clean(makefile) else "") |
| # Analysis |
| for analyzer in ANALYZERS: |
| row.append(1 if makefile.analyses.get(analyzer) else "") |
| # Write results |
| csvout.writerow(row) |
| |
| if __name__ == "__main__": |
| main() |
| |