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 make_annotation_link(annotations, analysis, modules):
172  if analysis:
173    return "<a href='javascript:update_details(%d)'>%s</a>" % (
174      annotations.Add(analysis, modules),
175      len(analysis)
176    )
177  else:
178    return "";
179
180
181def is_clean(makefile):
182  for analysis in makefile.analyses.values():
183    if analysis:
184      return False
185  return True
186
187class Annotations(object):
188  def __init__(self):
189    self.entries = []
190    self.count = 0
191
192  def Add(self, makefiles, modules):
193    self.entries.append((makefiles, modules))
194    self.count += 1
195    return self.count-1
196
197class SoongData(object):
198  def __init__(self, reader):
199    """Read the input file and store the modules and dependency mappings.
200    """
201    self.problems = dict()
202    self.deps = dict()
203    self.reverse_deps = dict()
204    self.module_types = dict()
205    self.makefiles = dict()
206    self.reverse_makefiles = dict()
207    self.installed = dict()
208    self.modules = set()
209
210    for (module, module_type, problem, dependencies, makefiles, installed) in reader:
211      self.modules.add(module)
212      makefiles = [f for f in makefiles.strip().split(' ') if f != ""]
213      self.module_types[module] = module_type
214      self.problems[module] = problem
215      self.deps[module] = [d for d in dependencies.strip().split(' ') if d != ""]
216      for dep in self.deps[module]:
217        if not dep in self.reverse_deps:
218          self.reverse_deps[dep] = []
219        self.reverse_deps[dep].append(module)
220      self.makefiles[module] = makefiles
221      for f in makefiles:
222        self.reverse_makefiles.setdefault(f, []).append(module)
223      for f in installed.strip().split(' '):
224        self.installed[f] = module
225
226def count_deps(depsdb, module, seen):
227  """Based on the depsdb, count the number of transitive dependencies.
228
229  You can pass in an reversed dependency graph to count the number of
230  modules that depend on the module."""
231  count = 0
232  seen.append(module)
233  if module in depsdb:
234    for dep in depsdb[module]:
235      if dep in seen:
236        continue
237      count += 1 + count_deps(depsdb, dep, seen)
238  return count
239
240def contains_unblocked_modules(soong, modules):
241  for m in modules:
242    if len(soong.deps[m]) == 0:
243      return True
244  return False
245
246def contains_blocked_modules(soong, modules):
247  for m in modules:
248    if len(soong.deps[m]) > 0:
249      return True
250  return False
251
252OTHER_PARTITON = "_other"
253HOST_PARTITON = "_host"
254
255def get_partition_from_installed(HOST_OUT_ROOT, PRODUCT_OUT, filename):
256  host_prefix = HOST_OUT_ROOT + "/"
257  device_prefix = PRODUCT_OUT + "/"
258
259  if filename.startswith(host_prefix):
260    return HOST_PARTITON
261
262  elif filename.startswith(device_prefix):
263    index = filename.find("/", len(device_prefix))
264    if index < 0:
265      return OTHER_PARTITON
266    return filename[len(device_prefix):index]
267
268  return OTHER_PARTITON
269
270def format_module_link(module):
271  return "<a class='ModuleLink' href='#module_%s'>%s</a>" % (module, module)
272
273def format_module_list(modules):
274  return "".join(["<div>%s</div>" % format_module_link(m) for m in modules])
275
276def main():
277  parser = argparse.ArgumentParser(description="Info about remaining Android.mk files.")
278  parser.add_argument("--device", type=str, required=True,
279                      help="TARGET_DEVICE")
280  parser.add_argument("--title", type=str,
281                      help="page title")
282  parser.add_argument("--codesearch", type=str,
283                      default="https://cs.android.com/android/platform/superproject/+/master:",
284                      help="page title")
285  parser.add_argument("--out_dir", type=str,
286                      default=None,
287                      help="Equivalent of $OUT_DIR, which will also be checked if"
288                        + " --out_dir is unset. If neither is set, default is"
289                        + " 'out'.")
290
291  args = parser.parse_args()
292
293  # Guess out directory name
294  if not args.out_dir:
295    args.out_dir = os.getenv("OUT_DIR", "out")
296  while args.out_dir.endswith("/") and len(args.out_dir) > 1:
297    args.out_dir = args.out_dir[:-1]
298
299  TARGET_DEVICE = args.device
300  HOST_OUT_ROOT = args.out_dir + "host"
301  PRODUCT_OUT = args.out_dir + "/target/product/%s" % TARGET_DEVICE
302
303  if args.title:
304    page_title = args.title
305  else:
306    page_title = "Remaining Android.mk files"
307
308  # Read target information
309  # TODO: Pull from configurable location. This is also slightly different because it's
310  # only a single build, where as the tree scanning we do below is all Android.mk files.
311  with open("%s/obj/PACKAGING/soong_conversion_intermediates/soong_conv_data"
312      % PRODUCT_OUT, "r", errors="ignore") as csvfile:
313    soong = SoongData(csv.reader(csvfile))
314
315  # Which modules are installed where
316  modules_by_partition = dict()
317  partitions = set()
318  for installed, module in soong.installed.items():
319    partition = get_partition_from_installed(HOST_OUT_ROOT, PRODUCT_OUT, installed)
320    modules_by_partition.setdefault(partition, []).append(module)
321    partitions.add(partition)
322
323  print("""
324  <html>
325    <head>
326      <title>%(page_title)s</title>
327      <style type="text/css">
328        body, table {
329          font-family: Roboto, sans-serif;
330          font-size: 9pt;
331        }
332        body {
333          margin: 0;
334          padding: 0;
335          display: flex;
336          flex-direction: column;
337          height: 100vh;
338        }
339        #container {
340          flex: 1;
341          display: flex;
342          flex-direction: row;
343          overflow: hidden;
344        }
345        #tables {
346          padding: 0 20px 0 20px;
347          overflow: scroll;
348          flex: 2 2 600px;
349        }
350        #details {
351          display: none;
352          overflow: scroll;
353          flex: 1 1 650px;
354          padding: 0 20px 0 20px;
355        }
356        h1 {
357          margin: 16px 0 16px 20px;
358        }
359        h2 {
360          margin: 12px 0 4px 0;
361        }
362        .DirName {
363          text-align: left;
364          width: 200px;
365          min-width: 200px;
366        }
367        .Count {
368          text-align: center;
369          width: 60px;
370          min-width: 60px;
371          max-width: 60px;
372        }
373        th.Clean,
374        th.Unblocked {
375          background-color: #1e8e3e;
376        }
377        th.Blocked {
378          background-color: #d93025;
379        }
380        th.Warning {
381          background-color: #e8710a;
382        }
383        th {
384          background-color: #1a73e8;
385          color: white;
386          font-weight: bold;
387        }
388        td.Unblocked {
389          background-color: #81c995;
390        }
391        td.Blocked {
392          background-color: #f28b82;
393        }
394        td, th {
395          padding: 2px 4px;
396          border-right: 2px solid white;
397        }
398        tr.AospDir td {
399          background-color: #e6f4ea;
400          border-right-color: #e6f4ea;
401        }
402        tr.GoogleDir td {
403          background-color: #e8f0fe;
404          border-right-color: #e8f0fe;
405        }
406        tr.PartnerDir td {
407          background-color: #fce8e6;
408          border-right-color: #fce8e6;
409        }
410        table {
411          border-spacing: 0;
412          border-collapse: collapse;
413        }
414        div.Makefile {
415          margin: 12px 0 0 0;
416        }
417        div.Makefile:first {
418          margin-top: 0;
419        }
420        div.FileModules {
421          padding: 4px 0 0 20px;
422        }
423        td.LineNo {
424          vertical-align: baseline;
425          padding: 6px 0 0 20px;
426          width: 50px;
427          vertical-align: baseline;
428        }
429        td.LineText {
430          vertical-align: baseline;
431          font-family: monospace;
432          padding: 6px 0 0 0;
433        }
434        a.CsLink {
435          font-family: monospace;
436        }
437        div.Help {
438          width: 550px;
439        }
440        table.HelpColumns tr {
441          border-bottom: 2px solid white;
442        }
443        .ModuleName {
444          vertical-align: baseline;
445          padding: 6px 0 0 20px;
446          width: 275px;
447        }
448        .ModuleDeps {
449          vertical-align: baseline;
450          padding: 6px 0 0 0;
451        }
452        table#Modules td {
453          vertical-align: baseline;
454        }
455        tr.Alt {
456          background-color: #ececec;
457        }
458        tr.Alt td {
459          border-right-color: #ececec;
460        }
461        .AnalysisCol {
462          width: 300px;
463          padding: 2px;
464          line-height: 21px;
465        }
466        .Analysis {
467          color: white;
468          font-weight: bold;
469          background-color: #e8710a;
470          border-radius: 6px;
471          margin: 4px;
472          padding: 2px 6px;
473          white-space: nowrap;
474        }
475        .Nav {
476          margin: 4px 0 16px 20px;
477        }
478        .NavSpacer {
479          display: inline-block;
480          width: 6px;
481        }
482        .ModuleDetails {
483          margin-top: 20px;
484        }
485        .ModuleDetails td {
486          vertical-align: baseline;
487        }
488      </style>
489    </head>
490    <body>
491      <h1>%(page_title)s</h1>
492      <div class="Nav">
493        <a href='#help'>Help</a>
494        <span class='NavSpacer'></span><span class='NavSpacer'> </span>
495        Partitions:
496  """ % {
497    "page_title": page_title,
498  })
499  for partition in sorted(partitions):
500    print("<a href='#partition_%s'>%s</a><span class='NavSpacer'></span>" % (partition, partition))
501
502  print("""
503        <span class='NavSpacer'></span><span class='NavSpacer'> </span>
504      </div>
505      <div id="container">
506        <div id="tables">
507        <a name="help"></a>
508        <div class="Help">
509          <p>
510          This page analyzes the remaining Android.mk files in the Android Source tree.
511          <p>
512          The modules are first broken down by which of the device filesystem partitions
513          they are installed to. This also includes host tools and testcases which don't
514          actually reside in their own partition but convenitely group together.
515          <p>
516          The makefiles for each partition are further are grouped into a set of directories
517          aritrarily picked to break down the problem size by owners.
518          <ul style="width: 300px">
519            <li style="background-color: #e6f4ea">AOSP directories are colored green.</li>
520            <li style="background-color: #e8f0fe">Google directories are colored blue.</li>
521            <li style="background-color: #fce8e6">Other partner directories are colored red.</li>
522          </ul>
523          Each of the makefiles are scanned for issues that are likely to come up during
524          conversion to soong.  Clicking the number in each cell shows additional information,
525          including the line that triggered the warning.
526          <p>
527          <table class="HelpColumns">
528            <tr>
529              <th>Total</th>
530              <td>The total number of makefiles in this each directory.</td>
531            </tr>
532            <tr>
533              <th class="Unblocked">Unblocked</th>
534              <td>Makefiles containing one or more modules that don't have any
535                  additional dependencies pending before conversion.</td>
536            </tr>
537            <tr>
538              <th class="Blocked">Blocked</th>
539              <td>Makefiles containiong one or more modules which <i>do</i> have
540                  additional prerequesite depenedencies that are not yet converted.</td>
541            </tr>
542            <tr>
543              <th class="Clean">Clean</th>
544              <td>The number of makefiles that have none of the following warnings.</td>
545            </tr>
546            <tr>
547              <th class="Warning">ifeq / ifneq</th>
548              <td>Makefiles that use <code>ifeq</code> or <code>ifneq</code>. i.e.
549              conditionals.</td>
550            </tr>
551            <tr>
552              <th class="Warning">Wacky Includes</th>
553              <td>Makefiles that <code>include</code> files other than the standard build-system
554                  defined template and macros.</td>
555            </tr>
556            <tr>
557              <th class="Warning">Calls base_rules</th>
558              <td>Makefiles that include base_rules.mk directly.</td>
559            </tr>
560            <tr>
561              <th class="Warning">Calls define</th>
562              <td>Makefiles that define their own macros. Some of these are easy to convert
563                  to soong <code>defaults</code>, but others are complex.</td>
564            </tr>
565            <tr>
566              <th class="Warning">Has ../</th>
567              <td>Makefiles containing the string "../" outside of a comment. These likely
568                  access files outside their directories.</td>
569            </tr>
570            <tr>
571              <th class="Warning">dist-for-goals</th>
572              <td>Makefiles that call <code>dist-for-goals</code> directly.</td>
573            </tr>
574            <tr>
575              <th class="Warning">.PHONY</th>
576              <td>Makefiles that declare .PHONY targets.</td>
577            </tr>
578            <tr>
579              <th class="Warning">renderscript</th>
580              <td>Makefiles defining targets that depend on <code>.rscript</code> source files.</td>
581            </tr>
582            <tr>
583              <th class="Warning">vts src</th>
584              <td>Makefiles defining targets that depend on <code>.vts</code> source files.</td>
585            </tr>
586            <tr>
587              <th class="Warning">COPY_HEADERS</th>
588              <td>Makefiles using LOCAL_COPY_HEADERS.</td>
589            </tr>
590          </table>
591          <p>
592          Following the list of directories is a list of the modules that are installed on
593          each partition. Potential issues from their makefiles are listed, as well as the
594          total number of dependencies (both blocking that module and blocked by that module)
595          and the list of direct dependencies.  Note: The number is the number of all transitive
596          dependencies and the list of modules is only the direct dependencies.
597        </div>
598  """)
599
600  annotations = Annotations()
601
602  # For each partition
603  makefiles_for_partitions = dict()
604  for partition in sorted(partitions):
605    modules = modules_by_partition[partition]
606
607    makefiles = set(itertools.chain.from_iterable(
608        [soong.makefiles[module] for module in modules]))
609
610    # Read makefiles
611    summary = Summary()
612    for filename in makefiles:
613      if not filename.startswith(args.out_dir + "/"):
614        summary.Add(Makefile(filename))
615
616    # Categorize directories by who is responsible
617    aosp_dirs = []
618    google_dirs = []
619    partner_dirs = []
620    for dirname in sorted(summary.directories.keys()):
621      if is_aosp(dirname):
622        aosp_dirs.append(dirname)
623      elif is_google(dirname):
624        google_dirs.append(dirname)
625      else:
626        partner_dirs.append(dirname)
627
628    print("""
629      <a name="partition_%(partition)s"></a>
630      <h2>%(partition)s</h2>
631      <table>
632        <tr>
633          <th class="DirName">Directory</th>
634          <th class="Count">Total</th>
635          <th class="Count Unblocked">Unblocked</th>
636          <th class="Count Blocked">Blocked</th>
637          <th class="Count Clean">Clean</th>
638    """ % {
639      "partition": partition
640    })
641
642    for analyzer in ANALYZERS:
643      print("""<th class="Count Warning">%s</th>""" % analyzer.title)
644
645    print("      </tr>")
646    for dirgroup, rowclass in [(aosp_dirs, "AospDir"),
647                               (google_dirs, "GoogleDir"),
648                               (partner_dirs, "PartnerDir"),]:
649      for dirname in dirgroup:
650        makefiles = summary.directories[dirname]
651
652        all_makefiles = [Analysis(makefile.filename, []) for makefile in makefiles]
653        clean_makefiles = [Analysis(makefile.filename, []) for makefile in makefiles
654            if is_clean(makefile)]
655        unblocked_makefiles = [Analysis(makefile.filename, []) for makefile in makefiles
656            if contains_unblocked_modules(soong,
657              soong.reverse_makefiles[makefile.filename])]
658        blocked_makefiles = [Analysis(makefile.filename, []) for makefile in makefiles
659            if contains_blocked_modules(soong,
660              soong.reverse_makefiles[makefile.filename])]
661
662        print("""
663          <tr class="%(rowclass)s">
664            <td class="DirName">%(dirname)s</td>
665            <td class="Count">%(makefiles)s</td>
666            <td class="Count">%(unblocked)s</td>
667            <td class="Count">%(blocked)s</td>
668            <td class="Count">%(clean)s</td>
669        """ % {
670          "rowclass": rowclass,
671          "dirname": dirname,
672          "makefiles": make_annotation_link(annotations, all_makefiles, modules),
673          "unblocked": make_annotation_link(annotations, unblocked_makefiles, modules),
674          "blocked": make_annotation_link(annotations, blocked_makefiles, modules),
675          "clean": make_annotation_link(annotations, clean_makefiles, modules),
676        })
677        for analyzer in ANALYZERS:
678          analyses = [m.analyses.get(analyzer) for m in makefiles if m.analyses.get(analyzer)]
679          print("""<td class="Count">%s</td>"""
680              % make_annotation_link(annotations, analyses, modules))
681
682        print("      </tr>")
683    print("""
684      </table>
685    """)
686
687    module_details = [(count_deps(soong.deps, m, []), -count_deps(soong.reverse_deps, m, []), m)
688               for m in modules]
689    module_details.sort()
690    module_details = [m[2] for m in module_details]
691    print("""
692      <table class="ModuleDetails">""")
693    print("<tr>")
694    print("  <th>Module Name</th>")
695    print("  <th>Issues</th>")
696    print("  <th colspan='2'>Blocked By</th>")
697    print("  <th colspan='2'>Blocking</th>")
698    print("</tr>")
699    altRow = True
700    for module in module_details:
701      analyses = set()
702      for filename in soong.makefiles[module]:
703        makefile = summary.makefiles.get(filename)
704        if makefile:
705          for analyzer, analysis in makefile.analyses.items():
706            if analysis:
707              analyses.add(analyzer.title)
708
709      altRow = not altRow
710      print("<tr class='%s'>" % ("Alt" if altRow else "",))
711      print("  <td><a name='module_%s'></a>%s</td>" % (module, module))
712      print("  <td class='AnalysisCol'>%s</td>" % " ".join(["<span class='Analysis'>%s</span>" % title
713          for title in analyses]))
714      print("  <td>%s</td>" % count_deps(soong.deps, module, []))
715      print("  <td>%s</td>" % format_module_list(soong.deps.get(module, [])))
716      print("  <td>%s</td>" % count_deps(soong.reverse_deps, module, []))
717      print("  <td>%s</td>" % format_module_list(soong.reverse_deps.get(module, [])))
718      print("</tr>")
719    print("""</table>""")
720
721  print("""
722    <script type="text/javascript">
723    function close_details() {
724      document.getElementById('details').style.display = 'none';
725    }
726
727    class LineMatch {
728      constructor(lineno, text) {
729        this.lineno = lineno;
730        this.text = text;
731      }
732    }
733
734    class Analysis {
735      constructor(filename, modules, line_matches) {
736        this.filename = filename;
737        this.modules = modules;
738        this.line_matches = line_matches;
739      }
740    }
741
742    class Module {
743      constructor(deps) {
744        this.deps = deps;
745      }
746    }
747
748    function make_module_link(module) {
749      var a = document.createElement('a');
750      a.className = 'ModuleLink';
751      a.innerText = module;
752      a.href = '#module_' + module;
753      return a;
754    }
755
756    function update_details(id) {
757      document.getElementById('details').style.display = 'block';
758
759      var analyses = ANALYSIS[id];
760
761      var details = document.getElementById("details_data");
762      while (details.firstChild) {
763          details.removeChild(details.firstChild);
764      }
765
766      for (var i=0; i<analyses.length; i++) {
767        var analysis = analyses[i];
768
769        var makefileDiv = document.createElement('div');
770        makefileDiv.className = 'Makefile';
771        details.appendChild(makefileDiv);
772
773        var fileA = document.createElement('a');
774        makefileDiv.appendChild(fileA);
775        fileA.className = 'CsLink';
776        fileA.href = '%(codesearch)s' + analysis.filename;
777        fileA.innerText = analysis.filename;
778        fileA.target = "_blank";
779
780        if (analysis.modules.length > 0) {
781          var moduleTable = document.createElement('table');
782          details.appendChild(moduleTable);
783
784          for (var j=0; j<analysis.modules.length; j++) {
785            var moduleRow = document.createElement('tr');
786            moduleTable.appendChild(moduleRow);
787
788            var moduleNameCell = document.createElement('td');
789            moduleRow.appendChild(moduleNameCell);
790            moduleNameCell.className = 'ModuleName';
791            moduleNameCell.appendChild(make_module_link(analysis.modules[j]));
792
793            var moduleData = MODULE_DATA[analysis.modules[j]];
794            console.log(moduleData);
795
796            var depCell = document.createElement('td');
797            moduleRow.appendChild(depCell);
798
799            if (moduleData.deps.length == 0) {
800              depCell.className = 'ModuleDeps Unblocked';
801              depCell.innerText = 'UNBLOCKED';
802            } else {
803              depCell.className = 'ModuleDeps Blocked';
804
805              for (var k=0; k<moduleData.deps.length; k++) {
806                depCell.appendChild(make_module_link(moduleData.deps[k]));
807                depCell.appendChild(document.createElement('br'));
808              }
809            }
810          }
811        }
812
813        if (analysis.line_matches.length > 0) {
814          var lineTable = document.createElement('table');
815          details.appendChild(lineTable);
816
817          for (var j=0; j<analysis.line_matches.length; j++) {
818            var line_match = analysis.line_matches[j];
819
820            var lineRow = document.createElement('tr');
821            lineTable.appendChild(lineRow);
822
823            var linenoCell = document.createElement('td');
824            lineRow.appendChild(linenoCell);
825            linenoCell.className = 'LineNo';
826
827            var linenoA = document.createElement('a');
828            linenoCell.appendChild(linenoA);
829            linenoA.className = 'CsLink';
830            linenoA.href = '%(codesearch)s' + analysis.filename
831                + ';l=' + line_match.lineno;
832            linenoA.innerText = line_match.lineno;
833            linenoA.target = "_blank";
834
835            var textCell = document.createElement('td');
836            lineRow.appendChild(textCell);
837            textCell.className = 'LineText';
838            textCell.innerText = line_match.text;
839          }
840        }
841      }
842    }
843
844    var ANALYSIS = [
845    """ % {
846        "codesearch": args.codesearch,
847    })
848  for entry, mods in annotations.entries:
849    print("  [")
850    for analysis in entry:
851      print("    new Analysis('%(filename)s', %(modules)s, [%(line_matches)s])," % {
852        "filename": analysis.filename,
853        #"modules": json.dumps([m for m in mods if m in filename in soong.makefiles[m]]),
854        "modules": json.dumps(
855            [m for m in soong.reverse_makefiles[analysis.filename] if m in mods]),
856        "line_matches": ", ".join([
857            "new LineMatch(%d, %s)" % (lineno, json.dumps(text))
858            for lineno, text in analysis.line_matches]),
859      })
860    print("  ],")
861  print("""
862    ];
863    var MODULE_DATA = {
864  """)
865  for module in soong.modules:
866    print("      '%(name)s': new Module(%(deps)s)," % {
867      "name": module,
868      "deps": json.dumps(soong.deps[module]),
869    })
870  print("""
871    };
872    </script>
873
874  """)
875
876  print("""
877      </div> <!-- id=tables -->
878      <div id="details">
879        <div style="text-align: right;">
880          <a href="javascript:close_details();">
881            <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>
882          </a>
883        </div>
884        <div id="details_data"></div>
885      </div>
886    </body>
887  </html>
888  """)
889
890if __name__ == "__main__":
891  main()
892
893