Merge "Add the path of GSI document in gsi_release.mk"
diff --git a/core/Makefile b/core/Makefile
index e0b1287..b8e6e8e 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -3933,7 +3933,7 @@
 INSTALLED_PVMFWIMAGE_TARGET := $(PRODUCT_OUT)/pvmfw.img
 INSTALLED_PVMFW_EMBEDDED_AVBKEY_TARGET := $(PRODUCT_OUT)/pvmfw_embedded.avbpubkey
 INTERNAL_PVMFWIMAGE_FILES := $(call module-target-built-files,pvmfw_img)
-INTERNAL_PVMFW_EMBEDDED_AVBKEY := $(call module-target-built-files,pvmfw_sign_key)
+INTERNAL_PVMFW_EMBEDDED_AVBKEY := $(call module-target-built-files,pvmfw_embedded_key)
 
 $(call declare-1p-container,$(INSTALLED_PVMFWIMAGE_TARGET),)
 $(call declare-container-license-deps,$(INSTALLED_PVMFWIMAGE_TARGET),$(INTERNAL_PVMFWIMAGE_FILES),$(PRODUCT_OUT)/:/)
@@ -6829,11 +6829,7 @@
 $(INTERNAL_SDK_TARGET): PRIVATE_DIR := $(sdk_dir)/$(sdk_name)
 $(INTERNAL_SDK_TARGET): PRIVATE_DEP_FILE := $(sdk_dep_file)
 $(INTERNAL_SDK_TARGET): PRIVATE_INPUT_FILES := $(sdk_atree_files)
-$(INTERNAL_SDK_TARGET): PRIVATE_PLATFORM_NAME := \
-  $(strip $(if $(filter $(PLATFORM_SDK_EXTENSION_VERSION),$(PLATFORM_BASE_SDK_EXTENSION_VERSION)),\
-    android-$(PLATFORM_SDK_VERSION),\
-    android-$(PLATFORM_SDK_VERSION)-ext$(PLATFORM_SDK_EXTENSION_VERSION)) \
-)
+
 # Set SDK_GNU_ERROR to non-empty to fail when a GNU target is built.
 #
 #SDK_GNU_ERROR := true
@@ -6858,7 +6854,7 @@
 	        -I $(PRODUCT_OUT) \
 	        -I $(HOST_OUT) \
 	        -I $(TARGET_COMMON_OUT_ROOT) \
-	        -v "PLATFORM_NAME=$(PRIVATE_PLATFORM_NAME)" \
+	        -v "PLATFORM_NAME=android-$(PLATFORM_VERSION)" \
 	        -v "OUT_DIR=$(OUT_DIR)" \
 	        -v "HOST_OUT=$(HOST_OUT)" \
 	        -v "TARGET_ARCH=$(TARGET_ARCH)" \
diff --git a/core/clear_vars.mk b/core/clear_vars.mk
index 8fe5214..e325760 100644
--- a/core/clear_vars.mk
+++ b/core/clear_vars.mk
@@ -153,7 +153,6 @@
 LOCAL_JAR_PROCESSOR_ARGS:=
 LOCAL_JAVACFLAGS:=
 LOCAL_JAVA_LANGUAGE_VERSION:=
-LOCAL_JAVA_LAYERS_FILE:=
 LOCAL_JAVA_LIBRARIES:=
 LOCAL_JAVA_RESOURCE_DIRS:=
 LOCAL_JAVA_RESOURCE_FILES:=
diff --git a/core/config.mk b/core/config.mk
index e8b984d..631ba34 100644
--- a/core/config.mk
+++ b/core/config.mk
@@ -166,6 +166,8 @@
 $(KATI_obsolete_var PRODUCT_SUPPORTS_BOOT_SIGNER,VB 1.0 and related variables are no longer supported)
 $(KATI_obsolete_var PRODUCT_VERITY_SIGNING_KEY,VB 1.0 and related variables are no longer supported)
 $(KATI_obsolete_var BOARD_PREBUILT_PVMFWIMAGE,pvmfw.bin is now built in AOSP and custom versions are no longer supported)
+$(KATI_obsolete_var BOARD_BUILD_SYSTEM_ROOT_IMAGE)
+
 # Used to force goals to build.  Only use for conditionally defined goals.
 .PHONY: FORCE
 FORCE:
@@ -612,7 +614,7 @@
 JARJAR := $(HOST_OUT_JAVA_LIBRARIES)/jarjar.jar
 DATA_BINDING_COMPILER := $(HOST_OUT_JAVA_LIBRARIES)/databinding-compiler.jar
 FAT16COPY := build/make/tools/fat16copy.py
-CHECK_ELF_FILE := build/make/tools/check_elf_file.py
+CHECK_ELF_FILE := $(HOST_OUT_EXECUTABLES)/check_elf_file$(HOST_EXECUTABLE_SUFFIX)
 LPMAKE := $(HOST_OUT_EXECUTABLES)/lpmake$(HOST_EXECUTABLE_SUFFIX)
 ADD_IMG_TO_TARGET_FILES := $(HOST_OUT_EXECUTABLES)/add_img_to_target_files$(HOST_EXECUTABLE_SUFFIX)
 BUILD_IMAGE := $(HOST_OUT_EXECUTABLES)/build_image$(HOST_EXECUTABLE_SUFFIX)
@@ -866,11 +868,6 @@
   endif
 endif
 
-# TODO(b/241346584): Mark BOARD_BUILD_SYSTEM_ROOT_IMAGE as KATI_obsolete_var after all users are removed
-ifeq ($(BOARD_BUILD_SYSTEM_ROOT_IMAGE),true)
-    $(error BOARD_BUILD_SYSTEM_ROOT_IMAGE is deprecated)
-endif
-
 ifeq ($(PRODUCT_USE_DYNAMIC_PARTITIONS),true)
     ifneq ($(PRODUCT_USE_DYNAMIC_PARTITION_SIZE),true)
         $(error PRODUCT_USE_DYNAMIC_PARTITION_SIZE must be true for devices with dynamic partitions)
diff --git a/core/definitions.mk b/core/definitions.mk
index 98fdd6c..afa7f7b 100644
--- a/core/definitions.mk
+++ b/core/definitions.mk
@@ -2604,8 +2604,6 @@
     $(if $(PRIVATE_SRCJARS),\@$(PRIVATE_SRCJAR_LIST_FILE)) \
     || ( rm -rf $(PRIVATE_CLASS_INTERMEDIATES_DIR) ; exit 41 ) \
 fi
-$(if $(PRIVATE_JAVA_LAYERS_FILE), $(hide) build/make/tools/java-layers.py \
-    $(PRIVATE_JAVA_LAYERS_FILE) @$(PRIVATE_JAVA_SOURCE_LIST),)
 $(if $(PRIVATE_JAR_EXCLUDE_FILES), $(hide) find $(PRIVATE_CLASS_INTERMEDIATES_DIR) \
     -name $(word 1, $(PRIVATE_JAR_EXCLUDE_FILES)) \
     $(addprefix -o -name , $(wordlist 2, 999, $(PRIVATE_JAR_EXCLUDE_FILES))) \
diff --git a/core/host_java_library.mk b/core/host_java_library.mk
index 0f95202..89aa53c 100644
--- a/core/host_java_library.mk
+++ b/core/host_java_library.mk
@@ -56,10 +56,6 @@
 
 include $(BUILD_SYSTEM)/java_common.mk
 
-# The layers file allows you to enforce a layering between java packages.
-# Run build/make/tools/java-layers.py for more details.
-layers_file := $(addprefix $(LOCAL_PATH)/, $(LOCAL_JAVA_LAYERS_FILE))
-
 # List of dependencies for anything that needs all java sources in place
 java_sources_deps := \
     $(java_sources) \
@@ -72,7 +68,6 @@
 
 # TODO(b/143658984): goma can't handle the --system argument to javac.
 #$(full_classes_compiled_jar): .KATI_NINJA_POOL := $(GOMA_POOL)
-$(full_classes_compiled_jar): PRIVATE_JAVA_LAYERS_FILE := $(layers_file)
 $(full_classes_compiled_jar): PRIVATE_JAVACFLAGS := $(LOCAL_JAVACFLAGS) $(annotation_processor_flags)
 $(full_classes_compiled_jar): PRIVATE_JAR_EXCLUDE_FILES :=
 $(full_classes_compiled_jar): PRIVATE_JAR_PACKAGES :=
diff --git a/core/java.mk b/core/java.mk
index 01951c0..b13ef4d 100644
--- a/core/java.mk
+++ b/core/java.mk
@@ -200,10 +200,6 @@
 $(eval $(call copy-one-file,$(full_classes_jar),$(full_classes_stubs_jar)))
 ALL_MODULES.$(my_register_name).STUBS := $(full_classes_stubs_jar)
 
-# The layers file allows you to enforce a layering between java packages.
-# Run build/make/tools/java-layers.py for more details.
-layers_file := $(addprefix $(LOCAL_PATH)/, $(LOCAL_JAVA_LAYERS_FILE))
-$(full_classes_compiled_jar): PRIVATE_JAVA_LAYERS_FILE := $(layers_file)
 $(full_classes_compiled_jar): PRIVATE_WARNINGS_ENABLE := $(LOCAL_WARNINGS_ENABLE)
 
 # Compile the java files to a .jar file.
diff --git a/core/tasks/module-info.mk b/core/tasks/module-info.mk
index dbd1e84..e83d408 100644
--- a/core/tasks/module-info.mk
+++ b/core/tasks/module-info.mk
@@ -41,3 +41,9 @@
 
 $(call dist-for-goals, general-tests, $(MODULE_INFO_JSON))
 $(call dist-for-goals, droidcore-unbundled, $(MODULE_INFO_JSON))
+
+# On every build, generate an all_modules.txt file to be used for autocompleting
+# the m command. After timing this using $(shell date +"%s.%3N"), it only adds
+# 0.01 seconds to the internal master build, and will only rerun on builds that
+# rerun kati.
+$(file >$(PRODUCT_OUT)/all_modules.txt,$(subst $(space),$(newline),$(ALL_MODULES)))
diff --git a/envsetup.sh b/envsetup.sh
index bc206c4..4c1aeaa 100644
--- a/envsetup.sh
+++ b/envsetup.sh
@@ -253,6 +253,9 @@
     local ATEST_PATH="$T/prebuilts/asuite/atest/$os_arch"
     ANDROID_BUILD_PATHS=$ANDROID_BUILD_PATHS:$ACLOUD_PATH:$AIDEGEN_PATH:$ATEST_PATH
 
+    # Build system
+    ANDROID_BUILD_PATHS=$ANDROID_BUILD_PATHS:$T/build/bazel/bin
+
     export ANDROID_BUILD_PATHS=$(tr -s : <<<"${ANDROID_BUILD_PATHS}:")
     export PATH=$ANDROID_BUILD_PATHS$PATH
 
@@ -294,22 +297,6 @@
     #export HOST_EXTRACFLAGS="-I "$T/system/kernel_headers/host_include
 }
 
-function bazel()
-{
-    if which bazel &>/dev/null; then
-        >&2 echo "NOTE: bazel() function sourced from Android's envsetup.sh is being used instead of $(which bazel)"
-        >&2 echo
-    fi
-
-    local T="$(gettop)"
-    if [ ! "$T" ]; then
-        >&2 echo "Couldn't locate the top of the Android tree. Try setting TOP. This bazel() function cannot be used outside of the AOSP directory."
-        return
-    fi
-
-    "$T/tools/bazel" "$@"
-}
-
 function printconfig()
 {
     local T=$(gettop)
@@ -1564,12 +1551,10 @@
     fi
 }
 
-# List all modules for the current device, as cached in module-info.json. If any build change is
-# made and it should be reflected in the output, you should run 'refreshmod' first.
+# List all modules for the current device, as cached in all_modules.txt. If any build change is
+# made and it should be reflected in the output, you should run `m nothing` first.
 function allmod() {
-    verifymodinfo || return 1
-
-    python3 -c "import json; print('\n'.join(sorted(json.load(open('$ANDROID_PRODUCT_OUT/module-info.json')).keys())))"
+    cat $ANDROID_PRODUCT_OUT/all_modules.txt 2>/dev/null
 }
 
 # Return the Bazel label of a Soong module if it is converted with bp2build.
@@ -1586,9 +1571,9 @@
     #
     # For a snappy result, use the latest generated version in soong_injection,
     # and ask users to run m bp2build if it doesn't exist.
-    converted_json="out/soong/soong_injection/metrics/converted_modules_path_map.json"
+    converted_json="$(get_abs_build_var OUT_DIR)/soong/soong_injection/metrics/converted_modules_path_map.json"
 
-    if [ ! -f $(gettop)/${converted_json} ]; then
+    if [ ! -f ${converted_json} ]; then
       echo "bp2build files not found. Have you ran 'm bp2build'?" >&2
       return 1
     fi
