Merge "Unconditionally generate build_manifest.pb"
diff --git a/core/Makefile b/core/Makefile
index f237724..dc8856b 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -5043,6 +5043,12 @@
   $(sort $(shell find build/make/target/product/security -type f -name "*.x509.pem" -o \
       -name "*.pk8"))
 
+ifneq (,$(wildcard packages/modules))
+INTERNAL_OTATOOLS_PACKAGE_FILES += \
+  $(sort $(shell find packages/modules -type f -name "*.x509.pem" -o -name "*.pk8" -o -name \
+      "key.pem"))
+endif
+
 ifneq (,$(wildcard device))
 INTERNAL_OTATOOLS_PACKAGE_FILES += \
   $(sort $(shell find device $(wildcard vendor) -type f -name "*.pk8" -o -name "verifiedboot*" -o \
diff --git a/target/product/aosp_riscv64.mk b/target/product/aosp_riscv64.mk
index 518f8b1..012cc41 100644
--- a/target/product/aosp_riscv64.mk
+++ b/target/product/aosp_riscv64.mk
@@ -30,33 +30,41 @@
 
 # GSI for system/product & support 64-bit apps only
 $(call inherit-product, $(SRC_TARGET_DIR)/product/core_64_bit_only.mk)
-$(call inherit-product, $(SRC_TARGET_DIR)/product/mainline_system.mk)
+#$(call inherit-product, $(SRC_TARGET_DIR)/product/mainline_system.mk)
+TARGET_FLATTEN_APEX := false
 
 #
 # All components inherited here go to system_ext image
 #
-$(call inherit-product, $(SRC_TARGET_DIR)/product/handheld_system_ext.mk)
-$(call inherit-product, $(SRC_TARGET_DIR)/product/telephony_system_ext.mk)
+#$(call inherit-product, $(SRC_TARGET_DIR)/product/handheld_system_ext.mk)
+#$(call inherit-product, $(SRC_TARGET_DIR)/product/telephony_system_ext.mk)
 
 #
 # All components inherited here go to product image
 #
-$(call inherit-product, $(SRC_TARGET_DIR)/product/aosp_product.mk)
+#$(call inherit-product, $(SRC_TARGET_DIR)/product/aosp_product.mk)
 
 #
 # All components inherited here go to vendor image
 #
 #$(call inherit-product-if-exists, device/generic/goldfish/riscv64-vendor.mk)
-$(call inherit-product, $(SRC_TARGET_DIR)/product/emulator_vendor.mk)
+#$(call inherit-product, $(SRC_TARGET_DIR)/product/emulator_vendor.mk)
 $(call inherit-product, $(SRC_TARGET_DIR)/board/generic_riscv64/device.mk)
 
 #
 # Special settings for GSI releasing
 #
 ifeq (aosp_riscv64,$(TARGET_PRODUCT))
-$(call inherit-product, $(SRC_TARGET_DIR)/product/gsi_release.mk)
+#$(call inherit-product, $(SRC_TARGET_DIR)/product/gsi_release.mk)
 endif
 
+# TODO: this list should come via mainline_system.mk, but for now list
+# just the modules that work for riscv64.
+PRODUCT_PACKAGES := \
+  init \
+  linker \
+  shell_and_utilities \
+
 PRODUCT_ARTIFACT_PATH_REQUIREMENT_ALLOWED_LIST += \
     root/init.zygote64.rc
 
diff --git a/tools/compliance/Android.bp b/tools/compliance/Android.bp
index 2527df7..8e13f2f 100644
--- a/tools/compliance/Android.bp
+++ b/tools/compliance/Android.bp
@@ -131,6 +131,17 @@
     testSrcs: ["cmd/xmlnotice/xmlnotice_test.go"],
 }
 
+blueprint_go_binary {
+    name: "compliance_sbom",
+    srcs: ["cmd/sbom/sbom.go"],
+    deps: [
+        "compliance-module",
+        "blueprint-deptools",
+        "soong-response",
+    ],
+    testSrcs: ["cmd/sbom/sbom_test.go"],
+}
+
 bootstrap_go_package {
     name: "compliance-module",
     srcs: [
diff --git a/tools/compliance/cmd/sbom/sbom.go b/tools/compliance/cmd/sbom/sbom.go
new file mode 100644
index 0000000..afb377e
--- /dev/null
+++ b/tools/compliance/cmd/sbom/sbom.go
@@ -0,0 +1,399 @@
+// Copyright 2022 Google LLC
+//
+// 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.
+
+package main
+
+import (
+	"bytes"
+	"flag"
+	"fmt"
+	"io"
+	"io/fs"
+	"os"
+	"path/filepath"
+	"sort"
+	"strings"
+	"time"
+
+	"android/soong/response"
+	"android/soong/tools/compliance"
+	"android/soong/tools/compliance/projectmetadata"
+
+	"github.com/google/blueprint/deptools"
+)
+
+var (
+	failNoneRequested = fmt.Errorf("\nNo license metadata files requested")
+	failNoLicenses    = fmt.Errorf("No licenses found")
+)
+
+type context struct {
+	stdout       io.Writer
+	stderr       io.Writer
+	rootFS       fs.FS
+	product      string
+	stripPrefix  []string
+	creationTime creationTimeGetter
+}
+
+func (ctx context) strip(installPath string) string {
+	for _, prefix := range ctx.stripPrefix {
+		if strings.HasPrefix(installPath, prefix) {
+			p := strings.TrimPrefix(installPath, prefix)
+			if 0 == len(p) {
+				p = ctx.product
+			}
+			if 0 == len(p) {
+				continue
+			}
+			return p
+		}
+	}
+	return installPath
+}
+
+// newMultiString creates a flag that allows multiple values in an array.
+func newMultiString(flags *flag.FlagSet, name, usage string) *multiString {
+	var f multiString
+	flags.Var(&f, name, usage)
+	return &f
+}
+
+// multiString implements the flag `Value` interface for multiple strings.
+type multiString []string
+
+func (ms *multiString) String() string     { return strings.Join(*ms, ", ") }
+func (ms *multiString) Set(s string) error { *ms = append(*ms, s); return nil }
+
+func main() {
+	var expandedArgs []string
+	for _, arg := range os.Args[1:] {
+		if strings.HasPrefix(arg, "@") {
+			f, err := os.Open(strings.TrimPrefix(arg, "@"))
+			if err != nil {
+				fmt.Fprintln(os.Stderr, err.Error())
+				os.Exit(1)
+			}
+
+			respArgs, err := response.ReadRspFile(f)
+			f.Close()
+			if err != nil {
+				fmt.Fprintln(os.Stderr, err.Error())
+				os.Exit(1)
+			}
+			expandedArgs = append(expandedArgs, respArgs...)
+		} else {
+			expandedArgs = append(expandedArgs, arg)
+		}
+	}
+
+	flags := flag.NewFlagSet("flags", flag.ExitOnError)
+
+	flags.Usage = func() {
+		fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...}
+
+Outputs an SBOM.spdx.
+
+Options:
+`, filepath.Base(os.Args[0]))
+		flags.PrintDefaults()
+	}
+
+	outputFile := flags.String("o", "-", "Where to write the SBOM spdx file. (default stdout)")
+	depsFile := flags.String("d", "", "Where to write the deps file")
+	product := flags.String("product", "", "The name of the product for which the notice is generated.")
+	stripPrefix := newMultiString(flags, "strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)")
+
+	flags.Parse(expandedArgs)
+
+	// Must specify at least one root target.
+	if flags.NArg() == 0 {
+		flags.Usage()
+		os.Exit(2)
+	}
+
+	if len(*outputFile) == 0 {
+		flags.Usage()
+		fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n")
+		os.Exit(2)
+	} else {
+		dir, err := filepath.Abs(filepath.Dir(*outputFile))
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err)
+			os.Exit(1)
+		}
+		fi, err := os.Stat(dir)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err)
+			os.Exit(1)
+		}
+		if !fi.IsDir() {
+			fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile)
+			os.Exit(1)
+		}
+	}
+
+	var ofile io.Writer
+	ofile = os.Stdout
+	var obuf *bytes.Buffer
+	if *outputFile != "-" {
+		obuf = &bytes.Buffer{}
+		ofile = obuf
+	}
+
+	ctx := &context{ofile, os.Stderr, compliance.FS, *product, *stripPrefix, actualTime}
+
+	deps, err := sbomGenerator(ctx, flags.Args()...)
+	if err != nil {
+		if err == failNoneRequested {
+			flags.Usage()
+		}
+		fmt.Fprintf(os.Stderr, "%s\n", err.Error())
+		os.Exit(1)
+	}
+
+	if *outputFile != "-" {
+		err := os.WriteFile(*outputFile, obuf.Bytes(), 0666)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "could not write output to %q: %s\n", *outputFile, err)
+			os.Exit(1)
+		}
+	}
+
+	if *depsFile != "" {
+		err := deptools.WriteDepFile(*depsFile, *outputFile, deps)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "could not write deps to %q: %s\n", *depsFile, err)
+			os.Exit(1)
+		}
+	}
+	os.Exit(0)
+}
+
+type creationTimeGetter func() time.Time
+
+// actualTime returns current time in UTC
+func actualTime() time.Time {
+	return time.Now().UTC()
+}
+
+// replaceSlashes replaces "/" by "-" for the library path to be used for packages & files SPDXID
+func replaceSlashes(x string) string {
+	return strings.ReplaceAll(x, "/", "-")
+}
+
+// getPackageName returns a package name of a target Node
+func getPackageName(_ *context, tn *compliance.TargetNode) string {
+	return replaceSlashes(tn.Name())
+}
+
+// getDocumentName returns a package name of a target Node
+func getDocumentName(ctx *context, tn *compliance.TargetNode, pm *projectmetadata.ProjectMetadata) string {
+	if len(ctx.product) > 0 {
+		return replaceSlashes(ctx.product)
+	}
+	if len(tn.ModuleName()) > 0 {
+		if pm != nil {
+			return replaceSlashes(pm.Name() + ":" + tn.ModuleName())
+		}
+		return replaceSlashes(tn.ModuleName())
+	}
+
+	// TO DO: Replace tn.Name() with pm.Name() + parts of the target name
+	return replaceSlashes(tn.Name())
+}
+
+// getDownloadUrl returns the download URL if available (GIT, SVN, etc..),
+// or NOASSERTION if not available, none determined or ambiguous
+func getDownloadUrl(_ *context, pm *projectmetadata.ProjectMetadata) string {
+	if pm == nil {
+		return "NOASSERTION"
+	}
+
+	urlsByTypeName := pm.UrlsByTypeName()
+	if urlsByTypeName == nil {
+		return "NOASSERTION"
+	}
+
+	url := urlsByTypeName.DownloadUrl()
+	if url == "" {
+		return "NOASSERTION"
+	}
+	return url
+}
+
+// getProjectMetadata returns the project metadata for the target node
+func getProjectMetadata(_ *context, pmix *projectmetadata.Index,
+	tn *compliance.TargetNode) (*projectmetadata.ProjectMetadata, error) {
+	pms, err := pmix.MetadataForProjects(tn.Projects()...)
+	if err != nil {
+		return nil, fmt.Errorf("Unable to read projects for %q: %w\n", tn, err)
+	}
+	if len(pms) == 0 {
+		return nil, nil
+	}
+
+	// TO DO: skip first element if it doesn't have one of the three info needed
+	return pms[0], nil
+}
+
+// sbomGenerator implements the spdx bom utility
+
+// SBOM is part of the new government regulation issued to improve national cyber security
+// and enhance software supply chain and transparency, see https://www.cisa.gov/sbom
+
+// sbomGenerator uses the SPDX standard, see the SPDX specification (https://spdx.github.io/spdx-spec/)
+// sbomGenerator is also following the internal google SBOM styleguide (http://goto.google.com/spdx-style-guide)
+func sbomGenerator(ctx *context, files ...string) ([]string, error) {
+	// Must be at least one root file.
+	if len(files) < 1 {
+		return nil, failNoneRequested
+	}
+
+	pmix := projectmetadata.NewIndex(ctx.rootFS)
+
+	lg, err := compliance.ReadLicenseGraph(ctx.rootFS, ctx.stderr, files)
+
+	if err != nil {
+		return nil, fmt.Errorf("Unable to read license text file(s) for %q: %v\n", files, err)
+	}
+
+	// implementing the licenses references for the packages
+	licenses := make(map[string]string)
+	concludedLicenses := func(licenseTexts []string) string {
+		licenseRefs := make([]string, 0, len(licenseTexts))
+		for _, licenseText := range licenseTexts {
+			license := strings.SplitN(licenseText, ":", 2)[0]
+			if _, ok := licenses[license]; !ok {
+				licenseRef := "LicenseRef-" + replaceSlashes(license)
+				licenses[license] = licenseRef
+			}
+
+			licenseRefs = append(licenseRefs, licenses[license])
+		}
+		if len(licenseRefs) > 1 {
+			return "(" + strings.Join(licenseRefs, " AND ") + ")"
+		} else if len(licenseRefs) == 1 {
+			return licenseRefs[0]
+		}
+		return "NONE"
+	}
+
+	isMainPackage := true
+	var mainPackage string
+	visitedNodes := make(map[*compliance.TargetNode]struct{})
+
+	// performing a Breadth-first top down walk of licensegraph and building package information
+	compliance.WalkTopDownBreadthFirst(nil, lg,
+		func(lg *compliance.LicenseGraph, tn *compliance.TargetNode, path compliance.TargetEdgePath) bool {
+			if err != nil {
+				return false
+			}
+			var pm *projectmetadata.ProjectMetadata
+			pm, err = getProjectMetadata(ctx, pmix, tn)
+			if err != nil {
+				return false
+			}
+
+			if isMainPackage {
+				mainPackage = getDocumentName(ctx, tn, pm)
+				fmt.Fprintf(ctx.stdout, "SPDXVersion: SPDX-2.2\n")
+				fmt.Fprintf(ctx.stdout, "DataLicense: CC-1.0\n")
+				fmt.Fprintf(ctx.stdout, "DocumentName: %s\n", mainPackage)
+				fmt.Fprintf(ctx.stdout, "SPDXID: SPDXRef-DOCUMENT-%s\n", mainPackage)
+				fmt.Fprintf(ctx.stdout, "DocumentNamespace: Android\n")
+				fmt.Fprintf(ctx.stdout, "Creator: Organization: Google LLC\n")
+				fmt.Fprintf(ctx.stdout, "Created: %s\n", ctx.creationTime().Format("2006-01-02T15:04:05Z"))
+				isMainPackage = false
+			}
+
+			relationships := make([]string, 0, 1)
+			defer func() {
+				if r := recover(); r != nil {
+					panic(r)
+				}
+				for _, relationship := range relationships {
+					fmt.Fprintln(ctx.stdout, relationship)
+				}
+			}()
+			if len(path) == 0 {
+				relationships = append(relationships,
+					fmt.Sprintf("Relationship: SPDXRef-DOCUMENT-%s DESCRIBES SPDXRef-Package-%s",
+						mainPackage, getPackageName(ctx, tn)))
+			} else {
+				// Check parent and identify annotation
+				parent := path[len(path)-1]
+				targetEdge := parent.Edge()
+				if targetEdge.IsRuntimeDependency() {
+					// Adding the dynamic link annotation RUNTIME_DEPENDENCY_OF relationship
+					relationships = append(relationships, fmt.Sprintf("Relationship: SPDXRef-Package-%s RUNTIME_DEPENDENCY_OF SPDXRef-Package-%s", getPackageName(ctx, tn), getPackageName(ctx, targetEdge.Target())))
+
+				} else if targetEdge.IsDerivation() {
+					// Adding the  derivation annotation as a CONTAINS relationship
+					relationships = append(relationships, fmt.Sprintf("Relationship: SPDXRef-Package-%s CONTAINS SPDXRef-Package-%s", getPackageName(ctx, targetEdge.Target()), getPackageName(ctx, tn)))
+
+				} else if targetEdge.IsBuildTool() {
+					// Adding the toolchain annotation as a BUILD_TOOL_OF relationship
+					relationships = append(relationships, fmt.Sprintf("Relationship: SPDXRef-Package-%s BUILD_TOOL_OF SPDXRef-Package-%s", getPackageName(ctx, tn), getPackageName(ctx, targetEdge.Target())))
+				} else {
+					panic(fmt.Errorf("Unknown dependency type: %v", targetEdge.Annotations()))
+				}
+			}
+
+			if _, alreadyVisited := visitedNodes[tn]; alreadyVisited {
+				return false
+			}
+			visitedNodes[tn] = struct{}{}
+			pkgName := getPackageName(ctx, tn)
+			fmt.Fprintf(ctx.stdout, "##### Package: %s\n", strings.Replace(pkgName, "-", "/", -2))
+			fmt.Fprintf(ctx.stdout, "PackageName: %s\n", pkgName)
+			if pm != nil && pm.Version() != "" {
+				fmt.Fprintf(ctx.stdout, "PackageVersion: %s\n", pm.Version())
+			}
+			fmt.Fprintf(ctx.stdout, "SPDXID: SPDXRef-Package-%s\n", pkgName)
+			fmt.Fprintf(ctx.stdout, "PackageDownloadLocation: %s\n", getDownloadUrl(ctx, pm))
+			fmt.Fprintf(ctx.stdout, "PackageLicenseConcluded: %s\n", concludedLicenses(tn.LicenseTexts()))
+			return true
+		})
+
+	fmt.Fprintf(ctx.stdout, "##### Non-standard license:\n")
+
+	licenseTexts := make([]string, 0, len(licenses))
+
+	for licenseText := range licenses {
+		licenseTexts = append(licenseTexts, licenseText)
+	}
+
+	sort.Strings(licenseTexts)
+
+	for _, licenseText := range licenseTexts {
+		fmt.Fprintf(ctx.stdout, "LicenseID: %s\n", licenses[licenseText])
+		// open the file
+		f, err := ctx.rootFS.Open(filepath.Clean(licenseText))
+		if err != nil {
+			return nil, fmt.Errorf("error opening license text file %q: %w", licenseText, err)
+		}
+
+		// read the file
+		text, err := io.ReadAll(f)
+		if err != nil {
+			return nil, fmt.Errorf("error reading license text file %q: %w", licenseText, err)
+		}
+		// adding the extracted license text
+		fmt.Fprintf(ctx.stdout, "ExtractedText: <text>%v</text>\n", string(text))
+	}
+
+	deps := licenseTexts
+	return deps, nil
+}
diff --git a/tools/compliance/cmd/sbom/sbom_test.go b/tools/compliance/cmd/sbom/sbom_test.go
new file mode 100644
index 0000000..5e70580
--- /dev/null
+++ b/tools/compliance/cmd/sbom/sbom_test.go
@@ -0,0 +1,1629 @@
+// Copyright 2022 Google LLC
+//
+// 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.
+
+package main
+
+import (
+	"bufio"
+	"bytes"
+	"fmt"
+	"os"
+	"reflect"
+	"regexp"
+	"strings"
+	"testing"
+	"time"
+
+	"android/soong/tools/compliance"
+)
+
+var (
+	spdxVersionTag              = regexp.MustCompile(`^\s*SPDXVersion: SPDX-2.2\s*$`)
+	spdxDataLicenseTag          = regexp.MustCompile(`^\s*DataLicense: CC-1.0\s*$`)
+	spdxDocumentNameTag         = regexp.MustCompile(`^\s*DocumentName:\s*Android*\s*$`)
+	spdxIDTag                   = regexp.MustCompile(`^\s*SPDXID:\s*SPDXRef-DOCUMENT-(.*)\s*$`)
+	spdxDocumentNameSpaceTag    = regexp.MustCompile(`^\s*DocumentNamespace:\s*Android\s*$`)
+	spdxCreatorOrganizationTag  = regexp.MustCompile(`^\s*Creator:\s*Organization:\s*Google LLC\s*$`)
+	spdxCreatedTimeTag          = regexp.MustCompile(`^\s*Created: 1970-01-01T00:00:00Z\s*$`)
+	spdxPackageTag              = regexp.MustCompile(`^\s*#####\s*Package:\s*(.*)\s*$`)
+	spdxPackageNameTag          = regexp.MustCompile(`^\s*PackageName:\s*(.*)\s*$`)
+	spdxPkgIDTag                = regexp.MustCompile(`^\s*SPDXID:\s*SPDXRef-Package-(.*)\s*$`)
+	spdxPkgDownloadLocationTag  = regexp.MustCompile(`^\s*PackageDownloadLocation:\s*NOASSERTION\s*$`)
+	spdxPkgLicenseDeclaredTag   = regexp.MustCompile(`^\s*PackageLicenseConcluded:\s*LicenseRef-(.*)\s*$`)
+	spdxRelationshipTag         = regexp.MustCompile(`^\s*Relationship:\s*SPDXRef-(.*)\s*(DESCRIBES|CONTAINS|BUILD_TOOL_OF|RUNTIME_DEPENDENCY_OF)\s*SPDXRef-Package-(.*)\s*$`)
+	spdxLicenseTag              = regexp.MustCompile(`^\s*##### Non-standard license:\s*$`)
+	spdxLicenseIDTag            = regexp.MustCompile(`^\s*LicenseID: LicenseRef-(.*)\s*$`)
+	spdxExtractedTextTag        = regexp.MustCompile(`^\s*ExtractedText:\s*<text>(.*)\s*$`)
+	spdxExtractedClosingTextTag = regexp.MustCompile(`^\s*</text>\s*$`)
+)
+
+func TestMain(m *testing.M) {
+	// Change into the parent directory before running the tests
+	// so they can find the testdata directory.
+	if err := os.Chdir(".."); err != nil {
+		fmt.Printf("failed to change to testdata directory: %s\n", err)
+		os.Exit(1)
+	}
+	os.Exit(m.Run())
+}
+
+func Test(t *testing.T) {
+	tests := []struct {
+		condition    string
+		name         string
+		outDir       string
+		roots        []string
+		stripPrefix  string
+		expectedOut  []matcher
+		expectedDeps []string
+	}{
+		{
+			condition: "firstparty",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []matcher{
+				spdxVersion{},
+				spdxDataLicense{},
+				spdxDocumentName{"Android"},
+				spdxID{"Android"},
+				spdxDocumentNameSpace{},
+				spdxCreatorOrganization{},
+				spdxCreatedTime{},
+				packageTag{"testdata/firstparty/highest.apex.meta_lic"},
+				packageName{"testdata/firstparty/highest.apex.meta_lic"},
+				spdxPkgID{"testdata/firstparty/highest.apex.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"DOCUMENT-Android ", "testdata-firstparty-highest.apex.meta_lic", "DESCRIBES"},
+				packageTag{"testdata/firstparty/bin/bin1.meta_lic"},
+				packageName{"testdata/firstparty/bin/bin1.meta_lic"},
+				spdxPkgID{"testdata/firstparty/bin/bin1.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"Package-testdata/firstparty/highest.apex.meta_lic ", "testdata/firstparty/bin/bin1.meta_lic", "CONTAINS"},
+				packageTag{"testdata/firstparty/bin/bin2.meta_lic"},
+				packageName{"testdata/firstparty/bin/bin2.meta_lic"},
+				spdxPkgID{"testdata/firstparty/bin/bin2.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"Package-testdata/firstparty/highest.apex.meta_lic ", "testdata-firstparty-bin-bin2.meta_lic", "CONTAINS"},
+				packageTag{"testdata/firstparty/lib/liba.so.meta_lic"},
+				packageName{"testdata/firstparty/lib/liba.so.meta_lic"},
+				spdxPkgID{"testdata/firstparty/lib/liba.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"Package-testdata/firstparty/highest.apex.meta_lic ", "testdata/firstparty/lib/liba.so.meta_lic", "CONTAINS"},
+				packageTag{"testdata/firstparty/lib/libb.so.meta_lic"},
+				packageName{"testdata/firstparty/lib/libb.so.meta_lic"},
+				spdxPkgID{"testdata/firstparty/lib/libb.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"Package-testdata/firstparty/highest.apex.meta_lic ", "testdata/firstparty/lib/libb.so.meta_lic", "CONTAINS"},
+				spdxRelationship{"Package-testdata/firstparty/bin/bin1.meta_lic ", "testdata/firstparty/lib/liba.so.meta_lic", "CONTAINS"},
+				packageTag{"testdata/firstparty/lib/libc.a.meta_lic"},
+				packageName{"testdata/firstparty/lib/libc.a.meta_lic"},
+				spdxPkgID{"testdata/firstparty/lib/libc.a.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"Package-testdata-firstparty-bin-bin1.meta_lic ", "testdata/firstparty/lib/libc.a.meta_lic", "CONTAINS"},
+				spdxRelationship{"Package-testdata/firstparty/lib/libb.so.meta_lic ", "testdata/firstparty/bin/bin2.meta_lic", "RUNTIME_DEPENDENCY_OF"},
+				packageTag{"testdata/firstparty/lib/libd.so.meta_lic"},
+				packageName{"testdata/firstparty/lib/libd.so.meta_lic"},
+				spdxPkgID{"testdata/firstparty/lib/libd.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"Package-testdata/firstparty/lib/libd.so.meta_lic ", "testdata/firstparty/bin/bin2.meta_lic", "RUNTIME_DEPENDENCY_OF"},
+				spdxLicense{},
+				spdxLicenseID{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxExtractedText{"&&&First Party License&&&"},
+				spdxExtractedClosingText{},
+			},
+			expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+		},
+		{
+			condition: "firstparty",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []matcher{
+				spdxVersion{},
+				spdxDataLicense{},
+				spdxDocumentName{"Android"},
+				spdxID{"Android"},
+				spdxDocumentNameSpace{},
+				spdxCreatorOrganization{},
+				spdxCreatedTime{},
+				packageTag{"testdata/firstparty/application.meta_lic"},
+				packageName{"testdata/firstparty/application.meta_lic"},
+				spdxPkgID{"testdata/firstparty/application.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"DOCUMENT-Android ", "testdata/firstparty/application.meta_lic", "DESCRIBES"},
+				packageTag{"testdata/firstparty/bin/bin3.meta_lic"},
+				packageName{"testdata/firstparty/bin/bin3.meta_lic"},
+				spdxPkgID{"testdata/firstparty/bin/bin3.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"Package-testdata/firstparty/bin/bin3.meta_lic ", "testdata-firstparty-application.meta_lic", "BUILD_TOOL_OF"},
+				packageTag{"testdata/firstparty/lib/liba.so.meta_lic"},
+				packageName{"testdata/firstparty/lib/liba.so.meta_lic"},
+				spdxPkgID{"testdata/firstparty/lib/liba.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"Package-testdata/firstparty/application.meta_lic ", "testdata/firstparty/lib/liba.so.meta_lic", "CONTAINS"},
+				packageTag{"testdata/firstparty/lib/libb.so.meta_lic"},
+				packageName{"testdata/firstparty/lib/libb.so.meta_lic"},
+				spdxPkgID{"testdata/firstparty/lib/libb.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"Package-testdata/firstparty/lib/libb.so.meta_lic ", "testdata-firstparty-application.meta_lic", "RUNTIME_DEPENDENCY_OF"},
+				spdxLicense{},
+				spdxLicenseID{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxExtractedText{"&&&First Party License&&&"},
+				spdxExtractedClosingText{},
+			},
+			expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+		},
+		{
+			condition: "firstparty",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []matcher{
+				spdxVersion{},
+				spdxDataLicense{},
+				spdxDocumentName{"Android"},
+				spdxID{"Android"},
+				spdxDocumentNameSpace{},
+				spdxCreatorOrganization{},
+				spdxCreatedTime{},
+				packageTag{"testdata/firstparty/container.zip.meta_lic"},
+				packageName{"testdata/firstparty/container.zip.meta_lic"},
+				spdxPkgID{"testdata/firstparty/container.zip.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"DOCUMENT-Android ", "testdata/firstparty/container.zip.meta_lic", "DESCRIBES"},
+				packageTag{"testdata/firstparty/bin/bin1.meta_lic"},
+				packageName{"testdata/firstparty/bin/bin1.meta_lic"},
+				spdxPkgID{"testdata/firstparty/bin/bin1.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"Package-testdata/firstparty/container.zip.meta_lic ", "testdata/firstparty/bin/bin1.meta_lic", "CONTAINS"},
+				packageTag{"testdata/firstparty/bin/bin2.meta_lic"},
+				packageName{"testdata/firstparty/bin/bin2.meta_lic"},
+				spdxPkgID{"testdata/firstparty/bin/bin2.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"Package-testdata/firstparty/container.zip.meta_lic ", "testdata/firstparty/bin/bin2.meta_lic", "CONTAINS"},
+				packageTag{"testdata/firstparty/lib/liba.so.meta_lic"},
+				packageName{"testdata/firstparty/lib/liba.so.meta_lic"},
+				spdxPkgID{"testdata/firstparty/lib/liba.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"Package-testdata/firstparty/container.zip.meta_lic ", "testdata/firstparty/lib/liba.so.meta_lic", "CONTAINS"},
+				packageTag{"testdata/firstparty/lib/libb.so.meta_lic"},
+				packageName{"testdata/firstparty/lib/libb.so.meta_lic"},
+				spdxPkgID{"testdata/firstparty/lib/libb.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"Package-testdata/firstparty/container.zip.meta_lic ", "testdata/firstparty/lib/libb.so.meta_lic", "CONTAINS"},
+				spdxRelationship{"Package-testdata/firstparty/bin/bin1.meta_lic ", "testdata/firstparty/lib/liba.so.meta_lic", "CONTAINS"},
+				packageTag{"testdata/firstparty/lib/libc.a.meta_lic"},
+				packageName{"testdata/firstparty/lib/libc.a.meta_lic"},
+				spdxPkgID{"testdata/firstparty/lib/libc.a.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"Package-testdata/firstparty/bin/bin1.meta_lic ", "testdata/firstparty/lib/libc.a.meta_lic", "CONTAINS"},
+				spdxRelationship{"Package-testdata/firstparty/lib/libb.so.meta_lic ", "testdata/firstparty/bin/bin2.meta_lic", "RUNTIME_DEPENDENCY_OF"},
+				packageTag{"testdata/firstparty/lib/libd.so.meta_lic"},
+				packageName{"testdata/firstparty/lib/libd.so.meta_lic"},
+				spdxPkgID{"testdata/firstparty/lib/libd.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"Package-testdata/firstparty/lib/libd.so.meta_lic ", "testdata/firstparty/bin/bin2.meta_lic", "RUNTIME_DEPENDENCY_OF"},
+				spdxLicense{},
+				spdxLicenseID{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxExtractedText{"&&&First Party License&&&"},
+				spdxExtractedClosingText{},
+			},
+			expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+		},
+		{
+			condition: "firstparty",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []matcher{
+				spdxVersion{},
+				spdxDataLicense{},
+				spdxDocumentName{"Android"},
+				spdxID{"Android"},
+				spdxDocumentNameSpace{},
+				spdxCreatorOrganization{},
+				spdxCreatedTime{},
+				packageTag{"testdata/firstparty/bin/bin1.meta_lic"},
+				packageName{"testdata/firstparty/bin/bin1.meta_lic"},
+				spdxPkgID{"testdata/firstparty/bin/bin1.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"DOCUMENT-Android ", "testdata/firstparty/bin/bin1.meta_lic", "DESCRIBES"},
+				packageTag{"testdata/firstparty/lib/liba.so.meta_lic"},
+				packageName{"testdata/firstparty/lib/liba.so.meta_lic"},
+				spdxPkgID{"testdata/firstparty/lib/liba.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"Package-testdata/firstparty/bin/bin1.meta_lic ", "testdata/firstparty/lib/liba.so.meta_lic", "CONTAINS"},
+				packageTag{"testdata/firstparty/lib/libc.a.meta_lic"},
+				packageName{"testdata/firstparty/lib/libc.a.meta_lic"},
+				spdxPkgID{"testdata/firstparty/lib/libc.a.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"Package-testdata/firstparty/bin/bin1.meta_lic ", "testdata/firstparty/lib/libc.a.meta_lic", "CONTAINS"},
+				spdxLicense{},
+				spdxLicenseID{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxExtractedText{"&&&First Party License&&&"},
+				spdxExtractedClosingText{},
+			},
+			expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+		},
+		{
+			condition: "firstparty",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []matcher{
+				spdxVersion{},
+				spdxDataLicense{},
+				spdxDocumentName{"Android"},
+				spdxID{"Android"},
+				spdxDocumentNameSpace{},
+				spdxCreatorOrganization{},
+				spdxCreatedTime{},
+				packageTag{"testdata/firstparty/lib/libd.so.meta_lic"},
+				packageName{"testdata/firstparty/lib/libd.so.meta_lic"},
+				spdxPkgID{"testdata/firstparty/lib/libd.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"DOCUMENT-Android ", "testdata/firstparty/lib/libd.so.meta_lic", "DESCRIBES"},
+				spdxLicense{},
+				spdxLicenseID{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxExtractedText{"&&&First Party License&&&"},
+				spdxExtractedClosingText{},
+			},
+			expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+		},
+		{
+			condition: "notice",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []matcher{
+				spdxVersion{},
+				spdxDataLicense{},
+				spdxDocumentName{"Android"},
+				spdxID{"Android"},
+				spdxDocumentNameSpace{},
+				spdxCreatorOrganization{},
+				spdxCreatedTime{},
+				packageTag{"testdata/notice/highest.apex.meta_lic"},
+				packageName{"testdata/notice/highest.apex.meta_lic"},
+				spdxPkgID{"testdata/notice/highest.apex.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"DOCUMENT-Android ", "testdata/notice/highest.apex.meta_lic", "DESCRIBES"},
+				packageTag{"testdata/notice/bin/bin1.meta_lic"},
+				packageName{"testdata/notice/bin/bin1.meta_lic"},
+				spdxPkgID{"testdata/notice/bin/bin1.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"Package-testdata/notice/highest.apex.meta_lic ", "testdata/notice/bin/bin1.meta_lic", "CONTAINS"},
+				packageTag{"testdata/notice/bin/bin2.meta_lic"},
+				packageName{"testdata/notice/bin/bin2.meta_lic"},
+				spdxPkgID{"testdata/notice/bin/bin2.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"Package-testdata/notice/highest.apex.meta_lic ", "testdata/notice/bin/bin2.meta_lic", "CONTAINS"},
+				packageTag{"testdata/notice/lib/liba.so.meta_lic"},
+				packageName{"testdata/notice/lib/liba.so.meta_lic"},
+				spdxPkgID{"testdata/notice/lib/liba.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-notice-NOTICE_LICENSE"},
+				spdxRelationship{"Package-testdata/notice/highest.apex.meta_lic ", "testdata/notice/lib/liba.so.meta_lic", "CONTAINS"},
+				packageTag{"testdata/notice/lib/libb.so.meta_lic"},
+				packageName{"testdata/notice/lib/libb.so.meta_lic"},
+				spdxPkgID{"testdata/notice/lib/libb.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"Package-testdata/notice/highest.apex.meta_lic ", "testdata/notice/lib/libb.so.meta_lic", "CONTAINS"},
+				spdxRelationship{"Package-testdata/notice/bin/bin1.meta_lic ", "testdata/notice/lib/liba.so.meta_lic", "CONTAINS"},
+				packageTag{"testdata/notice/lib/libc.a.meta_lic"},
+				packageName{"testdata/notice/lib/libc.a.meta_lic"},
+				spdxPkgID{"testdata/notice/lib/libc.a.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-notice-NOTICE_LICENSE"},
+				spdxRelationship{"Package-testdata/notice/bin/bin1.meta_lic ", "testdata/notice/lib/libc.a.meta_lic", "CONTAINS"},
+				spdxRelationship{"Package-testdata/notice/lib/libb.so.meta_lic ", "testdata/notice/bin/bin2.meta_lic", "RUNTIME_DEPENDENCY_OF"},
+				packageTag{"testdata/notice/lib/libd.so.meta_lic"},
+				packageName{"testdata/notice/lib/libd.so.meta_lic"},
+				spdxPkgID{"testdata/notice/lib/libd.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-notice-NOTICE_LICENSE"},
+				spdxRelationship{"Package-testdata/notice/lib/libd.so.meta_lic ", "testdata/notice/bin/bin2.meta_lic", "RUNTIME_DEPENDENCY_OF"},
+				spdxLicense{},
+				spdxLicenseID{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxExtractedText{"&&&First Party License&&&"},
+				spdxExtractedClosingText{},
+				spdxLicenseID{"testdata-notice-NOTICE_LICENSE"},
+				spdxExtractedText{"%%%Notice License%%%"},
+				spdxExtractedClosingText{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/notice/NOTICE_LICENSE",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []matcher{
+				spdxVersion{},
+				spdxDataLicense{},
+				spdxDocumentName{"Android"},
+				spdxID{"Android"},
+				spdxDocumentNameSpace{},
+				spdxCreatorOrganization{},
+				spdxCreatedTime{},
+				packageTag{"testdata/notice/container.zip.meta_lic"},
+				packageName{"testdata/notice/container.zip.meta_lic"},
+				spdxPkgID{"testdata/notice/container.zip.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"DOCUMENT-Android ", "testdata/notice/container.zip.meta_lic", "DESCRIBES"},
+				packageTag{"testdata/notice/bin/bin1.meta_lic"},
+				packageName{"testdata/notice/bin/bin1.meta_lic"},
+				spdxPkgID{"testdata/notice/bin/bin1.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"Package-testdata/notice/container.zip.meta_lic ", "testdata/notice/bin/bin1.meta_lic", "CONTAINS"},
+				packageTag{"testdata/notice/bin/bin2.meta_lic"},
+				packageName{"testdata/notice/bin/bin2.meta_lic"},
+				spdxPkgID{"testdata/notice/bin/bin2.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"Package-testdata/notice/container.zip.meta_lic ", "testdata/notice/bin/bin2.meta_lic", "CONTAINS"},
+				packageTag{"testdata/notice/lib/liba.so.meta_lic"},
+				packageName{"testdata/notice/lib/liba.so.meta_lic"},
+				spdxPkgID{"testdata/notice/lib/liba.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-notice-NOTICE_LICENSE"},
+				spdxRelationship{"Package-testdata/notice/container.zip.meta_lic ", "testdata/notice/lib/liba.so.meta_lic", "CONTAINS"},
+				packageTag{"testdata/notice/lib/libb.so.meta_lic"},
+				packageName{"testdata/notice/lib/libb.so.meta_lic"},
+				spdxPkgID{"testdata/notice/lib/libb.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"Package-testdata/notice/container.zip.meta_lic ", "testdata/notice/lib/libb.so.meta_lic", "CONTAINS"},
+				spdxRelationship{"Package-testdata/notice/bin/bin1.meta_lic ", "testdata/notice/lib/liba.so.meta_lic", "CONTAINS"},
+				packageTag{"testdata/notice/lib/libc.a.meta_lic"},
+				packageName{"testdata/notice/lib/libc.a.meta_lic"},
+				spdxPkgID{"testdata/notice/lib/libc.a.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-notice-NOTICE_LICENSE"},
+				spdxRelationship{"Package-testdata/notice/bin/bin1.meta_lic ", "testdata/notice/lib/libc.a.meta_lic", "CONTAINS"},
+				spdxRelationship{"Package-testdata/notice/lib/libb.so.meta_lic ", "testdata/notice/bin/bin2.meta_lic", "RUNTIME_DEPENDENCY_OF"},
+				packageTag{"testdata/notice/lib/libd.so.meta_lic"},
+				packageName{"testdata/notice/lib/libd.so.meta_lic"},
+				spdxPkgID{"testdata/notice/lib/libd.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-notice-NOTICE_LICENSE"},
+				spdxRelationship{"Package-testdata/notice/lib/libd.so.meta_lic ", "testdata/notice/bin/bin2.meta_lic", "RUNTIME_DEPENDENCY_OF"},
+				spdxLicense{},
+				spdxLicenseID{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxExtractedText{"&&&First Party License&&&"},
+				spdxExtractedClosingText{},
+				spdxLicenseID{"testdata-notice-NOTICE_LICENSE"},
+				spdxExtractedText{"%%%Notice License%%%"},
+				spdxExtractedClosingText{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/notice/NOTICE_LICENSE",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []matcher{
+				spdxVersion{},
+				spdxDataLicense{},
+				spdxDocumentName{"Android"},
+				spdxID{"Android"},
+				spdxDocumentNameSpace{},
+				spdxCreatorOrganization{},
+				spdxCreatedTime{},
+				packageTag{"testdata/notice/application.meta_lic"},
+				packageName{"testdata/notice/application.meta_lic"},
+				spdxPkgID{"testdata/notice/application.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"DOCUMENT-Android ", "testdata-notice-application.meta_lic", "DESCRIBES"},
+				packageTag{"testdata/notice/bin/bin3.meta_lic"},
+				packageName{"testdata/notice/bin/bin3.meta_lic"},
+				spdxPkgID{"testdata/notice/bin/bin3.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-notice-NOTICE_LICENSE"},
+				spdxRelationship{"Package-testdata-notice-bin-bin3.meta_lic ", "testdata/notice/application.meta_lic", "BUILD_TOOL_OF"},
+				packageTag{"testdata/notice/lib/liba.so.meta_lic"},
+				packageName{"testdata/notice/lib/liba.so.meta_lic"},
+				spdxPkgID{"testdata/notice/lib/liba.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-notice-NOTICE_LICENSE"},
+				spdxRelationship{"Package-testdata/notice/application.meta_lic ", "testdata-notice-lib-liba.so.meta_lic", "CONTAINS"},
+				packageTag{"testdata/notice/lib/libb.so.meta_lic"},
+				packageName{"testdata/notice/lib/libb.so.meta_lic"},
+				spdxPkgID{"testdata/notice/lib/libb.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"Package-testdata-notice-lib-libb.so.meta_lic ", "testdata/notice/application.meta_lic", "RUNTIME_DEPENDENCY_OF"},
+				spdxLicense{},
+				spdxLicenseID{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxExtractedText{"&&&First Party License&&&"},
+				spdxExtractedClosingText{},
+				spdxLicenseID{"testdata-notice-NOTICE_LICENSE"},
+				spdxExtractedText{"%%%Notice License%%%"},
+				spdxExtractedClosingText{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/notice/NOTICE_LICENSE",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []matcher{
+				spdxVersion{},
+				spdxDataLicense{},
+				spdxDocumentName{"Android"},
+				spdxID{"Android"},
+				spdxDocumentNameSpace{},
+				spdxCreatorOrganization{},
+				spdxCreatedTime{},
+				packageTag{"testdata/notice/bin/bin1.meta_lic"},
+				packageName{"testdata/notice/bin/bin1.meta_lic"},
+				spdxPkgID{"testdata/notice/bin/bin1.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"DOCUMENT-Android ", "testdata/notice/bin/bin1.meta_lic", "DESCRIBES"},
+				packageTag{"testdata/notice/lib/liba.so.meta_lic"},
+				packageName{"testdata/notice/lib/liba.so.meta_lic"},
+				spdxPkgID{"testdata/notice/lib/liba.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-notice-NOTICE_LICENSE"},
+				spdxRelationship{"Package-testdata/notice/bin/bin1.meta_lic ", "testdata/notice/lib/liba.so.meta_lic", "CONTAINS"},
+				packageTag{"testdata/notice/lib/libc.a.meta_lic"},
+				packageName{"testdata/notice/lib/libc.a.meta_lic"},
+				spdxPkgID{"testdata/notice/lib/libc.a.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-notice-NOTICE_LICENSE"},
+				spdxRelationship{"Package-testdata/notice/bin/bin1.meta_lic ", "testdata/notice/lib/libc.a.meta_lic", "CONTAINS"},
+				spdxLicense{},
+				spdxLicenseID{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxExtractedText{"&&&First Party License&&&"},
+				spdxExtractedClosingText{},
+				spdxLicenseID{"testdata-notice-NOTICE_LICENSE"},
+				spdxExtractedText{"%%%Notice License%%%"},
+				spdxExtractedClosingText{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/notice/NOTICE_LICENSE",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []matcher{
+				spdxVersion{},
+				spdxDataLicense{},
+				spdxDocumentName{"Android"},
+				spdxID{"Android"},
+				spdxDocumentNameSpace{},
+				spdxCreatorOrganization{},
+				spdxCreatedTime{},
+				packageTag{"testdata/notice/lib/libd.so.meta_lic"},
+				packageName{"testdata/notice/lib/libd.so.meta_lic"},
+				spdxPkgID{"testdata/notice/lib/libd.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-notice-NOTICE_LICENSE"},
+				spdxRelationship{"DOCUMENT-Android ", "testdata/notice/lib/libd.so.meta_lic", "DESCRIBES"},
+				spdxLicense{},
+				spdxLicenseID{"testdata-notice-NOTICE_LICENSE"},
+				spdxExtractedText{"%%%Notice License%%%"},
+				spdxExtractedClosingText{},
+			},
+			expectedDeps: []string{"testdata/notice/NOTICE_LICENSE"},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []matcher{
+				spdxVersion{},
+				spdxDataLicense{},
+				spdxDocumentName{"Android"},
+				spdxID{"Android"},
+				spdxDocumentNameSpace{},
+				spdxCreatorOrganization{},
+				spdxCreatedTime{},
+				packageTag{"testdata/reciprocal/highest.apex.meta_lic"},
+				packageName{"testdata/reciprocal/highest.apex.meta_lic"},
+				spdxPkgID{"testdata/reciprocal/highest.apex.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"DOCUMENT-Android ", "testdata/reciprocal/highest.apex.meta_lic", "DESCRIBES"},
+				packageTag{"testdata/reciprocal/bin/bin1.meta_lic"},
+				packageName{"testdata/reciprocal/bin/bin1.meta_lic"},
+				spdxPkgID{"testdata/reciprocal/bin/bin1.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"Package-testdata/reciprocal/highest.apex.meta_lic ", "testdata-reciprocal-bin-bin1.meta_lic", "CONTAINS"},
+				packageTag{"testdata/reciprocal/bin/bin2.meta_lic"},
+				packageName{"testdata/reciprocal/bin/bin2.meta_lic"},
+				spdxPkgID{"testdata/reciprocal/bin/bin2.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"Package-testdata/reciprocal/highest.apex.meta_lic ", "testdata-reciprocal-bin-bin2.meta_lic", "CONTAINS"},
+				packageTag{"testdata/reciprocal/lib/liba.so.meta_lic"},
+				packageName{"testdata/reciprocal/lib/liba.so.meta_lic"},
+				spdxPkgID{"testdata/reciprocal/lib/liba.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-reciprocal-RECIPROCAL_LICENSE"},
+				spdxRelationship{"Package-testdata/reciprocal/highest.apex.meta_lic ", "testdata/reciprocal/lib/liba.so.meta_lic", "CONTAINS"},
+				packageTag{"testdata/reciprocal/lib/libb.so.meta_lic"},
+				packageName{"testdata/reciprocal/lib/libb.so.meta_lic"},
+				spdxPkgID{"testdata/reciprocal/lib/libb.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"Package-testdata/reciprocal/highest.apex.meta_lic ", "testdata/reciprocal/lib/libb.so.meta_lic", "CONTAINS"},
+				spdxRelationship{"Package-testdata/reciprocal/bin/bin1.meta_lic ", "testdata/reciprocal/lib/liba.so.meta_lic", "CONTAINS"},
+				packageTag{"testdata/reciprocal/lib/libc.a.meta_lic"},
+				packageName{"testdata/reciprocal/lib/libc.a.meta_lic"},
+				spdxPkgID{"testdata/reciprocal/lib/libc.a.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-reciprocal-RECIPROCAL_LICENSE"},
+				spdxRelationship{"Package-testdata/reciprocal/bin/bin1.meta_lic ", "testdata/reciprocal/lib/libc.a.meta_lic", "CONTAINS"},
+				spdxRelationship{"Package-testdata/reciprocal/lib/libb.so.meta_lic ", "testdata/reciprocal/bin/bin2.meta_lic", "RUNTIME_DEPENDENCY_OF"},
+				packageTag{"testdata/reciprocal/lib/libd.so.meta_lic"},
+				packageName{"testdata/reciprocal/lib/libd.so.meta_lic"},
+				spdxPkgID{"testdata/reciprocal/lib/libd.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-notice-NOTICE_LICENSE"},
+				spdxRelationship{"Package-testdata/reciprocal/lib/libd.so.meta_lic ", "testdata/reciprocal/bin/bin2.meta_lic", "RUNTIME_DEPENDENCY_OF"},
+				spdxLicense{},
+				spdxLicenseID{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxExtractedText{"&&&First Party License&&&"},
+				spdxExtractedClosingText{},
+				spdxLicenseID{"testdata-notice-NOTICE_LICENSE"},
+				spdxExtractedText{"%%%Notice License%%%"},
+				spdxExtractedClosingText{},
+				spdxLicenseID{"testdata-reciprocal-RECIPROCAL_LICENSE"},
+				spdxExtractedText{"$$$Reciprocal License$$$"},
+				spdxExtractedClosingText{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/notice/NOTICE_LICENSE",
+				"testdata/reciprocal/RECIPROCAL_LICENSE",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []matcher{
+				spdxVersion{},
+				spdxDataLicense{},
+				spdxDocumentName{"Android"},
+				spdxID{"Android"},
+				spdxDocumentNameSpace{},
+				spdxCreatorOrganization{},
+				spdxCreatedTime{},
+				packageTag{"testdata/reciprocal/container.zip.meta_lic"},
+				packageName{"testdata/reciprocal/container.zip.meta_lic"},
+				spdxPkgID{"testdata/reciprocal/container.zip.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"DOCUMENT-Android ", "testdata/reciprocal/container.zip.meta_lic", "DESCRIBES"},
+				packageTag{"testdata/reciprocal/bin/bin1.meta_lic"},
+				packageName{"testdata/reciprocal/bin/bin1.meta_lic"},
+				spdxPkgID{"testdata/reciprocal/bin/bin1.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"Package-testdata/reciprocal/container.zip.meta_lic ", "testdata-reciprocal-bin-bin1.meta_lic", "CONTAINS"},
+				packageTag{"testdata/reciprocal/bin/bin2.meta_lic"},
+				packageName{"testdata/reciprocal/bin/bin2.meta_lic"},
+				spdxPkgID{"testdata/reciprocal/bin/bin2.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"Package-testdata/reciprocal/container.zip.meta_lic ", "testdata-reciprocal-bin-bin2.meta_lic", "CONTAINS"},
+				packageTag{"testdata/reciprocal/lib/liba.so.meta_lic"},
+				packageName{"testdata/reciprocal/lib/liba.so.meta_lic"},
+				spdxPkgID{"testdata/reciprocal/lib/liba.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-reciprocal-RECIPROCAL_LICENSE"},
+				spdxRelationship{"Package-testdata/reciprocal/container.zip.meta_lic ", "testdata/reciprocal/lib/liba.so.meta_lic", "CONTAINS"},
+				packageTag{"testdata/reciprocal/lib/libb.so.meta_lic"},
+				packageName{"testdata/reciprocal/lib/libb.so.meta_lic"},
+				spdxPkgID{"testdata/reciprocal/lib/libb.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"Package-testdata/reciprocal/container.zip.meta_lic ", "testdata/reciprocal/lib/libb.so.meta_lic", "CONTAINS"},
+				spdxRelationship{"Package-testdata/reciprocal/bin/bin1.meta_lic ", "testdata/reciprocal/lib/liba.so.meta_lic", "CONTAINS"},
+				packageTag{"testdata/reciprocal/lib/libc.a.meta_lic"},
+				packageName{"testdata/reciprocal/lib/libc.a.meta_lic"},
+				spdxPkgID{"testdata/reciprocal/lib/libc.a.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-reciprocal-RECIPROCAL_LICENSE"},
+				spdxRelationship{"Package-testdata/reciprocal/bin/bin1.meta_lic ", "testdata/reciprocal/lib/libc.a.meta_lic", "CONTAINS"},
+				spdxRelationship{"Package-testdata/reciprocal/lib/libb.so.meta_lic ", "testdata/reciprocal/bin/bin2.meta_lic", "RUNTIME_DEPENDENCY_OF"},
+				packageTag{"testdata/reciprocal/lib/libd.so.meta_lic"},
+				packageName{"testdata/reciprocal/lib/libd.so.meta_lic"},
+				spdxPkgID{"testdata/reciprocal/lib/libd.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-notice-NOTICE_LICENSE"},
+				spdxRelationship{"Package-testdata/reciprocal/lib/libd.so.meta_lic ", "testdata/reciprocal/bin/bin2.meta_lic", "RUNTIME_DEPENDENCY_OF"},
+				spdxLicense{},
+				spdxLicenseID{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxExtractedText{"&&&First Party License&&&"},
+				spdxExtractedClosingText{},
+				spdxLicenseID{"testdata-notice-NOTICE_LICENSE"},
+				spdxExtractedText{"%%%Notice License%%%"},
+				spdxExtractedClosingText{},
+				spdxLicenseID{"testdata-reciprocal-RECIPROCAL_LICENSE"},
+				spdxExtractedText{"$$$Reciprocal License$$$"},
+				spdxExtractedClosingText{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/notice/NOTICE_LICENSE",
+				"testdata/reciprocal/RECIPROCAL_LICENSE",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []matcher{
+				spdxVersion{},
+				spdxDataLicense{},
+				spdxDocumentName{"Android"},
+				spdxID{"Android"},
+				spdxDocumentNameSpace{},
+				spdxCreatorOrganization{},
+				spdxCreatedTime{},
+				packageTag{"testdata/reciprocal/application.meta_lic"},
+				packageName{"testdata/reciprocal/application.meta_lic"},
+				spdxPkgID{"testdata/reciprocal/application.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"DOCUMENT-Android ", "testdata/reciprocal/application.meta_lic", "DESCRIBES"},
+				packageTag{"testdata/reciprocal/bin/bin3.meta_lic"},
+				packageName{"testdata/reciprocal/bin/bin3.meta_lic"},
+				spdxPkgID{"testdata/reciprocal/bin/bin3.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-notice-NOTICE_LICENSE"},
+				spdxRelationship{"Package-testdata-reciprocal-bin-bin3.meta_lic ", "testdata/reciprocal/application.meta_lic", "BUILD_TOOL_OF"},
+				packageTag{"testdata/reciprocal/lib/liba.so.meta_lic"},
+				packageName{"testdata/reciprocal/lib/liba.so.meta_lic"},
+				spdxPkgID{"testdata/reciprocal/lib/liba.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-reciprocal-RECIPROCAL_LICENSE"},
+				spdxRelationship{"Package-testdata/reciprocal/application.meta_lic ", "testdata/reciprocal/lib/liba.so.meta_lic", "CONTAINS"},
+				packageTag{"testdata/reciprocal/lib/libb.so.meta_lic"},
+				packageName{"testdata/reciprocal/lib/libb.so.meta_lic"},
+				spdxPkgID{"testdata/reciprocal/lib/libb.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"Package-testdata/reciprocal/lib/libb.so.meta_lic ", "testdata/reciprocal/application.meta_lic", "RUNTIME_DEPENDENCY_OF"},
+				spdxLicense{},
+				spdxLicenseID{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxExtractedText{"&&&First Party License&&&"},
+				spdxExtractedClosingText{},
+				spdxLicenseID{"testdata-notice-NOTICE_LICENSE"},
+				spdxExtractedText{"%%%Notice License%%%"},
+				spdxExtractedClosingText{},
+				spdxLicenseID{"testdata-reciprocal-RECIPROCAL_LICENSE"},
+				spdxExtractedText{"$$$Reciprocal License$$$"},
+				spdxExtractedClosingText{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/notice/NOTICE_LICENSE",
+				"testdata/reciprocal/RECIPROCAL_LICENSE",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []matcher{
+				spdxVersion{},
+				spdxDataLicense{},
+				spdxDocumentName{"Android"},
+				spdxID{"Android"},
+				spdxDocumentNameSpace{},
+				spdxCreatorOrganization{},
+				spdxCreatedTime{},
+				packageTag{"testdata/reciprocal/bin/bin1.meta_lic"},
+				packageName{"testdata/reciprocal/bin/bin1.meta_lic"},
+				spdxPkgID{"testdata/reciprocal/bin/bin1.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"DOCUMENT-Android ", "testdata/reciprocal/bin/bin1.meta_lic", "DESCRIBES"},
+				packageTag{"testdata/reciprocal/lib/liba.so.meta_lic"},
+				packageName{"testdata/reciprocal/lib/liba.so.meta_lic"},
+				spdxPkgID{"testdata/reciprocal/lib/liba.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-reciprocal-RECIPROCAL_LICENSE"},
+				spdxRelationship{"Package-testdata/reciprocal/bin/bin1.meta_lic ", "testdata/reciprocal/lib/liba.so.meta_lic", "CONTAINS"},
+				packageTag{"testdata/reciprocal/lib/libc.a.meta_lic"},
+				packageName{"testdata/reciprocal/lib/libc.a.meta_lic"},
+				spdxPkgID{"testdata/reciprocal/lib/libc.a.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-reciprocal-RECIPROCAL_LICENSE"},
+				spdxRelationship{"Package-testdata/reciprocal/bin/bin1.meta_lic ", "testdata/reciprocal/lib/libc.a.meta_lic", "CONTAINS"},
+				spdxLicense{},
+				spdxLicenseID{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxExtractedText{"&&&First Party License&&&"},
+				spdxExtractedClosingText{},
+				spdxLicenseID{"testdata-reciprocal-RECIPROCAL_LICENSE"},
+				spdxExtractedText{"$$$Reciprocal License$$$"},
+				spdxExtractedClosingText{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/reciprocal/RECIPROCAL_LICENSE",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []matcher{
+				spdxVersion{},
+				spdxDataLicense{},
+				spdxDocumentName{"Android"},
+				spdxID{"Android"},
+				spdxDocumentNameSpace{},
+				spdxCreatorOrganization{},
+				spdxCreatedTime{},
+				packageTag{"testdata/reciprocal/lib/libd.so.meta_lic"},
+				packageName{"testdata/reciprocal/lib/libd.so.meta_lic"},
+				spdxPkgID{"testdata/reciprocal/lib/libd.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-notice-NOTICE_LICENSE"},
+				spdxRelationship{"DOCUMENT-Android ", "testdata/reciprocal/lib/libd.so.meta_lic", "DESCRIBES"},
+				spdxLicense{},
+				spdxLicenseID{"testdata-notice-NOTICE_LICENSE"},
+				spdxExtractedText{"%%%Notice License%%%"},
+				spdxExtractedClosingText{},
+			},
+			expectedDeps: []string{
+				"testdata/notice/NOTICE_LICENSE",
+			},
+		},
+		{
+			condition:   "restricted",
+			name:        "apex",
+			roots:       []string{"highest.apex.meta_lic"},
+			stripPrefix: "out/target/product/fictional/system/apex/",
+			expectedOut: []matcher{
+				spdxVersion{},
+				spdxDataLicense{},
+				spdxDocumentName{"Android"},
+				spdxID{"Android"},
+				spdxDocumentNameSpace{},
+				spdxCreatorOrganization{},
+				spdxCreatedTime{},
+				packageTag{"testdata/restricted/highest.apex.meta_lic"},
+				packageName{"testdata/restricted/highest.apex.meta_lic"},
+				spdxPkgID{"testdata/restricted/highest.apex.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"DOCUMENT-Android ", "testdata/restricted/highest.apex.meta_lic", "DESCRIBES"},
+				packageTag{"testdata/restricted/bin/bin1.meta_lic"},
+				packageName{"testdata/restricted/bin/bin1.meta_lic"},
+				spdxPkgID{"testdata/restricted/bin/bin1.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"Package-testdata/restricted/highest.apex.meta_lic ", "testdata/restricted/bin/bin1.meta_lic", "CONTAINS"},
+				packageTag{"testdata/restricted/bin/bin2.meta_lic"},
+				packageName{"testdata/restricted/bin/bin2.meta_lic"},
+				spdxPkgID{"testdata/restricted/bin/bin2.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"Package-testdata/restricted/highest.apex.meta_lic ", "testdata/restricted/bin/bin2.meta_lic", "CONTAINS"},
+				packageTag{"testdata/restricted/lib/liba.so.meta_lic"},
+				packageName{"testdata/restricted/lib/liba.so.meta_lic"},
+				spdxPkgID{"testdata/restricted/lib/liba.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-restricted-RESTRICTED_LICENSE"},
+				spdxRelationship{"Package-testdata/restricted/highest.apex.meta_lic ", "testdata/restricted/lib/liba.so.meta_lic", "CONTAINS"},
+				packageTag{"testdata/restricted/lib/libb.so.meta_lic"},
+				packageName{"testdata/restricted/lib/libb.so.meta_lic"},
+				spdxPkgID{"testdata/restricted/lib/libb.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-restricted-RESTRICTED_LICENSE"},
+				spdxRelationship{"Package-testdata/restricted/highest.apex.meta_lic ", "testdata/restricted/lib/libb.so.meta_lic", "CONTAINS"},
+				spdxRelationship{"Package-testdata/restricted/bin/bin1.meta_lic ", "testdata/restricted/lib/liba.so.meta_lic", "CONTAINS"},
+				packageTag{"testdata/restricted/lib/libc.a.meta_lic"},
+				packageName{"testdata/restricted/lib/libc.a.meta_lic"},
+				spdxPkgID{"testdata/restricted/lib/libc.a.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-reciprocal-RECIPROCAL_LICENSE"},
+				spdxRelationship{"Package-testdata/restricted/bin/bin1.meta_lic ", "testdata/restricted/lib/libc.a.meta_lic", "CONTAINS"},
+				spdxRelationship{"Package-testdata/restricted/lib/libb.so.meta_lic ", "testdata/restricted/bin/bin2.meta_lic", "RUNTIME_DEPENDENCY_OF"},
+				packageTag{"testdata/restricted/lib/libd.so.meta_lic"},
+				packageName{"testdata/restricted/lib/libd.so.meta_lic"},
+				spdxPkgID{"testdata/restricted/lib/libd.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-notice-NOTICE_LICENSE"},
+				spdxRelationship{"Package-testdata/restricted/lib/libd.so.meta_lic ", "testdata/restricted/bin/bin2.meta_lic", "RUNTIME_DEPENDENCY_OF"},
+				spdxLicense{},
+				spdxLicenseID{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxExtractedText{"&&&First Party License&&&"},
+				spdxExtractedClosingText{},
+				spdxLicenseID{"testdata-notice-NOTICE_LICENSE"},
+				spdxExtractedText{"%%%Notice License%%%"},
+				spdxExtractedClosingText{},
+				spdxLicenseID{"testdata-reciprocal-RECIPROCAL_LICENSE"},
+				spdxExtractedText{"$$$Reciprocal License$$$"},
+				spdxExtractedClosingText{},
+				spdxLicenseID{"testdata-restricted-RESTRICTED_LICENSE"},
+				spdxExtractedText{"###Restricted License###"},
+				spdxExtractedClosingText{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/notice/NOTICE_LICENSE",
+				"testdata/reciprocal/RECIPROCAL_LICENSE",
+				"testdata/restricted/RESTRICTED_LICENSE",
+			},
+		},
+		{
+			condition:   "restricted",
+			name:        "container",
+			roots:       []string{"container.zip.meta_lic"},
+			stripPrefix: "out/target/product/fictional/system/apex/",
+			expectedOut: []matcher{
+				spdxVersion{},
+				spdxDataLicense{},
+				spdxDocumentName{"Android"},
+				spdxID{"Android"},
+				spdxDocumentNameSpace{},
+				spdxCreatorOrganization{},
+				spdxCreatedTime{},
+				packageTag{"testdata/restricted/container.zip.meta_lic"},
+				packageName{"testdata/restricted/container.zip.meta_lic"},
+				spdxPkgID{"testdata/restricted/container.zip.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"DOCUMENT-Android ", "testdata/restricted/container.zip.meta_lic", "DESCRIBES"},
+				packageTag{"testdata/restricted/bin/bin1.meta_lic"},
+				packageName{"testdata/restricted/bin/bin1.meta_lic"},
+				spdxPkgID{"testdata/restricted/bin/bin1.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"Package-testdata/restricted/container.zip.meta_lic ", "testdata/restricted/bin/bin1.meta_lic", "CONTAINS"},
+				packageTag{"testdata/restricted/bin/bin2.meta_lic"},
+				packageName{"testdata/restricted/bin/bin2.meta_lic"},
+				spdxPkgID{"testdata/restricted/bin/bin2.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"Package-testdata/restricted/container.zip.meta_lic ", "testdata/restricted/bin/bin2.meta_lic", "CONTAINS"},
+				packageTag{"testdata/restricted/lib/liba.so.meta_lic"},
+				packageName{"testdata/restricted/lib/liba.so.meta_lic"},
+				spdxPkgID{"testdata/restricted/lib/liba.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-restricted-RESTRICTED_LICENSE"},
+				spdxRelationship{"Package-testdata/restricted/container.zip.meta_lic ", "testdata/restricted/lib/liba.so.meta_lic", "CONTAINS"},
+				packageTag{"testdata/restricted/lib/libb.so.meta_lic"},
+				packageName{"testdata/restricted/lib/libb.so.meta_lic"},
+				spdxPkgID{"testdata/restricted/lib/libb.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-restricted-RESTRICTED_LICENSE"},
+				spdxRelationship{"Package-testdata/restricted/container.zip.meta_lic ", "testdata/restricted/lib/libb.so.meta_lic", "CONTAINS"},
+				spdxRelationship{"Package-testdata/restricted/bin/bin1.meta_lic ", "testdata/restricted/lib/liba.so.meta_lic", "CONTAINS"},
+				packageTag{"testdata/restricted/lib/libc.a.meta_lic"},
+				packageName{"testdata/restricted/lib/libc.a.meta_lic"},
+				spdxPkgID{"testdata/restricted/lib/libc.a.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-reciprocal-RECIPROCAL_LICENSE"},
+				spdxRelationship{"Package-testdata/restricted/bin/bin1.meta_lic ", "testdata/restricted/lib/libc.a.meta_lic", "CONTAINS"},
+				spdxRelationship{"Package-testdata/restricted/lib/libb.so.meta_lic ", "testdata/restricted/bin/bin2.meta_lic", "RUNTIME_DEPENDENCY_OF"},
+				packageTag{"testdata/restricted/lib/libd.so.meta_lic"},
+				packageName{"testdata/restricted/lib/libd.so.meta_lic"},
+				spdxPkgID{"testdata/restricted/lib/libd.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-notice-NOTICE_LICENSE"},
+				spdxRelationship{"Package-testdata/restricted/lib/libd.so.meta_lic ", "testdata/restricted/bin/bin2.meta_lic", "RUNTIME_DEPENDENCY_OF"},
+				spdxLicense{},
+				spdxLicenseID{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxExtractedText{"&&&First Party License&&&"},
+				spdxExtractedClosingText{},
+				spdxLicenseID{"testdata-notice-NOTICE_LICENSE"},
+				spdxExtractedText{"%%%Notice License%%%"},
+				spdxExtractedClosingText{},
+				spdxLicenseID{"testdata-reciprocal-RECIPROCAL_LICENSE"},
+				spdxExtractedText{"$$$Reciprocal License$$$"},
+				spdxExtractedClosingText{},
+				spdxLicenseID{"testdata-restricted-RESTRICTED_LICENSE"},
+				spdxExtractedText{"###Restricted License###"},
+				spdxExtractedClosingText{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/notice/NOTICE_LICENSE",
+				"testdata/reciprocal/RECIPROCAL_LICENSE",
+				"testdata/restricted/RESTRICTED_LICENSE",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []matcher{
+				spdxVersion{},
+				spdxDataLicense{},
+				spdxDocumentName{"Android"},
+				spdxID{"Android"},
+				spdxDocumentNameSpace{},
+				spdxCreatorOrganization{},
+				spdxCreatedTime{},
+				packageTag{"testdata/restricted/bin/bin1.meta_lic"},
+				packageName{"testdata/restricted/bin/bin1.meta_lic"},
+				spdxPkgID{"testdata/restricted/bin/bin1.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"DOCUMENT-Android ", "testdata/restricted/bin/bin1.meta_lic", "DESCRIBES"},
+				packageTag{"testdata/restricted/lib/liba.so.meta_lic"},
+				packageName{"testdata/restricted/lib/liba.so.meta_lic"},
+				spdxPkgID{"testdata/restricted/lib/liba.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-restricted-RESTRICTED_LICENSE"},
+				spdxRelationship{"Package-testdata/restricted/bin/bin1.meta_lic ", "testdata/restricted/lib/liba.so.meta_lic", "CONTAINS"},
+				packageTag{"testdata/restricted/lib/libc.a.meta_lic"},
+				packageName{"testdata/restricted/lib/libc.a.meta_lic"},
+				spdxPkgID{"testdata/restricted/lib/libc.a.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-reciprocal-RECIPROCAL_LICENSE"},
+				spdxRelationship{"Package-testdata/restricted/bin/bin1.meta_lic ", "testdata/restricted/lib/libc.a.meta_lic", "CONTAINS"},
+				spdxLicense{},
+				spdxLicenseID{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxExtractedText{"&&&First Party License&&&"},
+				spdxExtractedClosingText{},
+				spdxLicenseID{"testdata-reciprocal-RECIPROCAL_LICENSE"},
+				spdxExtractedText{"$$$Reciprocal License$$$"},
+				spdxExtractedClosingText{},
+				spdxLicenseID{"testdata-restricted-RESTRICTED_LICENSE"},
+				spdxExtractedText{"###Restricted License###"},
+				spdxExtractedClosingText{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/reciprocal/RECIPROCAL_LICENSE",
+				"testdata/restricted/RESTRICTED_LICENSE",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []matcher{
+				spdxVersion{},
+				spdxDataLicense{},
+				spdxDocumentName{"Android"},
+				spdxID{"Android"},
+				spdxDocumentNameSpace{},
+				spdxCreatorOrganization{},
+				spdxCreatedTime{},
+				packageTag{"testdata/restricted/lib/libd.so.meta_lic"},
+				packageName{"testdata/restricted/lib/libd.so.meta_lic"},
+				spdxPkgID{"testdata/restricted/lib/libd.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-notice-NOTICE_LICENSE"},
+				spdxRelationship{"DOCUMENT-Android ", "testdata/restricted/lib/libd.so.meta_lic", "DESCRIBES"},
+				spdxLicense{},
+				spdxLicenseID{"testdata-notice-NOTICE_LICENSE"},
+				spdxExtractedText{"%%%Notice License%%%"},
+				spdxExtractedClosingText{},
+			},
+			expectedDeps: []string{"testdata/notice/NOTICE_LICENSE"},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []matcher{
+				spdxVersion{},
+				spdxDataLicense{},
+				spdxDocumentName{"Android"},
+				spdxID{"Android"},
+				spdxDocumentNameSpace{},
+				spdxCreatorOrganization{},
+				spdxCreatedTime{},
+				packageTag{"testdata/proprietary/highest.apex.meta_lic"},
+				packageName{"testdata/proprietary/highest.apex.meta_lic"},
+				spdxPkgID{"testdata/proprietary/highest.apex.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"DOCUMENT-Android ", "testdata/proprietary/highest.apex.meta_lic", "DESCRIBES"},
+				packageTag{"testdata/proprietary/bin/bin1.meta_lic"},
+				packageName{"testdata/proprietary/bin/bin1.meta_lic"},
+				spdxPkgID{"testdata/proprietary/bin/bin1.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"Package-testdata/proprietary/highest.apex.meta_lic ", "testdata/proprietary/bin/bin1.meta_lic", "CONTAINS"},
+				packageTag{"testdata/proprietary/bin/bin2.meta_lic"},
+				packageName{"testdata/proprietary/bin/bin2.meta_lic"},
+				spdxPkgID{"testdata/proprietary/bin/bin2.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-proprietary-PROPRIETARY_LICENSE"},
+				spdxRelationship{"Package-testdata/proprietary/highest.apex.meta_lic ", "testdata/proprietary/bin/bin2.meta_lic", "CONTAINS"},
+				packageTag{"testdata/proprietary/lib/liba.so.meta_lic"},
+				packageName{"testdata/proprietary/lib/liba.so.meta_lic"},
+				spdxPkgID{"testdata/proprietary/lib/liba.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-proprietary-PROPRIETARY_LICENSE"},
+				spdxRelationship{"Package-testdata/proprietary/highest.apex.meta_lic ", "testdata/proprietary/lib/liba.so.meta_lic", "CONTAINS"},
+				packageTag{"testdata/proprietary/lib/libb.so.meta_lic"},
+				packageName{"testdata/proprietary/lib/libb.so.meta_lic"},
+				spdxPkgID{"testdata/proprietary/lib/libb.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-restricted-RESTRICTED_LICENSE"},
+				spdxRelationship{"Package-testdata/proprietary/highest.apex.meta_lic ", "testdata/proprietary/lib/libb.so.meta_lic", "CONTAINS"},
+				spdxRelationship{"Package-testdata/proprietary/bin/bin1.meta_lic ", "testdata/proprietary/lib/liba.so.meta_lic", "CONTAINS"},
+				packageTag{"testdata/proprietary/lib/libc.a.meta_lic"},
+				packageName{"testdata/proprietary/lib/libc.a.meta_lic"},
+				spdxPkgID{"testdata/proprietary/lib/libc.a.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-proprietary-PROPRIETARY_LICENSE"},
+				spdxRelationship{"Package-testdata/proprietary/bin/bin1.meta_lic ", "testdata/proprietary/lib/libc.a.meta_lic", "CONTAINS"},
+				spdxRelationship{"Package-testdata-proprietary-lib-libb.so.meta_lic ", "testdata/proprietary/bin/bin2.meta_lic", "RUNTIME_DEPENDENCY_OF"},
+				packageTag{"testdata/proprietary/lib/libd.so.meta_lic"},
+				packageName{"testdata/proprietary/lib/libd.so.meta_lic"},
+				spdxPkgID{"testdata/proprietary/lib/libd.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-notice-NOTICE_LICENSE"},
+				spdxRelationship{"Package-testdata-proprietary-lib-libd.so.meta_lic ", "testdata/proprietary/bin/bin2.meta_lic", "RUNTIME_DEPENDENCY_OF"},
+				spdxLicense{},
+				spdxLicenseID{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxExtractedText{"&&&First Party License&&&"},
+				spdxExtractedClosingText{},
+				spdxLicenseID{"testdata-notice-NOTICE_LICENSE"},
+				spdxExtractedText{"%%%Notice License%%%"},
+				spdxExtractedClosingText{},
+				spdxLicenseID{"testdata-proprietary-PROPRIETARY_LICENSE"},
+				spdxExtractedText{"@@@Proprietary License@@@"},
+				spdxExtractedClosingText{},
+				spdxLicenseID{"testdata-restricted-RESTRICTED_LICENSE"},
+				spdxExtractedText{"###Restricted License###"},
+				spdxExtractedClosingText{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/notice/NOTICE_LICENSE",
+				"testdata/proprietary/PROPRIETARY_LICENSE",
+				"testdata/restricted/RESTRICTED_LICENSE",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []matcher{
+				spdxVersion{},
+				spdxDataLicense{},
+				spdxDocumentName{"Android"},
+				spdxID{"Android"},
+				spdxDocumentNameSpace{},
+				spdxCreatorOrganization{},
+				spdxCreatedTime{},
+				packageTag{"testdata/proprietary/container.zip.meta_lic"},
+				packageName{"testdata/proprietary/container.zip.meta_lic"},
+				spdxPkgID{"testdata/proprietary/container.zip.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"DOCUMENT-Android ", "testdata/proprietary/container.zip.meta_lic", "DESCRIBES"},
+				packageTag{"testdata/proprietary/bin/bin1.meta_lic"},
+				packageName{"testdata/proprietary/bin/bin1.meta_lic"},
+				spdxPkgID{"testdata/proprietary/bin/bin1.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"Package-testdata/proprietary/container.zip.meta_lic ", "testdata/proprietary/bin/bin1.meta_lic", "CONTAINS"},
+				packageTag{"testdata/proprietary/bin/bin2.meta_lic"},
+				packageName{"testdata/proprietary/bin/bin2.meta_lic"},
+				spdxPkgID{"testdata/proprietary/bin/bin2.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-proprietary-PROPRIETARY_LICENSE"},
+				spdxRelationship{"Package-testdata/proprietary/container.zip.meta_lic ", "testdata/proprietary/bin/bin2.meta_lic", "CONTAINS"},
+				packageTag{"testdata/proprietary/lib/liba.so.meta_lic"},
+				packageName{"testdata/proprietary/lib/liba.so.meta_lic"},
+				spdxPkgID{"testdata/proprietary/lib/liba.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-proprietary-PROPRIETARY_LICENSE"},
+				spdxRelationship{"Package-testdata/proprietary/container.zip.meta_lic ", "testdata/proprietary/lib/liba.so.meta_lic", "CONTAINS"},
+				packageTag{"testdata/proprietary/lib/libb.so.meta_lic"},
+				packageName{"testdata/proprietary/lib/libb.so.meta_lic"},
+				spdxPkgID{"testdata/proprietary/lib/libb.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-restricted-RESTRICTED_LICENSE"},
+				spdxRelationship{"Package-testdata/proprietary/container.zip.meta_lic ", "testdata/proprietary/lib/libb.so.meta_lic", "CONTAINS"},
+				spdxRelationship{"Package-testdata/proprietary/bin/bin1.meta_lic ", "testdata/proprietary/lib/liba.so.meta_lic", "CONTAINS"},
+				packageTag{"testdata/proprietary/lib/libc.a.meta_lic"},
+				packageName{"testdata/proprietary/lib/libc.a.meta_lic"},
+				spdxPkgID{"testdata/proprietary/lib/libc.a.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-proprietary-PROPRIETARY_LICENSE"},
+				spdxRelationship{"Package-testdata/proprietary/bin/bin1.meta_lic ", "testdata/proprietary/lib/libc.a.meta_lic", "CONTAINS"},
+				spdxRelationship{"Package-testdata-proprietary-lib-libb.so.meta_lic ", "testdata/proprietary/bin/bin2.meta_lic", "RUNTIME_DEPENDENCY_OF"},
+				packageTag{"testdata/proprietary/lib/libd.so.meta_lic"},
+				packageName{"testdata/proprietary/lib/libd.so.meta_lic"},
+				spdxPkgID{"testdata/proprietary/lib/libd.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-notice-NOTICE_LICENSE"},
+				spdxRelationship{"Package-testdata-proprietary-lib-libd.so.meta_lic ", "testdata/proprietary/bin/bin2.meta_lic", "RUNTIME_DEPENDENCY_OF"},
+				spdxLicense{},
+				spdxLicenseID{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxExtractedText{"&&&First Party License&&&"},
+				spdxExtractedClosingText{},
+				spdxLicenseID{"testdata-notice-NOTICE_LICENSE"},
+				spdxExtractedText{"%%%Notice License%%%"},
+				spdxExtractedClosingText{},
+				spdxLicenseID{"testdata-proprietary-PROPRIETARY_LICENSE"},
+				spdxExtractedText{"@@@Proprietary License@@@"},
+				spdxExtractedClosingText{},
+				spdxLicenseID{"testdata-restricted-RESTRICTED_LICENSE"},
+				spdxExtractedText{"###Restricted License###"},
+				spdxExtractedClosingText{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/notice/NOTICE_LICENSE",
+				"testdata/proprietary/PROPRIETARY_LICENSE",
+				"testdata/restricted/RESTRICTED_LICENSE",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []matcher{
+				spdxVersion{},
+				spdxDataLicense{},
+				spdxDocumentName{"Android"},
+				spdxID{"Android"},
+				spdxDocumentNameSpace{},
+				spdxCreatorOrganization{},
+				spdxCreatedTime{},
+				packageTag{"testdata/proprietary/application.meta_lic"},
+				packageName{"testdata/proprietary/application.meta_lic"},
+				spdxPkgID{"testdata/proprietary/application.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"DOCUMENT-Android ", "testdata/proprietary/application.meta_lic", "DESCRIBES"},
+				packageTag{"testdata/proprietary/bin/bin3.meta_lic"},
+				packageName{"testdata/proprietary/bin/bin3.meta_lic"},
+				spdxPkgID{"testdata/proprietary/bin/bin3.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-restricted-RESTRICTED_LICENSE"},
+				spdxRelationship{"Package-testdata/proprietary/bin/bin3.meta_lic ", "testdata/proprietary/application.meta_lic", "BUILD_TOOL_OF"},
+				packageTag{"testdata/proprietary/lib/liba.so.meta_lic"},
+				packageName{"testdata/proprietary/lib/liba.so.meta_lic"},
+				spdxPkgID{"testdata/proprietary/lib/liba.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-proprietary-PROPRIETARY_LICENSE"},
+				spdxRelationship{"Package-testdata/proprietary/application.meta_lic ", "testdata/proprietary/lib/liba.so.meta_lic", "CONTAINS"},
+				packageTag{"testdata/proprietary/lib/libb.so.meta_lic"},
+				packageName{"testdata/proprietary/lib/libb.so.meta_lic"},
+				spdxPkgID{"testdata/proprietary/lib/libb.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-restricted-RESTRICTED_LICENSE"},
+				spdxRelationship{"Package-testdata/proprietary/lib/libb.so.meta_lic ", "testdata/proprietary/application.meta_lic", "RUNTIME_DEPENDENCY_OF"},
+				spdxLicense{},
+				spdxLicenseID{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxExtractedText{"&&&First Party License&&&"},
+				spdxExtractedClosingText{},
+				spdxLicenseID{"testdata-proprietary-PROPRIETARY_LICENSE"},
+				spdxExtractedText{"@@@Proprietary License@@@"},
+				spdxExtractedClosingText{},
+				spdxLicenseID{"testdata-restricted-RESTRICTED_LICENSE"},
+				spdxExtractedText{"###Restricted License###"},
+				spdxExtractedClosingText{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/proprietary/PROPRIETARY_LICENSE",
+				"testdata/restricted/RESTRICTED_LICENSE",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []matcher{
+				spdxVersion{},
+				spdxDataLicense{},
+				spdxDocumentName{"Android"},
+				spdxID{"Android"},
+				spdxDocumentNameSpace{},
+				spdxCreatorOrganization{},
+				spdxCreatedTime{},
+				packageTag{"testdata/proprietary/bin/bin1.meta_lic"},
+				packageName{"testdata/proprietary/bin/bin1.meta_lic"},
+				spdxPkgID{"testdata/proprietary/bin/bin1.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxRelationship{"DOCUMENT-Android ", "testdata/proprietary/bin/bin1.meta_lic", "DESCRIBES"},
+				packageTag{"testdata/proprietary/lib/liba.so.meta_lic"},
+				packageName{"testdata/proprietary/lib/liba.so.meta_lic"},
+				spdxPkgID{"testdata/proprietary/lib/liba.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-proprietary-PROPRIETARY_LICENSE"},
+				spdxRelationship{"Package-testdata/proprietary/bin/bin1.meta_lic ", "testdata/proprietary/lib/liba.so.meta_lic", "CONTAINS"},
+				packageTag{"testdata/proprietary/lib/libc.a.meta_lic"},
+				packageName{"testdata/proprietary/lib/libc.a.meta_lic"},
+				spdxPkgID{"testdata/proprietary/lib/libc.a.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-proprietary-PROPRIETARY_LICENSE"},
+				spdxRelationship{"Package-testdata/proprietary/bin/bin1.meta_lic ", "testdata/proprietary/lib/libc.a.meta_lic", "CONTAINS"},
+				spdxLicense{},
+				spdxLicenseID{"testdata-firstparty-FIRST_PARTY_LICENSE"},
+				spdxExtractedText{"&&&First Party License&&&"},
+				spdxExtractedClosingText{},
+				spdxLicenseID{"testdata-proprietary-PROPRIETARY_LICENSE"},
+				spdxExtractedText{"@@@Proprietary License@@@"},
+				spdxExtractedClosingText{},
+			},
+			expectedDeps: []string{
+				"testdata/firstparty/FIRST_PARTY_LICENSE",
+				"testdata/proprietary/PROPRIETARY_LICENSE",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []matcher{
+				spdxVersion{},
+				spdxDataLicense{},
+				spdxDocumentName{"Android"},
+				spdxID{"Android"},
+				spdxDocumentNameSpace{},
+				spdxCreatorOrganization{},
+				spdxCreatedTime{},
+				packageTag{"testdata/proprietary/lib/libd.so.meta_lic"},
+				packageName{"testdata/proprietary/lib/libd.so.meta_lic"},
+				spdxPkgID{"testdata/proprietary/lib/libd.so.meta_lic"},
+				spdxPkgDownloadLocation{"NOASSERTION"},
+				spdxPkgLicenseDeclared{"testdata-notice-NOTICE_LICENSE"},
+				spdxRelationship{"DOCUMENT-Android ", "testdata/proprietary/lib/libd.so.meta_lic", "DESCRIBES"},
+				spdxLicense{},
+				spdxLicenseID{"testdata-notice-NOTICE_LICENSE"},
+				spdxExtractedText{"%%%Notice License%%%"},
+				spdxExtractedClosingText{},
+			},
+			expectedDeps: []string{"testdata/notice/NOTICE_LICENSE"},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.condition+" "+tt.name, func(t *testing.T) {
+			stdout := &bytes.Buffer{}
+			stderr := &bytes.Buffer{}
+
+			rootFiles := make([]string, 0, len(tt.roots))
+			for _, r := range tt.roots {
+				rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
+			}
+
+			ctx := context{stdout, stderr, compliance.GetFS(tt.outDir), "Android", []string{tt.stripPrefix}, fakeTime}
+
+			deps, err := sbomGenerator(&ctx, rootFiles...)
+			if err != nil {
+				t.Fatalf("sbom: error = %v, stderr = %v", err, stderr)
+				return
+			}
+			if stderr.Len() > 0 {
+				t.Errorf("sbom: gotStderr = %v, want none", stderr)
+			}
+
+			t.Logf("got stdout: %s", stdout.String())
+
+			t.Logf("want stdout: %s", matcherList(tt.expectedOut).String())
+
+			out := bufio.NewScanner(stdout)
+			lineno := 0
+			for out.Scan() {
+				line := out.Text()
+				if strings.TrimLeft(line, " ") == "" {
+					continue
+				}
+				if len(tt.expectedOut) <= lineno {
+					t.Errorf("sbom: unexpected output at line %d: got %q, want nothing (wanted %d lines)", lineno+1, line, len(tt.expectedOut))
+				} else if !tt.expectedOut[lineno].isMatch(line) {
+					t.Errorf("sbom: unexpected output at line %d: got %q, want %q", lineno+1, line, tt.expectedOut[lineno])
+				}
+				lineno++
+			}
+			for ; lineno < len(tt.expectedOut); lineno++ {
+				t.Errorf("bom: missing output line %d: ended early, want %q", lineno+1, tt.expectedOut[lineno])
+			}
+
+			t.Logf("got deps: %q", deps)
+
+			t.Logf("want deps: %q", tt.expectedDeps)
+
+			if g, w := deps, tt.expectedDeps; !reflect.DeepEqual(g, w) {
+				t.Errorf("unexpected deps, wanted:\n%s\ngot:\n%s\n",
+					strings.Join(w, "\n"), strings.Join(g, "\n"))
+			}
+		})
+	}
+}
+
+type matcher interface {
+	isMatch(line string) bool
+	String() string
+}
+
+type packageTag struct {
+	name string
+}
+
+func (m packageTag) isMatch(line string) bool {
+	groups := spdxPackageTag.FindStringSubmatch(line)
+	if len(groups) != 2 {
+		return false
+	}
+	return groups[1] == m.name
+}
+
+func (m packageTag) String() string {
+	return "##### Package: " + m.name
+}
+
+type packageName struct {
+	name string
+}
+
+func (m packageName) isMatch(line string) bool {
+	groups := spdxPackageNameTag.FindStringSubmatch(line)
+	if len(groups) != 2 {
+		return false
+	}
+	return groups[1] == replaceSlashes(m.name)
+}
+
+func (m packageName) String() string {
+	return "PackageName: " + replaceSlashes(m.name)
+}
+
+type spdxID struct {
+	name string
+}
+
+func (m spdxID) isMatch(line string) bool {
+	groups := spdxIDTag.FindStringSubmatch(line)
+	if len(groups) != 2 {
+		return false
+	}
+	return groups[1] == replaceSlashes(m.name)
+}
+
+func (m spdxID) String() string {
+	return "SPDXID: SPDXRef-DOCUMENT-" + replaceSlashes(m.name)
+}
+
+type spdxPkgID struct {
+	name string
+}
+
+func (m spdxPkgID) isMatch(line string) bool {
+	groups := spdxPkgIDTag.FindStringSubmatch(line)
+	if len(groups) != 2 {
+		return false
+	}
+	return groups[1] == replaceSlashes(m.name)
+}
+
+func (m spdxPkgID) String() string {
+	return "SPDXID: SPDXRef-Package-" + replaceSlashes(m.name)
+}
+
+type spdxVersion struct{}
+
+func (m spdxVersion) isMatch(line string) bool {
+	return spdxVersionTag.MatchString(line)
+}
+
+func (m spdxVersion) String() string {
+	return "SPDXVersion: SPDX-2.2"
+}
+
+type spdxDataLicense struct{}
+
+func (m spdxDataLicense) isMatch(line string) bool {
+	return spdxDataLicenseTag.MatchString(line)
+}
+
+func (m spdxDataLicense) String() string {
+	return "DataLicense: CC-1.0"
+}
+
+type spdxDocumentName struct {
+	name string
+}
+
+func (m spdxDocumentName) isMatch(line string) bool {
+	return spdxDocumentNameTag.MatchString(line)
+}
+
+func (m spdxDocumentName) String() string {
+	return "DocumentName: " + m.name
+}
+
+type spdxDocumentNameSpace struct {
+	name string
+}
+
+func (m spdxDocumentNameSpace) isMatch(line string) bool {
+	return spdxDocumentNameSpaceTag.MatchString(line)
+}
+
+func (m spdxDocumentNameSpace) String() string {
+	return "DocumentNameSpace: Android"
+}
+
+type spdxCreatorOrganization struct{}
+
+func (m spdxCreatorOrganization) isMatch(line string) bool {
+	return spdxCreatorOrganizationTag.MatchString(line)
+}
+
+func (m spdxCreatorOrganization) String() string {
+	return "Creator: Organization: Google LLC"
+}
+
+func fakeTime() time.Time {
+	return time.UnixMicro(0)
+}
+
+type spdxCreatedTime struct{}
+
+func (m spdxCreatedTime) isMatch(line string) bool {
+	return spdxCreatedTimeTag.MatchString(line)
+}
+
+func (m spdxCreatedTime) String() string {
+	return "Created: 1970-01-01T00:00:00Z"
+}
+
+type spdxPkgDownloadLocation struct {
+	name string
+}
+
+func (m spdxPkgDownloadLocation) isMatch(line string) bool {
+	return spdxPkgDownloadLocationTag.MatchString(line)
+}
+
+func (m spdxPkgDownloadLocation) String() string {
+	return "PackageDownloadLocation: " + m.name
+}
+
+type spdxPkgLicenseDeclared struct {
+	name string
+}
+
+func (m spdxPkgLicenseDeclared) isMatch(line string) bool {
+	groups := spdxPkgLicenseDeclaredTag.FindStringSubmatch(line)
+	if len(groups) != 2 {
+		return false
+	}
+	return groups[1] == replaceSlashes(m.name)
+}
+
+func (m spdxPkgLicenseDeclared) String() string {
+	return "PackageLicenseConcluded: LicenseRef-" + m.name
+}
+
+type spdxRelationship struct {
+	pkg1     string
+	pkg2     string
+	relation string
+}
+
+func (m spdxRelationship) isMatch(line string) bool {
+	groups := spdxRelationshipTag.FindStringSubmatch(line)
+	if len(groups) != 4 {
+		return false
+	}
+	return groups[1] == replaceSlashes(m.pkg1) && groups[2] == m.relation && groups[3] == replaceSlashes(m.pkg2)
+}
+
+func (m spdxRelationship) String() string {
+	return "Relationship: SPDXRef-" + replaceSlashes(m.pkg1) + " " + m.relation + " SPDXRef-Package-" + replaceSlashes(m.pkg2)
+}
+
+type spdxLicense struct{}
+
+func (m spdxLicense) isMatch(line string) bool {
+	return spdxLicenseTag.MatchString(line)
+}
+
+func (m spdxLicense) String() string {
+	return "##### Non-standard license:"
+}
+
+type spdxLicenseID struct {
+	name string
+}
+
+func (m spdxLicenseID) isMatch(line string) bool {
+	groups := spdxLicenseIDTag.FindStringSubmatch(line)
+	if len(groups) != 2 {
+		return false
+	}
+	return groups[1] == replaceSlashes(m.name)
+}
+
+func (m spdxLicenseID) String() string {
+	return "LicenseID: LicenseRef-" + m.name
+}
+
+type spdxExtractedText struct {
+	name string
+}
+
+func (m spdxExtractedText) isMatch(line string) bool {
+	groups := spdxExtractedTextTag.FindStringSubmatch(line)
+	if len(groups) != 2 {
+		return false
+	}
+	return groups[1] == replaceSlashes(m.name)
+}
+
+func (m spdxExtractedText) String() string {
+	return "ExtractedText: <text>" + m.name
+}
+
+type spdxExtractedClosingText struct{}
+
+func (m spdxExtractedClosingText) isMatch(line string) bool {
+	return spdxExtractedClosingTextTag.MatchString(line)
+}
+
+func (m spdxExtractedClosingText) String() string {
+	return "</text>"
+}
+
+type matcherList []matcher
+
+func (l matcherList) String() string {
+	var sb strings.Builder
+	for _, m := range l {
+		s := m.String()
+		fmt.Fprintf(&sb, "%s\n", s)
+	}
+	return sb.String()
+}
diff --git a/tools/compliance/graph.go b/tools/compliance/graph.go
index e69f3e2..80a2f47 100644
--- a/tools/compliance/graph.go
+++ b/tools/compliance/graph.go
@@ -58,13 +58,11 @@
 	/// (guarded by mu)
 	targets map[string]*TargetNode
 
-	// wgBU becomes non-nil when the bottom-up resolve begins and reaches 0
-	// (i.e. Wait() proceeds) when the bottom-up resolve completes. (guarded by mu)
-	wgBU *sync.WaitGroup
+	// onceBottomUp makes sure the bottom-up resolve walk only happens one time.
+	onceBottomUp sync.Once
 
-	// wgTD becomes non-nil when the top-down resolve begins and reaches 0 (i.e. Wait()
-	// proceeds) when the top-down resolve completes. (guarded by mu)
-	wgTD *sync.WaitGroup
+	// onceTopDown makes sure the top-down resolve walk only happens one time.
+	onceTopDown sync.Once
 
 	// shippedNodes caches the results of a full walk of nodes identifying targets
 	// distributed either directly or as derivative works. (creation guarded by mu)
@@ -139,6 +137,24 @@
 	return e.annotations
 }
 
+// IsRuntimeDependency returns true for edges representing shared libraries
+// linked dynamically at runtime.
+func (e *TargetEdge) IsRuntimeDependency() bool {
+	return edgeIsDynamicLink(e)
+}
+
+// IsDerivation returns true for edges where the target is a derivative
+// work of dependency.
+func (e *TargetEdge) IsDerivation() bool {
+	return edgeIsDerivation(e)
+}
+
+// IsBuildTool returns true for edges where the target is built
+// by dependency.
+func (e *TargetEdge) IsBuildTool() bool {
+	return !edgeIsDerivation(e) && !edgeIsDynamicLink(e)
+}
+
 // String returns a human-readable string representation of the edge.
 func (e *TargetEdge) String() string {
 	return fmt.Sprintf("%s -[%s]> %s", e.target.name, strings.Join(e.annotations.AsList(), ", "), e.dependency.name)
@@ -188,6 +204,11 @@
 	return s.edge.dependency
 }
 
+// Edge describes the target edge.
+func (s TargetEdgePathSegment) Edge() *TargetEdge {
+	return s.edge
+}
+
 // Annotations describes the type of edge by the set of annotations attached to
 // it.
 //
@@ -300,6 +321,11 @@
 	return tn.proto.GetPackageName()
 }
 
+// ModuleName returns the module name of the target.
+func (tn *TargetNode) ModuleName() string {
+	return tn.proto.GetModuleName()
+}
+
 // Projects returns the projects defining the target node. (unordered)
 //
 // In an ideal world, only 1 project defines a target, but the interaction
diff --git a/tools/compliance/policy_resolve.go b/tools/compliance/policy_resolve.go
index 93335a9..fc8ed4c 100644
--- a/tools/compliance/policy_resolve.go
+++ b/tools/compliance/policy_resolve.go
@@ -49,84 +49,71 @@
 func TraceBottomUpConditions(lg *LicenseGraph, conditionsFn TraceConditions) {
 
 	// short-cut if already walked and cached
-	lg.mu.Lock()
-	wg := lg.wgBU
+	lg.onceBottomUp.Do(func() {
+		// amap identifes targets previously walked. (guarded by mu)
+		amap := make(map[*TargetNode]struct{})
 
-	if wg != nil {
-		lg.mu.Unlock()
-		wg.Wait()
-		return
-	}
-	wg = &sync.WaitGroup{}
-	wg.Add(1)
-	lg.wgBU = wg
-	lg.mu.Unlock()
+		// mu guards concurrent access to amap
+		var mu sync.Mutex
 
-	// amap identifes targets previously walked. (guarded by mu)
-	amap := make(map[*TargetNode]struct{})
+		var walk func(target *TargetNode, treatAsAggregate bool) LicenseConditionSet
 
-	// mu guards concurrent access to amap
-	var mu sync.Mutex
+		walk = func(target *TargetNode, treatAsAggregate bool) LicenseConditionSet {
+			priorWalkResults := func() (LicenseConditionSet, bool) {
+				mu.Lock()
+				defer mu.Unlock()
 
-	var walk func(target *TargetNode, treatAsAggregate bool) LicenseConditionSet
-
-	walk = func(target *TargetNode, treatAsAggregate bool) LicenseConditionSet {
-		priorWalkResults := func() (LicenseConditionSet, bool) {
-			mu.Lock()
-			defer mu.Unlock()
-
-			if _, alreadyWalked := amap[target]; alreadyWalked {
-				if treatAsAggregate {
-					return target.resolution, true
+				if _, alreadyWalked := amap[target]; alreadyWalked {
+					if treatAsAggregate {
+						return target.resolution, true
+					}
+					if !target.pure {
+						return target.resolution, true
+					}
+					// previously walked in a pure aggregate context,
+					// needs to walk again in non-aggregate context
+				} else {
+					target.resolution |= conditionsFn(target)
+					amap[target] = struct{}{}
 				}
-				if !target.pure {
-					return target.resolution, true
-				}
-				// previously walked in a pure aggregate context,
-				// needs to walk again in non-aggregate context
-			} else {
-				target.resolution |= conditionsFn(target)
-				amap[target] = struct{}{}
+				target.pure = treatAsAggregate
+				return target.resolution, false
 			}
-			target.pure = treatAsAggregate
-			return target.resolution, false
-		}
-		cs, alreadyWalked := priorWalkResults()
-		if alreadyWalked {
+			cs, alreadyWalked := priorWalkResults()
+			if alreadyWalked {
+				return cs
+			}
+
+			c := make(chan LicenseConditionSet, len(target.edges))
+			// add all the conditions from all the dependencies
+			for _, edge := range target.edges {
+				go func(edge *TargetEdge) {
+					// walk dependency to get its conditions
+					cs := walk(edge.dependency, treatAsAggregate && edge.dependency.IsContainer())
+
+					// turn those into the conditions that apply to the target
+					cs = depConditionsPropagatingToTarget(lg, edge, cs, treatAsAggregate)
+
+					c <- cs
+				}(edge)
+			}
+			for i := 0; i < len(target.edges); i++ {
+				cs |= <-c
+			}
+			mu.Lock()
+			target.resolution |= cs
+			mu.Unlock()
+
+			// return conditions up the tree
 			return cs
 		}
 
-		c := make(chan LicenseConditionSet, len(target.edges))
-		// add all the conditions from all the dependencies
-		for _, edge := range target.edges {
-			go func(edge *TargetEdge) {
-				// walk dependency to get its conditions
-				cs := walk(edge.dependency, treatAsAggregate && edge.dependency.IsContainer())
-
-				// turn those into the conditions that apply to the target
-				cs = depConditionsPropagatingToTarget(lg, edge, cs, treatAsAggregate)
-
-				c <- cs
-			}(edge)
+		// walk each of the roots
+		for _, rname := range lg.rootFiles {
+			rnode := lg.targets[rname]
+			_ = walk(rnode, rnode.IsContainer())
 		}
-		for i := 0; i < len(target.edges); i++ {
-			cs |= <-c
-		}
-		mu.Lock()
-		target.resolution |= cs
-		mu.Unlock()
-
-		// return conditions up the tree
-		return cs
-	}
-
-	// walk each of the roots
-	for _, rname := range lg.rootFiles {
-		rnode := lg.targets[rname]
-		_ = walk(rnode, rnode.IsContainer())
-	}
-
-	wg.Done()
+	})
 }
 
 // ResolveTopDownCondtions performs a top-down walk of the LicenseGraph
@@ -145,78 +132,76 @@
 func TraceTopDownConditions(lg *LicenseGraph, conditionsFn TraceConditions) {
 
 	// short-cut if already walked and cached
-	lg.mu.Lock()
-	wg := lg.wgTD
+	lg.onceTopDown.Do(func() {
+		wg := &sync.WaitGroup{}
+		wg.Add(1)
 
-	if wg != nil {
-		lg.mu.Unlock()
-		wg.Wait()
-		return
-	}
-	wg = &sync.WaitGroup{}
-	wg.Add(1)
-	lg.wgTD = wg
-	lg.mu.Unlock()
+		// start with the conditions propagated up the graph
+		TraceBottomUpConditions(lg, conditionsFn)
 
-	// start with the conditions propagated up the graph
-	TraceBottomUpConditions(lg, conditionsFn)
+		// amap contains the set of targets already walked. (guarded by mu)
+		amap := make(map[*TargetNode]struct{})
 
-	// amap contains the set of targets already walked. (guarded by mu)
-	amap := make(map[*TargetNode]struct{})
+		// mu guards concurrent access to amap
+		var mu sync.Mutex
 
-	// mu guards concurrent access to amap
-	var mu sync.Mutex
+		var walk func(fnode *TargetNode, cs LicenseConditionSet, treatAsAggregate bool)
 
-	var walk func(fnode *TargetNode, cs LicenseConditionSet, treatAsAggregate bool)
-
-	walk = func(fnode *TargetNode, cs LicenseConditionSet, treatAsAggregate bool) {
-		defer wg.Done()
-		mu.Lock()
-		fnode.resolution |= conditionsFn(fnode)
-		fnode.resolution |= cs
-		fnode.pure = treatAsAggregate
-		amap[fnode] = struct{}{}
-		cs = fnode.resolution
-		mu.Unlock()
-		// for each dependency
-		for _, edge := range fnode.edges {
-			func(edge *TargetEdge) {
-				// dcs holds the dpendency conditions inherited from the target
-				dcs := targetConditionsPropagatingToDep(lg, edge, cs, treatAsAggregate, conditionsFn)
-				dnode := edge.dependency
+		walk = func(fnode *TargetNode, cs LicenseConditionSet, treatAsAggregate bool) {
+			defer wg.Done()
+			continueWalk := func() bool {
 				mu.Lock()
 				defer mu.Unlock()
-				depcs := dnode.resolution
-				_, alreadyWalked := amap[dnode]
-				if !dcs.IsEmpty() && alreadyWalked {
-					if dcs.Difference(depcs).IsEmpty() {
+
+				depcs := fnode.resolution
+				_, alreadyWalked := amap[fnode]
+				if alreadyWalked {
+					if cs.IsEmpty() {
+						return false
+					}
+					if cs.Difference(depcs).IsEmpty() {
 						// no new conditions
 
 						// pure aggregates never need walking a 2nd time with same conditions
 						if treatAsAggregate {
-							return
+							return false
 						}
 						// non-aggregates don't need walking as non-aggregate a 2nd time
-						if !dnode.pure {
-							return
+						if !fnode.pure {
+							return false
 						}
 						// previously walked as pure aggregate; need to re-walk as non-aggregate
 					}
 				}
+				fnode.resolution |= conditionsFn(fnode)
+				fnode.resolution |= cs
+				fnode.pure = treatAsAggregate
+				amap[fnode] = struct{}{}
+				cs = fnode.resolution
+				return true
+			}()
+			if !continueWalk {
+				return
+			}
+			// for each dependency
+			for _, edge := range fnode.edges {
+				// dcs holds the dpendency conditions inherited from the target
+				dcs := targetConditionsPropagatingToDep(lg, edge, cs, treatAsAggregate, conditionsFn)
+				dnode := edge.dependency
 				// add the conditions to the dependency
 				wg.Add(1)
 				go walk(dnode, dcs, treatAsAggregate && dnode.IsContainer())
-			}(edge)
+			}
 		}
-	}
 
-	// walk each of the roots
-	for _, rname := range lg.rootFiles {
-		rnode := lg.targets[rname]
-		wg.Add(1)
-		// add the conditions to the root and its transitive closure
-		go walk(rnode, NewLicenseConditionSet(), rnode.IsContainer())
-	}
-	wg.Done()
-	wg.Wait()
+		// walk each of the roots
+		for _, rname := range lg.rootFiles {
+			rnode := lg.targets[rname]
+			wg.Add(1)
+			// add the conditions to the root and its transitive closure
+			go walk(rnode, NewLicenseConditionSet(), rnode.IsContainer())
+		}
+		wg.Done()
+		wg.Wait()
+	})
 }
diff --git a/tools/event_log_tags.bzl b/tools/event_log_tags.bzl
deleted file mode 100644
index 35305ae..0000000
--- a/tools/event_log_tags.bzl
+++ /dev/null
@@ -1,35 +0,0 @@
-"""Event log tags generation rule"""
-
-load("@bazel_skylib//lib:paths.bzl", "paths")
-
-def _event_log_tags_impl(ctx):
-    out_files = []
-    for logtag_file in ctx.files.srcs:
-        out_filename = paths.replace_extension(logtag_file.basename, ".java")
-        out_file = ctx.actions.declare_file(out_filename)
-        out_files.append(out_file)
-        ctx.actions.run(
-            inputs = [logtag_file],
-            outputs = [out_file],
-            arguments = [
-                "-o",
-                out_file.path,
-                logtag_file.path,
-            ],
-            progress_message = "Generating Java logtag file from %s" % logtag_file.short_path,
-            executable = ctx.executable._logtag_to_java_tool,
-        )
-    return [DefaultInfo(files = depset(out_files))]
-
-event_log_tags = rule(
-    implementation = _event_log_tags_impl,
-    attrs = {
-        "srcs": attr.label_list(allow_files = [".logtags"], mandatory = True),
-        "_logtag_to_java_tool": attr.label(
-            executable = True,
-            cfg = "exec",
-            allow_files = True,
-            default = Label("//build/make/tools:java-event-log-tags"),
-        ),
-    },
-)
diff --git a/tools/releasetools/build_image.py b/tools/releasetools/build_image.py
index 7639ffd..252b1d5 100755
--- a/tools/releasetools/build_image.py
+++ b/tools/releasetools/build_image.py
@@ -410,7 +410,7 @@
       build_command.append("--casefold")
     if (needs_compress or prop_dict.get("f2fs_compress") == "true"):
       build_command.append("--compression")
-    if (prop_dict.get("mount_point") != "data"):
+    if "ro_mount_point" in prop_dict:
       build_command.append("--readonly")
     if (prop_dict.get("f2fs_compress") == "true"):
       build_command.append("--sldc")
@@ -757,6 +757,8 @@
     if not copy_prop(prop, "extfs_rsv_pct"):
       d["extfs_rsv_pct"] = "0"
 
+    d["ro_mount_point"] = "1"
+
   # Copy partition-specific properties.
   d["mount_point"] = mount_point
   if mount_point == "system":
diff --git a/tools/stub_diff_analyzer.py b/tools/stub_diff_analyzer.py
new file mode 100644
index 0000000..e49d092
--- /dev/null
+++ b/tools/stub_diff_analyzer.py
@@ -0,0 +1,328 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2022 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.
+
+from sys import exit
+from typing import List
+from glob import glob
+from pathlib import Path
+from collections import defaultdict
+from difflib import Differ
+from re import split
+from tqdm import tqdm
+import argparse
+
+
+DIFFER_CODE_LEN = 2
+
+class DifferCodes:
+    COMMON = '  '
+    UNIQUE_FIRST = '- '
+    UNIQUE_SECOND = '+ '
+    DIFF_IDENT = '? '
+
+class FilesDiffAnalyzer:
+    def __init__(self, args) -> None:
+        self.out_dir = args.out_dir
+        self.show_diff = args.show_diff
+        self.skip_words = args.skip_words
+        self.first_dir = args.first_dir
+        self.second_dir = args.second_dir
+        self.include_common = args.include_common
+
+        self.first_dir_files = self.get_files(self.first_dir)
+        self.second_dir_files = self.get_files(self.second_dir)
+        self.common_file_map = defaultdict(set)
+
+        self.map_common_files(self.first_dir_files, self.first_dir)
+        self.map_common_files(self.second_dir_files, self.second_dir)
+
+    def get_files(self, dir: str) -> List[str]:
+        """Get all files directory in the input directory including the files in the subdirectories
+
+        Recursively finds all files in the input directory.
+        Returns a list of file directory strings, which do not include directories but only files.
+        List is sorted in alphabetical order of the file directories.
+
+        Args:
+            dir: Directory to get the files. String.
+
+        Returns:
+            A list of file directory strings within the input directory.
+            Sorted in Alphabetical order.
+
+        Raises:
+            FileNotFoundError: An error occurred accessing the non-existing directory
+        """
+
+        if not dir_exists(dir):
+            raise FileNotFoundError("Directory does not exist")
+
+        if dir[:-2] != "**":
+            if dir[:-1] != "/":
+                dir += "/"
+            dir += "**"
+
+        return [file for file in sorted(glob(dir, recursive=True)) if Path(file).is_file()]
+
+    def map_common_files(self, files: List[str], dir: str) -> None:
+        for file in files:
+            file_name = file.split(dir, 1)[-1]
+            self.common_file_map[file_name].add(dir)
+        return
+
+    def compare_file_contents(self, first_file: str, second_file: str) -> List[str]:
+        """Compare the contents of the files and return different lines
+
+        Given two file directory strings, compare the contents of the two files
+        and return the list of file contents string prepended with unique identifier codes.
+        The identifier codes include:
+        - '  '(two empty space characters): Line common to two files
+        - '- '(minus followed by a space) : Line unique to first file
+        - '+ '(plus followed by a space)  : Line unique to second file
+
+        Args:
+            first_file: First file directory string to compare the content
+            second_file: Second file directory string to compare the content
+
+        Returns:
+            A list of the file content strings. For example:
+
+            [
+                "  Foo",
+                "- Bar",
+                "+ Baz"
+            ]
+        """
+
+        d = Differ()
+        first_file_contents = sort_methods(get_file_contents(first_file))
+        second_file_contents = sort_methods(get_file_contents(second_file))
+        diff = list(d.compare(first_file_contents, second_file_contents))
+        ret = [f"diff {first_file} {second_file}"]
+
+        idx = 0
+        while idx < len(diff):
+            line = diff[idx]
+            line_code = line[:DIFFER_CODE_LEN]
+
+            match line_code:
+                case DifferCodes.COMMON:
+                    if self.include_common:
+                        ret.append(line)
+
+                case DifferCodes.UNIQUE_FIRST:
+                    # Should compare line
+                    if (idx < len(diff) - 1 and
+                        (next_line_code := diff[idx + 1][:DIFFER_CODE_LEN])
+                        not in (DifferCodes.UNIQUE_FIRST, DifferCodes.COMMON)):
+                        delta = 1 if next_line_code == DifferCodes.UNIQUE_SECOND else 2
+                        line_to_compare = diff[idx + delta]
+                        if self.lines_differ(line, line_to_compare):
+                            ret.extend([line, line_to_compare])
+                        else:
+                            if self.include_common:
+                                ret.append(DifferCodes.COMMON +
+                                           line[DIFFER_CODE_LEN:])
+                        idx += delta
+                    else:
+                        ret.append(line)
+
+                case DifferCodes.UNIQUE_SECOND:
+                    ret.append(line)
+
+                case DifferCodes.DIFF_IDENT:
+                    pass
+            idx += 1
+        return ret
+
+    def lines_differ(self, line1: str, line2: str) -> bool:
+        """Check if the input lines are different or not
+
+        Compare the two lines word by word and check if the two lines are different or not.
+        If the different words in the comparing lines are included in skip_words,
+        the lines are not considered different.
+
+        Args:
+            line1:      first line to compare
+            line2:      second line to compare
+
+        Returns:
+            Boolean value indicating if the two lines are different or not
+
+        """
+        # Split by '.' or ' '(whitespace)
+        def split_words(line: str) -> List[str]:
+            return split('\\s|\\.', line[DIFFER_CODE_LEN:])
+
+        line1_words, line2_words = split_words(line1), split_words(line2)
+        if len(line1_words) != len(line2_words):
+            return True
+
+        for word1, word2 in zip(line1_words, line2_words):
+            if word1 != word2:
+                # not check if words are equal to skip word, but
+                # check if words contain skip word as substring
+                if all(sw not in word1 and sw not in word2 for sw in self.skip_words):
+                    return True
+
+        return False
+
+    def analyze(self) -> None:
+        """Analyze file contents in both directories and write to output or console.
+        """
+        for file in tqdm(sorted(self.common_file_map.keys())):
+            val = self.common_file_map[file]
+
+            # When file exists in both directories
+            lines = list()
+            if val == set([self.first_dir, self.second_dir]):
+                lines = self.compare_file_contents(
+                    self.first_dir + file, self.second_dir + file)
+            else:
+                existing_dir, not_existing_dir = (
+                    (self.first_dir, self.second_dir) if self.first_dir in val
+                    else (self.second_dir, self.first_dir))
+
+                lines = [f"{not_existing_dir}{file} does not exist."]
+
+                if self.show_diff:
+                    lines.append(f"Content of {existing_dir}{file}: \n")
+                    lines.extend(get_file_contents(existing_dir + file))
+
+            self.write(lines)
+
+    def write(self, lines: List[str]) -> None:
+        if self.out_dir == "":
+            pprint(lines)
+        else:
+            write_lines(self.out_dir, lines)
+
+###
+# Helper functions
+###
+
+def sort_methods(lines: List[str]) -> List[str]:
+    """Sort class methods in the file contents by alphabetical order
+
+    Given lines of Java file contents, return lines with class methods sorted in alphabetical order.
+    Also omit empty lines or lines with spaces.
+    For example:
+        l = [
+            "package android.test;",
+            "",
+            "public static final int ORANGE = 1;",
+            "",
+            "public class TestClass {",
+            "public TestClass() { throw new RuntimeException("Stub!"); }",
+            "public void foo() { throw new RuntimeException("Stub!"); }",
+            "public void bar() { throw new RuntimeException("Stub!"); }",
+            "}"
+        ]
+        sort_methods(l) returns
+        [
+            "package android.test;",
+            "public static final int ORANGE = 1;",
+            "public class TestClass {",
+            "public TestClass() { throw new RuntimeException("Stub!"); }",
+            "public void bar() { throw new RuntimeException("Stub!"); }",
+            "public void foo() { throw new RuntimeException("Stub!"); }",
+            "}"
+        ]
+
+    Args:
+        lines: List of strings consisted of Java file contents.
+
+    Returns:
+        A list of string with sorted class methods.
+
+    """
+    def is_not_blank(l: str) -> bool:
+        return bool(l) and not l.isspace()
+
+    ret = list()
+
+    in_class = False
+    buffer = list()
+    for line in lines:
+        if not in_class:
+            if "class" in line:
+                in_class = True
+                ret.append(line)
+            else:
+                # Adding static variables, package info, etc.
+                # Skipping empty or space lines.
+                if is_not_blank(line):
+                    ret.append(line)
+        else:
+            # End of class
+            if line and line[0] == "}":
+                in_class = False
+                ret.extend(sorted(buffer))
+                buffer = list()
+                ret.append(line)
+            else:
+                if is_not_blank(line):
+                    buffer.append(line)
+
+    return ret
+
+def get_file_contents(file_path: str) -> List[str]:
+    lines = list()
+    with open(file_path) as f:
+        lines = [line.rstrip('\n') for line in f]
+        f.close()
+    return lines
+
+def pprint(l: List[str]) -> None:
+    for line in l:
+        print(line)
+
+def write_lines(out_dir: str, lines: List[str]) -> None:
+    with open(out_dir, "a") as f:
+        f.writelines(line + '\n' for line in lines)
+        f.write("\n")
+        f.close()
+
+def dir_exists(dir: str) -> bool:
+    return Path(dir).exists()
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser()
+    parser.add_argument('first_dir', action='store', type=str,
+                        help="first path to compare file directory and contents")
+    parser.add_argument('second_dir', action='store', type=str,
+                        help="second path to compare file directory and contents")
+    parser.add_argument('--out', dest='out_dir',
+                        action='store', default="", type=str,
+                        help="optional directory to write log. If not set, will print to console")
+    parser.add_argument('--show-diff-file', dest='show_diff',
+                        action=argparse.BooleanOptionalAction,
+                        help="optional flag. If passed, will print out the content of the file unique to each directories")
+    parser.add_argument('--include-common', dest='include_common',
+                        action=argparse.BooleanOptionalAction,
+                        help="optional flag. If passed, will print out the contents common to both files as well,\
+                            instead of printing only diff lines.")
+    parser.add_argument('--skip-words', nargs='+',
+                        dest='skip_words', default=[], help="optional words to skip in comparison")
+
+    args = parser.parse_args()
+
+    if not args.first_dir or not args.second_dir:
+        parser.print_usage()
+        exit(0)
+
+    analyzer = FilesDiffAnalyzer(args)
+    analyzer.analyze()