Merge "Force extra config file to be .config"
diff --git a/core/Makefile b/core/Makefile
index 4e03c92..3030832 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -495,9 +495,20 @@
 		--title="Remaining Android.mk files for $(TARGET_DEVICE)-$(TARGET_BUILD_VARIANT)" \
 		--codesearch=$(PRIVATE_CODE_SEARCH_BASE_URL) \
 		--out_dir="$(OUT_DIR)" \
+		--mode=html \
 		> $@
 $(call dist-for-goals,droidcore,$(MK2BP_REMAINING_HTML))
 
+MK2BP_REMAINING_CSV := $(PRODUCT_OUT)/mk2bp_remaining.csv
+$(MK2BP_REMAINING_CSV): $(SOONG_CONV_DATA) $(MK2BP_CATALOG_SCRIPT)
+	@rm -f $@
+	$(hide) $(MK2BP_CATALOG_SCRIPT) \
+		--device=$(TARGET_DEVICE) \
+		--out_dir="$(OUT_DIR)" \
+		--mode=csv \
+		> $@
+$(call dist-for-goals,droidcore,$(MK2BP_REMAINING_CSV))
+
 # -----------------------------------------------------------------
 # Modules use -Wno-error, or added default -Wall -Werror
 WALL_WERROR := $(PRODUCT_OUT)/wall_werror.txt
diff --git a/core/combo/HOST_CROSS_linux_bionic-arm64.mk b/core/combo/HOST_CROSS_linux_bionic-arm64.mk
new file mode 100644
index 0000000..df6865f
--- /dev/null
+++ b/core/combo/HOST_CROSS_linux_bionic-arm64.mk
@@ -0,0 +1,22 @@
+#
+# Copyright (C) 2020 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Configuration for builds hosted on linux_arm-arm64
+# Included by combo/select.mk
+
+define $(combo_var_prefix)transform-shared-lib-to-toc
+$(call _gen_toc_command_for_elf,$(1),$(2))
+endef
diff --git a/core/envsetup.mk b/core/envsetup.mk
index 76e7dd3..a5571ae 100644
--- a/core/envsetup.mk
+++ b/core/envsetup.mk
@@ -148,15 +148,25 @@
 # BUILD_OS is the real host doing the build.
 BUILD_OS := $(HOST_OS)
 
-HOST_CROSS_OS :=
-# We can cross-build Windows binaries on Linux
+# We can do the cross-build only on Linux
 ifeq ($(HOST_OS),linux)
-ifeq ($(BUILD_HOST_static),)
-HOST_CROSS_OS := windows
-HOST_CROSS_ARCH := x86
-HOST_CROSS_2ND_ARCH := x86_64
-2ND_HOST_CROSS_IS_64_BIT := true
-endif
+  # Windows has been the default host_cross OS
+  ifeq (,$(filter-out windows,$(HOST_CROSS_OS)))
+    # We can only create static host binaries for Linux, so if static host
+    # binaries are requested, turn off Windows cross-builds.
+    ifeq ($(BUILD_HOST_static),)
+      HOST_CROSS_OS := windows
+      HOST_CROSS_ARCH := x86
+      HOST_CROSS_2ND_ARCH := x86_64
+      2ND_HOST_CROSS_IS_64_BIT := true
+    endif
+  else ifeq ($(HOST_CROSS_OS),linux_bionic)
+    ifeq (,$(HOST_CROSS_ARCH))
+      $(error HOST_CROSS_ARCH missing.)
+    endif
+  else
+    $(error Unsupported HOST_CROSS_OS $(HOST_CROSS_OS))
+  endif
 endif
 
 ifeq ($(HOST_OS),)
diff --git a/core/main.mk b/core/main.mk
index 0eca029..ebec885 100644
--- a/core/main.mk
+++ b/core/main.mk
@@ -608,8 +608,8 @@
   $(eval modules_32 := $(patsubst %:32,%,$(filter %:32,$(2)))) \
   $(eval modules_64 := $(patsubst %:64,%,$(filter %:64,$(2)))) \
   $(eval modules_both := $(filter-out %:32 %:64,$(2))) \