@@ -1748,7 +1733,7 @@
 
 function _complete_android_module_names() {
     local word=${COMP_WORDS[COMP_CWORD]}
-    COMPREPLY=( $(QUIET_VERIFYMODINFO=true allmod | grep -E "^$word") )
+    COMPREPLY=( $(allmod | grep -E "^$word") )
 }
 
 # Print colored exit condition
@@ -1842,59 +1827,6 @@
     fi
 )
 
-# Convenience entry point (like m) to use Bazel in AOSP.
-function b()
-(
-    # zsh breaks posix by not doing string-splitting on unquoted args by default.
-    # See https://zsh.sourceforge.io/Guide/zshguide05.html section 5.4.4.
-    # Tell it to emulate Bourne shell for this function.
-    if [ -n "$ZSH_VERSION" ]; then emulate -L sh; fi
-
-    # Look for the --run-soong-tests flag and skip passing --skip-soong-tests to Soong if present
-    local bazel_args=""
-    local skip_tests="--skip-soong-tests"
-    for i in $@; do
-        if [[ $i != "--run-soong-tests" ]]; then
-            bazel_args+="$i "
-        else
-            skip_tests=""
-        fi
-    done
-
-    # Generate BUILD, bzl files into the synthetic Bazel workspace (out/soong/workspace).
-    # RBE is disabled because it's not used with b builds and adds overhead: b/251441524
-    USE_RBE=false _trigger_build "all-modules" bp2build $skip_tests USE_BAZEL_ANALYSIS= || return 1
-    # Then, run Bazel using the synthetic workspace as the --package_path.
-    if [[ -z "$bazel_args" ]]; then
-        # If there are no args, show help and exit.
-        bazel help
-    else
-        # Else, always run with the bp2build configuration, which sets Bazel's package path to the synthetic workspace.
-        # Add the --config=bp2build after the first argument that doesn't start with a dash. That should be the bazel
-        # command. (build, test, run, ect) If the --config was added at the end, it wouldn't work with commands like:
-        # b run //foo -- --args-for-foo
-        local config_set=0
-
-        # Represent the args as an array, not a string.
-        local bazel_args_with_config=()
-        for arg in $bazel_args; do
-            if [[ $arg == "--" && $config_set -ne 1 ]]; # if we find --, insert config argument here
-            then
-                bazel_args_with_config+=("--config=bp2build -- ")
-                config_set=1
-            else
-                bazel_args_with_config+=("$arg ")
-            fi
-        done
-        if [[ $config_set -ne 1 ]]; then
-            bazel_args_with_config+=("--config=bp2build ")
-        fi
-
-        # Call Bazel.
-        bazel ${bazel_args_with_config[@]}
-    fi
-)
-
 function m()
 (
     _trigger_build "all-modules" "$@"
@@ -2046,13 +1978,7 @@
             return
             ;;
     esac
-    if [[ -z "$OUT_DIR" ]]; then
-      if [[ -z "$OUT_DIR_COMMON_BASE" ]]; then
-        OUT_DIR=out
-      else
-        OUT_DIR=${OUT_DIR_COMMON_BASE}/${PWD##*/}
-      fi
-    fi
+    OUT_DIR="$(get_abs_build_var OUT_DIR)"
     if [[ "$1" == "--regenerate" ]]; then
       shift 1
       NINJA_ARGS="-t commands $@" m
diff --git a/finalize-aidl-vndk-sdk-resources.sh b/finalize-aidl-vndk-sdk-resources.sh
index e74ba71..8e12c49 100755
--- a/finalize-aidl-vndk-sdk-resources.sh
+++ b/finalize-aidl-vndk-sdk-resources.sh
@@ -23,9 +23,11 @@
         out/host/linux-x86/bin/create_reference_dumps \
         -p aosp_arm64 --build-variant user
 
+    echo "NOTE: THIS INTENTIONALLY MAY FAIL AND REPAIR ITSELF (until 'DONE')"
     # Update new versions of files. See update-vndk-list.sh (which requires envsetup.sh)
     $m check-vndk-list || \
         { cp $top/out/soong/vndk/vndk.libraries.txt $top/build/make/target/product/gsi/current.txt; }
+    echo "DONE: THIS INTENTIONALLY MAY FAIL AND REPAIR ITSELF"
 
     # Finalize resources
     "$top/frameworks/base/tools/aapt2/tools/finalize_res.py" \
diff --git a/target/board/Android.mk b/target/board/Android.mk
index baa3d3a..21c0c10 100644
--- a/target/board/Android.mk
+++ b/target/board/Android.mk
@@ -19,8 +19,11 @@
 ifndef board_info_txt
 board_info_txt := $(wildcard $(TARGET_DEVICE_DIR)/board-info.txt)
 endif
-$(INSTALLED_ANDROID_INFO_TXT_TARGET): $(board_info_txt) build/make/tools/check_radio_versions.py
-	$(hide) build/make/tools/check_radio_versions.py $< $(BOARD_INFO_CHECK)
+CHECK_RADIO_VERSIONS := $(HOST_OUT_EXECUTABLES)/check_radio_versions$(HOST_EXECUTABLE_SUFFIX)
+$(INSTALLED_ANDROID_INFO_TXT_TARGET): $(board_info_txt) $(CHECK_RADIO_VERSIONS)
+	$(hide) $(CHECK_RADIO_VERSIONS) \
+		--board_info_txt $(board_info_txt) \
+		--board_info_check $(BOARD_INFO_CHECK)
 	$(call pretty,"Generated: ($@)")
 ifdef board_info_txt
 	$(hide) grep -v '#' $< > $@
diff --git a/tests/b_tests.sh b/tests/b_tests.sh
index f4e043c..45cb4f7 100755
--- a/tests/b_tests.sh
+++ b/tests/b_tests.sh
@@ -18,6 +18,9 @@
 
 source $(dirname $0)/../envsetup.sh
 
+# lunch required to set up PATH to use b
+lunch aosp_arm64
+
 test_target=//build/bazel/scripts/difftool:difftool
 
 b build "$test_target"
diff --git a/tools/Android.bp b/tools/Android.bp
index bd326f1..1f0d406 100644
--- a/tools/Android.bp
+++ b/tools/Android.bp
@@ -54,3 +54,13 @@
   name: "build-runfiles",
   srcs: ["build-runfiles.cc"],
 }
+
+python_binary_host {
+  name: "check_radio_versions",
+  srcs: ["check_radio_versions.py"],
+}
+
+python_binary_host {
+  name: "check_elf_file",
+  srcs: ["check_elf_file.py"],
+}
diff --git a/tools/check_elf_file.py b/tools/check_elf_file.py
index 0b80226..eaa1854 100755
--- a/tools/check_elf_file.py
+++ b/tools/check_elf_file.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 #
 # Copyright (C) 2019 The Android Open Source Project
 #
@@ -196,11 +196,7 @@
   def _read_llvm_readobj(cls, elf_file_path, header, llvm_readobj):
     """Run llvm-readobj and parse the output."""
     cmd = [llvm_readobj, '--dynamic-table', '--dyn-symbols', elf_file_path]
-    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-    out, _ = proc.communicate()
-    rc = proc.returncode
-    if rc != 0:
-      raise subprocess.CalledProcessError(rc, cmd, out)
+    out = subprocess.check_output(cmd, text=True)
     lines = out.splitlines()
     return cls._parse_llvm_readobj(elf_file_path, header, lines)
 
@@ -467,7 +463,7 @@
     """Check whether all undefined symbols are resolved to a definition."""
     all_elf_files = [self._file_under_test] + self._shared_libs
     missing_symbols = []
-    for sym, imported_vers in self._file_under_test.imported.iteritems():
+    for sym, imported_vers in self._file_under_test.imported.items():
       for imported_ver in imported_vers:
         lib = self._find_symbol_from_libs(all_elf_files, sym, imported_ver)
         if not lib:
diff --git a/tools/check_radio_versions.py b/tools/check_radio_versions.py
index ebe621f..d1d50e6 100755
--- a/tools/check_radio_versions.py
+++ b/tools/check_radio_versions.py
@@ -22,11 +22,18 @@
 except ImportError:
   from sha import sha as sha1
 
-if len(sys.argv) < 2:
+import argparse
+
+parser = argparse.ArgumentParser()
+parser.add_argument("--board_info_txt", nargs="?", required=True)
+parser.add_argument("--board_info_check", nargs="*", required=True)
+args = parser.parse_args()
+
+if not args.board_info_txt:
   sys.exit(0)
 
 build_info = {}
-f = open(sys.argv[1])
+f = open(args.board_info_txt)
 for line in f:
   line = line.strip()
   if line.startswith("require"):
@@ -36,7 +43,7 @@
 
 bad = False
 
-for item in sys.argv[2:]:
+for item in args.board_info_check:
   key, fn = item.split(":", 1)
 
   values = build_info.get(key, None)
@@ -52,8 +59,8 @@
   try:
     f = open(fn + ".sha1")
   except IOError:
-    if not bad: print
-    print "*** Error opening \"%s.sha1\"; can't verify %s" % (fn, key)
+    if not bad: print()
+    print("*** Error opening \"%s.sha1\"; can't verify %s" % (fn, key))
     bad = True
     continue
   for line in f:
@@ -63,17 +70,17 @@
     versions[h] = v
 
   if digest not in versions:
-    if not bad: print
-    print "*** SHA-1 hash of \"%s\" doesn't appear in \"%s.sha1\"" % (fn, fn)
+    if not bad: print()
+    print("*** SHA-1 hash of \"%s\" doesn't appear in \"%s.sha1\"" % (fn, fn))
     bad = True
     continue
 
   if versions[digest] not in values:
-    if not bad: print
-    print "*** \"%s\" is version %s; not any %s allowed by \"%s\"." % (
-        fn, versions[digest], key, sys.argv[1])
+    if not bad: print()
+    print("*** \"%s\" is version %s; not any %s allowed by \"%s\"." % (
+        fn, versions[digest], key, args.board_info_txt))
     bad = True
 
 if bad:
-  print
+  print()
   sys.exit(1)
diff --git a/tools/compare_fileslist.py b/tools/compare_fileslist.py
deleted file mode 100755
index 1f507d8..0000000
--- a/tools/compare_fileslist.py
+++ /dev/null
@@ -1,106 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright (C) 2009 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.
-#
-
-import cgi, os, string, sys
-
-def IsDifferent(row):
-  val = None
-  for v in row:
-    if v:
-      if not val:
-        val = v
-      else:
-        if val != v:
-          return True
-  return False
-
-def main(argv):
-  inputs = argv[1:]
-  data = {}
-  index = 0
-  for input in inputs:
-    f = file(input, "r")
-    lines = f.readlines()
-    f.close()
-    lines = map(string.split, lines)
-    lines = map(lambda (x,y): (y,int(x)), lines)
-    for fn,sz in lines:
-      if not data.has_key(fn):
-        data[fn] = {}
-      data[fn][index] = sz
-    index = index + 1
-  rows = []
-  for fn,sizes in data.iteritems():
-    row = [fn]
-    for i in range(0,index):
-      if sizes.has_key(i):
-        row.append(sizes[i])
-      else:
-        row.append(None)
-    rows.append(row)
-  rows = sorted(rows, key=lambda x: x[0])
-  print """<html>
-    <head>
-      <style type="text/css">
-        .fn, .sz, .z, .d {
-          padding-left: 10px;
-          padding-right: 10px;
-        }
-        .sz, .z, .d {
-          text-align: right;
-        }
-        .fn {
-          background-color: #ffffdd;
-        }
-        .sz {
-          background-color: #ffffcc;
-        }
-        .z {
-          background-color: #ffcccc;
-        }
-        .d {
-          background-color: #99ccff;
-        }
-      </style>
-    </head>
-    <body>
-  """
-  print "<table>"
-  print "<tr>"
-  for input in inputs:
-    combo = input.split(os.path.sep)[1]
-    print "  <td class='fn'>%s</td>" % cgi.escape(combo)
-  print "</tr>"
-
-  for row in rows:
-    print "<tr>"
-    for sz in row[1:]:
-      if not sz:
-        print "  <td class='z'>&nbsp;</td>"
-      elif IsDifferent(row[1:]):
-        print "  <td class='d'>%d</td>" % sz
-      else:
-        print "  <td class='sz'>%d</td>" % sz
-    print "  <td class='fn'>%s</td>" % cgi.escape(row[0])
-    print "</tr>"
-  print "</table>"
-  print "</body></html>"
-
-if __name__ == '__main__':
-  main(sys.argv)
-
-
diff --git a/tools/compliance/README.md b/tools/compliance/README.md
new file mode 100644
index 0000000..995d9ca
--- /dev/null
+++ b/tools/compliance/README.md
@@ -0,0 +1,101 @@
+# Compliance
+
+<!-- Much of this content appears too in doc.go
+When changing this file consider whether the change also applies to doc.go -->
+
+Package compliance provides an approved means for reading, consuming, and
+analyzing license metadata graphs.
+
+Assuming the license metadata and dependencies are fully and accurately
+recorded in the build system, any discrepancy between the official policy for
+open source license compliance and this code is **a bug in this code.**
+
+## Naming
+
+All of the code that directly reflects a policy decision belongs in a file with
+a name begninning `policy_`. Changes to these files need to be authored or
+reviewed by someone in OSPO or whichever successor group governs policy.
+
+The files with names not beginning `policy_` describe data types, and general,
+reusable algorithms.
+
+The source code for binary tools and utilities appears under the `cmd/`
+subdirectory. Other subdirectories contain reusable components that are not
+`compliance` per se.
+
+## Data Types
+
+A few principal types to understand are LicenseGraph, LicenseCondition, and
+ResolutionSet.
+
+### LicenseGraph
+
+A LicenseGraph is an immutable graph of the targets and dependencies reachable
+from a specific set of root targets. In general, the root targets will be the
+artifacts in a release or distribution. While conceptually immutable, parts of
+the graph may be loaded or evaluated lazily.
+
+Conceptually, the graph itself will always be a directed acyclic graph. One
+representation is a set of directed edges. Another is a set of nodes with
+directed edges to their dependencies.
+
+The edges have annotations, which can distinguish between build tools, runtime
+dependencies, and dependencies like 'contains' that make a derivative work.
+
+### LicenseCondition
+
+A LicenseCondition is an immutable tuple pairing a condition name with an
+originating target. e.g. Per current policy, a static library licensed under an
+MIT license would pair a "notice" condition with the static library target, and
+a dynamic license licensed under GPL would pair a "restricted" condition with
+the dynamic library target.
+
+### ResolutionSet
+
+A ResolutionSet is an immutable set of `AttachesTo`, `ActsOn`, `Resolves`
+tuples describing how license conditions apply to targets.
+
+`AttachesTo` is the trigger for acting. Distribution of the target invokes
+the policy.
+
+`ActsOn` is the target to share, give notice for, hide etc.
+
+`Resolves` is the set of conditions that the action resolves.
+
+For most condition types, `ActsOn` will be the target where the condition
+originated. For example, a notice condition policy means attribution or notice
+must be given for the target where the condition originates. Likewise, a
+proprietary condition policy means the privacy of the target where the
+condition originates must be respected. i.e. The thing acted on is the origin.
+
+Restricted conditions are different. The infectious nature of restricted often
+means sharing code that is not the target where the restricted condition
+originates. Linking an MIT library to a GPL library implies a policy to share
+the MIT library despite the MIT license having no source sharing requirement.
+
+In this case, one or more resolution tuples will have the MIT license module in
+`ActsOn` and the restricted condition originating at the GPL library module in
+`Resolves`. These tuples will `AttachTo` every target that depends on the GPL
+library because shipping any of those targets trigger the policy to share the
+code.
+
+## Processes
+
+### ReadLicenseGraph
+
+The principal means to ingest license metadata. Given the distribution targets,
+ReadLicenseGraph populates the LicenseGraph for those root targets.
+
+### NoticeIndex.IndexLicenseTexts
+
+IndexLicenseTexts reads, deduplicates and caches license texts for notice
+files. Also reads and caches project metadata for deriving library names.
+
+The algorithm for deriving library names has not been dictated by OSPO policy,
+but reflects a pragmatic attempt to comply with Android policy regarding
+unreleased product names, proprietary partner names etc.
+
+### projectmetadata.Index.MetadataForProjects
+
+MetadataForProjects reads, deduplicates and caches project METADATA files used
+for notice library names, and various properties appearing in SBOMs.
diff --git a/tools/compliance/cmd/testdata/proprietary/bin/bin3.meta_lic b/tools/compliance/cmd/testdata/proprietary/bin/bin3.meta_lic
index 7ef14e9..a7c3d01 100644
--- a/tools/compliance/cmd/testdata/proprietary/bin/bin3.meta_lic
+++ b/tools/compliance/cmd/testdata/proprietary/bin/bin3.meta_lic
@@ -2,7 +2,7 @@
 module_classes: "EXECUTABLES"
 projects:  "standalone/binary"
 license_kinds:  "SPDX-license-identifier-LGPL-2.0"
-license_conditions:  "restricted"
+license_conditions:  "restricted_allows_dynamic_linking"
 license_texts:  "testdata/restricted/RESTRICTED_LICENSE"
 is_container:  false
 built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin3"
diff --git a/tools/compliance/cmd/testdata/restricted/bin/bin3.meta_lic b/tools/compliance/cmd/testdata/restricted/bin/bin3.meta_lic
index 7ef14e9..a7c3d01 100644
--- a/tools/compliance/cmd/testdata/restricted/bin/bin3.meta_lic
+++ b/tools/compliance/cmd/testdata/restricted/bin/bin3.meta_lic
@@ -2,7 +2,7 @@
 module_classes: "EXECUTABLES"
 projects:  "standalone/binary"
 license_kinds:  "SPDX-license-identifier-LGPL-2.0"
-license_conditions:  "restricted"
+license_conditions:  "restricted_allows_dynamic_linking"
 license_texts:  "testdata/restricted/RESTRICTED_LICENSE"
 is_container:  false
 built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin3"
diff --git a/tools/compliance/cmd/testdata/restricted/lib/liba.so.meta_lic b/tools/compliance/cmd/testdata/restricted/lib/liba.so.meta_lic
index a505d4a..101ca19 100644
--- a/tools/compliance/cmd/testdata/restricted/lib/liba.so.meta_lic
+++ b/tools/compliance/cmd/testdata/restricted/lib/liba.so.meta_lic
@@ -1,7 +1,7 @@
 package_name:  "Device"
 projects:  "device/library"
 license_kinds:  "SPDX-license-identifier-LGPL-2.0"
-license_conditions:  "restricted"
+license_conditions:  "restricted_allows_dynamic_linking"
 license_texts:  "testdata/restricted/RESTRICTED_LICENSE"
 is_container:  false
 built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/liba.so"
diff --git a/tools/compliance/doc.go b/tools/compliance/doc.go
index a47c1cf..5ced9ee 100644
--- a/tools/compliance/doc.go
+++ b/tools/compliance/doc.go
@@ -11,6 +11,10 @@
 // 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.
+
+// Much of this content appears too in README.md
+// When changing this file consider whether the change also applies to README.md
+
 /*
 
 Package compliance provides an approved means for reading, consuming, and
@@ -31,6 +35,13 @@
 artifacts in a release or distribution. While conceptually immutable, parts of
 the graph may be loaded or evaluated lazily.
 
+Conceptually, the graph itself will always be a directed acyclic graph. One
+representation is a set of directed edges. Another is a set of nodes with
+directed edges to their dependencies.
+
+The edges have annotations, which can distinguish between build tools, runtime
+dependencies, and dependencies like 'contains' that make a derivative work.
+
 LicenseCondition
 ----------------
 
@@ -51,17 +62,13 @@
 
 `ActsOn` is the target to share, give notice for, hide etc.
 
-`Resolves` is the license condition that the action resolves.
+`Resolves` is the set of condition types that the action resolves.
 
-Remember: Each license condition pairs a condition name with an originating
-target so each resolution in a ResolutionSet has two targets it applies to and
-one target from which it originates, all of which may be the same target.
-
-For most condition types, `ActsOn` and `Resolves.Origin` will be the same
-target. For example, a notice condition policy means attribution or notice must
-be given for the target where the condition originates. Likewise, a proprietary
-condition policy means the privacy of the target where the condition originates
-must be respected. i.e. The thing acted on is the origin.
+For most condition types, `ActsOn` will be the target where the condition
+originated. For example, a notice condition policy means attribution or notice
+must be given for the target where the condition originates. Likewise, a
+proprietary condition policy means the privacy of the target where the
+condition originates must be respected. i.e. The thing acted on is the origin.
 
 Restricted conditions are different. The infectious nature of restricted often
 means sharing code that is not the target where the restricted condition
diff --git a/tools/compliance/graph.go b/tools/compliance/graph.go
index e73ab46..e69f3e2 100644
--- a/tools/compliance/graph.go
+++ b/tools/compliance/graph.go
@@ -300,23 +300,6 @@
 	return tn.proto.GetPackageName()
 }
 
-// ModuleTypes returns the list of module types implementing the target.
-// (unordered)
-//
-// In an ideal world, only 1 module type would implement each target, but the
-// interactions between Soong and Make for host versus product and for a
-// variety of architectures sometimes causes multiple module types per target
-// (often a regular build target and a prebuilt.)
-func (tn *TargetNode) ModuleTypes() []string {
-	return append([]string{}, tn.proto.ModuleTypes...)
-}
-
-// ModuleClasses returns the list of module classes implementing the target.
-// (unordered)
-func (tn *TargetNode) ModuleClasses() []string {
-	return append([]string{}, tn.proto.ModuleClasses...)
-}
-
 // Projects returns the projects defining the target node. (unordered)
 //
 // In an ideal world, only 1 project defines a target, but the interaction
@@ -326,14 +309,6 @@
 	return append([]string{}, tn.proto.Projects...)
 }
 
-// LicenseKinds returns the list of license kind names for the module or
-// target. (unordered)
-//
-// e.g. SPDX-license-identifier-MIT or legacy_proprietary
-func (tn *TargetNode) LicenseKinds() []string {
-	return append([]string{}, tn.proto.LicenseKinds...)
-}
-
 // LicenseConditions returns a copy of the set of license conditions
 // originating at the target. The values that appear and how each is resolved
 // is a matter of policy. (unordered)
diff --git a/tools/compliance/policy_policy.go b/tools/compliance/policy_policy.go
index 02d1d96..23e25c6 100644
--- a/tools/compliance/policy_policy.go
+++ b/tools/compliance/policy_policy.go
@@ -117,32 +117,6 @@
 func LicenseConditionSetFromNames(tn *TargetNode, names ...string) LicenseConditionSet {
 	cs := NewLicenseConditionSet()
 	for _, name := range names {
-		if name == "restricted" {
-			if 0 == len(tn.LicenseKinds()) {
-				cs = cs.Plus(RestrictedCondition)
-				continue
-			}
-			hasLgpl := false
-			hasGeneric := false
-			for _, kind := range tn.LicenseKinds() {
-				if anyLgpl.MatchString(kind) {
-					cs = cs.Plus(WeaklyRestrictedCondition)
-					hasLgpl = true
-				} else if versionedGpl.MatchString(kind) {
-					cs = cs.Plus(RestrictedCondition)
-				} else if genericGpl.MatchString(kind) {
-					hasGeneric = true
-				} else if kind == "legacy_restricted" || ccBySa.MatchString(kind) {
-					cs = cs.Plus(RestrictedCondition)
-				} else {
-					cs = cs.Plus(RestrictedCondition)
-				}
-			}
-			if hasGeneric && !hasLgpl {
-				cs = cs.Plus(RestrictedCondition)
-			}
-			continue
-		}
 		if lc, ok := RecognizedConditionNames[name]; ok {
 			cs |= LicenseConditionSet(lc)
 		}
diff --git a/tools/compliance/policy_resolvenotices_test.go b/tools/compliance/policy_resolvenotices_test.go
index ee5e3b8..92b0ce3 100644
--- a/tools/compliance/policy_resolvenotices_test.go
+++ b/tools/compliance/policy_resolvenotices_test.go
@@ -217,10 +217,10 @@
 			},
 			expectedResolutions: []res{
 				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
 				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "mitLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
 			},
 		},
 		{
@@ -245,7 +245,7 @@
 			expectedResolutions: []res{
 				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
 				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
-				{"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"},
+				{"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted_allows_dynamic_linking"},
 			},
 		},
 		{
@@ -258,17 +258,17 @@
 			},
 			expectedResolutions: []res{
 				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
 				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"},
-				{"apacheContainer.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
+				{"apacheContainer.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
 				{"apacheContainer.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "mitLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "mitLib.meta_lic", "lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
 				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"},
-				{"apacheBin.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
 				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
-				{"apacheBin.meta_lic", "mitLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
 			},
 		},
 		{
@@ -280,11 +280,11 @@
 			},
 			expectedResolutions: []res{
 				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
 				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"apacheContainer.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
 				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
 			},
 		},
 		{
@@ -309,7 +309,7 @@
 			expectedResolutions: []res{
 				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
 				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
-				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
 			},
 		},
 		{
@@ -336,7 +336,7 @@
 				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
 				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
 				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
 			},
 		},
 		{
@@ -363,7 +363,7 @@
 				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
 				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
 				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
-				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
 			},
 		},
 		{
diff --git a/tools/compliance/policy_resolveshare_test.go b/tools/compliance/policy_resolveshare_test.go
index d49dfa8..cf88058 100644
--- a/tools/compliance/policy_resolveshare_test.go
+++ b/tools/compliance/policy_resolveshare_test.go
@@ -73,8 +73,8 @@
 				{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
 			},
 			expectedResolutions: []res{
-				{"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"},
-				{"lgplBin.meta_lic", "apacheLib.meta_lic", "lgplBin.meta_lic", "restricted"},
+				{"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted_allows_dynamic_linking"},
+				{"lgplBin.meta_lic", "apacheLib.meta_lic", "lgplBin.meta_lic", "restricted_allows_dynamic_linking"},
 			},
 		},
 		{
@@ -84,7 +84,7 @@
 				{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
 			},
 			expectedResolutions: []res{
-				{"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"},
+				{"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted_allows_dynamic_linking"},
 			},
 		},
 		{
@@ -94,7 +94,7 @@
 				{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
 			},
 			expectedResolutions: []res{
-				{"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"},
+				{"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted_allows_dynamic_linking"},
 			},
 		},
 		{
diff --git a/tools/compliance/projectmetadata/projectmetadata.go b/tools/compliance/projectmetadata/projectmetadata.go
index b31413d..f9ddbae 100644
--- a/tools/compliance/projectmetadata/projectmetadata.go
+++ b/tools/compliance/projectmetadata/projectmetadata.go
@@ -40,11 +40,39 @@
 	project string
 }
 
+// ProjectUrlMap maps url type name to url value
+type ProjectUrlMap map[string]string
+
+// DownloadUrl returns the address of a download location
+func (m ProjectUrlMap) DownloadUrl() string {
+	for _, urlType := range []string{"GIT", "SVN", "HG", "DARCS"} {
+		if url, ok := m[urlType]; ok {
+			return url
+		}
+	}
+	return ""
+}
+
 // String returns a string representation of the metadata for error messages.
 func (pm *ProjectMetadata) String() string {
 	return fmt.Sprintf("project: %q\n%s", pm.project, pm.proto.String())
 }
 
+// ProjectName returns the name of the project.
+func (pm *ProjectMetadata) Name() string {
+	return pm.proto.GetName()
+}
+
+// ProjectVersion returns the version of the project if available.
+func (pm *ProjectMetadata) Version() string {
+	tp := pm.proto.GetThirdParty()
+	if tp != nil {
+		version := tp.GetVersion()
+		return version
+	}
+	return ""
+}
+
 // VersionedName returns the name of the project including the version if any.
 func (pm *ProjectMetadata) VersionedName() string {
 	name := pm.proto.GetName()
@@ -65,13 +93,35 @@
 	return pm.proto.GetDescription()
 }
 
+// UrlsByTypeName returns a map of URLs by Type Name
+func (pm *ProjectMetadata) UrlsByTypeName() ProjectUrlMap {
+	tp := pm.proto.GetThirdParty()
+	if tp == nil {
+		return nil
+	}
+	if len(tp.Url) == 0 {
+		return nil
+	}
+	urls := make(ProjectUrlMap)
+
+	for _, url := range tp.Url {
+		uri := url.GetValue()
+		if uri == "" {
+			continue
+		}
+		urls[project_metadata_proto.URL_Type_name[int32(url.GetType())]] = uri
+	}
+	return urls
+}
+
 // projectIndex describes a project to be read; after `wg.Wait()`, will contain either
 // a `ProjectMetadata`, pm (can be nil even without error), or a non-nil `err`.
 type projectIndex struct {
 	project string
-	pm *ProjectMetadata
-	err error
-	done chan struct{}
+	path    string
+	pm      *ProjectMetadata
+	err     error
+	done    chan struct{}
 }
 
 // finish marks the task to read the `projectIndex` completed.
@@ -181,6 +231,19 @@
 	return result, nil
 }
 
+// AllMetadataFiles returns the sorted list of all METADATA files read thus far.
+func (ix *Index) AllMetadataFiles() []string {
+	files := []string(nil)
+	ix.projects.Range(func(key, value any) bool {
+		pi := value.(*projectIndex)
+		if pi.path != "" {
+			files = append(files, pi.path)
+		}
+		return true
+	})
+	return files
+}
+
 // readMetadataFile tries to read and parse a METADATA file at `path` for `project`.
 func (ix *Index) readMetadataFile(pi *projectIndex, path string) {
 	f, err := ix.rootFS.Open(path)
@@ -201,9 +264,24 @@
 	pm := &ProjectMetadata{project: pi.project}
 	err = uo.Unmarshal(data, &pm.proto)
 	if err != nil {
-		pi.err = fmt.Errorf("error in project %q metadata %q: %w", pi.project, path, err)
+		pi.err = fmt.Errorf(`error in project %q METADATA %q: %v
+
+METADATA and METADATA.android files must parse as text protobufs
+defined by
+   build/soong/compliance/project_metadata_proto/project_metadata.proto
+
+* unknown fields don't matter
+* check invalid ENUM names
+* check quoting
+* check unescaped nested quotes
+* check the comment marker for protobuf is '#' not '//'
+
+if importing a library that uses a different sort of METADATA file, add
+a METADATA.android file beside it to parse instead
+`, pi.project, path, err)
 		return
 	}
 
+	pi.path = path
 	pi.pm = pm
 }
diff --git a/tools/compliance/projectmetadata/projectmetadata_test.go b/tools/compliance/projectmetadata/projectmetadata_test.go
index 1e4256f..0af0cd7 100644
--- a/tools/compliance/projectmetadata/projectmetadata_test.go
+++ b/tools/compliance/projectmetadata/projectmetadata_test.go
@@ -19,6 +19,7 @@
 	"strings"
 	"testing"
 
+	"android/soong/compliance/project_metadata_proto"
 	"android/soong/tools/compliance/testfs"
 )
 
@@ -40,8 +41,79 @@
 
 	// NO_NAME_0_1 represents a METADATA file with a description but no name
 	NO_NAME_0_1 = `description: "my library" third_party { version: "0.1" }`
+
+	// URL values per type
+	GIT_URL          = "http://example.github.com/my_lib"
+	SVN_URL          = "http://example.svn.com/my_lib"
+	HG_URL           = "http://example.hg.com/my_lib"
+	DARCS_URL        = "http://example.darcs.com/my_lib"
+	PIPER_URL        = "http://google3/third_party/my/package"
+	HOMEPAGE_URL     = "http://example.com/homepage"
+	OTHER_URL        = "http://google.com/"
+	ARCHIVE_URL      = "http://ftp.example.com/"
+	LOCAL_SOURCE_URL = "https://android.googlesource.com/platform/external/apache-http/"
 )
 
+// libWithUrl returns a METADATA file with the right download url
+func libWithUrl(urlTypes ...string) string {
+	var sb strings.Builder
+
+	fmt.Fprintln(&sb, `name: "mylib" description: "my library"
+	 third_party {
+	 	version: "1.0"`)
+
+	for _, urltype := range urlTypes {
+		var urlValue string
+		switch urltype {
+		case "GIT":
+			urlValue = GIT_URL
+		case "SVN":
+			urlValue = SVN_URL
+		case "HG":
+			urlValue = HG_URL
+		case "DARCS":
+			urlValue = DARCS_URL
+		case "PIPER":
+			urlValue = PIPER_URL
+		case "HOMEPAGE":
+			urlValue = HOMEPAGE_URL
+		case "OTHER":
+			urlValue = OTHER_URL
+		case "ARCHIVE":
+			urlValue = ARCHIVE_URL
+		case "LOCAL_SOURCE":
+			urlValue = LOCAL_SOURCE_URL
+		default:
+			panic(fmt.Errorf("unknown url type: %q. Please update libWithUrl() in build/make/tools/compliance/projectmetadata/projectmetadata_test.go", urltype))
+		}
+		fmt.Fprintf(&sb, "  url { type: %s value: %q }\n", urltype, urlValue)
+	}
+	fmt.Fprintln(&sb, `}`)
+
+	return sb.String()
+}
+
+func TestVerifyAllUrlTypes(t *testing.T) {
+	t.Run("verifyAllUrlTypes", func(t *testing.T) {
+		types := make([]string, 0, len(project_metadata_proto.URL_Type_value))
+		for t := range project_metadata_proto.URL_Type_value {
+			types = append(types, t)
+		}
+		libWithUrl(types...)
+	})
+}
+
+func TestUnknownPanics(t *testing.T) {
+	t.Run("Unknown panics", func(t *testing.T) {
+		defer func() {
+			if r := recover(); r == nil {
+				t.Errorf("unexpected success: got no error, want panic")
+			}
+		}()
+		libWithUrl("SOME WILD VALUE THAT DOES NOT EXIST")
+	})
+}
+
 func TestReadMetadataForProjects(t *testing.T) {
 	tests := []struct {
 		name          string
@@ -56,7 +128,13 @@
 				"/a/METADATA": []byte("name: \"Android\"\n"),
 			},
 			projects: []string{"/a"},
-			expected: []pmeta{{project: "/a", versionedName: "Android"}},
+			expected: []pmeta{{
+				project:       "/a",
+				versionedName: "Android",
+				name:          "Android",
+				version:       "",
+				downloadUrl:   "",
+			}},
 		},
 		{
 			name: "versioned",
@@ -64,7 +142,237 @@
 				"/a/METADATA": []byte(MY_LIB_1_0),
 			},
 			projects: []string{"/a"},
-			expected: []pmeta{{project: "/a", versionedName: "mylib_v_1.0"}},
+			expected: []pmeta{{
+				project:       "/a",
+				versionedName: "mylib_v_1.0",
+				name:          "mylib",
+				version:       "1.0",
+				downloadUrl:   "",
+			}},
+		},
+		{
+			name: "lib_with_homepage",
+			fs: &testfs.TestFS{
+				"/a/METADATA": []byte(libWithUrl("HOMEPAGE")),
+			},
+			projects: []string{"/a"},
+			expected: []pmeta{{
+				project:       "/a",
+				versionedName: "mylib_v_1.0",
+				name:          "mylib",
+				version:       "1.0",
+				downloadUrl:   "",
+			}},
+		},
+		{
+			name: "lib_with_git",
+			fs: &testfs.TestFS{
+				"/a/METADATA": []byte(libWithUrl("GIT")),
+			},
+			projects: []string{"/a"},
+			expected: []pmeta{{
+				project:       "/a",
+				versionedName: "mylib_v_1.0",
+				name:          "mylib",
+				version:       "1.0",
+				downloadUrl:   GIT_URL,
+			}},
+		},
+		{
+			name: "lib_with_svn",
+			fs: &testfs.TestFS{
+				"/a/METADATA": []byte(libWithUrl("SVN")),
+			},
+			projects: []string{"/a"},
+			expected: []pmeta{{
+				project:       "/a",
+				versionedName: "mylib_v_1.0",
+				name:          "mylib",
+				version:       "1.0",
+				downloadUrl:   SVN_URL,
+			}},
+		},
+		{
+			name: "lib_with_hg",
+			fs: &testfs.TestFS{
+				"/a/METADATA": []byte(libWithUrl("HG")),
+			},
+			projects: []string{"/a"},
+			expected: []pmeta{{
+				project:       "/a",
+				versionedName: "mylib_v_1.0",
+				name:          "mylib",
+				version:       "1.0",
+				downloadUrl:   HG_URL,
+			}},
+		},
+		{
+			name: "lib_with_darcs",
+			fs: &testfs.TestFS{
+				"/a/METADATA": []byte(libWithUrl("DARCS")),
+			},
+			projects: []string{"/a"},
+			expected: []pmeta{{
+				project:       "/a",
+				versionedName: "mylib_v_1.0",
+				name:          "mylib",
+				version:       "1.0",
+				downloadUrl:   DARCS_URL,
+			}},
+		},
+		{
+			name: "lib_with_piper",
+			fs: &testfs.TestFS{
+				"/a/METADATA": []byte(libWithUrl("PIPER")),
+			},
+			projects: []string{"/a"},
+			expected: []pmeta{{
+				project:       "/a",
+				versionedName: "mylib_v_1.0",
+				name:          "mylib",
+				version:       "1.0",
+				downloadUrl:   "",
+			}},
+		},
+		{
+			name: "lib_with_other",
+			fs: &testfs.TestFS{
+				"/a/METADATA": []byte(libWithUrl("OTHER")),
+			},
+			projects: []string{"/a"},
+			expected: []pmeta{{
+				project:       "/a",
+				versionedName: "mylib_v_1.0",
+				name:          "mylib",
+				version:       "1.0",
+				downloadUrl:   "",
+			}},
+		},
+		{
+			name: "lib_with_local_source",
+			fs: &testfs.TestFS{
+				"/a/METADATA": []byte(libWithUrl("LOCAL_SOURCE")),
+			},
+			projects: []string{"/a"},
+			expected: []pmeta{{
+				project:       "/a",
+				versionedName: "mylib_v_1.0",
+				name:          "mylib",
+				version:       "1.0",
+				downloadUrl:   "",
+			}},
+		},
+		{
+			name: "lib_with_archive",
+			fs: &testfs.TestFS{
+				"/a/METADATA": []byte(libWithUrl("ARCHIVE")),
+			},
+			projects: []string{"/a"},
+			expected: []pmeta{{
+				project:       "/a",
+				versionedName: "mylib_v_1.0",
+				name:          "mylib",
+				version:       "1.0",
+				downloadUrl:   "",
+			}},
+		},
+		{
+			name: "lib_with_all_downloads",
+			fs: &testfs.TestFS{
+				"/a/METADATA": []byte(libWithUrl("DARCS", "HG", "SVN", "GIT")),
+			},
+			projects: []string{"/a"},
+			expected: []pmeta{{
+				project:       "/a",
+				versionedName: "mylib_v_1.0",
+				name:          "mylib",
+				version:       "1.0",
+				downloadUrl:   GIT_URL,
+			}},
+		},
+		{
+			name: "lib_with_all_downloads_in_different_order",
+			fs: &testfs.TestFS{
+				"/a/METADATA": []byte(libWithUrl("DARCS", "GIT", "SVN", "HG")),
+			},
+			projects: []string{"/a"},
+			expected: []pmeta{{
+				project:       "/a",
+				versionedName: "mylib_v_1.0",
+				name:          "mylib",
+				version:       "1.0",
+				downloadUrl:   GIT_URL,
+			}},
+		},
+		{
+			name: "lib_with_all_but_git",
+			fs: &testfs.TestFS{
+				"/a/METADATA": []byte(libWithUrl("DARCS", "HG", "SVN")),
+			},
+			projects: []string{"/a"},
+			expected: []pmeta{{
+				project:       "/a",
+				versionedName: "mylib_v_1.0",
+				name:          "mylib",
+				version:       "1.0",
+				downloadUrl:   SVN_URL,
+			}},
+		},
+		{
+			name: "lib_with_all_but_git_and_svn",
+			fs: &testfs.TestFS{
+				"/a/METADATA": []byte(libWithUrl("DARCS", "HG")),
+			},
+			projects: []string{"/a"},
+			expected: []pmeta{{
+				project:       "/a",
+				versionedName: "mylib_v_1.0",
+				name:          "mylib",
+				version:       "1.0",
+				downloadUrl:   HG_URL,
+			}},
+		},
+		{
+			name: "lib_with_all_nondownloads_and_git",
+			fs: &testfs.TestFS{
+				"/a/METADATA": []byte(libWithUrl("HOMEPAGE", "LOCAL_SOURCE", "PIPER", "ARCHIVE", "GIT")),
+			},
+			projects: []string{"/a"},
+			expected: []pmeta{{
+				project:       "/a",
+				versionedName: "mylib_v_1.0",
+				name:          "mylib",
+				version:       "1.0",
+				downloadUrl:   GIT_URL,
+			}},
+		},
+		{
+			name: "lib_with_all_nondownloads",
+			fs: &testfs.TestFS{
+				"/a/METADATA": []byte(libWithUrl("HOMEPAGE", "LOCAL_SOURCE", "PIPER", "ARCHIVE")),
+			},
+			projects: []string{"/a"},
+			expected: []pmeta{{
+				project:       "/a",
+				versionedName: "mylib_v_1.0",
+				name:          "mylib",
+				version:       "1.0",
+				downloadUrl:   "",
+			}},
+		},
+		{
+			name: "lib_with_all_nondownloads",
+			fs: &testfs.TestFS{
+				"/a/METADATA": []byte(libWithUrl()),
+			},
+			projects: []string{"/a"},
+			expected: []pmeta{{
+				project:       "/a",
+				versionedName: "mylib_v_1.0",
+				name:          "mylib",
+				version:       "1.0",
+				downloadUrl:   "",
+			}},
 		},
 		{
 			name: "versioneddesc",
@@ -72,7 +380,13 @@
 				"/a/METADATA": []byte(NO_NAME_0_1),
 			},
 			projects: []string{"/a"},
-			expected: []pmeta{{project: "/a", versionedName: "my library"}},
+			expected: []pmeta{{
+				project:       "/a",
+				versionedName: "my library",
+				name:          "",
+				version:       "0.1",
+				downloadUrl:   "",
+			}},
 		},
 		{
 			name: "unterminated",
@@ -91,9 +405,27 @@
 			},
 			projects: []string{"/a", "/b", "/c"},
 			expected: []pmeta{
-				{project: "/a", versionedName: ""},
-				{project: "/b", versionedName: "mylib_v_1.0"},
-				{project: "/c", versionedName: "my library"},
+				{
+					project:       "/a",
+					versionedName: "",
+					name:          "",
+					version:       "",
+					downloadUrl:   "",
+				},
+				{
+					project:       "/b",
+					versionedName: "mylib_v_1.0",
+					name:          "mylib",
+					version:       "1.0",
+					downloadUrl:   "",
+				},
+				{
+					project:       "/c",
+					versionedName: "my library",
+					name:          "",
+					version:       "0.1",
+					downloadUrl:   "",
+				},
 			},
 		},
 		{
@@ -104,8 +436,20 @@
 			},
 			projects: []string{"/a", "/b", "/c"},
 			expected: []pmeta{
-				{project: "/a", versionedName: ""},
-				{project: "/b", versionedName: "mylib_v_1.0"},
+				{
+					project:       "/a",
+					versionedName: "",
+					name:          "",
+					version:       "",
+					downloadUrl:   "",
+				},
+				{
+					project:       "/b",
+					versionedName: "mylib_v_1.0",
+					name:          "mylib",
+					version:       "1.0",
+					downloadUrl:   "",
+				},
 			},
 		},
 		{
@@ -116,8 +460,20 @@
 			},
 			projects: []string{"/a", "/b", "/c"},
 			expected: []pmeta{
-				{project: "/a", versionedName: ""},
-				{project: "/c", versionedName: "my library"},
+				{
+					project:       "/a",
+					versionedName: "",
+					name:          "",
+					version:       "",
+					downloadUrl:   "",
+				},
+				{
+					project:       "/c",
+					versionedName: "my library",
+					name:          "",
+					version:       "0.1",
+					downloadUrl:   "",
+				},
 			},
 		},
 		{
@@ -128,8 +484,20 @@
 			},
 			projects: []string{"/a", "/b", "/c"},
 			expected: []pmeta{
-				{project: "/b", versionedName: "mylib_v_1.0"},
-				{project: "/c", versionedName: "my library"},
+				{
+					project:       "/b",
+					versionedName: "mylib_v_1.0",
+					name:          "mylib",
+					version:       "1.0",
+					downloadUrl:   "",
+				},
+				{
+					project:       "/c",
+					versionedName: "my library",
+					name:          "",
+					version:       "0.1",
+					downloadUrl:   "",
+				},
 			},
 		},
 		{
@@ -170,7 +538,13 @@
 				"/a/METADATA": []byte(EMPTY),
 			},
 			projects: []string{"/a"},
-			expected: []pmeta{{project: "/a", versionedName: ""}},
+			expected: []pmeta{{
+				project:       "/a",
+				versionedName: "",
+				name:          "",
+				version:       "",
+				downloadUrl:   "",
+			}},
 		},
 		{
 			name: "emptyother",
@@ -191,7 +565,13 @@
 				"/a/METADATA.android": []byte(MY_LIB_1_0),
 			},
 			projects: []string{"/a"},
-			expected: []pmeta{{project: "/a", versionedName: "mylib_v_1.0"}},
+			expected: []pmeta{{
+				project:       "/a",
+				versionedName: "mylib_v_1.0",
+				name:          "mylib",
+				version:       "1.0",
+				downloadUrl:   "",
+			}},
 		},
 		{
 			name: "enchilada",
@@ -203,9 +583,27 @@
 			},
 			projects: []string{"/a", "/b", "/c"},
 			expected: []pmeta{
-				{project: "/a", versionedName: ""},
-				{project: "/b", versionedName: "mylib_v_1.0"},
-				{project: "/c", versionedName: "my library"},
+				{
+					project:       "/a",
+					versionedName: "",
+					name:          "",
+					version:       "",
+					downloadUrl:   "",
+				},
+				{
+					project:       "/b",
+					versionedName: "mylib_v_1.0",
+					name:          "mylib",
+					version:       "1.0",
+					downloadUrl:   "",
+				},
+				{
+					project:       "/c",
+					versionedName: "my library",
+					name:          "",
+					version:       "0.1",
+					downloadUrl:   "",
+				},
 			},
 		},
 	}
@@ -255,10 +653,13 @@
 type pmeta struct {
 	project       string
 	versionedName string
+	name          string
+	version       string
+	downloadUrl   string
 }
 
 func (pm pmeta) String() string {
-	return fmt.Sprintf("project: %q versionedName: %q\n", pm.project, pm.versionedName)
+	return fmt.Sprintf("project: %q versionedName: %q name: %q version: %q downloadUrl: %q\n", pm.project, pm.versionedName, pm.name, pm.version, pm.downloadUrl)
 }
 
 func (pm pmeta) equals(other *ProjectMetadata) bool {
@@ -268,6 +669,15 @@
 	if pm.versionedName != other.VersionedName() {
 		return false
 	}
+	if pm.name != other.Name() {
+		return false
+	}
+	if pm.version != other.Version() {
+		return false
+	}
+	if pm.downloadUrl != other.UrlsByTypeName().DownloadUrl() {
+		return false
+	}
 	return true
 }
 
@@ -283,6 +693,15 @@
 	if pm.versionedName != other.VersionedName() {
 		fmt.Fprintf(&sb, " versionedName: %q", other.VersionedName())
 	}
+	if pm.name != other.Name() {
+		fmt.Fprintf(&sb, " name: %q", other.Name())
+	}
+	if pm.version != other.Version() {
+		fmt.Fprintf(&sb, " version: %q", other.Version())
+	}
+	if pm.downloadUrl != other.UrlsByTypeName().DownloadUrl() {
+		fmt.Fprintf(&sb, " downloadUrl: %q", other.UrlsByTypeName().DownloadUrl())
+	}
 	fmt.Fprintf(&sb, ", want")
 	if pm.project != other.project {
 		fmt.Fprintf(&sb, " project: %q", pm.project)
@@ -290,5 +709,14 @@
 	if pm.versionedName != other.VersionedName() {
 		fmt.Fprintf(&sb, " versionedName: %q", pm.versionedName)
 	}
+	if pm.name != other.Name() {
+		fmt.Fprintf(&sb, " name: %q", pm.name)
+	}
+	if pm.version != other.Version() {
+		fmt.Fprintf(&sb, " version: %q", pm.version)
+	}
+	if pm.downloadUrl != other.UrlsByTypeName().DownloadUrl() {
+		fmt.Fprintf(&sb, " downloadUrl: %q", pm.downloadUrl)
+	}
 	return sb.String()
 }
diff --git a/tools/compliance/test_util.go b/tools/compliance/test_util.go
index 6c50d3e..db711a7 100644
--- a/tools/compliance/test_util.go
+++ b/tools/compliance/test_util.go
@@ -57,7 +57,7 @@
 	LGPL = `` +
 		`package_name: "Free Library"
 license_kinds: "SPDX-license-identifier-LGPL-2.0"
-license_conditions: "restricted"
+license_conditions: "restricted_allows_dynamic_linking"
 `
 
 	// MPL starts a test metadata file for a module with MPL 2.0 reciprical licensing.
diff --git a/tools/fileslist_util.py b/tools/fileslist_util.py
index ff40d51..a1b1197 100755
--- a/tools/fileslist_util.py
+++ b/tools/fileslist_util.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 #
 # Copyright (C) 2016 The Android Open Source Project
 #
@@ -15,7 +15,9 @@
 # limitations under the License.
 #
 
-import getopt, json, sys
+import argparse
+import json
+import sys
 
 def PrintFileNames(path):
   with open(path) as jf:
@@ -27,42 +29,25 @@
   with open(path) as jf:
     data = json.load(jf)
   for line in data:
-    print "{0:12d}  {1}".format(line["Size"], line["Name"])
+    print(f"{line['Size']:12d}  {line['Name']}")
 
-def PrintUsage(name):
-  print("""
-Usage: %s -[nc] json_files_list
- -n produces list of files only
- -c produces classic installed-files.txt
-""" % (name))
+def main():
+  parser = argparse.ArgumentParser()
+  parser.add_argument("-n", action="store_true",
+                      help="produces list of files only")
+  parser.add_argument("-c", action="store_true",
+                      help="produces classic installed-files.txt")
+  parser.add_argument("json_files_list")
+  args = parser.parse_args()
 
-def main(argv):
-  try:
-    opts, args = getopt.getopt(argv[1:], "nc", "")
-  except getopt.GetoptError, err:
-    print(err)
-    PrintUsage(argv[0])
-    sys.exit(2)
-
-  if len(opts) == 0:
-    print("No conversion option specified")
-    PrintUsage(argv[0])
-    sys.exit(2)
-
-  if len(args) == 0:
-    print("No input file specified")
-    PrintUsage(argv[0])
-    sys.exit(2)
-
-  for o, a in opts:
-    if o == ("-n"):
-      PrintFileNames(args[0])
-      sys.exit()
-    elif o == ("-c"):
-      PrintCanonicalList(args[0])
-      sys.exit()
-    else:
-      assert False, "Unsupported option"
+  if args.n and args.c:
+    sys.exit("Cannot specify both -n and -c")
+  elif args.n:
+    PrintFileNames(args.json_files_list)
+  elif args.c:
+    PrintCanonicalList(args.json_files_list)
+  else:
+    sys.exit("No conversion option specified")
 
 if __name__ == '__main__':
-  main(sys.argv)
+  main()
diff --git a/tools/findleaves.py b/tools/findleaves.py
index 97302e9..86f3f3a 100755
--- a/tools/findleaves.py
+++ b/tools/findleaves.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 #
 # Copyright (C) 2009 The Android Open Source Project
 #
@@ -121,7 +121,7 @@
   results = list(set(perform_find(mindepth, prune, dirlist, filenames)))
   results.sort()
   for r in results:
-    print r
+    print(r)
 
 if __name__ == "__main__":
   main(sys.argv)
diff --git a/tools/java-layers.py b/tools/java-layers.py
deleted file mode 100755
index b3aec2b..0000000
--- a/tools/java-layers.py
+++ /dev/null
@@ -1,257 +0,0 @@
-#!/usr/bin/env python
-
-import os
-import re
-import sys
-
-def fail_with_usage():
-  sys.stderr.write("usage: java-layers.py DEPENDENCY_FILE SOURCE_DIRECTORIES...\n")
-  sys.stderr.write("\n")
-  sys.stderr.write("Enforces layering between java packages.  Scans\n")
-  sys.stderr.write("DIRECTORY and prints errors when the packages violate\n")
-  sys.stderr.write("the rules defined in the DEPENDENCY_FILE.\n")
-  sys.stderr.write("\n")
-  sys.stderr.write("Prints a warning when an unknown package is encountered\n")
-  sys.stderr.write("on the assumption that it should fit somewhere into the\n")
-  sys.stderr.write("layering.\n")
-  sys.stderr.write("\n")
-  sys.stderr.write("DEPENDENCY_FILE format\n")
-  sys.stderr.write("  - # starts comment\n")
-  sys.stderr.write("  - Lines consisting of two java package names:  The\n")
-  sys.stderr.write("    first package listed must not contain any references\n")
-  sys.stderr.write("    to any classes present in the second package, or any\n")
-  sys.stderr.write("    of its dependencies.\n")
-  sys.stderr.write("  - Lines consisting of one java package name:  The\n")
-  sys.stderr.write("    packge is assumed to be a high level package and\n")
-  sys.stderr.write("    nothing may depend on it.\n")
-  sys.stderr.write("  - Lines consisting of a dash (+) followed by one java\n")
-  sys.stderr.write("    package name: The package is considered a low level\n")
-  sys.stderr.write("    package and may not import any of the other packages\n")
-  sys.stderr.write("    listed in the dependency file.\n")
-  sys.stderr.write("  - Lines consisting of a plus (-) followed by one java\n")
-  sys.stderr.write("    package name: The package is considered \'legacy\'\n")
-  sys.stderr.write("    and excluded from errors.\n")
-  sys.stderr.write("\n")
-  sys.exit(1)
-
-class Dependency:
-  def __init__(self, filename, lineno, lower, top, lowlevel, legacy):
-    self.filename = filename
-    self.lineno = lineno
-    self.lower = lower
-    self.top = top
-    self.lowlevel = lowlevel
-    self.legacy = legacy
-    self.uppers = []
-    self.transitive = set()
-
-  def matches(self, imp):
-    for d in self.transitive:
-      if imp.startswith(d):
-        return True
-    return False
-
-class Dependencies:
-  def __init__(self, deps):
-    def recurse(obj, dep, visited):
-      global err
-      if dep in visited:
-        sys.stderr.write("%s:%d: Circular dependency found:\n"
-            % (dep.filename, dep.lineno))
-        for v in visited:
-          sys.stderr.write("%s:%d:    Dependency: %s\n"
-              % (v.filename, v.lineno, v.lower))
-        err = True
-        return
-      visited.append(dep)
-      for upper in dep.uppers:
-        obj.transitive.add(upper)
-        if upper in deps:
-          recurse(obj, deps[upper], visited)
-    self.deps = deps
-    self.parts = [(dep.lower.split('.'),dep) for dep in deps.itervalues()]
-    # transitive closure of dependencies
-    for dep in deps.itervalues():
-      recurse(dep, dep, [])
-    # disallow everything from the low level components
-    for dep in deps.itervalues():
-      if dep.lowlevel:
-        for d in deps.itervalues():
-          if dep != d and not d.legacy:
-            dep.transitive.add(d.lower)
-    # disallow the 'top' components everywhere but in their own package
-    for dep in deps.itervalues():
-      if dep.top and not dep.legacy:
-        for d in deps.itervalues():
-          if dep != d and not d.legacy:
-            d.transitive.add(dep.lower)
-    for dep in deps.itervalues():
-      dep.transitive = set([x+"." for x in dep.transitive])
-    if False:
-      for dep in deps.itervalues():
-        print "-->", dep.lower, "-->", dep.transitive
-
-  # Lookup the dep object for the given package.  If pkg is a subpackage
-  # of one with a rule, that one will be returned.  If no matches are found,
-  # None is returned.
-  def lookup(self, pkg):
-    # Returns the number of parts that match
-    def compare_parts(parts, pkg):
-      if len(parts) > len(pkg):
-        return 0
-      n = 0
-      for i in range(0, len(parts)):
-        if parts[i] != pkg[i]:
-          return 0
-        n = n + 1
-      return n
-    pkg = pkg.split(".")
-    matched = 0
-    result = None
-    for (parts,dep) in self.parts:
-      x = compare_parts(parts, pkg)
-      if x > matched:
-        matched = x
-        result = dep
-    return result
-
-def parse_dependency_file(filename):
-  global err
-  f = file(filename)
-  lines = f.readlines()
-  f.close()
-  def lineno(s, i):
-    i[0] = i[0] + 1
-    return (i[0],s)
-  n = [0]
-  lines = [lineno(x,n) for x in lines]
-  lines = [(n,s.split("#")[0].strip()) for (n,s) in lines]
-  lines = [(n,s) for (n,s) in lines if len(s) > 0]
-  lines = [(n,s.split()) for (n,s) in lines]
-  deps = {}
-  for n,words in lines:
-    if len(words) == 1:
-      lower = words[0]
-      top = True
-      legacy = False
-      lowlevel = False
-      if lower[0] == '+':
-        lower = lower[1:]
-        top = False
-        lowlevel = True
-      elif lower[0] == '-':
-        lower = lower[1:]
-        legacy = True
-      if lower in deps:
-        sys.stderr.write(("%s:%d: Package '%s' already defined on"
-            + " line %d.\n") % (filename, n, lower, deps[lower].lineno))
-        err = True
-      else:
-        deps[lower] = Dependency(filename, n, lower, top, lowlevel, legacy)
-    elif len(words) == 2:
-      lower = words[0]
-      upper = words[1]
-      if lower in deps:
-        dep = deps[lower]
-        if dep.top:
-          sys.stderr.write(("%s:%d: Can't add dependency to top level package "
-            + "'%s'\n") % (filename, n, lower))
-          err = True
-      else:
-        dep = Dependency(filename, n, lower, False, False, False)
-        deps[lower] = dep
-      dep.uppers.append(upper)
-    else:
-      sys.stderr.write("%s:%d: Too many words on line starting at \'%s\'\n" % (
-          filename, n, words[2]))
-      err = True
-  return Dependencies(deps)
-
-def find_java_files(srcs):
-  result = []
-  for d in srcs:
-    if d[0] == '@':
-      f = file(d[1:])
-      result.extend([fn for fn in [s.strip() for s in f.readlines()]
-          if len(fn) != 0])
-      f.close()
-    else:
-      for root, dirs, files in os.walk(d):
-        result.extend([os.sep.join((root,f)) for f in files
-            if f.lower().endswith(".java")])
-  return result
-
-COMMENTS = re.compile("//.*?\n|/\*.*?\*/", re.S)
-PACKAGE = re.compile("package\s+(.*)")
-IMPORT = re.compile("import\s+(.*)")
-
-def examine_java_file(deps, filename):
-  global err
-  # Yes, this is a crappy java parser.  Write a better one if you want to.
-  f = file(filename)
-  text = f.read()
-  f.close()
-  text = COMMENTS.sub("", text)
-  index = text.find("{")
-  if index < 0:
-    sys.stderr.write(("%s: Error: Unable to parse java. Can't find class "
-        + "declaration.\n") % filename)
-    err = True
-    return
-  text = text[0:index]
-  statements = [s.strip() for s in text.split(";")]
-  # First comes the package declaration.  Then iterate while we see import
-  # statements.  Anything else is either bad syntax that we don't care about
-  # because the compiler will fail, or the beginning of the class declaration.
-  m = PACKAGE.match(statements[0])
-  if not m:
-    sys.stderr.write(("%s: Error: Unable to parse java. Missing package "
-        + "statement.\n") % filename)
-    err = True
-    return
-  pkg = m.group(1)
-  imports = []
-  for statement in statements[1:]:
-    m = IMPORT.match(statement)
-    if not m:
-      break
-    imports.append(m.group(1))
-  # Do the checking
-  if False:
-    print filename
-    print "'%s' --> %s" % (pkg, imports)
-  dep = deps.lookup(pkg)
-  if not dep:
-    sys.stderr.write(("%s: Error: Package does not appear in dependency file: "
-      + "%s\n") % (filename, pkg))
-    err = True
-    return
-  for imp in imports:
-    if dep.matches(imp):
-      sys.stderr.write("%s: Illegal import in package '%s' of '%s'\n"
-          % (filename, pkg, imp))
-      err = True
-
-err = False
-
-def main(argv):
-  if len(argv) < 3:
-    fail_with_usage()
-  deps = parse_dependency_file(argv[1])
-
-  if err:
-    sys.exit(1)
-
-  java = find_java_files(argv[2:])
-  for filename in java:
-    examine_java_file(deps, filename)
-
-  if err:
-    sys.stderr.write("%s: Using this file as dependency file.\n" % argv[1])
-    sys.exit(1)
-
-  sys.exit(0)
-
-if __name__ == "__main__":
-  main(sys.argv)
-
diff --git a/tools/parsedeps.py b/tools/parsedeps.py
deleted file mode 100755
index 32d8ad7..0000000
--- a/tools/parsedeps.py
+++ /dev/null
@@ -1,151 +0,0 @@
-#!/usr/bin/env python
-# vim: ts=2 sw=2
-
-import optparse
-import re
-import sys
-
-
-class Dependency:
-  def __init__(self, tgt):
-    self.tgt = tgt
-    self.pos = ""
-    self.prereqs = set()
-    self.visit = 0
-
-  def add(self, prereq):
-    self.prereqs.add(prereq)
-
-
-class Dependencies:
-  def __init__(self):
-    self.lines = {}
-    self.__visit = 0
-    self.count = 0
-
-  def add(self, tgt, prereq):
-    t = self.lines.get(tgt)
-    if not t:
-      t = Dependency(tgt)
-      self.lines[tgt] = t
-    p = self.lines.get(prereq)
-    if not p:
-      p = Dependency(prereq)
-      self.lines[prereq] = p
-    t.add(p)
-    self.count = self.count + 1
-
-  def setPos(self, tgt, pos):
-    t = self.lines.get(tgt)
-    if not t:
-      t = Dependency(tgt)
-      self.lines[tgt] = t
-    t.pos = pos
-
-  def get(self, tgt):
-    if self.lines.has_key(tgt):
-      return self.lines[tgt]
-    else:
-      return None
-
-  def __iter__(self):
-    return self.lines.iteritems()
-
-  def trace(self, tgt, prereq):
-    self.__visit = self.__visit + 1
-    d = self.lines.get(tgt)
-    if not d:
-      return
-    return self.__trace(d, prereq)
-
-  def __trace(self, d, prereq):
-    if d.visit == self.__visit:
-      return d.trace
-    if d.tgt == prereq:
-      return [ [ d ], ]
-    d.visit = self.__visit
-    result = []
-    for pre in d.prereqs:
-      recursed = self.__trace(pre, prereq)
-      for r in recursed:
-        result.append([ d ] + r)
-    d.trace = result
-    return result
-
-def help():
-  print "Commands:"
-  print "  dep TARGET             Print the prerequisites for TARGET"
-  print "  trace TARGET PREREQ    Print the paths from TARGET to PREREQ"
-
-
-def main(argv):
-  opts = optparse.OptionParser()
-  opts.add_option("-i", "--interactive", action="store_true", dest="interactive",
-                    help="Interactive mode")
-  (options, args) = opts.parse_args()
-
-  deps = Dependencies()
-
-  filename = args[0]
-  print "Reading %s" % filename
-
-  if True:
-    f = open(filename)
-    for line in f:
-      line = line.strip()
-      if len(line) > 0:
-        if line[0] == '#':
-          pos,tgt = line.rsplit(":", 1)
-          pos = pos[1:].strip()
-          tgt = tgt.strip()
-          deps.setPos(tgt, pos)
-        else:
-          (tgt,prereq) = line.split(':', 1)
-          tgt = tgt.strip()
-          prereq = prereq.strip()
-          deps.add(tgt, prereq)
-    f.close()
-
-  print "Read %d dependencies. %d targets." % (deps.count, len(deps.lines))
-  while True:
-    line = raw_input("target> ")
-    if not line.strip():
-      continue
-    split = line.split()
-    cmd = split[0]
-    if len(split) == 2 and cmd == "dep":
-      tgt = split[1]
-      d = deps.get(tgt)
-      if d:
-        for prereq in d.prereqs:
-          print prereq.tgt
-    elif len(split) == 3 and cmd == "trace":
-      tgt = split[1]
-      prereq = split[2]
-      if False:
-        print "from %s to %s" % (tgt, prereq)
-      trace = deps.trace(tgt, prereq)
-      if trace:
-        width = 0
-        for g in trace:
-          for t in g:
-            if len(t.tgt) > width:
-              width = len(t.tgt)
-        for g in trace:
-          for t in g:
-            if t.pos:
-              print t.tgt, " " * (width-len(t.tgt)), "  #", t.pos
-            else:
-              print t.tgt
-          print
-    else:
-      help()
-
-if __name__ == "__main__":
-  try:
-    main(sys.argv)
-  except KeyboardInterrupt:
-    print
-  except EOFError:
-    print
-
diff --git a/tools/releasetools/add_img_to_target_files.py b/tools/releasetools/add_img_to_target_files.py
index 465e1a8..d308a55 100644
--- a/tools/releasetools/add_img_to_target_files.py
+++ b/tools/releasetools/add_img_to_target_files.py
@@ -842,14 +842,13 @@
   SYSTEM/ after rebuilding recovery.
   """
   common.ZipDelete(zip_filename, files_list)
-  output_zip = zipfile.ZipFile(zip_filename, "a",
+  with zipfile.ZipFile(zip_filename, "a",
                                compression=zipfile.ZIP_DEFLATED,
-                               allowZip64=True)
-  for item in files_list:
-    file_path = os.path.join(OPTIONS.input_tmp, item)
-    assert os.path.exists(file_path)
-    common.ZipWrite(output_zip, file_path, arcname=item)
-  common.ZipClose(output_zip)
+                               allowZip64=True) as output_zip:
+    for item in files_list:
+      file_path = os.path.join(OPTIONS.input_tmp, item)
+      assert os.path.exists(file_path)
+      common.ZipWrite(output_zip, file_path, arcname=item)
 
 
 def HasPartition(partition_name):
@@ -1176,7 +1175,7 @@
   AddVbmetaDigest(output_zip)
 
   if output_zip:
-    common.ZipClose(output_zip)
+    output_zip.close()
     if OPTIONS.replace_updated_files_list:
       ReplaceUpdatedFiles(output_zip.filename,
                           OPTIONS.replace_updated_files_list)
diff --git a/tools/releasetools/apex_utils.py b/tools/releasetools/apex_utils.py
index d7b0ba2..22992e8 100644
--- a/tools/releasetools/apex_utils.py
+++ b/tools/releasetools/apex_utils.py
@@ -415,7 +415,7 @@
   apex_zip = zipfile.ZipFile(apex_file, 'a', allowZip64=True)
   common.ZipWrite(apex_zip, payload_file, arcname=APEX_PAYLOAD_IMAGE)
   common.ZipWrite(apex_zip, payload_public_key, arcname=APEX_PUBKEY)
-  common.ZipClose(apex_zip)
+  apex_zip.close()
 
   # 3. Sign the APEX container with container_key.
   signed_apex = common.MakeTempFile(prefix='apex-container-', suffix='.apex')
diff --git a/tools/releasetools/check_ota_package_signature.py b/tools/releasetools/check_ota_package_signature.py
index b395c19..97957be 100755
--- a/tools/releasetools/check_ota_package_signature.py
+++ b/tools/releasetools/check_ota_package_signature.py
@@ -142,7 +142,7 @@
   """Verifies the payload and metadata signatures in an A/B OTA payload."""
   package_zip = zipfile.ZipFile(package, 'r', allowZip64=True)
   if 'payload.bin' not in package_zip.namelist():
-    common.ZipClose(package_zip)
+    package_zip.close()
     return
 
   print('Verifying A/B OTA payload signatures...')
@@ -160,7 +160,7 @@
          '--in_file=' + payload_file,
          '--public_key=' + pubkey]
   common.RunAndCheckOutput(cmd)
-  common.ZipClose(package_zip)
+  package_zip.close()
 
   # Verified successfully upon reaching here.
   print('\nPayload signatures VERIFIED\n\n')
diff --git a/tools/releasetools/check_target_files_vintf.py b/tools/releasetools/check_target_files_vintf.py
index c369a59..8f61eac 100755
--- a/tools/releasetools/check_target_files_vintf.py
+++ b/tools/releasetools/check_target_files_vintf.py
@@ -280,8 +280,8 @@
       inp_partition = os.path.join(inp, target_files_rel_path,"apex")
       if os.path.exists(inp_partition):
         apex_dir = root_dir + os.path.join(device_path + "/apex");
-        os.makedirs(apex_dir)
-        os.rename(inp_partition, apex_dir)
+        os.makedirs(root_dir + device_path)
+        shutil.copytree(inp_partition, apex_dir, symlinks=True)
         ExtractApexes(apex_dir, extracted_root)
         create_info_file = True
 
diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py
index 715802f..2f05d44 100644
--- a/tools/releasetools/common.py
+++ b/tools/releasetools/common.py
@@ -2797,18 +2797,6 @@
 def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
              compress_type=None):
 
-  # http://b/18015246
-  # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
-  # for files larger than 2GiB. We can work around this by adjusting their
-  # limit. Note that `zipfile.writestr()` will not work for strings larger than
-  # 2GiB. The Python interpreter sometimes rejects strings that large (though
-  # it isn't clear to me exactly what circumstances cause this).
-  # `zipfile.write()` must be used directly to work around this.
-  #
-  # This mess can be avoided if we port to python3.
-  saved_zip64_limit = zipfile.ZIP64_LIMIT
-  zipfile.ZIP64_LIMIT = (1 << 32) - 1
-
   if compress_type is None:
     compress_type = zip_file.compression
   if arcname is None:
@@ -2834,14 +2822,13 @@
   finally:
     os.chmod(filename, saved_stat.st_mode)
     os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
-    zipfile.ZIP64_LIMIT = saved_zip64_limit
 
 
 def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
                 compress_type=None):
   """Wrap zipfile.writestr() function to work around the zip64 limit.
 
-  Even with the ZIP64_LIMIT workaround, it won't allow writing a string
+  Python's zip implementation won't allow writing a string
   longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
   when calling crc32(bytes).
 
@@ -2850,9 +2837,6 @@
   when we know the string won't be too long.
   """
 
-  saved_zip64_limit = zipfile.ZIP64_LIMIT
-  zipfile.ZIP64_LIMIT = (1 << 32) - 1
-
   if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
     zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
     zinfo.compress_type = zip_file.compression
@@ -2885,7 +2869,6 @@
   zinfo.date_time = (2009, 1, 1, 0, 0, 0)
 
   zip_file.writestr(zinfo, data)
-  zipfile.ZIP64_LIMIT = saved_zip64_limit
 
 
 def ZipDelete(zip_filename, entries, force=False):
@@ -2919,18 +2902,6 @@
   os.replace(new_zipfile, zip_filename)
 
 
-def ZipClose(zip_file):
-  # http://b/18015246
-  # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
-  # central directory.
-  saved_zip64_limit = zipfile.ZIP64_LIMIT
-  zipfile.ZIP64_LIMIT = (1 << 32) - 1
-
-  zip_file.close()
-
-  zipfile.ZIP64_LIMIT = saved_zip64_limit
-
-
 class DeviceSpecificParams(object):
   module = None
 
diff --git a/tools/releasetools/img_from_target_files.py b/tools/releasetools/img_from_target_files.py
index 76da89c..f8bdd81 100755
--- a/tools/releasetools/img_from_target_files.py
+++ b/tools/releasetools/img_from_target_files.py
@@ -173,7 +173,7 @@
   logger.info('Writing super.img to archive...')
   with zipfile.ZipFile(
       output_file, 'a', compression=zipfile.ZIP_DEFLATED,
-      allowZip64=not OPTIONS.sparse_userimages) as output_zip:
+      allowZip64=True) as output_zip:
     common.ZipWrite(output_zip, super_file, 'super.img')
 
 
diff --git a/tools/releasetools/non_ab_ota.py b/tools/releasetools/non_ab_ota.py
index 6c927ec..ac85aa4 100644
--- a/tools/releasetools/non_ab_ota.py
+++ b/tools/releasetools/non_ab_ota.py
@@ -277,7 +277,7 @@
 
   # We haven't written the metadata entry, which will be done in
   # FinalizeMetadata.
-  common.ZipClose(output_zip)
+  output_zip.close()
 
   needed_property_files = (
       NonAbOtaPropertyFiles(),
@@ -531,7 +531,7 @@
 
   # We haven't written the metadata entry yet, which will be handled in
   # FinalizeMetadata().
-  common.ZipClose(output_zip)
+  output_zip.close()
 
   # Sign the generated zip package unless no_signing is specified.
   needed_property_files = (
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index 9d5c67d..60e95ad 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -487,7 +487,7 @@
       else:
         common.ZipWrite(target_zip, unzipped_file, arcname=info.filename)
 
-  common.ZipClose(target_zip)
+  target_zip.close()
 
   return target_file
 
@@ -624,7 +624,7 @@
 
     # TODO(xunchang) handle META/postinstall_config.txt'
 
-  common.ZipClose(partial_target_zip)
+  partial_target_zip.close()
 
   return partial_target_file
 
@@ -709,7 +709,7 @@
   # Write new ab_partitions.txt file
   common.ZipWrite(target_zip, new_ab_partitions, arcname=AB_PARTITIONS)
 
-  common.ZipClose(target_zip)
+  target_zip.close()
 
   return target_file
 
@@ -1017,11 +1017,11 @@
     common.ZipWriteStr(output_zip, "apex_info.pb", ota_apex_info,
                        compress_type=zipfile.ZIP_STORED)
 
-  common.ZipClose(target_zip)
+  target_zip.close()
 
   # We haven't written the metadata entry yet, which will be handled in
   # FinalizeMetadata().
-  common.ZipClose(output_zip)
+  output_zip.close()
 
   FinalizeMetadata(metadata, staging_file, output_file,
                    package_key=OPTIONS.package_key)
diff --git a/tools/releasetools/ota_utils.py b/tools/releasetools/ota_utils.py
index 9f41874..e36a2be 100644
--- a/tools/releasetools/ota_utils.py
+++ b/tools/releasetools/ota_utils.py
@@ -22,7 +22,7 @@
 
 import ota_metadata_pb2
 import common
-from common import (ZipDelete, ZipClose, OPTIONS, MakeTempFile,
+from common import (ZipDelete, OPTIONS, MakeTempFile,
                     ZipWriteStr, BuildInfo, LoadDictionaryFromFile,
                     SignFile, PARTITIONS_WITH_BUILD_PROP, PartitionBuildProps,
                     GetRamdiskFormat)
diff --git a/tools/releasetools/sign_target_files_apks.py b/tools/releasetools/sign_target_files_apks.py
index d3fbdad..4e7274f 100755
--- a/tools/releasetools/sign_target_files_apks.py
+++ b/tools/releasetools/sign_target_files_apks.py
@@ -901,7 +901,7 @@
   certs_zip = zipfile.ZipFile(temp_file, "w", allowZip64=True)
   for k in keys:
     common.ZipWrite(certs_zip, k)
-  common.ZipClose(certs_zip)
+  certs_zip.close()
   common.ZipWriteStr(output_zip, filename, temp_file.getvalue())
 
 
@@ -1529,8 +1529,8 @@
                      platform_api_level, codename_to_api_level_map,
                      compressed_extension)
 
-  common.ZipClose(input_zip)
-  common.ZipClose(output_zip)
+  input_zip.close()
+  output_zip.close()
 
   if OPTIONS.vendor_partitions and OPTIONS.vendor_otatools:
     BuildVendorPartitions(args[1])
diff --git a/tools/releasetools/test_common.py b/tools/releasetools/test_common.py
index 2a0e592..8c9655ad0 100644
--- a/tools/releasetools/test_common.py
+++ b/tools/releasetools/test_common.py
@@ -222,17 +222,17 @@
     info_dict = copy.deepcopy(self.TEST_INFO_FINGERPRINT_DICT)
     build_info = common.BuildInfo(info_dict)
     self.assertEqual(
-      'product-brand/product-name/product-device:version-release/build-id/'
-      'version-incremental:build-type/build-tags', build_info.fingerprint)
+        'product-brand/product-name/product-device:version-release/build-id/'
+        'version-incremental:build-type/build-tags', build_info.fingerprint)
 
     build_props = info_dict['build.prop'].build_props
     del build_props['ro.build.id']
     build_props['ro.build.legacy.id'] = 'legacy-build-id'
     build_info = common.BuildInfo(info_dict, use_legacy_id=True)
     self.assertEqual(
-      'product-brand/product-name/product-device:version-release/'
-      'legacy-build-id/version-incremental:build-type/build-tags',
-      build_info.fingerprint)
+        'product-brand/product-name/product-device:version-release/'
+        'legacy-build-id/version-incremental:build-type/build-tags',
+        build_info.fingerprint)
 
     self.assertRaises(common.ExternalError, common.BuildInfo, info_dict, None,
                       False)
@@ -241,9 +241,9 @@
     info_dict['vbmeta_digest'] = 'abcde12345'
     build_info = common.BuildInfo(info_dict, use_legacy_id=False)
     self.assertEqual(
-      'product-brand/product-name/product-device:version-release/'
-      'legacy-build-id.abcde123/version-incremental:build-type/build-tags',
-      build_info.fingerprint)
+        'product-brand/product-name/product-device:version-release/'
+        'legacy-build-id.abcde123/version-incremental:build-type/build-tags',
+        build_info.fingerprint)
 
   def test___getitem__(self):
     target_info = common.BuildInfo(self.TEST_INFO_DICT, None)
@@ -376,7 +376,7 @@
     info_dict['build.prop'].build_props[
         'ro.product.property_source_order'] = 'bad-source'
     with self.assertRaisesRegexp(common.ExternalError,
-        'Invalid ro.product.property_source_order'):
+                                 'Invalid ro.product.property_source_order'):
       info = common.BuildInfo(info_dict, None)
       info.GetBuildProp('ro.product.device')
 
@@ -459,7 +459,7 @@
       time.sleep(5)  # Make sure the atime/mtime will change measurably.
 
       common.ZipWrite(zip_file, test_file_name, **extra_zipwrite_args)
-      common.ZipClose(zip_file)
+      zip_file.close()
 
       self._verify(zip_file, zip_file_name, arcname, sha1_hash.hexdigest(),
                    test_file_name, expected_stat, expected_mode,
@@ -494,7 +494,7 @@
         expected_mode = extra_args.get("perms", zinfo_perms)
 
       common.ZipWriteStr(zip_file, zinfo_or_arcname, contents, **extra_args)
-      common.ZipClose(zip_file)
+      zip_file.close()
 
       self._verify(zip_file, zip_file_name, arcname, sha1(contents).hexdigest(),
                    expected_mode=expected_mode,
@@ -536,7 +536,7 @@
 
       common.ZipWrite(zip_file, test_file_name, **extra_args)
       common.ZipWriteStr(zip_file, arcname_small, small, **extra_args)
-      common.ZipClose(zip_file)
+      zip_file.close()
 
       # Verify the contents written by ZipWrite().
       self._verify(zip_file, zip_file_name, arcname_large,
@@ -551,12 +551,6 @@
       os.remove(zip_file_name)
       os.remove(test_file_name)
 
-  def _test_reset_ZIP64_LIMIT(self, func, *args):
-    default_limit = (1 << 31) - 1
-    self.assertEqual(default_limit, zipfile.ZIP64_LIMIT)
-    func(*args)
-    self.assertEqual(default_limit, zipfile.ZIP64_LIMIT)
-
   def test_ZipWrite(self):
     file_contents = os.urandom(1024)
     self._test_ZipWrite(file_contents)
@@ -581,7 +575,7 @@
     })
 
   def test_ZipWrite_resets_ZIP64_LIMIT(self):
-    self._test_reset_ZIP64_LIMIT(self._test_ZipWrite, "")
+    self._test_ZipWrite("")
 
   def test_ZipWriteStr(self):
     random_string = os.urandom(1024)
@@ -632,9 +626,9 @@
     })
 
   def test_ZipWriteStr_resets_ZIP64_LIMIT(self):
-    self._test_reset_ZIP64_LIMIT(self._test_ZipWriteStr, 'foo', b'')
+    self._test_ZipWriteStr('foo', b'')
     zinfo = zipfile.ZipInfo(filename="foo")
-    self._test_reset_ZIP64_LIMIT(self._test_ZipWriteStr, zinfo, b'')
+    self._test_ZipWriteStr(zinfo, b'')
 
   def test_bug21309935(self):
     zip_file = tempfile.NamedTemporaryFile(delete=False)
@@ -656,7 +650,7 @@
       zinfo = zipfile.ZipInfo(filename="qux")
       zinfo.external_attr = 0o700 << 16
       common.ZipWriteStr(zip_file, zinfo, random_string, perms=0o400)
-      common.ZipClose(zip_file)
+      zip_file.close()
 
       self._verify(zip_file, zip_file_name, "foo",
                    sha1(random_string).hexdigest(),
@@ -683,7 +677,7 @@
       common.ZipWrite(output_zip, entry_file.name, arcname='Test1')
       common.ZipWrite(output_zip, entry_file.name, arcname='Test2')
       common.ZipWrite(output_zip, entry_file.name, arcname='Test3')
-      common.ZipClose(output_zip)
+      output_zip.close()
     zip_file.close()
 
     try:
@@ -731,8 +725,8 @@
       common.ZipWrite(output_zip, entry_file.name, arcname='Foo3')
       common.ZipWrite(output_zip, entry_file.name, arcname='Bar4')
       common.ZipWrite(output_zip, entry_file.name, arcname='Dir5/Baz5')
-      common.ZipClose(output_zip)
-    common.ZipClose(output_zip)
+      output_zip.close()
+    output_zip.close()
     return zip_file
 
   @test_utils.SkipIfExternalToolsUnavailable()
@@ -819,9 +813,9 @@
   )
 
   APKCERTS_CERTMAP1 = {
-      'RecoveryLocalizer.apk' : 'certs/devkey',
-      'Settings.apk' : 'build/make/target/product/security/platform',
-      'TV.apk' : 'PRESIGNED',
+      'RecoveryLocalizer.apk': 'certs/devkey',
+      'Settings.apk': 'build/make/target/product/security/platform',
+      'TV.apk': 'PRESIGNED',
   }
 
   APKCERTS_TXT2 = (
@@ -836,10 +830,10 @@
   )
 
   APKCERTS_CERTMAP2 = {
-      'Compressed1.apk' : 'certs/compressed1',
-      'Compressed2a.apk' : 'certs/compressed2',
-      'Compressed2b.apk' : 'certs/compressed2',
-      'Compressed3.apk' : 'certs/compressed3',
+      'Compressed1.apk': 'certs/compressed1',
+      'Compressed2a.apk': 'certs/compressed2',
+      'Compressed2b.apk': 'certs/compressed2',
+      'Compressed3.apk': 'certs/compressed3',
   }
 
   APKCERTS_TXT3 = (
@@ -848,7 +842,7 @@
   )
 
   APKCERTS_CERTMAP3 = {
-      'Compressed4.apk' : 'certs/compressed4',
+      'Compressed4.apk': 'certs/compressed4',
   }
 
   # Test parsing with no optional fields, both optional fields, and only the
@@ -865,9 +859,9 @@
   )
 
   APKCERTS_CERTMAP4 = {
-      'RecoveryLocalizer.apk' : 'certs/devkey',
-      'Settings.apk' : 'build/make/target/product/security/platform',
-      'TV.apk' : 'PRESIGNED',
+      'RecoveryLocalizer.apk': 'certs/devkey',
+      'Settings.apk': 'build/make/target/product/security/platform',
+      'TV.apk': 'PRESIGNED',
   }
 
   def setUp(self):
@@ -971,7 +965,7 @@
     extracted_from_privkey = common.ExtractAvbPublicKey('avbtool', privkey)
     extracted_from_pubkey = common.ExtractAvbPublicKey('avbtool', pubkey)
     with open(extracted_from_privkey, 'rb') as privkey_fp, \
-        open(extracted_from_pubkey, 'rb') as pubkey_fp:
+            open(extracted_from_pubkey, 'rb') as pubkey_fp:
       self.assertEqual(privkey_fp.read(), pubkey_fp.read())
 
   def test_ParseCertificate(self):
@@ -1235,7 +1229,8 @@
     self.assertEqual(
         '1-5 9-10',
         sparse_image.file_map['//system/file1'].extra['text_str'])
-    self.assertTrue(sparse_image.file_map['//system/file2'].extra['incomplete'])
+    self.assertTrue(
+        sparse_image.file_map['//system/file2'].extra['incomplete'])
     self.assertTrue(
         sparse_image.file_map['/system/app/file3'].extra['incomplete'])
 
@@ -1343,7 +1338,7 @@
       'recovery_api_version': 3,
       'fstab_version': 2,
       'system_root_image': 'true',
-      'no_recovery' : 'true',
+      'no_recovery': 'true',
       'recovery_as_boot': 'true',
   }
 
@@ -1664,6 +1659,7 @@
     self.assertRaises(common.ExternalError, common._GenerateGkiCertificate,
                       test_file.name, 'generic_kernel')
 
+
 class InstallRecoveryScriptFormatTest(test_utils.ReleaseToolsTestCase):
   """Checks the format of install-recovery.sh.
 
@@ -1673,7 +1669,7 @@
   def setUp(self):
     self._tempdir = common.MakeTempDir()
     # Create a fake dict that contains the fstab info for boot&recovery.
-    self._info = {"fstab" : {}}
+    self._info = {"fstab": {}}
     fake_fstab = [
         "/dev/soc.0/by-name/boot /boot emmc defaults defaults",
         "/dev/soc.0/by-name/recovery /recovery emmc defaults defaults"]
@@ -2020,11 +2016,11 @@
           input_zip, 'odm', placeholder_values)
 
     self.assertEqual({
-      'ro.odm.build.date.utc': '1578430045',
-      'ro.odm.build.fingerprint':
-      'google/coral/coral:10/RP1A.200325.001/6337676:user/dev-keys',
-      'ro.product.odm.device': 'coral',
-      'ro.product.odm.name': 'product1',
+        'ro.odm.build.date.utc': '1578430045',
+        'ro.odm.build.fingerprint':
+        'google/coral/coral:10/RP1A.200325.001/6337676:user/dev-keys',
+        'ro.product.odm.device': 'coral',
+        'ro.product.odm.name': 'product1',
     }, partition_props.build_props)
 
     with zipfile.ZipFile(input_file, 'r', allowZip64=True) as input_zip:
