• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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-&#8203;goals", lambda line: "dist-for-goals" in line),
107  Analyzer(".PHONY", lambda line: ".PHONY" in line),
108  Analyzer("render-&#8203;script", lambda line: ".rscript" in line),
109  Analyzer("vts src", lambda line: ".vts" in line),
110  Analyzer("COPY_&#8203;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