-  $(eval ### For host cross modules, the primary arch is windows x86 and secondary is x86_64) \
-  $(if $(filter HOST_CROSS,$(1)), \
+  $(eval ### if 2ND_HOST_CROSS_IS_64_BIT, then primary/secondary are reversed for HOST_CROSS modules) \
+  $(if $(filter HOST_CROSS_true,$(1)_$(2ND_HOST_CROSS_IS_64_BIT)), \
     $(eval modules_1st_arch := $(modules_32)) \
     $(eval modules_2nd_arch := $(modules_64)), \
     $(eval modules_1st_arch := $(modules_64)) \
diff --git a/core/rbe.mk b/core/rbe.mk
index 5e55cfb..91606d4 100644
--- a/core/rbe.mk
+++ b/core/rbe.mk
@@ -57,7 +57,7 @@
   java_r8_d8_platform := $(platform),Pool=java16
 
   RBE_WRAPPER := $(rbe_dir)/rewrapper
-  RBE_CXX := --labels=type=compile,lang=cpp,compiler=clang --env_var_whitelist=PWD --exec_strategy=$(cxx_rbe_exec_strategy) --platform=$(cxx_platform) --compare=$(cxx_compare)
+  RBE_CXX := --labels=type=compile,lang=cpp,compiler=clang --env_var_allowlist=PWD --exec_strategy=$(cxx_rbe_exec_strategy) --platform=$(cxx_platform) --compare=$(cxx_compare)
 
   # Append rewrapper to existing *_WRAPPER variables so it's possible to
   # use both ccache and rewrapper.
diff --git a/target/board/BoardConfigGsiCommon.mk b/target/board/BoardConfigGsiCommon.mk
index 9d55f42..e34dc23 100644
--- a/target/board/BoardConfigGsiCommon.mk
+++ b/target/board/BoardConfigGsiCommon.mk
@@ -11,7 +11,9 @@
 # This flag is set by mainline but isn't desired for GSI.
 BOARD_USES_SYSTEM_OTHER_ODEX :=
 
-# system.img is always ext4 with sparse option
+# system.img is always ext4 and non-sparsed.
+TARGET_USERIMAGES_SPARSE_EXT_DISABLED := true
+
 # GSI also includes make_f2fs to support userdata parition in f2fs
 # for some devices
 TARGET_USERIMAGES_USE_F2FS := true
diff --git a/tools/mk2bp_catalog.py b/tools/mk2bp_catalog.py
index 83abd62..c2afb9b 100755
--- a/tools/mk2bp_catalog.py
+++ b/tools/mk2bp_catalog.py
@@ -168,22 +168,24 @@
       return True
   return False
 
-def make_annotation_link(annotations, analysis, modules):
-  if analysis:
-    return "<a href='javascript:update_details(%d)'>%s</a>" % (
-      annotations.Add(analysis, modules),
-      len(analysis)
-    )
-  else:
-    return "";
-
-
 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 = []
@@ -205,6 +207,7 @@
     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:
@@ -222,6 +225,29 @@
         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.
@@ -237,18 +263,6 @@
       count += 1 + count_deps(depsdb, dep, seen)
   return count
 
-def contains_unblocked_modules(soong, modules):
-  for m in modules:
-    if len(soong.deps[m]) == 0:
-      return True
-  return False
-
-def contains_blocked_modules(soong, modules):
-  for m in modules:
-    if len(soong.deps[m]) > 0:
-      return True
-  return False
-
 OTHER_PARTITON = "_other"
 HOST_PARTITON = "_host"
 
@@ -273,6 +287,27 @@
 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,
@@ -287,6 +322,9 @@
                       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()
 
@@ -297,14 +335,11 @@
     args.out_dir = args.out_dir[:-1]
 
   TARGET_DEVICE = args.device
-  HOST_OUT_ROOT = args.out_dir + "host"
+  global HOST_OUT_ROOT
+  HOST_OUT_ROOT = args.out_dir + "/host"
+  global PRODUCT_OUT
   PRODUCT_OUT = args.out_dir + "/target/product/%s" % TARGET_DEVICE
 
-  if args.title:
-    page_title = args.title
-  else:
-    page_title = "Remaining Android.mk files"
-
   # 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.
@@ -312,580 +347,688 @@
       % PRODUCT_OUT, "r", errors="ignore") as csvfile:
     soong = SoongData(csv.reader(csvfile))
 
-  # Which modules are installed where
-  modules_by_partition = dict()
-  partitions = set()
-  for installed, module in soong.installed.items():
-    partition = get_partition_from_installed(HOST_OUT_ROOT, PRODUCT_OUT, installed)
-    modules_by_partition.setdefault(partition, []).append(module)
-    partitions.add(partition)
+  # 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)
 
-  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 0 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;
-        }
-        .DirName {
-          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.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))
+  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()
 
-  print("""
-        <span class='NavSpacer'></span><span class='NavSpacer'> </span>
-      </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="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>
-  """)
+class HtmlProcessor(object):
+  def __init__(self, args, soong, all_makefiles):
+    self.args = args
+    self.soong = soong
+    self.all_makefiles = all_makefiles
+    self.annotations = Annotations()
 
-  annotations = Annotations()
+  def execute(self):
+    if self.args.title:
+      page_title = self.args.title
+    else:
+      page_title = "Remaining Android.mk files"
 
-  # For each partition
-  makefiles_for_partitions = dict()
-  for partition in sorted(partitions):
-    modules = modules_by_partition[partition]
-
-    makefiles = set(itertools.chain.from_iterable(
-        [soong.makefiles[module] for module in modules]))
-
-    # Read makefiles
-    summary = Summary()
-    for filename in makefiles:
-      if not filename.startswith(args.out_dir + "/"):
-        summary.Add(Makefile(filename))
-
-    # 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)
+    # 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("""
-      <a name="partition_%(partition)s"></a>
-      <h2>%(partition)s</h2>
-      <table>
-        <tr>
-          <th class="DirName">Directory</th>
-          <th class="Count">Total</th>
-          <th class="Count Unblocked">Unblocked</th>
-          <th class="Count Blocked">Blocked</th>
-          <th class="Count Clean">Clean</th>
+    <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:
     """ % {
-      "partition": partition
+      "page_title": page_title,
     })
+    for partition in sorted(partitions):
+      print("<a href='#partition_%s'>%s</a><span class='NavSpacer'></span>" % (partition, partition))
 
-    for analyzer in ANALYZERS:
-      print("""<th class="Count Warning">%s</th>""" % analyzer.title)
-
-    print("      </tr>")
-    for dirgroup, rowclass in [(aosp_dirs, "AospDir"),
-                               (google_dirs, "GoogleDir"),
-                               (partner_dirs, "PartnerDir"),]:
-      for dirname in dirgroup:
-        makefiles = summary.directories[dirname]
-
-        all_makefiles = [Analysis(makefile.filename, []) for makefile in makefiles]
-        clean_makefiles = [Analysis(makefile.filename, []) for makefile in makefiles
-            if is_clean(makefile)]
-        unblocked_makefiles = [Analysis(makefile.filename, []) for makefile in makefiles
-            if contains_unblocked_modules(soong,
-              soong.reverse_makefiles[makefile.filename])]
-        blocked_makefiles = [Analysis(makefile.filename, []) for makefile in makefiles
-            if contains_blocked_modules(soong,
-              soong.reverse_makefiles[makefile.filename])]
-
-        print("""
-          <tr class="%(rowclass)s">
-            <td class="DirName">%(dirname)s</td>
-            <td class="Count">%(makefiles)s</td>
-            <td class="Count">%(unblocked)s</td>
-            <td class="Count">%(blocked)s</td>
-            <td class="Count">%(clean)s</td>
-        """ % {
-          "rowclass": rowclass,
-          "dirname": dirname,
-          "makefiles": make_annotation_link(annotations, all_makefiles, modules),
-          "unblocked": make_annotation_link(annotations, unblocked_makefiles, modules),
-          "blocked": make_annotation_link(annotations, blocked_makefiles, modules),
-          "clean": make_annotation_link(annotations, clean_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>"""
-              % make_annotation_link(annotations, analyses, modules))
-
-        print("      </tr>")
     print("""
-      </table>
+          <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>
     """)
 
