aboutsummaryrefslogtreecommitdiff
path: root/tools/mk2bp_catalog.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/mk2bp_catalog.py')
-rwxr-xr-xtools/mk2bp_catalog.py1035
1 files changed, 1035 insertions, 0 deletions
diff --git a/tools/mk2bp_catalog.py b/tools/mk2bp_catalog.py
new file mode 100755
index 0000000000..c2afb9b948
--- /dev/null
+++ b/tools/mk2bp_catalog.py
@@ -0,0 +1,1035 @@
+#!/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-&#8203;goals", lambda line: "dist-for-goals" in line),
+ Analyzer(".PHONY", lambda line: ".PHONY" in line),
+ Analyzer("render-&#8203;script", lambda line: ".rscript" in line),
+ Analyzer("vts src", lambda line: ".vts" in line),
+ Analyzer("COPY_&#8203;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>")
+
+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("--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)
+
+ if args.mode == "html":
+ HtmlProcessor(args=args, soong=soong, all_makefiles=all_makefiles).execute()
+ elif args.mode == "csv":
+ CsvProcessor(args=args, soong=soong, all_makefiles=all_makefiles).execute()
+
+class HtmlProcessor(object):
+ def __init__(self, args, soong, all_makefiles):
+ self.args = args
+ self.soong = soong
+ self.all_makefiles = all_makefiles
+ 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():
+ 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):
+ self.args = args
+ self.soong = soong
+ self.all_makefiles = all_makefiles
+
+ 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]:
+ 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()
+