@@ -2207,8 +2203,8 @@
 
     copied_props = copy.deepcopy(partition_props)
     self.assertEqual({
-      'ro.odm.build.date.utc': '1578430045',
-      'ro.odm.build.fingerprint':
-      'google/coral/coral:10/RP1A.200325.001/6337676:user/dev-keys',
-      'ro.product.odm.device': 'coral',
+        'ro.odm.build.date.utc': '1578430045',
+        'ro.odm.build.fingerprint':
+        'google/coral/coral:10/RP1A.200325.001/6337676:user/dev-keys',
+        'ro.product.odm.device': 'coral',
     }, copied_props.build_props)
diff --git a/tools/whichgit b/tools/whichgit
index 24d6d87..b0bf2e4 100755
--- a/tools/whichgit
+++ b/tools/whichgit
@@ -95,11 +95,12 @@
   # Print the list of git directories that has one or more of the sources in it
   for project in sorted(get_referenced_projects(get_git_dirs(), sources)):
     print(project)
-    if "*" in args.why or project in args.why:
-      prefix = project + "/"
-      for f in sources:
-        if f.startswith(prefix):
-          print("  " + f)
+    if args.why:
+      if "*" in args.why or project in args.why:
+        prefix = project + "/"
+        for f in sources:
+          if f.startswith(prefix):
+            print("  " + f)
 
 
 if __name__ == "__main__":