-    module_details = [(count_deps(soong.deps, m, []), -count_deps(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 soong.makefiles[module]:
-        makefile = summary.makefiles.get(filename)
+    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:
-          for analyzer, analysis in makefile.analyses.items():
-            if analysis:
-              analyses.add(analyzer.title)
+          summary.Add(makefile)
+          overall_summary.Add(makefile)
 
-      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(soong.deps, module, []))
-      print("  <td>%s</td>" % format_module_list(soong.deps.get(module, [])))
-      print("  <td>%s</td>" % count_deps(soong.reverse_deps, module, []))
-      print("  <td>%s</td>" % format_module_list(soong.reverse_deps.get(module, [])))
+      # 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>")
-    print("""</table>""")
+      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)
 
-  print("""
-    <script type="text/javascript">
-    function close_details() {
-      document.getElementById('details').style.display = 'none';
-    }
+        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>""")
 
-    class LineMatch {
-      constructor(lineno, text) {
-        this.lineno = lineno;
-        this.text = text;
-      }
-    }
+    print_analysis_header("summary", "Overall Summary")
 
-    class Analysis {
-      constructor(filename, modules, line_matches) {
-        this.filename = filename;
-        this.modules = modules;
-        this.line_matches = line_matches;
-      }
-    }
+    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>
+    """)
 
-    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);
+    print("""
+      <script type="text/javascript">
+      function close_details() {
+        document.getElementById('details').style.display = 'none';
       }
 
-      for (var i=0; i<analyses.length; i++) {
-        var analysis = analyses[i];
+      class LineMatch {
+        constructor(lineno, text) {
+          this.lineno = lineno;
+          this.text = text;
+        }
+      }
 
-        var makefileDiv = document.createElement('div');
-        makefileDiv.className = 'Makefile';
-        details.appendChild(makefileDiv);
+      class Analysis {
+        constructor(filename, modules, line_matches) {
+          this.filename = filename;
+          this.modules = modules;
+          this.line_matches = line_matches;
+        }
+      }
 
-        var fileA = document.createElement('a');
-        makefileDiv.appendChild(fileA);
-        fileA.className = 'CsLink';
-        fileA.href = '%(codesearch)s' + analysis.filename;
-        fileA.innerText = analysis.filename;
-        fileA.target = "_blank";
+      class Module {
+        constructor(deps) {
+          this.deps = deps;
+        }
+      }
 
-        if (analysis.modules.length > 0) {
-          var moduleTable = document.createElement('table');
-          details.appendChild(moduleTable);
+      function make_module_link(module) {
+        var a = document.createElement('a');
+        a.className = 'ModuleLink';
+        a.innerText = module;
+        a.href = '#module_' + module;
+        return a;
+      }
 
-          for (var j=0; j<analysis.modules.length; j++) {
-            var moduleRow = document.createElement('tr');
-            moduleTable.appendChild(moduleRow);
+      function update_details(id) {
+        document.getElementById('details').style.display = 'block';
 
-            var moduleNameCell = document.createElement('td');
-            moduleRow.appendChild(moduleNameCell);
-            moduleNameCell.className = 'ModuleName';
-            moduleNameCell.appendChild(make_module_link(analysis.modules[j]));
+        var analyses = ANALYSIS[id];
 
-            var moduleData = MODULE_DATA[analysis.modules[j]];
-            console.log(moduleData);
+        var details = document.getElementById("details_data");
+        while (details.firstChild) {
+            details.removeChild(details.firstChild);
+        }
 
-            var depCell = document.createElement('td');
-            moduleRow.appendChild(depCell);
+        for (var i=0; i<analyses.length; i++) {
+          var analysis = analyses[i];
 
-            if (moduleData.deps.length == 0) {
-              depCell.className = 'ModuleDeps Unblocked';
-              depCell.innerText = 'UNBLOCKED';
-            } else {
-              depCell.className = 'ModuleDeps Blocked';
+          var makefileDiv = document.createElement('div');
+          makefileDiv.className = 'Makefile';
+          details.appendChild(makefileDiv);
 
-              for (var k=0; k<moduleData.deps.length; k++) {
-                depCell.appendChild(make_module_link(moduleData.deps[k]));
-                depCell.appendChild(document.createElement('br'));
+          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);
+          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];
+            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 lineRow = document.createElement('tr');
+              lineTable.appendChild(lineRow);
 
-            var linenoCell = document.createElement('td');
-            lineRow.appendChild(linenoCell);
-            linenoCell.className = 'LineNo';
+              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 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 textCell = document.createElement('td');
+              lineRow.appendChild(textCell);
+              textCell.className = 'LineText';
+              textCell.innerText = line_match.text;
+            }
           }
         }
       }
-    }
 
-    var ANALYSIS = [
-    """ % {
-        "codesearch": args.codesearch,
-    })
-  for entry, mods in 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 soong.makefiles[m]]),
-        "modules": json.dumps(
-            [m for m in 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]),
+      var ANALYSIS = [
+      """ % {
+          "codesearch": self.args.codesearch,
       })
-    print("  ],")
-  print("""
-    ];
-    var MODULE_DATA = {
-  """)
-  for module in soong.modules:
-    print("      '%(name)s': new Module(%(deps)s)," % {
-      "name": module,
-      "deps": json.dumps(soong.deps[module]),
-    })
-  print("""
-    };
-    </script>
+    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>
+    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>
-        <div id="details_data"></div>
-      </div>
-    </body>
-  </html>
-  """)
+      </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()
diff --git a/tools/releasetools/Android.bp b/tools/releasetools/Android.bp
index 7f727fb..45e0514 100644
--- a/tools/releasetools/Android.bp
+++ b/tools/releasetools/Android.bp
@@ -272,7 +272,9 @@
         "bsdiff",
         "imgdiff",
         "minigzip",
+        "lz4",
         "mkbootfs",
+        "signapk",
     ],
 }