Merge "Add missing apksigner dependency"
diff --git a/CleanSpec.mk b/CleanSpec.mk
index dd5c476..957da92 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -769,6 +769,10 @@
 # More of SOONG_HOST_OUT_EXECUTABLES has been moved to HOST_OUT_EXECUTABLES
 $(call add-clean-step, rm -rf $(SOONG_HOST_OUT))
 
+# Last of SOONG_HOST_OUT_EXECUTABLES has been moved to HOST_OUT_EXECUTABLES
+# Don't use SOONG_HOST_OUT, it is now an alias for HOST_OUT.
+$(call add-clean-step, rm -rf $(OUT_DIR)/soong/host)
+
 # ************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
 # ************************************************
diff --git a/core/Makefile b/core/Makefile
index c42bc48..b3841e9 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -1909,14 +1909,18 @@
 recovery_sepolicy := \
     $(TARGET_RECOVERY_ROOT_OUT)/sepolicy \
     $(TARGET_RECOVERY_ROOT_OUT)/plat_file_contexts \
+    $(TARGET_RECOVERY_ROOT_OUT)/plat_service_contexts \
     $(TARGET_RECOVERY_ROOT_OUT)/plat_property_contexts \
     $(TARGET_RECOVERY_ROOT_OUT)/system_ext_file_contexts \
+    $(TARGET_RECOVERY_ROOT_OUT)/system_ext_service_contexts \
     $(TARGET_RECOVERY_ROOT_OUT)/system_ext_property_contexts \
     $(TARGET_RECOVERY_ROOT_OUT)/vendor_file_contexts \
+    $(TARGET_RECOVERY_ROOT_OUT)/vendor_service_contexts \
     $(TARGET_RECOVERY_ROOT_OUT)/vendor_property_contexts \
     $(TARGET_RECOVERY_ROOT_OUT)/odm_file_contexts \
     $(TARGET_RECOVERY_ROOT_OUT)/odm_property_contexts \
     $(TARGET_RECOVERY_ROOT_OUT)/product_file_contexts \
+    $(TARGET_RECOVERY_ROOT_OUT)/product_service_contexts \
     $(TARGET_RECOVERY_ROOT_OUT)/product_property_contexts
 
 # Passed into rsync from non-recovery root to recovery root, to avoid overwriting recovery-specific
@@ -4731,6 +4735,7 @@
 
 ifeq ($(AB_OTA_UPDATER),true)
 updater_dep := system/update_engine/update_engine.conf
+updater_dep := external/zucchini/version_info.h
 endif
 
 # Build OTA tools if non-A/B is allowed
@@ -5204,6 +5209,7 @@
 ifeq ($(AB_OTA_UPDATER),true)
 	@# When using the A/B updater, include the updater config files in the zip.
 	$(hide) cp $(TOPDIR)system/update_engine/update_engine.conf $(zip_root)/META/update_engine_config.txt
+	$(hide) cp $(TOPDIR)external/zucchini/version_info.h $(zip_root)/META/zucchini_config.txt
 	$(hide) for part in $(strip $(AB_OTA_PARTITIONS)); do \
 	  echo "$${part}" >> $(zip_root)/META/ab_partitions.txt; \
 	done
diff --git a/core/android_soong_config_vars.mk b/core/android_soong_config_vars.mk
index 7bea305..cd42e1f 100644
--- a/core/android_soong_config_vars.mk
+++ b/core/android_soong_config_vars.mk
@@ -99,3 +99,7 @@
 ifeq (true,$(MODULE_BUILD_FROM_SOURCE))
 $(call add_soong_config_var_value,ANDROID,module_build_from_source,true)
 endif
+
+# TODO(b/196084106): Remove when Java optimizations enabled by default for
+# system packages.
+$(call add_soong_config_var,ANDROID,SYSTEM_OPTIMIZE_JAVA)
diff --git a/core/base_rules.mk b/core/base_rules.mk
index 02b424d..134cb8f 100644
--- a/core/base_rules.mk
+++ b/core/base_rules.mk
@@ -932,9 +932,13 @@
   # of files provided by this module.  Used by custom packaging rules like
   # package-modules.mk that need to copy the built files to a custom install
   # location during packaging.
+  #
+  # Translate copies from $(LOCAL_PREBUILT_MODULE_FILE) to $(LOCAL_BUILT_MODULE)
+  # so that package-modules.mk gets any transtive dependencies added to
+  # $(LOCAL_BUILT_MODULE), for example unstripped symbols files.
   ALL_MODULES.$(my_register_name).BUILT_INSTALLED := \
     $(strip $(ALL_MODULES.$(my_register_name).BUILT_INSTALLED) \
-      $(LOCAL_SOONG_INSTALL_PAIRS) \
+      $(patsubst $(LOCAL_PREBUILT_MODULE_FILE):%,$(LOCAL_BUILT_MODULE):%,$(LOCAL_SOONG_INSTALL_PAIRS)) \
       $(my_init_rc_pairs) \
       $(my_test_data_pairs) \
       $(my_vintf_pairs))
diff --git a/core/board_config.mk b/core/board_config.mk
index b3e6957..821607e 100644
--- a/core/board_config.mk
+++ b/core/board_config.mk
@@ -125,6 +125,42 @@
 # Defines the list of logical vendor ramdisk names to build or include in vendor_boot.
 _board_strip_readonly_list += BOARD_VENDOR_RAMDISK_FRAGMENTS
 
+# These are all variables used to build $(INSTALLED_MISC_INFO_TARGET)
+# in build/make/core/Makefile. Their values get used in command line
+# arguments, so they have to be stripped to make the ninja files stable.
+_board_strip_list :=
+_board_strip_list += BOARD_DTBOIMG_PARTITION_SIZE
+_board_strip_list += BOARD_AVB_DTBO_KEY_PATH
+_board_strip_list += BOARD_AVB_DTBO_ALGORITHM
+_board_strip_list += BOARD_AVB_DTBO_ROLLBACK_INDEX_LOCATION
+_board_strip_list += BOARD_AVB_PVMFW_KEY_PATH
+_board_strip_list += BOARD_AVB_PVMFW_ALGORITHM
+_board_strip_list += BOARD_AVB_PVMFW_ROLLBACK_INDEX_LOCATION
+_board_strip_list += BOARD_PARTIAL_OTA_UPDATE_PARTITIONS_LIST
+_board_strip_list += BOARD_BPT_DISK_SIZE
+_board_strip_list += BOARD_BPT_INPUT_FILES
+_board_strip_list += BOARD_BPT_MAKE_TABLE_ARGS
+_board_strip_list += BOARD_AVB_VBMETA_VENDOR_ROLLBACK_INDEX_LOCATION
+_board_strip_list += BOARD_AVB_VBMETA_VENDOR_ALGORITHM
+_board_strip_list += BOARD_AVB_VBMETA_VENDOR_KEY_PATH
+_board_strip_list += BOARD_AVB_VBMETA_VENDOR
+_board_strip_list += BOARD_AVB_VBMETA_SYSTEM_ROLLBACK_INDEX_LOCATION
+_board_strip_list += BOARD_AVB_VBMETA_SYSTEM_ALGORITHM
+_board_strip_list += BOARD_AVB_VBMETA_SYSTEM_KEY_PATH
+_board_strip_list += BOARD_AVB_VBMETA_SYSTEM
+_board_strip_list += BOARD_AVB_RECOVERY_KEY_PATH
+_board_strip_list += BOARD_AVB_RECOVERY_ALGORITHM
+_board_strip_list += BOARD_AVB_RECOVERY_ROLLBACK_INDEX_LOCATION
+_board_strip_list += BOARD_AVB_VENDOR_BOOT_KEY_PATH
+_board_strip_list += BOARD_AVB_VENDOR_BOOT_ALGORITHM
+_board_strip_list += BOARD_AVB_VENDOR_BOOT_ROLLBACK_INDEX_LOCATION
+_board_strip_list += BOARD_GKI_SIGNING_SIGNATURE_ARGS
+_board_strip_list += BOARD_GKI_SIGNING_ALGORITHM
+_board_strip_list += BOARD_GKI_SIGNING_KEY_PATH
+_board_strip_list += BOARD_MKBOOTIMG_ARGS
+_board_strip_list += BOARD_VENDOR_BOOTIMAGE_PARTITION_SIZE
+
+
 _build_broken_var_list := \
   BUILD_BROKEN_DUP_RULES \
   BUILD_BROKEN_DUP_SYSPROP \
@@ -185,16 +221,6 @@
   .KATI_READONLY := TARGET_DEVICE_DIR
 endif
 
-# Dumps all variables that match [A-Z][A-Z0-9_]* to the file at $(1)
-# It is used to print only the variables that are likely to be relevant to the
-# board configuration.
-define dump-public-variables
-$(file >$(OUT_DIR)/dump-public-variables-temp.txt,$(subst $(space),$(newline),$(.VARIABLES)))\
-$(file >$(1),\
-$(foreach v, $(shell grep -he "^[A-Z][A-Z0-9_]*$$" $(OUT_DIR)/dump-public-variables-temp.txt | grep -vhe "^SOONG_"),\
-$(v) := $(strip $($(v)))$(newline)))
-endef
-
 # TODO(colefaust) change this if to RBC_PRODUCT_CONFIG when
 # the board configuration is known to work on everything
 # the product config works on.
@@ -202,8 +228,7 @@
 include $(board_config_mk)
 else
   $(shell mkdir -p $(OUT_DIR)/rbc)
-
-  $(call dump-public-variables, $(OUT_DIR)/rbc/make_vars_pre_board_config.mk)
+  $(call dump-variables-rbc, $(OUT_DIR)/rbc/make_vars_pre_board_config.mk)
 
   $(shell $(OUT_DIR)/soong/mk2rbc \
     --mode=write -r --outdir $(OUT_DIR)/rbc \
@@ -214,10 +239,8 @@
     $(error board configuration converter failed: $(.SHELLSTATUS))
   endif
 
-  $(shell $(OUT_DIR)/soong/rbcrun \
-    RBC_OUT="make,global" \
-    $(OUT_DIR)/rbc/boardlauncher.rbc \
-    >$(OUT_DIR)/rbc/rbc_board_config_results.mk)
+  $(shell build/soong/scripts/update_out $(OUT_DIR)/rbc/rbc_board_config_results.mk \
+    $(OUT_DIR)/soong/rbcrun RBC_OUT="make,global" $(OUT_DIR)/rbc/boardlauncher.rbc)
   ifneq ($(.SHELLSTATUS),0)
     $(error board configuration runner failed: $(.SHELLSTATUS))
   endif
@@ -243,6 +266,7 @@
 
 # Clean up and verify BoardConfig variables
 $(foreach var,$(_board_strip_readonly_list),$(eval $(var) := $$(strip $$($(var)))))
+$(foreach var,$(_board_strip_list),$(eval $(var) := $$(strip $$($(var)))))
 $(foreach var,$(_board_true_false_vars), \
   $(if $(filter-out true false,$($(var))), \
     $(error Valid values of $(var) are "true", "false", and "". Not "$($(var))")))
diff --git a/core/config.mk b/core/config.mk
index 7c93610..4794816 100644
--- a/core/config.mk
+++ b/core/config.mk
@@ -833,12 +833,11 @@
 
 # A list of SEPolicy versions, besides PLATFORM_SEPOLICY_VERSION, that the framework supports.
 PLATFORM_SEPOLICY_COMPAT_VERSIONS := \
-    26.0 \
-    27.0 \
     28.0 \
     29.0 \
     30.0 \
     31.0 \
+    32.0 \
 
 .KATI_READONLY := \
     PLATFORM_SEPOLICY_COMPAT_VERSIONS \
diff --git a/core/config_sanitizers.mk b/core/config_sanitizers.mk
index 46f7f24..a0ff119 100644
--- a/core/config_sanitizers.mk
+++ b/core/config_sanitizers.mk
@@ -299,9 +299,6 @@
   my_sanitize := $(filter-out cfi,$(my_sanitize))
   my_cflags += -fno-lto
   my_ldflags += -fno-lto
-
-  # TODO(b/133876586): Disable experimental pass manager for fuzzer builds.
-  my_cflags += -fno-experimental-new-pass-manager
 endif
 
 ifneq ($(filter integer_overflow,$(my_sanitize)),)
diff --git a/core/definitions.mk b/core/definitions.mk
index 38b572b..c981152 100644
--- a/core/definitions.mk
+++ b/core/definitions.mk
@@ -580,9 +580,17 @@
 ## License metadata build rule for my_register_name $(1)
 ###########################################################
 define license-metadata-rule
-$(strip $(eval _dir := $(call license-metadata-dir)))
+$(foreach meta_lic, $(ALL_MODULES.$(1).DELAYED_META_LIC),$(call _license-metadata-rule,$(1),$(meta_lic)))
+$(call notice-rule,$(1))
+endef
+
+define _license-metadata-rule
 $(strip $(eval _srcs := $(strip $(foreach d,$(ALL_MODULES.$(1).NOTICE_DEPS),$(if $(strip $(ALL_MODULES.$(call word-colon,1,$(d)).INSTALLED)), $(ALL_MODULES.$(call word-colon,1,$(d)).INSTALLED),$(if $(strip $(ALL_MODULES.$(call word-colon,1,$(d)).BUILT)), $(ALL_MODULES.$(call word-colon,1,$(d)).BUILT), $(call word-colon,1,$d)))))))
-$(strip $(eval _deps := $(sort $(filter-out $(_dir)/$(1).meta_lic%,$(foreach d,$(ALL_MODULES.$(1).NOTICE_DEPS), $(_dir)/$(call word-colon,1,$(d)).meta_lic:$(call wordlist-colon,2,9999,$d))))))
+$(strip $(eval _deps := $(sort $(filter-out $(2)%,\
+   $(foreach d,$(ALL_MODULES.$(1).NOTICE_DEPS),\
+     $(addsuffix :$(call wordlist-colon,2,9999,$(d)), \
+       $(foreach dt,$(ALL_MODULES.$(d).BUILT) $(ALL_MODULES.$(d).INSTALLED),\
+         $(ALL_TARGETS.$(dt).META_LIC))))))))
 $(strip $(eval _notices := $(sort $(ALL_MODULES.$(1).NOTICES))))
 $(strip $(eval _tgts := $(sort $(ALL_MODULES.$(1).BUILT))))
 $(strip $(eval _inst := $(sort $(ALL_MODULES.$(1).INSTALLED))))
@@ -594,22 +602,22 @@
   $(foreach ns,$(_ns),$(ns):$(_d) ) \
 ))))
 
-$(_dir)/$(1).meta_lic: PRIVATE_KINDS := $(sort $(ALL_MODULES.$(1).LICENSE_KINDS))
-$(_dir)/$(1).meta_lic: PRIVATE_CONDITIONS := $(sort $(ALL_MODULES.$(1).LICENSE_CONDITIONS))
-$(_dir)/$(1).meta_lic: PRIVATE_NOTICES := $(_notices)
-$(_dir)/$(1).meta_lic: PRIVATE_NOTICE_DEPS := $(_deps)
-$(_dir)/$(1).meta_lic: PRIVATE_SOURCES := $(_srcs)
-$(_dir)/$(1).meta_lic: PRIVATE_TARGETS := $(_tgts)
-$(_dir)/$(1).meta_lic: PRIVATE_INSTALLED := $(_inst)
-$(_dir)/$(1).meta_lic: PRIVATE_PATH := $(_path)
-$(_dir)/$(1).meta_lic: PRIVATE_IS_CONTAINER := $(ALL_MODULES.$(1).IS_CONTAINER)
-$(_dir)/$(1).meta_lic: PRIVATE_PACKAGE_NAME := $(strip $(ALL_MODULES.$(1).LICENSE_PACKAGE_NAME))
-$(_dir)/$(1).meta_lic: PRIVATE_INSTALL_MAP := $(_map)
-$(_dir)/$(1).meta_lic: PRIVATE_MODULE_TYPE := $(ALL_MODULES.$(1).MODULE_TYPE)
-$(_dir)/$(1).meta_lic: PRIVATE_MODULE_CLASS := $(ALL_MODULES.$(1).MODULE_CLASS)
-$(_dir)/$(1).meta_lic: PRIVATE_INSTALL_MAP := $(_map)
-$(_dir)/$(1).meta_lic: $(BUILD_LICENSE_METADATA)
-$(_dir)/$(1).meta_lic : $(foreach d,$(_deps),$(call word-colon,1,$(d))) $(foreach n,$(_notices),$(call word-colon,1,$(n)) )
+$(2): PRIVATE_KINDS := $(sort $(ALL_MODULES.$(1).LICENSE_KINDS))
+$(2): PRIVATE_CONDITIONS := $(sort $(ALL_MODULES.$(1).LICENSE_CONDITIONS))
+$(2): PRIVATE_NOTICES := $(_notices)
+$(2): PRIVATE_NOTICE_DEPS := $(_deps)
+$(2): PRIVATE_SOURCES := $(_srcs)
+$(2): PRIVATE_TARGETS := $(_tgts)
+$(2): PRIVATE_INSTALLED := $(_inst)
+$(2): PRIVATE_PATH := $(_path)
+$(2): PRIVATE_IS_CONTAINER := $(ALL_MODULES.$(1).IS_CONTAINER)
+$(2): PRIVATE_PACKAGE_NAME := $(strip $(ALL_MODULES.$(1).LICENSE_PACKAGE_NAME))
+$(2): PRIVATE_INSTALL_MAP := $(_map)
+$(2): PRIVATE_MODULE_TYPE := $(ALL_MODULES.$(1).MODULE_TYPE)
+$(2): PRIVATE_MODULE_CLASS := $(ALL_MODULES.$(1).MODULE_CLASS)
+$(2): PRIVATE_INSTALL_MAP := $(_map)
+$(2): $(BUILD_LICENSE_METADATA)
+$(2) : $(foreach d,$(_deps),$(call word-colon,1,$(d))) $(foreach n,$(_notices),$(call word-colon,1,$(n)) )
 	rm -f $$@
 	mkdir -p $$(dir $$@)
 	$(BUILD_LICENSE_METADATA) \
@@ -627,7 +635,9 @@
 	  -p '$$(PRIVATE_PACKAGE_NAME)' \
 	  $$(addprefix -r ,$$(PRIVATE_PATH)) \
 	  -o $$@
+endef
 
+define notice-rule
 $(strip $(eval _mifs := $(sort $(ALL_MODULES.$(1).MODULE_INSTALLED_FILENAMES))))
 $(strip $(eval _infs := $(sort $(ALL_MODULES.$(1).INSTALLED_NOTICE_FILE))))
 
@@ -636,7 +646,6 @@
 $(if $(strip $(filter $(1),$(INSTALLED_NOTICE_FILES.$(inf).MODULE))),
 $(strip $(eval _mif := $(firstword $(foreach m,$(_mifs),$(if $(filter %/src/$(m).txt,$(inf)),$(m))))))
 
-$(inf) : $(_dir)/$(1).meta_lic
 $(inf): PRIVATE_INSTALLED_MODULE := $(_mif)
 $(inf) : PRIVATE_NOTICES := $(sort $(foreach n,$(_notices),$(call word-colon,1,$(n) )))
 
@@ -831,14 +840,9 @@
   $(foreach t,$(sort $(ALL_NON_MODULES)), \
     $(eval ALL_TARGETS.$(t).META_LIC := $(_dir)/$(t).meta_lic) \
   ) \
-  $(foreach m,$(sort $(ALL_MODULES)), \
-    $(foreach d,$(sort $(ALL_MODULES.$(m).BUILT) $(ALL_MODULES.$(m).INSTALLED)), \
-      $(eval ALL_TARGETS.$(d).META_LIC := $(_dir)/$(m).meta_lic) \
-    ) \
-  ) \
-)$(foreach t,$(sort $(ALL_NON_MODULES)),$(eval $(call non-module-license-metadata-rule,$(t))))$(strip \
-)$(foreach m,$(sort $(ALL_MODULES)),$(eval $(call license-metadata-rule,$(m))))$(strip \
-)$(eval $(call report-missing-licenses-rule))
+  $(foreach t,$(sort $(ALL_NON_MODULES)),$(eval $(call non-module-license-metadata-rule,$(t)))) \
+  $(foreach m,$(sort $(ALL_MODULES)),$(eval $(call license-metadata-rule,$(m)))) \
+  $(eval $(call report-missing-licenses-rule)))
 endef
 
 ###########################################################
diff --git a/core/dex_preopt_config.mk b/core/dex_preopt_config.mk
index 0c806c1..d5293cf 100644
--- a/core/dex_preopt_config.mk
+++ b/core/dex_preopt_config.mk
@@ -109,6 +109,8 @@
   $(call add_json_list, SystemServerJars,                        $(PRODUCT_SYSTEM_SERVER_JARS))
   $(call add_json_list, SystemServerApps,                        $(PRODUCT_SYSTEM_SERVER_APPS))
   $(call add_json_list, ApexSystemServerJars,                    $(PRODUCT_APEX_SYSTEM_SERVER_JARS))
+  $(call add_json_list, StandaloneSystemServerJars,              $(PRODUCT_STANDALONE_SYSTEM_SERVER_JARS))
+  $(call add_json_list, ApexStandaloneSystemServerJars,          $(PRODUCT_APEX_STANDALONE_SYSTEM_SERVER_JARS))
   $(call add_json_bool, BrokenSuboptimalOrderOfSystemServerJars, $(PRODUCT_BROKEN_SUBOPTIMAL_ORDER_OF_SYSTEM_SERVER_JARS))
   $(call add_json_list, SpeedApps,                               $(PRODUCT_DEXPREOPT_SPEED_APPS))
   $(call add_json_list, PreoptFlags,                             $(PRODUCT_DEX_PREOPT_DEFAULT_FLAGS))
diff --git a/core/envsetup.mk b/core/envsetup.mk
index bb1aa1e..21eac7f 100644
--- a/core/envsetup.mk
+++ b/core/envsetup.mk
@@ -310,6 +310,16 @@
 endif
 #################################################################
 
+# Dumps all variables that match [A-Z][A-Z0-9_]* (with a few exceptions)
+# to the file at $(1). It is used to print only the variables that are
+# likely to be relevant to the product or board configuration.
+define dump-variables-rbc
+$(file >$(OUT_DIR)/dump-variables-rbc-temp.txt,$(subst $(space),$(newline),$(.VARIABLES)))\
+$(file >$(1),\
+$(foreach v, $(shell grep -he "^[A-Z][A-Z0-9_]*$$" $(OUT_DIR)/dump-variables-rbc-temp.txt | grep -vhE "^(SOONG_.*|LOCAL_PATH|TOPDIR|PRODUCT_COPY_OUT_.*)$$"),\
+$(v) := $(strip $($(v)))$(newline)))
+endef
+
 # Read the product specs so we can get TARGET_DEVICE and other
 # variables that we need in order to locate the output files.
 include $(BUILD_SYSTEM)/product_config.mk
@@ -323,6 +333,12 @@
 SDK_HOST_ARCH := x86
 TARGET_OS := linux
 
+# Some board configuration files use $(PRODUCT_OUT)
+TARGET_OUT_ROOT := $(OUT_DIR)/target
+TARGET_PRODUCT_OUT_ROOT := $(TARGET_OUT_ROOT)/product
+PRODUCT_OUT := $(TARGET_PRODUCT_OUT_ROOT)/$(TARGET_DEVICE)
+.KATI_READONLY := TARGET_OUT_ROOT TARGET_PRODUCT_OUT_ROOT PRODUCT_OUT
+
 include $(BUILD_SYSTEM)/board_config.mk
 
 # the target build type defaults to release
@@ -335,28 +351,24 @@
 
 SOONG_OUT_DIR := $(OUT_DIR)/soong
 
-TARGET_OUT_ROOT := $(OUT_DIR)/target
-
 HOST_OUT_ROOT := $(OUT_DIR)/host
 
-.KATI_READONLY := SOONG_OUT_DIR TARGET_OUT_ROOT HOST_OUT_ROOT
+.KATI_READONLY := SOONG_OUT_DIR HOST_OUT_ROOT
 
 # We want to avoid two host bin directories in multilib build.
 HOST_OUT := $(HOST_OUT_ROOT)/$(HOST_OS)-$(HOST_PREBUILT_ARCH)
-SOONG_HOST_OUT := $(SOONG_OUT_DIR)/host/$(HOST_OS)-$(HOST_PREBUILT_ARCH)
+
+# Soong now installs to the same directory as Make.
+SOONG_HOST_OUT := $(HOST_OUT)
 
 HOST_CROSS_OUT := $(HOST_OUT_ROOT)/$(HOST_CROSS_OS)-$(HOST_CROSS_ARCH)
 
 .KATI_READONLY := HOST_OUT SOONG_HOST_OUT HOST_CROSS_OUT
 
-TARGET_PRODUCT_OUT_ROOT := $(TARGET_OUT_ROOT)/product
-
 TARGET_COMMON_OUT_ROOT := $(TARGET_OUT_ROOT)/common
 HOST_COMMON_OUT_ROOT := $(HOST_OUT_ROOT)/common
 
-PRODUCT_OUT := $(TARGET_PRODUCT_OUT_ROOT)/$(TARGET_DEVICE)
-
-.KATI_READONLY := TARGET_PRODUCT_OUT_ROOT TARGET_COMMON_OUT_ROOT HOST_COMMON_OUT_ROOT PRODUCT_OUT
+.KATI_READONLY := TARGET_COMMON_OUT_ROOT HOST_COMMON_OUT_ROOT
 
 OUT_DOCS := $(TARGET_COMMON_OUT_ROOT)/docs
 OUT_NDK_DOCS := $(TARGET_COMMON_OUT_ROOT)/ndk-docs
diff --git a/core/envsetup.rbc b/core/envsetup.rbc
deleted file mode 100644
index 4cc98c8..0000000
--- a/core/envsetup.rbc
+++ /dev/null
@@ -1,224 +0,0 @@
-# Copyright 2021 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
-#
-#      https://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.
-
-load(":build_id.rbc|init", _build_id_init = "init")
-
-def _all_versions():
-    """Returns all known versions."""
-    versions = ["OPR1", "OPD1", "OPD2", "OPM1", "OPM2", "PPR1", "PPD1", "PPD2", "PPM1", "PPM2", "QPR1"]
-    for v in ("Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"):
-        for e in ("P1A", "P1B", "P2A", "P2B", "D1A", "D1B", "D2A", "D2B", "Q1A", "Q1B", "Q2A", "Q2B", "Q3A", "Q3B"):
-            versions.append(v + e)
-    return versions
-
-def _allowed_versions(all_versions, min_version, max_version, default_version):
-    """Checks that version range and default versions is valid, returns all versions in range."""
-    for v in (min_version, max_version, default_version):
-        if v not in all_versions:
-            fail("% is invalid" % v)
-
-    min_i = all_versions.index(min_version)
-    max_i = all_versions.index(max_version)
-    def_i = all_versions.index(default_version)
-    if min_i > max_i:
-        fail("%s should come before %s in the version list" % (min_version, max_version))
-    if def_i < min_i or def_i > max_i:
-        fail("%s should come between % and %s" % (default_version, min_version, max_version))
-    return all_versions[min_i:max_i + 1]
-
-# This function is a manual conversion of the version_defaults.mk
-def _versions_default(g, all_versions, v):
-    """Handle various build version information.
-
-    Guarantees that the following are defined:
-     PLATFORM_VERSION
-     PLATFORM_SDK_VERSION
-     PLATFORM_VERSION_CODENAME
-     DEFAULT_APP_TARGET_SDK
-     BUILD_ID
-     BUILD_NUMBER
-     PLATFORM_SECURITY_PATCH
-     PLATFORM_VNDK_VERSION
-     PLATFORM_SYSTEMSDK_VERSIONS
-    """
-
-    # If build_id.rbc exists, it may override some of the defaults.
-    # Note that build.prop target also wants INTERNAL_BUILD_ID_MAKEFILE to be set if the file exists.
-    if _build_id_init != None:
-        _build_id_init(g)
-        g["INTERNAL_BUILD_ID_MAKEFILE"] = "build/make/core/build_id"
-
-    allowed_versions = _allowed_versions(all_versions, v.min_platform_version, v.max_platform_version, v.default_platform_version)
-    g.setdefault("TARGET_PLATFORM_VERSION", v.default_platform_version)
-    if g["TARGET_PLATFORM_VERSION"] not in allowed_versions:
-        fail("% is not valid, must be one of %s" % (g["TARGET_PLATFORM_VERSION"], allowed_versions))
-
-    g["DEFAULT_PLATFORM_VERSION"] = v.default_platform_version
-    g["PLATFORM_VERSION_LAST_STABLE"] = v.platform_version_last_stable
-    target_platform_version = g["TARGET_PLATFORM_VERSION"]
-    if v.codenames[target_platform_version]:
-        g.setdefault("PLATFORM_VERSION_CODENAME", v.codenames[target_platform_version])
-    else:
-        g.setdefault("PLATFORM_VERSION_CODENAME", target_platform_version)
-    # TODO(asmundak): set PLATFORM_VERSION_ALL_CODENAMES
-
-    g.setdefault("PLATFORM_SDK_VERSION", v.platform_sdk_version)
-    version_codename = g["PLATFORM_VERSION_CODENAME"]
-    if version_codename == "REL":
-        g.setdefault("PLATFORM_VERSION", g["PLATFORM_VERSION_LAST_STABLE"])
-        g["PLATFORM_PREVIEW_SDK_VERSION"] = 0
-        g.setdefault("DEFAULT_APP_TARGET_SDK", g["PLATFORM_SDK_VERSION"])
-        g.setdefault("PLATFORM_VNDK_VERSION", g["PLATFORM_SDK_VERSION"])
-    else:
-        g.setdefault("PLATFORM_VERSION", version_codename)
-        g.setdefault("PLATFORM_PREVIEW_SDK_VERSION", 1)
-        g.setdefault("DEFAULT_APP_TARGET_SDK", version_codename)
-        g.setdefault("PLATFORM_VNDK_VERSION", version_codename)
-
-    g.setdefault("PLATFORM_SYSTEMSDK_MIN_VERSION", 28)
-    versions = [str(i) for i in range(g["PLATFORM_SYSTEMSDK_MIN_VERSION"], g["PLATFORM_SDK_VERSION"] + 1)]
-    versions.append(version_codename)
-    g["PLATFORM_SYSTEMSDK_VERSIONS"] = sorted(versions)
-
-    #  Used to indicate the security patch that has been applied to the device.
-    #  It must signify that the build includes all security patches issued up through the designated Android Public Security Bulletin.
-    #  It must be of the form "YYYY-MM-DD" on production devices.
-    #  It must match one of the Android Security Patch Level strings of the Public Security Bulletins.
-    #  If there is no $PLATFORM_SECURITY_PATCH set, keep it empty.
-
-    g.setdefault("PLATFORM_SECURITY_PATCH", v.platform_security_patch)
-    dt = 'TZ="GMT" %s' % g["PLATFORM_SECURITY_PATCH"]
-    g.setdefault("PLATFORM_SECURITY_PATCH_TIMESTAMP", rblf_shell("date -d '%s' +%%s" % dt))
-
-    # Used to indicate the base os applied to the device. Can be an arbitrary string, but must be a single word.
-    # If there is no $PLATFORM_BASE_OS set, keep it empty.
-    g.setdefault("PLATFORM_BASE_OS", "")
-
-    # Used to signify special builds.  E.g., branches and/or releases, like "M5-RC7".  Can be an arbitrary string, but
-    # must be a single word and a valid file name. If there is no BUILD_ID set, make it obvious.
-    g.setdefault("BUILD_ID", "UNKNOWN")
-
-    # BUILD_NUMBER should be set to the source control value that represents the current state of the source code.
-    # E.g., a perforce changelist number or a git hash.  Can be an arbitrary string (to allow for source control that
-    # uses something other than numbers), but must be a single word and a valid file name.
-    #
-    # If no BUILD_NUMBER is set, create a useful "I am an engineering build from this date/time" value.  Make it start
-    # with a non-digit so that anyone trying to parse it as an integer will probably get "0".
-    g.setdefault("BUILD_NUMBER", "eng.%s.%s" % (g["USER"], "TIMESTAMP"))
-
-    # Used to set minimum supported target sdk version. Apps targeting SDK version lower than the set value will result
-    # in a warning being shown when any activity from the app is started.
-    g.setdefault("PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION", 23)
-
-    # This is the sdk extension version of this tree.
-    g["PLATFORM_SDK_EXTENSION_VERSION"] = v.platform_sdk_extension_version
-    # This is the sdk extension version that PLATFORM_SDK_VERSION ships with.
-    g["PLATFORM_BASE_SDK_EXTENSION_VERSION"] = v.platform_base_sdk_extension_version
-
-
-def init(g, v):
-    """Initializes globals.
-
-    The code is the Starlark counterpart of the contents of the
-    envsetup.mk file.
-    Args:
-        g: globals dictionary
-        v: version info struct
-    """
-    all_versions = _all_versions()
-    _versions_default(g, all_versions, v)
-    for v in all_versions:
-        g["IS_AT_LEAST" + v] = True
-        if v == g["TARGET_PLATFORM_VERSION"]:
-            break
-
-    # ---------------------------------------------------------------
-    # If you update the build system such that the environment setup or buildspec.mk need to be updated,
-    # increment this number, and people who haven't re-run those will have to do so before they can build.
-    # Make sure to also update the corresponding value in buildspec.mk.default and envsetup.sh.
-    g["CORRECT_BUILD_ENV_SEQUENCE_NUMBER"] = 13
-
-    g.setdefault("TARGET_PRODUCT", "aosp_arm")
-    g.setdefault("TARGET_BUILD_VARIANT", "eng")
-
-    g.setdefault("TARGET_BUILD_APPS", [])
-    g["TARGET_BUILD_UNBUNDLED"] = (g["TARGET_BUILD_APPS"] != []) or (getattr(g, "TARGET_BUILD_UNBUNDLED_IMAGE", "") != "")
-
-    # ---------------------------------------------------------------
-    # Set up configuration for host machine.  We don't do cross-compiles except for arm, so the HOST
-    # is whatever we are running on.
-    host = rblf_shell("uname -sm")
-    if host.find("Linux") >= 0:
-        g["HOST_OS"] = "linux"
-    elif host.find("Darwin") >= 0:
-        g["HOST_OS"] = "darwin"
-    else:
-        fail("Cannot run on %s OS" % host)
-
-    # TODO(asmundak): set g.HOST_OS_EXTRA
-
-    g["BUILD_OS"] = g["HOST_OS"]
-
-    # TODO(asmundak): check cross-OS build
-
-    if host.find("x86_64") >= 0:
-        g["HOST_ARCH"] = "x86_64"
-        g["HOST_2ND_ARCH"] = "x86"
-        g["HOST_IS_64_BIT"] = True
-    elif host.find("i686") >= 0 or host.find("x86") >= 0:
-        fail("Building on a 32-bit x86 host is not supported: %s" % host)
-    elif g["HOST_OS"] == "darwin":
-        g["HOST_2ND_ARCH"] = ""
-
-    g["HOST_2ND_ARCH_VAR_PREFIX"] = "2ND_"
-    g["HOST_2ND_ARCH_MODULE_SUFFIX"] = "_32"
-    g["HOST_CROSS_2ND_ARCH_VAR_PREFIX"] = "2ND_"
-    g["HOST_CROSS_2ND_ARCH_MODULE_SUFFIX"] = "_64"
-    g["TARGET_2ND_ARCH_VAR_PREFIX"] = "2ND_"
-
-    # TODO(asmundak): envsetup.mk lines 216-226:
-    # convert combo-related stuff from combo/select.mk
-
-    # on windows, the tools have .exe at the end, and we depend on the
-    # host config stuff being done first
-    g["BUILD_ARCH"] = g["HOST_ARCH"]
-    g["BUILD_2ND_ARCH"] = g["HOST_2ND_ARCH"]
-
-    # the host build defaults to release, and it must be release or debug
-    g.setdefault("HOST_BUILD_TYPE", "release")
-    if g["HOST_BUILD_TYPE"] not in ["release", "debug"]:
-        fail("HOST_BUILD_TYPE must be either release or debug, not '%s'" % g["HOST_BUILD_TYPE"])
-
-    g.update([
-	    ("TARGET_COPY_OUT_VENDOR", "||VENDOR-PATH-PH||"),
-    	("TARGET_COPY_OUT_PRODUCT", "||PRODUCT-PATH-PH||"),
-	    ("TARGET_COPY_OUT_PRODUCT_SERVICES", "||PRODUCT-PATH-PH||"),
-	    ("TARGET_COPY_OUT_SYSTEM_EXT", "||SYSTEM_EXT-PATH-PH||"),
-	    ("TARGET_COPY_OUT_ODM", "||ODM-PATH-PH||"),
-	    ("TARGET_COPY_OUT_VENDOR_DLKM", "||VENDOR_DLKM-PATH-PH||"),
-	    ("TARGET_COPY_OUT_ODM_DLKM", "||ODM_DLKM-PATH-PH||"),
-        ])
-
-    # TODO(asmundak): there is more stuff in envsetup.mk lines 249-292, but
-    # it does not seem to affect product configuration. Revisit this.
-    g["ART_APEX_JARS"] = [
-        "com.android.art:core-oj",
-        "com.android.art:core-libart",
-        "com.android.art:okhttp",
-        "com.android.art:bouncycastle",
-        "com.android.art:apache-xml",
-    ]
-
-    if g.get("TARGET_BUILD_TYPE", "") != "debug":
-        g["TARGET_BUILD_TYPE"] = "release"
diff --git a/core/host_java_library.mk b/core/host_java_library.mk
index 07797c8..0f95202 100644
--- a/core/host_java_library.mk
+++ b/core/host_java_library.mk
@@ -131,8 +131,3 @@
 ifeq ($(TURBINE_ENABLED),false)
 $(eval $(call copy-one-file,$(LOCAL_FULL_CLASSES_JACOCO_JAR),$(full_classes_header_jar)))
 endif
-
-#######################################
-# Capture deps added after base_rules.mk
-include $(BUILD_NOTICE_FILE)
-#######################################
diff --git a/core/java_common.mk b/core/java_common.mk
index 1073a78..5981b60 100644
--- a/core/java_common.mk
+++ b/core/java_common.mk
@@ -21,15 +21,20 @@
 # Modules can override this logic by specifying
 # LOCAL_JAVA_LANGUAGE_VERSION explicitly.
 ifeq (,$(LOCAL_JAVA_LANGUAGE_VERSION))
-  ifneq (,$(filter $(LOCAL_SDK_VERSION), $(TARGET_SDK_VERSIONS_WITHOUT_JAVA_18_SUPPORT)))
-    LOCAL_JAVA_LANGUAGE_VERSION := 1.7
-  else ifneq (,$(filter $(LOCAL_SDK_VERSION), $(TARGET_SDK_VERSIONS_WITHOUT_JAVA_19_SUPPORT)))
-    LOCAL_JAVA_LANGUAGE_VERSION := 1.8
-  else ifneq (,$(LOCAL_SDK_VERSION)$(TARGET_BUILD_USE_PREBUILT_SDKS))
-    # TODO(ccross): allow 1.9 for current and unbundled once we have SDK system modules
-    LOCAL_JAVA_LANGUAGE_VERSION := 1.8
-  else
+  ifdef LOCAL_IS_HOST_MODULE
+    # Host modules always default to 1.9
     LOCAL_JAVA_LANGUAGE_VERSION := 1.9
+  else
+    ifneq (,$(filter $(LOCAL_SDK_VERSION), $(TARGET_SDK_VERSIONS_WITHOUT_JAVA_18_SUPPORT)))
+      LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+    else ifneq (,$(filter $(LOCAL_SDK_VERSION), $(TARGET_SDK_VERSIONS_WITHOUT_JAVA_19_SUPPORT)))
+      LOCAL_JAVA_LANGUAGE_VERSION := 1.8
+    else ifneq (,$(LOCAL_SDK_VERSION)$(TARGET_BUILD_USE_PREBUILT_SDKS))
+      # TODO(ccross): allow 1.9 for current and unbundled once we have SDK system modules
+      LOCAL_JAVA_LANGUAGE_VERSION := 1.8
+    else
+      LOCAL_JAVA_LANGUAGE_VERSION := 1.9
+    endif
   endif
 endif
 LOCAL_JAVACFLAGS += -source $(LOCAL_JAVA_LANGUAGE_VERSION) -target $(LOCAL_JAVA_LANGUAGE_VERSION)
diff --git a/core/notice_files.mk b/core/notice_files.mk
index 36f2c8f..b3eedc0 100644
--- a/core/notice_files.mk
+++ b/core/notice_files.mk
@@ -125,16 +125,27 @@
 
 local_path := $(LOCAL_PATH)
 
+
+module_license_metadata :=
+
 ifdef my_register_name
-ALL_MODULES.$(my_register_name).LICENSE_PACKAGE_NAME := $(strip $(license_package_name))
-ALL_MODULES.$(my_register_name).MODULE_TYPE := $(strip $(ALL_MODULES.$(my_register_name).MODULE_TYPE) $(LOCAL_MODULE_TYPE))
-ALL_MODULES.$(my_register_name).MODULE_CLASS := $(strip $(ALL_MODULES.$(my_register_name).MODULE_CLASS) $(LOCAL_MODULE_CLASS))
-ALL_MODULES.$(my_register_name).LICENSE_KINDS := $(ALL_MODULES.$(my_register_name).LICENSE_KINDS) $(license_kinds)
-ALL_MODULES.$(my_register_name).LICENSE_CONDITIONS := $(ALL_MODULES.$(my_register_name).LICENSE_CONDITIONS) $(license_conditions)
-ALL_MODULES.$(my_register_name).LICENSE_INSTALL_MAP := $(ALL_MODULES.$(my_register_name).LICENSE_INSTALL_MAP) $(install_map)
-ALL_MODULES.$(my_register_name).NOTICE_DEPS := $(ALL_MODULES.$(my_register_name).NOTICE_DEPS) $(notice_deps)
-ALL_MODULES.$(my_register_name).IS_CONTAINER := $(strip $(filter-out false,$(ALL_MODULES.$(my_register_name).IS_CONTAINER) $(is_container)))
-ALL_MODULES.$(my_register_name).PATH := $(strip $(ALL_MODULES.$(my_register_name).PATH) $(local_path))
+  module_license_metadata := $(call local-intermediates-dir)/$(my_register_name).meta_lic
+
+  $(foreach target,$(ALL_MODULES.$(my_register_name).BUILT) $(ALL_MODULES.$(my_register_name).INSTALLED),\
+    $(eval ALL_TARGETS.$(target).META_LIC := $(module_license_metadata)))
+
+  ALL_MODULES.$(my_register_name).META_LIC := $(strip $(ALL_MODULES.$(my_register_name).META_LIC) $(module_license_metadata))
+
+  ALL_MODULES.$(my_register_name).DELAYED_META_LIC := $(strip $(ALL_MODULES.$(my_register_name).DELAYED_META_LIC) $(module_license_metadata))
+  ALL_MODULES.$(my_register_name).LICENSE_PACKAGE_NAME := $(strip $(license_package_name))
+  ALL_MODULES.$(my_register_name).MODULE_TYPE := $(strip $(ALL_MODULES.$(my_register_name).MODULE_TYPE) $(LOCAL_MODULE_TYPE))
+  ALL_MODULES.$(my_register_name).MODULE_CLASS := $(strip $(ALL_MODULES.$(my_register_name).MODULE_CLASS) $(LOCAL_MODULE_CLASS))
+  ALL_MODULES.$(my_register_name).LICENSE_KINDS := $(ALL_MODULES.$(my_register_name).LICENSE_KINDS) $(license_kinds)
+  ALL_MODULES.$(my_register_name).LICENSE_CONDITIONS := $(ALL_MODULES.$(my_register_name).LICENSE_CONDITIONS) $(license_conditions)
+  ALL_MODULES.$(my_register_name).LICENSE_INSTALL_MAP := $(ALL_MODULES.$(my_register_name).LICENSE_INSTALL_MAP) $(install_map)
+  ALL_MODULES.$(my_register_name).NOTICE_DEPS := $(ALL_MODULES.$(my_register_name).NOTICE_DEPS) $(notice_deps)
+  ALL_MODULES.$(my_register_name).IS_CONTAINER := $(strip $(filter-out false,$(ALL_MODULES.$(my_register_name).IS_CONTAINER) $(is_container)))
+  ALL_MODULES.$(my_register_name).PATH := $(strip $(ALL_MODULES.$(my_register_name).PATH) $(local_path))
 endif
 
 ifdef notice_file
@@ -196,6 +207,8 @@
 
 installed_notice_file := $($(my_prefix)OUT_NOTICE_FILES)/src/$(module_installed_filename).txt
 
+$(installed_notice_file): $(module_license_metadata)
+
 ifdef my_register_name
 ALL_MODULES.$(my_register_name).INSTALLED_NOTICE_FILE := $(ALL_MODULES.$(my_register_name).INSTALLED_NOTICE_FILE) $(installed_notice_file)
 ALL_MODULES.$(my_register_name).MODULE_INSTALLED_FILENAMES := $(ALL_MODULES.$(my_register_name).MODULE_INSTALLED_FILENAMES) $(module_installed_filename)
diff --git a/core/package_internal.mk b/core/package_internal.mk
index 9f5a599..800dbbc 100644
--- a/core/package_internal.mk
+++ b/core/package_internal.mk
@@ -35,6 +35,10 @@
 endif
 LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
 
+ifneq ($(strip $(LOCAL_MODULE_STEM)$(LOCAL_BUILT_MODULE_STEM)),)
+$(error $(LOCAL_PATH): Package modules may not define LOCAL_MODULE_STEM or LOCAL_BUILT_MODULE_STEM)
+endif
+
 ifneq ($(strip $(LOCAL_MODULE)),)
 $(error $(LOCAL_PATH): Package modules may not define LOCAL_MODULE)
 endif
diff --git a/core/product.mk b/core/product.mk
index 683c429..31b1beb 100644
--- a/core/product.mk
+++ b/core/product.mk
@@ -232,9 +232,15 @@
 _product_single_value_vars += PRODUCT_SUPPORTS_VERITY
 _product_single_value_vars += PRODUCT_SUPPORTS_VERITY_FEC
 _product_list_vars += PRODUCT_SYSTEM_SERVER_APPS
+# List of system_server classpath jars on the platform.
 _product_list_vars += PRODUCT_SYSTEM_SERVER_JARS
-# List of system_server jars delivered via apex. Format = <apex name>:<jar name>.
+# List of system_server classpath jars delivered via apex. Format = <apex name>:<jar name>.
 _product_list_vars += PRODUCT_APEX_SYSTEM_SERVER_JARS
+# List of jars on the platform that system_server loads dynamically using separate classloaders.
+_product_list_vars += PRODUCT_STANDALONE_SYSTEM_SERVER_JARS
+# List of jars delivered via apex that system_server loads dynamically using separate classloaders.
+# Format = <apex name>:<jar name>
+_product_list_vars += PRODUCT_APEX_STANDALONE_SYSTEM_SERVER_JARS
 # If true, then suboptimal order of system server jars does not cause an error.
 _product_single_value_vars += PRODUCT_BROKEN_SUBOPTIMAL_ORDER_OF_SYSTEM_SERVER_JARS
 # If true, then system server jars defined in Android.mk are supported.
@@ -275,10 +281,10 @@
 _product_single_value_vars += PRODUCT_DEX_PREOPT_RESOLVE_STARTUP_STRINGS
 
 # Boot image options.
+_product_list_vars += PRODUCT_DEX_PREOPT_BOOT_IMAGE_PROFILE_LOCATION
 _product_single_value_vars += \
     PRODUCT_EXPORT_BOOT_IMAGE_TO_DIST \
     PRODUCT_USE_PROFILE_FOR_BOOT_IMAGE \
-    PRODUCT_DEX_PREOPT_BOOT_IMAGE_PROFILE_LOCATION \
     PRODUCT_USES_DEFAULT_ART_CONFIG \
 
 _product_single_value_vars += PRODUCT_SYSTEM_SERVER_COMPILER_FILTER
diff --git a/core/product_config.mk b/core/product_config.mk
index 6588b8e..0e969fe 100644
--- a/core/product_config.mk
+++ b/core/product_config.mk
@@ -206,12 +206,18 @@
 ifndef RBC_PRODUCT_CONFIG
 $(call import-products, $(current_product_makefile))
 else
-  $(shell build/soong/scripts/rbc-run $(current_product_makefile) \
-      >$(OUT_DIR)/rbctemp.mk)
+  $(shell mkdir -p $(OUT_DIR)/rbc)
+  $(call dump-variables-rbc, $(OUT_DIR)/rbc/make_vars_pre_product_config.mk)
+
+  $(shell build/soong/scripts/update_out \
+    $(OUT_DIR)/rbc/rbc_product_config_results.mk \
+    build/soong/scripts/rbc-run \
+    $(current_product_makefile) \
+    $(OUT_DIR)/rbc/make_vars_pre_product_config.mk)
   ifneq ($(.SHELLSTATUS),0)
     $(error product configuration converter failed: $(.SHELLSTATUS))
   endif
-  include $(OUT_DIR)/rbctemp.mk
+  include $(OUT_DIR)/rbc/rbc_product_config_results.mk
   PRODUCTS += $(current_product_makefile)
 endif
 endif  # Import all or just the current product makefile
@@ -320,6 +326,16 @@
 
 PRODUCT_SYSTEM_SERVER_JARS := $(call qualify-platform-jars,$(PRODUCT_SYSTEM_SERVER_JARS))
 
+# Sort APEX boot and system server jars. We use deterministic alphabetical order
+# when constructing BOOTCLASSPATH and SYSTEMSERVERCLASSPATH definition on device
+# after an update. Enforce it in the build system as well to avoid recompiling
+# everything after an update due a change in the order.
+PRODUCT_APEX_BOOT_JARS := $(sort $(PRODUCT_APEX_BOOT_JARS))
+PRODUCT_APEX_SYSTEM_SERVER_JARS := $(sort $(PRODUCT_APEX_SYSTEM_SERVER_JARS))
+
+PRODUCT_STANDALONE_SYSTEM_SERVER_JARS := \
+  $(call qualify-platform-jars,$(PRODUCT_STANDALONE_SYSTEM_SERVER_JARS))
+
 ifndef PRODUCT_SYSTEM_NAME
   PRODUCT_SYSTEM_NAME := $(PRODUCT_NAME)
 endif
diff --git a/core/product_config.rbc b/core/product_config.rbc
index 24e38b1..45eca9f 100644
--- a/core/product_config.rbc
+++ b/core/product_config.rbc
@@ -12,26 +12,27 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-load("//build/make/core:envsetup.rbc", _envsetup_init = "init")
-
 """Runtime functions."""
 
 _soong_config_namespaces_key = "$SOONG_CONFIG_NAMESPACES"
-def _init_globals(version_info):
-    """Returns dict created from the runtime environment."""
-    globals = dict()
+_dist_for_goals_key = "$dist_for_goals"
+def _init_globals(input_variables_init):
+    """Initializes dictionaries of global variables.
 
-    # Environment variables
-    for k in dir(rblf_env):
-        globals[k] = getattr(rblf_env, k)
+    This function runs the given input_variables_init function,
+    passing it a globals dictionary and a handle as if it
+    were a regular product. It then returns 2 copies of
+    the globals dictionary, so that one can be kept around
+    to diff changes made to the other later.
+    """
+    globals_base = {"PRODUCT_SOONG_NAMESPACES": []}
+    input_variables_init(globals_base, __h_new())
 
-    # Variables set as var=value command line arguments
-    for k in dir(rblf_cli):
-        globals[k] = getattr(rblf_cli, k)
-
-    globals.setdefault("PRODUCT_SOONG_NAMESPACES", [])
-    globals.setdefault(_soong_config_namespaces_key, {})
-    _envsetup_init(globals, version_info)
+    # Rerun input_variables_init to produce a copy
+    # of globals_base, because starlark doesn't support
+    # deep copying objects.
+    globals = {"PRODUCT_SOONG_NAMESPACES": []}
+    input_variables_init(globals, __h_new())
 
     # Variables that should be defined.
     mandatory_vars = [
@@ -39,15 +40,14 @@
         "PLATFORM_VERSION",
         "PRODUCT_SOONG_NAMESPACES",
         # TODO(asmundak): do we need TARGET_ARCH? AOSP does not reference it
-        "TARGET_BUILD_TYPE",
         "TARGET_BUILD_VARIANT",
         "TARGET_PRODUCT",
     ]
     for bv in mandatory_vars:
         if not bv in globals:
             fail(bv, " is not defined")
-    return globals
 
+    return (globals, globals_base)
 
 def __print_attr(attr, value):
     # Allow using empty strings to clear variables, but not None values
@@ -91,6 +91,18 @@
                         __print_attr("SOONG_CONFIG_%s_%s" % (nsname, var), val)
                     else:
                         print("SOONG_CONFIG_%s_%s :=" % (nsname, var))
+        elif attr == _dist_for_goals_key:
+            goals = []
+            src_dst_list = []
+            goal_dst_list = []
+            for goal_name, goal_src_dst_list in sorted(val.items()):
+                goals.append(goal_name)
+                for sd in sorted(goal_src_dst_list):
+                    src_dst_list.append(":".join(sd))
+                    goal_dst_list.append(":".join((goal_name, sd[1])))
+            print("_all_dist_goal_output_pairs:=", " ".join(goal_dst_list))
+            print("_all_dist_goals:=", " ".join(goals))
+            print("_all_dist_src_dst_pairs:=", " ".join(src_dst_list))
         elif attr not in globals_base or globals_base[attr] != val:
             __print_attr(attr, val)
 
@@ -99,7 +111,7 @@
     seen = {item: 0 for item in value_list}
     return sorted(seen.keys()) if _options.rearrange == "sort" else seen.keys()
 
-def _product_configuration(top_pcm_name, top_pcm, version_info):
+def _product_configuration(top_pcm_name, top_pcm, input_variables_init):
     """Creates configuration."""
 
     # Product configuration is created by traversing product's inheritance
@@ -113,8 +125,7 @@
     # PCM means "Product Configuration Module", i.e., a Starlark file
     # whose body consists of a single init function.
 
-    globals_base = _init_globals(version_info)
-    globals = dict(**globals_base)
+    globals, globals_base = _init_globals(input_variables_init)
 
     config_postfix = []  # Configs in postfix order
 
@@ -208,13 +219,23 @@
     return (globals, configs[top_pcm_name][1], globals_base)
 
 
+def _dictionary_difference(a, b):
+    result = {}
+    for attr, val in a.items():
+        if attr not in b or b[attr] != val:
+            result[attr] = val
+    return result
+
 def _board_configuration(board_config_init, input_variables_init):
+    globals_base = {}
+    h_base = __h_new()
     globals = {}
     h = __h_new()
+
+    input_variables_init(globals_base, h_base)
     input_variables_init(globals, h)
-    globals_base = dict(**globals)
     board_config_init(globals, h)
-    return (globals, h[1], globals_base)
+    return (globals, _dictionary_difference(h[0], h_base[0]), globals_base)
 
 
 def _substitute_inherited(configs, pcm_name, cfg):
@@ -299,10 +320,11 @@
 def _soong_config_namespace(g, nsname):
     """Adds given namespace if it does not exist."""
 
-    if g[_soong_config_namespaces_key].get(nsname):
+    old = g.get(_soong_config_namespaces_key, {})
+    if old.get(nsname):
         return
+
     # A value cannot be updated, so we need to create a new dictionary
-    old = g[_soong_config_namespaces_key]
     g[_soong_config_namespaces_key] = dict([(k,v) for k,v in old.items()] + [(nsname, {})])
 
 def _soong_config_set(g, nsname, var, value):
@@ -361,7 +383,7 @@
 def __words(string_or_list):
     if type(string_or_list) == "list":
         return string_or_list
-    return string_or_list.split()
+    return _mkstrip(string_or_list).split()
 
 # Handle manipulation functions.
 # A handle passed to a PCM consists of:
@@ -485,6 +507,13 @@
             res.append(w)
     return res
 
+def _notdir(paths):
+    """Equivalent to the GNU make function $(notdir).
+
+    Returns the name of the file at the end of each path in paths.
+    """
+    return " ".join([__base(w) for w in __words(paths)])
+
 def __mk2regex(words):
     """Returns regular expression equivalent to Make pattern."""
 
@@ -506,6 +535,21 @@
     """Expands shell wildcard pattern."""
     return rblf_wildcard(pattern)
 
+def _mkdist_for_goals(g, goal, src_dst_list):
+    """Implements dist-for-goals macro."""
+    goals_map = g.get(_dist_for_goals_key, {})
+    pairs = goals_map.get(goal)
+    if pairs == None:
+        pairs = []
+        g[_dist_for_goals_key] = dict([(k,v) for k,v in goals_map.items()] + [(goal, pairs)])
+    for src_dst in __words(src_dst_list):
+        pair=src_dst.split(":")
+        if len(pair) > 2:
+            fail(src_dst + " should be a :-separated pair")
+        pairs.append((pair[0],pair[1] if len(pair) == 2 and pair[1] else __base(pair[0])))
+    g[_dist_for_goals_key][goal] = pairs
+
+
 def _mkerror(file, message = ""):
     """Prints error and stops."""
     fail("%s: %s. Stop" % (file, message))
@@ -514,6 +558,18 @@
     """Prints warning."""
     rblf_log(file, "warning", message, sep = ':')
 
+def _mk2rbc_error(loc, message):
+    """Prints a message about conversion error and stops.
+
+    If RBC_MK2RBC_CONTINUE environment variable is set,
+    the execution will continue after the message is printed.
+    """
+    if _options.mk2rbc_continue:
+        rblf_log(loc, message, sep = ':')
+    else:
+        _mkerror(loc, message)
+
+
 def _mkinfo(file, message = ""):
     """Prints info."""
     rblf_log(message)
@@ -624,6 +680,7 @@
         rearrange = "",
         trace_modules = False,
         trace_variables = [],
+        mk2rbc_continue = False,
     )
     for x in getattr(rblf_cli, "RBC_OUT", "").split(","):
         if x == "sort" or x == "unique":
@@ -641,6 +698,8 @@
             settings["trace_modules"] = True
         elif x != "":
             settings["trace_variables"].append(x)
+    if getattr(rblf_cli, "RBC_MK2RBC_CONTINUE", ""):
+        settings["mk2rbc_continue"] = True
     return struct(**settings)
 
 # Settings used during debugging.
@@ -665,15 +724,17 @@
     filter = _filter,
     filter_out = _filter_out,
     find_and_copy = _find_and_copy,
-    init_globals = _init_globals,
     inherit = _inherit,
     indirect = _indirect,
+    mk2rbc_error = _mk2rbc_error,
+    mkdist_for_goals = _mkdist_for_goals,
     mkinfo = _mkinfo,
     mkerror = _mkerror,
     mkpatsubst = _mkpatsubst,
     mkwarning = _mkwarning,
     mkstrip = _mkstrip,
     mksubst = _mksubst,
+    notdir = _notdir,
     printvars = _printvars,
     printglobals = _printglobals,
     product_configuration = _product_configuration,
diff --git a/core/soong_android_app_set.mk b/core/soong_android_app_set.mk
index f994165..ec3d8c8 100644
--- a/core/soong_android_app_set.mk
+++ b/core/soong_android_app_set.mk
@@ -6,9 +6,8 @@
   $(call pretty-error,soong_apk_set.mk may only be used from Soong)
 endif
 
-LOCAL_BUILT_MODULE_STEM := $(LOCAL_APK_SET_INSTALL_FILE)
-LOCAL_INSTALLED_MODULE_STEM := $(LOCAL_APK_SET_INSTALL_FILE)
-
+LOCAL_BUILT_MODULE_STEM := package.apk
+LOCAL_INSTALLED_MODULE_STEM := $(notdir $(LOCAL_PREBUILT_MODULE_FILE))
 
 # Use the Soong output as the checkbuild target instead of LOCAL_BUILT_MODULE
 # to avoid checkbuilds making an extra copy of every module.
@@ -18,22 +17,8 @@
 include $(BUILD_SYSTEM)/base_rules.mk
 #######################################
 
-## Extract master APK from APK set into given directory
-# $(1) APK set
-# $(2) APK entry to install (e.g., splits/base.apk
+$(eval $(call copy-one-file,$(LOCAL_PREBUILT_MODULE_FILE),$(LOCAL_BUILT_MODULE)))
 
-define extract-install-file-from-apk-set
-$(LOCAL_BUILT_MODULE): $(1)
-	@echo "Extracting $$@"
-	unzip -pq $$< $(2) >$$@
-endef
-
-$(eval $(call extract-install-file-from-apk-set,$(LOCAL_PREBUILT_MODULE_FILE),$(LOCAL_APK_SET_INSTALL_FILE)))
-# unzip returns 11 it there was nothing to extract, which is expected,
-# $(LOCAL_APK_SET_INSTALL_FILE) has is already there.
-LOCAL_POST_INSTALL_CMD := unzip -qoDD -j -d $(dir $(LOCAL_INSTALLED_MODULE)) \
-	$(LOCAL_PREBUILT_MODULE_FILE) -x $(LOCAL_APK_SET_INSTALL_FILE) || [[ $$? -eq 11 ]]
-$(LOCAL_INSTALLED_MODULE): PRIVATE_POST_INSTALL_CMD := $(LOCAL_POST_INSTALL_CMD)
 PACKAGES.$(LOCAL_MODULE).OVERRIDES := $(strip $(LOCAL_OVERRIDES_PACKAGES))
 
 PACKAGES := $(PACKAGES) $(LOCAL_MODULE)
diff --git a/core/soong_app_prebuilt.mk b/core/soong_app_prebuilt.mk
index dcb5a2e..006e1dc 100644
--- a/core/soong_app_prebuilt.mk
+++ b/core/soong_app_prebuilt.mk
@@ -258,8 +258,3 @@
 endif
 
 SOONG_ALREADY_CONV += $(LOCAL_MODULE)
-
-#######################################
-# Capture deps added after base_rules.mk
-include $(BUILD_NOTICE_FILE)
-#######################################
diff --git a/core/soong_cc_prebuilt.mk b/core/soong_cc_rust_prebuilt.mk
similarity index 89%
rename from core/soong_cc_prebuilt.mk
rename to core/soong_cc_rust_prebuilt.mk
index 49345e2..ca52374 100644
--- a/core/soong_cc_prebuilt.mk
+++ b/core/soong_cc_rust_prebuilt.mk
@@ -6,7 +6,7 @@
 # LOCAL_SOONG_VNDK_VERSION : means the version of VNDK where this module belongs
 
 ifneq ($(LOCAL_MODULE_MAKEFILE),$(SOONG_ANDROID_MK))
-  $(call pretty-error,soong_cc_prebuilt.mk may only be used from Soong)
+  $(call pretty-error,soong_cc_rust_prebuilt.mk may only be used from Soong)
 endif
 
 ifdef LOCAL_IS_HOST_MODULE
@@ -31,9 +31,9 @@
   $(call pretty-error,Unsupported LOCAL_MODULE_$(my_prefix)ARCH=$(LOCAL_MODULE_$(my_prefix)ARCH))
 endif
 
-# Don't install static libraries by default.
+# Don't install static/rlib/proc_macro libraries.
 ifndef LOCAL_UNINSTALLABLE_MODULE
-  ifeq (STATIC_LIBRARIES,$(LOCAL_MODULE_CLASS))
+  ifneq ($(filter STATIC_LIBRARIES RLIB_LIBRARIES PROC_MACRO_LIBRARIES,$(LOCAL_MODULE_CLASS)),)
     LOCAL_UNINSTALLABLE_MODULE := true
   endif
 endif
@@ -54,7 +54,7 @@
 include $(BUILD_SYSTEM)/base_rules.mk
 #######################################
 
-ifneq ($(filter STATIC_LIBRARIES SHARED_LIBRARIES HEADER_LIBRARIES,$(LOCAL_MODULE_CLASS)),)
+ifneq ($(filter STATIC_LIBRARIES SHARED_LIBRARIES RLIB_LIBRARIES DYLIB_LIBRARIES HEADER_LIBRARIES,$(LOCAL_MODULE_CLASS)),)
   # Soong module is a static or shared library
   EXPORTS_LIST += $(intermediates)
   EXPORTS.$(intermediates).FLAGS := $(LOCAL_EXPORT_CFLAGS)
@@ -113,6 +113,16 @@
     $(LOCAL_2ND_ARCH_VAR_PREFIX)$(my_prefix)DEPENDENCIES_ON_SHARED_LIBRARIES += \
       $(my_register_name):$(LOCAL_INSTALLED_MODULE):$(subst $(space),$(comma),$(my_shared_libraries))
   endif
+  ifdef LOCAL_DYLIB_LIBRARIES
+    my_dylibs := $(LOCAL_DYLIB_LIBRARIES)
+    # Treat these as shared library dependencies for installation purposes.
+    ifdef LOCAL_USE_VNDK
+      my_dylibs := $(foreach l,$(my_dylibs),\
+        $(if $(SPLIT_VENDOR.SHARED_LIBRARIES.$(l)),$(l).vendor,$(l)))
+    endif
+    $(LOCAL_2ND_ARCH_VAR_PREFIX)$(my_prefix)DEPENDENCIES_ON_SHARED_LIBRARIES += \
+      $(my_register_name):$(LOCAL_INSTALLED_MODULE):$(subst $(space),$(comma),$(my_dylibs))
+  endif
 endif
 
 my_check_same_vndk_variants :=
@@ -233,9 +243,9 @@
 
 $(LOCAL_BUILT_MODULE): $(LOCAL_ADDITIONAL_DEPENDENCIES)
 
-# We don't care about installed static libraries, since the libraries have
+# We don't care about installed rlib/static libraries, since the libraries have
 # already been linked into the module at that point. We do, however, care
-# about the NOTICE files for any static libraries that we use.
+# about the NOTICE files for any rlib/static libraries that we use.
 # (see notice_files.mk)
 #
 # Filter out some NDK libraries that are not being exported.
@@ -247,6 +257,9 @@
 installed_static_library_notice_file_targets := \
     $(foreach lib,$(my_static_libraries) $(LOCAL_WHOLE_STATIC_LIBRARIES), \
       NOTICE-$(if $(LOCAL_IS_HOST_MODULE),HOST$(if $(my_host_cross),_CROSS,),TARGET)-STATIC_LIBRARIES-$(lib))
+installed_static_library_notice_file_targets += \
+    $(foreach lib,$(LOCAL_RLIB_LIBRARIES), \
+      NOTICE-$(if $(LOCAL_IS_HOST_MODULE),HOST$(if $(my_host_cross),_CROSS,),TARGET)-RLIB_LIBRARIES-$(lib))
 
 $(notice_target): | $(installed_static_library_notice_file_targets)
 $(LOCAL_INSTALLED_MODULE): | $(notice_target)
diff --git a/core/soong_config.mk b/core/soong_config.mk
index 4999464..a48b4d8 100644
--- a/core/soong_config.mk
+++ b/core/soong_config.mk
@@ -37,7 +37,7 @@
 
 $(call add_json_bool, Allow_missing_dependencies,        $(filter true,$(ALLOW_MISSING_DEPENDENCIES)))
 $(call add_json_bool, Unbundled_build,                   $(TARGET_BUILD_UNBUNDLED))
-$(call add_json_bool, Unbundled_build_apps,              $(TARGET_BUILD_APPS))
+$(call add_json_list, Unbundled_build_apps,              $(TARGET_BUILD_APPS))
 $(call add_json_bool, Unbundled_build_image,             $(TARGET_BUILD_UNBUNDLED_IMAGE))
 $(call add_json_bool, Always_use_prebuilt_sdks,          $(TARGET_BUILD_USE_PREBUILT_SDKS))
 
@@ -271,6 +271,8 @@
 $(call add_json_list, SepolicyFreezeTestExtraDirs,         $(SEPOLICY_FREEZE_TEST_EXTRA_DIRS))
 $(call add_json_list, SepolicyFreezeTestExtraPrebuiltDirs, $(SEPOLICY_FREEZE_TEST_EXTRA_PREBUILT_DIRS))
 
+$(call add_json_bool, GenerateAidlNdkPlatformBackend, $(filter true,$(NEED_AIDL_NDK_PLATFORM_BACKEND)))
+
 $(call json_end)
 
 $(file >$(SOONG_VARIABLES).tmp,$(json_contents))
diff --git a/core/soong_java_prebuilt.mk b/core/soong_java_prebuilt.mk
index 801a265..b819cdc 100644
--- a/core/soong_java_prebuilt.mk
+++ b/core/soong_java_prebuilt.mk
@@ -209,8 +209,3 @@
 		$(hide) touch $@)
 
 SOONG_ALREADY_CONV += $(LOCAL_MODULE)
-
-#######################################
-# Capture deps added after base_rules.mk
-include $(BUILD_NOTICE_FILE)
-#######################################
diff --git a/core/soong_rust_prebuilt.mk b/core/soong_rust_prebuilt.mk
deleted file mode 100644
index 435a7d8..0000000
--- a/core/soong_rust_prebuilt.mk
+++ /dev/null
@@ -1,187 +0,0 @@
-# Native prebuilt coming from Soong.
-# Extra inputs:
-# LOCAL_SOONG_UNSTRIPPED_BINARY
-
-ifneq ($(LOCAL_MODULE_MAKEFILE),$(SOONG_ANDROID_MK))
-  $(call pretty-error,soong_rust_prebuilt.mk may only be used from Soong)
-endif
-
-ifdef LOCAL_IS_HOST_MODULE
-  ifneq ($(HOST_OS),$(LOCAL_MODULE_HOST_OS))
-    my_prefix := HOST_CROSS_
-    LOCAL_HOST_PREFIX := $(my_prefix)
-  else
-    my_prefix := HOST_
-    LOCAL_HOST_PREFIX :=
-  endif
-else
-  my_prefix := TARGET_
-endif
-
-ifeq ($($(my_prefix)ARCH),$(LOCAL_MODULE_$(my_prefix)ARCH))
-  # primary arch
-  LOCAL_2ND_ARCH_VAR_PREFIX :=
-else ifeq ($($(my_prefix)2ND_ARCH),$(LOCAL_MODULE_$(my_prefix)ARCH))
-  # secondary arch
-  LOCAL_2ND_ARCH_VAR_PREFIX := $($(my_prefix)2ND_ARCH_VAR_PREFIX)
-else
-  $(call pretty-error,Unsupported LOCAL_MODULE_$(my_prefix)ARCH=$(LOCAL_MODULE_$(my_prefix)ARCH))
-endif
-
-# Don't install static/rlib/proc_macro libraries.
-ifndef LOCAL_UNINSTALLABLE_MODULE
-  ifneq ($(filter STATIC_LIBRARIES RLIB_LIBRARIES PROC_MACRO_LIBRARIES,$(LOCAL_MODULE_CLASS)),)
-    LOCAL_UNINSTALLABLE_MODULE := true
-  endif
-endif
-
-# Use the Soong output as the checkbuild target instead of LOCAL_BUILT_MODULE
-# to avoid checkbuilds making an extra copy of every module.
-LOCAL_CHECKED_MODULE := $(LOCAL_PREBUILT_MODULE_FILE)
-
-#######################################
-include $(BUILD_SYSTEM)/base_rules.mk
-#######################################
-
-ifneq ($(filter STATIC_LIBRARIES SHARED_LIBRARIES RLIB_LIBRARIES DYLIB_LIBRARIES,$(LOCAL_MODULE_CLASS)),)
-  # Soong module is a static or shared library
-  EXPORTS_LIST += $(intermediates)
-  EXPORTS.$(intermediates).FLAGS := $(LOCAL_EXPORT_CFLAGS)
-  EXPORTS.$(intermediates).DEPS := $(LOCAL_EXPORT_C_INCLUDE_DEPS)
-
-  SOONG_ALREADY_CONV += $(LOCAL_MODULE)
-
-  my_link_type := $(LOCAL_SOONG_LINK_TYPE)
-  my_warn_types :=
-  my_allowed_types :=
-  my_link_deps :=
-  my_2nd_arch_prefix := $(LOCAL_2ND_ARCH_VAR_PREFIX)
-  my_common :=
-  include $(BUILD_SYSTEM)/link_type.mk
-endif
-
-
-ifdef LOCAL_USE_VNDK
-  ifneq ($(LOCAL_VNDK_DEPEND_ON_CORE_VARIANT),true)
-    name_without_suffix := $(patsubst %.vendor,%,$(LOCAL_MODULE))
-    ifneq ($(name_without_suffix),$(LOCAL_MODULE))
-      SPLIT_VENDOR.$(LOCAL_MODULE_CLASS).$(name_without_suffix) := 1
-    else
-      name_without_suffix := $(patsubst %.product,%,$(LOCAL_MODULE))
-      ifneq ($(name_without_suffix),$(LOCAL_MODULE))
-        SPLIT_PRODUCT.$(LOCAL_MODULE_CLASS).$(name_without_suffix) := 1
-      endif
-    endif
-    name_without_suffix :=
-  endif
-endif
-
-# The real dependency will be added after all Android.mks are loaded and the install paths
-# of the shared libraries are determined.
-ifdef LOCAL_INSTALLED_MODULE
-  ifdef LOCAL_SHARED_LIBRARIES
-    my_shared_libraries := $(LOCAL_SHARED_LIBRARIES)
-    ifdef LOCAL_USE_VNDK
-      my_shared_libraries := $(foreach l,$(my_shared_libraries),\
-        $(if $(SPLIT_VENDOR.SHARED_LIBRARIES.$(l)),$(l).vendor,$(l)))
-    endif
-    $(LOCAL_2ND_ARCH_VAR_PREFIX)$(my_prefix)DEPENDENCIES_ON_SHARED_LIBRARIES += \
-      $(my_register_name):$(LOCAL_INSTALLED_MODULE):$(subst $(space),$(comma),$(my_shared_libraries))
-  endif
-  ifdef LOCAL_DYLIB_LIBRARIES
-    my_dylibs := $(LOCAL_DYLIB_LIBRARIES)
-    # Treat these as shared library dependencies for installation purposes.
-    ifdef LOCAL_USE_VNDK
-      my_dylibs := $(foreach l,$(my_dylibs),\
-        $(if $(SPLIT_VENDOR.SHARED_LIBRARIES.$(l)),$(l).vendor,$(l)))
-    endif
-    $(LOCAL_2ND_ARCH_VAR_PREFIX)$(my_prefix)DEPENDENCIES_ON_SHARED_LIBRARIES += \
-      $(my_register_name):$(LOCAL_INSTALLED_MODULE):$(subst $(space),$(comma),$(my_dylibs))
-  endif
-endif
-
-$(LOCAL_BUILT_MODULE): $(LOCAL_PREBUILT_MODULE_FILE)
-ifeq ($(LOCAL_IS_HOST_MODULE) $(if $(filter EXECUTABLES SHARED_LIBRARIES NATIVE_TESTS,$(LOCAL_MODULE_CLASS)),true,),true true)
-	$(copy-or-link-prebuilt-to-target)
-  ifneq ($(filter EXECUTABLES NATIVE_TESTS,$(LOCAL_MODULE_CLASS)),)
-	[ -x $@ ] || ( $(call echo-error,$@,Target of symlink is not executable); false )
-  endif
-else
-	$(transform-prebuilt-to-target)
-  ifneq ($(filter EXECUTABLES NATIVE_TESTS,$(LOCAL_MODULE_CLASS)),)
-	$(hide) chmod +x $@
-  endif
-endif
-
-ifndef LOCAL_IS_HOST_MODULE
-  ifdef LOCAL_SOONG_UNSTRIPPED_BINARY
-    my_symbol_path := $(if $(LOCAL_SOONG_SYMBOL_PATH),$(LOCAL_SOONG_SYMBOL_PATH),$(my_module_path))
-    # Store a copy with symbols for symbolic debugging
-    my_unstripped_path := $(TARGET_OUT_UNSTRIPPED)/$(patsubst $(PRODUCT_OUT)/%,%,$(my_symbol_path))
-    # drop /root as /root is mounted as /
-    my_unstripped_path := $(patsubst $(TARGET_OUT_UNSTRIPPED)/root/%,$(TARGET_OUT_UNSTRIPPED)/%, $(my_unstripped_path))
-    symbolic_output := $(my_unstripped_path)/$(my_installed_module_stem)
-    $(eval $(call copy-one-file,$(LOCAL_SOONG_UNSTRIPPED_BINARY),$(symbolic_output)))
-    $(LOCAL_BUILT_MODULE): | $(symbolic_output)
-  endif
-endif
-
-create_coverage_zip :=
-
-ifeq ($(NATIVE_COVERAGE),true)
-   create_coverage_zip := true
-endif
-
-# Until Rust supports LLVM coverage, Soong assumes GCOV coverage in both cases.
-# Therefore we should create the coverage zip with the gcno files in this case as well.
-ifeq ($(CLANG_COVERAGE),true)
-   create_coverage_zip := true
-endif
-
-ifdef create_coverage_zip
-  ifneq (,$(strip $(LOCAL_PREBUILT_COVERAGE_ARCHIVE)))
-    $(eval $(call copy-one-file,$(LOCAL_PREBUILT_COVERAGE_ARCHIVE),$(intermediates)/$(LOCAL_MODULE).zip))
-    ifneq ($(LOCAL_UNINSTALLABLE_MODULE),true)
-      ifdef LOCAL_IS_HOST_MODULE
-        my_coverage_path := $($(my_prefix)OUT_COVERAGE)/$(patsubst $($(my_prefix)OUT)/%,%,$(my_module_path))
-      else
-        my_coverage_path := $(TARGET_OUT_COVERAGE)/$(patsubst $(PRODUCT_OUT)/%,%,$(my_module_path))
-      endif
-      my_coverage_path := $(my_coverage_path)/$(patsubst %.so,%,$(my_installed_module_stem)).zip
-      $(eval $(call copy-one-file,$(LOCAL_PREBUILT_COVERAGE_ARCHIVE),$(my_coverage_path)))
-      $(LOCAL_BUILT_MODULE): $(my_coverage_path)
-    endif
-  endif
-endif
-
-# A product may be configured to strip everything in some build variants.
-# We do the stripping as a post-install command so that LOCAL_BUILT_MODULE
-# is still with the symbols and we don't need to clean it (and relink) when
-# you switch build variant.
-ifneq ($(filter $(STRIP_EVERYTHING_BUILD_VARIANTS),$(TARGET_BUILD_VARIANT)),)
-$(LOCAL_INSTALLED_MODULE): PRIVATE_POST_INSTALL_CMD := \
-  $($(LOCAL_2ND_ARCH_VAR_PREFIX)TARGET_STRIP) --strip-all $(LOCAL_INSTALLED_MODULE)
-endif
-
-$(LOCAL_BUILT_MODULE): $(LOCAL_ADDITIONAL_DEPENDENCIES)
-
-# We don't care about installed rlib/static libraries, since the libraries have
-# already been linked into the module at that point. We do, however, care
-# about the NOTICE files for any rlib/static libraries that we use.
-# (see notice_files.mk)
-#
-# Filter out some NDK libraries that are not being exported.
-my_static_libraries := \
-    $(filter-out ndk_libc++_static ndk_libc++abi ndk_libandroid_support ndk_libunwind \
-      ndk_libc++_static.native_bridge ndk_libc++abi.native_bridge \
-      ndk_libandroid_support.native_bridge ndk_libunwind.native_bridge, \
-      $(LOCAL_STATIC_LIBRARIES))
-installed_static_library_notice_file_targets := \
-    $(foreach lib,$(my_static_libraries), \
-      NOTICE-$(if $(LOCAL_IS_HOST_MODULE),HOST$(if $(my_host_cross),_CROSS,),TARGET)-STATIC_LIBRARIES-$(lib))
-installed_static_library_notice_file_targets += \
-    $(foreach lib,$(LOCAL_RLIB_LIBRARIES), \
-      NOTICE-$(if $(LOCAL_IS_HOST_MODULE),HOST$(if $(my_host_cross),_CROSS,),TARGET)-RLIB_LIBRARIES-$(lib))
-
-$(notice_target): | $(installed_static_library_notice_file_targets)
-$(LOCAL_INSTALLED_MODULE): | $(notice_target)
diff --git a/envsetup.sh b/envsetup.sh
index 1687309..a23bbad 100644
--- a/envsetup.sh
+++ b/envsetup.sh
@@ -252,9 +252,7 @@
     esac
 
     ANDROID_BUILD_PATHS=$(get_build_var ANDROID_BUILD_PATHS):$ANDROID_TOOLCHAIN
-    if [ -n "$ANDROID_TOOLCHAIN_2ND_ARCH" ]; then
-        ANDROID_BUILD_PATHS=$ANDROID_BUILD_PATHS:$ANDROID_TOOLCHAIN_2ND_ARCH
-    fi
+    ANDROID_BUILD_PATHS=$ANDROID_BUILD_PATHS:$ANDROID_TOOLCHAIN_2ND_ARCH
     ANDROID_BUILD_PATHS=$ANDROID_BUILD_PATHS:$ANDROID_DEV_SCRIPTS
 
     # Append llvm binutils prebuilts path to ANDROID_BUILD_PATHS.
@@ -287,8 +285,9 @@
     local ACLOUD_PATH="$T/prebuilts/asuite/acloud/$os_arch"
     local AIDEGEN_PATH="$T/prebuilts/asuite/aidegen/$os_arch"
     local ATEST_PATH="$T/prebuilts/asuite/atest/$os_arch"
-    export ANDROID_BUILD_PATHS=$ANDROID_BUILD_PATHS:$ACLOUD_PATH:$AIDEGEN_PATH:$ATEST_PATH:
+    ANDROID_BUILD_PATHS=$ANDROID_BUILD_PATHS:$ACLOUD_PATH:$AIDEGEN_PATH:$ATEST_PATH
 
+    export ANDROID_BUILD_PATHS=$(tr -s : <<<"${ANDROID_BUILD_PATHS}:")
     export PATH=$ANDROID_BUILD_PATHS$PATH
 
     # out with the duplicate old
diff --git a/target/product/base_system.mk b/target/product/base_system.mk
index 68dd980..a995b8b 100644
--- a/target/product/base_system.mk
+++ b/target/product/base_system.mk
@@ -79,6 +79,7 @@
     dnsmasq \
     DownloadProvider \
     dpm \
+    dump.erofs \
     dumpstate \
     dumpsys \
     DynamicSystemInstallationService \
@@ -89,6 +90,7 @@
     framework-minus-apex \
     framework-res \
     framework-sysconfig.xml \
+    fsck.erofs \
     fsck_msdos \
     fsverity-release-cert-der \
     fs_config_files_system \
@@ -213,6 +215,7 @@
     MediaProviderLegacy \
     mediaserver \
     mke2fs \
+    mkfs.erofs \
     monkey \
     mtpd \
     ndc \
@@ -318,9 +321,11 @@
     atest \
     bcc \
     bit \
+    dump.erofs \
     e2fsck \
     fastboot \
     flags_health_check \
+    fsck.erofs \
     icu-data_host_i18n_apex \
     icu_tzdata.dat_host_tzdata_apex \
     idmap2 \
@@ -329,6 +334,7 @@
     lpdump \
     minigzip \
     mke2fs \
+    mkfs.erofs \
     resize2fs \
     sgdisk \
     sqlite3 \
@@ -362,7 +368,6 @@
     adb_keys \
     arping \
     dmuserd \
-    gdbserver \
     idlcli \
     init-debug.rc \
     iotop \
diff --git a/target/product/base_vendor.mk b/target/product/base_vendor.mk
index 7622a69..5004b85 100644
--- a/target/product/base_vendor.mk
+++ b/target/product/base_vendor.mk
@@ -25,6 +25,7 @@
     linker.recovery \
     otacerts.recovery \
     recovery \
+    servicemanager.recovery \
     shell_and_utilities_recovery \
     watchdogd.recovery \
 
diff --git a/target/product/core_64_bit_only.mk b/target/product/core_64_bit_only.mk
index 53c9c74..061728f 100644
--- a/target/product/core_64_bit_only.mk
+++ b/target/product/core_64_bit_only.mk
@@ -25,6 +25,9 @@
 # Set the zygote property to select the 64-bit script.
 # This line must be parsed before the one in core_minimal.mk
 PRODUCT_VENDOR_PROPERTIES += ro.zygote=zygote64
+# A 64-bit-only platform does not have dex2oat32, so make sure dex2oat64 is
+# used for dexopt.
+PRODUCT_VENDOR_PROPERTIES += dalvik.vm.dex2oat64.enabled=true
 
 TARGET_SUPPORTS_32_BIT_APPS := false
 TARGET_SUPPORTS_64_BIT_APPS := true
diff --git a/target/product/default_art_config.mk b/target/product/default_art_config.mk
index e988d00..f98f7e2 100644
--- a/target/product/default_art_config.mk
+++ b/target/product/default_art_config.mk
@@ -65,7 +65,8 @@
     com.android.tethering:framework-tethering \
     com.android.wifi:framework-wifi
 
-# APEX system server jars. Keep the list sorted by module names and then library names.
+# List of system_server classpath jars delivered via apex.
+# Keep the list sorted by module names and then library names.
 PRODUCT_APEX_SYSTEM_SERVER_JARS := \
     com.android.appsearch:service-appsearch \
     com.android.art:service-art \
@@ -74,6 +75,18 @@
 
 PRODUCT_DEX_PREOPT_BOOT_IMAGE_PROFILE_LOCATION += art/build/boot/boot-image-profile.txt
 
+# List of jars on the platform that system_server loads dynamically using separate classloaders.
+# Keep the list sorted library names.
+PRODUCT_STANDALONE_SYSTEM_SERVER_JARS := \
+
+# List of jars delivered via apex that system_server loads dynamically using separate classloaders.
+# Keep the list sorted by module names and then library names.
+PRODUCT_APEX_STANDALONE_SYSTEM_SERVER_JARS := \
+    com.android.os.statsd:service-statsd \
+    com.android.scheduling:service-scheduling \
+    com.android.tethering:service-connectivity \
+    com.android.wifi:service-wifi \
+
 # Minimal configuration for running dex2oat (default argument values).
 # PRODUCT_USES_DEFAULT_ART_CONFIG must be true to enable boot image compilation.
 PRODUCT_USES_DEFAULT_ART_CONFIG := true
diff --git a/target/product/gsi/Android.mk b/target/product/gsi/Android.mk
index 39848e5..0d788fa 100644
--- a/target/product/gsi/Android.mk
+++ b/target/product/gsi/Android.mk
@@ -50,11 +50,21 @@
 _vndk_check_failure_message += "       Run \`update-vndk-list.sh\` to update $(LATEST_VNDK_LIB_LIST)"
 endif
 
+# The *-ndk_platform.so libraries no longer exist and are removed from the VNDK set. However, they
+# can exist if NEED_AIDL_NDK_PLATFORM_BACKEND is set to true for legacy devices. Don't be bothered
+# with the extraneous libraries.
+ifeq ($(NEED_AIDL_NDK_PLATFORM_BACKEND),true)
+	_READ_INTERNAL_VNDK_LIB_LIST := sed /ndk_platform.so/d $(INTERNAL_VNDK_LIB_LIST)
+else
+	_READ_INTERNAL_VNDK_LIB_LIST := cat $(INTERNAL_VNDK_LIB_LIST)
+endif
+
 $(check-vndk-list-timestamp): $(INTERNAL_VNDK_LIB_LIST) $(LATEST_VNDK_LIB_LIST) $(HOST_OUT_EXECUTABLES)/update-vndk-list.sh
-	$(hide) ( diff --old-line-format="Removed %L" \
+	$(hide) ($(_READ_INTERNAL_VNDK_LIB_LIST) | \
+	diff --old-line-format="Removed %L" \
 	  --new-line-format="Added %L" \
 	  --unchanged-line-format="" \
-	  $(LATEST_VNDK_LIB_LIST) $(INTERNAL_VNDK_LIB_LIST) \
+	  $(LATEST_VNDK_LIB_LIST) - \
 	  || ( echo -e $(_vndk_check_failure_message); exit 1 ))
 	$(hide) mkdir -p $(dir $@)
 	$(hide) touch $@
@@ -84,9 +94,13 @@
 	        echo "  echo Run lunch or choosecombo first" >> $@; \
 	        echo "  exit 1" >> $@; \
 	        echo "fi" >> $@; \
-	        echo "cd \$${ANDROID_BUILD_TOP}" >> $@; \
-	        echo "cp $(PRIVATE_INTERNAL_VNDK_LIB_LIST) $(PRIVATE_LATEST_VNDK_LIB_LIST)" >> $@; \
-	        echo "echo $(PRIVATE_LATEST_VNDK_LIB_LIST) updated." >> $@
+	        echo "cd \$${ANDROID_BUILD_TOP}" >> $@
+ifeq ($(NEED_AIDL_NDK_PLATFORM_BACKEND),true)
+	$(hide) echo "sed /ndk_platform.so/d $(PRIVATE_INTERNAL_VNDK_LIB_LIST) > $(PRIVATE_LATEST_VNDK_LIB_LIST)" >> $@
+else
+	$(hide) echo "cp $(PRIVATE_INTERNAL_VNDK_LIB_LIST) $(PRIVATE_LATEST_VNDK_LIB_LIST)" >> $@
+endif
+	$(hide) echo "echo $(PRIVATE_LATEST_VNDK_LIB_LIST) updated." >> $@
 endif
 	@chmod a+x $@
 
diff --git a/target/product/gsi/current.txt b/target/product/gsi/current.txt
index 399652c..52754d4 100644
--- a/target/product/gsi/current.txt
+++ b/target/product/gsi/current.txt
@@ -19,11 +19,8 @@
 LLNDK: libvndksupport.so
 LLNDK: libvulkan.so
 VNDK-SP: android.hardware.common-V2-ndk.so
-VNDK-SP: android.hardware.common-V2-ndk_platform.so
 VNDK-SP: android.hardware.common.fmq-V1-ndk.so
-VNDK-SP: android.hardware.common.fmq-V1-ndk_platform.so
 VNDK-SP: android.hardware.graphics.common-V2-ndk.so
-VNDK-SP: android.hardware.graphics.common-V2-ndk_platform.so
 VNDK-SP: android.hardware.graphics.common@1.0.so
 VNDK-SP: android.hardware.graphics.common@1.1.so
 VNDK-SP: android.hardware.graphics.common@1.2.so
@@ -61,15 +58,13 @@
 VNDK-SP: libz.so
 VNDK-core: android.hardware.audio.common@2.0.so
 VNDK-core: android.hardware.authsecret-V1-ndk.so
-VNDK-core: android.hardware.authsecret-V1-ndk_platform.so
 VNDK-core: android.hardware.automotive.occupant_awareness-V1-ndk.so
-VNDK-core: android.hardware.automotive.occupant_awareness-V1-ndk_platform.so
 VNDK-core: android.hardware.configstore-utils.so
 VNDK-core: android.hardware.configstore@1.0.so
 VNDK-core: android.hardware.configstore@1.1.so
 VNDK-core: android.hardware.confirmationui-support-lib.so
+VNDK-core: android.hardware.dumpstate-V1-ndk.so
 VNDK-core: android.hardware.gnss-V1-ndk.so
-VNDK-core: android.hardware.gnss-V1-ndk_platform.so
 VNDK-core: android.hardware.graphics.allocator@2.0.so
 VNDK-core: android.hardware.graphics.allocator@3.0.so
 VNDK-core: android.hardware.graphics.allocator@4.0.so
@@ -77,63 +72,38 @@
 VNDK-core: android.hardware.graphics.bufferqueue@2.0.so
 VNDK-core: android.hardware.health-V1-ndk.so
 VNDK-core: android.hardware.health.storage-V1-ndk.so
-VNDK-core: android.hardware.health.storage-V1-ndk_platform.so
 VNDK-core: android.hardware.identity-V3-ndk.so
-VNDK-core: android.hardware.identity-V3-ndk_platform.so
 VNDK-core: android.hardware.keymaster-V3-ndk.so
-VNDK-core: android.hardware.keymaster-V3-ndk_platform.so
 VNDK-core: android.hardware.light-V1-ndk.so
-VNDK-core: android.hardware.light-V1-ndk_platform.so
 VNDK-core: android.hardware.media.bufferpool@2.0.so
 VNDK-core: android.hardware.media.omx@1.0.so
 VNDK-core: android.hardware.media@1.0.so
 VNDK-core: android.hardware.memtrack-V1-ndk.so
-VNDK-core: android.hardware.memtrack-V1-ndk_platform.so
 VNDK-core: android.hardware.memtrack@1.0.so
 VNDK-core: android.hardware.oemlock-V1-ndk.so
-VNDK-core: android.hardware.oemlock-V1-ndk_platform.so
 VNDK-core: android.hardware.power-V2-ndk.so
-VNDK-core: android.hardware.power-V2-ndk_platform.so
 VNDK-core: android.hardware.power.stats-V1-ndk.so
-VNDK-core: android.hardware.power.stats-V1-ndk_platform.so
 VNDK-core: android.hardware.radio-V1-ndk.so
-VNDK-core: android.hardware.radio-V1-ndk_platform.so
 VNDK-core: android.hardware.radio.config-V1-ndk.so
-VNDK-core: android.hardware.radio.config-V1-ndk_platform.so
 VNDK-core: android.hardware.radio.data-V1-ndk.so
-VNDK-core: android.hardware.radio.data-V1-ndk_platform.so
 VNDK-core: android.hardware.radio.messaging-V1-ndk.so
-VNDK-core: android.hardware.radio.messaging-V1-ndk_platform.so
 VNDK-core: android.hardware.radio.modem-V1-ndk.so
-VNDK-core: android.hardware.radio.modem-V1-ndk_platform.so
 VNDK-core: android.hardware.radio.network-V1-ndk.so
-VNDK-core: android.hardware.radio.network-V1-ndk_platform.so
 VNDK-core: android.hardware.radio.sim-V1-ndk.so
-VNDK-core: android.hardware.radio.sim-V1-ndk_platform.so
 VNDK-core: android.hardware.radio.voice-V1-ndk.so
-VNDK-core: android.hardware.radio.voice-V1-ndk_platform.so
 VNDK-core: android.hardware.rebootescrow-V1-ndk.so
-VNDK-core: android.hardware.rebootescrow-V1-ndk_platform.so
 VNDK-core: android.hardware.security.keymint-V1-ndk.so
-VNDK-core: android.hardware.security.keymint-V1-ndk_platform.so
 VNDK-core: android.hardware.security.secureclock-V1-ndk.so
-VNDK-core: android.hardware.security.secureclock-V1-ndk_platform.so
 VNDK-core: android.hardware.security.sharedsecret-V1-ndk.so
-VNDK-core: android.hardware.security.sharedsecret-V1-ndk_platform.so
 VNDK-core: android.hardware.soundtrigger@2.0-core.so
 VNDK-core: android.hardware.soundtrigger@2.0.so
 VNDK-core: android.hardware.vibrator-V2-ndk.so
-VNDK-core: android.hardware.vibrator-V2-ndk_platform.so
 VNDK-core: android.hardware.weaver-V1-ndk.so
-VNDK-core: android.hardware.weaver-V1-ndk_platform.so
 VNDK-core: android.hardware.wifi.hostapd-V1-ndk.so
-VNDK-core: android.hardware.wifi.hostapd-V1-ndk_platform.so
 VNDK-core: android.hidl.token@1.0-utils.so
 VNDK-core: android.hidl.token@1.0.so
 VNDK-core: android.system.keystore2-V1-ndk.so
-VNDK-core: android.system.keystore2-V1-ndk_platform.so
 VNDK-core: android.system.suspend-V1-ndk.so
-VNDK-core: android.system.suspend-V1-ndk_platform.so
 VNDK-core: android.system.suspend@1.0.so
 VNDK-core: libaudioroute.so
 VNDK-core: libaudioutils.so
diff --git a/core/build_id.rbc b/tests/board.rbc
similarity index 65%
rename from core/build_id.rbc
rename to tests/board.rbc
index 4f33833..8696e40 100644
--- a/core/build_id.rbc
+++ b/tests/board.rbc
@@ -1,4 +1,3 @@
-
 # Copyright 2021 Google LLC
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,9 +12,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# This file has been manually converted from build_id.mk
-def init(g):
+load("//build/make/core:product_config.rbc", "rblf")
 
-    # BUILD_ID is usually used to specify the branch name (like "MAIN") or a branch name and a release candidate
-    # (like "CRB01").  It must be a single word, and is capitalized by convention.
-    g["BUILD_ID"]="AOSP.MASTER"
\ No newline at end of file
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  g["A_LIST_VARIABLE"] += ["bar"]
diff --git a/core/build_id.rbc b/tests/board_input_vars.rbc
similarity index 65%
copy from core/build_id.rbc
copy to tests/board_input_vars.rbc
index 4f33833..69d9cd6 100644
--- a/core/build_id.rbc
+++ b/tests/board_input_vars.rbc
@@ -1,4 +1,3 @@
-
 # Copyright 2021 Google LLC
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,9 +12,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# This file has been manually converted from build_id.mk
-def init(g):
+load("//build/make/core:product_config.rbc", "rblf")
 
-    # BUILD_ID is usually used to specify the branch name (like "MAIN") or a branch name and a release candidate
-    # (like "CRB01").  It must be a single word, and is capitalized by convention.
-    g["BUILD_ID"]="AOSP.MASTER"
\ No newline at end of file
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  g["A_LIST_VARIABLE"] = ["foo"]
diff --git a/tests/conversion_error.rbc b/tests/conversion_error.rbc
new file mode 100644
index 0000000..5212378
--- /dev/null
+++ b/tests/conversion_error.rbc
@@ -0,0 +1,27 @@
+# Copyright 2021 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
+#
+#      https://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.
+
+
+# Run test configuration and verify its result.
+# The main configuration file is device.rbc.
+# It inherits part1.rbc and also includes include1.rbc
+# TODO(asmundak): more tests are needed to verify that:
+#  * multi-level inheritance works as expected
+#  * all runtime functions (wildcard, regex, etc.) work
+
+load("//build/make/core:product_config.rbc", "rblf")
+load(":version_defaults.rbc", "version_defaults")
+load(":device.rbc", "init")
+
+rblf.mk2rbc_error("file.mk:123", "cannot convert")
diff --git a/tests/input_variables.rbc b/tests/input_variables.rbc
new file mode 100644
index 0000000..0bb100f
--- /dev/null
+++ b/tests/input_variables.rbc
@@ -0,0 +1,28 @@
+# Copyright 2021 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
+#
+#      https://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.
+
+# This file was generated by running `m RBC_PRODUCT_CONFIG=1 nothing`
+# and then copying it from out/rbc/out/rbc/make_vars_pre_product_config.rbc.
+# It was manually trimmed down afterwards to just the variables we need.
+
+load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  g["PLATFORM_VERSION_CODENAME"] = "Tiramisu"
+  g["PLATFORM_VERSION"] = "Tiramisu"
+  g["TARGET_BUILD_VARIANT"] = "userdebug"
+  g["TARGET_BUILD_TYPE"] = "release"
+  g["TARGET_PRODUCT"] = "aosp_arm64"
+  g["PLATFORM_SDK_VERSION"] = "31"
diff --git a/tests/device.rbc b/tests/product.rbc
similarity index 95%
rename from tests/device.rbc
rename to tests/product.rbc
index 37c5d0c..9ae6393 100644
--- a/tests/device.rbc
+++ b/tests/product.rbc
@@ -55,6 +55,9 @@
   rblf.soong_config_set(g, "NS2", "v3", "abc")
   rblf.soong_config_set(g, "NS2", "v3", "xyz")
 
+  rblf.mkdist_for_goals(g, "goal", "dir1/file1:out1 dir1/file2:out2")
+  rblf.mkdist_for_goals(g, "goal", "dir2/file2:")
+
   if rblf.board_platform_in(g, "board1 board2"):
     cfg["PRODUCT_PACKAGES"] += ["bad_package"]
   g["TARGET_BOARD_PLATFORM"] = "board1"
diff --git a/tests/run.rbc b/tests/run.rbc
index b40d1c6..53eda16 100644
--- a/tests/run.rbc
+++ b/tests/run.rbc
@@ -13,16 +13,18 @@
 # limitations under the License.
 
 
-# Run test configuration and verify its result.
-# The main configuration file is device.rbc.
+# Run test product configuration and verify its result.
+# The main configuration file is product.rbc.
 # It inherits part1.rbc and also includes include1.rbc
 # TODO(asmundak): more tests are needed to verify that:
 #  * multi-level inheritance works as expected
 #  * all runtime functions (wildcard, regex, etc.) work
 
 load("//build/make/core:product_config.rbc", "rblf")
-load(":version_defaults.rbc", "version_defaults")
-load(":device.rbc", "init")
+load(":input_variables.rbc", input_variables_init = "init")
+load(":product.rbc", "init")
+load(":board.rbc", board_init = "init")
+load(":board_input_vars.rbc", board_input_vars_init = "init")
 
 def assert_eq(expected, actual):
     if expected != actual:
@@ -52,7 +54,12 @@
 assert_eq([], rblf.filter(["a", "", "b"], "f"))
 assert_eq(["", "b"], rblf.filter_out(["a", "" ], ["a", "", "b"] ))
 
-(globals, config, globals_base) = rblf.product_configuration("test/device", init, version_defaults)
+assert_eq("foo.c no_folder", rblf.notdir(["src/foo.c", "no_folder"]))
+assert_eq("foo.c no_folder", rblf.notdir("src/foo.c no_folder"))
+assert_eq("", rblf.notdir("/"))
+assert_eq("", rblf.notdir(""))
+
+(globals, config, globals_base) = rblf.product_configuration("test/device", init, input_variables_init)
 assert_eq(
     {
       "PRODUCT_COPY_FILES": [
@@ -92,8 +99,20 @@
     {k:v for k, v in sorted(ns.items()) }
 )
 
-assert_eq("S", globals["PLATFORM_VERSION"])
-assert_eq(30, globals["PLATFORM_SDK_VERSION"])
+assert_eq("Tiramisu", globals["PLATFORM_VERSION"])
+assert_eq("31", globals["PLATFORM_SDK_VERSION"])
 
 assert_eq("xyz", rblf.soong_config_get(globals, "NS2", "v3"))
 assert_eq(None, rblf.soong_config_get(globals, "NS2", "nonexistant_var"))
+
+goals = globals["$dist_for_goals"]
+assert_eq(
+    {
+        "goal": [("dir1/file1", "out1"), ("dir1/file2", "out2"), ("dir2/file2", "file2")]
+    },
+    { k:v for k,v in sorted(goals.items()) }
+)
+
+(board_globals, board_config, board_globals_base) = rblf.board_configuration(board_init, board_input_vars_init)
+assert_eq({"A_LIST_VARIABLE": ["foo", "bar"]}, board_globals)
+assert_eq({"A_LIST_VARIABLE": ["foo"]}, board_globals_base)
diff --git a/tools/compliance/Android.bp b/tools/compliance/Android.bp
new file mode 100644
index 0000000..afb3080
--- /dev/null
+++ b/tools/compliance/Android.bp
@@ -0,0 +1,89 @@
+//
+// Copyright (C) 2021 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.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+blueprint_go_binary {
+    name: "checkshare",
+    srcs: ["cmd/checkshare.go"],
+    deps: ["compliance-module"],
+    testSrcs: ["cmd/checkshare_test.go"],
+}
+
+blueprint_go_binary {
+    name: "listshare",
+    srcs: ["cmd/listshare.go"],
+    deps: ["compliance-module"],
+    testSrcs: ["cmd/listshare_test.go"],
+}
+
+blueprint_go_binary {
+    name: "dumpgraph",
+    srcs: ["cmd/dumpgraph.go"],
+    deps: ["compliance-module"],
+    testSrcs: ["cmd/dumpgraph_test.go"],
+}
+
+blueprint_go_binary {
+    name: "dumpresolutions",
+    srcs: ["cmd/dumpresolutions.go"],
+    deps: ["compliance-module"],
+    testSrcs: ["cmd/dumpresolutions_test.go"],
+}
+
+bootstrap_go_package {
+    name: "compliance-module",
+    srcs: [
+        "actionset.go",
+        "condition.go",
+        "conditionset.go",
+        "doc.go",
+        "graph.go",
+        "policy/policy.go",
+        "policy/resolve.go",
+        "policy/resolvenotices.go",
+        "policy/resolveshare.go",
+        "policy/resolveprivacy.go",
+        "policy/shareprivacyconflicts.go",
+        "policy/shipped.go",
+        "policy/walk.go",
+        "readgraph.go",
+        "resolution.go",
+        "resolutionset.go",
+    ],
+    testSrcs: [
+        "condition_test.go",
+        "conditionset_test.go",
+        "readgraph_test.go",
+        "policy/policy_test.go",
+        "policy/resolve_test.go",
+        "policy/resolvenotices_test.go",
+        "policy/resolveshare_test.go",
+        "policy/resolveprivacy_test.go",
+        "policy/shareprivacyconflicts_test.go",
+        "policy/shipped_test.go",
+        "policy/walk_test.go",
+        "resolutionset_test.go",
+        "test_util.go",
+    ],
+    deps: [
+        "golang-protobuf-proto",
+        "golang-protobuf-encoding-prototext",
+        "license_metadata_proto",
+    ],
+    pkgPath: "compliance",
+}
diff --git a/tools/compliance/actionset.go b/tools/compliance/actionset.go
new file mode 100644
index 0000000..d575321
--- /dev/null
+++ b/tools/compliance/actionset.go
@@ -0,0 +1,110 @@
+// Copyright 2021 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 compliance
+
+import (
+	"fmt"
+	"sort"
+	"strings"
+)
+
+// actionSet maps `actOn` target nodes to the license conditions the actions resolve.
+type actionSet map[*TargetNode]*LicenseConditionSet
+
+// String returns a string representation of the set.
+func (as actionSet) String() string {
+	var sb strings.Builder
+	fmt.Fprintf(&sb, "{")
+	osep := ""
+	for actsOn, cs := range as {
+		cl := cs.AsList()
+		sort.Sort(cl)
+		fmt.Fprintf(&sb, "%s%s -> %s", osep, actsOn.name, cl.String())
+		osep = ", "
+	}
+	fmt.Fprintf(&sb, "}")
+	return sb.String()
+}
+
+// byName returns the subset of `as` actions where the condition name is in `names`.
+func (as actionSet) byName(names ConditionNames) actionSet {
+	result := make(actionSet)
+	for actsOn, cs := range as {
+		bn := cs.ByName(names)
+		if bn.IsEmpty() {
+			continue
+		}
+		result[actsOn] = bn
+	}
+	return result
+}
+
+// byActsOn returns the subset of `as` where `actsOn` is in the `reachable` target node set.
+func (as actionSet) byActsOn(reachable *TargetNodeSet) actionSet {
+	result := make(actionSet)
+	for actsOn, cs := range as {
+		if !reachable.Contains(actsOn) || cs.IsEmpty() {
+			continue
+		}
+		result[actsOn] = cs.Copy()
+	}
+	return result
+}
+
+// copy returns another actionSet with the same value as `as`
+func (as actionSet) copy() actionSet {
+	result := make(actionSet)
+	for actsOn, cs := range as {
+		if cs.IsEmpty() {
+			continue
+		}
+		result[actsOn] = cs.Copy()
+	}
+	return result
+}
+
+// addSet adds all of the actions of `other` if not already present.
+func (as actionSet) addSet(other actionSet) {
+	for actsOn, cs := range other {
+		as.add(actsOn, cs)
+	}
+}
+
+// add makes the action on `actsOn` to resolve the conditions in `cs` a member of the set.
+func (as actionSet) add(actsOn *TargetNode, cs *LicenseConditionSet) {
+	if acs, ok := as[actsOn]; ok {
+		acs.AddSet(cs)
+	} else {
+		as[actsOn] = cs.Copy()
+	}
+}
+
+// addCondition makes the action on `actsOn` to resolve `lc` a member of the set.
+func (as actionSet) addCondition(actsOn *TargetNode, lc LicenseCondition) {
+	if _, ok := as[actsOn]; !ok {
+		as[actsOn] = newLicenseConditionSet()
+	}
+	as[actsOn].Add(lc)
+}
+
+// isEmpty returns true if no action to resolve a condition exists.
+func (as actionSet) isEmpty() bool {
+	for _, cs := range as {
+		if !cs.IsEmpty() {
+			return false
+		}
+	}
+	return true
+}
diff --git a/tools/compliance/cmd/checkshare.go b/tools/compliance/cmd/checkshare.go
new file mode 100644
index 0000000..efac8dc
--- /dev/null
+++ b/tools/compliance/cmd/checkshare.go
@@ -0,0 +1,114 @@
+// Copyright 2021 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 (
+	"compliance"
+	"flag"
+	"fmt"
+	"io"
+	"os"
+	"path/filepath"
+	"sort"
+)
+
+func init() {
+	flag.Usage = func() {
+		fmt.Fprintf(os.Stderr, `Usage: %s file.meta_lic {file.meta_lic...}
+
+Reports on stderr any targets where policy says that the source both
+must and must not be shared. The error report indicates the target, the
+license condition with origin that has a source privacy policy, and the
+license condition with origin that has a source sharing policy.
+
+Any given target may appear multiple times with different combinations
+of conflicting license conditions.
+
+If all the source code that policy says must be shared may be shared,
+outputs "PASS" to stdout and exits with status 0.
+
+If policy says any source must both be shared and not be shared,
+outputs "FAIL" to stdout and exits with status 1.
+`, filepath.Base(os.Args[0]))
+	}
+}
+
+var (
+	failConflicts = fmt.Errorf("conflicts")
+	failNoneRequested = fmt.Errorf("\nNo metadata files requested")
+	failNoLicenses = fmt.Errorf("No licenses")
+)
+
+
+// byError orders conflicts by error string
+type byError []compliance.SourceSharePrivacyConflict
+
+func (l byError) Len() int           { return len(l) }
+func (l byError) Swap(i, j int)      { l[i], l[j] = l[j], l[i] }
+func (l byError) Less(i, j int) bool { return l[i].Error() < l[j].Error() }
+
+func main() {
+	flag.Parse()
+
+	// Must specify at least one root target.
+	if flag.NArg() == 0 {
+		flag.Usage()
+		os.Exit(2)
+	}
+
+	err := checkShare(os.Stdout, os.Stderr, flag.Args()...)
+	if err != nil {
+		if err != failConflicts {
+			if err == failNoneRequested {
+				flag.Usage()
+			}
+			fmt.Fprintf(os.Stderr, "%s\n", err.Error())
+		}
+		os.Exit(1)
+	}
+	os.Exit(0)
+}
+
+// checkShare implements the checkshare utility.
+func checkShare(stdout, stderr io.Writer, files ...string) error {
+
+	if len(files) < 1 {
+		return failNoneRequested
+	}
+
+	// Read the license graph from the license metadata files (*.meta_lic).
+	licenseGraph, err := compliance.ReadLicenseGraph(os.DirFS("."), stderr, files)
+	if err != nil {
+		return fmt.Errorf("Unable to read license metadata file(s) %q: %w\n", files, err)
+	}
+	if licenseGraph == nil {
+		return failNoLicenses
+	}
+
+	// Apply policy to find conflicts and report them to stderr lexicographically ordered.
+	conflicts := compliance.ConflictingSharedPrivateSource(licenseGraph)
+	sort.Sort(byError(conflicts))
+	for _, conflict := range conflicts {
+		fmt.Fprintln(stderr, conflict.Error())
+	}
+
+	// Indicate pass or fail on stdout.
+	if len(conflicts) > 0 {
+		fmt.Fprintln(stdout, "FAIL")
+		return failConflicts
+	}
+	fmt.Fprintln(stdout, "PASS")
+	return nil
+}
diff --git a/tools/compliance/cmd/checkshare_test.go b/tools/compliance/cmd/checkshare_test.go
new file mode 100644
index 0000000..8ea7748
--- /dev/null
+++ b/tools/compliance/cmd/checkshare_test.go
@@ -0,0 +1,299 @@
+// Copyright 2021 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"
+	"fmt"
+	"strings"
+	"testing"
+)
+
+type outcome struct {
+	target           string
+	privacyOrigin    string
+	privacyCondition string
+	shareOrigin      string
+	shareCondition   string
+}
+
+func (o *outcome) String() string {
+	return fmt.Sprintf("%s %s from %s and must share from %s %s",
+		o.target, o.privacyCondition, o.privacyOrigin, o.shareCondition, o.shareOrigin)
+}
+
+type outcomeList []*outcome
+
+func (ol outcomeList) String() string {
+	result := ""
+	for _, o := range ol {
+		result = result + o.String() + "\n"
+	}
+	return result
+}
+
+func Test(t *testing.T) {
+	tests := []struct {
+		condition        string
+		name             string
+		roots            []string
+		expectedStdout   string
+		expectedOutcomes outcomeList
+	}{
+		{
+			condition:      "firstparty",
+			name:           "apex",
+			roots:          []string{"highest.apex.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "firstparty",
+			name:           "container",
+			roots:          []string{"container.zip.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "firstparty",
+			name:           "application",
+			roots:          []string{"application.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "firstparty",
+			name:           "binary",
+			roots:          []string{"bin/bin2.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "firstparty",
+			name:           "library",
+			roots:          []string{"lib/libd.so.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "notice",
+			name:           "apex",
+			roots:          []string{"highest.apex.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "notice",
+			name:           "container",
+			roots:          []string{"container.zip.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "notice",
+			name:           "application",
+			roots:          []string{"application.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "notice",
+			name:           "binary",
+			roots:          []string{"bin/bin2.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "notice",
+			name:           "library",
+			roots:          []string{"lib/libd.so.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "reciprocal",
+			name:           "apex",
+			roots:          []string{"highest.apex.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "reciprocal",
+			name:           "container",
+			roots:          []string{"container.zip.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "reciprocal",
+			name:           "application",
+			roots:          []string{"application.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "reciprocal",
+			name:           "binary",
+			roots:          []string{"bin/bin2.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "reciprocal",
+			name:           "library",
+			roots:          []string{"lib/libd.so.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "restricted",
+			name:           "apex",
+			roots:          []string{"highest.apex.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "restricted",
+			name:           "container",
+			roots:          []string{"container.zip.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "restricted",
+			name:           "application",
+			roots:          []string{"application.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "restricted",
+			name:           "binary",
+			roots:          []string{"bin/bin2.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "restricted",
+			name:           "library",
+			roots:          []string{"lib/libd.so.meta_lic"},
+			expectedStdout: "PASS",
+		},
+		{
+			condition:      "proprietary",
+			name:           "apex",
+			roots:          []string{"highest.apex.meta_lic"},
+			expectedStdout: "FAIL",
+			expectedOutcomes: outcomeList{
+				&outcome{
+					target:           "testdata/proprietary/bin/bin2.meta_lic",
+					privacyOrigin:    "testdata/proprietary/bin/bin2.meta_lic",
+					privacyCondition: "proprietary",
+					shareOrigin:      "testdata/proprietary/lib/libb.so.meta_lic",
+					shareCondition:   "restricted",
+				},
+			},
+		},
+		{
+			condition:      "proprietary",
+			name:           "container",
+			roots:          []string{"container.zip.meta_lic"},
+			expectedStdout: "FAIL",
+			expectedOutcomes: outcomeList{
+				&outcome{
+					target:           "testdata/proprietary/bin/bin2.meta_lic",
+					privacyOrigin:    "testdata/proprietary/bin/bin2.meta_lic",
+					privacyCondition: "proprietary",
+					shareOrigin:      "testdata/proprietary/lib/libb.so.meta_lic",
+					shareCondition:   "restricted",
+				},
+			},
+		},
+		{
+			condition:      "proprietary",
+			name:           "application",
+			roots:          []string{"application.meta_lic"},
+			expectedStdout: "FAIL",
+			expectedOutcomes: outcomeList{
+				&outcome{
+					target:           "testdata/proprietary/lib/liba.so.meta_lic",
+					privacyOrigin:    "testdata/proprietary/lib/liba.so.meta_lic",
+					privacyCondition: "proprietary",
+					shareOrigin:      "testdata/proprietary/lib/libb.so.meta_lic",
+					shareCondition:   "restricted",
+				},
+			},
+		},
+		{
+			condition:      "proprietary",
+			name:           "binary",
+			roots:          []string{"bin/bin2.meta_lic", "lib/libb.so.meta_lic"},
+			expectedStdout: "FAIL",
+			expectedOutcomes: outcomeList{
+				&outcome{
+					target:           "testdata/proprietary/bin/bin2.meta_lic",
+					privacyOrigin:    "testdata/proprietary/bin/bin2.meta_lic",
+					privacyCondition: "proprietary",
+					shareOrigin:      "testdata/proprietary/lib/libb.so.meta_lic",
+					shareCondition:   "restricted",
+				},
+			},
+		},
+		{
+			condition:      "proprietary",
+			name:           "library",
+			roots:          []string{"lib/libd.so.meta_lic"},
+			expectedStdout: "PASS",
+		},
+	}
+	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)
+			}
+			err := checkShare(stdout, stderr, rootFiles...)
+			if err != nil && err != failConflicts {
+				t.Fatalf("checkshare: error = %v, stderr = %v", err, stderr)
+				return
+			}
+			var actualStdout string
+			for _, s := range strings.Split(stdout.String(), "\n") {
+				ts := strings.TrimLeft(s, " \t")
+				if len(ts) < 1 {
+					continue
+				}
+				if 0 < len(actualStdout) {
+					t.Errorf("checkshare: unexpected multiple output lines %q, want %q", actualStdout+"\n"+ts, tt.expectedStdout)
+				}
+				actualStdout = ts
+			}
+			if actualStdout != tt.expectedStdout {
+				t.Errorf("checkshare: unexpected stdout %q, want %q", actualStdout, tt.expectedStdout)
+			}
+			errList := strings.Split(stderr.String(), "\n")
+			actualOutcomes := make(outcomeList, 0, len(errList))
+			for _, cstring := range errList {
+				ts := strings.TrimLeft(cstring, " \t")
+				if len(ts) < 1 {
+					continue
+				}
+				cFields := strings.Split(ts, " ")
+				actualOutcomes = append(actualOutcomes, &outcome{
+					target:           cFields[0],
+					privacyOrigin:    cFields[3],
+					privacyCondition: cFields[1],
+					shareOrigin:      cFields[9],
+					shareCondition:   cFields[8],
+				})
+			}
+			if len(actualOutcomes) != len(tt.expectedOutcomes) {
+				t.Errorf("checkshare: unexpected got %d outcomes %s, want %d outcomes %s",
+					len(actualOutcomes), actualOutcomes, len(tt.expectedOutcomes), tt.expectedOutcomes)
+				return
+			}
+			for i := range actualOutcomes {
+				if actualOutcomes[i].String() != tt.expectedOutcomes[i].String() {
+					t.Errorf("checkshare: unexpected outcome #%d, got %q, want %q",
+						i+1, actualOutcomes[i], tt.expectedOutcomes[i])
+				}
+			}
+		})
+	}
+}
diff --git a/tools/compliance/cmd/dumpgraph.go b/tools/compliance/cmd/dumpgraph.go
new file mode 100644
index 0000000..1ee63b2
--- /dev/null
+++ b/tools/compliance/cmd/dumpgraph.go
@@ -0,0 +1,178 @@
+// Copyright 2021 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 (
+	"compliance"
+	"flag"
+	"fmt"
+	"io"
+	"os"
+	"path/filepath"
+	"sort"
+	"strings"
+)
+
+var (
+	graphViz        = flag.Bool("dot", false, "Whether to output graphviz (i.e. dot) format.")
+	labelConditions = flag.Bool("label_conditions", false, "Whether to label target nodes with conditions.")
+	stripPrefix     = flag.String("strip_prefix", "", "Prefix to remove from paths. i.e. path to root")
+
+	failNoneRequested = fmt.Errorf("\nNo license metadata files requested")
+	failNoLicenses = fmt.Errorf("No licenses found")
+)
+
+type context struct {
+	graphViz        bool
+	labelConditions bool
+	stripPrefix     string
+}
+
+func init() {
+	flag.Usage = func() {
+		fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...}
+
+Outputs space-separated Target Dependency Annotations tuples for each
+edge in the license graph. When -dot flag given, outputs the nodes and
+edges in graphViz directed graph format.
+
+In plain text mode, multiple values within a field are colon-separated.
+e.g. multiple annotations appear as annotation1:annotation2:annotation3
+or when -label_conditions is requested, Target and Dependency become
+target:condition1:condition2 etc.
+
+Options:
+`, filepath.Base(os.Args[0]))
+		flag.PrintDefaults()
+	}
+}
+
+func main() {
+	flag.Parse()
+
+	// Must specify at least one root target.
+	if flag.NArg() == 0 {
+		flag.Usage()
+		os.Exit(2)
+	}
+
+	ctx := &context{*graphViz, *labelConditions, *stripPrefix}
+
+	err := dumpGraph(ctx, os.Stdout, os.Stderr, flag.Args()...)
+	if err != nil {
+		if err == failNoneRequested {
+			flag.Usage()
+		}
+		fmt.Fprintf(os.Stderr, "%s\n", err.Error())
+		os.Exit(1)
+	}
+	os.Exit(0)
+}
+
+// dumpGraph implements the dumpgraph utility.
+func dumpGraph(ctx *context, stdout, stderr io.Writer, files ...string) error {
+	if len(files) < 1 {
+		return failNoneRequested
+	}
+
+	// Read the license graph from the license metadata files (*.meta_lic).
+	licenseGraph, err := compliance.ReadLicenseGraph(os.DirFS("."), stderr, files)
+	if err != nil {
+		return fmt.Errorf("Unable to read license metadata file(s) %q: %w\n", files, err)
+	}
+	if licenseGraph == nil {
+		return failNoLicenses
+	}
+
+	// Sort the edges of the graph.
+	edges := licenseGraph.Edges()
+	sort.Sort(edges)
+
+	// nodes maps license metadata file names to graphViz node names when ctx.graphViz is true.
+	var nodes map[string]string
+	n := 0
+
+	// targetOut calculates the string to output for `target` separating conditions as needed using `sep`.
+	targetOut := func(target *compliance.TargetNode, sep string) string {
+		tOut := strings.TrimPrefix(target.Name(), ctx.stripPrefix)
+		if ctx.labelConditions {
+			conditions := target.LicenseConditions().Names()
+			sort.Strings(conditions)
+			if len(conditions) > 0 {
+				tOut += sep + strings.Join(conditions, sep)
+			}
+		}
+		return tOut
+	}
+
+	// makeNode maps `target` to a graphViz node name.
+	makeNode := func(target *compliance.TargetNode) {
+		tName := target.Name()
+		if _, ok := nodes[tName]; !ok {
+			nodeName := fmt.Sprintf("n%d", n)
+			nodes[tName] = nodeName
+			fmt.Fprintf(stdout, "\t%s [label=\"%s\"];\n", nodeName, targetOut(target, "\\n"))
+			n++
+		}
+	}
+
+	// If graphviz output, map targets to node names, and start the directed graph.
+	if ctx.graphViz {
+		nodes = make(map[string]string)
+		targets := licenseGraph.Targets()
+		sort.Sort(targets)
+
+		fmt.Fprintf(stdout, "strict digraph {\n\trankdir=RL;\n")
+		for _, target := range targets {
+			makeNode(target)
+		}
+	}
+
+	// Print the sorted edges to stdout ...
+	for _, e := range edges {
+		// sort the annotations for repeatability/stability
+		annotations := e.Annotations().AsList()
+		sort.Strings(annotations)
+
+		tName := e.Target().Name()
+		dName := e.Dependency().Name()
+
+		if ctx.graphViz {
+			// ... one edge per line labelled with \\n-separated annotations.
+			tNode := nodes[tName]
+			dNode := nodes[dName]
+			fmt.Fprintf(stdout, "\t%s -> %s [label=\"%s\"];\n", dNode, tNode, strings.Join(annotations, "\\n"))
+		} else {
+			// ... one edge per line with annotations in a colon-separated tuple.
+			fmt.Fprintf(stdout, "%s %s %s\n", targetOut(e.Target(), ":"), targetOut(e.Dependency(), ":"), strings.Join(annotations, ":"))
+		}
+	}
+
+	// If graphViz output, rank the root nodes together, and complete the directed graph.
+	if ctx.graphViz {
+		fmt.Fprintf(stdout, "\t{rank=same;")
+		for _, f := range files {
+			fName := f
+			if !strings.HasSuffix(fName, ".meta_lic") {
+				fName += ".meta_lic"
+			}
+			if fNode, ok := nodes[fName]; ok {
+				fmt.Fprintf(stdout, " %s", fNode)
+			}
+		}
+		fmt.Fprintf(stdout, "}\n}\n")
+	}
+	return nil
+}
diff --git a/tools/compliance/cmd/dumpgraph_test.go b/tools/compliance/cmd/dumpgraph_test.go
new file mode 100644
index 0000000..b7d66f7
--- /dev/null
+++ b/tools/compliance/cmd/dumpgraph_test.go
@@ -0,0 +1,1258 @@
+// Copyright 2021 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"
+	"fmt"
+	"strings"
+	"testing"
+)
+
+func Test_plaintext(t *testing.T) {
+	tests := []struct {
+		condition   string
+		name        string
+		roots       []string
+		ctx         context
+		expectedOut []string
+	}{
+		{
+			condition: "firstparty",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []string{
+				"testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/lib/liba.so.meta_lic static",
+				"testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/lib/libc.a.meta_lic static",
+				"testdata/firstparty/bin/bin2.meta_lic testdata/firstparty/lib/libb.so.meta_lic dynamic",
+				"testdata/firstparty/bin/bin2.meta_lic testdata/firstparty/lib/libd.so.meta_lic dynamic",
+				"testdata/firstparty/highest.apex.meta_lic testdata/firstparty/bin/bin1.meta_lic static",
+				"testdata/firstparty/highest.apex.meta_lic testdata/firstparty/bin/bin2.meta_lic static",
+				"testdata/firstparty/highest.apex.meta_lic testdata/firstparty/lib/liba.so.meta_lic static",
+				"testdata/firstparty/highest.apex.meta_lic testdata/firstparty/lib/libb.so.meta_lic static",
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/firstparty/"},
+			expectedOut: []string{
+				"bin/bin1.meta_lic lib/liba.so.meta_lic static",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic static",
+				"bin/bin2.meta_lic lib/libb.so.meta_lic dynamic",
+				"bin/bin2.meta_lic lib/libd.so.meta_lic dynamic",
+				"highest.apex.meta_lic bin/bin1.meta_lic static",
+				"highest.apex.meta_lic bin/bin2.meta_lic static",
+				"highest.apex.meta_lic lib/liba.so.meta_lic static",
+				"highest.apex.meta_lic lib/libb.so.meta_lic static",
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/firstparty/", labelConditions: true},
+			expectedOut: []string{
+				"bin/bin1.meta_lic:notice lib/liba.so.meta_lic:notice static",
+				"bin/bin1.meta_lic:notice lib/libc.a.meta_lic:notice static",
+				"bin/bin2.meta_lic:notice lib/libb.so.meta_lic:notice dynamic",
+				"bin/bin2.meta_lic:notice lib/libd.so.meta_lic:notice dynamic",
+				"highest.apex.meta_lic:notice bin/bin1.meta_lic:notice static",
+				"highest.apex.meta_lic:notice bin/bin2.meta_lic:notice static",
+				"highest.apex.meta_lic:notice lib/liba.so.meta_lic:notice static",
+				"highest.apex.meta_lic:notice lib/libb.so.meta_lic:notice static",
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []string{
+				"testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/lib/liba.so.meta_lic static",
+				"testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/lib/libc.a.meta_lic static",
+				"testdata/firstparty/bin/bin2.meta_lic testdata/firstparty/lib/libb.so.meta_lic dynamic",
+				"testdata/firstparty/bin/bin2.meta_lic testdata/firstparty/lib/libd.so.meta_lic dynamic",
+				"testdata/firstparty/container.zip.meta_lic testdata/firstparty/bin/bin1.meta_lic static",
+				"testdata/firstparty/container.zip.meta_lic testdata/firstparty/bin/bin2.meta_lic static",
+				"testdata/firstparty/container.zip.meta_lic testdata/firstparty/lib/liba.so.meta_lic static",
+				"testdata/firstparty/container.zip.meta_lic testdata/firstparty/lib/libb.so.meta_lic static",
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []string{
+				"testdata/firstparty/application.meta_lic testdata/firstparty/bin/bin3.meta_lic toolchain",
+				"testdata/firstparty/application.meta_lic testdata/firstparty/lib/liba.so.meta_lic static",
+				"testdata/firstparty/application.meta_lic testdata/firstparty/lib/libb.so.meta_lic dynamic",
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []string{
+				"testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/lib/liba.so.meta_lic static",
+				"testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/lib/libc.a.meta_lic static",
+			},
+		},
+		{
+			condition:   "firstparty",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			expectedOut: []string{},
+		},
+		{
+			condition: "notice",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []string{
+				"testdata/notice/bin/bin1.meta_lic testdata/notice/lib/liba.so.meta_lic static",
+				"testdata/notice/bin/bin1.meta_lic testdata/notice/lib/libc.a.meta_lic static",
+				"testdata/notice/bin/bin2.meta_lic testdata/notice/lib/libb.so.meta_lic dynamic",
+				"testdata/notice/bin/bin2.meta_lic testdata/notice/lib/libd.so.meta_lic dynamic",
+				"testdata/notice/highest.apex.meta_lic testdata/notice/bin/bin1.meta_lic static",
+				"testdata/notice/highest.apex.meta_lic testdata/notice/bin/bin2.meta_lic static",
+				"testdata/notice/highest.apex.meta_lic testdata/notice/lib/liba.so.meta_lic static",
+				"testdata/notice/highest.apex.meta_lic testdata/notice/lib/libb.so.meta_lic static",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/notice/"},
+			expectedOut: []string{
+				"bin/bin1.meta_lic lib/liba.so.meta_lic static",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic static",
+				"bin/bin2.meta_lic lib/libb.so.meta_lic dynamic",
+				"bin/bin2.meta_lic lib/libd.so.meta_lic dynamic",
+				"highest.apex.meta_lic bin/bin1.meta_lic static",
+				"highest.apex.meta_lic bin/bin2.meta_lic static",
+				"highest.apex.meta_lic lib/liba.so.meta_lic static",
+				"highest.apex.meta_lic lib/libb.so.meta_lic static",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/notice/", labelConditions: true},
+			expectedOut: []string{
+				"bin/bin1.meta_lic:notice lib/liba.so.meta_lic:notice static",
+				"bin/bin1.meta_lic:notice lib/libc.a.meta_lic:notice static",
+				"bin/bin2.meta_lic:notice lib/libb.so.meta_lic:notice dynamic",
+				"bin/bin2.meta_lic:notice lib/libd.so.meta_lic:notice dynamic",
+				"highest.apex.meta_lic:notice bin/bin1.meta_lic:notice static",
+				"highest.apex.meta_lic:notice bin/bin2.meta_lic:notice static",
+				"highest.apex.meta_lic:notice lib/liba.so.meta_lic:notice static",
+				"highest.apex.meta_lic:notice lib/libb.so.meta_lic:notice static",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []string{
+				"testdata/notice/bin/bin1.meta_lic testdata/notice/lib/liba.so.meta_lic static",
+				"testdata/notice/bin/bin1.meta_lic testdata/notice/lib/libc.a.meta_lic static",
+				"testdata/notice/bin/bin2.meta_lic testdata/notice/lib/libb.so.meta_lic dynamic",
+				"testdata/notice/bin/bin2.meta_lic testdata/notice/lib/libd.so.meta_lic dynamic",
+				"testdata/notice/container.zip.meta_lic testdata/notice/bin/bin1.meta_lic static",
+				"testdata/notice/container.zip.meta_lic testdata/notice/bin/bin2.meta_lic static",
+				"testdata/notice/container.zip.meta_lic testdata/notice/lib/liba.so.meta_lic static",
+				"testdata/notice/container.zip.meta_lic testdata/notice/lib/libb.so.meta_lic static",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []string{
+				"testdata/notice/application.meta_lic testdata/notice/bin/bin3.meta_lic toolchain",
+				"testdata/notice/application.meta_lic testdata/notice/lib/liba.so.meta_lic static",
+				"testdata/notice/application.meta_lic testdata/notice/lib/libb.so.meta_lic dynamic",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []string{
+				"testdata/notice/bin/bin1.meta_lic testdata/notice/lib/liba.so.meta_lic static",
+				"testdata/notice/bin/bin1.meta_lic testdata/notice/lib/libc.a.meta_lic static",
+			},
+		},
+		{
+			condition:   "notice",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			expectedOut: []string{},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []string{
+				"testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/lib/liba.so.meta_lic static",
+				"testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/lib/libc.a.meta_lic static",
+				"testdata/reciprocal/bin/bin2.meta_lic testdata/reciprocal/lib/libb.so.meta_lic dynamic",
+				"testdata/reciprocal/bin/bin2.meta_lic testdata/reciprocal/lib/libd.so.meta_lic dynamic",
+				"testdata/reciprocal/highest.apex.meta_lic testdata/reciprocal/bin/bin1.meta_lic static",
+				"testdata/reciprocal/highest.apex.meta_lic testdata/reciprocal/bin/bin2.meta_lic static",
+				"testdata/reciprocal/highest.apex.meta_lic testdata/reciprocal/lib/liba.so.meta_lic static",
+				"testdata/reciprocal/highest.apex.meta_lic testdata/reciprocal/lib/libb.so.meta_lic static",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/reciprocal/"},
+			expectedOut: []string{
+				"bin/bin1.meta_lic lib/liba.so.meta_lic static",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic static",
+				"bin/bin2.meta_lic lib/libb.so.meta_lic dynamic",
+				"bin/bin2.meta_lic lib/libd.so.meta_lic dynamic",
+				"highest.apex.meta_lic bin/bin1.meta_lic static",
+				"highest.apex.meta_lic bin/bin2.meta_lic static",
+				"highest.apex.meta_lic lib/liba.so.meta_lic static",
+				"highest.apex.meta_lic lib/libb.so.meta_lic static",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/reciprocal/", labelConditions: true},
+			expectedOut: []string{
+				"bin/bin1.meta_lic:notice lib/liba.so.meta_lic:reciprocal static",
+				"bin/bin1.meta_lic:notice lib/libc.a.meta_lic:reciprocal static",
+				"bin/bin2.meta_lic:notice lib/libb.so.meta_lic:notice dynamic",
+				"bin/bin2.meta_lic:notice lib/libd.so.meta_lic:notice dynamic",
+				"highest.apex.meta_lic:notice bin/bin1.meta_lic:notice static",
+				"highest.apex.meta_lic:notice bin/bin2.meta_lic:notice static",
+				"highest.apex.meta_lic:notice lib/liba.so.meta_lic:reciprocal static",
+				"highest.apex.meta_lic:notice lib/libb.so.meta_lic:notice static",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []string{
+				"testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/lib/liba.so.meta_lic static",
+				"testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/lib/libc.a.meta_lic static",
+				"testdata/reciprocal/bin/bin2.meta_lic testdata/reciprocal/lib/libb.so.meta_lic dynamic",
+				"testdata/reciprocal/bin/bin2.meta_lic testdata/reciprocal/lib/libd.so.meta_lic dynamic",
+				"testdata/reciprocal/container.zip.meta_lic testdata/reciprocal/bin/bin1.meta_lic static",
+				"testdata/reciprocal/container.zip.meta_lic testdata/reciprocal/bin/bin2.meta_lic static",
+				"testdata/reciprocal/container.zip.meta_lic testdata/reciprocal/lib/liba.so.meta_lic static",
+				"testdata/reciprocal/container.zip.meta_lic testdata/reciprocal/lib/libb.so.meta_lic static",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []string{
+				"testdata/reciprocal/application.meta_lic testdata/reciprocal/bin/bin3.meta_lic toolchain",
+				"testdata/reciprocal/application.meta_lic testdata/reciprocal/lib/liba.so.meta_lic static",
+				"testdata/reciprocal/application.meta_lic testdata/reciprocal/lib/libb.so.meta_lic dynamic",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []string{
+				"testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/lib/liba.so.meta_lic static",
+				"testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/lib/libc.a.meta_lic static",
+			},
+		},
+		{
+			condition:   "reciprocal",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			expectedOut: []string{},
+		},
+		{
+			condition: "restricted",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []string{
+				"testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/liba.so.meta_lic static",
+				"testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/libc.a.meta_lic static",
+				"testdata/restricted/bin/bin2.meta_lic testdata/restricted/lib/libb.so.meta_lic dynamic",
+				"testdata/restricted/bin/bin2.meta_lic testdata/restricted/lib/libd.so.meta_lic dynamic",
+				"testdata/restricted/highest.apex.meta_lic testdata/restricted/bin/bin1.meta_lic static",
+				"testdata/restricted/highest.apex.meta_lic testdata/restricted/bin/bin2.meta_lic static",
+				"testdata/restricted/highest.apex.meta_lic testdata/restricted/lib/liba.so.meta_lic static",
+				"testdata/restricted/highest.apex.meta_lic testdata/restricted/lib/libb.so.meta_lic static",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/restricted/"},
+			expectedOut: []string{
+				"bin/bin1.meta_lic lib/liba.so.meta_lic static",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic static",
+				"bin/bin2.meta_lic lib/libb.so.meta_lic dynamic",
+				"bin/bin2.meta_lic lib/libd.so.meta_lic dynamic",
+				"highest.apex.meta_lic bin/bin1.meta_lic static",
+				"highest.apex.meta_lic bin/bin2.meta_lic static",
+				"highest.apex.meta_lic lib/liba.so.meta_lic static",
+				"highest.apex.meta_lic lib/libb.so.meta_lic static",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/restricted/", labelConditions: true},
+			expectedOut: []string{
+				"bin/bin1.meta_lic:notice lib/liba.so.meta_lic:restricted static",
+				"bin/bin1.meta_lic:notice lib/libc.a.meta_lic:reciprocal static",
+				"bin/bin2.meta_lic:notice lib/libb.so.meta_lic:restricted dynamic",
+				"bin/bin2.meta_lic:notice lib/libd.so.meta_lic:notice dynamic",
+				"highest.apex.meta_lic:notice bin/bin1.meta_lic:notice static",
+				"highest.apex.meta_lic:notice bin/bin2.meta_lic:notice static",
+				"highest.apex.meta_lic:notice lib/liba.so.meta_lic:restricted static",
+				"highest.apex.meta_lic:notice lib/libb.so.meta_lic:restricted static",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []string{
+				"testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/liba.so.meta_lic static",
+				"testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/libc.a.meta_lic static",
+				"testdata/restricted/bin/bin2.meta_lic testdata/restricted/lib/libb.so.meta_lic dynamic",
+				"testdata/restricted/bin/bin2.meta_lic testdata/restricted/lib/libd.so.meta_lic dynamic",
+				"testdata/restricted/container.zip.meta_lic testdata/restricted/bin/bin1.meta_lic static",
+				"testdata/restricted/container.zip.meta_lic testdata/restricted/bin/bin2.meta_lic static",
+				"testdata/restricted/container.zip.meta_lic testdata/restricted/lib/liba.so.meta_lic static",
+				"testdata/restricted/container.zip.meta_lic testdata/restricted/lib/libb.so.meta_lic static",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []string{
+				"testdata/restricted/application.meta_lic testdata/restricted/bin/bin3.meta_lic toolchain",
+				"testdata/restricted/application.meta_lic testdata/restricted/lib/liba.so.meta_lic static",
+				"testdata/restricted/application.meta_lic testdata/restricted/lib/libb.so.meta_lic dynamic",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []string{
+				"testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/liba.so.meta_lic static",
+				"testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/libc.a.meta_lic static",
+			},
+		},
+		{
+			condition:   "restricted",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			expectedOut: []string{},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []string{
+				"testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/lib/liba.so.meta_lic static",
+				"testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/lib/libc.a.meta_lic static",
+				"testdata/proprietary/bin/bin2.meta_lic testdata/proprietary/lib/libb.so.meta_lic dynamic",
+				"testdata/proprietary/bin/bin2.meta_lic testdata/proprietary/lib/libd.so.meta_lic dynamic",
+				"testdata/proprietary/highest.apex.meta_lic testdata/proprietary/bin/bin1.meta_lic static",
+				"testdata/proprietary/highest.apex.meta_lic testdata/proprietary/bin/bin2.meta_lic static",
+				"testdata/proprietary/highest.apex.meta_lic testdata/proprietary/lib/liba.so.meta_lic static",
+				"testdata/proprietary/highest.apex.meta_lic testdata/proprietary/lib/libb.so.meta_lic static",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/proprietary/"},
+			expectedOut: []string{
+				"bin/bin1.meta_lic lib/liba.so.meta_lic static",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic static",
+				"bin/bin2.meta_lic lib/libb.so.meta_lic dynamic",
+				"bin/bin2.meta_lic lib/libd.so.meta_lic dynamic",
+				"highest.apex.meta_lic bin/bin1.meta_lic static",
+				"highest.apex.meta_lic bin/bin2.meta_lic static",
+				"highest.apex.meta_lic lib/liba.so.meta_lic static",
+				"highest.apex.meta_lic lib/libb.so.meta_lic static",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/proprietary/", labelConditions: true},
+			expectedOut: []string{
+				"bin/bin1.meta_lic:notice lib/liba.so.meta_lic:by_exception_only:proprietary static",
+				"bin/bin1.meta_lic:notice lib/libc.a.meta_lic:by_exception_only:proprietary static",
+				"bin/bin2.meta_lic:by_exception_only:proprietary lib/libb.so.meta_lic:restricted dynamic",
+				"bin/bin2.meta_lic:by_exception_only:proprietary lib/libd.so.meta_lic:notice dynamic",
+				"highest.apex.meta_lic:notice bin/bin1.meta_lic:notice static",
+				"highest.apex.meta_lic:notice bin/bin2.meta_lic:by_exception_only:proprietary static",
+				"highest.apex.meta_lic:notice lib/liba.so.meta_lic:by_exception_only:proprietary static",
+				"highest.apex.meta_lic:notice lib/libb.so.meta_lic:restricted static",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []string{
+				"testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/lib/liba.so.meta_lic static",
+				"testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/lib/libc.a.meta_lic static",
+				"testdata/proprietary/bin/bin2.meta_lic testdata/proprietary/lib/libb.so.meta_lic dynamic",
+				"testdata/proprietary/bin/bin2.meta_lic testdata/proprietary/lib/libd.so.meta_lic dynamic",
+				"testdata/proprietary/container.zip.meta_lic testdata/proprietary/bin/bin1.meta_lic static",
+				"testdata/proprietary/container.zip.meta_lic testdata/proprietary/bin/bin2.meta_lic static",
+				"testdata/proprietary/container.zip.meta_lic testdata/proprietary/lib/liba.so.meta_lic static",
+				"testdata/proprietary/container.zip.meta_lic testdata/proprietary/lib/libb.so.meta_lic static",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []string{
+				"testdata/proprietary/application.meta_lic testdata/proprietary/bin/bin3.meta_lic toolchain",
+				"testdata/proprietary/application.meta_lic testdata/proprietary/lib/liba.so.meta_lic static",
+				"testdata/proprietary/application.meta_lic testdata/proprietary/lib/libb.so.meta_lic dynamic",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []string{
+				"testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/lib/liba.so.meta_lic static",
+				"testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/lib/libc.a.meta_lic static",
+			},
+		},
+		{
+			condition:   "proprietary",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			expectedOut: []string{},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.condition+" "+tt.name, func(t *testing.T) {
+			expectedOut := &bytes.Buffer{}
+			for _, eo := range tt.expectedOut {
+				expectedOut.WriteString(eo)
+				expectedOut.WriteString("\n")
+			}
+
+			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)
+			}
+			err := dumpGraph(&tt.ctx, stdout, stderr, rootFiles...)
+			if err != nil {
+				t.Fatalf("dumpgraph: error = %v, stderr = %v", err, stderr)
+				return
+			}
+			if stderr.Len() > 0 {
+				t.Errorf("dumpgraph: gotStderr = %v, want none", stderr)
+			}
+			out := stdout.String()
+			expected := expectedOut.String()
+			if out != expected {
+				outList := strings.Split(out, "\n")
+				expectedList := strings.Split(expected, "\n")
+				startLine := 0
+				for len(outList) > startLine && len(expectedList) > startLine && outList[startLine] == expectedList[startLine] {
+					startLine++
+				}
+				t.Errorf("listshare: gotStdout = %v, want %v, somewhere near line %d Stdout = %v, want %v",
+					out, expected, startLine+1, outList[startLine], expectedList[startLine])
+			}
+		})
+	}
+}
+
+type testContext struct {
+	nextNode int
+	nodes    map[string]string
+}
+
+type matcher interface {
+	matchString(*testContext) string
+	typeString() string
+}
+
+type targetMatcher struct {
+	target     string
+	conditions []string
+}
+
+func (tm *targetMatcher) matchString(ctx *testContext) string {
+	m := tm.target
+	if len(tm.conditions) > 0 {
+		m += "\\n" + strings.Join(tm.conditions, "\\n")
+	}
+	m = ctx.nodes[tm.target] + " [label=\"" + m + "\"];"
+	return m
+}
+
+func (tm *targetMatcher) typeString() string {
+	return "target"
+}
+
+type edgeMatcher struct {
+	target      string
+	dep         string
+	annotations []string
+}
+
+func (em *edgeMatcher) matchString(ctx *testContext) string {
+	return ctx.nodes[em.dep] + " -> " + ctx.nodes[em.target] + " [label=\"" + strings.Join(em.annotations, "\\n") + "\"];"
+}
+
+func (tm *edgeMatcher) typeString() string {
+	return "edge"
+}
+
+type getMatcher func(*testContext) matcher
+
+func matchTarget(target string, conditions ...string) getMatcher {
+	return func(ctx *testContext) matcher {
+		ctx.nodes[target] = fmt.Sprintf("n%d", ctx.nextNode)
+		ctx.nextNode++
+		return &targetMatcher{target, append([]string{}, conditions...)}
+	}
+}
+
+func matchEdge(target, dep string, annotations ...string) getMatcher {
+	return func(ctx *testContext) matcher {
+		if _, ok := ctx.nodes[target]; !ok {
+			panic(fmt.Errorf("no node for target %v in %v -> %v [label=\"%s\"];", target, dep, target, strings.Join(annotations, "\\n")))
+		}
+		if _, ok := ctx.nodes[dep]; !ok {
+			panic(fmt.Errorf("no node for dep %v in %v -> %v [label=\"%s\"];", target, dep, target, strings.Join(annotations, "\\n")))
+		}
+		return &edgeMatcher{target, dep, append([]string{}, annotations...)}
+	}
+}
+
+func Test_graphviz(t *testing.T) {
+	tests := []struct {
+		condition   string
+		name        string
+		roots       []string
+		ctx         context
+		expectedOut []getMatcher
+	}{
+		{
+			condition: "firstparty",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/firstparty/bin/bin1.meta_lic"),
+				matchTarget("testdata/firstparty/bin/bin2.meta_lic"),
+				matchTarget("testdata/firstparty/highest.apex.meta_lic"),
+				matchTarget("testdata/firstparty/lib/liba.so.meta_lic"),
+				matchTarget("testdata/firstparty/lib/libb.so.meta_lic"),
+				matchTarget("testdata/firstparty/lib/libc.a.meta_lic"),
+				matchTarget("testdata/firstparty/lib/libd.so.meta_lic"),
+				matchEdge("testdata/firstparty/bin/bin1.meta_lic", "testdata/firstparty/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/firstparty/bin/bin1.meta_lic", "testdata/firstparty/lib/libc.a.meta_lic", "static"),
+				matchEdge("testdata/firstparty/bin/bin2.meta_lic", "testdata/firstparty/lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("testdata/firstparty/bin/bin2.meta_lic", "testdata/firstparty/lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("testdata/firstparty/highest.apex.meta_lic", "testdata/firstparty/bin/bin1.meta_lic", "static"),
+				matchEdge("testdata/firstparty/highest.apex.meta_lic", "testdata/firstparty/bin/bin2.meta_lic", "static"),
+				matchEdge("testdata/firstparty/highest.apex.meta_lic", "testdata/firstparty/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/firstparty/highest.apex.meta_lic", "testdata/firstparty/lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/firstparty/"},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchTarget("lib/liba.so.meta_lic"),
+				matchTarget("lib/libb.so.meta_lic"),
+				matchTarget("lib/libc.a.meta_lic"),
+				matchTarget("lib/libd.so.meta_lic"),
+				matchEdge("bin/bin1.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("bin/bin1.meta_lic", "lib/libc.a.meta_lic", "static"),
+				matchEdge("bin/bin2.meta_lic", "lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("bin/bin2.meta_lic", "lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("highest.apex.meta_lic", "bin/bin1.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "bin/bin2.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/firstparty/", labelConditions: true},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic", "notice"),
+				matchTarget("bin/bin2.meta_lic", "notice"),
+				matchTarget("highest.apex.meta_lic", "notice"),
+				matchTarget("lib/liba.so.meta_lic", "notice"),
+				matchTarget("lib/libb.so.meta_lic", "notice"),
+				matchTarget("lib/libc.a.meta_lic", "notice"),
+				matchTarget("lib/libd.so.meta_lic", "notice"),
+				matchEdge("bin/bin1.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("bin/bin1.meta_lic", "lib/libc.a.meta_lic", "static"),
+				matchEdge("bin/bin2.meta_lic", "lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("bin/bin2.meta_lic", "lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("highest.apex.meta_lic", "bin/bin1.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "bin/bin2.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/firstparty/bin/bin1.meta_lic"),
+				matchTarget("testdata/firstparty/bin/bin2.meta_lic"),
+				matchTarget("testdata/firstparty/container.zip.meta_lic"),
+				matchTarget("testdata/firstparty/lib/liba.so.meta_lic"),
+				matchTarget("testdata/firstparty/lib/libb.so.meta_lic"),
+				matchTarget("testdata/firstparty/lib/libc.a.meta_lic"),
+				matchTarget("testdata/firstparty/lib/libd.so.meta_lic"),
+				matchEdge("testdata/firstparty/bin/bin1.meta_lic", "testdata/firstparty/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/firstparty/bin/bin1.meta_lic", "testdata/firstparty/lib/libc.a.meta_lic", "static"),
+				matchEdge("testdata/firstparty/bin/bin2.meta_lic", "testdata/firstparty/lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("testdata/firstparty/bin/bin2.meta_lic", "testdata/firstparty/lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("testdata/firstparty/container.zip.meta_lic", "testdata/firstparty/bin/bin1.meta_lic", "static"),
+				matchEdge("testdata/firstparty/container.zip.meta_lic", "testdata/firstparty/bin/bin2.meta_lic", "static"),
+				matchEdge("testdata/firstparty/container.zip.meta_lic", "testdata/firstparty/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/firstparty/container.zip.meta_lic", "testdata/firstparty/lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/firstparty/application.meta_lic"),
+				matchTarget("testdata/firstparty/bin/bin3.meta_lic"),
+				matchTarget("testdata/firstparty/lib/liba.so.meta_lic"),
+				matchTarget("testdata/firstparty/lib/libb.so.meta_lic"),
+				matchEdge("testdata/firstparty/application.meta_lic", "testdata/firstparty/bin/bin3.meta_lic", "toolchain"),
+				matchEdge("testdata/firstparty/application.meta_lic", "testdata/firstparty/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/firstparty/application.meta_lic", "testdata/firstparty/lib/libb.so.meta_lic", "dynamic"),
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/firstparty/bin/bin1.meta_lic"),
+				matchTarget("testdata/firstparty/lib/liba.so.meta_lic"),
+				matchTarget("testdata/firstparty/lib/libc.a.meta_lic"),
+				matchEdge("testdata/firstparty/bin/bin1.meta_lic", "testdata/firstparty/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/firstparty/bin/bin1.meta_lic", "testdata/firstparty/lib/libc.a.meta_lic", "static"),
+			},
+		},
+		{
+			condition:   "firstparty",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			expectedOut: []getMatcher{matchTarget("testdata/firstparty/lib/libd.so.meta_lic")},
+		},
+		{
+			condition: "notice",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/notice/bin/bin1.meta_lic"),
+				matchTarget("testdata/notice/bin/bin2.meta_lic"),
+				matchTarget("testdata/notice/highest.apex.meta_lic"),
+				matchTarget("testdata/notice/lib/liba.so.meta_lic"),
+				matchTarget("testdata/notice/lib/libb.so.meta_lic"),
+				matchTarget("testdata/notice/lib/libc.a.meta_lic"),
+				matchTarget("testdata/notice/lib/libd.so.meta_lic"),
+				matchEdge("testdata/notice/bin/bin1.meta_lic", "testdata/notice/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/notice/bin/bin1.meta_lic", "testdata/notice/lib/libc.a.meta_lic", "static"),
+				matchEdge("testdata/notice/bin/bin2.meta_lic", "testdata/notice/lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("testdata/notice/bin/bin2.meta_lic", "testdata/notice/lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("testdata/notice/highest.apex.meta_lic", "testdata/notice/bin/bin1.meta_lic", "static"),
+				matchEdge("testdata/notice/highest.apex.meta_lic", "testdata/notice/bin/bin2.meta_lic", "static"),
+				matchEdge("testdata/notice/highest.apex.meta_lic", "testdata/notice/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/notice/highest.apex.meta_lic", "testdata/notice/lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "notice",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/notice/"},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchTarget("lib/liba.so.meta_lic"),
+				matchTarget("lib/libb.so.meta_lic"),
+				matchTarget("lib/libc.a.meta_lic"),
+				matchTarget("lib/libd.so.meta_lic"),
+				matchEdge("bin/bin1.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("bin/bin1.meta_lic", "lib/libc.a.meta_lic", "static"),
+				matchEdge("bin/bin2.meta_lic", "lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("bin/bin2.meta_lic", "lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("highest.apex.meta_lic", "bin/bin1.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "bin/bin2.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "notice",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/notice/", labelConditions: true},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic", "notice"),
+				matchTarget("bin/bin2.meta_lic", "notice"),
+				matchTarget("highest.apex.meta_lic", "notice"),
+				matchTarget("lib/liba.so.meta_lic", "notice"),
+				matchTarget("lib/libb.so.meta_lic", "notice"),
+				matchTarget("lib/libc.a.meta_lic", "notice"),
+				matchTarget("lib/libd.so.meta_lic", "notice"),
+				matchEdge("bin/bin1.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("bin/bin1.meta_lic", "lib/libc.a.meta_lic", "static"),
+				matchEdge("bin/bin2.meta_lic", "lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("bin/bin2.meta_lic", "lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("highest.apex.meta_lic", "bin/bin1.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "bin/bin2.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "notice",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/notice/bin/bin1.meta_lic"),
+				matchTarget("testdata/notice/bin/bin2.meta_lic"),
+				matchTarget("testdata/notice/container.zip.meta_lic"),
+				matchTarget("testdata/notice/lib/liba.so.meta_lic"),
+				matchTarget("testdata/notice/lib/libb.so.meta_lic"),
+				matchTarget("testdata/notice/lib/libc.a.meta_lic"),
+				matchTarget("testdata/notice/lib/libd.so.meta_lic"),
+				matchEdge("testdata/notice/bin/bin1.meta_lic", "testdata/notice/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/notice/bin/bin1.meta_lic", "testdata/notice/lib/libc.a.meta_lic", "static"),
+				matchEdge("testdata/notice/bin/bin2.meta_lic", "testdata/notice/lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("testdata/notice/bin/bin2.meta_lic", "testdata/notice/lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("testdata/notice/container.zip.meta_lic", "testdata/notice/bin/bin1.meta_lic", "static"),
+				matchEdge("testdata/notice/container.zip.meta_lic", "testdata/notice/bin/bin2.meta_lic", "static"),
+				matchEdge("testdata/notice/container.zip.meta_lic", "testdata/notice/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/notice/container.zip.meta_lic", "testdata/notice/lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "notice",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/notice/application.meta_lic"),
+				matchTarget("testdata/notice/bin/bin3.meta_lic"),
+				matchTarget("testdata/notice/lib/liba.so.meta_lic"),
+				matchTarget("testdata/notice/lib/libb.so.meta_lic"),
+				matchEdge("testdata/notice/application.meta_lic", "testdata/notice/bin/bin3.meta_lic", "toolchain"),
+				matchEdge("testdata/notice/application.meta_lic", "testdata/notice/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/notice/application.meta_lic", "testdata/notice/lib/libb.so.meta_lic", "dynamic"),
+			},
+		},
+		{
+			condition: "notice",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/notice/bin/bin1.meta_lic"),
+				matchTarget("testdata/notice/lib/liba.so.meta_lic"),
+				matchTarget("testdata/notice/lib/libc.a.meta_lic"),
+				matchEdge("testdata/notice/bin/bin1.meta_lic", "testdata/notice/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/notice/bin/bin1.meta_lic", "testdata/notice/lib/libc.a.meta_lic", "static"),
+			},
+		},
+		{
+			condition:   "notice",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			expectedOut: []getMatcher{matchTarget("testdata/notice/lib/libd.so.meta_lic")},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/reciprocal/bin/bin1.meta_lic"),
+				matchTarget("testdata/reciprocal/bin/bin2.meta_lic"),
+				matchTarget("testdata/reciprocal/highest.apex.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/liba.so.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/libb.so.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/libc.a.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/libd.so.meta_lic"),
+				matchEdge("testdata/reciprocal/bin/bin1.meta_lic", "testdata/reciprocal/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/reciprocal/bin/bin1.meta_lic", "testdata/reciprocal/lib/libc.a.meta_lic", "static"),
+				matchEdge("testdata/reciprocal/bin/bin2.meta_lic", "testdata/reciprocal/lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("testdata/reciprocal/bin/bin2.meta_lic", "testdata/reciprocal/lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("testdata/reciprocal/highest.apex.meta_lic", "testdata/reciprocal/bin/bin1.meta_lic", "static"),
+				matchEdge("testdata/reciprocal/highest.apex.meta_lic", "testdata/reciprocal/bin/bin2.meta_lic", "static"),
+				matchEdge("testdata/reciprocal/highest.apex.meta_lic", "testdata/reciprocal/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/reciprocal/highest.apex.meta_lic", "testdata/reciprocal/lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/reciprocal/"},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchTarget("lib/liba.so.meta_lic"),
+				matchTarget("lib/libb.so.meta_lic"),
+				matchTarget("lib/libc.a.meta_lic"),
+				matchTarget("lib/libd.so.meta_lic"),
+				matchEdge("bin/bin1.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("bin/bin1.meta_lic", "lib/libc.a.meta_lic", "static"),
+				matchEdge("bin/bin2.meta_lic", "lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("bin/bin2.meta_lic", "lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("highest.apex.meta_lic", "bin/bin1.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "bin/bin2.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/reciprocal/", labelConditions: true},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic", "notice"),
+				matchTarget("bin/bin2.meta_lic", "notice"),
+				matchTarget("highest.apex.meta_lic", "notice"),
+				matchTarget("lib/liba.so.meta_lic", "reciprocal"),
+				matchTarget("lib/libb.so.meta_lic", "notice"),
+				matchTarget("lib/libc.a.meta_lic", "reciprocal"),
+				matchTarget("lib/libd.so.meta_lic", "notice"),
+				matchEdge("bin/bin1.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("bin/bin1.meta_lic", "lib/libc.a.meta_lic", "static"),
+				matchEdge("bin/bin2.meta_lic", "lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("bin/bin2.meta_lic", "lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("highest.apex.meta_lic", "bin/bin1.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "bin/bin2.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/reciprocal/bin/bin1.meta_lic"),
+				matchTarget("testdata/reciprocal/bin/bin2.meta_lic"),
+				matchTarget("testdata/reciprocal/container.zip.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/liba.so.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/libb.so.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/libc.a.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/libd.so.meta_lic"),
+				matchEdge("testdata/reciprocal/bin/bin1.meta_lic", "testdata/reciprocal/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/reciprocal/bin/bin1.meta_lic", "testdata/reciprocal/lib/libc.a.meta_lic", "static"),
+				matchEdge("testdata/reciprocal/bin/bin2.meta_lic", "testdata/reciprocal/lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("testdata/reciprocal/bin/bin2.meta_lic", "testdata/reciprocal/lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("testdata/reciprocal/container.zip.meta_lic", "testdata/reciprocal/bin/bin1.meta_lic", "static"),
+				matchEdge("testdata/reciprocal/container.zip.meta_lic", "testdata/reciprocal/bin/bin2.meta_lic", "static"),
+				matchEdge("testdata/reciprocal/container.zip.meta_lic", "testdata/reciprocal/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/reciprocal/container.zip.meta_lic", "testdata/reciprocal/lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/reciprocal/application.meta_lic"),
+				matchTarget("testdata/reciprocal/bin/bin3.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/liba.so.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/libb.so.meta_lic"),
+				matchEdge("testdata/reciprocal/application.meta_lic", "testdata/reciprocal/bin/bin3.meta_lic", "toolchain"),
+				matchEdge("testdata/reciprocal/application.meta_lic", "testdata/reciprocal/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/reciprocal/application.meta_lic", "testdata/reciprocal/lib/libb.so.meta_lic", "dynamic"),
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/reciprocal/bin/bin1.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/liba.so.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/libc.a.meta_lic"),
+				matchEdge("testdata/reciprocal/bin/bin1.meta_lic", "testdata/reciprocal/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/reciprocal/bin/bin1.meta_lic", "testdata/reciprocal/lib/libc.a.meta_lic", "static"),
+			},
+		},
+		{
+			condition:   "reciprocal",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			expectedOut: []getMatcher{matchTarget("testdata/reciprocal/lib/libd.so.meta_lic")},
+		},
+		{
+			condition: "restricted",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/restricted/bin/bin1.meta_lic"),
+				matchTarget("testdata/restricted/bin/bin2.meta_lic"),
+				matchTarget("testdata/restricted/highest.apex.meta_lic"),
+				matchTarget("testdata/restricted/lib/liba.so.meta_lic"),
+				matchTarget("testdata/restricted/lib/libb.so.meta_lic"),
+				matchTarget("testdata/restricted/lib/libc.a.meta_lic"),
+				matchTarget("testdata/restricted/lib/libd.so.meta_lic"),
+				matchEdge("testdata/restricted/bin/bin1.meta_lic", "testdata/restricted/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/restricted/bin/bin1.meta_lic", "testdata/restricted/lib/libc.a.meta_lic", "static"),
+				matchEdge("testdata/restricted/bin/bin2.meta_lic", "testdata/restricted/lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("testdata/restricted/bin/bin2.meta_lic", "testdata/restricted/lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("testdata/restricted/highest.apex.meta_lic", "testdata/restricted/bin/bin1.meta_lic", "static"),
+				matchEdge("testdata/restricted/highest.apex.meta_lic", "testdata/restricted/bin/bin2.meta_lic", "static"),
+				matchEdge("testdata/restricted/highest.apex.meta_lic", "testdata/restricted/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/restricted/highest.apex.meta_lic", "testdata/restricted/lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/restricted/"},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchTarget("lib/liba.so.meta_lic"),
+				matchTarget("lib/libb.so.meta_lic"),
+				matchTarget("lib/libc.a.meta_lic"),
+				matchTarget("lib/libd.so.meta_lic"),
+				matchEdge("bin/bin1.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("bin/bin1.meta_lic", "lib/libc.a.meta_lic", "static"),
+				matchEdge("bin/bin2.meta_lic", "lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("bin/bin2.meta_lic", "lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("highest.apex.meta_lic", "bin/bin1.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "bin/bin2.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/restricted/", labelConditions: true},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic", "notice"),
+				matchTarget("bin/bin2.meta_lic", "notice"),
+				matchTarget("highest.apex.meta_lic", "notice"),
+				matchTarget("lib/liba.so.meta_lic", "restricted"),
+				matchTarget("lib/libb.so.meta_lic", "restricted"),
+				matchTarget("lib/libc.a.meta_lic", "reciprocal"),
+				matchTarget("lib/libd.so.meta_lic", "notice"),
+				matchEdge("bin/bin1.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("bin/bin1.meta_lic", "lib/libc.a.meta_lic", "static"),
+				matchEdge("bin/bin2.meta_lic", "lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("bin/bin2.meta_lic", "lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("highest.apex.meta_lic", "bin/bin1.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "bin/bin2.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/restricted/bin/bin1.meta_lic"),
+				matchTarget("testdata/restricted/bin/bin2.meta_lic"),
+				matchTarget("testdata/restricted/container.zip.meta_lic"),
+				matchTarget("testdata/restricted/lib/liba.so.meta_lic"),
+				matchTarget("testdata/restricted/lib/libb.so.meta_lic"),
+				matchTarget("testdata/restricted/lib/libc.a.meta_lic"),
+				matchTarget("testdata/restricted/lib/libd.so.meta_lic"),
+				matchEdge("testdata/restricted/bin/bin1.meta_lic", "testdata/restricted/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/restricted/bin/bin1.meta_lic", "testdata/restricted/lib/libc.a.meta_lic", "static"),
+				matchEdge("testdata/restricted/bin/bin2.meta_lic", "testdata/restricted/lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("testdata/restricted/bin/bin2.meta_lic", "testdata/restricted/lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("testdata/restricted/container.zip.meta_lic", "testdata/restricted/bin/bin1.meta_lic", "static"),
+				matchEdge("testdata/restricted/container.zip.meta_lic", "testdata/restricted/bin/bin2.meta_lic", "static"),
+				matchEdge("testdata/restricted/container.zip.meta_lic", "testdata/restricted/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/restricted/container.zip.meta_lic", "testdata/restricted/lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/restricted/application.meta_lic"),
+				matchTarget("testdata/restricted/bin/bin3.meta_lic"),
+				matchTarget("testdata/restricted/lib/liba.so.meta_lic"),
+				matchTarget("testdata/restricted/lib/libb.so.meta_lic"),
+				matchEdge("testdata/restricted/application.meta_lic", "testdata/restricted/bin/bin3.meta_lic", "toolchain"),
+				matchEdge("testdata/restricted/application.meta_lic", "testdata/restricted/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/restricted/application.meta_lic", "testdata/restricted/lib/libb.so.meta_lic", "dynamic"),
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/restricted/bin/bin1.meta_lic"),
+				matchTarget("testdata/restricted/lib/liba.so.meta_lic"),
+				matchTarget("testdata/restricted/lib/libc.a.meta_lic"),
+				matchEdge("testdata/restricted/bin/bin1.meta_lic", "testdata/restricted/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/restricted/bin/bin1.meta_lic", "testdata/restricted/lib/libc.a.meta_lic", "static"),
+			},
+		},
+		{
+			condition:   "restricted",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			expectedOut: []getMatcher{matchTarget("testdata/restricted/lib/libd.so.meta_lic")},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/proprietary/bin/bin1.meta_lic"),
+				matchTarget("testdata/proprietary/bin/bin2.meta_lic"),
+				matchTarget("testdata/proprietary/highest.apex.meta_lic"),
+				matchTarget("testdata/proprietary/lib/liba.so.meta_lic"),
+				matchTarget("testdata/proprietary/lib/libb.so.meta_lic"),
+				matchTarget("testdata/proprietary/lib/libc.a.meta_lic"),
+				matchTarget("testdata/proprietary/lib/libd.so.meta_lic"),
+				matchEdge("testdata/proprietary/bin/bin1.meta_lic", "testdata/proprietary/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/proprietary/bin/bin1.meta_lic", "testdata/proprietary/lib/libc.a.meta_lic", "static"),
+				matchEdge("testdata/proprietary/bin/bin2.meta_lic", "testdata/proprietary/lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("testdata/proprietary/bin/bin2.meta_lic", "testdata/proprietary/lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("testdata/proprietary/highest.apex.meta_lic", "testdata/proprietary/bin/bin1.meta_lic", "static"),
+				matchEdge("testdata/proprietary/highest.apex.meta_lic", "testdata/proprietary/bin/bin2.meta_lic", "static"),
+				matchEdge("testdata/proprietary/highest.apex.meta_lic", "testdata/proprietary/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/proprietary/highest.apex.meta_lic", "testdata/proprietary/lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/proprietary/"},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchTarget("lib/liba.so.meta_lic"),
+				matchTarget("lib/libb.so.meta_lic"),
+				matchTarget("lib/libc.a.meta_lic"),
+				matchTarget("lib/libd.so.meta_lic"),
+				matchEdge("bin/bin1.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("bin/bin1.meta_lic", "lib/libc.a.meta_lic", "static"),
+				matchEdge("bin/bin2.meta_lic", "lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("bin/bin2.meta_lic", "lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("highest.apex.meta_lic", "bin/bin1.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "bin/bin2.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/proprietary/", labelConditions: true},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic", "notice"),
+				matchTarget("bin/bin2.meta_lic", "by_exception_only", "proprietary"),
+				matchTarget("highest.apex.meta_lic", "notice"),
+				matchTarget("lib/liba.so.meta_lic", "by_exception_only", "proprietary"),
+				matchTarget("lib/libb.so.meta_lic", "restricted"),
+				matchTarget("lib/libc.a.meta_lic", "by_exception_only", "proprietary"),
+				matchTarget("lib/libd.so.meta_lic", "notice"),
+				matchEdge("bin/bin1.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("bin/bin1.meta_lic", "lib/libc.a.meta_lic", "static"),
+				matchEdge("bin/bin2.meta_lic", "lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("bin/bin2.meta_lic", "lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("highest.apex.meta_lic", "bin/bin1.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "bin/bin2.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/liba.so.meta_lic", "static"),
+				matchEdge("highest.apex.meta_lic", "lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/proprietary/bin/bin1.meta_lic"),
+				matchTarget("testdata/proprietary/bin/bin2.meta_lic"),
+				matchTarget("testdata/proprietary/container.zip.meta_lic"),
+				matchTarget("testdata/proprietary/lib/liba.so.meta_lic"),
+				matchTarget("testdata/proprietary/lib/libb.so.meta_lic"),
+				matchTarget("testdata/proprietary/lib/libc.a.meta_lic"),
+				matchTarget("testdata/proprietary/lib/libd.so.meta_lic"),
+				matchEdge("testdata/proprietary/bin/bin1.meta_lic", "testdata/proprietary/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/proprietary/bin/bin1.meta_lic", "testdata/proprietary/lib/libc.a.meta_lic", "static"),
+				matchEdge("testdata/proprietary/bin/bin2.meta_lic", "testdata/proprietary/lib/libb.so.meta_lic", "dynamic"),
+				matchEdge("testdata/proprietary/bin/bin2.meta_lic", "testdata/proprietary/lib/libd.so.meta_lic", "dynamic"),
+				matchEdge("testdata/proprietary/container.zip.meta_lic", "testdata/proprietary/bin/bin1.meta_lic", "static"),
+				matchEdge("testdata/proprietary/container.zip.meta_lic", "testdata/proprietary/bin/bin2.meta_lic", "static"),
+				matchEdge("testdata/proprietary/container.zip.meta_lic", "testdata/proprietary/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/proprietary/container.zip.meta_lic", "testdata/proprietary/lib/libb.so.meta_lic", "static"),
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/proprietary/application.meta_lic"),
+				matchTarget("testdata/proprietary/bin/bin3.meta_lic"),
+				matchTarget("testdata/proprietary/lib/liba.so.meta_lic"),
+				matchTarget("testdata/proprietary/lib/libb.so.meta_lic"),
+				matchEdge("testdata/proprietary/application.meta_lic", "testdata/proprietary/bin/bin3.meta_lic", "toolchain"),
+				matchEdge("testdata/proprietary/application.meta_lic", "testdata/proprietary/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/proprietary/application.meta_lic", "testdata/proprietary/lib/libb.so.meta_lic", "dynamic"),
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/proprietary/bin/bin1.meta_lic"),
+				matchTarget("testdata/proprietary/lib/liba.so.meta_lic"),
+				matchTarget("testdata/proprietary/lib/libc.a.meta_lic"),
+				matchEdge("testdata/proprietary/bin/bin1.meta_lic", "testdata/proprietary/lib/liba.so.meta_lic", "static"),
+				matchEdge("testdata/proprietary/bin/bin1.meta_lic", "testdata/proprietary/lib/libc.a.meta_lic", "static"),
+			},
+		},
+		{
+			condition:   "proprietary",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			expectedOut: []getMatcher{matchTarget("testdata/proprietary/lib/libd.so.meta_lic")},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.condition+" "+tt.name, func(t *testing.T) {
+			ctx := &testContext{0, make(map[string]string)}
+
+			expectedOut := &bytes.Buffer{}
+			for _, eo := range tt.expectedOut {
+				m := eo(ctx)
+				expectedOut.WriteString(m.matchString(ctx))
+				expectedOut.WriteString("\n")
+			}
+
+			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)
+			}
+			tt.ctx.graphViz = true
+			err := dumpGraph(&tt.ctx, stdout, stderr, rootFiles...)
+			if err != nil {
+				t.Fatalf("dumpgraph: error = %v, stderr = %v", err, stderr)
+				return
+			}
+			if stderr.Len() > 0 {
+				t.Errorf("dumpgraph: gotStderr = %v, want none", stderr)
+			}
+			outList := strings.Split(stdout.String(), "\n")
+			outLine := 0
+			if outList[outLine] != "strict digraph {" {
+				t.Errorf("dumpgraph: got 1st line %v, want strict digraph {")
+			}
+			outLine++
+			if strings.HasPrefix(strings.TrimLeft(outList[outLine], " \t"), "rankdir") {
+				outLine++
+			}
+			endOut := len(outList)
+			for endOut > 0 && strings.TrimLeft(outList[endOut-1], " \t") == "" {
+				endOut--
+			}
+			if outList[endOut-1] != "}" {
+				t.Errorf("dumpgraph: got last line %v, want }", outList[endOut-1])
+			}
+			endOut--
+			if strings.HasPrefix(strings.TrimLeft(outList[endOut-1], " \t"), "{rank=same") {
+				endOut--
+			}
+			expectedList := strings.Split(expectedOut.String(), "\n")
+			for len(expectedList) > 0 && expectedList[len(expectedList)-1] == "" {
+				expectedList = expectedList[0 : len(expectedList)-1]
+			}
+			matchLine := 0
+
+			for outLine < endOut && matchLine < len(expectedList) && strings.TrimLeft(outList[outLine], " \t") == expectedList[matchLine] {
+				outLine++
+				matchLine++
+			}
+			if outLine < endOut || matchLine < len(expectedList) {
+				if outLine >= endOut {
+					t.Errorf("dumpgraph: missing lines at end of graph, want %d lines %v", len(expectedList)-matchLine, strings.Join(expectedList[matchLine:], "\n"))
+				} else if matchLine >= len(expectedList) {
+					t.Errorf("dumpgraph: unexpected lines at end of graph starting line %d, got %v, want nothing", outLine+1, strings.Join(outList[outLine:], "\n"))
+				} else {
+					t.Errorf("dumpgraph: at line %d, got %v, want %v", outLine+1, strings.Join(outList[outLine:], "\n"), strings.Join(expectedList[matchLine:], "\n"))
+				}
+			}
+		})
+	}
+}
diff --git a/tools/compliance/cmd/dumpresolutions.go b/tools/compliance/cmd/dumpresolutions.go
new file mode 100644
index 0000000..36fbb7d
--- /dev/null
+++ b/tools/compliance/cmd/dumpresolutions.go
@@ -0,0 +1,284 @@
+// Copyright 2021 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 (
+	"compliance"
+	"flag"
+	"fmt"
+	"io"
+	"os"
+	"path/filepath"
+	"sort"
+	"strings"
+)
+
+var (
+	conditions      = newMultiString("c", "License condition to resolve. (may be given multiple times)")
+	graphViz        = flag.Bool("dot", false, "Whether to output graphviz (i.e. dot) format.")
+	labelConditions = flag.Bool("label_conditions", false, "Whether to label target nodes with conditions.")
+	stripPrefix     = flag.String("strip_prefix", "", "Prefix to remove from paths. i.e. path to root")
+
+	failNoneRequested = fmt.Errorf("\nNo license metadata files requested")
+	failNoLicenses = fmt.Errorf("No licenses found")
+)
+
+type context struct {
+	conditions      []string
+	graphViz        bool
+	labelConditions bool
+	stripPrefix     string
+}
+
+func init() {
+	flag.Usage = func() {
+		fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...}
+
+Outputs a space-separated Target ActsOn Origin Condition tuple for each
+resolution in the graph. When -dot flag given, outputs nodes and edges
+in graphviz directed graph format.
+
+If one or more '-c condition' conditions are given, outputs the joined
+set of resolutions for all of the conditions. Otherwise, outputs the
+result of the bottom-up and top-down resolve only.
+
+In plain text mode, when '-label_conditions' is requested, the Target
+and Origin have colon-separated license conditions appended:
+i.e. target:condition1:condition2 etc.
+
+Options:
+`, filepath.Base(os.Args[0]))
+		flag.PrintDefaults()
+	}
+}
+
+// newMultiString creates a flag that allows multiple values in an array.
+func newMultiString(name, usage string) *multiString {
+	var f multiString
+	flag.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() {
+	flag.Parse()
+
+	// Must specify at least one root target.
+	if flag.NArg() == 0 {
+		flag.Usage()
+		os.Exit(2)
+	}
+
+	ctx := &context{
+		conditions:      append([]string{}, *conditions...),
+		graphViz:        *graphViz,
+		labelConditions: *labelConditions,
+		stripPrefix:     *stripPrefix,
+	}
+	err := dumpResolutions(ctx, os.Stdout, os.Stderr, flag.Args()...)
+	if err != nil {
+		if err == failNoneRequested {
+			flag.Usage()
+		}
+		fmt.Fprintf(os.Stderr, "%s\n", err.Error())
+		os.Exit(1)
+	}
+	os.Exit(0)
+}
+
+// dumpResolutions implements the dumpresolutions utility.
+func dumpResolutions(ctx *context, stdout, stderr io.Writer, files ...string) error {
+	if len(files) < 1 {
+		return failNoneRequested
+	}
+
+	// Read the license graph from the license metadata files (*.meta_lic).
+	licenseGraph, err := compliance.ReadLicenseGraph(os.DirFS("."), stderr, files)
+	if err != nil {
+		return fmt.Errorf("Unable to read license metadata file(s) %q: %v\n", files, err)
+	}
+	if licenseGraph == nil {
+		return failNoLicenses
+	}
+
+	// resolutions will contain the requested set of resolutions.
+	var resolutions *compliance.ResolutionSet
+
+	resolutions = compliance.ResolveTopDownConditions(licenseGraph)
+	if len(ctx.conditions) > 0 {
+		rlist := make([]*compliance.ResolutionSet, 0, len(ctx.conditions))
+		for _, c := range ctx.conditions {
+			rlist = append(rlist, compliance.WalkResolutionsForCondition(licenseGraph, resolutions, compliance.ConditionNames{c}))
+		}
+		if len(rlist) == 1 {
+			resolutions = rlist[0]
+		} else {
+			resolutions = compliance.JoinResolutionSets(rlist...)
+		}
+	}
+
+	// nodes maps license metadata file names to graphViz node names when graphViz requested.
+	nodes := make(map[string]string)
+	n := 0
+
+	// targetOut calculates the string to output for `target` adding `sep`-separated conditions as needed.
+	targetOut := func(target *compliance.TargetNode, sep string) string {
+		tOut := strings.TrimPrefix(target.Name(), ctx.stripPrefix)
+		if ctx.labelConditions {
+			conditions := make([]string, 0, target.LicenseConditions().Count())
+			for _, lc := range target.LicenseConditions().AsList() {
+				conditions = append(conditions, lc.Name())
+			}
+			sort.Strings(conditions)
+			if len(conditions) > 0 {
+				tOut += sep + strings.Join(conditions, sep)
+			}
+		}
+		return tOut
+	}
+
+	// makeNode maps `target` to a graphViz node name.
+	makeNode := func(target *compliance.TargetNode) {
+		tName := target.Name()
+		if _, ok := nodes[tName]; !ok {
+			nodeName := fmt.Sprintf("n%d", n)
+			nodes[tName] = nodeName
+			fmt.Fprintf(stdout, "\t%s [label=\"%s\"];\n", nodeName, targetOut(target, "\\n"))
+			n++
+		}
+	}
+
+	// outputResolution prints a resolution in the requested format to `stdout`, where one can read
+	// a resolution as `tname` resolves `oname`'s conditions named in `cnames`.
+	// `tname` is the name of the target the resolution applies to.
+	// `oname` is the name of the target where the conditions originate.
+	// `cnames` is the list of conditions to resolve.
+	outputResolution := func(tname, aname, oname string, cnames []string) {
+		if ctx.graphViz {
+			// ... one edge per line labelled with \\n-separated annotations.
+			tNode := nodes[tname]
+			aNode := nodes[aname]
+			oNode := nodes[oname]
+			fmt.Fprintf(stdout, "\t%s -> %s; %s -> %s [label=\"%s\"];\n", tNode, aNode, aNode, oNode, strings.Join(cnames, "\\n"))
+		} else {
+			// ... one edge per line with names in a colon-separated tuple.
+			fmt.Fprintf(stdout, "%s %s %s %s\n", tname, aname, oname, strings.Join(cnames, ":"))
+		}
+	}
+
+	// outputSingleton prints `tname` to plain text in the unexpected event that `tname` is the name of
+	// a target in `resolutions.AppliesTo()` but has no conditions to resolve.
+	outputSingleton := func(tname, aname string) {
+		if !ctx.graphViz {
+			fmt.Fprintf(stdout, "%s %s\n", tname, aname)
+		}
+	}
+
+	// Sort the resolutions by targetname for repeatability/stability.
+	targets := resolutions.AttachesTo()
+	sort.Sort(targets)
+
+	// If graphviz output, start the directed graph.
+	if ctx.graphViz {
+		fmt.Fprintf(stdout, "strict digraph {\n\trankdir=LR;\n")
+		for _, target := range targets {
+			makeNode(target)
+			rl := compliance.ResolutionList(resolutions.Resolutions(target))
+			sort.Sort(rl)
+			for _, r := range rl {
+				makeNode(r.ActsOn())
+			}
+			conditions := rl.AllConditions().AsList()
+			sort.Sort(conditions)
+			for _, lc := range conditions {
+				makeNode(lc.Origin())
+			}
+		}
+	}
+
+	// Output the sorted targets.
+	for _, target := range targets {
+		var tname string
+		if ctx.graphViz {
+			tname = target.Name()
+		} else {
+			tname = targetOut(target, ":")
+		}
+
+		rl := compliance.ResolutionList(resolutions.Resolutions(target))
+		sort.Sort(rl)
+		for _, r := range rl {
+			var aname string
+			if ctx.graphViz {
+				aname = r.ActsOn().Name()
+			} else {
+				aname = targetOut(r.ActsOn(), ":")
+			}
+
+			conditions := r.Resolves().AsList()
+			sort.Sort(conditions)
+
+			// poname is the previous origin name or "" if no previous
+			poname := ""
+
+			// cnames accumulates the list of condition names originating at a single origin that apply to `target`.
+			cnames := make([]string, 0, len(conditions))
+
+			// Output 1 line for each attachesTo+actsOn+origin combination.
+			for _, condition := range conditions {
+				var oname string
+				if ctx.graphViz {
+					oname = condition.Origin().Name()
+				} else {
+					oname = targetOut(condition.Origin(), ":")
+				}
+
+				// Detect when origin changes and output prior origin's conditions.
+				if poname != oname && poname != "" {
+					outputResolution(tname, aname, poname, cnames)
+					cnames = cnames[:0]
+				}
+				poname = oname
+				cnames = append(cnames, condition.Name())
+			}
+			// Output last origin's conditions or a singleton if no origins.
+			if poname == "" {
+				outputSingleton(tname, aname)
+			} else {
+				outputResolution(tname, aname, poname, cnames)
+			}
+		}
+	}
+	// If graphViz output, rank the root nodes together, and complete the directed graph.
+	if ctx.graphViz {
+		fmt.Fprintf(stdout, "\t{rank=same;")
+		for _, f := range files {
+			fName := f
+			if !strings.HasSuffix(fName, ".meta_lic") {
+				fName += ".meta_lic"
+			}
+			if fNode, ok := nodes[fName]; ok {
+				fmt.Fprintf(stdout, " %s", fNode)
+			}
+		}
+		fmt.Fprintf(stdout, "}\n}\n")
+	}
+	return nil
+}
diff --git a/tools/compliance/cmd/dumpresolutions_test.go b/tools/compliance/cmd/dumpresolutions_test.go
new file mode 100644
index 0000000..1328a36
--- /dev/null
+++ b/tools/compliance/cmd/dumpresolutions_test.go
@@ -0,0 +1,4544 @@
+// Copyright 2021 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"
+	"fmt"
+	"strings"
+	"testing"
+)
+
+func Test_plaintext(t *testing.T) {
+	tests := []struct {
+		condition   string
+		name        string
+		roots       []string
+		ctx         context
+		expectedOut []string
+	}{
+		{
+			condition: "firstparty",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []string{
+				"testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/bin/bin1.meta_lic notice",
+				"testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/lib/liba.so.meta_lic testdata/firstparty/lib/liba.so.meta_lic notice",
+				"testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/lib/libc.a.meta_lic testdata/firstparty/lib/libc.a.meta_lic notice",
+				"testdata/firstparty/bin/bin2.meta_lic testdata/firstparty/bin/bin2.meta_lic testdata/firstparty/bin/bin2.meta_lic notice",
+				"testdata/firstparty/highest.apex.meta_lic testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/bin/bin1.meta_lic notice",
+				"testdata/firstparty/highest.apex.meta_lic testdata/firstparty/bin/bin2.meta_lic testdata/firstparty/bin/bin2.meta_lic notice",
+				"testdata/firstparty/highest.apex.meta_lic testdata/firstparty/highest.apex.meta_lic testdata/firstparty/highest.apex.meta_lic notice",
+				"testdata/firstparty/highest.apex.meta_lic testdata/firstparty/lib/liba.so.meta_lic testdata/firstparty/lib/liba.so.meta_lic notice",
+				"testdata/firstparty/highest.apex.meta_lic testdata/firstparty/lib/libb.so.meta_lic testdata/firstparty/lib/libb.so.meta_lic notice",
+				"testdata/firstparty/highest.apex.meta_lic testdata/firstparty/lib/libc.a.meta_lic testdata/firstparty/lib/libc.a.meta_lic notice",
+				"testdata/firstparty/lib/liba.so.meta_lic testdata/firstparty/lib/liba.so.meta_lic testdata/firstparty/lib/liba.so.meta_lic notice",
+				"testdata/firstparty/lib/libb.so.meta_lic testdata/firstparty/lib/libb.so.meta_lic testdata/firstparty/lib/libb.so.meta_lic notice",
+				"testdata/firstparty/lib/libc.a.meta_lic testdata/firstparty/lib/libc.a.meta_lic testdata/firstparty/lib/libc.a.meta_lic notice",
+				"testdata/firstparty/lib/libd.so.meta_lic testdata/firstparty/lib/libd.so.meta_lic testdata/firstparty/lib/libd.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/firstparty/"},
+			expectedOut: []string{
+				"bin/bin1.meta_lic bin/bin1.meta_lic bin/bin1.meta_lic notice",
+				"bin/bin1.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic notice",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic lib/libc.a.meta_lic notice",
+				"bin/bin2.meta_lic bin/bin2.meta_lic bin/bin2.meta_lic notice",
+				"highest.apex.meta_lic bin/bin1.meta_lic bin/bin1.meta_lic notice",
+				"highest.apex.meta_lic bin/bin2.meta_lic bin/bin2.meta_lic notice",
+				"highest.apex.meta_lic highest.apex.meta_lic highest.apex.meta_lic notice",
+				"highest.apex.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic notice",
+				"highest.apex.meta_lic lib/libb.so.meta_lic lib/libb.so.meta_lic notice",
+				"highest.apex.meta_lic lib/libc.a.meta_lic lib/libc.a.meta_lic notice",
+				"lib/liba.so.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic notice",
+				"lib/libb.so.meta_lic lib/libb.so.meta_lic lib/libb.so.meta_lic notice",
+				"lib/libc.a.meta_lic lib/libc.a.meta_lic lib/libc.a.meta_lic notice",
+				"lib/libd.so.meta_lic lib/libd.so.meta_lic lib/libd.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "apex_trimmed_notice",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"notice"},
+				stripPrefix: "testdata/firstparty/",
+			},
+			expectedOut: []string{
+				"bin/bin1.meta_lic bin/bin1.meta_lic bin/bin1.meta_lic notice",
+				"bin/bin1.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic notice",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic lib/libc.a.meta_lic notice",
+				"bin/bin2.meta_lic bin/bin2.meta_lic bin/bin2.meta_lic notice",
+				"highest.apex.meta_lic bin/bin1.meta_lic bin/bin1.meta_lic notice",
+				"highest.apex.meta_lic bin/bin2.meta_lic bin/bin2.meta_lic notice",
+				"highest.apex.meta_lic highest.apex.meta_lic highest.apex.meta_lic notice",
+				"highest.apex.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic notice",
+				"highest.apex.meta_lic lib/libb.so.meta_lic lib/libb.so.meta_lic notice",
+				"highest.apex.meta_lic lib/libc.a.meta_lic lib/libc.a.meta_lic notice",
+				"lib/liba.so.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic notice",
+				"lib/libb.so.meta_lic lib/libb.so.meta_lic lib/libb.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "apex_trimmed_share",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"reciprocal", "restricted"},
+				stripPrefix: "testdata/firstparty/",
+			},
+			expectedOut: []string{},
+		},
+		{
+			condition: "firstparty",
+			name:      "apex_trimmed_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"proprietary"},
+				stripPrefix: "testdata/firstparty/",
+			},
+			expectedOut: []string{},
+		},
+		{
+			condition: "firstparty",
+			name:      "apex_trimmed_share_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"reciprocal", "restricted", "proprietary"},
+				stripPrefix: "testdata/firstparty/",
+			},
+			expectedOut: []string{},
+		},
+		{
+			condition: "firstparty",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/firstparty/", labelConditions: true},
+			expectedOut: []string{
+				"bin/bin1.meta_lic:notice bin/bin1.meta_lic:notice bin/bin1.meta_lic:notice notice",
+				"bin/bin1.meta_lic:notice lib/liba.so.meta_lic:notice lib/liba.so.meta_lic:notice notice",
+				"bin/bin1.meta_lic:notice lib/libc.a.meta_lic:notice lib/libc.a.meta_lic:notice notice",
+				"bin/bin2.meta_lic:notice bin/bin2.meta_lic:notice bin/bin2.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice bin/bin1.meta_lic:notice bin/bin1.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice bin/bin2.meta_lic:notice bin/bin2.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice highest.apex.meta_lic:notice highest.apex.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice lib/liba.so.meta_lic:notice lib/liba.so.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice lib/libb.so.meta_lic:notice lib/libb.so.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice lib/libc.a.meta_lic:notice lib/libc.a.meta_lic:notice notice",
+				"lib/liba.so.meta_lic:notice lib/liba.so.meta_lic:notice lib/liba.so.meta_lic:notice notice",
+				"lib/libb.so.meta_lic:notice lib/libb.so.meta_lic:notice lib/libb.so.meta_lic:notice notice",
+				"lib/libc.a.meta_lic:notice lib/libc.a.meta_lic:notice lib/libc.a.meta_lic:notice notice",
+				"lib/libd.so.meta_lic:notice lib/libd.so.meta_lic:notice lib/libd.so.meta_lic:notice notice",
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []string{
+				"testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/bin/bin1.meta_lic notice",
+				"testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/lib/liba.so.meta_lic testdata/firstparty/lib/liba.so.meta_lic notice",
+				"testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/lib/libc.a.meta_lic testdata/firstparty/lib/libc.a.meta_lic notice",
+				"testdata/firstparty/bin/bin2.meta_lic testdata/firstparty/bin/bin2.meta_lic testdata/firstparty/bin/bin2.meta_lic notice",
+				"testdata/firstparty/container.zip.meta_lic testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/bin/bin1.meta_lic notice",
+				"testdata/firstparty/container.zip.meta_lic testdata/firstparty/bin/bin2.meta_lic testdata/firstparty/bin/bin2.meta_lic notice",
+				"testdata/firstparty/container.zip.meta_lic testdata/firstparty/container.zip.meta_lic testdata/firstparty/container.zip.meta_lic notice",
+				"testdata/firstparty/container.zip.meta_lic testdata/firstparty/lib/liba.so.meta_lic testdata/firstparty/lib/liba.so.meta_lic notice",
+				"testdata/firstparty/container.zip.meta_lic testdata/firstparty/lib/libb.so.meta_lic testdata/firstparty/lib/libb.so.meta_lic notice",
+				"testdata/firstparty/container.zip.meta_lic testdata/firstparty/lib/libc.a.meta_lic testdata/firstparty/lib/libc.a.meta_lic notice",
+				"testdata/firstparty/lib/liba.so.meta_lic testdata/firstparty/lib/liba.so.meta_lic testdata/firstparty/lib/liba.so.meta_lic notice",
+				"testdata/firstparty/lib/libb.so.meta_lic testdata/firstparty/lib/libb.so.meta_lic testdata/firstparty/lib/libb.so.meta_lic notice",
+				"testdata/firstparty/lib/libc.a.meta_lic testdata/firstparty/lib/libc.a.meta_lic testdata/firstparty/lib/libc.a.meta_lic notice",
+				"testdata/firstparty/lib/libd.so.meta_lic testdata/firstparty/lib/libd.so.meta_lic testdata/firstparty/lib/libd.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []string{
+				"testdata/firstparty/application.meta_lic testdata/firstparty/application.meta_lic testdata/firstparty/application.meta_lic notice",
+				"testdata/firstparty/application.meta_lic testdata/firstparty/lib/liba.so.meta_lic testdata/firstparty/lib/liba.so.meta_lic notice",
+				"testdata/firstparty/bin/bin3.meta_lic testdata/firstparty/bin/bin3.meta_lic testdata/firstparty/bin/bin3.meta_lic notice",
+				"testdata/firstparty/lib/liba.so.meta_lic testdata/firstparty/lib/liba.so.meta_lic testdata/firstparty/lib/liba.so.meta_lic notice",
+				"testdata/firstparty/lib/libb.so.meta_lic testdata/firstparty/lib/libb.so.meta_lic testdata/firstparty/lib/libb.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []string{
+				"testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/bin/bin1.meta_lic notice",
+				"testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/lib/liba.so.meta_lic testdata/firstparty/lib/liba.so.meta_lic notice",
+				"testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/lib/libc.a.meta_lic testdata/firstparty/lib/libc.a.meta_lic notice",
+				"testdata/firstparty/lib/liba.so.meta_lic testdata/firstparty/lib/liba.so.meta_lic testdata/firstparty/lib/liba.so.meta_lic notice",
+				"testdata/firstparty/lib/libc.a.meta_lic testdata/firstparty/lib/libc.a.meta_lic testdata/firstparty/lib/libc.a.meta_lic notice",
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []string{
+				"testdata/firstparty/lib/libd.so.meta_lic testdata/firstparty/lib/libd.so.meta_lic testdata/firstparty/lib/libd.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []string{
+				"testdata/notice/bin/bin1.meta_lic testdata/notice/bin/bin1.meta_lic testdata/notice/bin/bin1.meta_lic notice",
+				"testdata/notice/bin/bin1.meta_lic testdata/notice/lib/liba.so.meta_lic testdata/notice/lib/liba.so.meta_lic notice",
+				"testdata/notice/bin/bin1.meta_lic testdata/notice/lib/libc.a.meta_lic testdata/notice/lib/libc.a.meta_lic notice",
+				"testdata/notice/bin/bin2.meta_lic testdata/notice/bin/bin2.meta_lic testdata/notice/bin/bin2.meta_lic notice",
+				"testdata/notice/highest.apex.meta_lic testdata/notice/bin/bin1.meta_lic testdata/notice/bin/bin1.meta_lic notice",
+				"testdata/notice/highest.apex.meta_lic testdata/notice/bin/bin2.meta_lic testdata/notice/bin/bin2.meta_lic notice",
+				"testdata/notice/highest.apex.meta_lic testdata/notice/highest.apex.meta_lic testdata/notice/highest.apex.meta_lic notice",
+				"testdata/notice/highest.apex.meta_lic testdata/notice/lib/liba.so.meta_lic testdata/notice/lib/liba.so.meta_lic notice",
+				"testdata/notice/highest.apex.meta_lic testdata/notice/lib/libb.so.meta_lic testdata/notice/lib/libb.so.meta_lic notice",
+				"testdata/notice/highest.apex.meta_lic testdata/notice/lib/libc.a.meta_lic testdata/notice/lib/libc.a.meta_lic notice",
+				"testdata/notice/lib/liba.so.meta_lic testdata/notice/lib/liba.so.meta_lic testdata/notice/lib/liba.so.meta_lic notice",
+				"testdata/notice/lib/libb.so.meta_lic testdata/notice/lib/libb.so.meta_lic testdata/notice/lib/libb.so.meta_lic notice",
+				"testdata/notice/lib/libc.a.meta_lic testdata/notice/lib/libc.a.meta_lic testdata/notice/lib/libc.a.meta_lic notice",
+				"testdata/notice/lib/libd.so.meta_lic testdata/notice/lib/libd.so.meta_lic testdata/notice/lib/libd.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/notice/"},
+			expectedOut: []string{
+				"bin/bin1.meta_lic bin/bin1.meta_lic bin/bin1.meta_lic notice",
+				"bin/bin1.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic notice",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic lib/libc.a.meta_lic notice",
+				"bin/bin2.meta_lic bin/bin2.meta_lic bin/bin2.meta_lic notice",
+				"highest.apex.meta_lic bin/bin1.meta_lic bin/bin1.meta_lic notice",
+				"highest.apex.meta_lic bin/bin2.meta_lic bin/bin2.meta_lic notice",
+				"highest.apex.meta_lic highest.apex.meta_lic highest.apex.meta_lic notice",
+				"highest.apex.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic notice",
+				"highest.apex.meta_lic lib/libb.so.meta_lic lib/libb.so.meta_lic notice",
+				"highest.apex.meta_lic lib/libc.a.meta_lic lib/libc.a.meta_lic notice",
+				"lib/liba.so.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic notice",
+				"lib/libb.so.meta_lic lib/libb.so.meta_lic lib/libb.so.meta_lic notice",
+				"lib/libc.a.meta_lic lib/libc.a.meta_lic lib/libc.a.meta_lic notice",
+				"lib/libd.so.meta_lic lib/libd.so.meta_lic lib/libd.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "apex_trimmed_notice",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"notice"},
+				stripPrefix: "testdata/notice/",
+			},
+			expectedOut: []string{
+				"bin/bin1.meta_lic bin/bin1.meta_lic bin/bin1.meta_lic notice",
+				"bin/bin1.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic notice",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic lib/libc.a.meta_lic notice",
+				"bin/bin2.meta_lic bin/bin2.meta_lic bin/bin2.meta_lic notice",
+				"highest.apex.meta_lic bin/bin1.meta_lic bin/bin1.meta_lic notice",
+				"highest.apex.meta_lic bin/bin2.meta_lic bin/bin2.meta_lic notice",
+				"highest.apex.meta_lic highest.apex.meta_lic highest.apex.meta_lic notice",
+				"highest.apex.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic notice",
+				"highest.apex.meta_lic lib/libb.so.meta_lic lib/libb.so.meta_lic notice",
+				"highest.apex.meta_lic lib/libc.a.meta_lic lib/libc.a.meta_lic notice",
+				"lib/liba.so.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic notice",
+				"lib/libb.so.meta_lic lib/libb.so.meta_lic lib/libb.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "apex_trimmed_share",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"reciprocal", "restricted"},
+				stripPrefix: "testdata/notice/",
+			},
+			expectedOut: []string{},
+		},
+		{
+			condition: "notice",
+			name:      "apex_trimmed_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"proprietary"},
+				stripPrefix: "testdata/notice/",
+			},
+			expectedOut: []string{},
+		},
+		{
+			condition: "notice",
+			name:      "apex_trimmed_share_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"reciprocal", "restricted", "proprietary"},
+				stripPrefix: "testdata/notice/",
+			},
+			expectedOut: []string{},
+		},
+		{
+			condition: "notice",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/notice/", labelConditions: true},
+			expectedOut: []string{
+				"bin/bin1.meta_lic:notice bin/bin1.meta_lic:notice bin/bin1.meta_lic:notice notice",
+				"bin/bin1.meta_lic:notice lib/liba.so.meta_lic:notice lib/liba.so.meta_lic:notice notice",
+				"bin/bin1.meta_lic:notice lib/libc.a.meta_lic:notice lib/libc.a.meta_lic:notice notice",
+				"bin/bin2.meta_lic:notice bin/bin2.meta_lic:notice bin/bin2.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice bin/bin1.meta_lic:notice bin/bin1.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice bin/bin2.meta_lic:notice bin/bin2.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice highest.apex.meta_lic:notice highest.apex.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice lib/liba.so.meta_lic:notice lib/liba.so.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice lib/libb.so.meta_lic:notice lib/libb.so.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice lib/libc.a.meta_lic:notice lib/libc.a.meta_lic:notice notice",
+				"lib/liba.so.meta_lic:notice lib/liba.so.meta_lic:notice lib/liba.so.meta_lic:notice notice",
+				"lib/libb.so.meta_lic:notice lib/libb.so.meta_lic:notice lib/libb.so.meta_lic:notice notice",
+				"lib/libc.a.meta_lic:notice lib/libc.a.meta_lic:notice lib/libc.a.meta_lic:notice notice",
+				"lib/libd.so.meta_lic:notice lib/libd.so.meta_lic:notice lib/libd.so.meta_lic:notice notice",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []string{
+				"testdata/notice/bin/bin1.meta_lic testdata/notice/bin/bin1.meta_lic testdata/notice/bin/bin1.meta_lic notice",
+				"testdata/notice/bin/bin1.meta_lic testdata/notice/lib/liba.so.meta_lic testdata/notice/lib/liba.so.meta_lic notice",
+				"testdata/notice/bin/bin1.meta_lic testdata/notice/lib/libc.a.meta_lic testdata/notice/lib/libc.a.meta_lic notice",
+				"testdata/notice/bin/bin2.meta_lic testdata/notice/bin/bin2.meta_lic testdata/notice/bin/bin2.meta_lic notice",
+				"testdata/notice/container.zip.meta_lic testdata/notice/bin/bin1.meta_lic testdata/notice/bin/bin1.meta_lic notice",
+				"testdata/notice/container.zip.meta_lic testdata/notice/bin/bin2.meta_lic testdata/notice/bin/bin2.meta_lic notice",
+				"testdata/notice/container.zip.meta_lic testdata/notice/container.zip.meta_lic testdata/notice/container.zip.meta_lic notice",
+				"testdata/notice/container.zip.meta_lic testdata/notice/lib/liba.so.meta_lic testdata/notice/lib/liba.so.meta_lic notice",
+				"testdata/notice/container.zip.meta_lic testdata/notice/lib/libb.so.meta_lic testdata/notice/lib/libb.so.meta_lic notice",
+				"testdata/notice/container.zip.meta_lic testdata/notice/lib/libc.a.meta_lic testdata/notice/lib/libc.a.meta_lic notice",
+				"testdata/notice/lib/liba.so.meta_lic testdata/notice/lib/liba.so.meta_lic testdata/notice/lib/liba.so.meta_lic notice",
+				"testdata/notice/lib/libb.so.meta_lic testdata/notice/lib/libb.so.meta_lic testdata/notice/lib/libb.so.meta_lic notice",
+				"testdata/notice/lib/libc.a.meta_lic testdata/notice/lib/libc.a.meta_lic testdata/notice/lib/libc.a.meta_lic notice",
+				"testdata/notice/lib/libd.so.meta_lic testdata/notice/lib/libd.so.meta_lic testdata/notice/lib/libd.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []string{
+				"testdata/notice/application.meta_lic testdata/notice/application.meta_lic testdata/notice/application.meta_lic notice",
+				"testdata/notice/application.meta_lic testdata/notice/lib/liba.so.meta_lic testdata/notice/lib/liba.so.meta_lic notice",
+				"testdata/notice/bin/bin3.meta_lic testdata/notice/bin/bin3.meta_lic testdata/notice/bin/bin3.meta_lic notice",
+				"testdata/notice/lib/liba.so.meta_lic testdata/notice/lib/liba.so.meta_lic testdata/notice/lib/liba.so.meta_lic notice",
+				"testdata/notice/lib/libb.so.meta_lic testdata/notice/lib/libb.so.meta_lic testdata/notice/lib/libb.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []string{
+				"testdata/notice/bin/bin1.meta_lic testdata/notice/bin/bin1.meta_lic testdata/notice/bin/bin1.meta_lic notice",
+				"testdata/notice/bin/bin1.meta_lic testdata/notice/lib/liba.so.meta_lic testdata/notice/lib/liba.so.meta_lic notice",
+				"testdata/notice/bin/bin1.meta_lic testdata/notice/lib/libc.a.meta_lic testdata/notice/lib/libc.a.meta_lic notice",
+				"testdata/notice/lib/liba.so.meta_lic testdata/notice/lib/liba.so.meta_lic testdata/notice/lib/liba.so.meta_lic notice",
+				"testdata/notice/lib/libc.a.meta_lic testdata/notice/lib/libc.a.meta_lic testdata/notice/lib/libc.a.meta_lic notice",
+			},
+		},
+		{
+			condition: "notice",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []string{
+				"testdata/notice/lib/libd.so.meta_lic testdata/notice/lib/libd.so.meta_lic testdata/notice/lib/libd.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []string{
+				"testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/bin/bin1.meta_lic notice",
+				"testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/lib/liba.so.meta_lic testdata/reciprocal/lib/liba.so.meta_lic reciprocal",
+				"testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/lib/libc.a.meta_lic testdata/reciprocal/lib/libc.a.meta_lic reciprocal",
+				"testdata/reciprocal/bin/bin2.meta_lic testdata/reciprocal/bin/bin2.meta_lic testdata/reciprocal/bin/bin2.meta_lic notice",
+				"testdata/reciprocal/highest.apex.meta_lic testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/bin/bin1.meta_lic notice",
+				"testdata/reciprocal/highest.apex.meta_lic testdata/reciprocal/bin/bin2.meta_lic testdata/reciprocal/bin/bin2.meta_lic notice",
+				"testdata/reciprocal/highest.apex.meta_lic testdata/reciprocal/highest.apex.meta_lic testdata/reciprocal/highest.apex.meta_lic notice",
+				"testdata/reciprocal/highest.apex.meta_lic testdata/reciprocal/lib/liba.so.meta_lic testdata/reciprocal/lib/liba.so.meta_lic reciprocal",
+				"testdata/reciprocal/highest.apex.meta_lic testdata/reciprocal/lib/libb.so.meta_lic testdata/reciprocal/lib/libb.so.meta_lic notice",
+				"testdata/reciprocal/highest.apex.meta_lic testdata/reciprocal/lib/libc.a.meta_lic testdata/reciprocal/lib/libc.a.meta_lic reciprocal",
+				"testdata/reciprocal/lib/liba.so.meta_lic testdata/reciprocal/lib/liba.so.meta_lic testdata/reciprocal/lib/liba.so.meta_lic reciprocal",
+				"testdata/reciprocal/lib/libb.so.meta_lic testdata/reciprocal/lib/libb.so.meta_lic testdata/reciprocal/lib/libb.so.meta_lic notice",
+				"testdata/reciprocal/lib/libc.a.meta_lic testdata/reciprocal/lib/libc.a.meta_lic testdata/reciprocal/lib/libc.a.meta_lic reciprocal",
+				"testdata/reciprocal/lib/libd.so.meta_lic testdata/reciprocal/lib/libd.so.meta_lic testdata/reciprocal/lib/libd.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/reciprocal/"},
+			expectedOut: []string{
+				"bin/bin1.meta_lic bin/bin1.meta_lic bin/bin1.meta_lic notice",
+				"bin/bin1.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic reciprocal",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic lib/libc.a.meta_lic reciprocal",
+				"bin/bin2.meta_lic bin/bin2.meta_lic bin/bin2.meta_lic notice",
+				"highest.apex.meta_lic bin/bin1.meta_lic bin/bin1.meta_lic notice",
+				"highest.apex.meta_lic bin/bin2.meta_lic bin/bin2.meta_lic notice",
+				"highest.apex.meta_lic highest.apex.meta_lic highest.apex.meta_lic notice",
+				"highest.apex.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic reciprocal",
+				"highest.apex.meta_lic lib/libb.so.meta_lic lib/libb.so.meta_lic notice",
+				"highest.apex.meta_lic lib/libc.a.meta_lic lib/libc.a.meta_lic reciprocal",
+				"lib/liba.so.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic reciprocal",
+				"lib/libb.so.meta_lic lib/libb.so.meta_lic lib/libb.so.meta_lic notice",
+				"lib/libc.a.meta_lic lib/libc.a.meta_lic lib/libc.a.meta_lic reciprocal",
+				"lib/libd.so.meta_lic lib/libd.so.meta_lic lib/libd.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex_trimmed_notice",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"notice"},
+				stripPrefix: "testdata/reciprocal/",
+			},
+			expectedOut: []string{
+				"bin/bin1.meta_lic bin/bin1.meta_lic bin/bin1.meta_lic notice",
+				"bin/bin2.meta_lic bin/bin2.meta_lic bin/bin2.meta_lic notice",
+				"highest.apex.meta_lic bin/bin1.meta_lic bin/bin1.meta_lic notice",
+				"highest.apex.meta_lic bin/bin2.meta_lic bin/bin2.meta_lic notice",
+				"highest.apex.meta_lic highest.apex.meta_lic highest.apex.meta_lic notice",
+				"highest.apex.meta_lic lib/libb.so.meta_lic lib/libb.so.meta_lic notice",
+				"lib/libb.so.meta_lic lib/libb.so.meta_lic lib/libb.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex_trimmed_share",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"reciprocal", "restricted"},
+				stripPrefix: "testdata/reciprocal/",
+			},
+			expectedOut: []string{
+				"bin/bin1.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic reciprocal",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic lib/libc.a.meta_lic reciprocal",
+				"highest.apex.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic reciprocal",
+				"highest.apex.meta_lic lib/libc.a.meta_lic lib/libc.a.meta_lic reciprocal",
+				"lib/liba.so.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic reciprocal",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex_trimmed_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"proprietary"},
+				stripPrefix: "testdata/reciprocal/",
+			},
+			expectedOut: []string{},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex_trimmed_share_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"reciprocal", "restricted", "proprietary"},
+				stripPrefix: "testdata/reciprocal/",
+			},
+			expectedOut: []string{
+				"bin/bin1.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic reciprocal",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic lib/libc.a.meta_lic reciprocal",
+				"highest.apex.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic reciprocal",
+				"highest.apex.meta_lic lib/libc.a.meta_lic lib/libc.a.meta_lic reciprocal",
+				"lib/liba.so.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic reciprocal",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/reciprocal/", labelConditions: true},
+			expectedOut: []string{
+				"bin/bin1.meta_lic:notice bin/bin1.meta_lic:notice bin/bin1.meta_lic:notice notice",
+				"bin/bin1.meta_lic:notice lib/liba.so.meta_lic:reciprocal lib/liba.so.meta_lic:reciprocal reciprocal",
+				"bin/bin1.meta_lic:notice lib/libc.a.meta_lic:reciprocal lib/libc.a.meta_lic:reciprocal reciprocal",
+				"bin/bin2.meta_lic:notice bin/bin2.meta_lic:notice bin/bin2.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice bin/bin1.meta_lic:notice bin/bin1.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice bin/bin2.meta_lic:notice bin/bin2.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice highest.apex.meta_lic:notice highest.apex.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice lib/liba.so.meta_lic:reciprocal lib/liba.so.meta_lic:reciprocal reciprocal",
+				"highest.apex.meta_lic:notice lib/libb.so.meta_lic:notice lib/libb.so.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice lib/libc.a.meta_lic:reciprocal lib/libc.a.meta_lic:reciprocal reciprocal",
+				"lib/liba.so.meta_lic:reciprocal lib/liba.so.meta_lic:reciprocal lib/liba.so.meta_lic:reciprocal reciprocal",
+				"lib/libb.so.meta_lic:notice lib/libb.so.meta_lic:notice lib/libb.so.meta_lic:notice notice",
+				"lib/libc.a.meta_lic:reciprocal lib/libc.a.meta_lic:reciprocal lib/libc.a.meta_lic:reciprocal reciprocal",
+				"lib/libd.so.meta_lic:notice lib/libd.so.meta_lic:notice lib/libd.so.meta_lic:notice notice",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []string{
+				"testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/bin/bin1.meta_lic notice",
+				"testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/lib/liba.so.meta_lic testdata/reciprocal/lib/liba.so.meta_lic reciprocal",
+				"testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/lib/libc.a.meta_lic testdata/reciprocal/lib/libc.a.meta_lic reciprocal",
+				"testdata/reciprocal/bin/bin2.meta_lic testdata/reciprocal/bin/bin2.meta_lic testdata/reciprocal/bin/bin2.meta_lic notice",
+				"testdata/reciprocal/container.zip.meta_lic testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/bin/bin1.meta_lic notice",
+				"testdata/reciprocal/container.zip.meta_lic testdata/reciprocal/bin/bin2.meta_lic testdata/reciprocal/bin/bin2.meta_lic notice",
+				"testdata/reciprocal/container.zip.meta_lic testdata/reciprocal/container.zip.meta_lic testdata/reciprocal/container.zip.meta_lic notice",
+				"testdata/reciprocal/container.zip.meta_lic testdata/reciprocal/lib/liba.so.meta_lic testdata/reciprocal/lib/liba.so.meta_lic reciprocal",
+				"testdata/reciprocal/container.zip.meta_lic testdata/reciprocal/lib/libb.so.meta_lic testdata/reciprocal/lib/libb.so.meta_lic notice",
+				"testdata/reciprocal/container.zip.meta_lic testdata/reciprocal/lib/libc.a.meta_lic testdata/reciprocal/lib/libc.a.meta_lic reciprocal",
+				"testdata/reciprocal/lib/liba.so.meta_lic testdata/reciprocal/lib/liba.so.meta_lic testdata/reciprocal/lib/liba.so.meta_lic reciprocal",
+				"testdata/reciprocal/lib/libb.so.meta_lic testdata/reciprocal/lib/libb.so.meta_lic testdata/reciprocal/lib/libb.so.meta_lic notice",
+				"testdata/reciprocal/lib/libc.a.meta_lic testdata/reciprocal/lib/libc.a.meta_lic testdata/reciprocal/lib/libc.a.meta_lic reciprocal",
+				"testdata/reciprocal/lib/libd.so.meta_lic testdata/reciprocal/lib/libd.so.meta_lic testdata/reciprocal/lib/libd.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []string{
+				"testdata/reciprocal/application.meta_lic testdata/reciprocal/application.meta_lic testdata/reciprocal/application.meta_lic notice",
+				"testdata/reciprocal/application.meta_lic testdata/reciprocal/lib/liba.so.meta_lic testdata/reciprocal/lib/liba.so.meta_lic reciprocal",
+				"testdata/reciprocal/bin/bin3.meta_lic testdata/reciprocal/bin/bin3.meta_lic testdata/reciprocal/bin/bin3.meta_lic notice",
+				"testdata/reciprocal/lib/liba.so.meta_lic testdata/reciprocal/lib/liba.so.meta_lic testdata/reciprocal/lib/liba.so.meta_lic reciprocal",
+				"testdata/reciprocal/lib/libb.so.meta_lic testdata/reciprocal/lib/libb.so.meta_lic testdata/reciprocal/lib/libb.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []string{
+				"testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/bin/bin1.meta_lic notice",
+				"testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/lib/liba.so.meta_lic testdata/reciprocal/lib/liba.so.meta_lic reciprocal",
+				"testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/lib/libc.a.meta_lic testdata/reciprocal/lib/libc.a.meta_lic reciprocal",
+				"testdata/reciprocal/lib/liba.so.meta_lic testdata/reciprocal/lib/liba.so.meta_lic testdata/reciprocal/lib/liba.so.meta_lic reciprocal",
+				"testdata/reciprocal/lib/libc.a.meta_lic testdata/reciprocal/lib/libc.a.meta_lic testdata/reciprocal/lib/libc.a.meta_lic reciprocal",
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []string{
+				"testdata/reciprocal/lib/libd.so.meta_lic testdata/reciprocal/lib/libd.so.meta_lic testdata/reciprocal/lib/libd.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []string{
+				"testdata/restricted/bin/bin1.meta_lic testdata/restricted/bin/bin1.meta_lic testdata/restricted/bin/bin1.meta_lic notice",
+				"testdata/restricted/bin/bin1.meta_lic testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted",
+				"testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/liba.so.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted",
+				"testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/libc.a.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted",
+				"testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/libc.a.meta_lic testdata/restricted/lib/libc.a.meta_lic reciprocal",
+				"testdata/restricted/bin/bin2.meta_lic testdata/restricted/bin/bin2.meta_lic testdata/restricted/bin/bin2.meta_lic notice",
+				"testdata/restricted/bin/bin2.meta_lic testdata/restricted/bin/bin2.meta_lic testdata/restricted/lib/libb.so.meta_lic restricted",
+				"testdata/restricted/bin/bin2.meta_lic testdata/restricted/lib/libb.so.meta_lic testdata/restricted/lib/libb.so.meta_lic restricted",
+				"testdata/restricted/bin/bin2.meta_lic testdata/restricted/lib/libd.so.meta_lic testdata/restricted/lib/libb.so.meta_lic restricted",
+				"testdata/restricted/highest.apex.meta_lic testdata/restricted/bin/bin1.meta_lic testdata/restricted/bin/bin1.meta_lic notice",
+				"testdata/restricted/highest.apex.meta_lic testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted",
+				"testdata/restricted/highest.apex.meta_lic testdata/restricted/bin/bin2.meta_lic testdata/restricted/bin/bin2.meta_lic notice",
+				"testdata/restricted/highest.apex.meta_lic testdata/restricted/bin/bin2.meta_lic testdata/restricted/lib/libb.so.meta_lic restricted",
+				"testdata/restricted/highest.apex.meta_lic testdata/restricted/highest.apex.meta_lic testdata/restricted/highest.apex.meta_lic notice",
+				"testdata/restricted/highest.apex.meta_lic testdata/restricted/highest.apex.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted",
+				"testdata/restricted/highest.apex.meta_lic testdata/restricted/highest.apex.meta_lic testdata/restricted/lib/libb.so.meta_lic restricted",
+				"testdata/restricted/highest.apex.meta_lic testdata/restricted/lib/liba.so.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted",
+				"testdata/restricted/highest.apex.meta_lic testdata/restricted/lib/libb.so.meta_lic testdata/restricted/lib/libb.so.meta_lic restricted",
+				"testdata/restricted/highest.apex.meta_lic testdata/restricted/lib/libc.a.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted",
+				"testdata/restricted/highest.apex.meta_lic testdata/restricted/lib/libc.a.meta_lic testdata/restricted/lib/libc.a.meta_lic reciprocal",
+				"testdata/restricted/highest.apex.meta_lic testdata/restricted/lib/libd.so.meta_lic testdata/restricted/lib/libb.so.meta_lic restricted",
+				"testdata/restricted/lib/liba.so.meta_lic testdata/restricted/lib/liba.so.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted",
+				"testdata/restricted/lib/libb.so.meta_lic testdata/restricted/lib/libb.so.meta_lic testdata/restricted/lib/libb.so.meta_lic restricted",
+				"testdata/restricted/lib/libc.a.meta_lic testdata/restricted/lib/libc.a.meta_lic testdata/restricted/lib/libc.a.meta_lic reciprocal",
+				"testdata/restricted/lib/libd.so.meta_lic testdata/restricted/lib/libd.so.meta_lic testdata/restricted/lib/libd.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/restricted/"},
+			expectedOut: []string{
+				"bin/bin1.meta_lic bin/bin1.meta_lic bin/bin1.meta_lic notice",
+				"bin/bin1.meta_lic bin/bin1.meta_lic lib/liba.so.meta_lic restricted",
+				"bin/bin1.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic restricted",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic lib/liba.so.meta_lic restricted",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic lib/libc.a.meta_lic reciprocal",
+				"bin/bin2.meta_lic bin/bin2.meta_lic bin/bin2.meta_lic notice",
+				"bin/bin2.meta_lic bin/bin2.meta_lic lib/libb.so.meta_lic restricted",
+				"bin/bin2.meta_lic lib/libb.so.meta_lic lib/libb.so.meta_lic restricted",
+				"bin/bin2.meta_lic lib/libd.so.meta_lic lib/libb.so.meta_lic restricted",
+				"highest.apex.meta_lic bin/bin1.meta_lic bin/bin1.meta_lic notice",
+				"highest.apex.meta_lic bin/bin1.meta_lic lib/liba.so.meta_lic restricted",
+				"highest.apex.meta_lic bin/bin2.meta_lic bin/bin2.meta_lic notice",
+				"highest.apex.meta_lic bin/bin2.meta_lic lib/libb.so.meta_lic restricted",
+				"highest.apex.meta_lic highest.apex.meta_lic highest.apex.meta_lic notice",
+				"highest.apex.meta_lic highest.apex.meta_lic lib/liba.so.meta_lic restricted",
+				"highest.apex.meta_lic highest.apex.meta_lic lib/libb.so.meta_lic restricted",
+				"highest.apex.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic restricted",
+				"highest.apex.meta_lic lib/libb.so.meta_lic lib/libb.so.meta_lic restricted",
+				"highest.apex.meta_lic lib/libc.a.meta_lic lib/liba.so.meta_lic restricted",
+				"highest.apex.meta_lic lib/libc.a.meta_lic lib/libc.a.meta_lic reciprocal",
+				"highest.apex.meta_lic lib/libd.so.meta_lic lib/libb.so.meta_lic restricted",
+				"lib/liba.so.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic restricted",
+				"lib/libb.so.meta_lic lib/libb.so.meta_lic lib/libb.so.meta_lic restricted",
+				"lib/libc.a.meta_lic lib/libc.a.meta_lic lib/libc.a.meta_lic reciprocal",
+				"lib/libd.so.meta_lic lib/libd.so.meta_lic lib/libd.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "apex_trimmed_notice",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"notice"},
+				stripPrefix: "testdata/restricted/",
+			},
+			expectedOut: []string{
+				"bin/bin1.meta_lic bin/bin1.meta_lic bin/bin1.meta_lic notice",
+				"bin/bin2.meta_lic bin/bin2.meta_lic bin/bin2.meta_lic notice",
+				"highest.apex.meta_lic bin/bin1.meta_lic bin/bin1.meta_lic notice",
+				"highest.apex.meta_lic bin/bin2.meta_lic bin/bin2.meta_lic notice",
+				"highest.apex.meta_lic highest.apex.meta_lic highest.apex.meta_lic notice",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "apex_trimmed_share",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"reciprocal", "restricted"},
+				stripPrefix: "testdata/restricted/",
+			},
+			expectedOut: []string{
+				"bin/bin1.meta_lic bin/bin1.meta_lic lib/liba.so.meta_lic restricted",
+				"bin/bin1.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic restricted",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic lib/liba.so.meta_lic restricted",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic lib/libc.a.meta_lic reciprocal",
+				"bin/bin2.meta_lic bin/bin2.meta_lic lib/libb.so.meta_lic restricted",
+				"bin/bin2.meta_lic lib/libb.so.meta_lic lib/libb.so.meta_lic restricted",
+				"highest.apex.meta_lic bin/bin1.meta_lic lib/liba.so.meta_lic restricted",
+				"highest.apex.meta_lic bin/bin2.meta_lic lib/libb.so.meta_lic restricted",
+				"highest.apex.meta_lic highest.apex.meta_lic lib/liba.so.meta_lic restricted",
+				"highest.apex.meta_lic highest.apex.meta_lic lib/libb.so.meta_lic restricted",
+				"highest.apex.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic restricted",
+				"highest.apex.meta_lic lib/libb.so.meta_lic lib/libb.so.meta_lic restricted",
+				"highest.apex.meta_lic lib/libc.a.meta_lic lib/liba.so.meta_lic restricted",
+				"highest.apex.meta_lic lib/libc.a.meta_lic lib/libc.a.meta_lic reciprocal",
+				"lib/liba.so.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic restricted",
+				"lib/libb.so.meta_lic lib/libb.so.meta_lic lib/libb.so.meta_lic restricted",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "apex_trimmed_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"proprietary"},
+				stripPrefix: "testdata/restricted/",
+			},
+			expectedOut: []string{},
+		},
+		{
+			condition: "restricted",
+			name:      "apex_trimmed_share_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"reciprocal", "restricted", "proprietary"},
+				stripPrefix: "testdata/restricted/",
+			},
+			expectedOut: []string{
+				"bin/bin1.meta_lic bin/bin1.meta_lic lib/liba.so.meta_lic restricted",
+				"bin/bin1.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic restricted",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic lib/liba.so.meta_lic restricted",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic lib/libc.a.meta_lic reciprocal",
+				"bin/bin2.meta_lic bin/bin2.meta_lic lib/libb.so.meta_lic restricted",
+				"bin/bin2.meta_lic lib/libb.so.meta_lic lib/libb.so.meta_lic restricted",
+				"highest.apex.meta_lic bin/bin1.meta_lic lib/liba.so.meta_lic restricted",
+				"highest.apex.meta_lic bin/bin2.meta_lic lib/libb.so.meta_lic restricted",
+				"highest.apex.meta_lic highest.apex.meta_lic lib/liba.so.meta_lic restricted",
+				"highest.apex.meta_lic highest.apex.meta_lic lib/libb.so.meta_lic restricted",
+				"highest.apex.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic restricted",
+				"highest.apex.meta_lic lib/libb.so.meta_lic lib/libb.so.meta_lic restricted",
+				"highest.apex.meta_lic lib/libc.a.meta_lic lib/liba.so.meta_lic restricted",
+				"highest.apex.meta_lic lib/libc.a.meta_lic lib/libc.a.meta_lic reciprocal",
+				"lib/liba.so.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic restricted",
+				"lib/libb.so.meta_lic lib/libb.so.meta_lic lib/libb.so.meta_lic restricted",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/restricted/", labelConditions: true},
+			expectedOut: []string{
+				"bin/bin1.meta_lic:notice bin/bin1.meta_lic:notice bin/bin1.meta_lic:notice notice",
+				"bin/bin1.meta_lic:notice bin/bin1.meta_lic:notice lib/liba.so.meta_lic:restricted restricted",
+				"bin/bin1.meta_lic:notice lib/liba.so.meta_lic:restricted lib/liba.so.meta_lic:restricted restricted",
+				"bin/bin1.meta_lic:notice lib/libc.a.meta_lic:reciprocal lib/liba.so.meta_lic:restricted restricted",
+				"bin/bin1.meta_lic:notice lib/libc.a.meta_lic:reciprocal lib/libc.a.meta_lic:reciprocal reciprocal",
+				"bin/bin2.meta_lic:notice bin/bin2.meta_lic:notice bin/bin2.meta_lic:notice notice",
+				"bin/bin2.meta_lic:notice bin/bin2.meta_lic:notice lib/libb.so.meta_lic:restricted restricted",
+				"bin/bin2.meta_lic:notice lib/libb.so.meta_lic:restricted lib/libb.so.meta_lic:restricted restricted",
+				"bin/bin2.meta_lic:notice lib/libd.so.meta_lic:notice lib/libb.so.meta_lic:restricted restricted",
+				"highest.apex.meta_lic:notice bin/bin1.meta_lic:notice bin/bin1.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice bin/bin1.meta_lic:notice lib/liba.so.meta_lic:restricted restricted",
+				"highest.apex.meta_lic:notice bin/bin2.meta_lic:notice bin/bin2.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice bin/bin2.meta_lic:notice lib/libb.so.meta_lic:restricted restricted",
+				"highest.apex.meta_lic:notice highest.apex.meta_lic:notice highest.apex.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice highest.apex.meta_lic:notice lib/liba.so.meta_lic:restricted restricted",
+				"highest.apex.meta_lic:notice highest.apex.meta_lic:notice lib/libb.so.meta_lic:restricted restricted",
+				"highest.apex.meta_lic:notice lib/liba.so.meta_lic:restricted lib/liba.so.meta_lic:restricted restricted",
+				"highest.apex.meta_lic:notice lib/libb.so.meta_lic:restricted lib/libb.so.meta_lic:restricted restricted",
+				"highest.apex.meta_lic:notice lib/libc.a.meta_lic:reciprocal lib/liba.so.meta_lic:restricted restricted",
+				"highest.apex.meta_lic:notice lib/libc.a.meta_lic:reciprocal lib/libc.a.meta_lic:reciprocal reciprocal",
+				"highest.apex.meta_lic:notice lib/libd.so.meta_lic:notice lib/libb.so.meta_lic:restricted restricted",
+				"lib/liba.so.meta_lic:restricted lib/liba.so.meta_lic:restricted lib/liba.so.meta_lic:restricted restricted",
+				"lib/libb.so.meta_lic:restricted lib/libb.so.meta_lic:restricted lib/libb.so.meta_lic:restricted restricted",
+				"lib/libc.a.meta_lic:reciprocal lib/libc.a.meta_lic:reciprocal lib/libc.a.meta_lic:reciprocal reciprocal",
+				"lib/libd.so.meta_lic:notice lib/libd.so.meta_lic:notice lib/libd.so.meta_lic:notice notice",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []string{
+				"testdata/restricted/bin/bin1.meta_lic testdata/restricted/bin/bin1.meta_lic testdata/restricted/bin/bin1.meta_lic notice",
+				"testdata/restricted/bin/bin1.meta_lic testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted",
+				"testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/liba.so.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted",
+				"testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/libc.a.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted",
+				"testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/libc.a.meta_lic testdata/restricted/lib/libc.a.meta_lic reciprocal",
+				"testdata/restricted/bin/bin2.meta_lic testdata/restricted/bin/bin2.meta_lic testdata/restricted/bin/bin2.meta_lic notice",
+				"testdata/restricted/bin/bin2.meta_lic testdata/restricted/bin/bin2.meta_lic testdata/restricted/lib/libb.so.meta_lic restricted",
+				"testdata/restricted/bin/bin2.meta_lic testdata/restricted/lib/libb.so.meta_lic testdata/restricted/lib/libb.so.meta_lic restricted",
+				"testdata/restricted/bin/bin2.meta_lic testdata/restricted/lib/libd.so.meta_lic testdata/restricted/lib/libb.so.meta_lic restricted",
+				"testdata/restricted/container.zip.meta_lic testdata/restricted/bin/bin1.meta_lic testdata/restricted/bin/bin1.meta_lic notice",
+				"testdata/restricted/container.zip.meta_lic testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted",
+				"testdata/restricted/container.zip.meta_lic testdata/restricted/bin/bin2.meta_lic testdata/restricted/bin/bin2.meta_lic notice",
+				"testdata/restricted/container.zip.meta_lic testdata/restricted/bin/bin2.meta_lic testdata/restricted/lib/libb.so.meta_lic restricted",
+				"testdata/restricted/container.zip.meta_lic testdata/restricted/container.zip.meta_lic testdata/restricted/container.zip.meta_lic notice",
+				"testdata/restricted/container.zip.meta_lic testdata/restricted/container.zip.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted",
+				"testdata/restricted/container.zip.meta_lic testdata/restricted/container.zip.meta_lic testdata/restricted/lib/libb.so.meta_lic restricted",
+				"testdata/restricted/container.zip.meta_lic testdata/restricted/lib/liba.so.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted",
+				"testdata/restricted/container.zip.meta_lic testdata/restricted/lib/libb.so.meta_lic testdata/restricted/lib/libb.so.meta_lic restricted",
+				"testdata/restricted/container.zip.meta_lic testdata/restricted/lib/libc.a.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted",
+				"testdata/restricted/container.zip.meta_lic testdata/restricted/lib/libc.a.meta_lic testdata/restricted/lib/libc.a.meta_lic reciprocal",
+				"testdata/restricted/container.zip.meta_lic testdata/restricted/lib/libd.so.meta_lic testdata/restricted/lib/libb.so.meta_lic restricted",
+				"testdata/restricted/lib/liba.so.meta_lic testdata/restricted/lib/liba.so.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted",
+				"testdata/restricted/lib/libb.so.meta_lic testdata/restricted/lib/libb.so.meta_lic testdata/restricted/lib/libb.so.meta_lic restricted",
+				"testdata/restricted/lib/libc.a.meta_lic testdata/restricted/lib/libc.a.meta_lic testdata/restricted/lib/libc.a.meta_lic reciprocal",
+				"testdata/restricted/lib/libd.so.meta_lic testdata/restricted/lib/libd.so.meta_lic testdata/restricted/lib/libd.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []string{
+				"testdata/restricted/application.meta_lic testdata/restricted/application.meta_lic testdata/restricted/application.meta_lic notice",
+				"testdata/restricted/application.meta_lic testdata/restricted/application.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted",
+				"testdata/restricted/application.meta_lic testdata/restricted/application.meta_lic testdata/restricted/lib/libb.so.meta_lic restricted",
+				"testdata/restricted/application.meta_lic testdata/restricted/lib/liba.so.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted",
+				"testdata/restricted/application.meta_lic testdata/restricted/lib/liba.so.meta_lic testdata/restricted/lib/libb.so.meta_lic restricted",
+				"testdata/restricted/application.meta_lic testdata/restricted/lib/libb.so.meta_lic testdata/restricted/lib/libb.so.meta_lic restricted",
+				"testdata/restricted/bin/bin3.meta_lic testdata/restricted/bin/bin3.meta_lic testdata/restricted/bin/bin3.meta_lic restricted",
+				"testdata/restricted/lib/liba.so.meta_lic testdata/restricted/lib/liba.so.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted",
+				"testdata/restricted/lib/libb.so.meta_lic testdata/restricted/lib/libb.so.meta_lic testdata/restricted/lib/libb.so.meta_lic restricted",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []string{
+				"testdata/restricted/bin/bin1.meta_lic testdata/restricted/bin/bin1.meta_lic testdata/restricted/bin/bin1.meta_lic notice",
+				"testdata/restricted/bin/bin1.meta_lic testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted",
+				"testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/liba.so.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted",
+				"testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/libc.a.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted",
+				"testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/libc.a.meta_lic testdata/restricted/lib/libc.a.meta_lic reciprocal",
+				"testdata/restricted/lib/liba.so.meta_lic testdata/restricted/lib/liba.so.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted",
+				"testdata/restricted/lib/libc.a.meta_lic testdata/restricted/lib/libc.a.meta_lic testdata/restricted/lib/libc.a.meta_lic reciprocal",
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []string{
+				"testdata/restricted/lib/libd.so.meta_lic testdata/restricted/lib/libd.so.meta_lic testdata/restricted/lib/libd.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []string{
+				"testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/bin/bin1.meta_lic notice",
+				"testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/lib/liba.so.meta_lic testdata/proprietary/lib/liba.so.meta_lic by_exception_only:proprietary",
+				"testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/lib/libc.a.meta_lic testdata/proprietary/lib/libc.a.meta_lic by_exception_only:proprietary",
+				"testdata/proprietary/bin/bin2.meta_lic testdata/proprietary/bin/bin2.meta_lic testdata/proprietary/bin/bin2.meta_lic by_exception_only:proprietary",
+				"testdata/proprietary/bin/bin2.meta_lic testdata/proprietary/bin/bin2.meta_lic testdata/proprietary/lib/libb.so.meta_lic restricted",
+				"testdata/proprietary/bin/bin2.meta_lic testdata/proprietary/lib/libb.so.meta_lic testdata/proprietary/lib/libb.so.meta_lic restricted",
+				"testdata/proprietary/bin/bin2.meta_lic testdata/proprietary/lib/libd.so.meta_lic testdata/proprietary/lib/libb.so.meta_lic restricted",
+				"testdata/proprietary/highest.apex.meta_lic testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/bin/bin1.meta_lic notice",
+				"testdata/proprietary/highest.apex.meta_lic testdata/proprietary/bin/bin2.meta_lic testdata/proprietary/bin/bin2.meta_lic by_exception_only:proprietary",
+				"testdata/proprietary/highest.apex.meta_lic testdata/proprietary/bin/bin2.meta_lic testdata/proprietary/lib/libb.so.meta_lic restricted",
+				"testdata/proprietary/highest.apex.meta_lic testdata/proprietary/highest.apex.meta_lic testdata/proprietary/highest.apex.meta_lic notice",
+				"testdata/proprietary/highest.apex.meta_lic testdata/proprietary/highest.apex.meta_lic testdata/proprietary/lib/libb.so.meta_lic restricted",
+				"testdata/proprietary/highest.apex.meta_lic testdata/proprietary/lib/liba.so.meta_lic testdata/proprietary/lib/liba.so.meta_lic by_exception_only:proprietary",
+				"testdata/proprietary/highest.apex.meta_lic testdata/proprietary/lib/libb.so.meta_lic testdata/proprietary/lib/libb.so.meta_lic restricted",
+				"testdata/proprietary/highest.apex.meta_lic testdata/proprietary/lib/libc.a.meta_lic testdata/proprietary/lib/libc.a.meta_lic by_exception_only:proprietary",
+				"testdata/proprietary/highest.apex.meta_lic testdata/proprietary/lib/libd.so.meta_lic testdata/proprietary/lib/libb.so.meta_lic restricted",
+				"testdata/proprietary/lib/liba.so.meta_lic testdata/proprietary/lib/liba.so.meta_lic testdata/proprietary/lib/liba.so.meta_lic by_exception_only:proprietary",
+				"testdata/proprietary/lib/libb.so.meta_lic testdata/proprietary/lib/libb.so.meta_lic testdata/proprietary/lib/libb.so.meta_lic restricted",
+				"testdata/proprietary/lib/libc.a.meta_lic testdata/proprietary/lib/libc.a.meta_lic testdata/proprietary/lib/libc.a.meta_lic by_exception_only:proprietary",
+				"testdata/proprietary/lib/libd.so.meta_lic testdata/proprietary/lib/libd.so.meta_lic testdata/proprietary/lib/libd.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/proprietary/"},
+			expectedOut: []string{
+				"bin/bin1.meta_lic bin/bin1.meta_lic bin/bin1.meta_lic notice",
+				"bin/bin1.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic by_exception_only:proprietary",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic lib/libc.a.meta_lic by_exception_only:proprietary",
+				"bin/bin2.meta_lic bin/bin2.meta_lic bin/bin2.meta_lic by_exception_only:proprietary",
+				"bin/bin2.meta_lic bin/bin2.meta_lic lib/libb.so.meta_lic restricted",
+				"bin/bin2.meta_lic lib/libb.so.meta_lic lib/libb.so.meta_lic restricted",
+				"bin/bin2.meta_lic lib/libd.so.meta_lic lib/libb.so.meta_lic restricted",
+				"highest.apex.meta_lic bin/bin1.meta_lic bin/bin1.meta_lic notice",
+				"highest.apex.meta_lic bin/bin2.meta_lic bin/bin2.meta_lic by_exception_only:proprietary",
+				"highest.apex.meta_lic bin/bin2.meta_lic lib/libb.so.meta_lic restricted",
+				"highest.apex.meta_lic highest.apex.meta_lic highest.apex.meta_lic notice",
+				"highest.apex.meta_lic highest.apex.meta_lic lib/libb.so.meta_lic restricted",
+				"highest.apex.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic by_exception_only:proprietary",
+				"highest.apex.meta_lic lib/libb.so.meta_lic lib/libb.so.meta_lic restricted",
+				"highest.apex.meta_lic lib/libc.a.meta_lic lib/libc.a.meta_lic by_exception_only:proprietary",
+				"highest.apex.meta_lic lib/libd.so.meta_lic lib/libb.so.meta_lic restricted",
+				"lib/liba.so.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic by_exception_only:proprietary",
+				"lib/libb.so.meta_lic lib/libb.so.meta_lic lib/libb.so.meta_lic restricted",
+				"lib/libc.a.meta_lic lib/libc.a.meta_lic lib/libc.a.meta_lic by_exception_only:proprietary",
+				"lib/libd.so.meta_lic lib/libd.so.meta_lic lib/libd.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex_trimmed_notice",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"notice"},
+				stripPrefix: "testdata/proprietary/",
+			},
+			expectedOut: []string{
+				"bin/bin1.meta_lic bin/bin1.meta_lic bin/bin1.meta_lic notice",
+				"highest.apex.meta_lic bin/bin1.meta_lic bin/bin1.meta_lic notice",
+				"highest.apex.meta_lic highest.apex.meta_lic highest.apex.meta_lic notice",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex_trimmed_share",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"reciprocal", "restricted"},
+				stripPrefix: "testdata/proprietary/",
+			},
+			expectedOut: []string{
+				"bin/bin2.meta_lic bin/bin2.meta_lic lib/libb.so.meta_lic restricted",
+				"bin/bin2.meta_lic lib/libb.so.meta_lic lib/libb.so.meta_lic restricted",
+				"highest.apex.meta_lic bin/bin2.meta_lic lib/libb.so.meta_lic restricted",
+				"highest.apex.meta_lic highest.apex.meta_lic lib/libb.so.meta_lic restricted",
+				"highest.apex.meta_lic lib/libb.so.meta_lic lib/libb.so.meta_lic restricted",
+				"lib/libb.so.meta_lic lib/libb.so.meta_lic lib/libb.so.meta_lic restricted",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex_trimmed_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"proprietary"},
+				stripPrefix: "testdata/proprietary/",
+			},
+			expectedOut: []string{
+				"bin/bin1.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic proprietary",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic lib/libc.a.meta_lic proprietary",
+				"bin/bin2.meta_lic bin/bin2.meta_lic bin/bin2.meta_lic proprietary",
+				"highest.apex.meta_lic bin/bin2.meta_lic bin/bin2.meta_lic proprietary",
+				"highest.apex.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic proprietary",
+				"highest.apex.meta_lic lib/libc.a.meta_lic lib/libc.a.meta_lic proprietary",
+				"lib/liba.so.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic proprietary",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex_trimmed_share_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"reciprocal", "restricted", "proprietary"},
+				stripPrefix: "testdata/proprietary/",
+			},
+			expectedOut: []string{
+				"bin/bin1.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic proprietary",
+				"bin/bin1.meta_lic lib/libc.a.meta_lic lib/libc.a.meta_lic proprietary",
+				"bin/bin2.meta_lic bin/bin2.meta_lic bin/bin2.meta_lic proprietary",
+				"bin/bin2.meta_lic bin/bin2.meta_lic lib/libb.so.meta_lic restricted",
+				"bin/bin2.meta_lic lib/libb.so.meta_lic lib/libb.so.meta_lic restricted",
+				"highest.apex.meta_lic bin/bin2.meta_lic bin/bin2.meta_lic proprietary",
+				"highest.apex.meta_lic bin/bin2.meta_lic lib/libb.so.meta_lic restricted",
+				"highest.apex.meta_lic highest.apex.meta_lic lib/libb.so.meta_lic restricted",
+				"highest.apex.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic proprietary",
+				"highest.apex.meta_lic lib/libb.so.meta_lic lib/libb.so.meta_lic restricted",
+				"highest.apex.meta_lic lib/libc.a.meta_lic lib/libc.a.meta_lic proprietary",
+				"lib/liba.so.meta_lic lib/liba.so.meta_lic lib/liba.so.meta_lic proprietary",
+				"lib/libb.so.meta_lic lib/libb.so.meta_lic lib/libb.so.meta_lic restricted",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/proprietary/", labelConditions: true},
+			expectedOut: []string{
+				"bin/bin1.meta_lic:notice bin/bin1.meta_lic:notice bin/bin1.meta_lic:notice notice",
+				"bin/bin1.meta_lic:notice lib/liba.so.meta_lic:by_exception_only:proprietary lib/liba.so.meta_lic:by_exception_only:proprietary by_exception_only:proprietary",
+				"bin/bin1.meta_lic:notice lib/libc.a.meta_lic:by_exception_only:proprietary lib/libc.a.meta_lic:by_exception_only:proprietary by_exception_only:proprietary",
+				"bin/bin2.meta_lic:by_exception_only:proprietary bin/bin2.meta_lic:by_exception_only:proprietary bin/bin2.meta_lic:by_exception_only:proprietary by_exception_only:proprietary",
+				"bin/bin2.meta_lic:by_exception_only:proprietary bin/bin2.meta_lic:by_exception_only:proprietary lib/libb.so.meta_lic:restricted restricted",
+				"bin/bin2.meta_lic:by_exception_only:proprietary lib/libb.so.meta_lic:restricted lib/libb.so.meta_lic:restricted restricted",
+				"bin/bin2.meta_lic:by_exception_only:proprietary lib/libd.so.meta_lic:notice lib/libb.so.meta_lic:restricted restricted",
+				"highest.apex.meta_lic:notice bin/bin1.meta_lic:notice bin/bin1.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice bin/bin2.meta_lic:by_exception_only:proprietary bin/bin2.meta_lic:by_exception_only:proprietary by_exception_only:proprietary",
+				"highest.apex.meta_lic:notice bin/bin2.meta_lic:by_exception_only:proprietary lib/libb.so.meta_lic:restricted restricted",
+				"highest.apex.meta_lic:notice highest.apex.meta_lic:notice highest.apex.meta_lic:notice notice",
+				"highest.apex.meta_lic:notice highest.apex.meta_lic:notice lib/libb.so.meta_lic:restricted restricted",
+				"highest.apex.meta_lic:notice lib/liba.so.meta_lic:by_exception_only:proprietary lib/liba.so.meta_lic:by_exception_only:proprietary by_exception_only:proprietary",
+				"highest.apex.meta_lic:notice lib/libb.so.meta_lic:restricted lib/libb.so.meta_lic:restricted restricted",
+				"highest.apex.meta_lic:notice lib/libc.a.meta_lic:by_exception_only:proprietary lib/libc.a.meta_lic:by_exception_only:proprietary by_exception_only:proprietary",
+				"highest.apex.meta_lic:notice lib/libd.so.meta_lic:notice lib/libb.so.meta_lic:restricted restricted",
+				"lib/liba.so.meta_lic:by_exception_only:proprietary lib/liba.so.meta_lic:by_exception_only:proprietary lib/liba.so.meta_lic:by_exception_only:proprietary by_exception_only:proprietary",
+				"lib/libb.so.meta_lic:restricted lib/libb.so.meta_lic:restricted lib/libb.so.meta_lic:restricted restricted",
+				"lib/libc.a.meta_lic:by_exception_only:proprietary lib/libc.a.meta_lic:by_exception_only:proprietary lib/libc.a.meta_lic:by_exception_only:proprietary by_exception_only:proprietary",
+				"lib/libd.so.meta_lic:notice lib/libd.so.meta_lic:notice lib/libd.so.meta_lic:notice notice",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []string{
+				"testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/bin/bin1.meta_lic notice",
+				"testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/lib/liba.so.meta_lic testdata/proprietary/lib/liba.so.meta_lic by_exception_only:proprietary",
+				"testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/lib/libc.a.meta_lic testdata/proprietary/lib/libc.a.meta_lic by_exception_only:proprietary",
+				"testdata/proprietary/bin/bin2.meta_lic testdata/proprietary/bin/bin2.meta_lic testdata/proprietary/bin/bin2.meta_lic by_exception_only:proprietary",
+				"testdata/proprietary/bin/bin2.meta_lic testdata/proprietary/bin/bin2.meta_lic testdata/proprietary/lib/libb.so.meta_lic restricted",
+				"testdata/proprietary/bin/bin2.meta_lic testdata/proprietary/lib/libb.so.meta_lic testdata/proprietary/lib/libb.so.meta_lic restricted",
+				"testdata/proprietary/bin/bin2.meta_lic testdata/proprietary/lib/libd.so.meta_lic testdata/proprietary/lib/libb.so.meta_lic restricted",
+				"testdata/proprietary/container.zip.meta_lic testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/bin/bin1.meta_lic notice",
+				"testdata/proprietary/container.zip.meta_lic testdata/proprietary/bin/bin2.meta_lic testdata/proprietary/bin/bin2.meta_lic by_exception_only:proprietary",
+				"testdata/proprietary/container.zip.meta_lic testdata/proprietary/bin/bin2.meta_lic testdata/proprietary/lib/libb.so.meta_lic restricted",
+				"testdata/proprietary/container.zip.meta_lic testdata/proprietary/container.zip.meta_lic testdata/proprietary/container.zip.meta_lic notice",
+				"testdata/proprietary/container.zip.meta_lic testdata/proprietary/container.zip.meta_lic testdata/proprietary/lib/libb.so.meta_lic restricted",
+				"testdata/proprietary/container.zip.meta_lic testdata/proprietary/lib/liba.so.meta_lic testdata/proprietary/lib/liba.so.meta_lic by_exception_only:proprietary",
+				"testdata/proprietary/container.zip.meta_lic testdata/proprietary/lib/libb.so.meta_lic testdata/proprietary/lib/libb.so.meta_lic restricted",
+				"testdata/proprietary/container.zip.meta_lic testdata/proprietary/lib/libc.a.meta_lic testdata/proprietary/lib/libc.a.meta_lic by_exception_only:proprietary",
+				"testdata/proprietary/container.zip.meta_lic testdata/proprietary/lib/libd.so.meta_lic testdata/proprietary/lib/libb.so.meta_lic restricted",
+				"testdata/proprietary/lib/liba.so.meta_lic testdata/proprietary/lib/liba.so.meta_lic testdata/proprietary/lib/liba.so.meta_lic by_exception_only:proprietary",
+				"testdata/proprietary/lib/libb.so.meta_lic testdata/proprietary/lib/libb.so.meta_lic testdata/proprietary/lib/libb.so.meta_lic restricted",
+				"testdata/proprietary/lib/libc.a.meta_lic testdata/proprietary/lib/libc.a.meta_lic testdata/proprietary/lib/libc.a.meta_lic by_exception_only:proprietary",
+				"testdata/proprietary/lib/libd.so.meta_lic testdata/proprietary/lib/libd.so.meta_lic testdata/proprietary/lib/libd.so.meta_lic notice",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []string{
+				"testdata/proprietary/application.meta_lic testdata/proprietary/application.meta_lic testdata/proprietary/application.meta_lic notice",
+				"testdata/proprietary/application.meta_lic testdata/proprietary/application.meta_lic testdata/proprietary/lib/libb.so.meta_lic restricted",
+				"testdata/proprietary/application.meta_lic testdata/proprietary/lib/liba.so.meta_lic testdata/proprietary/lib/liba.so.meta_lic by_exception_only:proprietary",
+				"testdata/proprietary/application.meta_lic testdata/proprietary/lib/liba.so.meta_lic testdata/proprietary/lib/libb.so.meta_lic restricted",
+				"testdata/proprietary/application.meta_lic testdata/proprietary/lib/libb.so.meta_lic testdata/proprietary/lib/libb.so.meta_lic restricted",
+				"testdata/proprietary/bin/bin3.meta_lic testdata/proprietary/bin/bin3.meta_lic testdata/proprietary/bin/bin3.meta_lic restricted",
+				"testdata/proprietary/lib/liba.so.meta_lic testdata/proprietary/lib/liba.so.meta_lic testdata/proprietary/lib/liba.so.meta_lic by_exception_only:proprietary",
+				"testdata/proprietary/lib/libb.so.meta_lic testdata/proprietary/lib/libb.so.meta_lic testdata/proprietary/lib/libb.so.meta_lic restricted",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []string{
+				"testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/bin/bin1.meta_lic notice",
+				"testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/lib/liba.so.meta_lic testdata/proprietary/lib/liba.so.meta_lic by_exception_only:proprietary",
+				"testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/lib/libc.a.meta_lic testdata/proprietary/lib/libc.a.meta_lic by_exception_only:proprietary",
+				"testdata/proprietary/lib/liba.so.meta_lic testdata/proprietary/lib/liba.so.meta_lic testdata/proprietary/lib/liba.so.meta_lic by_exception_only:proprietary",
+				"testdata/proprietary/lib/libc.a.meta_lic testdata/proprietary/lib/libc.a.meta_lic testdata/proprietary/lib/libc.a.meta_lic by_exception_only:proprietary",
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []string{
+				"testdata/proprietary/lib/libd.so.meta_lic testdata/proprietary/lib/libd.so.meta_lic testdata/proprietary/lib/libd.so.meta_lic notice",
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.condition+" "+tt.name, func(t *testing.T) {
+			expectedOut := &bytes.Buffer{}
+			for _, eo := range tt.expectedOut {
+				expectedOut.WriteString(eo)
+				expectedOut.WriteString("\n")
+			}
+
+			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)
+			}
+			err := dumpResolutions(&tt.ctx, stdout, stderr, rootFiles...)
+			if err != nil {
+				t.Fatalf("dumpresolutions: error = %v, stderr = %v", err, stderr)
+				return
+			}
+			if stderr.Len() > 0 {
+				t.Errorf("dumpresolutions: gotStderr = %v, want none", stderr)
+			}
+			out := stdout.String()
+			expected := expectedOut.String()
+			if out != expected {
+				outList := strings.Split(out, "\n")
+				expectedList := strings.Split(expected, "\n")
+				startLine := 0
+				for len(outList) > startLine && len(expectedList) > startLine && outList[startLine] == expectedList[startLine] {
+					startLine++
+				}
+				t.Errorf("listshare: gotStdout = %v, want %v, somewhere near line %d Stdout = %v, want %v",
+					out, expected, startLine+1, outList[startLine], expectedList[startLine])
+			}
+		})
+	}
+}
+
+type testContext struct {
+	nextNode int
+	nodes    map[string]string
+}
+
+type matcher interface {
+	matchString(*testContext) string
+	typeString() string
+}
+
+type targetMatcher struct {
+	target     string
+	conditions []string
+}
+
+func (tm *targetMatcher) matchString(ctx *testContext) string {
+	m := tm.target
+	if len(tm.conditions) > 0 {
+		m += "\\n" + strings.Join(tm.conditions, "\\n")
+	}
+	m = ctx.nodes[tm.target] + " [label=\"" + m + "\"];"
+	return m
+}
+
+func (tm *targetMatcher) typeString() string {
+	return "target"
+}
+
+type resolutionMatcher struct {
+	appliesTo  string
+	actsOn     string
+	origin     string
+	conditions []string
+}
+
+func (rm *resolutionMatcher) matchString(ctx *testContext) string {
+	return ctx.nodes[rm.appliesTo] + " -> " + ctx.nodes[rm.actsOn] + "; " +
+		ctx.nodes[rm.actsOn] + " -> " + ctx.nodes[rm.origin] +
+		" [label=\"" + strings.Join(rm.conditions, "\\n") + "\"];"
+}
+
+func (rm *resolutionMatcher) typeString() string {
+	return "resolution"
+}
+
+type getMatcher func(*testContext) matcher
+
+func matchTarget(target string, conditions ...string) getMatcher {
+	return func(ctx *testContext) matcher {
+		ctx.nodes[target] = fmt.Sprintf("n%d", ctx.nextNode)
+		ctx.nextNode++
+		return &targetMatcher{target, append([]string{}, conditions...)}
+	}
+}
+
+func matchResolution(appliesTo, actsOn, origin string, conditions ...string) getMatcher {
+	return func(ctx *testContext) matcher {
+		if _, ok := ctx.nodes[appliesTo]; !ok {
+			ctx.nodes[appliesTo] = fmt.Sprintf("unknown%d", ctx.nextNode)
+			ctx.nextNode++
+		}
+		if _, ok := ctx.nodes[actsOn]; !ok {
+			ctx.nodes[actsOn] = fmt.Sprintf("unknown%d", ctx.nextNode)
+			ctx.nextNode++
+		}
+		if _, ok := ctx.nodes[origin]; !ok {
+			ctx.nodes[origin] = fmt.Sprintf("unknown%d", ctx.nextNode)
+			ctx.nextNode++
+		}
+		return &resolutionMatcher{appliesTo, actsOn, origin, append([]string{}, conditions...)}
+	}
+}
+
+func Test_graphviz(t *testing.T) {
+	tests := []struct {
+		condition   string
+		name        string
+		roots       []string
+		ctx         context
+		expectedOut []getMatcher
+	}{
+		{
+			condition: "firstparty",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/firstparty/bin/bin1.meta_lic"),
+				matchTarget("testdata/firstparty/lib/liba.so.meta_lic"),
+				matchTarget("testdata/firstparty/lib/libc.a.meta_lic"),
+				matchTarget("testdata/firstparty/bin/bin2.meta_lic"),
+				matchTarget("testdata/firstparty/highest.apex.meta_lic"),
+				matchTarget("testdata/firstparty/lib/libb.so.meta_lic"),
+				matchTarget("testdata/firstparty/lib/libd.so.meta_lic"),
+				matchResolution(
+					"testdata/firstparty/bin/bin1.meta_lic",
+					"testdata/firstparty/bin/bin1.meta_lic",
+					"testdata/firstparty/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/bin/bin1.meta_lic",
+					"testdata/firstparty/lib/liba.so.meta_lic",
+					"testdata/firstparty/lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/bin/bin1.meta_lic",
+					"testdata/firstparty/lib/libc.a.meta_lic",
+					"testdata/firstparty/lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/bin/bin2.meta_lic",
+					"testdata/firstparty/bin/bin2.meta_lic",
+					"testdata/firstparty/bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/highest.apex.meta_lic",
+					"testdata/firstparty/bin/bin1.meta_lic",
+					"testdata/firstparty/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/highest.apex.meta_lic",
+					"testdata/firstparty/bin/bin2.meta_lic",
+					"testdata/firstparty/bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/highest.apex.meta_lic",
+					"testdata/firstparty/highest.apex.meta_lic",
+					"testdata/firstparty/highest.apex.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/highest.apex.meta_lic",
+					"testdata/firstparty/lib/liba.so.meta_lic",
+					"testdata/firstparty/lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/highest.apex.meta_lic",
+					"testdata/firstparty/lib/libb.so.meta_lic",
+					"testdata/firstparty/lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/highest.apex.meta_lic",
+					"testdata/firstparty/lib/libc.a.meta_lic",
+					"testdata/firstparty/lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/lib/liba.so.meta_lic",
+					"testdata/firstparty/lib/liba.so.meta_lic",
+					"testdata/firstparty/lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/lib/libb.so.meta_lic",
+					"testdata/firstparty/lib/libb.so.meta_lic",
+					"testdata/firstparty/lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/lib/libc.a.meta_lic",
+					"testdata/firstparty/lib/libc.a.meta_lic",
+					"testdata/firstparty/lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/lib/libd.so.meta_lic",
+					"testdata/firstparty/lib/libd.so.meta_lic",
+					"testdata/firstparty/lib/libd.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/firstparty/"},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("lib/liba.so.meta_lic"),
+				matchTarget("lib/libc.a.meta_lic"),
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchTarget("lib/libb.so.meta_lic"),
+				matchTarget("lib/libd.so.meta_lic"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"lib/libd.so.meta_lic",
+					"lib/libd.so.meta_lic",
+					"lib/libd.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "apex_trimmed_notice",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"notice"},
+				stripPrefix: "testdata/firstparty/",
+			},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("lib/liba.so.meta_lic"),
+				matchTarget("lib/libc.a.meta_lic"),
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchTarget("lib/libb.so.meta_lic"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "apex_trimmed_share",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"reciprocal", "restricted"},
+				stripPrefix: "testdata/firstparty/",
+			},
+			expectedOut: []getMatcher{},
+		},
+		{
+			condition: "firstparty",
+			name:      "apex_trimmed_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"proprietary"},
+				stripPrefix: "testdata/firstparty/",
+			},
+			expectedOut: []getMatcher{},
+		},
+		{
+			condition: "firstparty",
+			name:      "apex_trimmed_share_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"reciprocal", "restricted", "proprietary"},
+				stripPrefix: "testdata/firstparty/",
+			},
+			expectedOut: []getMatcher{},
+		},
+		{
+			condition: "firstparty",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/firstparty/", labelConditions: true},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic", "notice"),
+				matchTarget("lib/liba.so.meta_lic", "notice"),
+				matchTarget("lib/libc.a.meta_lic", "notice"),
+				matchTarget("bin/bin2.meta_lic", "notice"),
+				matchTarget("highest.apex.meta_lic", "notice"),
+				matchTarget("lib/libb.so.meta_lic", "notice"),
+				matchTarget("lib/libd.so.meta_lic", "notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"lib/libd.so.meta_lic",
+					"lib/libd.so.meta_lic",
+					"lib/libd.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/firstparty/bin/bin1.meta_lic"),
+				matchTarget("testdata/firstparty/lib/liba.so.meta_lic"),
+				matchTarget("testdata/firstparty/lib/libc.a.meta_lic"),
+				matchTarget("testdata/firstparty/bin/bin2.meta_lic"),
+				matchTarget("testdata/firstparty/container.zip.meta_lic"),
+				matchTarget("testdata/firstparty/lib/libb.so.meta_lic"),
+				matchTarget("testdata/firstparty/lib/libd.so.meta_lic"),
+				matchResolution(
+					"testdata/firstparty/bin/bin1.meta_lic",
+					"testdata/firstparty/bin/bin1.meta_lic",
+					"testdata/firstparty/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/bin/bin1.meta_lic",
+					"testdata/firstparty/lib/liba.so.meta_lic",
+					"testdata/firstparty/lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/bin/bin1.meta_lic",
+					"testdata/firstparty/lib/libc.a.meta_lic",
+					"testdata/firstparty/lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/bin/bin2.meta_lic",
+					"testdata/firstparty/bin/bin2.meta_lic",
+					"testdata/firstparty/bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/container.zip.meta_lic",
+					"testdata/firstparty/bin/bin1.meta_lic",
+					"testdata/firstparty/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/container.zip.meta_lic",
+					"testdata/firstparty/bin/bin2.meta_lic",
+					"testdata/firstparty/bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/container.zip.meta_lic",
+					"testdata/firstparty/container.zip.meta_lic",
+					"testdata/firstparty/container.zip.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/container.zip.meta_lic",
+					"testdata/firstparty/lib/liba.so.meta_lic",
+					"testdata/firstparty/lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/container.zip.meta_lic",
+					"testdata/firstparty/lib/libb.so.meta_lic",
+					"testdata/firstparty/lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/container.zip.meta_lic",
+					"testdata/firstparty/lib/libc.a.meta_lic",
+					"testdata/firstparty/lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/lib/liba.so.meta_lic",
+					"testdata/firstparty/lib/liba.so.meta_lic",
+					"testdata/firstparty/lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/lib/libb.so.meta_lic",
+					"testdata/firstparty/lib/libb.so.meta_lic",
+					"testdata/firstparty/lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/lib/libc.a.meta_lic",
+					"testdata/firstparty/lib/libc.a.meta_lic",
+					"testdata/firstparty/lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/lib/libd.so.meta_lic",
+					"testdata/firstparty/lib/libd.so.meta_lic",
+					"testdata/firstparty/lib/libd.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/firstparty/application.meta_lic"),
+				matchTarget("testdata/firstparty/lib/liba.so.meta_lic"),
+				matchTarget("testdata/firstparty/bin/bin3.meta_lic"),
+				matchTarget("testdata/firstparty/lib/libb.so.meta_lic"),
+				matchResolution(
+					"testdata/firstparty/application.meta_lic",
+					"testdata/firstparty/application.meta_lic",
+					"testdata/firstparty/application.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/application.meta_lic",
+					"testdata/firstparty/lib/liba.so.meta_lic",
+					"testdata/firstparty/lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/bin/bin3.meta_lic",
+					"testdata/firstparty/bin/bin3.meta_lic",
+					"testdata/firstparty/bin/bin3.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/lib/liba.so.meta_lic",
+					"testdata/firstparty/lib/liba.so.meta_lic",
+					"testdata/firstparty/lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/lib/libb.so.meta_lic",
+					"testdata/firstparty/lib/libb.so.meta_lic",
+					"testdata/firstparty/lib/libb.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/firstparty/bin/bin1.meta_lic"),
+				matchTarget("testdata/firstparty/lib/liba.so.meta_lic"),
+				matchTarget("testdata/firstparty/lib/libc.a.meta_lic"),
+				matchResolution(
+					"testdata/firstparty/bin/bin1.meta_lic",
+					"testdata/firstparty/bin/bin1.meta_lic",
+					"testdata/firstparty/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/bin/bin1.meta_lic",
+					"testdata/firstparty/lib/liba.so.meta_lic",
+					"testdata/firstparty/lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/bin/bin1.meta_lic",
+					"testdata/firstparty/lib/libc.a.meta_lic",
+					"testdata/firstparty/lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/lib/liba.so.meta_lic",
+					"testdata/firstparty/lib/liba.so.meta_lic",
+					"testdata/firstparty/lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/firstparty/lib/libc.a.meta_lic",
+					"testdata/firstparty/lib/libc.a.meta_lic",
+					"testdata/firstparty/lib/libc.a.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "firstparty",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/firstparty/lib/libd.so.meta_lic"),
+				matchResolution(
+					"testdata/firstparty/lib/libd.so.meta_lic",
+					"testdata/firstparty/lib/libd.so.meta_lic",
+					"testdata/firstparty/lib/libd.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "notice",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/notice/bin/bin1.meta_lic"),
+				matchTarget("testdata/notice/lib/liba.so.meta_lic"),
+				matchTarget("testdata/notice/lib/libc.a.meta_lic"),
+				matchTarget("testdata/notice/bin/bin2.meta_lic"),
+				matchTarget("testdata/notice/highest.apex.meta_lic"),
+				matchTarget("testdata/notice/lib/libb.so.meta_lic"),
+				matchTarget("testdata/notice/lib/libd.so.meta_lic"),
+				matchResolution(
+					"testdata/notice/bin/bin1.meta_lic",
+					"testdata/notice/bin/bin1.meta_lic",
+					"testdata/notice/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/bin/bin1.meta_lic",
+					"testdata/notice/lib/liba.so.meta_lic",
+					"testdata/notice/lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/bin/bin1.meta_lic",
+					"testdata/notice/lib/libc.a.meta_lic",
+					"testdata/notice/lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/bin/bin2.meta_lic",
+					"testdata/notice/bin/bin2.meta_lic",
+					"testdata/notice/bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/highest.apex.meta_lic",
+					"testdata/notice/bin/bin1.meta_lic",
+					"testdata/notice/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/highest.apex.meta_lic",
+					"testdata/notice/bin/bin2.meta_lic",
+					"testdata/notice/bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/highest.apex.meta_lic",
+					"testdata/notice/highest.apex.meta_lic",
+					"testdata/notice/highest.apex.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/highest.apex.meta_lic",
+					"testdata/notice/lib/liba.so.meta_lic",
+					"testdata/notice/lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/highest.apex.meta_lic",
+					"testdata/notice/lib/libb.so.meta_lic",
+					"testdata/notice/lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/highest.apex.meta_lic",
+					"testdata/notice/lib/libc.a.meta_lic",
+					"testdata/notice/lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/lib/liba.so.meta_lic",
+					"testdata/notice/lib/liba.so.meta_lic",
+					"testdata/notice/lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/lib/libb.so.meta_lic",
+					"testdata/notice/lib/libb.so.meta_lic",
+					"testdata/notice/lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/lib/libc.a.meta_lic",
+					"testdata/notice/lib/libc.a.meta_lic",
+					"testdata/notice/lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/lib/libd.so.meta_lic",
+					"testdata/notice/lib/libd.so.meta_lic",
+					"testdata/notice/lib/libd.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "notice",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/notice/"},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("lib/liba.so.meta_lic"),
+				matchTarget("lib/libc.a.meta_lic"),
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchTarget("lib/libb.so.meta_lic"),
+				matchTarget("lib/libd.so.meta_lic"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"lib/libd.so.meta_lic",
+					"lib/libd.so.meta_lic",
+					"lib/libd.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "notice",
+			name:      "apex_trimmed_notice",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"notice"},
+				stripPrefix: "testdata/notice/",
+			},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("lib/liba.so.meta_lic"),
+				matchTarget("lib/libc.a.meta_lic"),
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchTarget("lib/libb.so.meta_lic"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "notice",
+			name:      "apex_trimmed_share",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"reciprocal", "restricted"},
+				stripPrefix: "testdata/notice/",
+			},
+			expectedOut: []getMatcher{},
+		},
+		{
+			condition: "notice",
+			name:      "apex_trimmed_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"proprietary"},
+				stripPrefix: "testdata/notice/",
+			},
+			expectedOut: []getMatcher{},
+		},
+		{
+			condition: "notice",
+			name:      "apex_trimmed_share_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"reciprocal", "restricted", "proprietary"},
+				stripPrefix: "testdata/notice/",
+			},
+			expectedOut: []getMatcher{},
+		},
+		{
+			condition: "notice",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/notice/", labelConditions: true},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic", "notice"),
+				matchTarget("lib/liba.so.meta_lic", "notice"),
+				matchTarget("lib/libc.a.meta_lic", "notice"),
+				matchTarget("bin/bin2.meta_lic", "notice"),
+				matchTarget("highest.apex.meta_lic", "notice"),
+				matchTarget("lib/libb.so.meta_lic", "notice"),
+				matchTarget("lib/libd.so.meta_lic", "notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"lib/libd.so.meta_lic",
+					"lib/libd.so.meta_lic",
+					"lib/libd.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "notice",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/notice/bin/bin1.meta_lic"),
+				matchTarget("testdata/notice/lib/liba.so.meta_lic"),
+				matchTarget("testdata/notice/lib/libc.a.meta_lic"),
+				matchTarget("testdata/notice/bin/bin2.meta_lic"),
+				matchTarget("testdata/notice/container.zip.meta_lic"),
+				matchTarget("testdata/notice/lib/libb.so.meta_lic"),
+				matchTarget("testdata/notice/lib/libd.so.meta_lic"),
+				matchResolution(
+					"testdata/notice/bin/bin1.meta_lic",
+					"testdata/notice/bin/bin1.meta_lic",
+					"testdata/notice/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/bin/bin1.meta_lic",
+					"testdata/notice/lib/liba.so.meta_lic",
+					"testdata/notice/lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/bin/bin1.meta_lic",
+					"testdata/notice/lib/libc.a.meta_lic",
+					"testdata/notice/lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/bin/bin2.meta_lic",
+					"testdata/notice/bin/bin2.meta_lic",
+					"testdata/notice/bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/container.zip.meta_lic",
+					"testdata/notice/bin/bin1.meta_lic",
+					"testdata/notice/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/container.zip.meta_lic",
+					"testdata/notice/bin/bin2.meta_lic",
+					"testdata/notice/bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/container.zip.meta_lic",
+					"testdata/notice/container.zip.meta_lic",
+					"testdata/notice/container.zip.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/container.zip.meta_lic",
+					"testdata/notice/lib/liba.so.meta_lic",
+					"testdata/notice/lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/container.zip.meta_lic",
+					"testdata/notice/lib/libb.so.meta_lic",
+					"testdata/notice/lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/container.zip.meta_lic",
+					"testdata/notice/lib/libc.a.meta_lic",
+					"testdata/notice/lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/lib/liba.so.meta_lic",
+					"testdata/notice/lib/liba.so.meta_lic",
+					"testdata/notice/lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/lib/libb.so.meta_lic",
+					"testdata/notice/lib/libb.so.meta_lic",
+					"testdata/notice/lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/lib/libc.a.meta_lic",
+					"testdata/notice/lib/libc.a.meta_lic",
+					"testdata/notice/lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/lib/libd.so.meta_lic",
+					"testdata/notice/lib/libd.so.meta_lic",
+					"testdata/notice/lib/libd.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "notice",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/notice/application.meta_lic"),
+				matchTarget("testdata/notice/lib/liba.so.meta_lic"),
+				matchTarget("testdata/notice/bin/bin3.meta_lic"),
+				matchTarget("testdata/notice/lib/libb.so.meta_lic"),
+				matchResolution(
+					"testdata/notice/application.meta_lic",
+					"testdata/notice/application.meta_lic",
+					"testdata/notice/application.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/application.meta_lic",
+					"testdata/notice/lib/liba.so.meta_lic",
+					"testdata/notice/lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/bin/bin3.meta_lic",
+					"testdata/notice/bin/bin3.meta_lic",
+					"testdata/notice/bin/bin3.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/lib/liba.so.meta_lic",
+					"testdata/notice/lib/liba.so.meta_lic",
+					"testdata/notice/lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/lib/libb.so.meta_lic",
+					"testdata/notice/lib/libb.so.meta_lic",
+					"testdata/notice/lib/libb.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "notice",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/notice/bin/bin1.meta_lic"),
+				matchTarget("testdata/notice/lib/liba.so.meta_lic"),
+				matchTarget("testdata/notice/lib/libc.a.meta_lic"),
+				matchResolution(
+					"testdata/notice/bin/bin1.meta_lic",
+					"testdata/notice/bin/bin1.meta_lic",
+					"testdata/notice/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/bin/bin1.meta_lic",
+					"testdata/notice/lib/liba.so.meta_lic",
+					"testdata/notice/lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/bin/bin1.meta_lic",
+					"testdata/notice/lib/libc.a.meta_lic",
+					"testdata/notice/lib/libc.a.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/lib/liba.so.meta_lic",
+					"testdata/notice/lib/liba.so.meta_lic",
+					"testdata/notice/lib/liba.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/notice/lib/libc.a.meta_lic",
+					"testdata/notice/lib/libc.a.meta_lic",
+					"testdata/notice/lib/libc.a.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "notice",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/notice/lib/libd.so.meta_lic"),
+				matchResolution(
+					"testdata/notice/lib/libd.so.meta_lic",
+					"testdata/notice/lib/libd.so.meta_lic",
+					"testdata/notice/lib/libd.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/reciprocal/bin/bin1.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/liba.so.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/libc.a.meta_lic"),
+				matchTarget("testdata/reciprocal/bin/bin2.meta_lic"),
+				matchTarget("testdata/reciprocal/highest.apex.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/libb.so.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/libd.so.meta_lic"),
+				matchResolution(
+					"testdata/reciprocal/bin/bin1.meta_lic",
+					"testdata/reciprocal/bin/bin1.meta_lic",
+					"testdata/reciprocal/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/reciprocal/bin/bin1.meta_lic",
+					"testdata/reciprocal/lib/liba.so.meta_lic",
+					"testdata/reciprocal/lib/liba.so.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"testdata/reciprocal/bin/bin1.meta_lic",
+					"testdata/reciprocal/lib/libc.a.meta_lic",
+					"testdata/reciprocal/lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"testdata/reciprocal/bin/bin2.meta_lic",
+					"testdata/reciprocal/bin/bin2.meta_lic",
+					"testdata/reciprocal/bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/reciprocal/highest.apex.meta_lic",
+					"testdata/reciprocal/bin/bin1.meta_lic",
+					"testdata/reciprocal/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/reciprocal/highest.apex.meta_lic",
+					"testdata/reciprocal/bin/bin2.meta_lic",
+					"testdata/reciprocal/bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/reciprocal/highest.apex.meta_lic",
+					"testdata/reciprocal/highest.apex.meta_lic",
+					"testdata/reciprocal/highest.apex.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/reciprocal/highest.apex.meta_lic",
+					"testdata/reciprocal/lib/liba.so.meta_lic",
+					"testdata/reciprocal/lib/liba.so.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"testdata/reciprocal/highest.apex.meta_lic",
+					"testdata/reciprocal/lib/libb.so.meta_lic",
+					"testdata/reciprocal/lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/reciprocal/highest.apex.meta_lic",
+					"testdata/reciprocal/lib/libc.a.meta_lic",
+					"testdata/reciprocal/lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"testdata/reciprocal/lib/liba.so.meta_lic",
+					"testdata/reciprocal/lib/liba.so.meta_lic",
+					"testdata/reciprocal/lib/liba.so.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"testdata/reciprocal/lib/libb.so.meta_lic",
+					"testdata/reciprocal/lib/libb.so.meta_lic",
+					"testdata/reciprocal/lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/reciprocal/lib/libc.a.meta_lic",
+					"testdata/reciprocal/lib/libc.a.meta_lic",
+					"testdata/reciprocal/lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"testdata/reciprocal/lib/libd.so.meta_lic",
+					"testdata/reciprocal/lib/libd.so.meta_lic",
+					"testdata/reciprocal/lib/libd.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/reciprocal/"},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("lib/liba.so.meta_lic"),
+				matchTarget("lib/libc.a.meta_lic"),
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchTarget("lib/libb.so.meta_lic"),
+				matchTarget("lib/libd.so.meta_lic"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"lib/libd.so.meta_lic",
+					"lib/libd.so.meta_lic",
+					"lib/libd.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex_trimmed_notice",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"notice"},
+				stripPrefix: "testdata/reciprocal/",
+			},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchTarget("lib/libb.so.meta_lic"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex_trimmed_share",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"reciprocal", "restricted"},
+				stripPrefix: "testdata/reciprocal/",
+			},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("lib/liba.so.meta_lic"),
+				matchTarget("lib/libc.a.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"reciprocal"),
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex_trimmed_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"proprietary"},
+				stripPrefix: "testdata/reciprocal/",
+			},
+			expectedOut: []getMatcher{},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex_trimmed_share_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"reciprocal", "restricted", "proprietary"},
+				stripPrefix: "testdata/reciprocal/",
+			},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("lib/liba.so.meta_lic"),
+				matchTarget("lib/libc.a.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"reciprocal"),
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/reciprocal/", labelConditions: true},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic", "notice"),
+				matchTarget("lib/liba.so.meta_lic", "reciprocal"),
+				matchTarget("lib/libc.a.meta_lic", "reciprocal"),
+				matchTarget("bin/bin2.meta_lic", "notice"),
+				matchTarget("highest.apex.meta_lic", "notice"),
+				matchTarget("lib/libb.so.meta_lic", "notice"),
+				matchTarget("lib/libd.so.meta_lic", "notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"lib/libd.so.meta_lic",
+					"lib/libd.so.meta_lic",
+					"lib/libd.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/reciprocal/bin/bin1.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/liba.so.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/libc.a.meta_lic"),
+				matchTarget("testdata/reciprocal/bin/bin2.meta_lic"),
+				matchTarget("testdata/reciprocal/container.zip.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/libb.so.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/libd.so.meta_lic"),
+				matchResolution(
+					"testdata/reciprocal/bin/bin1.meta_lic",
+					"testdata/reciprocal/bin/bin1.meta_lic",
+					"testdata/reciprocal/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/reciprocal/bin/bin1.meta_lic",
+					"testdata/reciprocal/lib/liba.so.meta_lic",
+					"testdata/reciprocal/lib/liba.so.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"testdata/reciprocal/bin/bin1.meta_lic",
+					"testdata/reciprocal/lib/libc.a.meta_lic",
+					"testdata/reciprocal/lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"testdata/reciprocal/bin/bin2.meta_lic",
+					"testdata/reciprocal/bin/bin2.meta_lic",
+					"testdata/reciprocal/bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/reciprocal/container.zip.meta_lic",
+					"testdata/reciprocal/bin/bin1.meta_lic",
+					"testdata/reciprocal/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/reciprocal/container.zip.meta_lic",
+					"testdata/reciprocal/bin/bin2.meta_lic",
+					"testdata/reciprocal/bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/reciprocal/container.zip.meta_lic",
+					"testdata/reciprocal/container.zip.meta_lic",
+					"testdata/reciprocal/container.zip.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/reciprocal/container.zip.meta_lic",
+					"testdata/reciprocal/lib/liba.so.meta_lic",
+					"testdata/reciprocal/lib/liba.so.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"testdata/reciprocal/container.zip.meta_lic",
+					"testdata/reciprocal/lib/libb.so.meta_lic",
+					"testdata/reciprocal/lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/reciprocal/container.zip.meta_lic",
+					"testdata/reciprocal/lib/libc.a.meta_lic",
+					"testdata/reciprocal/lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"testdata/reciprocal/lib/liba.so.meta_lic",
+					"testdata/reciprocal/lib/liba.so.meta_lic",
+					"testdata/reciprocal/lib/liba.so.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"testdata/reciprocal/lib/libb.so.meta_lic",
+					"testdata/reciprocal/lib/libb.so.meta_lic",
+					"testdata/reciprocal/lib/libb.so.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/reciprocal/lib/libc.a.meta_lic",
+					"testdata/reciprocal/lib/libc.a.meta_lic",
+					"testdata/reciprocal/lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"testdata/reciprocal/lib/libd.so.meta_lic",
+					"testdata/reciprocal/lib/libd.so.meta_lic",
+					"testdata/reciprocal/lib/libd.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/reciprocal/application.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/liba.so.meta_lic"),
+				matchTarget("testdata/reciprocal/bin/bin3.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/libb.so.meta_lic"),
+				matchResolution(
+					"testdata/reciprocal/application.meta_lic",
+					"testdata/reciprocal/application.meta_lic",
+					"testdata/reciprocal/application.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/reciprocal/application.meta_lic",
+					"testdata/reciprocal/lib/liba.so.meta_lic",
+					"testdata/reciprocal/lib/liba.so.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"testdata/reciprocal/bin/bin3.meta_lic",
+					"testdata/reciprocal/bin/bin3.meta_lic",
+					"testdata/reciprocal/bin/bin3.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/reciprocal/lib/liba.so.meta_lic",
+					"testdata/reciprocal/lib/liba.so.meta_lic",
+					"testdata/reciprocal/lib/liba.so.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"testdata/reciprocal/lib/libb.so.meta_lic",
+					"testdata/reciprocal/lib/libb.so.meta_lic",
+					"testdata/reciprocal/lib/libb.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/reciprocal/bin/bin1.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/liba.so.meta_lic"),
+				matchTarget("testdata/reciprocal/lib/libc.a.meta_lic"),
+				matchResolution(
+					"testdata/reciprocal/bin/bin1.meta_lic",
+					"testdata/reciprocal/bin/bin1.meta_lic",
+					"testdata/reciprocal/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/reciprocal/bin/bin1.meta_lic",
+					"testdata/reciprocal/lib/liba.so.meta_lic",
+					"testdata/reciprocal/lib/liba.so.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"testdata/reciprocal/bin/bin1.meta_lic",
+					"testdata/reciprocal/lib/libc.a.meta_lic",
+					"testdata/reciprocal/lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"testdata/reciprocal/lib/liba.so.meta_lic",
+					"testdata/reciprocal/lib/liba.so.meta_lic",
+					"testdata/reciprocal/lib/liba.so.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"testdata/reciprocal/lib/libc.a.meta_lic",
+					"testdata/reciprocal/lib/libc.a.meta_lic",
+					"testdata/reciprocal/lib/libc.a.meta_lic",
+					"reciprocal"),
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/reciprocal/lib/libd.so.meta_lic"),
+				matchResolution(
+					"testdata/reciprocal/lib/libd.so.meta_lic",
+					"testdata/reciprocal/lib/libd.so.meta_lic",
+					"testdata/reciprocal/lib/libd.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/restricted/bin/bin1.meta_lic"),
+				matchTarget("testdata/restricted/lib/liba.so.meta_lic"),
+				matchTarget("testdata/restricted/lib/libc.a.meta_lic"),
+				matchTarget("testdata/restricted/bin/bin2.meta_lic"),
+				matchTarget("testdata/restricted/lib/libb.so.meta_lic"),
+				matchTarget("testdata/restricted/lib/libd.so.meta_lic"),
+				matchTarget("testdata/restricted/highest.apex.meta_lic"),
+				matchResolution(
+					"testdata/restricted/bin/bin1.meta_lic",
+					"testdata/restricted/bin/bin1.meta_lic",
+					"testdata/restricted/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/restricted/bin/bin1.meta_lic",
+					"testdata/restricted/bin/bin1.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/bin/bin1.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/bin/bin1.meta_lic",
+					"testdata/restricted/lib/libc.a.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/bin/bin1.meta_lic",
+					"testdata/restricted/lib/libc.a.meta_lic",
+					"testdata/restricted/lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"testdata/restricted/bin/bin2.meta_lic",
+					"testdata/restricted/bin/bin2.meta_lic",
+					"testdata/restricted/bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/restricted/bin/bin2.meta_lic",
+					"testdata/restricted/bin/bin2.meta_lic",
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/bin/bin2.meta_lic",
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/bin/bin2.meta_lic",
+					"testdata/restricted/lib/libd.so.meta_lic",
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/highest.apex.meta_lic",
+					"testdata/restricted/bin/bin1.meta_lic",
+					"testdata/restricted/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/restricted/highest.apex.meta_lic",
+					"testdata/restricted/bin/bin1.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/highest.apex.meta_lic",
+					"testdata/restricted/bin/bin2.meta_lic",
+					"testdata/restricted/bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/restricted/highest.apex.meta_lic",
+					"testdata/restricted/bin/bin2.meta_lic",
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/highest.apex.meta_lic",
+					"testdata/restricted/highest.apex.meta_lic",
+					"testdata/restricted/highest.apex.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/restricted/highest.apex.meta_lic",
+					"testdata/restricted/highest.apex.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/highest.apex.meta_lic",
+					"testdata/restricted/highest.apex.meta_lic",
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/highest.apex.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/highest.apex.meta_lic",
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/highest.apex.meta_lic",
+					"testdata/restricted/lib/libc.a.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/highest.apex.meta_lic",
+					"testdata/restricted/lib/libc.a.meta_lic",
+					"testdata/restricted/lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"testdata/restricted/highest.apex.meta_lic",
+					"testdata/restricted/lib/libd.so.meta_lic",
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/lib/libc.a.meta_lic",
+					"testdata/restricted/lib/libc.a.meta_lic",
+					"testdata/restricted/lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"testdata/restricted/lib/libd.so.meta_lic",
+					"testdata/restricted/lib/libd.so.meta_lic",
+					"testdata/restricted/lib/libd.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/restricted/"},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("lib/liba.so.meta_lic"),
+				matchTarget("lib/libc.a.meta_lic"),
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("lib/libb.so.meta_lic"),
+				matchTarget("lib/libd.so.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"lib/libd.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libd.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"lib/libd.so.meta_lic",
+					"lib/libd.so.meta_lic",
+					"lib/libd.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "apex_trimmed_notice",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"notice"},
+				stripPrefix: "testdata/restricted/",
+			},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "apex_trimmed_share",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"reciprocal", "restricted"},
+				stripPrefix: "testdata/restricted/",
+			},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("lib/liba.so.meta_lic"),
+				matchTarget("lib/libc.a.meta_lic"),
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("lib/libb.so.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "apex_trimmed_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"proprietary"},
+				stripPrefix: "testdata/restricted/",
+			},
+			expectedOut: []getMatcher{},
+		},
+		{
+			condition: "restricted",
+			name:      "apex_trimmed_share_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"reciprocal", "restricted", "proprietary"},
+				stripPrefix: "testdata/restricted/",
+			},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("lib/liba.so.meta_lic"),
+				matchTarget("lib/libc.a.meta_lic"),
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("lib/libb.so.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/restricted/", labelConditions: true},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic", "notice"),
+				matchTarget("lib/liba.so.meta_lic", "restricted"),
+				matchTarget("lib/libc.a.meta_lic", "reciprocal"),
+				matchTarget("bin/bin2.meta_lic", "notice"),
+				matchTarget("lib/libb.so.meta_lic", "restricted"),
+				matchTarget("lib/libd.so.meta_lic", "notice"),
+				matchTarget("highest.apex.meta_lic", "notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"lib/libd.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libd.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"lib/libd.so.meta_lic",
+					"lib/libd.so.meta_lic",
+					"lib/libd.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/restricted/bin/bin1.meta_lic"),
+				matchTarget("testdata/restricted/lib/liba.so.meta_lic"),
+				matchTarget("testdata/restricted/lib/libc.a.meta_lic"),
+				matchTarget("testdata/restricted/bin/bin2.meta_lic"),
+				matchTarget("testdata/restricted/lib/libb.so.meta_lic"),
+				matchTarget("testdata/restricted/lib/libd.so.meta_lic"),
+				matchTarget("testdata/restricted/container.zip.meta_lic"),
+				matchResolution(
+					"testdata/restricted/bin/bin1.meta_lic",
+					"testdata/restricted/bin/bin1.meta_lic",
+					"testdata/restricted/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/restricted/bin/bin1.meta_lic",
+					"testdata/restricted/bin/bin1.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/bin/bin1.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/bin/bin1.meta_lic",
+					"testdata/restricted/lib/libc.a.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/bin/bin1.meta_lic",
+					"testdata/restricted/lib/libc.a.meta_lic",
+					"testdata/restricted/lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"testdata/restricted/bin/bin2.meta_lic",
+					"testdata/restricted/bin/bin2.meta_lic",
+					"testdata/restricted/bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/restricted/bin/bin2.meta_lic",
+					"testdata/restricted/bin/bin2.meta_lic",
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/bin/bin2.meta_lic",
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/bin/bin2.meta_lic",
+					"testdata/restricted/lib/libd.so.meta_lic",
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/container.zip.meta_lic",
+					"testdata/restricted/bin/bin1.meta_lic",
+					"testdata/restricted/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/restricted/container.zip.meta_lic",
+					"testdata/restricted/bin/bin1.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/container.zip.meta_lic",
+					"testdata/restricted/bin/bin2.meta_lic",
+					"testdata/restricted/bin/bin2.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/restricted/container.zip.meta_lic",
+					"testdata/restricted/bin/bin2.meta_lic",
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/container.zip.meta_lic",
+					"testdata/restricted/container.zip.meta_lic",
+					"testdata/restricted/container.zip.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/restricted/container.zip.meta_lic",
+					"testdata/restricted/container.zip.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/container.zip.meta_lic",
+					"testdata/restricted/container.zip.meta_lic",
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/container.zip.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/container.zip.meta_lic",
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/container.zip.meta_lic",
+					"testdata/restricted/lib/libc.a.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/container.zip.meta_lic",
+					"testdata/restricted/lib/libc.a.meta_lic",
+					"testdata/restricted/lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"testdata/restricted/container.zip.meta_lic",
+					"testdata/restricted/lib/libd.so.meta_lic",
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/lib/libc.a.meta_lic",
+					"testdata/restricted/lib/libc.a.meta_lic",
+					"testdata/restricted/lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"testdata/restricted/lib/libd.so.meta_lic",
+					"testdata/restricted/lib/libd.so.meta_lic",
+					"testdata/restricted/lib/libd.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/restricted/application.meta_lic"),
+				matchTarget("testdata/restricted/lib/liba.so.meta_lic"),
+				matchTarget("testdata/restricted/lib/libb.so.meta_lic"),
+				matchTarget("testdata/restricted/bin/bin3.meta_lic"),
+				matchResolution(
+					"testdata/restricted/application.meta_lic",
+					"testdata/restricted/application.meta_lic",
+					"testdata/restricted/application.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/restricted/application.meta_lic",
+					"testdata/restricted/application.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/application.meta_lic",
+					"testdata/restricted/application.meta_lic",
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/application.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/application.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/application.meta_lic",
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/bin/bin3.meta_lic",
+					"testdata/restricted/bin/bin3.meta_lic",
+					"testdata/restricted/bin/bin3.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"testdata/restricted/lib/libb.so.meta_lic",
+					"restricted"),
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/restricted/bin/bin1.meta_lic"),
+				matchTarget("testdata/restricted/lib/liba.so.meta_lic"),
+				matchTarget("testdata/restricted/lib/libc.a.meta_lic"),
+				matchResolution(
+					"testdata/restricted/bin/bin1.meta_lic",
+					"testdata/restricted/bin/bin1.meta_lic",
+					"testdata/restricted/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/restricted/bin/bin1.meta_lic",
+					"testdata/restricted/bin/bin1.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/bin/bin1.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/bin/bin1.meta_lic",
+					"testdata/restricted/lib/libc.a.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/bin/bin1.meta_lic",
+					"testdata/restricted/lib/libc.a.meta_lic",
+					"testdata/restricted/lib/libc.a.meta_lic",
+					"reciprocal"),
+				matchResolution(
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"testdata/restricted/lib/liba.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/restricted/lib/libc.a.meta_lic",
+					"testdata/restricted/lib/libc.a.meta_lic",
+					"testdata/restricted/lib/libc.a.meta_lic",
+					"reciprocal"),
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/restricted/lib/libd.so.meta_lic"),
+				matchResolution(
+					"testdata/restricted/lib/libd.so.meta_lic",
+					"testdata/restricted/lib/libd.so.meta_lic",
+					"testdata/restricted/lib/libd.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/proprietary/bin/bin1.meta_lic"),
+				matchTarget("testdata/proprietary/lib/liba.so.meta_lic"),
+				matchTarget("testdata/proprietary/lib/libc.a.meta_lic"),
+				matchTarget("testdata/proprietary/bin/bin2.meta_lic"),
+				matchTarget("testdata/proprietary/lib/libb.so.meta_lic"),
+				matchTarget("testdata/proprietary/lib/libd.so.meta_lic"),
+				matchTarget("testdata/proprietary/highest.apex.meta_lic"),
+				matchResolution(
+					"testdata/proprietary/bin/bin1.meta_lic",
+					"testdata/proprietary/bin/bin1.meta_lic",
+					"testdata/proprietary/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/proprietary/bin/bin1.meta_lic",
+					"testdata/proprietary/lib/liba.so.meta_lic",
+					"testdata/proprietary/lib/liba.so.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"testdata/proprietary/bin/bin1.meta_lic",
+					"testdata/proprietary/lib/libc.a.meta_lic",
+					"testdata/proprietary/lib/libc.a.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"testdata/proprietary/bin/bin2.meta_lic",
+					"testdata/proprietary/bin/bin2.meta_lic",
+					"testdata/proprietary/bin/bin2.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"testdata/proprietary/bin/bin2.meta_lic",
+					"testdata/proprietary/bin/bin2.meta_lic",
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/proprietary/bin/bin2.meta_lic",
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/proprietary/bin/bin2.meta_lic",
+					"testdata/proprietary/lib/libd.so.meta_lic",
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/proprietary/highest.apex.meta_lic",
+					"testdata/proprietary/bin/bin1.meta_lic",
+					"testdata/proprietary/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/proprietary/highest.apex.meta_lic",
+					"testdata/proprietary/bin/bin2.meta_lic",
+					"testdata/proprietary/bin/bin2.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"testdata/proprietary/highest.apex.meta_lic",
+					"testdata/proprietary/bin/bin2.meta_lic",
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/proprietary/highest.apex.meta_lic",
+					"testdata/proprietary/highest.apex.meta_lic",
+					"testdata/proprietary/highest.apex.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/proprietary/highest.apex.meta_lic",
+					"testdata/proprietary/highest.apex.meta_lic",
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/proprietary/highest.apex.meta_lic",
+					"testdata/proprietary/lib/liba.so.meta_lic",
+					"testdata/proprietary/lib/liba.so.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"testdata/proprietary/highest.apex.meta_lic",
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/proprietary/highest.apex.meta_lic",
+					"testdata/proprietary/lib/libc.a.meta_lic",
+					"testdata/proprietary/lib/libc.a.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"testdata/proprietary/highest.apex.meta_lic",
+					"testdata/proprietary/lib/libd.so.meta_lic",
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/proprietary/lib/liba.so.meta_lic",
+					"testdata/proprietary/lib/liba.so.meta_lic",
+					"testdata/proprietary/lib/liba.so.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/proprietary/lib/libc.a.meta_lic",
+					"testdata/proprietary/lib/libc.a.meta_lic",
+					"testdata/proprietary/lib/libc.a.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"testdata/proprietary/lib/libd.so.meta_lic",
+					"testdata/proprietary/lib/libd.so.meta_lic",
+					"testdata/proprietary/lib/libd.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex_trimmed",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/proprietary/"},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("lib/liba.so.meta_lic"),
+				matchTarget("lib/libc.a.meta_lic"),
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("lib/libb.so.meta_lic"),
+				matchTarget("lib/libd.so.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"lib/libd.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libd.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"lib/libd.so.meta_lic",
+					"lib/libd.so.meta_lic",
+					"lib/libd.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex_trimmed_notice",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"notice"},
+				stripPrefix: "testdata/proprietary/",
+			},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex_trimmed_share",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"reciprocal", "restricted"},
+				stripPrefix: "testdata/proprietary/",
+			},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("lib/libb.so.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex_trimmed_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"proprietary"},
+				stripPrefix: "testdata/proprietary/",
+			},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("lib/liba.so.meta_lic"),
+				matchTarget("lib/libc.a.meta_lic"),
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"proprietary"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"proprietary"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"proprietary"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"proprietary"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"proprietary"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"proprietary"),
+				matchResolution(
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"proprietary"),
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex_trimmed_share_private",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx: context{
+				conditions:  []string{"reciprocal", "restricted", "proprietary"},
+				stripPrefix: "testdata/proprietary/",
+			},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic"),
+				matchTarget("lib/liba.so.meta_lic"),
+				matchTarget("lib/libc.a.meta_lic"),
+				matchTarget("bin/bin2.meta_lic"),
+				matchTarget("lib/libb.so.meta_lic"),
+				matchTarget("highest.apex.meta_lic"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"proprietary"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"proprietary"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"proprietary"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"proprietary"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"proprietary"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"proprietary"),
+				matchResolution(
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"proprietary"),
+				matchResolution(
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex_trimmed_labelled",
+			roots:     []string{"highest.apex.meta_lic"},
+			ctx:       context{stripPrefix: "testdata/proprietary/", labelConditions: true},
+			expectedOut: []getMatcher{
+				matchTarget("bin/bin1.meta_lic", "notice"),
+				matchTarget("lib/liba.so.meta_lic", "by_exception_only", "proprietary"),
+				matchTarget("lib/libc.a.meta_lic", "by_exception_only", "proprietary"),
+				matchTarget("bin/bin2.meta_lic", "by_exception_only", "proprietary"),
+				matchTarget("lib/libb.so.meta_lic", "restricted"),
+				matchTarget("lib/libd.so.meta_lic", "notice"),
+				matchTarget("highest.apex.meta_lic", "notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"bin/bin1.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"bin/bin2.meta_lic",
+					"lib/libd.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin1.meta_lic",
+					"bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"bin/bin2.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"bin/bin2.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"notice"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"highest.apex.meta_lic",
+					"lib/libd.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"lib/liba.so.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"lib/libc.a.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"lib/libd.so.meta_lic",
+					"lib/libd.so.meta_lic",
+					"lib/libd.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/proprietary/bin/bin1.meta_lic"),
+				matchTarget("testdata/proprietary/lib/liba.so.meta_lic"),
+				matchTarget("testdata/proprietary/lib/libc.a.meta_lic"),
+				matchTarget("testdata/proprietary/bin/bin2.meta_lic"),
+				matchTarget("testdata/proprietary/lib/libb.so.meta_lic"),
+				matchTarget("testdata/proprietary/lib/libd.so.meta_lic"),
+				matchTarget("testdata/proprietary/container.zip.meta_lic"),
+				matchResolution(
+					"testdata/proprietary/bin/bin1.meta_lic",
+					"testdata/proprietary/bin/bin1.meta_lic",
+					"testdata/proprietary/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/proprietary/bin/bin1.meta_lic",
+					"testdata/proprietary/lib/liba.so.meta_lic",
+					"testdata/proprietary/lib/liba.so.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"testdata/proprietary/bin/bin1.meta_lic",
+					"testdata/proprietary/lib/libc.a.meta_lic",
+					"testdata/proprietary/lib/libc.a.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"testdata/proprietary/bin/bin2.meta_lic",
+					"testdata/proprietary/bin/bin2.meta_lic",
+					"testdata/proprietary/bin/bin2.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"testdata/proprietary/bin/bin2.meta_lic",
+					"testdata/proprietary/bin/bin2.meta_lic",
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/proprietary/bin/bin2.meta_lic",
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/proprietary/bin/bin2.meta_lic",
+					"testdata/proprietary/lib/libd.so.meta_lic",
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/proprietary/container.zip.meta_lic",
+					"testdata/proprietary/bin/bin1.meta_lic",
+					"testdata/proprietary/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/proprietary/container.zip.meta_lic",
+					"testdata/proprietary/bin/bin2.meta_lic",
+					"testdata/proprietary/bin/bin2.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"testdata/proprietary/container.zip.meta_lic",
+					"testdata/proprietary/bin/bin2.meta_lic",
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/proprietary/container.zip.meta_lic",
+					"testdata/proprietary/container.zip.meta_lic",
+					"testdata/proprietary/container.zip.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/proprietary/container.zip.meta_lic",
+					"testdata/proprietary/container.zip.meta_lic",
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/proprietary/container.zip.meta_lic",
+					"testdata/proprietary/lib/liba.so.meta_lic",
+					"testdata/proprietary/lib/liba.so.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"testdata/proprietary/container.zip.meta_lic",
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/proprietary/container.zip.meta_lic",
+					"testdata/proprietary/lib/libc.a.meta_lic",
+					"testdata/proprietary/lib/libc.a.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"testdata/proprietary/container.zip.meta_lic",
+					"testdata/proprietary/lib/libd.so.meta_lic",
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/proprietary/lib/liba.so.meta_lic",
+					"testdata/proprietary/lib/liba.so.meta_lic",
+					"testdata/proprietary/lib/liba.so.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/proprietary/lib/libc.a.meta_lic",
+					"testdata/proprietary/lib/libc.a.meta_lic",
+					"testdata/proprietary/lib/libc.a.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"testdata/proprietary/lib/libd.so.meta_lic",
+					"testdata/proprietary/lib/libd.so.meta_lic",
+					"testdata/proprietary/lib/libd.so.meta_lic",
+					"notice"),
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/proprietary/application.meta_lic"),
+				matchTarget("testdata/proprietary/lib/liba.so.meta_lic"),
+				matchTarget("testdata/proprietary/lib/libb.so.meta_lic"),
+				matchTarget("testdata/proprietary/bin/bin3.meta_lic"),
+				matchResolution(
+					"testdata/proprietary/application.meta_lic",
+					"testdata/proprietary/application.meta_lic",
+					"testdata/proprietary/application.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/proprietary/application.meta_lic",
+					"testdata/proprietary/application.meta_lic",
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/proprietary/application.meta_lic",
+					"testdata/proprietary/lib/liba.so.meta_lic",
+					"testdata/proprietary/lib/liba.so.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"testdata/proprietary/application.meta_lic",
+					"testdata/proprietary/lib/liba.so.meta_lic",
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/proprietary/application.meta_lic",
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/proprietary/bin/bin3.meta_lic",
+					"testdata/proprietary/bin/bin3.meta_lic",
+					"testdata/proprietary/bin/bin3.meta_lic",
+					"restricted"),
+				matchResolution(
+					"testdata/proprietary/lib/liba.so.meta_lic",
+					"testdata/proprietary/lib/liba.so.meta_lic",
+					"testdata/proprietary/lib/liba.so.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"testdata/proprietary/lib/libb.so.meta_lic",
+					"restricted"),
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/proprietary/bin/bin1.meta_lic"),
+				matchTarget("testdata/proprietary/lib/liba.so.meta_lic"),
+				matchTarget("testdata/proprietary/lib/libc.a.meta_lic"),
+				matchResolution(
+					"testdata/proprietary/bin/bin1.meta_lic",
+					"testdata/proprietary/bin/bin1.meta_lic",
+					"testdata/proprietary/bin/bin1.meta_lic",
+					"notice"),
+				matchResolution(
+					"testdata/proprietary/bin/bin1.meta_lic",
+					"testdata/proprietary/lib/liba.so.meta_lic",
+					"testdata/proprietary/lib/liba.so.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"testdata/proprietary/bin/bin1.meta_lic",
+					"testdata/proprietary/lib/libc.a.meta_lic",
+					"testdata/proprietary/lib/libc.a.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"testdata/proprietary/lib/liba.so.meta_lic",
+					"testdata/proprietary/lib/liba.so.meta_lic",
+					"testdata/proprietary/lib/liba.so.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+				matchResolution(
+					"testdata/proprietary/lib/libc.a.meta_lic",
+					"testdata/proprietary/lib/libc.a.meta_lic",
+					"testdata/proprietary/lib/libc.a.meta_lic",
+					"by_exception_only",
+					"proprietary"),
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "library",
+			roots:     []string{"lib/libd.so.meta_lic"},
+			expectedOut: []getMatcher{
+				matchTarget("testdata/proprietary/lib/libd.so.meta_lic"),
+				matchResolution(
+					"testdata/proprietary/lib/libd.so.meta_lic",
+					"testdata/proprietary/lib/libd.so.meta_lic",
+					"testdata/proprietary/lib/libd.so.meta_lic",
+					"notice"),
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.condition+" "+tt.name, func(t *testing.T) {
+			ctx := &testContext{0, make(map[string]string)}
+
+			expectedOut := &bytes.Buffer{}
+			for _, eo := range tt.expectedOut {
+				m := eo(ctx)
+				expectedOut.WriteString(m.matchString(ctx))
+				expectedOut.WriteString("\n")
+			}
+
+			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)
+			}
+			tt.ctx.graphViz = true
+			err := dumpResolutions(&tt.ctx, stdout, stderr, rootFiles...)
+
+			if err != nil {
+				t.Fatalf("dumpresolutions: error = %v, stderr = %v", err, stderr)
+				return
+			}
+			if stderr.Len() > 0 {
+				t.Errorf("dumpresolutions: gotStderr = %v, want none", stderr)
+			}
+			outList := strings.Split(stdout.String(), "\n")
+			outLine := 0
+			if outList[outLine] != "strict digraph {" {
+				t.Errorf("dumpresolutions: got 1st line %v, want strict digraph {")
+			}
+			outLine++
+			if strings.HasPrefix(strings.TrimLeft(outList[outLine], " \t"), "rankdir") {
+				outLine++
+			}
+			endOut := len(outList)
+			for endOut > 0 && strings.TrimLeft(outList[endOut-1], " \t") == "" {
+				endOut--
+			}
+			if outList[endOut-1] != "}" {
+				t.Errorf("dumpresolutions: got last line %v, want }", outList[endOut-1])
+			}
+			endOut--
+			if strings.HasPrefix(strings.TrimLeft(outList[endOut-1], " \t"), "{rank=same") {
+				endOut--
+			}
+			expectedList := strings.Split(expectedOut.String(), "\n")
+			for len(expectedList) > 0 && expectedList[len(expectedList)-1] == "" {
+				expectedList = expectedList[0 : len(expectedList)-1]
+			}
+			matchLine := 0
+
+			for outLine < endOut && matchLine < len(expectedList) && strings.TrimLeft(outList[outLine], " \t") == expectedList[matchLine] {
+				outLine++
+				matchLine++
+			}
+			if outLine < endOut || matchLine < len(expectedList) {
+				if outLine >= endOut {
+					t.Errorf("dumpresolutions: missing lines at end of graph, want %d lines %v", len(expectedList)-matchLine, strings.Join(expectedList[matchLine:], "\n"))
+				} else if matchLine >= len(expectedList) {
+					t.Errorf("dumpresolutions: unexpected lines at end of graph starting line %d, got %v, want nothing", outLine+1, strings.Join(outList[outLine:], "\n"))
+				} else {
+					t.Errorf("dumpresolutions: at line %d, got %v, want %v", outLine+1, strings.Join(outList[outLine:], "\n"), strings.Join(expectedList[matchLine:], "\n"))
+				}
+			}
+		})
+	}
+}
diff --git a/tools/compliance/cmd/listshare.go b/tools/compliance/cmd/listshare.go
new file mode 100644
index 0000000..bba2308
--- /dev/null
+++ b/tools/compliance/cmd/listshare.go
@@ -0,0 +1,124 @@
+// Copyright 2021 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 (
+	"compliance"
+	"flag"
+	"fmt"
+	"io"
+	"os"
+	"path/filepath"
+	"sort"
+)
+
+func init() {
+	flag.Usage = func() {
+		fmt.Fprintf(os.Stderr, `Usage: %s file.meta_lic {file.meta_lic...}
+
+Outputs a csv file with 1 project per line in the first field followed
+by target:condition pairs describing why the project must be shared.
+
+Each target is the path to a generated license metadata file for a
+Soong module or Make target, and the license condition is either
+restricted (e.g. GPL) or reciprocal (e.g. MPL).
+`, filepath.Base(os.Args[0]))
+	}
+}
+
+var (
+	failNoneRequested = fmt.Errorf("\nNo license metadata files requested")
+	failNoLicenses = fmt.Errorf("No licenses found")
+)
+
+func main() {
+	flag.Parse()
+
+	// Must specify at least one root target.
+	if flag.NArg() == 0 {
+		flag.Usage()
+		os.Exit(2)
+	}
+
+	err := listShare(os.Stdout, os.Stderr, flag.Args()...)
+	if err != nil {
+		if err == failNoneRequested {
+			flag.Usage()
+		}
+		fmt.Fprintf(os.Stderr, "%s\n", err.Error())
+		os.Exit(1)
+	}
+	os.Exit(0)
+}
+
+// listShare implements the listshare utility.
+func listShare(stdout, stderr io.Writer, files ...string) error {
+	// Must be at least one root file.
+	if len(files) < 1 {
+		return failNoneRequested
+	}
+
+	// Read the license graph from the license metadata files (*.meta_lic).
+	licenseGraph, err := compliance.ReadLicenseGraph(os.DirFS("."), stderr, files)
+	if err != nil {
+		return fmt.Errorf("Unable to read license metadata file(s) %q: %v\n", files, err)
+	}
+	if licenseGraph == nil {
+		return failNoLicenses
+	}
+
+	// shareSource contains all source-sharing resolutions.
+	shareSource := compliance.ResolveSourceSharing(licenseGraph)
+
+	// Group the resolutions by project.
+	presolution := make(map[string]*compliance.LicenseConditionSet)
+	for _, target := range shareSource.AttachesTo() {
+		rl := shareSource.Resolutions(target)
+		sort.Sort(rl)
+		for _, r := range rl {
+			for _, p := range r.ActsOn().Projects() {
+				if _, ok := presolution[p]; !ok {
+					presolution[p] = r.Resolves().Copy()
+					continue
+				}
+				presolution[p].AddSet(r.Resolves())
+			}
+		}
+	}
+
+	// Sort the projects for repeatability/stability.
+	projects := make([]string, 0, len(presolution))
+	for p := range presolution {
+		projects = append(projects, p)
+	}
+	sort.Strings(projects)
+
+	// Output the sorted projects and the source-sharing license conditions that each project resolves.
+	for _, p := range projects {
+		fmt.Fprintf(stdout, "%s", p)
+
+		// Sort the conditions for repeatability/stability.
+		conditions := presolution[p].AsList()
+		sort.Sort(conditions)
+
+		// Output the sorted origin:condition pairs.
+		for _, lc := range conditions {
+			fmt.Fprintf(stdout, ",%s:%s", lc.Origin().Name(), lc.Name())
+		}
+		fmt.Fprintf(stdout, "\n")
+	}
+
+	return nil
+}
diff --git a/tools/compliance/cmd/listshare_test.go b/tools/compliance/cmd/listshare_test.go
new file mode 100644
index 0000000..b4847e3
--- /dev/null
+++ b/tools/compliance/cmd/listshare_test.go
@@ -0,0 +1,405 @@
+// Copyright 2021 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"
+	"strings"
+	"testing"
+)
+
+func Test(t *testing.T) {
+	type projectShare struct {
+		project    string
+		conditions []string
+	}
+	tests := []struct {
+		condition   string
+		name        string
+		roots       []string
+		expectedOut []projectShare
+	}{
+		{
+			condition:   "firstparty",
+			name:        "apex",
+			roots:       []string{"highest.apex.meta_lic"},
+			expectedOut: []projectShare{},
+		},
+		{
+			condition:   "firstparty",
+			name:        "container",
+			roots:       []string{"container.zip.meta_lic"},
+			expectedOut: []projectShare{},
+		},
+		{
+			condition:   "firstparty",
+			name:        "application",
+			roots:       []string{"application.meta_lic"},
+			expectedOut: []projectShare{},
+		},
+		{
+			condition:   "firstparty",
+			name:        "binary",
+			roots:       []string{"bin/bin1.meta_lic"},
+			expectedOut: []projectShare{},
+		},
+		{
+			condition:   "firstparty",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			expectedOut: []projectShare{},
+		},
+		{
+			condition:   "notice",
+			name:        "apex",
+			roots:       []string{"highest.apex.meta_lic"},
+			expectedOut: []projectShare{},
+		},
+		{
+			condition:   "notice",
+			name:        "container",
+			roots:       []string{"container.zip.meta_lic"},
+			expectedOut: []projectShare{},
+		},
+		{
+			condition:   "notice",
+			name:        "application",
+			roots:       []string{"application.meta_lic"},
+			expectedOut: []projectShare{},
+		},
+		{
+			condition:   "notice",
+			name:        "binary",
+			roots:       []string{"bin/bin1.meta_lic"},
+			expectedOut: []projectShare{},
+		},
+		{
+			condition:   "notice",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			expectedOut: []projectShare{},
+		},
+		{
+			condition: "reciprocal",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []projectShare{
+				{
+					project:    "device/library",
+					conditions: []string{"lib/liba.so.meta_lic:reciprocal"},
+				},
+				{
+					project: "static/library",
+					conditions: []string{
+						"lib/libc.a.meta_lic:reciprocal",
+					},
+				},
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []projectShare{
+				{
+					project:    "device/library",
+					conditions: []string{"lib/liba.so.meta_lic:reciprocal"},
+				},
+				{
+					project: "static/library",
+					conditions: []string{
+						"lib/libc.a.meta_lic:reciprocal",
+					},
+				},
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []projectShare{
+				{
+					project:    "device/library",
+					conditions: []string{"lib/liba.so.meta_lic:reciprocal"},
+				},
+			},
+		},
+		{
+			condition: "reciprocal",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []projectShare{
+				{
+					project: "device/library",
+					conditions: []string{
+						"lib/liba.so.meta_lic:reciprocal",
+					},
+				},
+				{
+					project: "static/library",
+					conditions: []string{
+						"lib/libc.a.meta_lic:reciprocal",
+					},
+				},
+			},
+		},
+		{
+			condition:   "reciprocal",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			expectedOut: []projectShare{},
+		},
+		{
+			condition: "restricted",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []projectShare{
+				{
+					project:    "base/library",
+					conditions: []string{"lib/libb.so.meta_lic:restricted"},
+				},
+				{
+					project:    "device/library",
+					conditions: []string{"lib/liba.so.meta_lic:restricted"},
+				},
+				{
+					project:    "dynamic/binary",
+					conditions: []string{"lib/libb.so.meta_lic:restricted"},
+				},
+				{
+					project: "highest/apex",
+					conditions: []string{
+						"lib/liba.so.meta_lic:restricted",
+						"lib/libb.so.meta_lic:restricted",
+					},
+				},
+				{
+					project: "static/binary",
+					conditions: []string{
+						"lib/liba.so.meta_lic:restricted",
+					},
+				},
+				{
+					project: "static/library",
+					conditions: []string{
+						"lib/liba.so.meta_lic:restricted",
+						"lib/libc.a.meta_lic:reciprocal",
+					},
+				},
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []projectShare{
+				{
+					project:    "base/library",
+					conditions: []string{"lib/libb.so.meta_lic:restricted"},
+				},
+				{
+					project: "container/zip",
+					conditions: []string{
+						"lib/liba.so.meta_lic:restricted",
+						"lib/libb.so.meta_lic:restricted",
+					},
+				},
+				{
+					project:    "device/library",
+					conditions: []string{"lib/liba.so.meta_lic:restricted"},
+				},
+				{
+					project:    "dynamic/binary",
+					conditions: []string{"lib/libb.so.meta_lic:restricted"},
+				},
+				{
+					project: "static/binary",
+					conditions: []string{
+						"lib/liba.so.meta_lic:restricted",
+					},
+				},
+				{
+					project: "static/library",
+					conditions: []string{
+						"lib/liba.so.meta_lic:restricted",
+						"lib/libc.a.meta_lic:reciprocal",
+					},
+				},
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []projectShare{
+				{
+					project: "device/library",
+					conditions: []string{
+						"lib/liba.so.meta_lic:restricted",
+						"lib/libb.so.meta_lic:restricted",
+					},
+				},
+				{
+					project: "distributable/application",
+					conditions: []string{
+						"lib/liba.so.meta_lic:restricted",
+						"lib/libb.so.meta_lic:restricted",
+					},
+				},
+			},
+		},
+		{
+			condition: "restricted",
+			name:      "binary",
+			roots:     []string{"bin/bin1.meta_lic"},
+			expectedOut: []projectShare{
+				{
+					project: "device/library",
+					conditions: []string{
+						"lib/liba.so.meta_lic:restricted",
+					},
+				},
+				{
+					project: "static/binary",
+					conditions: []string{
+						"lib/liba.so.meta_lic:restricted",
+					},
+				},
+				{
+					project: "static/library",
+					conditions: []string{
+						"lib/liba.so.meta_lic:restricted",
+						"lib/libc.a.meta_lic:reciprocal",
+					},
+				},
+			},
+		},
+		{
+			condition:   "restricted",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			expectedOut: []projectShare{},
+		},
+		{
+			condition: "proprietary",
+			name:      "apex",
+			roots:     []string{"highest.apex.meta_lic"},
+			expectedOut: []projectShare{
+				{
+					project:    "base/library",
+					conditions: []string{"lib/libb.so.meta_lic:restricted"},
+				},
+				{
+					project:    "dynamic/binary",
+					conditions: []string{"lib/libb.so.meta_lic:restricted"},
+				},
+				{
+					project:    "highest/apex",
+					conditions: []string{"lib/libb.so.meta_lic:restricted"},
+				},
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "container",
+			roots:     []string{"container.zip.meta_lic"},
+			expectedOut: []projectShare{
+				{
+					project:    "base/library",
+					conditions: []string{"lib/libb.so.meta_lic:restricted"},
+				},
+				{
+					project:    "container/zip",
+					conditions: []string{"lib/libb.so.meta_lic:restricted"},
+				},
+				{
+					project:    "dynamic/binary",
+					conditions: []string{"lib/libb.so.meta_lic:restricted"},
+				},
+			},
+		},
+		{
+			condition: "proprietary",
+			name:      "application",
+			roots:     []string{"application.meta_lic"},
+			expectedOut: []projectShare{
+				{
+					project:    "device/library",
+					conditions: []string{"lib/libb.so.meta_lic:restricted"},
+				},
+				{
+					project:    "distributable/application",
+					conditions: []string{"lib/libb.so.meta_lic:restricted"},
+				},
+			},
+		},
+		{
+			condition:   "proprietary",
+			name:        "binary",
+			roots:       []string{"bin/bin1.meta_lic"},
+			expectedOut: []projectShare{},
+		},
+		{
+			condition:   "proprietary",
+			name:        "library",
+			roots:       []string{"lib/libd.so.meta_lic"},
+			expectedOut: []projectShare{},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.condition+" "+tt.name, func(t *testing.T) {
+			expectedOut := &bytes.Buffer{}
+			for _, p := range tt.expectedOut {
+				expectedOut.WriteString(p.project)
+				for _, lc := range p.conditions {
+					expectedOut.WriteString(",")
+					expectedOut.WriteString("testdata/")
+					expectedOut.WriteString(tt.condition)
+					expectedOut.WriteString("/")
+					expectedOut.WriteString(lc)
+				}
+				expectedOut.WriteString("\n")
+			}
+
+			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)
+			}
+			err := listShare(stdout, stderr, rootFiles...)
+			if err != nil {
+				t.Fatalf("listshare: error = %v, stderr = %v", err, stderr)
+				return
+			}
+			if stderr.Len() > 0 {
+				t.Errorf("listshare: gotStderr = %v, want none", stderr)
+			}
+			out := stdout.String()
+			expected := expectedOut.String()
+			if out != expected {
+				outList := strings.Split(out, "\n")
+				expectedList := strings.Split(expected, "\n")
+				startLine := 0
+				for len(outList) > startLine && len(expectedList) > startLine && outList[startLine] == expectedList[startLine] {
+					startLine++
+				}
+				t.Errorf("listshare: gotStdout = %v, want %v, somewhere near line %d Stdout = %v, want %v",
+					out, expected, startLine+1, outList[startLine], expectedList[startLine])
+			}
+		})
+	}
+}
diff --git a/tools/compliance/cmd/testdata/README.md b/tools/compliance/cmd/testdata/README.md
new file mode 100644
index 0000000..9872c04
--- /dev/null
+++ b/tools/compliance/cmd/testdata/README.md
@@ -0,0 +1,321 @@
+## Test data
+
+Each directory under testdata/ defines a similar build graph.
+All have the same structure, but different versions of the graph have different
+license metadata.
+
+### Testdata build graph structure:
+
+The structure is meant to simulate some common scenarios:
+
+*   a `lib/` directory with some libraries
+*   a `bin/` directory with some executables
+*   one of the binaries, `bin3`, is a toolchain executable like a compiler
+*   an `application` built with the `bin3` compiler and linking a couple libraries
+*   a pure aggregation `continer.zip` that merely bundles files together, and
+*   an apex file (more like an apk file) with some binaries and libraries.
+
+The testdata starts with a `firstparty/` version containng only first-party
+licenses, and each subsequent directory introduces more restrictive conditions:
+
+*   `notice/` starts with `firstparty/` adds third-party notice conditions
+*   `reciprocal/` starts with `notice/` and adds some reciprocal conditions
+*   `restricted/` starts with `reciprocal/` and adds some restricted conditions
+*   `proprietary/` starts with `restricted/` and add some privacy conditions
+
+#### a `lib/` directory with some libraries
+
+```dot
+strict digraph {
+	liba [label="lib/liba.so.meta_lic"];
+	libb [label="lib/libb.so.meta_lic"];
+	libc [label="lib/libc.a.meta_lic"];
+	libd [label="lib/libd.so.meta_lic"];
+}
+```
+
+#### a `bin/` directory with some executables
+
+strict digraph {
+	rankdir=LR;
+	bin1 [label="bin/bin1.meta_lic"];
+	bin2 [label="bin/bin2.meta_lic"];
+	bin3 [label="bin/bin3.meta_lic\ntoolchain"];
+	liba [label="lib/liba.so.meta_lic"];
+	libb [label="lib/libb.so.meta_lic"];
+	libc [label="lib/libc.a.meta_lic"];
+	libd [label="lib/libd.so.meta_lic"];
+	bin1 -> liba [label="static"];
+	bin1 -> libc [label="static"];
+	bin2 -> libb [label="dynamic"];
+	bin2 -> libd [label="dynamic"];
+	{rank=same; bin1 bin2 bin3}
+}
+
+#### an `application` built with the `bin3` compiler and linking a couple libraries
+
+```dot
+strict digraph {
+	rankdir=LR;
+	app [label="application.meta_lic"];
+	bin3 [label="bin/bin3.meta_lic"];
+	liba [label="lib/liba.so.meta_lic"];
+	libb [label="lib/libb.so.meta_lic"];
+	app -> bin3 [label="toolchain"];
+	app -> liba [label="static"];
+	app -> libb [label="dynamic"];
+	{rank=same; app}
+}
+```
+
+#### a pure aggregation `container.zip` that merely bundles files together
+
+strict digraph {
+	rankdir=LR;
+	bin1 [label="bin/bin1.meta_lic"];
+	bin2 [label="bin/bin2.meta_lic"];
+	container [label="container.zip.meta_lic"];
+	liba [label="lib/liba.so.meta_lic"];
+	libb [label="lib/libb.so.meta_lic"];
+	libc [label="lib/libc.a.meta_lic"];
+	libd [label="lib/libd.so.meta_lic"];
+	bin1 -> liba [label="static"];
+	bin1 -> libc [label="static"];
+	bin2 -> libb [label="dynamic"];
+	bin2 -> libd [label="dynamic"];
+	container -> bin1 [label="static"];
+	container -> bin2 [label="static"];
+	container -> liba [label="static"];
+	container -> libb [label="static"];
+	{rank=same; container}
+}
+
+#### an apex file (more like an apk file) with some binaries and libraries
+
+```dot
+strict digraph {
+	rankdir=LR;
+	apex [label="highest.apex.meta_lic"];
+	bin1 [label="bin/bin1.meta_lic"];
+	bin2 [label="bin/bin2.meta_lic"];
+	bin3 [label="bin/bin3.meta_lic"];
+	liba [label="lib/liba.so.meta_lic"];
+	libb [label="lib/libb.so.meta_lic"];
+	libc [label="lib/libc.a.meta_lic"];
+	libd [label="lib/libd.so.meta_lic"];
+	bin1 -> liba [label="static"];
+	bin1 -> libc [label="static"];
+	bin2 -> libb [label="dynamic"];
+	bin2 -> libd [label="dynamic"];
+	apex -> bin1 [label="static"];
+	apex -> bin2 [label="static"];
+	apex -> liba [label="static"];
+	apex -> libb [label="static"];
+	{rank=same; apex}
+}
+```
+
+#### the whole build graph
+
+```dot
+strict digraph {
+	rankdir=LR;
+	apex [label="highest.apex.meta_lic"];
+	app [label="application.meta_lic"];
+	bin1 [label="bin/bin1.meta_lic"];
+	bin2 [label="bin/bin2.meta_lic"];
+	bin3 [label="bin/bin3.meta_lic"];
+	container [label="container.zip.meta_lic"];
+	liba [label="lib/liba.so.meta_lic"];
+	libb [label="lib/libb.so.meta_lic"];
+	libc [label="lib/libc.a.meta_lic"];
+	libd [label="lib/libd.so.meta_lic"];
+	app -> bin3 [label="toolchain"];
+	app -> liba [label="static"];
+	app -> libb [label="dynamic"];
+	bin1 -> liba [label="static"];
+	bin1 -> libc [label="static"];
+	bin2 -> libb [label="dynamic"];
+	bin2 -> libd [label="dynamic"];
+	container -> bin1 [label="static"];
+	container -> bin2 [label="static"];
+	container -> liba [label="static"];
+	container -> libb [label="static"];
+	apex -> bin1 [label="static"];
+	apex -> bin2 [label="static"];
+	apex -> liba [label="static"];
+	apex -> libb [label="static"];
+	{rank=same; app container apex}
+}
+```
+
+
+### firstparty/ testdata starts with all first-party licensing
+
+```dot
+strict digraph {
+	rankdir=LR;
+	app [label="firstparty/application.meta_lic"];
+	bin1 [label="firstparty/bin/bin1.meta_lic"];
+	bin2 [label="firstparty/bin/bin2.meta_lic"];
+	bin3 [label="firstparty/bin/bin3.meta_lic"];
+	container [label="firstparty/container.zip.meta_lic"];
+	apex [label="firstparty/highest.apex.meta_lic"];
+	liba [label="firstparty/lib/liba.so.meta_lic"];
+	libb [label="firstparty/lib/libb.so.meta_lic"];
+	libc [label="firstparty/lib/libc.a.meta_lic"];
+	lib [label="firstparty/lib/libd.so.meta_lic"];
+	app -> bin3 [label="toolchain"];
+	app -> liba [label="static"];
+	app -> libb [label="dynamic"];
+	bin1 -> liba [label="static"];
+	bin1 -> libc [label="static"];
+	bin2 -> libb [label="dynamic"];
+	bin2 -> libd [label="dynamic"];
+	container -> bin1 [label="static"];
+	container -> bin2 [label="static"];
+	container -> liba [label="static"];
+	container -> libb [label="static"];
+	apex -> bin1 [label="static"];
+	apex -> bin2 [label="static"];
+	apex -> liba [label="static"];
+	apex -> libb [label="static"];
+	{rank=same; app container apex}
+}
+```
+
+### notice/ testdata introduces third-party notice conditions
+
+```dot
+strict digraph {
+	rankdir=LR;
+	app [label="notice/application.meta_lic"];
+	bin1 [label="notice/bin/bin1.meta_lic"];
+	bin2 [label="notice/bin/bin2.meta_lic"];
+	bin3 [label="notice/bin/bin3.meta_lic\nnotice"];
+	container [label="notice/container.zip.meta_lic"];
+	apex [label="notice/highest.apex.meta_lic"];
+	liba [label="notice/lib/liba.so.meta_lic\nnotice"];
+	libb [label="notice/lib/libb.so.meta_lic"];
+	libc [label="notice/lib/libc.a.meta_lic\nnotice"];
+	libd [label="notice/lib/libd.so.meta_lic\nnotice"];
+	app -> bin3 [label="toolchain"];
+	app -> liba [label="static"];
+	app -> libb [label="dynamic"];
+	bin1 -> liba [label="static"];
+	bin1 -> libc [label="static"];
+	bin2 -> libb [label="dynamic"];
+	bin2 -> libd [label="dynamic"];
+	container -> bin1 [label="static"];
+	container -> bin2 [label="static"];
+	container -> liba [label="static"];
+	container -> libb [label="static"];
+	apex -> bin1 [label="static"];
+	apex -> bin2 [label="static"];
+	apex -> liba [label="static"];
+	apex -> libb [label="static"];
+	{rank=same; app container apex}
+}
+```
+
+### reciprocal/ testdata introduces third-party reciprocal sharing conditions
+
+```dot
+strict digraph {
+	rankdir=LR;
+	app [label="reciprocal/application.meta_lic"];
+	bin1 [label="reciprocal/bin/bin1.meta_lic"];
+	bin2 [label="reciprocal/bin/bin2.meta_lic"];
+	bin3 [label="reciprocal/bin/bin3.meta_lic\nnotice"];
+	container [label="reciprocal/container.zip.meta_lic"];
+	apex [label="reciprocal/highest.apex.meta_lic"];
+	liba [label="reciprocal/lib/liba.so.meta_lic\nreciprocal"];
+	libb [label="reciprocal/lib/libb.so.meta_lic"];
+	libc [label="reciprocal/lib/libc.a.meta_lic\nreciprocal"];
+	libd [label="reciprocal/lib/libd.so.meta_lic\nnotice"];
+	app -> bin3 [label="toolchain"];
+	app -> liba [label="static"];
+	app -> libb [label="dynamic"];
+	bin1 -> liba [label="static"];
+	bin1 -> libc [label="static"];
+	bin2 -> libb [label="dynamic"];
+	bin2 -> libd [label="dynamic"];
+	container -> bin1 [label="static"];
+	container -> bin2 [label="static"];
+	container -> liba [label="static"];
+	container -> libb [label="static"];
+	apex -> bin1 [label="static"];
+	apex -> bin2 [label="static"];
+	apex -> liba [label="static"];
+	apex -> libb [label="static"];
+	{rank=same; app container apex}
+}
+```
+
+### restricted/ testdata introduces restricted source sharing conditions
+
+```dot
+strict digraph {
+	rankdir=LR;
+	app [label="restricted/application.meta_lic"];
+	bin1 [label="restricted/bin/bin1.meta_lic"];
+	bin2 [label="restricted/bin/bin2.meta_lic"];
+	bin3 [label="restricted/bin/bin3.meta_lic\nrestricted"];
+	container [label="restricted/container.zip.meta_lic"];
+	apex [label="restricted/highest.apex.meta_lic"];
+	liba [label="restricted/lib/liba.so.meta_lic\nrestricted"];
+	libb [label="restricted/lib/libb.so.meta_lic\nrestricted"];
+	libc [label="restricted/lib/libc.a.meta_lic\nreciprocal"];
+	libd [label="restricted/lib/libd.so.meta_lic\nnotice"];
+	app -> bin3 [label="toolchain"];
+	app -> liba [label="static"];
+	app -> libb [label="dynamic"];
+	bin1 -> liba [label="static"];
+	bin1 -> libc [label="static"];
+	bin2 -> libb [label="dynamic"];
+	bin2 -> libd [label="dynamic"];
+	container -> bin1 [label="static"];
+	container -> bin2 [label="static"];
+	container -> liba [label="static"];
+	container -> libb [label="static"];
+	apex -> bin1 [label="static"];
+	apex -> bin2 [label="static"];
+	apex -> liba [label="static"];
+	apex -> libb [label="static"];
+	{rank=same; app container apex}
+}
+```
+
+### proprietary/ testdata introduces privacy conditions
+
+```dot
+strict digraph {
+	rankdir=LR;
+	app [label="proprietary/application.meta_lic"];
+	bin1 [label="proprietary/bin/bin1.meta_lic"];
+	bin2 [label="proprietary/bin/bin2.meta_lic\nby_exception_only\nproprietary"];
+	bin3 [label="proprietary/bin/bin3.meta_lic\nrestricted"];
+	container [label="proprietary/container.zip.meta_lic"];
+	apex [label="proprietary/highest.apex.meta_lic"];
+	liba [label="proprietary/lib/liba.so.meta_lic\nby_exception_only\nproprietary"];
+	libb [label="proprietary/lib/libb.so.meta_lic\nrestricted"];
+	libc [label="proprietary/lib/libc.a.meta_lic\nby_exception_only\nproprietary"];
+	libd [label="proprietary/lib/libd.so.meta_lic\nnotice"];
+	app -> bin3 [label="toolchain"];
+	app -> liba [label="static"];
+	app -> libb [label="dynamic"];
+	bin1 -> liba [label="static"];
+	bin1 -> libc [label="static"];
+	bin2 -> libb [label="dynamic"];
+	bin2 -> libd [label="dynamic"];
+	container -> bin1 [label="static"];
+	container -> bin2 [label="static"];
+	container -> liba [label="static"];
+	container -> libb [label="static"];
+	apex -> bin1 [label="static"];
+	apex -> bin2 [label="static"];
+	apex -> liba [label="static"];
+	apex -> libb [label="static"];
+	{rank=same; app container apex}
+}
+```
diff --git a/tools/compliance/cmd/testdata/firstparty/application.meta_lic b/tools/compliance/cmd/testdata/firstparty/application.meta_lic
new file mode 100644
index 0000000..58a1566
--- /dev/null
+++ b/tools/compliance/cmd/testdata/firstparty/application.meta_lic
@@ -0,0 +1,24 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "distributable/application"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/application_intermediates/application"
+installed:  "out/target/product/fictional/bin/application"
+sources:  "out/target/product/fictional/system/lib/liba.a"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/bin/bin3"
+deps:  {
+  file:  "testdata/firstparty/bin/bin3.meta_lic"
+  annotations:  "toolchain"
+}
+deps:  {
+  file:  "testdata/firstparty/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/firstparty/lib/libb.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/firstparty/bin/bin1.meta_lic b/tools/compliance/cmd/testdata/firstparty/bin/bin1.meta_lic
new file mode 100644
index 0000000..34d81d9
--- /dev/null
+++ b/tools/compliance/cmd/testdata/firstparty/bin/bin1.meta_lic
@@ -0,0 +1,19 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "static/binary"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin1"
+installed:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/lib/liba.a"
+sources:  "out/target/product/fictional/system/lib/libc.a"
+deps:  {
+  file:  "testdata/firstparty/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/firstparty/lib/libc.a.meta_lic"
+  annotations:  "static"
+}
diff --git a/tools/compliance/cmd/testdata/firstparty/bin/bin2.meta_lic b/tools/compliance/cmd/testdata/firstparty/bin/bin2.meta_lic
new file mode 100644
index 0000000..6154421
--- /dev/null
+++ b/tools/compliance/cmd/testdata/firstparty/bin/bin2.meta_lic
@@ -0,0 +1,19 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "dynamic/binary"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin1"
+installed:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/lib/libd.so"
+deps:  {
+  file:  "testdata/firstparty/lib/libb.so.meta_lic"
+  annotations:  "dynamic"
+}
+deps:  {
+  file:  "testdata/firstparty/lib/libd.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/firstparty/bin/bin3.meta_lic b/tools/compliance/cmd/testdata/firstparty/bin/bin3.meta_lic
new file mode 100644
index 0000000..9b7908e
--- /dev/null
+++ b/tools/compliance/cmd/testdata/firstparty/bin/bin3.meta_lic
@@ -0,0 +1,9 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "standalone/binary"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin3"
+installed:  "out/target/product/fictional/system/bin/bin3"
diff --git a/tools/compliance/cmd/testdata/firstparty/container.zip.meta_lic b/tools/compliance/cmd/testdata/firstparty/container.zip.meta_lic
new file mode 100644
index 0000000..350b123
--- /dev/null
+++ b/tools/compliance/cmd/testdata/firstparty/container.zip.meta_lic
@@ -0,0 +1,36 @@
+package_name:  "Android"
+projects:  "container/zip"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  true
+built:  "out/target/product/fictional/obj/ETC/container_intermediates/container.zip"
+installed:  "out/target/product/fictional/data/container.zip"
+install_map {
+  from_path:  "out/target/product/fictional/system/lib/"
+  container_path:  ""
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/bin/"
+  container_path:  ""
+}
+sources:  "out/target/product/fictional/system/lib/liba.so"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/bin/bin2"
+deps:  {
+  file:  "testdata/firstparty/bin/bin1.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/firstparty/bin/bin2.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/firstparty/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/firstparty/lib/libb.so.meta_lic"
+  annotations:  "static"
+}
diff --git a/tools/compliance/cmd/testdata/firstparty/highest.apex.meta_lic b/tools/compliance/cmd/testdata/firstparty/highest.apex.meta_lic
new file mode 100644
index 0000000..53f81a2
--- /dev/null
+++ b/tools/compliance/cmd/testdata/firstparty/highest.apex.meta_lic
@@ -0,0 +1,44 @@
+package_name:  "Android"
+projects:  "highest/apex"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  true
+built:  "out/target/product/fictional/obj/ETC/highest_intermediates/highest.apex"
+installed:  "out/target/product/fictional/system/apex/highest.apex"
+install_map {
+  from_path:  "out/target/product/fictional/system/lib/liba.so"
+  container_path:  "lib/liba.so"
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/lib/libb.so"
+  container_path:  "lib/libb.so"
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/bin/bin1"
+  container_path:  "bin/bin1"
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/bin/bin2"
+  container_path:  "bin/bin2"
+}
+sources:  "out/target/product/fictional/system/lib/liba.so"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/bin/bin2"
+deps:  {
+  file:  "testdata/firstparty/bin/bin1.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/firstparty/bin/bin2.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/firstparty/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/firstparty/lib/libb.so.meta_lic"
+  annotations:  "static"
+}
diff --git a/tools/compliance/cmd/testdata/firstparty/lib/liba.so.meta_lic b/tools/compliance/cmd/testdata/firstparty/lib/liba.so.meta_lic
new file mode 100644
index 0000000..7913af0
--- /dev/null
+++ b/tools/compliance/cmd/testdata/firstparty/lib/liba.so.meta_lic
@@ -0,0 +1,9 @@
+package_name:  "Android"
+projects:  "device/library"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/liba.so"
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/liba.a"
+installed:  "out/target/product/fictional/system/lib/liba.so"
diff --git a/tools/compliance/cmd/testdata/firstparty/lib/libb.so.meta_lic b/tools/compliance/cmd/testdata/firstparty/lib/libb.so.meta_lic
new file mode 100644
index 0000000..a4935d4
--- /dev/null
+++ b/tools/compliance/cmd/testdata/firstparty/lib/libb.so.meta_lic
@@ -0,0 +1,9 @@
+package_name:  "Android"
+projects:  "base/library"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libb.so"
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libb.a"
+installed:  "out/target/product/fictional/system/lib/libb.so"
diff --git a/tools/compliance/cmd/testdata/firstparty/lib/libc.a.meta_lic b/tools/compliance/cmd/testdata/firstparty/lib/libc.a.meta_lic
new file mode 100644
index 0000000..fa7459a
--- /dev/null
+++ b/tools/compliance/cmd/testdata/firstparty/lib/libc.a.meta_lic
@@ -0,0 +1,7 @@
+package_name:  "Android"
+projects:  "static/library"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libc.a"
diff --git a/tools/compliance/cmd/testdata/firstparty/lib/libd.so.meta_lic b/tools/compliance/cmd/testdata/firstparty/lib/libd.so.meta_lic
new file mode 100644
index 0000000..a2db94a
--- /dev/null
+++ b/tools/compliance/cmd/testdata/firstparty/lib/libd.so.meta_lic
@@ -0,0 +1,8 @@
+package_name:  "Android"
+projects:  "dynamic/library"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libd.so"
+installed:  "out/target/product/fictional/system/lib/libd.so"
diff --git a/tools/compliance/cmd/testdata/notice/application.meta_lic b/tools/compliance/cmd/testdata/notice/application.meta_lic
new file mode 100644
index 0000000..56c60ef
--- /dev/null
+++ b/tools/compliance/cmd/testdata/notice/application.meta_lic
@@ -0,0 +1,24 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "distributable/application"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/application_intermediates/application"
+installed:  "out/target/product/fictional/bin/application"
+sources:  "out/target/product/fictional/system/lib/liba.a"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/bin/bin3"
+deps:  {
+  file:  "testdata/notice/bin/bin3.meta_lic"
+  annotations:  "toolchain"
+}
+deps:  {
+  file:  "testdata/notice/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/notice/lib/libb.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/notice/bin/bin1.meta_lic b/tools/compliance/cmd/testdata/notice/bin/bin1.meta_lic
new file mode 100644
index 0000000..9bede1b
--- /dev/null
+++ b/tools/compliance/cmd/testdata/notice/bin/bin1.meta_lic
@@ -0,0 +1,19 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "static/binary"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin1"
+installed:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/lib/liba.a"
+sources:  "out/target/product/fictional/system/lib/libc.a"
+deps:  {
+  file:  "testdata/notice/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/notice/lib/libc.a.meta_lic"
+  annotations:  "static"
+}
diff --git a/tools/compliance/cmd/testdata/notice/bin/bin2.meta_lic b/tools/compliance/cmd/testdata/notice/bin/bin2.meta_lic
new file mode 100644
index 0000000..86e06c6
--- /dev/null
+++ b/tools/compliance/cmd/testdata/notice/bin/bin2.meta_lic
@@ -0,0 +1,19 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "dynamic/binary"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin1"
+installed:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/lib/libd.so"
+deps:  {
+  file:  "testdata/notice/lib/libb.so.meta_lic"
+  annotations:  "dynamic"
+}
+deps:  {
+  file:  "testdata/notice/lib/libd.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/notice/bin/bin3.meta_lic b/tools/compliance/cmd/testdata/notice/bin/bin3.meta_lic
new file mode 100644
index 0000000..285d899
--- /dev/null
+++ b/tools/compliance/cmd/testdata/notice/bin/bin3.meta_lic
@@ -0,0 +1,8 @@
+package_name:  "Compiler"
+module_classes: "EXECUTABLES"
+projects:  "standalone/binary"
+license_kinds:  "SPDX-license-identifier-NCSA"
+license_conditions:  "notice"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin3"
+installed:  "out/target/product/fictional/system/bin/bin3"
diff --git a/tools/compliance/cmd/testdata/notice/container.zip.meta_lic b/tools/compliance/cmd/testdata/notice/container.zip.meta_lic
new file mode 100644
index 0000000..e8af61c
--- /dev/null
+++ b/tools/compliance/cmd/testdata/notice/container.zip.meta_lic
@@ -0,0 +1,36 @@
+package_name:  "Android"
+projects:  "container/zip"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  true
+built:  "out/target/product/fictional/obj/ETC/container_intermediates/container.zip"
+installed:  "out/target/product/fictional/data/container.zip"
+install_map {
+  from_path:  "out/target/product/fictional/system/lib/"
+  container_path:  ""
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/bin/"
+  container_path:  ""
+}
+sources:  "out/target/product/fictional/system/lib/liba.so"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/bin/bin2"
+deps:  {
+  file:  "testdata/notice/bin/bin1.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/notice/bin/bin2.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/notice/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/notice/lib/libb.so.meta_lic"
+  annotations:  "static"
+}
diff --git a/tools/compliance/cmd/testdata/notice/highest.apex.meta_lic b/tools/compliance/cmd/testdata/notice/highest.apex.meta_lic
new file mode 100644
index 0000000..9b90aa5
--- /dev/null
+++ b/tools/compliance/cmd/testdata/notice/highest.apex.meta_lic
@@ -0,0 +1,44 @@
+package_name:  "Android"
+projects:  "highest/apex"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  true
+built:  "out/target/product/fictional/obj/ETC/highest_intermediates/highest.apex"
+installed:  "out/target/product/fictional/system/apex/highest.apex"
+install_map {
+  from_path:  "out/target/product/fictional/system/lib/liba.so"
+  container_path:  "lib/liba.so"
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/lib/libb.so"
+  container_path:  "lib/libb.so"
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/bin/bin1"
+  container_path:  "bin/bin1"
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/bin/bin2"
+  container_path:  "bin/bin2"
+}
+sources:  "out/target/product/fictional/system/lib/liba.so"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/bin/bin2"
+deps:  {
+  file:  "testdata/notice/bin/bin1.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/notice/bin/bin2.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/notice/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/notice/lib/libb.so.meta_lic"
+  annotations:  "static"
+}
diff --git a/tools/compliance/cmd/testdata/notice/lib/liba.so.meta_lic b/tools/compliance/cmd/testdata/notice/lib/liba.so.meta_lic
new file mode 100644
index 0000000..a69839f
--- /dev/null
+++ b/tools/compliance/cmd/testdata/notice/lib/liba.so.meta_lic
@@ -0,0 +1,8 @@
+package_name:  "Device"
+projects:  "device/library"
+license_kinds:  "SPDX-license-identifier-BSD"
+license_conditions:  "notice"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/liba.so"
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/liba.a"
+installed:  "out/target/product/fictional/system/lib/liba.so"
diff --git a/tools/compliance/cmd/testdata/notice/lib/libb.so.meta_lic b/tools/compliance/cmd/testdata/notice/lib/libb.so.meta_lic
new file mode 100644
index 0000000..a4935d4
--- /dev/null
+++ b/tools/compliance/cmd/testdata/notice/lib/libb.so.meta_lic
@@ -0,0 +1,9 @@
+package_name:  "Android"
+projects:  "base/library"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libb.so"
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libb.a"
+installed:  "out/target/product/fictional/system/lib/libb.so"
diff --git a/tools/compliance/cmd/testdata/notice/lib/libc.a.meta_lic b/tools/compliance/cmd/testdata/notice/lib/libc.a.meta_lic
new file mode 100644
index 0000000..eb0f81f
--- /dev/null
+++ b/tools/compliance/cmd/testdata/notice/lib/libc.a.meta_lic
@@ -0,0 +1,6 @@
+package_name:  "External"
+projects:  "static/library"
+license_kinds:  "SPDX-license-identifier-MIT"
+license_conditions:  "notice"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libc.a"
diff --git a/tools/compliance/cmd/testdata/notice/lib/libd.so.meta_lic b/tools/compliance/cmd/testdata/notice/lib/libd.so.meta_lic
new file mode 100644
index 0000000..942d298
--- /dev/null
+++ b/tools/compliance/cmd/testdata/notice/lib/libd.so.meta_lic
@@ -0,0 +1,7 @@
+package_name:  "External"
+projects:  "dynamic/library"
+license_kinds:  "SPDX-license-identifier-MIT"
+license_conditions:  "notice"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libd.so"
+installed:  "out/target/product/fictional/system/lib/libd.so"
diff --git a/tools/compliance/cmd/testdata/proprietary/application.meta_lic b/tools/compliance/cmd/testdata/proprietary/application.meta_lic
new file mode 100644
index 0000000..51b97c5
--- /dev/null
+++ b/tools/compliance/cmd/testdata/proprietary/application.meta_lic
@@ -0,0 +1,24 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "distributable/application"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/application_intermediates/application"
+installed:  "out/target/product/fictional/bin/application"
+sources:  "out/target/product/fictional/system/lib/liba.a"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/bin/bin3"
+deps:  {
+  file:  "testdata/proprietary/bin/bin3.meta_lic"
+  annotations:  "toolchain"
+}
+deps:  {
+  file:  "testdata/proprietary/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/proprietary/lib/libb.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/proprietary/bin/bin1.meta_lic b/tools/compliance/cmd/testdata/proprietary/bin/bin1.meta_lic
new file mode 100644
index 0000000..c815858
--- /dev/null
+++ b/tools/compliance/cmd/testdata/proprietary/bin/bin1.meta_lic
@@ -0,0 +1,19 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "static/binary"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin1"
+installed:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/lib/liba.a"
+sources:  "out/target/product/fictional/system/lib/libc.a"
+deps:  {
+  file:  "testdata/proprietary/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/proprietary/lib/libc.a.meta_lic"
+  annotations:  "static"
+}
diff --git a/tools/compliance/cmd/testdata/proprietary/bin/bin2.meta_lic b/tools/compliance/cmd/testdata/proprietary/bin/bin2.meta_lic
new file mode 100644
index 0000000..6b89ba4
--- /dev/null
+++ b/tools/compliance/cmd/testdata/proprietary/bin/bin2.meta_lic
@@ -0,0 +1,19 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "dynamic/binary"
+license_kinds:  "legacy_proprietary"
+license_conditions:  "proprietary"
+license_conditions:  "by_exception_only"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin1"
+installed:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/lib/libd.so"
+deps:  {
+  file:  "testdata/proprietary/lib/libb.so.meta_lic"
+  annotations:  "dynamic"
+}
+deps:  {
+  file:  "testdata/proprietary/lib/libd.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/proprietary/bin/bin3.meta_lic b/tools/compliance/cmd/testdata/proprietary/bin/bin3.meta_lic
new file mode 100644
index 0000000..f93553d
--- /dev/null
+++ b/tools/compliance/cmd/testdata/proprietary/bin/bin3.meta_lic
@@ -0,0 +1,8 @@
+package_name:  "Compiler"
+module_classes: "EXECUTABLES"
+projects:  "standalone/binary"
+license_kinds:  "SPDX-license-identifier-LGPL-2.0"
+license_conditions:  "restricted"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin3"
+installed:  "out/target/product/fictional/system/bin/bin3"
diff --git a/tools/compliance/cmd/testdata/proprietary/container.zip.meta_lic b/tools/compliance/cmd/testdata/proprietary/container.zip.meta_lic
new file mode 100644
index 0000000..889e17e
--- /dev/null
+++ b/tools/compliance/cmd/testdata/proprietary/container.zip.meta_lic
@@ -0,0 +1,36 @@
+package_name:  "Android"
+projects:  "container/zip"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  true
+built:  "out/target/product/fictional/obj/ETC/container_intermediates/container.zip"
+installed:  "out/target/product/fictional/data/container.zip"
+install_map {
+  from_path:  "out/target/product/fictional/system/lib/"
+  container_path:  ""
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/bin/"
+  container_path:  ""
+}
+sources:  "out/target/product/fictional/system/lib/liba.so"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/bin/bin2"
+deps:  {
+  file:  "testdata/proprietary/bin/bin1.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/proprietary/bin/bin2.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/proprietary/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/proprietary/lib/libb.so.meta_lic"
+  annotations:  "static"
+}
diff --git a/tools/compliance/cmd/testdata/proprietary/highest.apex.meta_lic b/tools/compliance/cmd/testdata/proprietary/highest.apex.meta_lic
new file mode 100644
index 0000000..d615404
--- /dev/null
+++ b/tools/compliance/cmd/testdata/proprietary/highest.apex.meta_lic
@@ -0,0 +1,44 @@
+package_name:  "Android"
+projects:  "highest/apex"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  true
+built:  "out/target/product/fictional/obj/ETC/highest_intermediates/highest.apex"
+installed:  "out/target/product/fictional/system/apex/highest.apex"
+install_map {
+  from_path:  "out/target/product/fictional/system/lib/liba.so"
+  container_path:  "lib/liba.so"
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/lib/libb.so"
+  container_path:  "lib/libb.so"
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/bin/bin1"
+  container_path:  "bin/bin1"
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/bin/bin2"
+  container_path:  "bin/bin2"
+}
+sources:  "out/target/product/fictional/system/lib/liba.so"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/bin/bin2"
+deps:  {
+  file:  "testdata/proprietary/bin/bin1.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/proprietary/bin/bin2.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/proprietary/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/proprietary/lib/libb.so.meta_lic"
+  annotations:  "static"
+}
diff --git a/tools/compliance/cmd/testdata/proprietary/lib/liba.so.meta_lic b/tools/compliance/cmd/testdata/proprietary/lib/liba.so.meta_lic
new file mode 100644
index 0000000..51141c8
--- /dev/null
+++ b/tools/compliance/cmd/testdata/proprietary/lib/liba.so.meta_lic
@@ -0,0 +1,9 @@
+package_name:  "Device"
+projects:  "device/library"
+license_kinds:  "legacy_proprietary"
+license_conditions:  "proprietary"
+license_conditions:  "by_exception_only"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/liba.so"
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/liba.a"
+installed:  "out/target/product/fictional/system/lib/liba.so"
diff --git a/tools/compliance/cmd/testdata/proprietary/lib/libb.so.meta_lic b/tools/compliance/cmd/testdata/proprietary/lib/libb.so.meta_lic
new file mode 100644
index 0000000..c1b86d7
--- /dev/null
+++ b/tools/compliance/cmd/testdata/proprietary/lib/libb.so.meta_lic
@@ -0,0 +1,8 @@
+package_name:  "Android"
+projects:  "base/library"
+license_kinds:  "SPDX-license-identifier-GPL-2.0"
+license_conditions:  "restricted"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libb.so"
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libb.a"
+installed:  "out/target/product/fictional/system/lib/libb.so"
diff --git a/tools/compliance/cmd/testdata/proprietary/lib/libc.a.meta_lic b/tools/compliance/cmd/testdata/proprietary/lib/libc.a.meta_lic
new file mode 100644
index 0000000..1ade7da
--- /dev/null
+++ b/tools/compliance/cmd/testdata/proprietary/lib/libc.a.meta_lic
@@ -0,0 +1,7 @@
+package_name:  "External"
+projects:  "static/library"
+license_kinds:  "legacy_proprietary"
+license_conditions:  "proprietary"
+license_conditions:  "by_exception_only"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libc.a"
diff --git a/tools/compliance/cmd/testdata/proprietary/lib/libd.so.meta_lic b/tools/compliance/cmd/testdata/proprietary/lib/libd.so.meta_lic
new file mode 100644
index 0000000..942d298
--- /dev/null
+++ b/tools/compliance/cmd/testdata/proprietary/lib/libd.so.meta_lic
@@ -0,0 +1,7 @@
+package_name:  "External"
+projects:  "dynamic/library"
+license_kinds:  "SPDX-license-identifier-MIT"
+license_conditions:  "notice"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libd.so"
+installed:  "out/target/product/fictional/system/lib/libd.so"
diff --git a/tools/compliance/cmd/testdata/reciprocal/application.meta_lic b/tools/compliance/cmd/testdata/reciprocal/application.meta_lic
new file mode 100644
index 0000000..015c2d9
--- /dev/null
+++ b/tools/compliance/cmd/testdata/reciprocal/application.meta_lic
@@ -0,0 +1,24 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "distributable/application"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/application_intermediates/application"
+installed:  "out/target/product/fictional/bin/application"
+sources:  "out/target/product/fictional/system/lib/liba.a"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/bin/bin3"
+deps:  {
+  file:  "testdata/reciprocal/bin/bin3.meta_lic"
+  annotations:  "toolchain"
+}
+deps:  {
+  file:  "testdata/reciprocal/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/reciprocal/lib/libb.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/reciprocal/bin/bin1.meta_lic b/tools/compliance/cmd/testdata/reciprocal/bin/bin1.meta_lic
new file mode 100644
index 0000000..4ebf653
--- /dev/null
+++ b/tools/compliance/cmd/testdata/reciprocal/bin/bin1.meta_lic
@@ -0,0 +1,19 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "static/binary"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin1"
+installed:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/lib/liba.a"
+sources:  "out/target/product/fictional/system/lib/libc.a"
+deps:  {
+  file:  "testdata/reciprocal/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/reciprocal/lib/libc.a.meta_lic"
+  annotations:  "static"
+}
diff --git a/tools/compliance/cmd/testdata/reciprocal/bin/bin2.meta_lic b/tools/compliance/cmd/testdata/reciprocal/bin/bin2.meta_lic
new file mode 100644
index 0000000..4d28608
--- /dev/null
+++ b/tools/compliance/cmd/testdata/reciprocal/bin/bin2.meta_lic
@@ -0,0 +1,19 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "dynamic/binary"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin1"
+installed:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/lib/libd.so"
+deps:  {
+  file:  "testdata/reciprocal/lib/libb.so.meta_lic"
+  annotations:  "dynamic"
+}
+deps:  {
+  file:  "testdata/reciprocal/lib/libd.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/reciprocal/bin/bin3.meta_lic b/tools/compliance/cmd/testdata/reciprocal/bin/bin3.meta_lic
new file mode 100644
index 0000000..285d899
--- /dev/null
+++ b/tools/compliance/cmd/testdata/reciprocal/bin/bin3.meta_lic
@@ -0,0 +1,8 @@
+package_name:  "Compiler"
+module_classes: "EXECUTABLES"
+projects:  "standalone/binary"
+license_kinds:  "SPDX-license-identifier-NCSA"
+license_conditions:  "notice"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin3"
+installed:  "out/target/product/fictional/system/bin/bin3"
diff --git a/tools/compliance/cmd/testdata/reciprocal/container.zip.meta_lic b/tools/compliance/cmd/testdata/reciprocal/container.zip.meta_lic
new file mode 100644
index 0000000..ea3598f
--- /dev/null
+++ b/tools/compliance/cmd/testdata/reciprocal/container.zip.meta_lic
@@ -0,0 +1,36 @@
+package_name:  "Android"
+projects:  "container/zip"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  true
+built:  "out/target/product/fictional/obj/ETC/container_intermediates/container.zip"
+installed:  "out/target/product/fictional/data/container.zip"
+install_map {
+  from_path:  "out/target/product/fictional/system/lib/"
+  container_path:  ""
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/bin/"
+  container_path:  ""
+}
+sources:  "out/target/product/fictional/system/lib/liba.so"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/bin/bin2"
+deps:  {
+  file:  "testdata/reciprocal/bin/bin1.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/reciprocal/bin/bin2.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/reciprocal/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/reciprocal/lib/libb.so.meta_lic"
+  annotations:  "static"
+}
diff --git a/tools/compliance/cmd/testdata/reciprocal/highest.apex.meta_lic b/tools/compliance/cmd/testdata/reciprocal/highest.apex.meta_lic
new file mode 100644
index 0000000..1fec741
--- /dev/null
+++ b/tools/compliance/cmd/testdata/reciprocal/highest.apex.meta_lic
@@ -0,0 +1,44 @@
+package_name:  "Android"
+projects:  "highest/apex"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  true
+built:  "out/target/product/fictional/obj/ETC/highest_intermediates/highest.apex"
+installed:  "out/target/product/fictional/system/apex/highest.apex"
+install_map {
+  from_path:  "out/target/product/fictional/system/lib/liba.so"
+  container_path:  "lib/liba.so"
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/lib/libb.so"
+  container_path:  "lib/libb.so"
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/bin/bin1"
+  container_path:  "bin/bin1"
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/bin/bin2"
+  container_path:  "bin/bin2"
+}
+sources:  "out/target/product/fictional/system/lib/liba.so"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/bin/bin2"
+deps:  {
+  file:  "testdata/reciprocal/bin/bin1.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/reciprocal/bin/bin2.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/reciprocal/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/reciprocal/lib/libb.so.meta_lic"
+  annotations:  "static"
+}
diff --git a/tools/compliance/cmd/testdata/reciprocal/lib/liba.so.meta_lic b/tools/compliance/cmd/testdata/reciprocal/lib/liba.so.meta_lic
new file mode 100644
index 0000000..79d7a9e
--- /dev/null
+++ b/tools/compliance/cmd/testdata/reciprocal/lib/liba.so.meta_lic
@@ -0,0 +1,8 @@
+package_name:  "Device"
+projects:  "device/library"
+license_kinds:  "SPDX-license-identifier-MPL"
+license_conditions:  "reciprocal"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/liba.so"
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/liba.a"
+installed:  "out/target/product/fictional/system/lib/liba.so"
diff --git a/tools/compliance/cmd/testdata/reciprocal/lib/libb.so.meta_lic b/tools/compliance/cmd/testdata/reciprocal/lib/libb.so.meta_lic
new file mode 100644
index 0000000..a4935d4
--- /dev/null
+++ b/tools/compliance/cmd/testdata/reciprocal/lib/libb.so.meta_lic
@@ -0,0 +1,9 @@
+package_name:  "Android"
+projects:  "base/library"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libb.so"
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libb.a"
+installed:  "out/target/product/fictional/system/lib/libb.so"
diff --git a/tools/compliance/cmd/testdata/reciprocal/lib/libc.a.meta_lic b/tools/compliance/cmd/testdata/reciprocal/lib/libc.a.meta_lic
new file mode 100644
index 0000000..8f6d356
--- /dev/null
+++ b/tools/compliance/cmd/testdata/reciprocal/lib/libc.a.meta_lic
@@ -0,0 +1,6 @@
+package_name:  "External"
+projects:  "static/library"
+license_kinds:  "SPDX-license-identifier-MPL"
+license_conditions:  "reciprocal"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libc.a"
diff --git a/tools/compliance/cmd/testdata/reciprocal/lib/libd.so.meta_lic b/tools/compliance/cmd/testdata/reciprocal/lib/libd.so.meta_lic
new file mode 100644
index 0000000..942d298
--- /dev/null
+++ b/tools/compliance/cmd/testdata/reciprocal/lib/libd.so.meta_lic
@@ -0,0 +1,7 @@
+package_name:  "External"
+projects:  "dynamic/library"
+license_kinds:  "SPDX-license-identifier-MIT"
+license_conditions:  "notice"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libd.so"
+installed:  "out/target/product/fictional/system/lib/libd.so"
diff --git a/tools/compliance/cmd/testdata/restricted/application.meta_lic b/tools/compliance/cmd/testdata/restricted/application.meta_lic
new file mode 100644
index 0000000..a06a2c8
--- /dev/null
+++ b/tools/compliance/cmd/testdata/restricted/application.meta_lic
@@ -0,0 +1,24 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "distributable/application"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/application_intermediates/application"
+installed:  "out/target/product/fictional/bin/application"
+sources:  "out/target/product/fictional/system/lib/liba.a"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/bin/bin3"
+deps:  {
+  file:  "testdata/restricted/bin/bin3.meta_lic"
+  annotations:  "toolchain"
+}
+deps:  {
+  file:  "testdata/restricted/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/restricted/lib/libb.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/restricted/bin/bin1.meta_lic b/tools/compliance/cmd/testdata/restricted/bin/bin1.meta_lic
new file mode 100644
index 0000000..dd8a2e0
--- /dev/null
+++ b/tools/compliance/cmd/testdata/restricted/bin/bin1.meta_lic
@@ -0,0 +1,19 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "static/binary"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin1"
+installed:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/lib/liba.a"
+sources:  "out/target/product/fictional/system/lib/libc.a"
+deps:  {
+  file:  "testdata/restricted/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/restricted/lib/libc.a.meta_lic"
+  annotations:  "static"
+}
diff --git a/tools/compliance/cmd/testdata/restricted/bin/bin2.meta_lic b/tools/compliance/cmd/testdata/restricted/bin/bin2.meta_lic
new file mode 100644
index 0000000..714b537
--- /dev/null
+++ b/tools/compliance/cmd/testdata/restricted/bin/bin2.meta_lic
@@ -0,0 +1,19 @@
+package_name:  "Android"
+module_classes: "EXECUTABLES"
+projects:  "dynamic/binary"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin1"
+installed:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/lib/libd.so"
+deps:  {
+  file:  "testdata/restricted/lib/libb.so.meta_lic"
+  annotations:  "dynamic"
+}
+deps:  {
+  file:  "testdata/restricted/lib/libd.so.meta_lic"
+  annotations:  "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/restricted/bin/bin3.meta_lic b/tools/compliance/cmd/testdata/restricted/bin/bin3.meta_lic
new file mode 100644
index 0000000..f93553d
--- /dev/null
+++ b/tools/compliance/cmd/testdata/restricted/bin/bin3.meta_lic
@@ -0,0 +1,8 @@
+package_name:  "Compiler"
+module_classes: "EXECUTABLES"
+projects:  "standalone/binary"
+license_kinds:  "SPDX-license-identifier-LGPL-2.0"
+license_conditions:  "restricted"
+is_container:  false
+built:  "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin3"
+installed:  "out/target/product/fictional/system/bin/bin3"
diff --git a/tools/compliance/cmd/testdata/restricted/container.zip.meta_lic b/tools/compliance/cmd/testdata/restricted/container.zip.meta_lic
new file mode 100644
index 0000000..a63263b
--- /dev/null
+++ b/tools/compliance/cmd/testdata/restricted/container.zip.meta_lic
@@ -0,0 +1,36 @@
+package_name:  "Android"
+projects:  "container/zip"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  true
+built:  "out/target/product/fictional/obj/ETC/container_intermediates/container.zip"
+installed:  "out/target/product/fictional/data/container.zip"
+install_map {
+  from_path:  "out/target/product/fictional/system/lib/"
+  container_path:  ""
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/bin/"
+  container_path:  ""
+}
+sources:  "out/target/product/fictional/system/lib/liba.so"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/bin/bin2"
+deps:  {
+  file:  "testdata/restricted/bin/bin1.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/restricted/bin/bin2.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/restricted/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/restricted/lib/libb.so.meta_lic"
+  annotations:  "static"
+}
diff --git a/tools/compliance/cmd/testdata/restricted/highest.apex.meta_lic b/tools/compliance/cmd/testdata/restricted/highest.apex.meta_lic
new file mode 100644
index 0000000..dba419a
--- /dev/null
+++ b/tools/compliance/cmd/testdata/restricted/highest.apex.meta_lic
@@ -0,0 +1,44 @@
+package_name:  "Android"
+projects:  "highest/apex"
+license_kinds:  "SPDX-license-identifier-Apache-2.0"
+license_conditions:  "notice"
+license_texts:  "build/soong/licenses/LICENSE"
+is_container:  true
+built:  "out/target/product/fictional/obj/ETC/highest_intermediates/highest.apex"
+installed:  "out/target/product/fictional/system/apex/highest.apex"
+install_map {
+  from_path:  "out/target/product/fictional/system/lib/liba.so"
+  container_path:  "lib/liba.so"
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/lib/libb.so"
+  container_path:  "lib/libb.so"
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/bin/bin1"
+  container_path:  "bin/bin1"
+}
+install_map {
+  from_path:  "out/target/product/fictional/system/bin/bin2"
+  container_path:  "bin/bin2"
+}
+sources:  "out/target/product/fictional/system/lib/liba.so"
+sources:  "out/target/product/fictional/system/lib/libb.so"
+sources:  "out/target/product/fictional/system/bin/bin1"
+sources:  "out/target/product/fictional/system/bin/bin2"
+deps:  {
+  file:  "testdata/restricted/bin/bin1.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/restricted/bin/bin2.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/restricted/lib/liba.so.meta_lic"
+  annotations:  "static"
+}
+deps:  {
+  file:  "testdata/restricted/lib/libb.so.meta_lic"
+  annotations:  "static"
+}
diff --git a/tools/compliance/cmd/testdata/restricted/lib/liba.so.meta_lic b/tools/compliance/cmd/testdata/restricted/lib/liba.so.meta_lic
new file mode 100644
index 0000000..b1d4560
--- /dev/null
+++ b/tools/compliance/cmd/testdata/restricted/lib/liba.so.meta_lic
@@ -0,0 +1,8 @@
+package_name:  "Device"
+projects:  "device/library"
+license_kinds:  "SPDX-license-identifier-LGPL-2.0"
+license_conditions:  "restricted"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/liba.so"
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/liba.a"
+installed:  "out/target/product/fictional/system/lib/liba.so"
diff --git a/tools/compliance/cmd/testdata/restricted/lib/libb.so.meta_lic b/tools/compliance/cmd/testdata/restricted/lib/libb.so.meta_lic
new file mode 100644
index 0000000..c1b86d7
--- /dev/null
+++ b/tools/compliance/cmd/testdata/restricted/lib/libb.so.meta_lic
@@ -0,0 +1,8 @@
+package_name:  "Android"
+projects:  "base/library"
+license_kinds:  "SPDX-license-identifier-GPL-2.0"
+license_conditions:  "restricted"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libb.so"
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libb.a"
+installed:  "out/target/product/fictional/system/lib/libb.so"
diff --git a/tools/compliance/cmd/testdata/restricted/lib/libc.a.meta_lic b/tools/compliance/cmd/testdata/restricted/lib/libc.a.meta_lic
new file mode 100644
index 0000000..8f6d356
--- /dev/null
+++ b/tools/compliance/cmd/testdata/restricted/lib/libc.a.meta_lic
@@ -0,0 +1,6 @@
+package_name:  "External"
+projects:  "static/library"
+license_kinds:  "SPDX-license-identifier-MPL"
+license_conditions:  "reciprocal"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libc.a"
diff --git a/tools/compliance/cmd/testdata/restricted/lib/libd.so.meta_lic b/tools/compliance/cmd/testdata/restricted/lib/libd.so.meta_lic
new file mode 100644
index 0000000..942d298
--- /dev/null
+++ b/tools/compliance/cmd/testdata/restricted/lib/libd.so.meta_lic
@@ -0,0 +1,7 @@
+package_name:  "External"
+projects:  "dynamic/library"
+license_kinds:  "SPDX-license-identifier-MIT"
+license_conditions:  "notice"
+is_container:  false
+built:  "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libd.so"
+installed:  "out/target/product/fictional/system/lib/libd.so"
diff --git a/tools/compliance/condition.go b/tools/compliance/condition.go
new file mode 100644
index 0000000..b5c8cec
--- /dev/null
+++ b/tools/compliance/condition.go
@@ -0,0 +1,165 @@
+// Copyright 2021 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 compliance
+
+import (
+	"fmt"
+	"strings"
+)
+
+// LicenseCondition describes an individual license condition or requirement
+// originating at a specific target node. (immutable)
+//
+// e.g. A module licensed under GPL terms would originate a `restricted` condition.
+type LicenseCondition struct {
+	name   string
+	origin *TargetNode
+}
+
+// Name returns the name of the condition. e.g. "restricted" or "notice"
+func (lc LicenseCondition) Name() string {
+	return lc.name
+}
+
+// Origin identifies the TargetNode where the condition originates.
+func (lc LicenseCondition) Origin() *TargetNode {
+	return lc.origin
+}
+
+// asString returns a string representation of a license condition:
+// origin+separator+condition.
+func (lc LicenseCondition) asString(separator string) string {
+	return lc.origin.name + separator + lc.name
+}
+
+// ConditionList implements introspection methods to arrays of LicenseCondition.
+type ConditionList []LicenseCondition
+
+
+// ConditionList orders arrays of LicenseCondition by Origin and Name.
+
+// Len returns the length of the list.
+func (l ConditionList) Len() int      { return len(l) }
+
+// Swap rearranges 2 elements in the list so each occupies the other's former position.
+func (l ConditionList) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
+
+// Less returns true when the `i`th element is lexicographically less than tht `j`th element.
+func (l ConditionList) Less(i, j int) bool {
+	if l[i].origin.name == l[j].origin.name {
+		return l[i].name < l[j].name
+	}
+	return l[i].origin.name < l[j].origin.name
+}
+
+// String returns a string representation of the set.
+func (cl ConditionList) String() string {
+	var sb strings.Builder
+	fmt.Fprintf(&sb, "[")
+	sep := ""
+	for _, lc := range cl {
+		fmt.Fprintf(&sb, "%s%s:%s", sep, lc.origin.name, lc.name)
+		sep = ", "
+	}
+	fmt.Fprintf(&sb, "]")
+	return sb.String()
+}
+
+// Names returns the list of the conditions' names.
+func (cl ConditionList) Names() []string {
+	result := make([]string, 0, len(cl))
+	for _, lc := range cl {
+		result = append(result, lc.name)
+	}
+	return result
+}
+
+// HasByName returns true if the list contains any condition matching `name`.
+func (cl ConditionList) HasByName(name ConditionNames) bool {
+	for _, lc := range cl {
+		if name.Contains(lc.name) {
+			return true
+		}
+	}
+	return false
+}
+
+// ByName returns the sublist of conditions that match `name`.
+func (cl ConditionList) ByName(name ConditionNames) ConditionList {
+	result := make(ConditionList, 0, cl.CountByName(name))
+	for _, lc := range cl {
+		if name.Contains(lc.name) {
+			result = append(result, lc)
+		}
+	}
+	return result
+}
+
+// CountByName returns the size of the sublist of conditions that match `name`.
+func (cl ConditionList) CountByName(name ConditionNames) int {
+	size := 0
+	for _, lc := range cl {
+		if name.Contains(lc.name) {
+			size++
+		}
+	}
+	return size
+}
+
+// HasByOrigin returns true if the list contains any condition originating at `origin`.
+func (cl ConditionList) HasByOrigin(origin *TargetNode) bool {
+	for _, lc := range cl {
+		if lc.origin.name == origin.name {
+			return true
+		}
+	}
+	return false
+}
+
+// ByOrigin returns the sublist of conditions that originate at `origin`.
+func (cl ConditionList) ByOrigin(origin *TargetNode) ConditionList {
+	result := make(ConditionList, 0, cl.CountByOrigin(origin))
+	for _, lc := range cl {
+		if lc.origin.name == origin.name {
+			result = append(result, lc)
+		}
+	}
+	return result
+}
+
+// CountByOrigin returns the size of the sublist of conditions that originate at `origin`.
+func (cl ConditionList) CountByOrigin(origin *TargetNode) int {
+	size := 0
+	for _, lc := range cl {
+		if lc.origin.name == origin.name {
+			size++
+		}
+	}
+	return size
+}
+
+// ConditionNames implements the Contains predicate for slices of condition
+// name strings.
+type ConditionNames []string
+
+// Contains returns true if the name matches one of the ConditionNames.
+func (cn ConditionNames) Contains(name string) bool {
+	for _, cname := range cn {
+		if cname == name {
+			return true
+		}
+	}
+	return false
+}
diff --git a/tools/compliance/condition_test.go b/tools/compliance/condition_test.go
new file mode 100644
index 0000000..0507469
--- /dev/null
+++ b/tools/compliance/condition_test.go
@@ -0,0 +1,218 @@
+// Copyright 2021 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 compliance
+
+import (
+	"sort"
+	"strings"
+	"testing"
+)
+
+func TestConditionNames(t *testing.T) {
+	impliesShare := ConditionNames([]string{"restricted", "reciprocal"})
+
+	if impliesShare.Contains("notice") {
+		t.Errorf("impliesShare.Contains(\"notice\") got true, want false")
+	}
+
+	if !impliesShare.Contains("restricted") {
+		t.Errorf("impliesShare.Contains(\"restricted\") got false, want true")
+	}
+
+	if !impliesShare.Contains("reciprocal") {
+		t.Errorf("impliesShare.Contains(\"reciprocal\") got false, want true")
+	}
+
+	if impliesShare.Contains("") {
+		t.Errorf("impliesShare.Contains(\"\") got true, want false")
+	}
+}
+
+func TestConditionList(t *testing.T) {
+	tests := []struct {
+		name       string
+		conditions map[string][]string
+		byName     map[string][]string
+		byOrigin   map[string][]string
+	}{
+		{
+			name: "noticeonly",
+			conditions: map[string][]string{
+				"notice": []string{"bin1", "lib1"},
+			},
+			byName: map[string][]string{
+				"notice":     []string{"bin1", "lib1"},
+				"restricted": []string{},
+			},
+			byOrigin: map[string][]string{
+				"bin1": []string{"notice"},
+				"lib1": []string{"notice"},
+				"bin2": []string{},
+				"lib2": []string{},
+			},
+		},
+		{
+			name:       "empty",
+			conditions: map[string][]string{},
+			byName: map[string][]string{
+				"notice":     []string{},
+				"restricted": []string{},
+			},
+			byOrigin: map[string][]string{
+				"bin1": []string{},
+				"lib1": []string{},
+				"bin2": []string{},
+				"lib2": []string{},
+			},
+		},
+		{
+			name: "everything",
+			conditions: map[string][]string{
+				"notice":            []string{"bin1", "bin2", "lib1", "lib2"},
+				"reciprocal":        []string{"bin1", "bin2", "lib1", "lib2"},
+				"restricted":        []string{"bin1", "bin2", "lib1", "lib2"},
+				"by_exception_only": []string{"bin1", "bin2", "lib1", "lib2"},
+			},
+			byName: map[string][]string{
+				"permissive":        []string{},
+				"notice":            []string{"bin1", "bin2", "lib1", "lib2"},
+				"reciprocal":        []string{"bin1", "bin2", "lib1", "lib2"},
+				"restricted":        []string{"bin1", "bin2", "lib1", "lib2"},
+				"by_exception_only": []string{"bin1", "bin2", "lib1", "lib2"},
+			},
+			byOrigin: map[string][]string{
+				"bin1":  []string{"notice", "reciprocal", "restricted", "by_exception_only"},
+				"bin2":  []string{"notice", "reciprocal", "restricted", "by_exception_only"},
+				"lib1":  []string{"notice", "reciprocal", "restricted", "by_exception_only"},
+				"lib2":  []string{"notice", "reciprocal", "restricted", "by_exception_only"},
+				"other": []string{},
+			},
+		},
+		{
+			name: "allbutoneeach",
+			conditions: map[string][]string{
+				"notice":            []string{"bin2", "lib1", "lib2"},
+				"reciprocal":        []string{"bin1", "lib1", "lib2"},
+				"restricted":        []string{"bin1", "bin2", "lib2"},
+				"by_exception_only": []string{"bin1", "bin2", "lib1"},
+			},
+			byName: map[string][]string{
+				"permissive":        []string{},
+				"notice":            []string{"bin2", "lib1", "lib2"},
+				"reciprocal":        []string{"bin1", "lib1", "lib2"},
+				"restricted":        []string{"bin1", "bin2", "lib2"},
+				"by_exception_only": []string{"bin1", "bin2", "lib1"},
+			},
+			byOrigin: map[string][]string{
+				"bin1":  []string{"reciprocal", "restricted", "by_exception_only"},
+				"bin2":  []string{"notice", "restricted", "by_exception_only"},
+				"lib1":  []string{"notice", "reciprocal", "by_exception_only"},
+				"lib2":  []string{"notice", "reciprocal", "restricted"},
+				"other": []string{},
+			},
+		},
+		{
+			name: "oneeach",
+			conditions: map[string][]string{
+				"notice":            []string{"bin1"},
+				"reciprocal":        []string{"bin2"},
+				"restricted":        []string{"lib1"},
+				"by_exception_only": []string{"lib2"},
+			},
+			byName: map[string][]string{
+				"permissive":        []string{},
+				"notice":            []string{"bin1"},
+				"reciprocal":        []string{"bin2"},
+				"restricted":        []string{"lib1"},
+				"by_exception_only": []string{"lib2"},
+			},
+			byOrigin: map[string][]string{
+				"bin1":  []string{"notice"},
+				"bin2":  []string{"reciprocal"},
+				"lib1":  []string{"restricted"},
+				"lib2":  []string{"by_exception_only"},
+				"other": []string{},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			lg := newLicenseGraph()
+			cl := toConditionList(lg, tt.conditions)
+			for names, expected := range tt.byName {
+				name := ConditionNames(strings.Split(names, ":"))
+				if cl.HasByName(name) {
+					if len(expected) == 0 {
+						t.Errorf("unexpected ConditionList.HasByName(%q): got true, want false", name)
+					}
+				} else {
+					if len(expected) != 0 {
+						t.Errorf("unexpected ConditionList.HasByName(%q): got false, want true", name)
+					}
+				}
+				if len(expected) != cl.CountByName(name) {
+					t.Errorf("unexpected ConditionList.CountByName(%q): got %d, want %d", name, cl.CountByName(name), len(expected))
+				}
+				byName := cl.ByName(name)
+				if len(expected) != len(byName) {
+					t.Errorf("unexpected ConditionList.ByName(%q): got %v, want %v", name, byName, expected)
+				} else {
+					sort.Strings(expected)
+					actual := make([]string, 0, len(byName))
+					for _, lc := range byName {
+						actual = append(actual, lc.Origin().Name())
+					}
+					sort.Strings(actual)
+					for i := 0; i < len(expected); i++ {
+						if expected[i] != actual[i] {
+							t.Errorf("unexpected ConditionList.ByName(%q) index %d in %v: got %s, want %s", name, i, actual, actual[i], expected[i])
+						}
+					}
+				}
+			}
+			for origin, expected := range tt.byOrigin {
+				onode := newTestNode(lg, origin)
+				if cl.HasByOrigin(onode) {
+					if len(expected) == 0 {
+						t.Errorf("unexpected ConditionList.HasByOrigin(%q): got true, want false", origin)
+					}
+				} else {
+					if len(expected) != 0 {
+						t.Errorf("unexpected ConditionList.HasByOrigin(%q): got false, want true", origin)
+					}
+				}
+				if len(expected) != cl.CountByOrigin(onode) {
+					t.Errorf("unexpected ConditionList.CountByOrigin(%q): got %d, want %d", origin, cl.CountByOrigin(onode), len(expected))
+				}
+				byOrigin := cl.ByOrigin(onode)
+				if len(expected) != len(byOrigin) {
+					t.Errorf("unexpected ConditionList.ByOrigin(%q): got %v, want %v", origin, byOrigin, expected)
+				} else {
+					sort.Strings(expected)
+					actual := make([]string, 0, len(byOrigin))
+					for _, lc := range byOrigin {
+						actual = append(actual, lc.Name())
+					}
+					sort.Strings(actual)
+					for i := 0; i < len(expected); i++ {
+						if expected[i] != actual[i] {
+							t.Errorf("unexpected ConditionList.ByOrigin(%q) index %d in %v: got %s, want %s", origin, i, actual, actual[i], expected[i])
+						}
+					}
+				}
+			}
+		})
+	}
+}
diff --git a/tools/compliance/conditionset.go b/tools/compliance/conditionset.go
new file mode 100644
index 0000000..1ad15ca
--- /dev/null
+++ b/tools/compliance/conditionset.go
@@ -0,0 +1,278 @@
+// Copyright 2021 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 compliance
+
+import (
+	"fmt"
+)
+
+// NewLicenseConditionSet creates a new instance or variable of *LicenseConditionSet.
+func NewLicenseConditionSet(conditions ...LicenseCondition) *LicenseConditionSet {
+	cs := newLicenseConditionSet()
+	cs.Add(conditions...)
+	return cs
+}
+
+// LicenseConditionSet describes a mutable set of immutable license conditions.
+type LicenseConditionSet struct {
+	// conditions describes the set of license conditions i.e. (condition name, origin target) pairs
+	// by mapping condition name -> origin target -> true.
+	conditions map[string]map[*TargetNode]bool
+}
+
+// Add makes all `conditions` members of the set if they were not previously.
+func (cs *LicenseConditionSet) Add(conditions ...LicenseCondition) {
+	if len(conditions) == 0 {
+		return
+	}
+	for _, lc := range conditions {
+		if _, ok := cs.conditions[lc.name]; !ok {
+			cs.conditions[lc.name] = make(map[*TargetNode]bool)
+		}
+		cs.conditions[lc.name][lc.origin] = true
+	}
+}
+
+// AddSet makes all elements of `conditions` members of the set if they were not previously.
+func (cs *LicenseConditionSet) AddSet(other *LicenseConditionSet) {
+	if len(other.conditions) == 0 {
+		return
+	}
+	for name, origins := range other.conditions {
+		if len(origins) == 0 {
+			continue
+		}
+		if _, ok := cs.conditions[name]; !ok {
+			cs.conditions[name] = make(map[*TargetNode]bool)
+		}
+		for origin := range origins {
+			cs.conditions[name][origin] = other.conditions[name][origin]
+		}
+	}
+}
+
+// ByName returns a list of the conditions in the set matching `names`.
+func (cs *LicenseConditionSet) ByName(names ...ConditionNames) *LicenseConditionSet {
+	other := newLicenseConditionSet()
+	for _, cn := range names {
+		for _, name := range cn {
+			if origins, ok := cs.conditions[name]; ok {
+				other.conditions[name] = make(map[*TargetNode]bool)
+				for origin := range origins {
+					other.conditions[name][origin] = true
+				}
+			}
+		}
+	}
+	return other
+}
+
+// HasAnyByName returns true if the set contains any conditions matching `names` originating at any target.
+func (cs *LicenseConditionSet) HasAnyByName(names ...ConditionNames) bool {
+	for _, cn := range names {
+		for _, name := range cn {
+			if origins, ok := cs.conditions[name]; ok {
+				if len(origins) > 0 {
+					return true
+				}
+			}
+		}
+	}
+	return false
+}
+
+// CountByName returns the number of conditions matching `names` originating at any target.
+func (cs *LicenseConditionSet) CountByName(names ...ConditionNames) int {
+	size := 0
+	for _, cn := range names {
+		for _, name := range cn {
+			if origins, ok := cs.conditions[name]; ok {
+				size += len(origins)
+			}
+		}
+	}
+	return size
+}
+
+// ByOrigin returns all of the conditions that originate at `origin` regardless of name.
+func (cs *LicenseConditionSet) ByOrigin(origin *TargetNode) *LicenseConditionSet {
+	other := newLicenseConditionSet()
+	for name, origins := range cs.conditions {
+		if _, ok := origins[origin]; ok {
+			other.conditions[name] = make(map[*TargetNode]bool)
+			other.conditions[name][origin] = true
+		}
+	}
+	return other
+}
+
+// HasAnyByOrigin returns true if the set contains any conditions originating at `origin` regardless of condition name.
+func (cs *LicenseConditionSet) HasAnyByOrigin(origin *TargetNode) bool {
+	for _, origins := range cs.conditions {
+		if _, ok := origins[origin]; ok {
+			return true
+		}
+	}
+	return false
+}
+
+// CountByOrigin returns the number of conditions originating at `origin` regardless of condition name.
+func (cs *LicenseConditionSet) CountByOrigin(origin *TargetNode) int {
+	size := 0
+	for _, origins := range cs.conditions {
+		if _, ok := origins[origin]; ok {
+			size++
+		}
+	}
+	return size
+}
+
+// AsList returns a list of all the conditions in the set.
+func (cs *LicenseConditionSet) AsList() ConditionList {
+	result := make(ConditionList, 0, cs.Count())
+	for name, origins := range cs.conditions {
+		for origin := range origins {
+			result = append(result, LicenseCondition{name, origin})
+		}
+	}
+	return result
+}
+
+// Names returns a list of the names of the conditions in the set.
+func (cs *LicenseConditionSet) Names() []string {
+	result := make([]string, 0, len(cs.conditions))
+	for name := range cs.conditions {
+		result = append(result, name)
+	}
+	return result
+}
+
+// Count returns the number of conditions in the set.
+func (cs *LicenseConditionSet) Count() int {
+	size := 0
+	for _, origins := range cs.conditions {
+		size += len(origins)
+	}
+	return size
+}
+
+// Copy creates a new LicenseCondition variable with the same value.
+func (cs *LicenseConditionSet) Copy() *LicenseConditionSet {
+	other := newLicenseConditionSet()
+	for name := range cs.conditions {
+		other.conditions[name] = make(map[*TargetNode]bool)
+		for origin := range cs.conditions[name] {
+			other.conditions[name][origin] = cs.conditions[name][origin]
+		}
+	}
+	return other
+}
+
+// HasCondition returns true if the set contains any condition matching both `names` and `origin`.
+func (cs *LicenseConditionSet) HasCondition(names ConditionNames, origin *TargetNode) bool {
+	for _, name := range names {
+		if origins, ok := cs.conditions[name]; ok {
+			_, isPresent := origins[origin]
+			if isPresent {
+				return true
+			}
+		}
+	}
+	return false
+}
+
+// IsEmpty returns true when the set of conditions contains zero elements.
+func (cs *LicenseConditionSet) IsEmpty() bool {
+	for _, origins := range cs.conditions {
+		if 0 < len(origins) {
+			return false
+		}
+	}
+	return true
+}
+
+// RemoveAllByName changes the set to delete all conditions matching `names`.
+func (cs *LicenseConditionSet) RemoveAllByName(names ...ConditionNames) {
+	for _, cn := range names {
+		for _, name := range cn {
+			delete(cs.conditions, name)
+		}
+	}
+}
+
+// Remove changes the set to delete `conditions`.
+func (cs *LicenseConditionSet) Remove(conditions ...LicenseCondition) {
+	for _, lc := range conditions {
+		if _, isPresent := cs.conditions[lc.name]; !isPresent {
+			panic(fmt.Errorf("attempt to remove non-existent condition: %q", lc.asString(":")))
+		}
+		if _, isPresent := cs.conditions[lc.name][lc.origin]; !isPresent {
+			panic(fmt.Errorf("attempt to remove non-existent origin: %q", lc.asString(":")))
+		}
+		delete(cs.conditions[lc.name], lc.origin)
+	}
+}
+
+// removeSet changes the set to delete all conditions also present in `other`.
+func (cs *LicenseConditionSet) RemoveSet(other *LicenseConditionSet) {
+	for name, origins := range other.conditions {
+		if _, isPresent := cs.conditions[name]; !isPresent {
+			continue
+		}
+		for origin := range origins {
+			delete(cs.conditions[name], origin)
+		}
+	}
+}
+
+// compliance-only LicenseConditionSet methods
+
+// newLicenseConditionSet constructs a set of `conditions`.
+func newLicenseConditionSet() *LicenseConditionSet {
+	return &LicenseConditionSet{make(map[string]map[*TargetNode]bool)}
+}
+
+// add changes the set to include each element of `conditions` originating at `origin`.
+func (cs *LicenseConditionSet) add(origin *TargetNode, conditions ...string) {
+	for _, name := range conditions {
+		if _, ok := cs.conditions[name]; !ok {
+			cs.conditions[name] = make(map[*TargetNode]bool)
+		}
+		cs.conditions[name][origin] = true
+	}
+}
+
+// asStringList returns the conditions in the set as `separator`-separated (origin, condition-name) pair strings.
+func (cs *LicenseConditionSet) asStringList(separator string) []string {
+	result := make([]string, 0, cs.Count())
+	for name, origins := range cs.conditions {
+		for origin := range origins {
+			result = append(result, origin.name+separator+name)
+		}
+	}
+	return result
+}
+
+// conditionNamesArray implements a `contains` predicate for arrays of ConditionNames
+type conditionNamesArray []ConditionNames
+
+func (cn conditionNamesArray) contains(name string) bool {
+	for _, names := range cn {
+		if names.Contains(name) {
+			return true
+		}
+	}
+	return false
+}
diff --git a/tools/compliance/conditionset_test.go b/tools/compliance/conditionset_test.go
new file mode 100644
index 0000000..eac0680
--- /dev/null
+++ b/tools/compliance/conditionset_test.go
@@ -0,0 +1,590 @@
+// Copyright 2021 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 compliance
+
+import (
+	"sort"
+	"strings"
+	"testing"
+)
+
+type byName map[string][]string
+
+func (bn byName) checkPublic(ls *LicenseConditionSet, t *testing.T) {
+	for names, expected := range bn {
+		name := ConditionNames(strings.Split(names, ":"))
+		if ls.HasAnyByName(name) {
+			if len(expected) == 0 {
+				t.Errorf("unexpected LicenseConditionSet.HasAnyByName(%q): got true, want false", name)
+			}
+		} else {
+			if len(expected) != 0 {
+				t.Errorf("unexpected LicenseConditionSet.HasAnyByName(%q): got false, want true", name)
+			}
+		}
+		if len(expected) != ls.CountByName(name) {
+			t.Errorf("unexpected LicenseConditionSet.CountByName(%q): got %d, want %d", name, ls.CountByName(name), len(expected))
+		}
+		byName := ls.ByName(name).AsList()
+		if len(expected) != len(byName) {
+			t.Errorf("unexpected LicenseConditionSet.ByName(%q): got %v, want %v", name, byName, expected)
+		} else {
+			sort.Strings(expected)
+			actual := make([]string, 0, len(byName))
+			for _, lc := range byName {
+				actual = append(actual, lc.Origin().Name())
+			}
+			sort.Strings(actual)
+			for i := 0; i < len(expected); i++ {
+				if expected[i] != actual[i] {
+					t.Errorf("unexpected LicenseConditionSet.ByName(%q) index %d in %v: got %s, want %s", name, i, actual, actual[i], expected[i])
+				}
+			}
+		}
+	}
+}
+
+type byOrigin map[string][]string
+
+func (bo byOrigin) checkPublic(lg *LicenseGraph, ls *LicenseConditionSet, t *testing.T) {
+	expectedCount := 0
+	for origin, expected := range bo {
+		expectedCount += len(expected)
+		onode := newTestNode(lg, origin)
+		if ls.HasAnyByOrigin(onode) {
+			if len(expected) == 0 {
+				t.Errorf("unexpected LicenseConditionSet.HasAnyByOrigin(%q): got true, want false", origin)
+			}
+		} else {
+			if len(expected) != 0 {
+				t.Errorf("unexpected LicenseConditionSet.HasAnyByOrigin(%q): got false, want true", origin)
+			}
+		}
+		if len(expected) != ls.CountByOrigin(onode) {
+			t.Errorf("unexpected LicenseConditionSet.CountByOrigin(%q): got %d, want %d", origin, ls.CountByOrigin(onode), len(expected))
+		}
+		byOrigin := ls.ByOrigin(onode).AsList()
+		if len(expected) != len(byOrigin) {
+			t.Errorf("unexpected LicenseConditionSet.ByOrigin(%q): got %v, want %v", origin, byOrigin, expected)
+		} else {
+			sort.Strings(expected)
+			actual := make([]string, 0, len(byOrigin))
+			for _, lc := range byOrigin {
+				actual = append(actual, lc.Name())
+			}
+			sort.Strings(actual)
+			for i := 0; i < len(expected); i++ {
+				if expected[i] != actual[i] {
+					t.Errorf("unexpected LicenseConditionSet.ByOrigin(%q) index %d in %v: got %s, want %s", origin, i, actual, actual[i], expected[i])
+				}
+			}
+		}
+	}
+	if expectedCount != ls.Count() {
+		t.Errorf("unexpected LicenseConditionSet.Count(): got %d, want %d", ls.Count(), expectedCount)
+	}
+	if ls.IsEmpty() {
+		if expectedCount != 0 {
+			t.Errorf("unexpected LicenseConditionSet.IsEmpty(): got true, want false")
+		}
+	} else {
+		if expectedCount == 0 {
+			t.Errorf("unexpected LicenseConditionSet.IsEmpty(): got false, want true")
+		}
+	}
+}
+
+func TestConditionSet(t *testing.T) {
+	tests := []struct {
+		name       string
+		conditions map[string][]string
+		add        map[string][]string
+		byName     map[string][]string
+		byOrigin   map[string][]string
+	}{
+		{
+			name:       "empty",
+			conditions: map[string][]string{},
+			add:        map[string][]string{},
+			byName: map[string][]string{
+				"notice":     []string{},
+				"restricted": []string{},
+			},
+			byOrigin: map[string][]string{
+				"bin1": []string{},
+				"lib1": []string{},
+				"bin2": []string{},
+				"lib2": []string{},
+			},
+		},
+		{
+			name: "noticeonly",
+			conditions: map[string][]string{
+				"notice": []string{"bin1", "lib1"},
+			},
+			byName: map[string][]string{
+				"notice":     []string{"bin1", "lib1"},
+				"restricted": []string{},
+			},
+			byOrigin: map[string][]string{
+				"bin1": []string{"notice"},
+				"lib1": []string{"notice"},
+				"bin2": []string{},
+				"lib2": []string{},
+			},
+		},
+		{
+			name: "noticeonlyadded",
+			conditions: map[string][]string{
+				"notice": []string{"bin1", "lib1"},
+			},
+			add: map[string][]string{
+				"notice": []string{"bin1", "bin2"},
+			},
+			byName: map[string][]string{
+				"notice":     []string{"bin1", "bin2", "lib1"},
+				"restricted": []string{},
+			},
+			byOrigin: map[string][]string{
+				"bin1": []string{"notice"},
+				"lib1": []string{"notice"},
+				"bin2": []string{"notice"},
+				"lib2": []string{},
+			},
+		},
+		{
+			name: "everything",
+			conditions: map[string][]string{
+				"notice":            []string{"bin1", "bin2", "lib1", "lib2"},
+				"reciprocal":        []string{"bin1", "bin2", "lib1", "lib2"},
+				"restricted":        []string{"bin1", "bin2", "lib1", "lib2"},
+				"by_exception_only": []string{"bin1", "bin2", "lib1", "lib2"},
+			},
+			add: map[string][]string{
+				"notice":            []string{"bin1", "bin2", "lib1", "lib2"},
+				"reciprocal":        []string{"bin1", "bin2", "lib1", "lib2"},
+				"restricted":        []string{"bin1", "bin2", "lib1", "lib2"},
+				"by_exception_only": []string{"bin1", "bin2", "lib1", "lib2"},
+			},
+			byName: map[string][]string{
+				"permissive":        []string{},
+				"notice":            []string{"bin1", "bin2", "lib1", "lib2"},
+				"reciprocal":        []string{"bin1", "bin2", "lib1", "lib2"},
+				"restricted":        []string{"bin1", "bin2", "lib1", "lib2"},
+				"by_exception_only": []string{"bin1", "bin2", "lib1", "lib2"},
+			},
+			byOrigin: map[string][]string{
+				"bin1":  []string{"notice", "reciprocal", "restricted", "by_exception_only"},
+				"bin2":  []string{"notice", "reciprocal", "restricted", "by_exception_only"},
+				"lib1":  []string{"notice", "reciprocal", "restricted", "by_exception_only"},
+				"lib2":  []string{"notice", "reciprocal", "restricted", "by_exception_only"},
+				"other": []string{},
+			},
+		},
+		{
+			name: "allbutoneeach",
+			conditions: map[string][]string{
+				"notice":            []string{"bin2", "lib1", "lib2"},
+				"reciprocal":        []string{"bin1", "lib1", "lib2"},
+				"restricted":        []string{"bin1", "bin2", "lib2"},
+				"by_exception_only": []string{"bin1", "bin2", "lib1"},
+			},
+			byName: map[string][]string{
+				"permissive":        []string{},
+				"notice":            []string{"bin2", "lib1", "lib2"},
+				"reciprocal":        []string{"bin1", "lib1", "lib2"},
+				"restricted":        []string{"bin1", "bin2", "lib2"},
+				"by_exception_only": []string{"bin1", "bin2", "lib1"},
+			},
+			byOrigin: map[string][]string{
+				"bin1":  []string{"reciprocal", "restricted", "by_exception_only"},
+				"bin2":  []string{"notice", "restricted", "by_exception_only"},
+				"lib1":  []string{"notice", "reciprocal", "by_exception_only"},
+				"lib2":  []string{"notice", "reciprocal", "restricted"},
+				"other": []string{},
+			},
+		},
+		{
+			name: "allbutoneeachadded",
+			conditions: map[string][]string{
+				"notice":            []string{"bin2", "lib1", "lib2"},
+				"reciprocal":        []string{"bin1", "lib1", "lib2"},
+				"restricted":        []string{"bin1", "bin2", "lib2"},
+				"by_exception_only": []string{"bin1", "bin2", "lib1"},
+			},
+			add: map[string][]string{
+				"notice":            []string{"bin2", "lib1", "lib2"},
+				"reciprocal":        []string{"bin1", "lib1", "lib2"},
+				"restricted":        []string{"bin1", "bin2", "lib2"},
+				"by_exception_only": []string{"bin1", "bin2", "lib1"},
+			},
+			byName: map[string][]string{
+				"permissive":        []string{},
+				"notice":            []string{"bin2", "lib1", "lib2"},
+				"reciprocal":        []string{"bin1", "lib1", "lib2"},
+				"restricted":        []string{"bin1", "bin2", "lib2"},
+				"by_exception_only": []string{"bin1", "bin2", "lib1"},
+			},
+			byOrigin: map[string][]string{
+				"bin1":  []string{"reciprocal", "restricted", "by_exception_only"},
+				"bin2":  []string{"notice", "restricted", "by_exception_only"},
+				"lib1":  []string{"notice", "reciprocal", "by_exception_only"},
+				"lib2":  []string{"notice", "reciprocal", "restricted"},
+				"other": []string{},
+			},
+		},
+		{
+			name: "allbutoneeachfilled",
+			conditions: map[string][]string{
+				"notice":            []string{"bin2", "lib1", "lib2"},
+				"reciprocal":        []string{"bin1", "lib1", "lib2"},
+				"restricted":        []string{"bin1", "bin2", "lib2"},
+				"by_exception_only": []string{"bin1", "bin2", "lib1"},
+			},
+			add: map[string][]string{
+				"notice":            []string{"bin1", "bin2", "lib1"},
+				"reciprocal":        []string{"bin1", "bin2", "lib2"},
+				"restricted":        []string{"bin1", "lib1", "lib2"},
+				"by_exception_only": []string{"bin2", "lib1", "lib2"},
+			},
+			byName: map[string][]string{
+				"permissive":        []string{},
+				"notice":            []string{"bin1", "bin2", "lib1", "lib2"},
+				"reciprocal":        []string{"bin1", "bin2", "lib1", "lib2"},
+				"restricted":        []string{"bin1", "bin2", "lib1", "lib2"},
+				"by_exception_only": []string{"bin1", "bin2", "lib1", "lib2"},
+			},
+			byOrigin: map[string][]string{
+				"bin1":  []string{"notice", "reciprocal", "restricted", "by_exception_only"},
+				"bin2":  []string{"notice", "reciprocal", "restricted", "by_exception_only"},
+				"lib1":  []string{"notice", "reciprocal", "restricted", "by_exception_only"},
+				"lib2":  []string{"notice", "reciprocal", "restricted", "by_exception_only"},
+				"other": []string{},
+			},
+		},
+		{
+			name: "oneeach",
+			conditions: map[string][]string{
+				"notice":            []string{"bin1"},
+				"reciprocal":        []string{"bin2"},
+				"restricted":        []string{"lib1"},
+				"by_exception_only": []string{"lib2"},
+			},
+			byName: map[string][]string{
+				"permissive":        []string{},
+				"notice":            []string{"bin1"},
+				"reciprocal":        []string{"bin2"},
+				"restricted":        []string{"lib1"},
+				"by_exception_only": []string{"lib2"},
+			},
+			byOrigin: map[string][]string{
+				"bin1":  []string{"notice"},
+				"bin2":  []string{"reciprocal"},
+				"lib1":  []string{"restricted"},
+				"lib2":  []string{"by_exception_only"},
+				"other": []string{},
+			},
+		},
+		{
+			name: "oneeachoverlap",
+			conditions: map[string][]string{
+				"notice":            []string{"bin1"},
+				"reciprocal":        []string{"bin2"},
+				"restricted":        []string{"lib1"},
+				"by_exception_only": []string{"lib2"},
+			},
+			add: map[string][]string{
+				"notice":            []string{"lib2"},
+				"reciprocal":        []string{"lib1"},
+				"restricted":        []string{"bin2"},
+				"by_exception_only": []string{"bin1"},
+			},
+			byName: map[string][]string{
+				"permissive":        []string{},
+				"notice":            []string{"bin1", "lib2"},
+				"reciprocal":        []string{"bin2", "lib1"},
+				"restricted":        []string{"bin2", "lib1"},
+				"by_exception_only": []string{"bin1", "lib2"},
+			},
+			byOrigin: map[string][]string{
+				"bin1":  []string{"by_exception_only", "notice"},
+				"bin2":  []string{"reciprocal", "restricted"},
+				"lib1":  []string{"reciprocal", "restricted"},
+				"lib2":  []string{"by_exception_only", "notice"},
+				"other": []string{},
+			},
+		},
+		{
+			name: "oneeachadded",
+			conditions: map[string][]string{
+				"notice":            []string{"bin1"},
+				"reciprocal":        []string{"bin2"},
+				"restricted":        []string{"lib1"},
+				"by_exception_only": []string{"lib2"},
+			},
+			add: map[string][]string{
+				"notice":            []string{"bin1"},
+				"reciprocal":        []string{"bin2"},
+				"restricted":        []string{"lib1"},
+				"by_exception_only": []string{"lib2"},
+			},
+			byName: map[string][]string{
+				"permissive":        []string{},
+				"notice":            []string{"bin1"},
+				"reciprocal":        []string{"bin2"},
+				"restricted":        []string{"lib1"},
+				"by_exception_only": []string{"lib2"},
+			},
+			byOrigin: map[string][]string{
+				"bin1":  []string{"notice"},
+				"bin2":  []string{"reciprocal"},
+				"lib1":  []string{"restricted"},
+				"lib2":  []string{"by_exception_only"},
+				"other": []string{},
+			},
+		},
+	}
+	for _, tt := range tests {
+		testPublicInterface := func(lg *LicenseGraph, cs *LicenseConditionSet, t *testing.T) {
+			byName(tt.byName).checkPublic(cs, t)
+			byOrigin(tt.byOrigin).checkPublic(lg, cs, t)
+		}
+		t.Run(tt.name+"_public_interface", func(t *testing.T) {
+			lg := newLicenseGraph()
+			cs := NewLicenseConditionSet(toConditionList(lg, tt.conditions)...)
+			if tt.add != nil {
+				cs.Add(toConditionList(lg, tt.add)...)
+			}
+			testPublicInterface(lg, cs, t)
+		})
+
+		t.Run("Copy() of "+tt.name+"_public_interface", func(t *testing.T) {
+			lg := newLicenseGraph()
+			cs := NewLicenseConditionSet(toConditionList(lg, tt.conditions)...)
+			if tt.add != nil {
+				cs.Add(toConditionList(lg, tt.add)...)
+			}
+			testPublicInterface(lg, cs.Copy(), t)
+		})
+
+		testPrivateInterface := func(lg *LicenseGraph, cs *LicenseConditionSet, t *testing.T) {
+			slist := make([]string, 0, cs.Count())
+			for origin, expected := range tt.byOrigin {
+				for _, name := range expected {
+					slist = append(slist, origin+";"+name)
+				}
+			}
+			actualSlist := cs.asStringList(";")
+			if len(slist) != len(actualSlist) {
+				t.Errorf("unexpected LicenseConditionSet.asStringList(\";\"): got %v, want %v", actualSlist, slist)
+			} else {
+				sort.Strings(slist)
+				sort.Strings(actualSlist)
+				for i := 0; i < len(slist); i++ {
+					if slist[i] != actualSlist[i] {
+						t.Errorf("unexpected LicenseConditionSet.asStringList(\";\") index %d in %v: got %s, want %s", i, actualSlist, actualSlist[i], slist[i])
+					}
+				}
+			}
+		}
+
+		t.Run(tt.name+"_private_list_interface", func(t *testing.T) {
+			lg := newLicenseGraph()
+			cs := newLicenseConditionSet()
+			for name, origins := range tt.conditions {
+				for _, origin := range origins {
+					cs.add(newTestNode(lg, origin), name)
+				}
+			}
+			if tt.add != nil {
+				cs.Add(toConditionList(lg, tt.add)...)
+			}
+			testPrivateInterface(lg, cs, t)
+		})
+
+		t.Run(tt.name+"_private_set_interface", func(t *testing.T) {
+			lg := newLicenseGraph()
+			cs := newLicenseConditionSet()
+			for name, origins := range tt.conditions {
+				for _, origin := range origins {
+					cs.add(newTestNode(lg, origin), name)
+				}
+			}
+			if tt.add != nil {
+				other := newLicenseConditionSet()
+				for name, origins := range tt.add {
+					for _, origin := range origins {
+						other.add(newTestNode(lg, origin), name)
+					}
+				}
+				cs.AddSet(other)
+			}
+			testPrivateInterface(lg, cs, t)
+		})
+	}
+}
+
+func TestConditionSet_Removals(t *testing.T) {
+	tests := []struct {
+		name         string
+		conditions   map[string][]string
+		removeByName []ConditionNames
+		removeSet    map[string][]string
+		byName       map[string][]string
+		byOrigin     map[string][]string
+	}{
+		{
+			name:         "emptybyname",
+			conditions:   map[string][]string{},
+			removeByName: []ConditionNames{{"reciprocal", "restricted"}},
+			byName: map[string][]string{
+				"notice":     []string{},
+				"restricted": []string{},
+			},
+			byOrigin: map[string][]string{
+				"bin1": []string{},
+				"lib1": []string{},
+				"bin2": []string{},
+				"lib2": []string{},
+			},
+		},
+		{
+			name:       "emptybyset",
+			conditions: map[string][]string{},
+			removeSet: map[string][]string{
+				"notice":     []string{"bin1", "bin2"},
+				"restricted": []string{"lib1", "lib2"},
+			},
+			byName: map[string][]string{
+				"notice":     []string{},
+				"restricted": []string{},
+			},
+			byOrigin: map[string][]string{
+				"bin1": []string{},
+				"lib1": []string{},
+				"bin2": []string{},
+				"lib2": []string{},
+			},
+		},
+		{
+			name: "everythingremovenone",
+			conditions: map[string][]string{
+				"notice":            []string{"bin1", "bin2", "lib1", "lib2"},
+				"reciprocal":        []string{"bin1", "bin2", "lib1", "lib2"},
+				"restricted":        []string{"bin1", "bin2", "lib1", "lib2"},
+				"by_exception_only": []string{"bin1", "bin2", "lib1", "lib2"},
+			},
+			removeByName: []ConditionNames{{"permissive", "unencumbered"}},
+			removeSet: map[string][]string{
+				"notice": []string{"apk1"},
+			},
+			byName: map[string][]string{
+				"permissive":        []string{},
+				"notice":            []string{"bin1", "bin2", "lib1", "lib2"},
+				"reciprocal":        []string{"bin1", "bin2", "lib1", "lib2"},
+				"restricted":        []string{"bin1", "bin2", "lib1", "lib2"},
+				"by_exception_only": []string{"bin1", "bin2", "lib1", "lib2"},
+			},
+			byOrigin: map[string][]string{
+				"bin1":  []string{"notice", "reciprocal", "restricted", "by_exception_only"},
+				"bin2":  []string{"notice", "reciprocal", "restricted", "by_exception_only"},
+				"lib1":  []string{"notice", "reciprocal", "restricted", "by_exception_only"},
+				"lib2":  []string{"notice", "reciprocal", "restricted", "by_exception_only"},
+				"other": []string{},
+			},
+		},
+		{
+			name: "everythingremovesome",
+			conditions: map[string][]string{
+				"notice":            []string{"bin1", "bin2", "lib1", "lib2"},
+				"reciprocal":        []string{"bin1", "bin2", "lib1", "lib2"},
+				"restricted":        []string{"bin1", "bin2", "lib1", "lib2"},
+				"by_exception_only": []string{"bin1", "bin2", "lib1", "lib2"},
+			},
+			removeByName: []ConditionNames{{"restricted", "by_exception_only"}},
+			removeSet: map[string][]string{
+				"notice": []string{"lib1"},
+			},
+			byName: map[string][]string{
+				"permissive":        []string{},
+				"notice":            []string{"bin1", "bin2", "lib2"},
+				"reciprocal":        []string{"bin1", "bin2", "lib1", "lib2"},
+				"restricted":        []string{},
+				"by_exception_only": []string{},
+			},
+			byOrigin: map[string][]string{
+				"bin1":  []string{"notice", "reciprocal"},
+				"bin2":  []string{"notice", "reciprocal"},
+				"lib1":  []string{"reciprocal"},
+				"lib2":  []string{"notice", "reciprocal"},
+				"other": []string{},
+			},
+		},
+		{
+			name: "everythingremoveall",
+			conditions: map[string][]string{
+				"notice":            []string{"bin1", "bin2", "lib1", "lib2"},
+				"reciprocal":        []string{"bin1", "bin2", "lib1", "lib2"},
+				"restricted":        []string{"bin1", "bin2", "lib1", "lib2"},
+				"by_exception_only": []string{"bin1", "bin2", "lib1", "lib2"},
+			},
+			removeByName: []ConditionNames{{"restricted", "by_exception_only"}},
+			removeSet: map[string][]string{
+				"notice":     []string{"bin1", "bin2", "lib1", "lib2"},
+				"reciprocal": []string{"bin1", "bin2", "lib1", "lib2"},
+				"restricted": []string{"bin1"},
+			},
+			byName: map[string][]string{
+				"permissive":        []string{},
+				"notice":            []string{},
+				"reciprocal":        []string{},
+				"restricted":        []string{},
+				"by_exception_only": []string{},
+			},
+			byOrigin: map[string][]string{
+				"bin1":  []string{},
+				"bin2":  []string{},
+				"lib1":  []string{},
+				"lib2":  []string{},
+				"other": []string{},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			lg := newLicenseGraph()
+			cs := newLicenseConditionSet()
+			for name, origins := range tt.conditions {
+				for _, origin := range origins {
+					cs.add(newTestNode(lg, origin), name)
+				}
+			}
+			if tt.removeByName != nil {
+				cs.RemoveAllByName(tt.removeByName...)
+			}
+			if tt.removeSet != nil {
+				other := newLicenseConditionSet()
+				for name, origins := range tt.removeSet {
+					for _, origin := range origins {
+						other.add(newTestNode(lg, origin), name)
+					}
+				}
+				cs.RemoveSet(other)
+			}
+			byName(tt.byName).checkPublic(cs, t)
+			byOrigin(tt.byOrigin).checkPublic(lg, cs, t)
+		})
+	}
+}
diff --git a/tools/compliance/doc.go b/tools/compliance/doc.go
new file mode 100644
index 0000000..a47c1cf
--- /dev/null
+++ b/tools/compliance/doc.go
@@ -0,0 +1,77 @@
+// Copyright 2021 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 compliance provides an approved means for reading, consuming, and
+analyzing license metadata graphs.
+
+Assuming the license metadata and dependencies are fully and accurately
+recorded in the build system, any discrepancy between the official policy for
+open source license compliance and this code is a bug in this code.
+
+A few principal types to understand are LicenseGraph, LicenseCondition, and
+ResolutionSet.
+
+LicenseGraph
+------------
+
+A LicenseGraph is an immutable graph of the targets and dependencies reachable
+from a specific set of root targets. In general, the root targets will be the
+artifacts in a release or distribution. While conceptually immutable, parts of
+the graph may be loaded or evaluated lazily.
+
+LicenseCondition
+----------------
+
+A LicenseCondition is an immutable tuple pairing a condition name with an
+originating target. e.g. Per current policy, a static library licensed under an
+MIT license would pair a "notice" condition with the static library target, and
+a dynamic license licensed under GPL would pair a "restricted" condition with
+the dynamic library target.
+
+ResolutionSet
+-------------
+
+A ResolutionSet is an immutable set of `AttachesTo`, `ActsOn`, `Resolves`
+tuples describing how license conditions apply to targets.
+
+`AttachesTo` is the trigger for acting. Distribution of the target invokes
+the policy.
+
+`ActsOn` is the target to share, give notice for, hide etc.
+
+`Resolves` is the license condition that the action resolves.
+
+Remember: Each license condition pairs a condition name with an originating
+target so each resolution in a ResolutionSet has two targets it applies to and
+one target from which it originates, all of which may be the same target.
+
+For most condition types, `ActsOn` and `Resolves.Origin` will be the same
+target. For example, a notice condition policy means attribution or notice must
+be given for the target where the condition originates. Likewise, a proprietary
+condition policy means the privacy of the target where the condition originates
+must be respected. i.e. The thing acted on is the origin.
+
+Restricted conditions are different. The infectious nature of restricted often
+means sharing code that is not the target where the restricted condition
+originates. Linking an MIT library to a GPL library implies a policy to share
+the MIT library despite the MIT license having no source sharing requirement.
+
+In this case, one or more resolution tuples will have the MIT license module in
+`ActsOn` and the restricted condition originating at the GPL library module in
+`Resolves`. These tuples will `AttachTo` every target that depends on the GPL
+library because shipping any of those targets trigger the policy to share the
+code.
+*/
+package compliance
diff --git a/tools/compliance/graph.go b/tools/compliance/graph.go
new file mode 100644
index 0000000..9dcfa66
--- /dev/null
+++ b/tools/compliance/graph.go
@@ -0,0 +1,503 @@
+// Copyright 2021 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 compliance
+
+import (
+	"fmt"
+	"sort"
+	"strings"
+	"sync"
+)
+
+// LicenseGraph describes the immutable license metadata for a set of root
+// targets and the transitive closure of their dependencies.
+//
+// Alternatively, a graph is a set of edges. In this case directed, annotated
+// edges from targets to dependencies.
+//
+// A LicenseGraph provides the frame of reference for all of the other types
+// defined here. It is possible to have multiple graphs, and to have targets,
+// edges, and resolutions from multiple graphs. But it is an error to try to
+// mix items from different graphs in the same operation.
+// May panic if attempted.
+//
+// The compliance package assumes specific private implementations of each of
+// these interfaces. May panic if attempts are made to combine different
+// implementations of some interfaces with expected implementations of other
+// interfaces here.
+type LicenseGraph struct {
+	// rootFiles identifies the original set of files to read. (immutable)
+	//
+	// Defines the starting "top" for top-down walks.
+	//
+	// Alternatively, an instance of licenseGraphImp conceptually defines a scope within
+	// the universe of build graphs as a sub-graph rooted at rootFiles where all edges
+	// and targets for the instance are defined relative to and within that scope. For
+	// most analyses, the correct scope is to root the graph at all of the distributed
+	// artifacts.
+	rootFiles []string
+
+	// edges lists the directed edges in the graph from target to dependency. (guarded by mu)
+	//
+	// Alternatively, the graph is the set of `edges`.
+	edges []*dependencyEdge
+
+	// targets identifies, indexes by name, and describes the entire set of target node files.
+	/// (guarded by mu)
+	targets map[string]*TargetNode
+
+	// index facilitates looking up edges from targets. (creation guarded by my)
+	//
+	// This is a forward index from target to dependencies. i.e. "top-down"
+	index map[string][]*dependencyEdge
+
+	// rsBU caches the results of a full bottom-up resolve. (creation guarded by mu)
+	//
+	// A bottom-up resolve is a prerequisite for all of the top-down resolves so caching
+	// the result is a performance win.
+	rsBU *ResolutionSet
+
+	// rsTD caches the results of a full top-down resolve. (creation guarded by mu)
+	//
+	// A top-down resolve is a prerequisite for final resolutions.
+	// e.g. a shipped node inheriting a `restricted` condition from a parent through a
+	// dynamic dependency implies a notice dependency on the parent; even though, the
+	// distribution does not happen as a result of the dynamic dependency itself.
+	rsTD *ResolutionSet
+
+	// shippedNodes caches the results of a full walk of nodes identifying targets
+	// distributed either directly or as derivative works. (creation guarded by mu)
+	shippedNodes *TargetNodeSet
+
+	// mu guards against concurrent update.
+	mu sync.Mutex
+}
+
+// TargetNode returns the target node identified by `name`.
+func (lg *LicenseGraph) TargetNode(name string) *TargetNode {
+	if _, ok := lg.targets[name]; !ok {
+		panic(fmt.Errorf("target node %q missing from graph", name))
+	}
+	return lg.targets[name]
+}
+
+// HasTargetNode returns true if a target node identified by `name` appears in
+// the graph.
+func (lg *LicenseGraph) HasTargetNode(name string) bool {
+	_, isPresent := lg.targets[name]
+	return isPresent
+}
+
+// Edges returns the list of edges in the graph. (unordered)
+func (lg *LicenseGraph) Edges() TargetEdgeList {
+	edges := make(TargetEdgeList, 0, len(lg.edges))
+	for _, e := range lg.edges {
+		edges = append(edges, TargetEdge{lg, e})
+	}
+	return edges
+}
+
+// Targets returns the list of target nodes in the graph. (unordered)
+func (lg *LicenseGraph) Targets() TargetNodeList {
+	targets := make(TargetNodeList, 0, len(lg.targets))
+	for target := range lg.targets {
+		targets = append(targets, lg.targets[target])
+	}
+	return targets
+}
+
+// compliance-only LicenseGraph methods
+
+// newLicenseGraph constructs a new, empty instance of LicenseGraph.
+func newLicenseGraph() *LicenseGraph {
+	return &LicenseGraph{
+		rootFiles: []string{},
+		edges:     make([]*dependencyEdge, 0, 1000),
+		targets:   make(map[string]*TargetNode),
+	}
+}
+
+// indexForward guarantees the `index` map is populated to look up edges by
+// `target`.
+func (lg *LicenseGraph) indexForward() {
+	lg.mu.Lock()
+	defer func() {
+		lg.mu.Unlock()
+	}()
+
+	if lg.index != nil {
+		return
+	}
+
+	lg.index = make(map[string][]*dependencyEdge)
+	for _, e := range lg.edges {
+		if _, ok := lg.index[e.target]; ok {
+			lg.index[e.target] = append(lg.index[e.target], e)
+		} else {
+			lg.index[e.target] = []*dependencyEdge{e}
+		}
+	}
+}
+
+// TargetEdge describes a directed, annotated edge from a target to a
+// dependency. (immutable)
+//
+// A LicenseGraph, above, is a set of TargetEdges.
+//
+// i.e. `Target` depends on `Dependency` in the manner described by
+// `Annotations`.
+type TargetEdge struct {
+	// lg identifies the scope, i.e. license graph, in which the edge appears.
+	lg *LicenseGraph
+
+	// e identifies describes the target, dependency, and annotations of the edge.
+	e *dependencyEdge
+}
+
+// Target identifies the target that depends on the dependency.
+//
+// Target needs Dependency to build.
+func (e TargetEdge) Target() *TargetNode {
+	return e.lg.targets[e.e.target]
+}
+
+// Dependency identifies the target depended on by the target.
+//
+// Dependency builds without Target, but Target needs Dependency to build.
+func (e TargetEdge) Dependency() *TargetNode {
+	return e.lg.targets[e.e.dependency]
+}
+
+// Annotations describes the type of edge by the set of annotations attached to
+// it.
+//
+// Only annotations prescribed by policy have any meaning for licensing, and
+// the meaning for licensing is likewise prescribed by policy. Other annotations
+// are preserved and ignored by policy.
+func (e TargetEdge) Annotations() TargetEdgeAnnotations {
+	return e.e.annotations
+}
+
+// TargetEdgeList orders lists of edges by target then dependency then annotations.
+type TargetEdgeList []TargetEdge
+
+// Len returns the count of the elmements in the list.
+func (l TargetEdgeList) Len() int      { return len(l) }
+
+// Swap rearranges 2 elements so that each occupies the other's former position.
+func (l TargetEdgeList) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
+
+// Less returns true when the `i`th element is lexicographically less than the `j`th.
+func (l TargetEdgeList) Less(i, j int) bool {
+	if l[i].e.target == l[j].e.target {
+		if l[i].e.dependency == l[j].e.dependency {
+			return l[i].e.annotations.Compare(l[j].e.annotations) < 0
+		}
+		return l[i].e.dependency < l[j].e.dependency
+	}
+	return l[i].e.target < l[j].e.target
+}
+
+// TargetEdgePath describes a sequence of edges starting at a root and ending
+// at some final dependency.
+type TargetEdgePath []TargetEdge
+
+// NewTargetEdgePath creates a new, empty path with capacity `cap`.
+func NewTargetEdgePath(cap int) *TargetEdgePath {
+	p := make(TargetEdgePath, 0, cap)
+	return &p
+}
+
+// Push appends a new edge to the list verifying that the target of the new
+// edge is the dependency of the prior.
+func (p *TargetEdgePath) Push(edge TargetEdge) {
+	if len(*p) == 0 {
+		*p = append(*p, edge)
+		return
+	}
+	if (*p)[len(*p)-1].e.dependency != edge.e.target {
+		panic(fmt.Errorf("disjoint path %s does not end at %s", p.String(), edge.e.target))
+	}
+	*p = append(*p, edge)
+}
+
+// Pop shortens the path by 1 edge.
+func (p *TargetEdgePath) Pop() {
+	if len(*p) == 0 {
+		panic(fmt.Errorf("attempt to remove edge from empty path"))
+	}
+	*p = (*p)[:len(*p)-1]
+}
+
+// Clear makes the path length 0.
+func (p *TargetEdgePath) Clear() {
+	*p = (*p)[:0]
+}
+
+// String returns a string representation of the path: [n1 -> n2 -> ... -> nn].
+func (p *TargetEdgePath) String() string {
+	if p == nil {
+		return "nil"
+	}
+	if len(*p) == 0 {
+		return "[]"
+	}
+	var sb strings.Builder
+	fmt.Fprintf(&sb, "[")
+	for _, e := range *p {
+		fmt.Fprintf(&sb, "%s -> ", e.e.target)
+	}
+	fmt.Fprintf(&sb, "%s]", (*p)[len(*p)-1].e.dependency)
+	return sb.String()
+}
+
+// TargetNode describes a module or target identified by the name of a specific
+// metadata file. (immutable)
+//
+// Each metadata file corresponds to a Soong module or to a Make target.
+//
+// A target node can appear as the target or as the dependency in edges.
+// Most target nodes appear as both target in one edge and as dependency in
+// other edges.
+type TargetNode targetNode
+
+// Name returns the string that identifies the target node.
+// i.e. path to license metadata file
+func (tn *TargetNode) Name() string {
+	return tn.name
+}
+
+// PackageName returns the string that identifes the package for the target.
+func (tn *TargetNode) PackageName() string {
+	return tn.proto.GetPackageName()
+}
+
+// ModuleTypes returns the list of module types implementing the target.
+// (unordered)
+//
+// In an ideal world, only 1 module type would implement each target, but the
+// interactions between Soong and Make for host versus product and for a
+// variety of architectures sometimes causes multiple module types per target
+// (often a regular build target and a prebuilt.)
+func (tn *TargetNode) ModuleTypes() []string {
+	return append([]string{}, tn.proto.ModuleTypes...)
+}
+
+// ModuleClasses returns the list of module classes implementing the target.
+// (unordered)
+func (tn *TargetNode) ModuleClasses() []string {
+	return append([]string{}, tn.proto.ModuleClasses...)
+}
+
+// Projects returns the projects defining the target node. (unordered)
+//
+// In an ideal world, only 1 project defines a target, but the interaction
+// between Soong and Make for a variety of architectures and for host versus
+// product means a module is sometimes defined more than once.
+func (tn *TargetNode) Projects() []string {
+	return append([]string{}, tn.proto.Projects...)
+}
+
+// LicenseKinds returns the list of license kind names for the module or
+// target. (unordered)
+//
+// e.g. SPDX-license-identifier-MIT or legacy_proprietary
+func (tn *TargetNode) LicenseKinds() []string {
+	return append([]string{}, tn.proto.LicenseKinds...)
+}
+
+// LicenseConditions returns a copy of the set of license conditions
+// originating at the target. The values that appear and how each is resolved
+// is a matter of policy. (unordered)
+//
+// e.g. notice or proprietary
+func (tn *TargetNode) LicenseConditions() *LicenseConditionSet {
+	result := newLicenseConditionSet()
+	result.add(tn, tn.proto.LicenseConditions...)
+	return result
+}
+
+// LicenseTexts returns the paths to the files containing the license texts for
+// the target. (unordered)
+func (tn *TargetNode) LicenseTexts() []string {
+	return append([]string{}, tn.proto.LicenseTexts...)
+}
+
+// IsContainer returns true if the target represents a container that merely
+// aggregates other targets.
+func (tn *TargetNode) IsContainer() bool {
+	return tn.proto.GetIsContainer()
+}
+
+// Built returns the list of files built by the module or target. (unordered)
+func (tn *TargetNode) Built() []string {
+	return append([]string{}, tn.proto.Built...)
+}
+
+// Installed returns the list of files installed by the module or target.
+// (unordered)
+func (tn *TargetNode) Installed() []string {
+	return append([]string{}, tn.proto.Installed...)
+}
+
+// InstallMap returns the list of path name transformations to make to move
+// files from their original location in the file system to their destination
+// inside a container. (unordered)
+func (tn *TargetNode) InstallMap() []InstallMap {
+	result := make([]InstallMap, 0, len(tn.proto.InstallMap))
+	for _, im := range tn.proto.InstallMap {
+		result = append(result, InstallMap{im.GetFromPath(), im.GetContainerPath()})
+	}
+	return result
+}
+
+// Sources returns the list of file names depended on by the target, which may
+// be a proper subset of those made available by dependency modules.
+// (unordered)
+func (tn *TargetNode) Sources() []string {
+	return append([]string{}, tn.proto.Sources...)
+}
+
+// InstallMap describes the mapping from an input filesystem file to file in a
+// container.
+type InstallMap struct {
+	// FromPath is the input path on the filesystem.
+	FromPath string
+
+	// ContainerPath is the path to the same file inside the container or
+	// installed location.
+	ContainerPath string
+}
+
+// TargetEdgeAnnotations describes an immutable set of annotations attached to
+// an edge from a target to a dependency.
+//
+// Annotations typically distinguish between static linkage versus dynamic
+// versus tools that are used at build time but are not linked in any way.
+type TargetEdgeAnnotations struct {
+	annotations map[string]bool
+}
+
+// newEdgeAnnotations creates a new instance of TargetEdgeAnnotations.
+func newEdgeAnnotations() TargetEdgeAnnotations {
+	return TargetEdgeAnnotations{make(map[string]bool)}
+}
+
+// HasAnnotation returns true if an annotation `ann` is in the set.
+func (ea TargetEdgeAnnotations) HasAnnotation(ann string) bool {
+	_, ok := ea.annotations[ann]
+	return ok
+}
+
+// Compare orders TargetAnnotations returning:
+// -1 when ea < other,
+// +1 when ea > other, and
+// 0 when ea == other.
+func (ea TargetEdgeAnnotations) Compare(other TargetEdgeAnnotations) int {
+	a1 := ea.AsList()
+	a2 := other.AsList()
+	sort.Strings(a1)
+	sort.Strings(a2)
+	for k := 0; k < len(a1) && k < len(a2); k++ {
+		if a1[k] < a2[k] {
+			return -1
+		}
+		if a1[k] > a2[k] {
+			return 1
+		}
+	}
+	if len(a1) < len(a2) {
+		return -1
+	}
+	if len(a1) > len(a2) {
+		return 1
+	}
+	return 0
+}
+
+// AsList returns the list of annotation names attached to the edge.
+// (unordered)
+func (ea TargetEdgeAnnotations) AsList() []string {
+	l := make([]string, 0, len(ea.annotations))
+	for ann := range ea.annotations {
+		l = append(l, ann)
+	}
+	return l
+}
+
+// TargetNodeSet describes a set of distinct nodes in a license graph.
+type TargetNodeSet struct {
+	nodes map[*TargetNode]bool
+}
+
+// Contains returns true when `target` is an element of the set.
+func (ts *TargetNodeSet) Contains(target *TargetNode) bool {
+	_, isPresent := ts.nodes[target]
+	return isPresent
+}
+
+// AsList returns the list of target nodes in the set. (unordered)
+func (ts *TargetNodeSet) AsList() TargetNodeList {
+	result := make(TargetNodeList, 0, len(ts.nodes))
+	for tn := range ts.nodes {
+		result = append(result, tn)
+	}
+	return result
+}
+
+// Names returns the array of target node namess in the set. (unordered)
+func (ts *TargetNodeSet) Names() []string {
+	result := make([]string, 0, len(ts.nodes))
+	for tn := range ts.nodes {
+		result = append(result, tn.name)
+	}
+	return result
+}
+
+// TargetNodeList orders a list of targets by name.
+type TargetNodeList []*TargetNode
+
+// Len returns the count of elements in the list.
+func (l TargetNodeList) Len() int      { return len(l) }
+
+// Swap rearranges 2 elements so that each occupies the other's former position.
+func (l TargetNodeList) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
+
+// Less returns true when the `i`th element is lexicographicallt less than the `j`th.
+func (l TargetNodeList) Less(i, j int) bool {
+	return l[i].name < l[j].name
+}
+
+// String returns a string representation of the list.
+func (l TargetNodeList) String() string {
+	var sb strings.Builder
+	fmt.Fprintf(&sb, "[")
+	sep := ""
+	for _, tn := range l {
+		fmt.Fprintf(&sb, "%s%s", sep, tn.name)
+		sep = " "
+	}
+	fmt.Fprintf(&sb, "]")
+	return sb.String()
+}
+
+// Names returns an array the names of the nodes in the same order as the nodes in the list.
+func (l TargetNodeList) Names() []string {
+	result := make([]string, 0, len(l))
+	for _, tn := range l {
+		result = append(result, tn.name)
+	}
+	return result
+}
diff --git a/tools/compliance/policy/policy.go b/tools/compliance/policy/policy.go
new file mode 100644
index 0000000..9dab05b
--- /dev/null
+++ b/tools/compliance/policy/policy.go
@@ -0,0 +1,238 @@
+// Copyright 2021 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 compliance
+
+import (
+	"regexp"
+	"strings"
+)
+
+var (
+	// ImpliesUnencumbered lists the condition names representing an author attempt to disclaim copyright.
+	ImpliesUnencumbered = ConditionNames{"unencumbered"}
+
+	// ImpliesPermissive lists the condition names representing copyrighted but "licensed without policy requirements".
+	ImpliesPermissive = ConditionNames{"permissive"}
+
+	// ImpliesNotice lists the condition names implying a notice or attribution policy.
+	ImpliesNotice = ConditionNames{"unencumbered", "permissive", "notice", "reciprocal", "restricted", "proprietary", "by_exception_only"}
+
+	// ImpliesReciprocal lists the condition names implying a local source-sharing policy.
+	ImpliesReciprocal = ConditionNames{"reciprocal"}
+
+	// Restricted lists the condition names implying an infectious source-sharing policy.
+	ImpliesRestricted = ConditionNames{"restricted"}
+
+	// ImpliesProprietary lists the condition names implying a confidentiality policy.
+	ImpliesProprietary = ConditionNames{"proprietary"}
+
+	// ImpliesByExceptionOnly lists the condition names implying a policy for "license review and approval before use".
+	ImpliesByExceptionOnly = ConditionNames{"proprietary", "by_exception_only"}
+
+	// ImpliesPrivate lists the condition names implying a source-code privacy policy.
+	ImpliesPrivate = ConditionNames{"proprietary"}
+
+	// ImpliesShared lists the condition names implying a source-code sharing policy.
+	ImpliesShared = ConditionNames{"reciprocal", "restricted"}
+)
+
+var (
+	anyLgpl      = regexp.MustCompile(`^SPDX-license-identifier-LGPL.*`)
+	versionedGpl = regexp.MustCompile(`^SPDX-license-identifier-GPL-\p{N}.*`)
+	genericGpl   = regexp.MustCompile(`^SPDX-license-identifier-GPL$`)
+	ccBySa       = regexp.MustCompile(`^SPDX-license-identifier-CC-BY.*-SA.*`)
+)
+
+// Resolution happens in two passes:
+//
+// 1. A bottom-up traversal propagates license conditions up to targets from
+// dendencies as needed.
+//
+// 2. For each condition of interest, a top-down traversal adjusts the attached
+// conditions pushing restricted down from targets into linked dependencies.
+//
+// The behavior of the 2 passes gets controlled by the 2 functions below.
+//
+// The first function controls what happens during the bottom-up traversal. In
+// general conditions flow up through static links but not other dependencies;
+// except, restricted sometimes flows up through dynamic links.
+//
+// In general, too, the originating target gets acted on to resolve the
+// condition (e.g. providing notice), but again restricted is special in that
+// it requires acting on (i.e. sharing source of) both the originating module
+// and the target using the module.
+//
+// The latter function controls what happens during the top-down traversal. In
+// general, only restricted conditions flow down at all, and only through
+// static links.
+//
+// Not all restricted licenses are create equal. Some have special rules or
+// exceptions. e.g. LGPL or "with classpath excption".
+
+// depActionsApplicableToTarget returns the actions which propagate up an
+// edge from dependency to target.
+//
+// This function sets the policy for the bottom-up traversal and how conditions
+// flow up the graph from dependencies to targets.
+//
+// If a pure aggregation is built into a derivative work that is not a pure
+// aggregation, per policy it ceases to be a pure aggregation in the context of
+// that derivative work. The `treatAsAggregate` parameter will be false for
+// non-aggregates and for aggregates in non-aggregate contexts.
+func depActionsApplicableToTarget(e TargetEdge, depActions actionSet, treatAsAggregate bool) actionSet {
+	result := make(actionSet)
+	if edgeIsDerivation(e) {
+		result.addSet(depActions)
+		for _, cs := range depActions.byName(ImpliesRestricted) {
+			result.add(e.Target(), cs)
+		}
+		return result
+	}
+	if !edgeIsDynamicLink(e) {
+		return result
+	}
+
+	restricted := depActions.byName(ImpliesRestricted)
+	for actsOn, cs := range restricted {
+		for _, lc := range cs.AsList() {
+			hasGpl := false
+			hasLgpl := false
+			hasClasspath := false
+			hasGeneric := false
+			hasOther := false
+			for _, kind := range lc.origin.LicenseKinds() {
+				if strings.HasSuffix(kind, "-with-classpath-exception") {
+					hasClasspath = true
+				} else if anyLgpl.MatchString(kind) {
+					hasLgpl = true
+				} else if versionedGpl.MatchString(kind) {
+					hasGpl = true
+				} else if genericGpl.MatchString(kind) {
+					hasGeneric = true
+				} else if kind == "legacy_restricted" || ccBySa.MatchString(kind) {
+					hasOther = true
+				}
+			}
+			if hasOther || hasGpl {
+				result.addCondition(actsOn, lc)
+				result.addCondition(e.Target(), lc)
+				continue
+			}
+			if hasClasspath && !edgeNodesAreIndependentModules(e) {
+				result.addCondition(actsOn, lc)
+				result.addCondition(e.Target(), lc)
+				continue
+			}
+			if hasLgpl || hasClasspath {
+				continue
+			}
+			if !hasGeneric {
+				continue
+			}
+			result.addCondition(actsOn, lc)
+			result.addCondition(e.Target(), lc)
+		}
+	}
+	return result
+}
+
+// targetConditionsApplicableToDep returns the conditions which propagate down
+// an edge from target to dependency.
+//
+// This function sets the policy for the top-down traversal and how conditions
+// flow down the graph from targets to dependencies.
+//
+// If a pure aggregation is built into a derivative work that is not a pure
+// aggregation, per policy it ceases to be a pure aggregation in the context of
+// that derivative work. The `treatAsAggregate` parameter will be false for
+// non-aggregates and for aggregates in non-aggregate contexts.
+func targetConditionsApplicableToDep(e TargetEdge, targetConditions *LicenseConditionSet, treatAsAggregate bool) *LicenseConditionSet {
+	result := targetConditions.Copy()
+
+	// reverse direction -- none of these apply to things depended-on, only to targets depending-on.
+	result.RemoveAllByName(ConditionNames{"unencumbered", "permissive", "notice", "reciprocal", "proprietary", "by_exception_only"})
+
+	if !edgeIsDerivation(e) && !edgeIsDynamicLink(e) {
+		// target is not a derivative work of dependency and is not linked to dependency
+		result.RemoveAllByName(ImpliesRestricted)
+		return result
+	}
+	if treatAsAggregate {
+		// If the author of a pure aggregate licenses it restricted, apply restricted to immediate dependencies.
+		// Otherwise, restricted does not propagate back down to dependencies.
+		restricted := result.ByName(ImpliesRestricted).AsList()
+		for _, lc := range restricted {
+			if lc.origin.name != e.e.target {
+				result.Remove(lc)
+			}
+		}
+		return result
+	}
+	if edgeIsDerivation(e) {
+		return result
+	}
+	restricted := result.ByName(ImpliesRestricted).AsList()
+	for _, lc := range restricted {
+		hasGpl := false
+		hasLgpl := false
+		hasClasspath := false
+		hasGeneric := false
+		hasOther := false
+		for _, kind := range lc.origin.LicenseKinds() {
+			if strings.HasSuffix(kind, "-with-classpath-exception") {
+				hasClasspath = true
+			} else if anyLgpl.MatchString(kind) {
+				hasLgpl = true
+			} else if versionedGpl.MatchString(kind) {
+				hasGpl = true
+			} else if genericGpl.MatchString(kind) {
+				hasGeneric = true
+			} else if kind == "legacy_restricted" || ccBySa.MatchString(kind) {
+				hasOther = true
+			}
+		}
+		if hasOther || hasGpl {
+			continue
+		}
+		if hasClasspath && !edgeNodesAreIndependentModules(e) {
+			continue
+		}
+		if hasGeneric && !hasLgpl && !hasClasspath {
+			continue
+		}
+		result.Remove(lc)
+	}
+	return result
+}
+
+// edgeIsDynamicLink returns true for edges representing shared libraries
+// linked dynamically at runtime.
+func edgeIsDynamicLink(e TargetEdge) bool {
+	return e.e.annotations.HasAnnotation("dynamic")
+}
+
+// edgeIsDerivation returns true for edges where the target is a derivative
+// work of dependency.
+func edgeIsDerivation(e TargetEdge) bool {
+	isDynamic := e.e.annotations.HasAnnotation("dynamic")
+	isToolchain := e.e.annotations.HasAnnotation("toolchain")
+	return !isDynamic && !isToolchain
+}
+
+// edgeNodesAreIndependentModules returns true for edges where the target and
+// dependency are independent modules.
+func edgeNodesAreIndependentModules(e TargetEdge) bool {
+	return e.Target().PackageName() != e.Dependency().PackageName()
+}
diff --git a/tools/compliance/policy/policy_test.go b/tools/compliance/policy/policy_test.go
new file mode 100644
index 0000000..aea307f
--- /dev/null
+++ b/tools/compliance/policy/policy_test.go
@@ -0,0 +1,300 @@
+// Copyright 2021 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 compliance
+
+import (
+	"bytes"
+	"fmt"
+	"sort"
+	"strings"
+	"testing"
+)
+
+func TestPolicy_edgeConditions(t *testing.T) {
+	tests := []struct {
+		name                     string
+		edge                     annotated
+		treatAsAggregate         bool
+		otherCondition           string
+		expectedDepActions       []string
+		expectedTargetConditions []string
+	}{
+		{
+			name:                     "firstparty",
+			edge:                     annotated{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			expectedDepActions:       []string{"apacheLib.meta_lic:apacheLib.meta_lic:notice"},
+			expectedTargetConditions: []string{},
+		},
+		{
+			name:                     "notice",
+			edge:                     annotated{"mitBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			expectedDepActions:       []string{"mitLib.meta_lic:mitLib.meta_lic:notice"},
+			expectedTargetConditions: []string{},
+		},
+		{
+			name: "fponlgpl",
+			edge: annotated{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"static"}},
+			expectedDepActions: []string{
+				"apacheBin.meta_lic:lgplLib.meta_lic:restricted",
+				"lgplLib.meta_lic:lgplLib.meta_lic:restricted",
+			},
+			expectedTargetConditions: []string{},
+		},
+		{
+			name:                     "fponlgpldynamic",
+			edge:                     annotated{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
+			expectedDepActions:       []string{},
+			expectedTargetConditions: []string{},
+		},
+		{
+			name: "fpongpl",
+			edge: annotated{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			expectedDepActions: []string{
+				"apacheBin.meta_lic:gplLib.meta_lic:restricted",
+				"gplLib.meta_lic:gplLib.meta_lic:restricted",
+			},
+			expectedTargetConditions: []string{},
+		},
+		{
+			name: "fpongpldynamic",
+			edge: annotated{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"dynamic"}},
+			expectedDepActions: []string{
+				"apacheBin.meta_lic:gplLib.meta_lic:restricted",
+				"gplLib.meta_lic:gplLib.meta_lic:restricted",
+			},
+			expectedTargetConditions: []string{},
+		},
+		{
+			name:                     "independentmodule",
+			edge:                     annotated{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+			expectedDepActions:       []string{},
+			expectedTargetConditions: []string{},
+		},
+		{
+			name: "independentmodulestatic",
+			edge: annotated{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}},
+			expectedDepActions: []string{
+				"apacheBin.meta_lic:gplWithClasspathException.meta_lic:restricted",
+				"gplWithClasspathException.meta_lic:gplWithClasspathException.meta_lic:restricted",
+			},
+			expectedTargetConditions: []string{},
+		},
+		{
+			name: "dependentmodule",
+			edge: annotated{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+			expectedDepActions: []string{
+				"dependentModule.meta_lic:gplWithClasspathException.meta_lic:restricted",
+				"gplWithClasspathException.meta_lic:gplWithClasspathException.meta_lic:restricted",
+			},
+			expectedTargetConditions: []string{},
+		},
+
+		{
+			name:                     "lgplonfp",
+			edge:                     annotated{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			expectedDepActions:       []string{"apacheLib.meta_lic:apacheLib.meta_lic:notice"},
+			expectedTargetConditions: []string{"lgplBin.meta_lic:restricted"},
+		},
+		{
+			name:                     "lgplonfpdynamic",
+			edge:                     annotated{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			expectedDepActions:       []string{},
+			expectedTargetConditions: []string{},
+		},
+		{
+			name:                     "gplonfp",
+			edge:                     annotated{"gplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			expectedDepActions:       []string{"apacheLib.meta_lic:apacheLib.meta_lic:notice"},
+			expectedTargetConditions: []string{"gplBin.meta_lic:restricted"},
+		},
+		{
+			name:                     "gplcontainer",
+			edge:                     annotated{"gplContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			treatAsAggregate:         true,
+			expectedDepActions:       []string{"apacheLib.meta_lic:apacheLib.meta_lic:notice"},
+			expectedTargetConditions: []string{"gplContainer.meta_lic:restricted"},
+		},
+		{
+			name:             "gploncontainer",
+			edge:             annotated{"apacheContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			treatAsAggregate: true,
+			otherCondition:   "gplLib.meta_lic:restricted",
+			expectedDepActions: []string{
+				"apacheContainer.meta_lic:gplLib.meta_lic:restricted",
+				"apacheLib.meta_lic:apacheLib.meta_lic:notice",
+				"apacheLib.meta_lic:gplLib.meta_lic:restricted",
+				"gplLib.meta_lic:gplLib.meta_lic:restricted",
+			},
+			expectedTargetConditions: []string{},
+		},
+		{
+			name:             "gplonbin",
+			edge:             annotated{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			treatAsAggregate: false,
+			otherCondition:   "gplLib.meta_lic:restricted",
+			expectedDepActions: []string{
+				"apacheBin.meta_lic:gplLib.meta_lic:restricted",
+				"apacheLib.meta_lic:apacheLib.meta_lic:notice",
+				"apacheLib.meta_lic:gplLib.meta_lic:restricted",
+				"gplLib.meta_lic:gplLib.meta_lic:restricted",
+			},
+			expectedTargetConditions: []string{"gplLib.meta_lic:restricted"},
+		},
+		{
+			name:                     "gplonfpdynamic",
+			edge:                     annotated{"gplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			expectedDepActions:       []string{},
+			expectedTargetConditions: []string{"gplBin.meta_lic:restricted"},
+		},
+		{
+			name:                     "independentmodulereverse",
+			edge:                     annotated{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"dynamic"}},
+			expectedDepActions:       []string{},
+			expectedTargetConditions: []string{},
+		},
+		{
+			name:                     "independentmodulereversestatic",
+			edge:                     annotated{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+			expectedDepActions:       []string{"apacheBin.meta_lic:apacheBin.meta_lic:notice"},
+			expectedTargetConditions: []string{"gplWithClasspathException.meta_lic:restricted"},
+		},
+		{
+			name:                     "dependentmodulereverse",
+			edge:                     annotated{"gplWithClasspathException.meta_lic", "dependentModule.meta_lic", []string{"dynamic"}},
+			expectedDepActions:       []string{},
+			expectedTargetConditions: []string{"gplWithClasspathException.meta_lic:restricted"},
+		},
+		{
+			name: "ponr",
+			edge: annotated{"proprietary.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			expectedDepActions: []string{
+				"proprietary.meta_lic:gplLib.meta_lic:restricted",
+				"gplLib.meta_lic:gplLib.meta_lic:restricted",
+			},
+			expectedTargetConditions: []string{},
+		},
+		{
+			name:                     "ronp",
+			edge:                     annotated{"gplBin.meta_lic", "proprietary.meta_lic", []string{"static"}},
+			expectedDepActions:       []string{"proprietary.meta_lic:proprietary.meta_lic:proprietary"},
+			expectedTargetConditions: []string{"gplBin.meta_lic:restricted"},
+		},
+		{
+			name:                     "noticeonb_e_o",
+			edge:                     annotated{"mitBin.meta_lic", "by_exception.meta_lic", []string{"static"}},
+			expectedDepActions:       []string{"by_exception.meta_lic:by_exception.meta_lic:by_exception_only"},
+			expectedTargetConditions: []string{},
+		},
+		{
+			name:                     "b_e_oonnotice",
+			edge:                     annotated{"by_exception.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			expectedDepActions:       []string{"mitLib.meta_lic:mitLib.meta_lic:notice"},
+			expectedTargetConditions: []string{},
+		},
+		{
+			name:                     "noticeonrecip",
+			edge:                     annotated{"mitBin.meta_lic", "mplLib.meta_lic", []string{"static"}},
+			expectedDepActions:       []string{"mplLib.meta_lic:mplLib.meta_lic:reciprocal"},
+			expectedTargetConditions: []string{},
+		},
+		{
+			name:                     "reciponnotice",
+			edge:                     annotated{"mplBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			expectedDepActions:       []string{"mitLib.meta_lic:mitLib.meta_lic:notice"},
+			expectedTargetConditions: []string{},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			fs := make(testFS)
+			stderr := &bytes.Buffer{}
+			target := meta[tt.edge.target] + fmt.Sprintf("deps: {\n  file: \"%s\"\n", tt.edge.dep)
+			for _, ann := range tt.edge.annotations {
+				target += fmt.Sprintf("  annotations: \"%s\"\n", ann)
+			}
+			fs[tt.edge.target] = []byte(target + "}\n")
+			fs[tt.edge.dep] = []byte(meta[tt.edge.dep])
+			lg, err := ReadLicenseGraph(&fs, stderr, []string{tt.edge.target})
+			if err != nil {
+				t.Errorf("unexpected error reading graph: %w", err)
+				return
+			}
+			// simulate a condition inherited from another edge/dependency.
+			otherTarget := ""
+			otherCondition := ""
+			if len(tt.otherCondition) > 0 {
+				fields := strings.Split(tt.otherCondition, ":")
+				otherTarget = fields[0]
+				otherCondition = fields[1]
+				// other target must exist in graph
+				lg.targets[otherTarget] = &TargetNode{name: otherTarget}
+				lg.targets[otherTarget].proto.LicenseConditions = append(lg.targets[otherTarget].proto.LicenseConditions, otherCondition)
+			}
+			if tt.expectedDepActions != nil {
+				depActions := make(actionSet)
+				depActions[lg.targets[tt.edge.dep]] = lg.targets[tt.edge.dep].LicenseConditions()
+				if otherTarget != "" {
+					// simulate a sub-dependency's condition having already propagated up to dep and about to go to target
+					otherCs := lg.targets[otherTarget].LicenseConditions()
+					depActions[lg.targets[tt.edge.dep]].AddSet(otherCs)
+					depActions[lg.targets[otherTarget]] = otherCs
+				}
+				asActual := depActionsApplicableToTarget(lg.Edges()[0], depActions, tt.treatAsAggregate)
+				asExpected := make(actionSet)
+				for _, triple := range tt.expectedDepActions {
+					fields := strings.Split(triple, ":")
+					actsOn := lg.targets[fields[0]]
+					origin := lg.targets[fields[1]]
+					expectedConditions := newLicenseConditionSet()
+					expectedConditions.add(origin, fields[2:]...)
+					if _, ok := asExpected[actsOn]; ok {
+						asExpected[actsOn].AddSet(expectedConditions)
+					} else {
+						asExpected[actsOn] = expectedConditions
+					}
+				}
+
+				checkSameActions(lg, asActual, asExpected, t)
+			}
+			if tt.expectedTargetConditions != nil {
+				targetConditions := lg.TargetNode(tt.edge.target).LicenseConditions()
+				if otherTarget != "" {
+					targetConditions.add(lg.targets[otherTarget], otherCondition)
+				}
+				cs := targetConditionsApplicableToDep(
+					lg.Edges()[0],
+					targetConditions,
+					tt.treatAsAggregate)
+				actual := make([]string, 0, cs.Count())
+				for _, lc := range cs.AsList() {
+					actual = append(actual, lc.asString(":"))
+				}
+				sort.Strings(actual)
+				sort.Strings(tt.expectedTargetConditions)
+				if len(actual) != len(tt.expectedTargetConditions) {
+					t.Errorf("unexpected number of target conditions: got %v with %d conditions, want %v with %d conditions",
+						actual, len(actual), tt.expectedTargetConditions, len(tt.expectedTargetConditions))
+				} else {
+					for i := 0; i < len(actual); i++ {
+						if actual[i] != tt.expectedTargetConditions[i] {
+							t.Errorf("unexpected target condition at element %d: got %q, want %q",
+								i, actual[i], tt.expectedTargetConditions[i])
+						}
+					}
+				}
+			}
+		})
+	}
+}
diff --git a/tools/compliance/policy/resolve.go b/tools/compliance/policy/resolve.go
new file mode 100644
index 0000000..9962e68
--- /dev/null
+++ b/tools/compliance/policy/resolve.go
@@ -0,0 +1,217 @@
+// Copyright 2021 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 compliance
+
+// ResolveBottomUpConditions performs a bottom-up walk of the LicenseGraph
+// propagating conditions up the graph as necessary according to the properties
+// of each edge and according to each license condition in question.
+//
+// Subsequent top-down walks of the graph will filter some resolutions and may
+// introduce new resolutions.
+//
+// e.g. if a "restricted" condition applies to a binary, it also applies to all
+// of the statically-linked libraries and the transitive closure of their static
+// dependencies; even if neither they nor the transitive closure of their
+// dependencies originate any "restricted" conditions. The bottom-up walk will
+// not resolve the library and its transitive closure, but the later top-down
+// walk will.
+func ResolveBottomUpConditions(lg *LicenseGraph) *ResolutionSet {
+
+	// short-cut if already walked and cached
+	lg.mu.Lock()
+	rs := lg.rsBU
+	lg.mu.Unlock()
+
+	if rs != nil {
+		return rs
+	}
+
+	// must be indexed for fast lookup
+	lg.indexForward()
+
+	rs = newResolutionSet()
+
+	// cmap contains an entry for every target that was previously walked as a pure aggregate only.
+	cmap := make(map[string]bool)
+
+	var walk func(f string, treatAsAggregate bool) actionSet
+
+	walk = func(f string, treatAsAggregate bool) actionSet {
+		target := lg.targets[f]
+		result := make(actionSet)
+		result[target] = newLicenseConditionSet()
+		result[target].add(target, target.proto.LicenseConditions...)
+		if preresolved, ok := rs.resolutions[target]; ok {
+			if treatAsAggregate {
+				result.addSet(preresolved)
+				return result
+			}
+			if _, asAggregate := cmap[f]; !asAggregate {
+				result.addSet(preresolved)
+				return result
+			}
+			// previously walked in a pure aggregate context,
+			// needs to walk again in non-aggregate context
+			delete(cmap, f)
+		}
+		if treatAsAggregate {
+			cmap[f] = true
+		}
+
+		// add all the conditions from all the dependencies
+		for _, edge := range lg.index[f] {
+			// walk dependency to get its conditions
+			as := walk(edge.dependency, treatAsAggregate && lg.targets[edge.dependency].IsContainer())
+
+			// turn those into the conditions that apply to the target
+			as = depActionsApplicableToTarget(TargetEdge{lg, edge}, as, treatAsAggregate)
+
+			// add them to the result
+			result.addSet(as)
+		}
+
+		// record these conditions as applicable to the target
+		rs.addConditions(target, result)
+		rs.addSelf(target, result.byName(ImpliesRestricted))
+
+		// return this up the tree
+		return result
+	}
+
+	// walk each of the roots
+	for _, r := range lg.rootFiles {
+		_ = walk(r, lg.targets[r].IsContainer())
+	}
+
+	// if not yet cached, save the result
+	lg.mu.Lock()
+	if lg.rsBU == nil {
+		lg.rsBU = rs
+	} else {
+		// if we end up with 2, release the later for garbage collection
+		rs = lg.rsBU
+	}
+	lg.mu.Unlock()
+
+	return rs
+}
+
+// ResolveTopDownCondtions performs a top-down walk of the LicenseGraph
+// resolving all reachable nodes for `condition`. Policy establishes the rules
+// for transforming and propagating resolutions down the graph.
+//
+// e.g. For current policy, none of the conditions propagate from target to
+// dependency except restricted. For restricted, the policy is to share the
+// source of any libraries linked to restricted code and to provide notice.
+func ResolveTopDownConditions(lg *LicenseGraph) *ResolutionSet {
+
+	// short-cut if already walked and cached
+	lg.mu.Lock()
+	rs := lg.rsTD
+	lg.mu.Unlock()
+
+	if rs != nil {
+		return rs
+	}
+
+	// start with the conditions propagated up the graph
+	rs = ResolveBottomUpConditions(lg)
+
+	// rmap maps 'appliesTo' targets to their applicable conditions
+	//
+	// rmap is the resulting ResolutionSet
+	rmap := make(map[*TargetNode]actionSet)
+	for attachesTo, as := range rs.resolutions {
+		rmap[attachesTo] = as.copy()
+	}
+
+	path := make([]*dependencyEdge, 0, 32)
+
+	var walk func(f string, cs *LicenseConditionSet, treatAsAggregate bool)
+
+	walk = func(f string, cs *LicenseConditionSet, treatAsAggregate bool) {
+		fnode := lg.targets[f]
+		if !cs.IsEmpty() {
+			parentsAllAggregate := true
+			for _, e := range path {
+				target := lg.targets[e.target]
+				if _, ok := rmap[target]; !ok {
+					rmap[target] = make(actionSet)
+				}
+				rmap[target].add(fnode, cs)
+				if !target.IsContainer() {
+					parentsAllAggregate = false
+					break
+				}
+			}
+			if parentsAllAggregate {
+				if _, ok := rmap[fnode]; !ok {
+					rmap[fnode] = make(actionSet)
+				}
+				rmap[fnode].add(fnode, cs)
+			}
+		}
+		// add conditions attached to `f`
+		cs = cs.Copy()
+		for _, fcs := range rs.resolutions[fnode] {
+			cs.AddSet(fcs)
+		}
+		// for each dependency
+		for _, edge := range lg.index[f] {
+			e := TargetEdge{lg, edge}
+			// dcs holds the dpendency conditions inherited from the target
+			dcs := targetConditionsApplicableToDep(e, cs, treatAsAggregate)
+			if dcs.IsEmpty() {
+				if !treatAsAggregate || (!edgeIsDerivation(e) && !edgeIsDynamicLink(e)) {
+					continue
+				}
+			}
+			path = append(path, edge)
+			// add the conditions to the dependency
+			walk(edge.dependency, dcs, treatAsAggregate && lg.targets[edge.dependency].IsContainer())
+			path = path[:len(path)-1]
+		}
+	}
+
+	// walk each of the roots
+	for _, r := range lg.rootFiles {
+		as, ok := rs.resolutions[lg.targets[r]]
+		if !ok {
+			// no conditions in root or transitive closure of dependencies
+			continue
+		}
+		if as.isEmpty() {
+			continue
+		}
+
+		path = path[:0]
+		// add the conditions to the root and its transitive closure
+		walk(r, newLicenseConditionSet(), lg.targets[r].IsContainer())
+	}
+
+	rs = &ResolutionSet{rmap}
+
+	// if not yet cached, save the result
+	lg.mu.Lock()
+	if lg.rsTD == nil {
+		lg.rsTD = rs
+	} else {
+		// if we end up with 2, release the later for garbage collection
+		rs = lg.rsTD
+	}
+	lg.mu.Unlock()
+
+	return rs
+}
diff --git a/tools/compliance/policy/resolve_test.go b/tools/compliance/policy/resolve_test.go
new file mode 100644
index 0000000..aa5bb2a
--- /dev/null
+++ b/tools/compliance/policy/resolve_test.go
@@ -0,0 +1,755 @@
+// Copyright 2021 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 compliance
+
+import (
+	"bytes"
+	"testing"
+)
+
+func TestResolveBottomUpConditions(t *testing.T) {
+	tests := []struct {
+		name                string
+		roots               []string
+		edges               []annotated
+		expectedResolutions []res
+	}{
+		{
+			name:  "firstparty",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "firstpartytool",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"toolchain"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "firstpartydeep",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "firstpartywide",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "firstpartydynamic",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "firstpartydynamicdeep",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "firstpartydynamicwide",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "restricted",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "restrictedtool",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"toolchain"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "restricteddeep",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "restrictedwide",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "restricteddynamic",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "restricteddynamicdeep",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "restricteddynamicwide",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "weakrestricted",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "weakrestrictedtool",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"toolchain"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "weakrestricteddeep",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "weakrestrictedwide",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "lgplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "weakrestricteddynamic",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "weakrestricteddynamicdeep",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "weakrestricteddynamicwide",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "classpath",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "classpathdependent",
+			roots: []string{"dependentModule.meta_lic"},
+			edges: []annotated{
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"dependentModule.meta_lic", "dependentModule.meta_lic", "dependentModule.meta_lic", "notice"},
+				{"dependentModule.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "classpathdynamic",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "classpathdependentdynamic",
+			roots: []string{"dependentModule.meta_lic"},
+			edges: []annotated{
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"dependentModule.meta_lic", "dependentModule.meta_lic", "dependentModule.meta_lic", "notice"},
+				{"dependentModule.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			stderr := &bytes.Buffer{}
+			lg, err := toGraph(stderr, tt.roots, tt.edges)
+			if err != nil {
+				t.Errorf("unexpected test data error: got %w, want no error", err)
+				return
+			}
+			expectedRs := toResolutionSet(lg, tt.expectedResolutions)
+			actualRs := ResolveBottomUpConditions(lg)
+			checkSame(actualRs, expectedRs, t)
+		})
+	}
+}
+
+func TestResolveTopDownConditions(t *testing.T) {
+	tests := []struct {
+		name                string
+		roots               []string
+		edges               []annotated
+		expectedResolutions []res
+	}{
+		{
+			name:  "firstparty",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "firstpartydynamic",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "restricted",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+				{"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "restrictedtool",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplBin.meta_lic", []string{"toolchain"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+				{"gplBin.meta_lic", "gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+				{"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "restricteddeep",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "mitBin.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "mplLib.meta_lic", []string{"static"}},
+				{"mitBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "mitBin.meta_lic", "mitBin.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "mplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "mplLib.meta_lic", "mplLib.meta_lic", "reciprocal"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "mplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "mplLib.meta_lic", "mplLib.meta_lic", "reciprocal"},
+				{"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"mitBin.meta_lic", "mitBin.meta_lic", "mitBin.meta_lic", "notice"},
+				{"mitBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+				{"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+				{"mplLib.meta_lic", "mplLib.meta_lic", "mplLib.meta_lic", "reciprocal"},
+			},
+		},
+		{
+			name:  "restrictedwide",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "restricteddynamic",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"dynamic"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "restricteddynamicdeep",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "mitBin.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"dynamic"}},
+				{"apacheBin.meta_lic", "mplLib.meta_lic", []string{"dynamic"}},
+				{"mitBin.meta_lic", "mitLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "mitBin.meta_lic", "mitBin.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "mplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "mplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"mitBin.meta_lic", "mitBin.meta_lic", "mitBin.meta_lic", "notice"},
+				{"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+				{"mplLib.meta_lic", "mplLib.meta_lic", "mplLib.meta_lic", "reciprocal"},
+			},
+		},
+		{
+			name:  "restricteddynamicwide",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "weakrestricted",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "weakrestrictedtool",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "lgplBin.meta_lic", []string{"toolchain"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+				{"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"},
+				{"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "weakrestricteddeep",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "mitLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "weakrestrictedwide",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "lgplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "weakrestricteddynamic",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "weakrestricteddynamicdeep",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "weakrestricteddynamicwide",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "classpath",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "classpathdependent",
+			roots: []string{"dependentModule.meta_lic"},
+			edges: []annotated{
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}},
+				{"dependentModule.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"dependentModule.meta_lic", "dependentModule.meta_lic", "dependentModule.meta_lic", "notice"},
+				{"dependentModule.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"dependentModule.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+				{"dependentModule.meta_lic", "mitLib.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "classpathdynamic",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "classpathdependentdynamic",
+			roots: []string{"dependentModule.meta_lic"},
+			edges: []annotated{
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+				{"dependentModule.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"dependentModule.meta_lic", "dependentModule.meta_lic", "dependentModule.meta_lic", "notice"},
+				{"dependentModule.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"dependentModule.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+				{"dependentModule.meta_lic", "mitLib.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			stderr := &bytes.Buffer{}
+			lg, err := toGraph(stderr, tt.roots, tt.edges)
+			if err != nil {
+				t.Errorf("unexpected test data error: got %w, want no error", err)
+				return
+			}
+			expectedRs := toResolutionSet(lg, tt.expectedResolutions)
+			actualRs := ResolveTopDownConditions(lg)
+			checkSame(actualRs, expectedRs, t)
+		})
+	}
+}
diff --git a/tools/compliance/policy/resolvenotices.go b/tools/compliance/policy/resolvenotices.go
new file mode 100644
index 0000000..80b5e02
--- /dev/null
+++ b/tools/compliance/policy/resolvenotices.go
@@ -0,0 +1,21 @@
+// Copyright 2021 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 compliance
+
+// ResolveNotices implements the policy for notices.
+func ResolveNotices(lg *LicenseGraph) *ResolutionSet {
+	rs := ResolveTopDownConditions(lg)
+	return WalkResolutionsForCondition(lg, rs, ImpliesNotice)
+}
diff --git a/tools/compliance/policy/resolvenotices_test.go b/tools/compliance/policy/resolvenotices_test.go
new file mode 100644
index 0000000..b428d5b
--- /dev/null
+++ b/tools/compliance/policy/resolvenotices_test.go
@@ -0,0 +1,467 @@
+// Copyright 2021 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 compliance
+
+import (
+	"bytes"
+	"testing"
+)
+
+func TestResolveNotices(t *testing.T) {
+	tests := []struct {
+		name                string
+		roots               []string
+		edges               []annotated
+		expectedResolutions []res
+	}{
+		{
+			name:  "firstparty",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "firstpartydynamic",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "firstpartydynamicshipped",
+			roots: []string{"apacheBin.meta_lic", "apacheLib.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "restricted",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "restrictedtool",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplBin.meta_lic", []string{"toolchain"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "restricteddeep",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "mitBin.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "mplLib.meta_lic", []string{"static"}},
+				{"mitBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "mitBin.meta_lic", "mitBin.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "mplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "mplLib.meta_lic", "mplLib.meta_lic", "reciprocal"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "mplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "mplLib.meta_lic", "mplLib.meta_lic", "reciprocal"},
+				{"mitBin.meta_lic", "mitBin.meta_lic", "mitBin.meta_lic", "notice"},
+				{"mitBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "restrictedwide",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "restricteddynamic",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"dynamic"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "restricteddynamicshipped",
+			roots: []string{"apacheBin.meta_lic", "mitLib.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"dynamic"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "restricteddynamicdeep",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "mitBin.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"dynamic"}},
+				{"apacheBin.meta_lic", "mplLib.meta_lic", []string{"dynamic"}},
+				{"mitBin.meta_lic", "mitLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "mitBin.meta_lic", "mitBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"mitBin.meta_lic", "mitBin.meta_lic", "mitBin.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "restricteddynamicwide",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "restricteddynamicwideshipped",
+			roots: []string{"apacheContainer.meta_lic", "gplLib.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "weakrestricted",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "weakrestrictedtool",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "lgplBin.meta_lic", []string{"toolchain"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "weakrestrictedtoolshipped",
+			roots: []string{"apacheBin.meta_lic", "lgplBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "lgplBin.meta_lic", []string{"toolchain"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+				{"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "weakrestricteddeep",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "mitLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "weakrestrictedwide",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "lgplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "weakrestricteddynamic",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "weakrestricteddynamicshipped",
+			roots: []string{"apacheBin.meta_lic", "lgplLib.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "weakrestricteddynamicdeep",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "weakrestricteddynamicdeepshipped",
+			roots: []string{"apacheContainer.meta_lic", "lgplLib.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "weakrestricteddynamicwide",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "weakrestricteddynamicwideshipped",
+			roots: []string{"apacheContainer.meta_lic", "lgplLib.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "classpath",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "classpathdependent",
+			roots: []string{"dependentModule.meta_lic"},
+			edges: []annotated{
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}},
+				{"dependentModule.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"dependentModule.meta_lic", "dependentModule.meta_lic", "dependentModule.meta_lic", "notice"},
+				{"dependentModule.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"dependentModule.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+				{"dependentModule.meta_lic", "mitLib.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "classpathdynamic",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:  "classpathdynamicshipped",
+			roots: []string{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "classpathdependentdynamic",
+			roots: []string{"dependentModule.meta_lic"},
+			edges: []annotated{
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+				{"dependentModule.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"dependentModule.meta_lic", "dependentModule.meta_lic", "dependentModule.meta_lic", "notice"},
+				{"dependentModule.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"dependentModule.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+				{"dependentModule.meta_lic", "mitLib.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "classpathdependentdynamicshipped",
+			roots: []string{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+				{"dependentModule.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"dependentModule.meta_lic", "dependentModule.meta_lic", "dependentModule.meta_lic", "notice"},
+				{"dependentModule.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"dependentModule.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+				{"dependentModule.meta_lic", "mitLib.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			stderr := &bytes.Buffer{}
+			lg, err := toGraph(stderr, tt.roots, tt.edges)
+			if err != nil {
+				t.Errorf("unexpected test data error: got %w, want no error", err)
+				return
+			}
+			expectedRs := toResolutionSet(lg, tt.expectedResolutions)
+			actualRs := ResolveNotices(lg)
+			checkSame(actualRs, expectedRs, t)
+		})
+	}
+}
diff --git a/tools/compliance/policy/resolveprivacy.go b/tools/compliance/policy/resolveprivacy.go
new file mode 100644
index 0000000..dabbc62
--- /dev/null
+++ b/tools/compliance/policy/resolveprivacy.go
@@ -0,0 +1,21 @@
+// Copyright 2021 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 compliance
+
+// ResolveSourcePrivacy implements the policy for source privacy.
+func ResolveSourcePrivacy(lg *LicenseGraph) *ResolutionSet {
+	rs := ResolveTopDownConditions(lg)
+	return WalkResolutionsForCondition(lg, rs, ImpliesPrivate)
+}
diff --git a/tools/compliance/policy/resolveprivacy_test.go b/tools/compliance/policy/resolveprivacy_test.go
new file mode 100644
index 0000000..25772bb
--- /dev/null
+++ b/tools/compliance/policy/resolveprivacy_test.go
@@ -0,0 +1,87 @@
+// Copyright 2021 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 compliance
+
+import (
+	"bytes"
+	"testing"
+)
+
+func TestResolveSourcePrivacy(t *testing.T) {
+	tests := []struct {
+		name                string
+		roots               []string
+		edges               []annotated
+		expectedResolutions []res
+	}{
+		{
+			name:  "firstparty",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{},
+		},
+		{
+			name:  "notice",
+			roots: []string{"mitBin.meta_lic"},
+			edges: []annotated{
+				{"mitBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{},
+		},
+		{
+			name:  "lgpl",
+			roots: []string{"lgplBin.meta_lic"},
+			edges: []annotated{
+				{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{},
+		},
+		{
+			name:  "proprietaryonresricted",
+			roots: []string{"proprietary.meta_lic"},
+			edges: []annotated{
+				{"proprietary.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"proprietary.meta_lic", "proprietary.meta_lic", "proprietary.meta_lic", "proprietary"},
+			},
+		},
+		{
+			name:  "restrictedonproprietary",
+			roots: []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "proprietary.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"gplBin.meta_lic", "proprietary.meta_lic", "proprietary.meta_lic", "proprietary"},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			stderr := &bytes.Buffer{}
+			lg, err := toGraph(stderr, tt.roots, tt.edges)
+			if err != nil {
+				t.Errorf("unexpected test data error: got %w, want no error", err)
+				return
+			}
+			expectedRs := toResolutionSet(lg, tt.expectedResolutions)
+			actualRs := ResolveSourcePrivacy(lg)
+			checkSame(actualRs, expectedRs, t)
+		})
+	}
+}
diff --git a/tools/compliance/policy/resolveshare.go b/tools/compliance/policy/resolveshare.go
new file mode 100644
index 0000000..24efd28
--- /dev/null
+++ b/tools/compliance/policy/resolveshare.go
@@ -0,0 +1,21 @@
+// Copyright 2021 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 compliance
+
+// ResolveSourceSharing implements the policy for source-sharing.
+func ResolveSourceSharing(lg *LicenseGraph) *ResolutionSet {
+	rs := ResolveTopDownConditions(lg)
+	return WalkResolutionsForCondition(lg, rs, ImpliesShared)
+}
diff --git a/tools/compliance/policy/resolveshare_test.go b/tools/compliance/policy/resolveshare_test.go
new file mode 100644
index 0000000..7371ccf
--- /dev/null
+++ b/tools/compliance/policy/resolveshare_test.go
@@ -0,0 +1,295 @@
+// Copyright 2021 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 compliance
+
+import (
+	"bytes"
+	"testing"
+)
+
+func TestResolveSourceSharing(t *testing.T) {
+	tests := []struct {
+		name                string
+		roots               []string
+		edges               []annotated
+		expectedResolutions []res
+	}{
+		{
+			name:  "independentmodulerestricted",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{},
+		},
+		{
+			name:  "independentmodulerestrictedshipped",
+			roots: []string{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "independentmodulestaticrestricted",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "dependentmodulerestricted",
+			roots: []string{"dependentModule.meta_lic"},
+			edges: []annotated{
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"dependentModule.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "dependentmodulerestrictedshipclasspath",
+			roots: []string{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"dependentModule.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "lgplonfprestricted",
+			roots: []string{"lgplBin.meta_lic"},
+			edges: []annotated{
+				{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"},
+				{"lgplBin.meta_lic", "apacheLib.meta_lic", "lgplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "lgplonfpdynamicrestricted",
+			roots: []string{"lgplBin.meta_lic"},
+			edges: []annotated{
+				{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "lgplonfpdynamicrestrictedshiplib",
+			roots: []string{"lgplBin.meta_lic", "apacheLib.meta_lic"},
+			edges: []annotated{
+				{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "gplonfprestricted",
+			roots: []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"gplBin.meta_lic", "gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+				{"gplBin.meta_lic", "apacheLib.meta_lic", "gplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "gplcontainerrestricted",
+			roots: []string{"gplContainer.meta_lic"},
+			edges: []annotated{
+				{"gplContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"gplContainer.meta_lic", "gplContainer.meta_lic", "gplContainer.meta_lic", "restricted"},
+				{"gplContainer.meta_lic", "apacheLib.meta_lic", "gplContainer.meta_lic", "restricted"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "gplContainer.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "gploncontainerrestricted",
+			roots: []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "gplonbinrestricted",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "gplonfpdynamicrestricted",
+			roots: []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"gplBin.meta_lic", "gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "gplonfpdynamicrestrictedshiplib",
+			roots: []string{"gplBin.meta_lic", "apacheLib.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"gplBin.meta_lic", "gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+				{"gplBin.meta_lic", "apacheLib.meta_lic", "gplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "independentmodulereverserestricted",
+			roots: []string{"gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "independentmodulereversestaticrestricted",
+			roots: []string{"gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "dependentmodulereverserestricted",
+			roots: []string{"gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "dependentModule.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "dependentmodulereverserestrictedshipdependent",
+			roots: []string{"gplWithClasspathException.meta_lic", "dependentModule.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "dependentModule.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"gplWithClasspathException.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "ponrrestricted",
+			roots: []string{"proprietary.meta_lic"},
+			edges: []annotated{
+				{"proprietary.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"proprietary.meta_lic", "proprietary.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"proprietary.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "ronprestricted",
+			roots: []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "proprietary.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"gplBin.meta_lic", "gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+				{"gplBin.meta_lic", "proprietary.meta_lic", "gplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:  "noticeonb_e_orestricted",
+			roots: []string{"mitBin.meta_lic"},
+			edges: []annotated{
+				{"mitBin.meta_lic", "by_exception.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{},
+		},
+		{
+			name:  "b_e_oonnoticerestricted",
+			roots: []string{"by_exception.meta_lic"},
+			edges: []annotated{
+				{"by_exception.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{},
+		},
+		{
+			name:  "noticeonreciprecip",
+			roots: []string{"mitBin.meta_lic"},
+			edges: []annotated{
+				{"mitBin.meta_lic", "mplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"mitBin.meta_lic", "mplLib.meta_lic", "mplLib.meta_lic", "reciprocal"},
+			},
+		},
+		{
+			name:  "reciponnoticerecip",
+			roots: []string{"mplBin.meta_lic"},
+			edges: []annotated{
+				{"mplBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"mplBin.meta_lic", "mplBin.meta_lic", "mplBin.meta_lic", "reciprocal"},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			stderr := &bytes.Buffer{}
+			lg, err := toGraph(stderr, tt.roots, tt.edges)
+			if err != nil {
+				t.Errorf("unexpected test data error: got %w, want no error", err)
+				return
+			}
+			expectedRs := toResolutionSet(lg, tt.expectedResolutions)
+			actualRs := ResolveSourceSharing(lg)
+			checkSame(actualRs, expectedRs, t)
+		})
+	}
+}
diff --git a/tools/compliance/policy/shareprivacyconflicts.go b/tools/compliance/policy/shareprivacyconflicts.go
new file mode 100644
index 0000000..dabdff5
--- /dev/null
+++ b/tools/compliance/policy/shareprivacyconflicts.go
@@ -0,0 +1,91 @@
+// Copyright 2021 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 compliance
+
+import (
+	"fmt"
+)
+
+// SourceSharePrivacyConflict describes an individual conflict between a source-sharing
+// condition and a source privacy condition
+type SourceSharePrivacyConflict struct {
+	SourceNode       *TargetNode
+	ShareCondition   LicenseCondition
+	PrivacyCondition LicenseCondition
+}
+
+// Error returns a string describing the conflict.
+func (conflict SourceSharePrivacyConflict) Error() string {
+	return fmt.Sprintf("%s %s from %s and must share from %s %s\n",
+		conflict.SourceNode.name,
+		conflict.PrivacyCondition.name, conflict.PrivacyCondition.origin.name,
+		conflict.ShareCondition.name, conflict.ShareCondition.origin.name)
+}
+
+// IsEqualTo returns true when `conflict` and `other` describe the same conflict.
+func (conflict SourceSharePrivacyConflict) IsEqualTo(other SourceSharePrivacyConflict) bool {
+	return conflict.SourceNode.name == other.SourceNode.name &&
+		conflict.ShareCondition.name == other.ShareCondition.name &&
+		conflict.ShareCondition.origin.name == other.ShareCondition.origin.name &&
+		conflict.PrivacyCondition.name == other.PrivacyCondition.name &&
+		conflict.PrivacyCondition.origin.name == other.PrivacyCondition.origin.name
+}
+
+// ConflictingSharedPrivateSource lists all of the targets where conflicting conditions to
+// share the source and to keep the source private apply to the target.
+func ConflictingSharedPrivateSource(lg *LicenseGraph) []SourceSharePrivacyConflict {
+	// shareSource is the set of all source-sharing resolutions.
+	shareSource := ResolveSourceSharing(lg)
+	if shareSource.IsEmpty() {
+		return []SourceSharePrivacyConflict{}
+	}
+
+	// privateSource is the set of all source privacy resolutions.
+	privateSource := ResolveSourcePrivacy(lg)
+	if privateSource.IsEmpty() {
+		return []SourceSharePrivacyConflict{}
+	}
+
+	// combined is the combination of source-sharing and source privacy.
+	combined := JoinResolutionSets(shareSource, privateSource)
+
+	// size is the size of the result
+	size := 0
+	for _, actsOn := range combined.ActsOn() {
+		rl := combined.ResolutionsByActsOn(actsOn)
+		size += rl.CountConditionsByName(ImpliesShared) * rl.CountConditionsByName(ImpliesPrivate)
+	}
+	if size == 0 {
+		return []SourceSharePrivacyConflict{}
+	}
+	result := make([]SourceSharePrivacyConflict, 0, size)
+	for _, actsOn := range combined.ActsOn() {
+		rl := combined.ResolutionsByActsOn(actsOn)
+		if len(rl) == 0 {
+			continue
+		}
+
+		pconditions := rl.ByName(ImpliesPrivate).AllConditions().AsList()
+		ssconditions := rl.ByName(ImpliesShared).AllConditions().AsList()
+
+		// report all conflicting condition combinations
+		for _, p := range pconditions {
+			for _, ss := range ssconditions {
+				result = append(result, SourceSharePrivacyConflict{actsOn, ss, p})
+			}
+		}
+	}
+	return result
+}
diff --git a/tools/compliance/policy/shareprivacyconflicts_test.go b/tools/compliance/policy/shareprivacyconflicts_test.go
new file mode 100644
index 0000000..162c1fe
--- /dev/null
+++ b/tools/compliance/policy/shareprivacyconflicts_test.go
@@ -0,0 +1,129 @@
+// Copyright 2021 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 compliance
+
+import (
+	"bytes"
+	"sort"
+	"testing"
+)
+
+// byConflict orders conflicts by target then share then privacy
+type byConflict []SourceSharePrivacyConflict
+
+// Len returns the count of elements in the slice.
+func (l byConflict) Len() int      { return len(l) }
+
+// Swap rearranged 2 elements so that each occupies the other's former
+// position.
+func (l byConflict) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
+
+// Less returns true when the `i`th element is lexicographically less than
+// the `j`th element.
+func (l byConflict) Less(i, j int) bool {
+	if l[i].SourceNode.name == l[j].SourceNode.name {
+		if l[i].ShareCondition.origin.name == l[j].ShareCondition.origin.name {
+			if l[i].ShareCondition.name == l[j].ShareCondition.name {
+				if l[i].PrivacyCondition.origin.name == l[j].PrivacyCondition.origin.name {
+					return l[i].PrivacyCondition.name < l[j].PrivacyCondition.name
+				}
+				return l[i].PrivacyCondition.origin.name < l[j].PrivacyCondition.origin.name
+			}
+			return l[i].ShareCondition.name < l[j].ShareCondition.name
+		}
+		return l[i].ShareCondition.origin.name < l[j].ShareCondition.origin.name
+	}
+	return l[i].SourceNode.name < l[j].SourceNode.name
+}
+
+func TestConflictingSharedPrivateSource(t *testing.T) {
+	tests := []struct {
+		name              string
+		roots             []string
+		edges             []annotated
+		expectedConflicts []confl
+	}{
+		{
+			name:  "firstparty",
+			roots: []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedConflicts: []confl{},
+		},
+		{
+			name:  "notice",
+			roots: []string{"mitBin.meta_lic"},
+			edges: []annotated{
+				{"mitBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedConflicts: []confl{},
+		},
+		{
+			name:  "lgpl",
+			roots: []string{"lgplBin.meta_lic"},
+			edges: []annotated{
+				{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedConflicts: []confl{},
+		},
+		{
+			name:  "proprietaryonrestricted",
+			roots: []string{"proprietary.meta_lic"},
+			edges: []annotated{
+				{"proprietary.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedConflicts: []confl{
+				{"proprietary.meta_lic", "gplLib.meta_lic:restricted", "proprietary.meta_lic:proprietary"},
+			},
+		},
+		{
+			name:  "restrictedonproprietary",
+			roots: []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "proprietary.meta_lic", []string{"static"}},
+			},
+			expectedConflicts: []confl{
+				{"proprietary.meta_lic", "gplBin.meta_lic:restricted", "proprietary.meta_lic:proprietary"},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			stderr := &bytes.Buffer{}
+			lg, err := toGraph(stderr, tt.roots, tt.edges)
+			if err != nil {
+				t.Errorf("unexpected test data error: got %w, want no error", err)
+				return
+			}
+			expectedConflicts := toConflictList(lg, tt.expectedConflicts)
+			actualConflicts := ConflictingSharedPrivateSource(lg)
+			sort.Sort(byConflict(expectedConflicts))
+			sort.Sort(byConflict(actualConflicts))
+			if len(expectedConflicts) != len(actualConflicts) {
+				t.Errorf("unexpected number of share/privacy conflicts: got %v with %d conflicts, want %v with %d conflicts",
+					actualConflicts, len(actualConflicts), expectedConflicts, len(expectedConflicts))
+			} else {
+				for i := 0; i < len(actualConflicts); i++ {
+					if !actualConflicts[i].IsEqualTo(expectedConflicts[i]) {
+						t.Errorf("unexpected share/privacy conflict at element %d: got %q, want %q",
+							i, actualConflicts[i].Error(), expectedConflicts[i].Error())
+					}
+				}
+			}
+
+		})
+	}
+}
diff --git a/tools/compliance/policy/shipped.go b/tools/compliance/policy/shipped.go
new file mode 100644
index 0000000..74eb343
--- /dev/null
+++ b/tools/compliance/policy/shipped.go
@@ -0,0 +1,54 @@
+// Copyright 2021 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 compliance
+
+// ShippedNodes returns the set of nodes in a license graph where the target or
+// a derivative work gets distributed. (caches result)
+func ShippedNodes(lg *LicenseGraph) *TargetNodeSet {
+	lg.mu.Lock()
+	shipped := lg.shippedNodes
+	lg.mu.Unlock()
+	if shipped != nil {
+		return shipped
+	}
+
+	tset := make(map[*TargetNode]bool)
+
+	WalkTopDown(lg, func(lg *LicenseGraph, tn *TargetNode, path TargetEdgePath) bool {
+		if _, alreadyWalked := tset[tn]; alreadyWalked {
+			return false
+		}
+		if len(path) > 0 {
+			if !edgeIsDerivation(path[len(path)-1]) {
+				return false
+			}
+		}
+		tset[tn] = true
+		return true
+	})
+
+	shipped = &TargetNodeSet{tset}
+
+	lg.mu.Lock()
+	if lg.shippedNodes == nil {
+		lg.shippedNodes = shipped
+	} else {
+		// if we end up with 2, release the later for garbage collection.
+		shipped = lg.shippedNodes
+	}
+	lg.mu.Unlock()
+
+	return shipped
+}
diff --git a/tools/compliance/policy/shipped_test.go b/tools/compliance/policy/shipped_test.go
new file mode 100644
index 0000000..53a8469
--- /dev/null
+++ b/tools/compliance/policy/shipped_test.go
@@ -0,0 +1,130 @@
+// Copyright 2021 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 compliance
+
+import (
+	"bytes"
+	"sort"
+	"testing"
+)
+
+func TestShippedNodes(t *testing.T) {
+	tests := []struct {
+		name          string
+		roots         []string
+		edges         []annotated
+		expectedNodes []string
+	}{
+		{
+			name:      "singleton",
+			roots:     []string{"apacheLib.meta_lic"},
+			edges: []annotated{},
+			expectedNodes: []string{"apacheLib.meta_lic"},
+		},
+		{
+			name:      "simplebinary",
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedNodes: []string{"apacheBin.meta_lic", "apacheLib.meta_lic"},
+		},
+		{
+			name:      "simpledynamic",
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedNodes: []string{"apacheBin.meta_lic"},
+		},
+		{
+			name:      "container",
+			roots:     []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedNodes: []string{
+				"apacheContainer.meta_lic",
+				"apacheLib.meta_lic",
+				"gplLib.meta_lic",
+			},
+		},
+		{
+			name:      "binary",
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedNodes: []string{
+				"apacheBin.meta_lic",
+				"apacheLib.meta_lic",
+				"gplLib.meta_lic",
+			},
+		},
+		{
+			name:      "binarydynamic",
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedNodes: []string{
+				"apacheBin.meta_lic",
+				"apacheLib.meta_lic",
+			},
+		},
+		{
+			name:      "containerdeep",
+			roots:     []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+				{"apacheLib.meta_lic", "gplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedNodes: []string{
+				"apacheContainer.meta_lic",
+				"apacheBin.meta_lic",
+				"apacheLib.meta_lic",
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			stderr := &bytes.Buffer{}
+			lg, err := toGraph(stderr, tt.roots, tt.edges)
+			if err != nil {
+				t.Errorf("unexpected test data error: got %w, want no error", err)
+				return
+			}
+			expectedNodes := append([]string{}, tt.expectedNodes...)
+			actualNodes := ShippedNodes(lg).Names()
+			sort.Strings(expectedNodes)
+			sort.Strings(actualNodes)
+                        if len(expectedNodes) != len(actualNodes) {
+				t.Errorf("unexpected number of shipped nodes: got %v with %d nodes, want %v with %d nodes",
+					actualNodes, len(actualNodes), expectedNodes, len(expectedNodes))
+				return
+			}
+			for i := 0; i < len(actualNodes); i++ {
+				if expectedNodes[i] != actualNodes[i] {
+					t.Errorf("unexpected node at index %d: got %q in %v, want %q in %v",
+						i, actualNodes[i], actualNodes, expectedNodes[i], expectedNodes)
+				}
+			}
+		})
+	}
+}
diff --git a/tools/compliance/policy/walk.go b/tools/compliance/policy/walk.go
new file mode 100644
index 0000000..8b6737d
--- /dev/null
+++ b/tools/compliance/policy/walk.go
@@ -0,0 +1,76 @@
+// Copyright 2021 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 compliance
+
+// VisitNode is called for each root and for each walked dependency node by
+// WalkTopDown. When VisitNode returns true, WalkTopDown will proceed to walk
+// down the dependences of the node
+type VisitNode func(*LicenseGraph, *TargetNode, TargetEdgePath) bool
+
+// WalkTopDown does a top-down walk of `lg` calling `visit` and descending
+// into depenencies when `visit` returns true.
+func WalkTopDown(lg *LicenseGraph, visit VisitNode) {
+	path := NewTargetEdgePath(32)
+
+	// must be indexed for fast lookup
+	lg.indexForward()
+
+	var walk func(f string)
+	walk = func(f string) {
+		visitChildren := visit(lg, lg.targets[f], *path)
+		if !visitChildren {
+			return
+		}
+		for _, edge := range lg.index[f] {
+			path.Push(TargetEdge{lg, edge})
+			walk(edge.dependency)
+			path.Pop()
+		}
+	}
+
+	for _, r := range lg.rootFiles {
+		path.Clear()
+		walk(r)
+	}
+}
+
+// WalkResolutionsForCondition performs a top-down walk of the LicenseGraph
+// resolving all distributed works for condition `names`.
+func WalkResolutionsForCondition(lg *LicenseGraph, rs *ResolutionSet, names ConditionNames) *ResolutionSet {
+	shipped := ShippedNodes(lg)
+
+	// rmap maps 'attachesTo' targets to the `actsOn` targets and applicable conditions
+	//
+	// rmap is the resulting ResolutionSet
+	rmap := make(map[*TargetNode]actionSet)
+
+	WalkTopDown(lg, func(lg *LicenseGraph, tn *TargetNode, _ TargetEdgePath) bool {
+		if _, ok := rmap[tn]; ok {
+			return false
+		}
+		if !shipped.Contains(tn) {
+			return false
+		}
+		if as, ok := rs.resolutions[tn]; ok {
+			fas := as.byActsOn(shipped).byName(names)
+			if !fas.isEmpty() {
+				rmap[tn] = fas
+			}
+		}
+		return tn.IsContainer() // descend into containers
+	})
+
+	return &ResolutionSet{rmap}
+}
diff --git a/tools/compliance/policy/walk_test.go b/tools/compliance/policy/walk_test.go
new file mode 100644
index 0000000..2eef702
--- /dev/null
+++ b/tools/compliance/policy/walk_test.go
@@ -0,0 +1,629 @@
+// Copyright 2021 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 compliance
+
+import (
+	"bytes"
+	"testing"
+)
+
+func TestWalkResolutionsForCondition(t *testing.T) {
+	tests := []struct {
+		name                string
+		condition           ConditionNames
+		roots               []string
+		edges               []annotated
+		expectedResolutions []res
+	}{
+		{
+			name:      "firstparty",
+			condition: ImpliesNotice,
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:      "notice",
+			condition: ImpliesNotice,
+			roots:     []string{"mitBin.meta_lic"},
+			edges: []annotated{
+				{"mitBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"mitBin.meta_lic", "mitBin.meta_lic", "mitBin.meta_lic", "notice"},
+				{"mitBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:      "fponlgplnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "fponlgpldynamicnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+			},
+		},
+		{
+			name:      "independentmodulenotice",
+			condition: ImpliesNotice,
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+			},
+		},
+		{
+			name:      "independentmodulerestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{},
+		},
+		{
+			name:      "independentmodulestaticnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "independentmodulestaticrestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "dependentmodulenotice",
+			condition: ImpliesNotice,
+			roots:     []string{"dependentModule.meta_lic"},
+			edges: []annotated{
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"dependentModule.meta_lic", "dependentModule.meta_lic", "dependentModule.meta_lic", "notice"},
+				{"dependentModule.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "dependentmodulerestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"dependentModule.meta_lic"},
+			edges: []annotated{
+				{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"dependentModule.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "lgplonfpnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"lgplBin.meta_lic"},
+			edges: []annotated{
+				{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"},
+				{"lgplBin.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+				{"lgplBin.meta_lic", "apacheLib.meta_lic", "lgplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "lgplonfprestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"lgplBin.meta_lic"},
+			edges: []annotated{
+				{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"},
+				{"lgplBin.meta_lic", "apacheLib.meta_lic", "lgplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "lgplonfpdynamicnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"lgplBin.meta_lic"},
+			edges: []annotated{
+				{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "lgplonfpdynamicrestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"lgplBin.meta_lic"},
+			edges: []annotated{
+				{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gplonfpnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"gplBin.meta_lic", "gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+				{"gplBin.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+				{"gplBin.meta_lic", "apacheLib.meta_lic", "gplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gplonfprestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"gplBin.meta_lic", "gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+				{"gplBin.meta_lic", "apacheLib.meta_lic", "gplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gplcontainernotice",
+			condition: ImpliesNotice,
+			roots:     []string{"gplContainer.meta_lic"},
+			edges: []annotated{
+				{"gplContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"gplContainer.meta_lic", "gplContainer.meta_lic", "gplContainer.meta_lic", "restricted"},
+				{"gplContainer.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+				{"gplContainer.meta_lic", "apacheLib.meta_lic", "gplContainer.meta_lic", "restricted"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "gplContainer.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gplcontainerrestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplContainer.meta_lic"},
+			edges: []annotated{
+				{"gplContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"gplContainer.meta_lic", "gplContainer.meta_lic", "gplContainer.meta_lic", "restricted"},
+				{"gplContainer.meta_lic", "apacheLib.meta_lic", "gplContainer.meta_lic", "restricted"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "gplContainer.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gploncontainernotice",
+			condition: ImpliesNotice,
+			roots:     []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheLib.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+				{"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gploncontainerrestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"apacheContainer.meta_lic"},
+			edges: []annotated{
+				{"apacheContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gplonbinnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gplonbinrestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"apacheBin.meta_lic"},
+			edges: []annotated{
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "apacheLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gplonfpdynamicnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"gplBin.meta_lic", "gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gplonfpdynamicrestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"gplBin.meta_lic", "gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "gplonfpdynamicrestrictedshipped",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplBin.meta_lic", "apacheLib.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"gplBin.meta_lic", "gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+				{"gplBin.meta_lic", "apacheLib.meta_lic", "gplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "independentmodulereversenotice",
+			condition: ImpliesNotice,
+			roots:     []string{"gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "independentmodulereverserestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "independentmodulereverserestrictedshipped",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "independentmodulereversestaticnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+				{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "independentmodulereversestaticrestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "dependentmodulereversenotice",
+			condition: ImpliesNotice,
+			roots:     []string{"gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "dependentModule.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "dependentmodulereverserestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplWithClasspathException.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "dependentModule.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "dependentmodulereverserestrictedshipped",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplWithClasspathException.meta_lic", "dependentModule.meta_lic"},
+			edges: []annotated{
+				{"gplWithClasspathException.meta_lic", "dependentModule.meta_lic", []string{"dynamic"}},
+			},
+			expectedResolutions: []res{
+				{"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+				{"gplWithClasspathException.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "ponrnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"proprietary.meta_lic"},
+			edges: []annotated{
+				{"proprietary.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"proprietary.meta_lic", "proprietary.meta_lic", "proprietary.meta_lic", "proprietary"},
+				{"proprietary.meta_lic", "proprietary.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"proprietary.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "ponrrestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"proprietary.meta_lic"},
+			edges: []annotated{
+				{"proprietary.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"proprietary.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+				{"proprietary.meta_lic", "proprietary.meta_lic", "gplLib.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "ponrproprietary",
+			condition: ImpliesProprietary,
+			roots:     []string{"proprietary.meta_lic"},
+			edges: []annotated{
+				{"proprietary.meta_lic", "gplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"proprietary.meta_lic", "proprietary.meta_lic", "proprietary.meta_lic", "proprietary"},
+			},
+		},
+		{
+			name:      "ronpnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "proprietary.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"gplBin.meta_lic", "gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+				{"gplBin.meta_lic", "proprietary.meta_lic", "proprietary.meta_lic", "proprietary"},
+				{"gplBin.meta_lic", "proprietary.meta_lic", "gplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "ronprestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "proprietary.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"gplBin.meta_lic", "gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+				{"gplBin.meta_lic", "proprietary.meta_lic", "gplBin.meta_lic", "restricted"},
+			},
+		},
+		{
+			name:      "ronpproprietary",
+			condition: ImpliesProprietary,
+			roots:     []string{"gplBin.meta_lic"},
+			edges: []annotated{
+				{"gplBin.meta_lic", "proprietary.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"gplBin.meta_lic", "proprietary.meta_lic", "proprietary.meta_lic", "proprietary"},
+			},
+		},
+		{
+			name:      "noticeonb_e_onotice",
+			condition: ImpliesNotice,
+			roots:     []string{"mitBin.meta_lic"},
+			edges: []annotated{
+				{"mitBin.meta_lic", "by_exception.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"mitBin.meta_lic", "mitBin.meta_lic", "mitBin.meta_lic", "notice"},
+				{"mitBin.meta_lic", "by_exception.meta_lic", "by_exception.meta_lic", "by_exception_only"},
+			},
+		},
+		{
+			name:      "noticeonb_e_orestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"mitBin.meta_lic"},
+			edges: []annotated{
+				{"mitBin.meta_lic", "by_exception.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{},
+		},
+		{
+			name:      "noticeonb_e_ob_e_o",
+			condition: ImpliesByExceptionOnly,
+			roots:     []string{"mitBin.meta_lic"},
+			edges: []annotated{
+				{"mitBin.meta_lic", "by_exception.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"mitBin.meta_lic", "by_exception.meta_lic", "by_exception.meta_lic", "by_exception_only"},
+			},
+		},
+		{
+			name:      "b_e_oonnoticenotice",
+			condition: ImpliesNotice,
+			roots:     []string{"by_exception.meta_lic"},
+			edges: []annotated{
+				{"by_exception.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"by_exception.meta_lic", "by_exception.meta_lic", "by_exception.meta_lic", "by_exception_only"},
+				{"by_exception.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:      "b_e_oonnoticerestricted",
+			condition: ImpliesRestricted,
+			roots:     []string{"by_exception.meta_lic"},
+			edges: []annotated{
+				{"by_exception.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{},
+		},
+		{
+			name:      "b_e_oonnoticeb_e_o",
+			condition: ImpliesByExceptionOnly,
+			roots:     []string{"by_exception.meta_lic"},
+			edges: []annotated{
+				{"by_exception.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"by_exception.meta_lic", "by_exception.meta_lic", "by_exception.meta_lic", "by_exception_only"},
+			},
+		},
+		{
+			name:      "noticeonrecipnotice",
+			condition: ImpliesNotice,
+			roots:     []string{"mitBin.meta_lic"},
+			edges: []annotated{
+				{"mitBin.meta_lic", "mplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"mitBin.meta_lic", "mitBin.meta_lic", "mitBin.meta_lic", "notice"},
+				{"mitBin.meta_lic", "mplLib.meta_lic", "mplLib.meta_lic", "reciprocal"},
+			},
+		},
+		{
+			name:      "noticeonreciprecip",
+			condition: ImpliesReciprocal,
+			roots:     []string{"mitBin.meta_lic"},
+			edges: []annotated{
+				{"mitBin.meta_lic", "mplLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"mitBin.meta_lic", "mplLib.meta_lic", "mplLib.meta_lic", "reciprocal"},
+			},
+		},
+		{
+			name:      "reciponnoticenotice",
+			condition: ImpliesNotice,
+			roots:     []string{"mplBin.meta_lic"},
+			edges: []annotated{
+				{"mplBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"mplBin.meta_lic", "mplBin.meta_lic", "mplBin.meta_lic", "reciprocal"},
+				{"mplBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"},
+			},
+		},
+		{
+			name:      "reciponnoticerecip",
+			condition: ImpliesReciprocal,
+			roots:     []string{"mplBin.meta_lic"},
+			edges: []annotated{
+				{"mplBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+			},
+			expectedResolutions: []res{
+				{"mplBin.meta_lic", "mplBin.meta_lic", "mplBin.meta_lic", "reciprocal"},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			stderr := &bytes.Buffer{}
+			lg, err := toGraph(stderr, tt.roots, tt.edges)
+			if err != nil {
+				t.Errorf("unexpected test data error: got %w, want no error", err)
+				return
+			}
+			expectedRs := toResolutionSet(lg, tt.expectedResolutions)
+			actualRs := WalkResolutionsForCondition(lg, ResolveTopDownConditions(lg), tt.condition)
+			checkSame(actualRs, expectedRs, t)
+		})
+	}
+}
diff --git a/tools/compliance/readgraph.go b/tools/compliance/readgraph.go
new file mode 100644
index 0000000..0b5ebfe
--- /dev/null
+++ b/tools/compliance/readgraph.go
@@ -0,0 +1,259 @@
+// Copyright 2021 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 compliance
+
+import (
+	"fmt"
+	"io"
+	"io/fs"
+	"strings"
+	"sync"
+
+	"android/soong/compliance/license_metadata_proto"
+
+	"google.golang.org/protobuf/encoding/prototext"
+)
+
+var (
+	// ConcurrentReaders is the size of the task pool for limiting resource usage e.g. open files.
+	ConcurrentReaders = 5
+)
+
+// result describes the outcome of reading and parsing a single license metadata file.
+type result struct {
+	// file identifies the path to the license metadata file
+	file string
+
+	// target contains the parsed metadata or nil if an error
+	target *TargetNode
+
+	// edges contains the parsed dependencies
+	edges []*dependencyEdge
+
+	// err is nil unless an error occurs
+	err error
+}
+
+// receiver coordinates the tasks for reading and parsing license metadata files.
+type receiver struct {
+	// lg accumulates the read metadata and becomes the final resulting LicensGraph.
+	lg *LicenseGraph
+
+	// rootFS locates the root of the file system from which to read the files.
+	rootFS fs.FS
+
+	// stderr identifies the error output writer.
+	stderr io.Writer
+
+	// task provides a fixed-size task pool to limit concurrent open files etc.
+	task chan bool
+
+	// results returns one license metadata file result at a time.
+	results chan *result
+
+	// wg detects when done
+	wg sync.WaitGroup
+}
+
+// ReadLicenseGraph reads and parses `files` and their dependencies into a LicenseGraph.
+//
+// `files` become the root files of the graph for top-down walks of the graph.
+func ReadLicenseGraph(rootFS fs.FS, stderr io.Writer, files []string) (*LicenseGraph, error) {
+	if len(files) == 0 {
+		return nil, fmt.Errorf("no license metadata to analyze")
+	}
+	if ConcurrentReaders < 1 {
+		return nil, fmt.Errorf("need at least one task in pool")
+	}
+
+	lg := newLicenseGraph()
+	for _, f := range files {
+		if strings.HasSuffix(f, ".meta_lic") {
+			lg.rootFiles = append(lg.rootFiles, f)
+		} else {
+			lg.rootFiles = append(lg.rootFiles, f+".meta_lic")
+		}
+	}
+
+	recv := &receiver{
+		lg:      lg,
+		rootFS:  rootFS,
+		stderr:  stderr,
+		task:    make(chan bool, ConcurrentReaders),
+		results: make(chan *result, ConcurrentReaders),
+		wg:      sync.WaitGroup{},
+	}
+	for i := 0; i < ConcurrentReaders; i++ {
+		recv.task <- true
+	}
+
+	readFiles := func() {
+		lg.mu.Lock()
+		// identify the metadata files to schedule reading tasks for
+		for _, f := range lg.rootFiles {
+			lg.targets[f] = nil
+		}
+		lg.mu.Unlock()
+
+		// schedule tasks to read the files
+		for _, f := range lg.rootFiles {
+			readFile(recv, f)
+		}
+
+		// schedule a task to wait until finished and close the channel.
+		go func() {
+			recv.wg.Wait()
+			close(recv.task)
+			close(recv.results)
+		}()
+	}
+	go readFiles()
+
+	// tasks to read license metadata files are scheduled; read and process results from channel
+	var err error
+	for recv.results != nil {
+		select {
+		case r, ok := <-recv.results:
+			if ok {
+				// handle errors by nil'ing ls, setting err, and clobbering results channel
+				if r.err != nil {
+					err = r.err
+					fmt.Fprintf(recv.stderr, "%s\n", err.Error())
+					lg = nil
+					recv.results = nil
+					continue
+				}
+
+				// record the parsed metadata (guarded by mutex)
+				recv.lg.mu.Lock()
+				recv.lg.targets[r.file] = r.target
+				if len(r.edges) > 0 {
+					recv.lg.edges = append(recv.lg.edges, r.edges...)
+				}
+				recv.lg.mu.Unlock()
+			} else {
+				// finished -- nil the results channel
+				recv.results = nil
+			}
+		}
+	}
+
+	return lg, err
+
+}
+
+// targetNode contains the license metadata for a node in the license graph.
+type targetNode struct {
+	proto license_metadata_proto.LicenseMetadata
+
+	// name is the path to the metadata file
+	name string
+}
+
+// dependencyEdge describes a single edge in the license graph.
+type dependencyEdge struct {
+	// target identifies the target node being built and/or installed.
+	target string
+
+	// dependency identifies the target node being depended on.
+	//
+	// i.e. `dependency` is necessary to build `target`.
+	dependency string
+
+	// annotations are a set of text attributes attached to the edge.
+	//
+	// Policy prescribes meaning to a limited set of annotations; others
+	// are preserved and ignored.
+	annotations TargetEdgeAnnotations
+}
+
+// addDependencies converts the proto AnnotatedDependencies into `edges`
+func addDependencies(edges *[]*dependencyEdge, target string, dependencies []*license_metadata_proto.AnnotatedDependency) error {
+	for _, ad := range dependencies {
+		dependency := ad.GetFile()
+		if len(dependency) == 0 {
+			return fmt.Errorf("missing dependency name")
+		}
+		annotations := newEdgeAnnotations()
+		for _, a := range ad.Annotations {
+			if len(a) == 0 {
+				continue
+			}
+			annotations.annotations[a] = true
+		}
+		*edges = append(*edges, &dependencyEdge{target, dependency, annotations})
+	}
+	return nil
+}
+
+// readFile is a task to read and parse a single license metadata file, and to schedule
+// additional tasks for reading and parsing dependencies as necessary.
+func readFile(recv *receiver, file string) {
+	recv.wg.Add(1)
+	<-recv.task
+	go func() {
+		f, err := recv.rootFS.Open(file)
+		if err != nil {
+			recv.results <- &result{file, nil, nil, fmt.Errorf("error opening license metadata %q: %w", file, err)}
+			return
+		}
+
+		// read the file
+		data, err := io.ReadAll(f)
+		if err != nil {
+			recv.results <- &result{file, nil, nil, fmt.Errorf("error reading license metadata %q: %w", file, err)}
+			return
+		}
+
+		tn := &TargetNode{name: file}
+
+		err = prototext.Unmarshal(data, &tn.proto)
+		if err != nil {
+			recv.results <- &result{file, nil, nil, fmt.Errorf("error license metadata %q: %w", file, err)}
+			return
+		}
+
+		edges := []*dependencyEdge{}
+		err = addDependencies(&edges, file, tn.proto.Deps)
+		if err != nil {
+			recv.results <- &result{file, nil, nil, fmt.Errorf("error license metadata dependency %q: %w", file, err)}
+			return
+		}
+		tn.proto.Deps = []*license_metadata_proto.AnnotatedDependency{}
+
+		// send result for this file and release task before scheduling dependencies,
+		// but do not signal done to WaitGroup until dependencies are scheduled.
+		recv.results <- &result{file, tn, edges, nil}
+		recv.task <- true
+
+		// schedule tasks as necessary to read dependencies
+		for _, e := range edges {
+			// decide, signal and record whether to schedule task in critical section
+			recv.lg.mu.Lock()
+			_, alreadyScheduled := recv.lg.targets[e.dependency]
+			if !alreadyScheduled {
+				recv.lg.targets[e.dependency] = nil
+			}
+			recv.lg.mu.Unlock()
+			// schedule task to read dependency file outside critical section
+			if !alreadyScheduled {
+				readFile(recv, e.dependency)
+			}
+		}
+
+		// signal task done after scheduling dependencies
+		recv.wg.Done()
+	}()
+}
diff --git a/tools/compliance/readgraph_test.go b/tools/compliance/readgraph_test.go
new file mode 100644
index 0000000..6248209
--- /dev/null
+++ b/tools/compliance/readgraph_test.go
@@ -0,0 +1,139 @@
+// Copyright 2021 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 compliance
+
+import (
+	"bytes"
+	"sort"
+	"strings"
+	"testing"
+)
+
+func TestReadLicenseGraph(t *testing.T) {
+	tests := []struct {
+		name            string
+		fs              *testFS
+		roots           []string
+		expectedError   string
+		expectedEdges   []edge
+		expectedTargets []string
+	}{
+		{
+			name: "trivial",
+			fs: &testFS{
+				"app.meta_lic": []byte("package_name: \"Android\"\n"),
+			},
+			roots:           []string{"app.meta_lic"},
+			expectedEdges:   []edge{},
+			expectedTargets: []string{"app.meta_lic"},
+		},
+		{
+			name: "unterminated",
+			fs: &testFS{
+				"app.meta_lic": []byte("package_name: \"Android\n"),
+			},
+			roots:         []string{"app.meta_lic"},
+			expectedError: `invalid character '\n' in string`,
+		},
+		{
+			name: "danglingref",
+			fs: &testFS{
+				"app.meta_lic": []byte(AOSP + "deps: {\n  file: \"lib.meta_lic\"\n}\n"),
+			},
+			roots:         []string{"app.meta_lic"},
+			expectedError: `unknown file "lib.meta_lic"`,
+		},
+		{
+			name: "singleedge",
+			fs: &testFS{
+				"app.meta_lic": []byte(AOSP + "deps: {\n  file: \"lib.meta_lic\"\n}\n"),
+				"lib.meta_lic": []byte(AOSP),
+			},
+			roots:           []string{"app.meta_lic"},
+			expectedEdges:   []edge{{"app.meta_lic", "lib.meta_lic"}},
+			expectedTargets: []string{"app.meta_lic", "lib.meta_lic"},
+		},
+		{
+			name: "fullgraph",
+			fs: &testFS{
+				"apex.meta_lic": []byte(AOSP + "deps: {\n  file: \"app.meta_lic\"\n}\ndeps: {\n  file: \"bin.meta_lic\"\n}\n"),
+				"app.meta_lic":  []byte(AOSP),
+				"bin.meta_lic":  []byte(AOSP + "deps: {\n  file: \"lib.meta_lic\"\n}\n"),
+				"lib.meta_lic":  []byte(AOSP),
+			},
+			roots: []string{"apex.meta_lic"},
+			expectedEdges: []edge{
+				{"apex.meta_lic", "app.meta_lic"},
+				{"apex.meta_lic", "bin.meta_lic"},
+				{"bin.meta_lic", "lib.meta_lic"},
+			},
+			expectedTargets: []string{"apex.meta_lic", "app.meta_lic", "bin.meta_lic", "lib.meta_lic"},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			stderr := &bytes.Buffer{}
+			lg, err := ReadLicenseGraph(tt.fs, stderr, tt.roots)
+			if err != nil {
+				if len(tt.expectedError) == 0 {
+					t.Errorf("unexpected error: got %w, want no error", err)
+				} else if !strings.Contains(err.Error(), tt.expectedError) {
+					t.Errorf("unexpected error: got %w, want %q", err, tt.expectedError)
+				}
+				return
+			}
+			if 0 < len(tt.expectedError) {
+				t.Errorf("unexpected success: got no error, want %q err", tt.expectedError)
+				return
+			}
+			if lg == nil {
+				t.Errorf("missing license graph: got nil, want license graph")
+				return
+			}
+			actualEdges := make([]edge, 0)
+			for _, e := range lg.Edges() {
+				actualEdges = append(actualEdges, edge{e.Target().Name(), e.Dependency().Name()})
+			}
+			sort.Sort(byEdge(tt.expectedEdges))
+			sort.Sort(byEdge(actualEdges))
+			if len(tt.expectedEdges) != len(actualEdges) {
+				t.Errorf("unexpected number of edges: got %v with %d elements, want %v with %d elements",
+					actualEdges, len(actualEdges), tt.expectedEdges, len(tt.expectedEdges))
+			} else {
+				for i := 0; i < len(actualEdges); i++ {
+					if tt.expectedEdges[i] != actualEdges[i] {
+						t.Errorf("unexpected edge at element %d: got %s, want %s", i, actualEdges[i], tt.expectedEdges[i])
+					}
+				}
+			}
+			actualTargets := make([]string, 0)
+			for _, t := range lg.Targets() {
+				actualTargets = append(actualTargets, t.Name())
+			}
+			sort.Strings(tt.expectedTargets)
+			sort.Strings(actualTargets)
+			if len(tt.expectedTargets) != len(actualTargets) {
+				t.Errorf("unexpected number of targets: got %v with %d elements, want %v with %d elements",
+					actualTargets, len(actualTargets), tt.expectedTargets, len(tt.expectedTargets))
+			} else {
+				for i := 0; i < len(actualTargets); i++ {
+					if tt.expectedTargets[i] != actualTargets[i] {
+						t.Errorf("unexpected target at element %d: got %s, want %s", i, actualTargets[i], tt.expectedTargets[i])
+					}
+				}
+			}
+		})
+	}
+}
diff --git a/tools/compliance/resolution.go b/tools/compliance/resolution.go
new file mode 100644
index 0000000..0865ecd
--- /dev/null
+++ b/tools/compliance/resolution.go
@@ -0,0 +1,208 @@
+// Copyright 2021 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 compliance
+
+import (
+	"fmt"
+	"sort"
+	"strings"
+)
+
+// Resolution describes an action to resolve one or more license conditions.
+//
+// `AttachesTo` identifies the target node that when distributed triggers the action.
+// `ActsOn` identifies the target node that is the object of the action.
+// `Resolves` identifies one or more license conditions that the action resolves.
+//
+// e.g. Suppose an MIT library is linked to a binary that also links to GPL code.
+//
+// A resolution would attach to the binary to share (act on) the MIT library to
+// resolve the restricted condition originating from the GPL code.
+type Resolution struct {
+	attachesTo, actsOn *TargetNode
+	cs                 *LicenseConditionSet
+}
+
+// AttachesTo returns the target node the resolution attaches to.
+func (r Resolution) AttachesTo() *TargetNode {
+	return r.attachesTo
+}
+
+// ActsOn returns the target node that must be acted on to resolve the condition.
+//
+// i.e. The node for which notice must be given or whose source must be shared etc.
+func (r Resolution) ActsOn() *TargetNode {
+	return r.actsOn
+}
+
+// Resolves returns the set of license condition the resolution satisfies.
+func (r Resolution) Resolves() *LicenseConditionSet {
+	return r.cs.Copy()
+}
+
+// asString returns a string representation of the resolution.
+func (r Resolution) asString() string {
+	var sb strings.Builder
+	cl := r.cs.AsList()
+	sort.Sort(cl)
+	fmt.Fprintf(&sb, "%s -> %s -> %s", r.attachesTo.name, r.actsOn.name, cl.String())
+	return sb.String()
+}
+
+// ResolutionList represents a partial order of Resolutions ordered by
+// AttachesTo() and ActsOn() leaving `Resolves()` unordered.
+type ResolutionList []Resolution
+
+// Len returns the count of elements in the list.
+func (l ResolutionList) Len() int      { return len(l) }
+
+// Swap rearranges 2 elements so that each occupies the other's former position.
+func (l ResolutionList) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
+
+// Less returns true when the `i`th element is lexicographically less than tht `j`th.
+func (l ResolutionList) Less(i, j int) bool {
+	if l[i].attachesTo.name == l[j].attachesTo.name {
+		return l[i].actsOn.name < l[j].actsOn.name
+	}
+	return l[i].attachesTo.name < l[j].attachesTo.name
+}
+
+// String returns a string representation of the list.
+func (rl ResolutionList) String() string {
+	var sb strings.Builder
+	fmt.Fprintf(&sb, "[")
+	sep := ""
+	for _, r := range rl {
+		fmt.Fprintf(&sb, "%s%s", sep, r.asString())
+		sep = ", "
+	}
+	fmt.Fprintf(&sb, "]")
+	return sb.String()
+}
+
+// AllConditions returns the union of all license conditions resolved by any
+// element of the list.
+func (rl ResolutionList) AllConditions() *LicenseConditionSet {
+	result := newLicenseConditionSet()
+	for _, r := range rl {
+		result.AddSet(r.cs)
+	}
+	return result
+}
+
+// ByName returns the sub-list of resolutions resolving conditions matching
+// `names`.
+func (rl ResolutionList) ByName(names ConditionNames) ResolutionList {
+	result := make(ResolutionList, 0, rl.CountByName(names))
+	for _, r := range rl {
+		if r.Resolves().HasAnyByName(names) {
+			result = append(result, Resolution{r.attachesTo, r.actsOn, r.cs.ByName(names)})
+		}
+	}
+	return result
+}
+
+// CountByName returns the number of resolutions resolving conditions matching
+// `names`.
+func (rl ResolutionList) CountByName(names ConditionNames) int {
+	c := 0
+	for _, r := range rl {
+		if r.Resolves().HasAnyByName(names) {
+			c++
+		}
+	}
+	return c
+}
+
+// CountConditionsByName returns a count of distinct resolution/conditions
+// pairs matching `names`.
+//
+// A single resolution might resolve multiple conditions matching `names`.
+func (rl ResolutionList) CountConditionsByName(names ConditionNames) int {
+	c := 0
+	for _, r := range rl {
+		c += r.Resolves().CountByName(names)
+	}
+	return c
+}
+
+// ByAttachesTo returns the sub-list of resolutions attached to `attachesTo`.
+func (rl ResolutionList) ByAttachesTo(attachesTo *TargetNode) ResolutionList {
+	result := make(ResolutionList, 0, rl.CountByActsOn(attachesTo))
+	for _, r := range rl {
+		if r.attachesTo == attachesTo {
+			result = append(result, r)
+		}
+	}
+	return result
+}
+
+// CountByAttachesTo returns the number of resolutions attached to
+// `attachesTo`.
+func (rl ResolutionList) CountByAttachesTo(attachesTo *TargetNode) int {
+	c := 0
+	for _, r := range rl {
+		if r.attachesTo == attachesTo {
+			c++
+		}
+	}
+	return c
+}
+
+// ByActsOn returns the sub-list of resolutions matching `actsOn`.
+func (rl ResolutionList) ByActsOn(actsOn *TargetNode) ResolutionList {
+	result := make(ResolutionList, 0, rl.CountByActsOn(actsOn))
+	for _, r := range rl {
+		if r.actsOn == actsOn {
+			result = append(result, r)
+		}
+	}
+	return result
+}
+
+// CountByActsOn returns the number of resolutions matching `actsOn`.
+func (rl ResolutionList) CountByActsOn(actsOn *TargetNode) int {
+	c := 0
+	for _, r := range rl {
+		if r.actsOn == actsOn {
+			c++
+		}
+	}
+	return c
+}
+
+// ByOrigin returns the sub-list of resolutions resolving license conditions
+// originating at `origin`.
+func (rl ResolutionList) ByOrigin(origin *TargetNode) ResolutionList {
+	result := make(ResolutionList, 0, rl.CountByOrigin(origin))
+	for _, r := range rl {
+		if r.Resolves().HasAnyByOrigin(origin) {
+			result = append(result, Resolution{r.attachesTo, r.actsOn, r.cs.ByOrigin(origin)})
+		}
+	}
+	return result
+}
+
+// CountByOrigin returns the number of resolutions resolving license conditions
+// originating at `origin`.
+func (rl ResolutionList) CountByOrigin(origin *TargetNode) int {
+	c := 0
+	for _, r := range rl {
+		if r.Resolves().HasAnyByOrigin(origin) {
+			c++
+		}
+	}
+	return c
+}
diff --git a/tools/compliance/resolutionset.go b/tools/compliance/resolutionset.go
new file mode 100644
index 0000000..ea49db9
--- /dev/null
+++ b/tools/compliance/resolutionset.go
@@ -0,0 +1,304 @@
+// Copyright 2021 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 compliance
+
+import (
+	"fmt"
+	"strings"
+)
+
+// JoinResolutionSets returns a new ResolutionSet combining the resolutions from
+// multiple resolution sets. All sets must be derived from the same license
+// graph.
+//
+// e.g. combine "restricted", "reciprocal", and "proprietary" resolutions.
+func JoinResolutionSets(resolutions ...*ResolutionSet) *ResolutionSet {
+	if len(resolutions) < 1 {
+		panic(fmt.Errorf("attempt to join 0 resolution sets"))
+	}
+	rmap := make(map[*TargetNode]actionSet)
+	for _, r := range resolutions {
+		if len(r.resolutions) < 1 {
+			continue
+		}
+		for attachesTo, as := range r.resolutions {
+			if as.isEmpty() {
+				continue
+			}
+			if _, ok := rmap[attachesTo]; !ok {
+				rmap[attachesTo] = as.copy()
+				continue
+			}
+			rmap[attachesTo].addSet(as)
+		}
+	}
+	return &ResolutionSet{rmap}
+}
+
+// ResolutionSet describes an immutable set of targets and the license
+// conditions each target must satisfy or "resolve" in a specific context.
+//
+// Ultimately, the purpose of recording the license metadata and building a
+// license graph is to identify, describe, and verify the necessary actions or
+// operations for compliance policy.
+//
+// i.e. What is the source-sharing policy? Has it been met? Meet it.
+//
+// i.e. Are there incompatible policy requirements? Such as a source-sharing
+// policy applied to code that policy also says may not be shared? If so, stop
+// and remove the dependencies that create the situation.
+//
+// The ResolutionSet is the base unit for mapping license conditions to the
+// targets triggering some necessary action per policy. Different ResolutionSet
+// values may be calculated for different contexts.
+//
+// e.g. Suppose an unencumbered binary links in a notice .a library.
+//
+// An "unencumbered" condition would originate from the binary, and a "notice"
+// condition would originate from the .a library. A ResolutionSet for the
+// context of the Notice policy might apply both conditions to the binary while
+// preserving the origin of each condition. By applying the notice condition to
+// the binary, the ResolutionSet stipulates the policy that the release of the
+// unencumbered binary must provide suitable notice for the .a library.
+//
+// The resulting ResolutionSet could be used for building a notice file, for
+// validating that a suitable notice has been built into the distribution, or
+// for reporting what notices need to be given.
+//
+// Resolutions for different contexts may be combined in a new ResolutionSet
+// using JoinResolutions(...).
+//
+// See: resolve.go for:
+//  * ResolveBottomUpConditions(...)
+//  * ResolveTopDownForCondition(...)
+// See also: policy.go for:
+//  * ResolveSourceSharing(...)
+//  * ResolveSourcePrivacy(...)
+type ResolutionSet struct {
+	// resolutions maps names of target with applicable conditions to the set of conditions that apply.
+	resolutions map[*TargetNode]actionSet
+}
+
+// String returns a string representation of the set.
+func (rs *ResolutionSet) String() string {
+	var sb strings.Builder
+	fmt.Fprintf(&sb, "{")
+	sep := ""
+	for attachesTo, as := range rs.resolutions {
+		fmt.Fprintf(&sb, "%s%s -> %s", sep, attachesTo.name, as.String())
+		sep = ", "
+	}
+	fmt.Fprintf(&sb, "}")
+	return sb.String()
+}
+
+// AttachesTo identifies the list of targets triggering action to resolve
+// conditions. (unordered)
+func (rs *ResolutionSet) AttachesTo() TargetNodeList {
+	targets := make(TargetNodeList, 0, len(rs.resolutions))
+	for attachesTo := range rs.resolutions {
+		targets = append(targets, attachesTo)
+	}
+	return targets
+}
+
+// ActsOn identifies the list of targets to act on (share, give notice etc.)
+// to resolve conditions. (unordered)
+func (rs *ResolutionSet) ActsOn() TargetNodeList {
+	tset := make(map[*TargetNode]bool)
+	for _, as := range rs.resolutions {
+		for actsOn := range as {
+			tset[actsOn] = true
+		}
+	}
+	targets := make(TargetNodeList, 0, len(tset))
+	for target := range tset {
+		targets = append(targets, target)
+	}
+	return targets
+}
+
+// Origins identifies the list of targets originating conditions to resolve.
+// (unordered)
+func (rs *ResolutionSet) Origins() TargetNodeList {
+	tset := make(map[*TargetNode]bool)
+	for _, as := range rs.resolutions {
+		for _, cs := range as {
+			for _, origins := range cs.conditions {
+				for origin := range origins {
+					tset[origin] = true
+				}
+			}
+		}
+	}
+	targets := make(TargetNodeList, 0, len(tset))
+	for target := range tset {
+		targets = append(targets, target)
+	}
+	return targets
+}
+
+// Resolutions returns the list of resolutions that `attachedTo`
+// target must resolve. Returns empty list if no conditions apply.
+//
+// Panics if `attachedTo` does not appear in the set.
+func (rs *ResolutionSet) Resolutions(attachedTo *TargetNode) ResolutionList {
+	as, ok := rs.resolutions[attachedTo]
+	if !ok {
+		return ResolutionList{}
+	}
+	result := make(ResolutionList, 0, len(as))
+	for actsOn, cs := range as {
+		result = append(result, Resolution{attachedTo, actsOn, cs.Copy()})
+	}
+	return result
+}
+
+// ResolutionsByActsOn returns the list of resolutions that must `actOn` to
+// resolvee. Returns empty list if no conditions apply.
+//
+// Panics if `actOn` does not appear in the set.
+func (rs *ResolutionSet) ResolutionsByActsOn(actOn *TargetNode) ResolutionList {
+	c := 0
+	for _, as := range rs.resolutions {
+		if _, ok := as[actOn]; ok {
+			c++
+		}
+	}
+	result := make(ResolutionList, 0, c)
+	for attachedTo, as := range rs.resolutions {
+		if cs, ok := as[actOn]; ok {
+			result = append(result, Resolution{attachedTo, actOn, cs.Copy()})
+		}
+	}
+	return result
+}
+
+// AttachesToByOrigin identifies the list of targets requiring action to
+// resolve conditions originating at `origin`. (unordered)
+func (rs *ResolutionSet) AttachesToByOrigin(origin *TargetNode) TargetNodeList {
+	tset := make(map[*TargetNode]bool)
+	for attachesTo, as := range rs.resolutions {
+		for _, cs := range as {
+			if cs.HasAnyByOrigin(origin) {
+				tset[attachesTo] = true
+				break
+			}
+		}
+	}
+	targets := make(TargetNodeList, 0, len(tset))
+	for target := range tset {
+		targets = append(targets, target)
+	}
+	return targets
+}
+
+// AttachesToTarget returns true if the set contains conditions that
+// are `attachedTo`.
+func (rs *ResolutionSet) AttachesToTarget(attachedTo *TargetNode) bool {
+	_, isPresent := rs.resolutions[attachedTo]
+	return isPresent
+}
+
+// AnyByNameAttachToTarget returns true if the set contains conditions matching
+// `names` that attach to `attachedTo`.
+func (rs *ResolutionSet) AnyByNameAttachToTarget(attachedTo *TargetNode, names ...ConditionNames) bool {
+	as, isPresent := rs.resolutions[attachedTo]
+	if !isPresent {
+		return false
+	}
+	for _, cs := range as {
+		for _, cn := range names {
+			for _, name := range cn {
+				_, isPresent = cs.conditions[name]
+				if isPresent {
+					return true
+				}
+			}
+		}
+	}
+	return false
+}
+
+// AllByNameAttachTo returns true if the set contains at least one condition
+// matching each element of `names` for `attachedTo`.
+func (rs *ResolutionSet) AllByNameAttachToTarget(attachedTo *TargetNode, names ...ConditionNames) bool {
+	as, isPresent := rs.resolutions[attachedTo]
+	if !isPresent {
+		return false
+	}
+	for _, cn := range names {
+		found := false
+	asloop:
+		for _, cs := range as {
+			for _, name := range cn {
+				_, isPresent = cs.conditions[name]
+				if isPresent {
+					found = true
+					break asloop
+				}
+			}
+		}
+		if !found {
+			return false
+		}
+	}
+	return true
+}
+
+// IsEmpty returns true if the set contains no conditions to resolve.
+func (rs *ResolutionSet) IsEmpty() bool {
+	for _, as := range rs.resolutions {
+		if !as.isEmpty() {
+			return false
+		}
+	}
+	return true
+}
+
+// compliance-only ResolutionSet methods
+
+// newResolutionSet constructs a new, empty instance of resolutionSetImp for graph `lg`.
+func newResolutionSet() *ResolutionSet {
+	return &ResolutionSet{make(map[*TargetNode]actionSet)}
+}
+
+// addConditions attaches all of the license conditions in `as` to `attachTo` to act on the originating node if not already applied.
+func (rs *ResolutionSet) addConditions(attachTo *TargetNode, as actionSet) {
+	_, ok := rs.resolutions[attachTo]
+	if !ok {
+		rs.resolutions[attachTo] = as.copy()
+		return
+	}
+	rs.resolutions[attachTo].addSet(as)
+}
+
+// add attaches all of the license conditions in `as` to `attachTo` to act on `attachTo` if not already applied.
+func (rs *ResolutionSet) addSelf(attachTo *TargetNode, as actionSet) {
+	for _, cs := range as {
+		if cs.IsEmpty() {
+			return
+		}
+		_, ok := rs.resolutions[attachTo]
+		if !ok {
+			rs.resolutions[attachTo] = make(actionSet)
+		}
+		_, ok = rs.resolutions[attachTo][attachTo]
+		if !ok {
+			rs.resolutions[attachTo][attachTo] = newLicenseConditionSet()
+		}
+		rs.resolutions[attachTo][attachTo].AddSet(cs)
+	}
+}
diff --git a/tools/compliance/resolutionset_test.go b/tools/compliance/resolutionset_test.go
new file mode 100644
index 0000000..e50e823
--- /dev/null
+++ b/tools/compliance/resolutionset_test.go
@@ -0,0 +1,201 @@
+// Copyright 2021 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 compliance
+
+import (
+	"sort"
+	"testing"
+)
+
+var (
+	// bottomUp describes the bottom-up resolve of a hypothetical graph
+	// the graph has a container image, a couple binaries, and a couple
+	// libraries. bin1 statically links lib1 and dynamically links lib2;
+	// bin2 dynamically links lib1 and statically links lib2.
+	// binc represents a compiler or other toolchain binary used for
+	// building the other binaries.
+	bottomUp = []res{
+		{"image", "image", "image", "notice"},
+		{"image", "image", "bin2", "restricted"},
+		{"image", "bin1", "bin1", "reciprocal"},
+		{"image", "bin2", "bin2", "restricted"},
+		{"image", "lib1", "lib1", "notice"},
+		{"image", "lib2", "lib2", "notice"},
+		{"binc", "binc", "binc", "proprietary"},
+		{"bin1", "bin1", "bin1", "reciprocal"},
+		{"bin1", "lib1", "lib1", "notice"},
+		{"bin2", "bin2", "bin2", "restricted"},
+		{"bin2", "lib2", "lib2", "notice"},
+		{"lib1", "lib1", "lib1", "notice"},
+		{"lib2", "lib2", "lib2", "notice"},
+	}
+
+	// notice describes bottomUp after a top-down notice resolve.
+	notice = []res{
+		{"image", "image", "image", "notice"},
+		{"image", "image", "bin2", "restricted"},
+		{"image", "bin1", "bin1", "reciprocal"},
+		{"image", "bin2", "bin2", "restricted"},
+		{"image", "lib1", "lib1", "notice"},
+		{"image", "lib2", "bin2", "restricted"},
+		{"image", "lib2", "lib2", "notice"},
+		{"bin1", "bin1", "bin1", "reciprocal"},
+		{"bin1", "lib1", "lib1", "notice"},
+		{"bin2", "bin2", "bin2", "restricted"},
+		{"bin2", "lib2", "bin2", "restricted"},
+		{"bin2", "lib2", "lib2", "notice"},
+		{"lib1", "lib1", "lib1", "notice"},
+		{"lib2", "lib2", "lib2", "notice"},
+	}
+
+	// share describes bottomUp after a top-down share resolve.
+	share = []res{
+		{"image", "image", "bin2", "restricted"},
+		{"image", "bin1", "bin1", "reciprocal"},
+		{"image", "bin2", "bin2", "restricted"},
+		{"image", "lib2", "bin2", "restricted"},
+		{"bin1", "bin1", "bin1", "reciprocal"},
+		{"bin2", "bin2", "bin2", "restricted"},
+		{"bin2", "lib2", "bin2", "restricted"},
+	}
+
+	// proprietary describes bottomUp after a top-down proprietary resolve.
+	// Note that the proprietary binc is not reachable through the toolchain
+	// dependency.
+	proprietary = []res{}
+)
+
+func TestResolutionSet_JoinResolutionSets(t *testing.T) {
+	lg := newLicenseGraph()
+
+	rsNotice := toResolutionSet(lg, notice)
+	rsShare := toResolutionSet(lg, share)
+	rsExpected := toResolutionSet(lg, append(notice, share...))
+
+	rsActual := JoinResolutionSets(rsNotice, rsShare)
+	checkSame(rsActual, rsExpected, t)
+}
+
+func TestResolutionSet_JoinResolutionsEmpty(t *testing.T) {
+	lg := newLicenseGraph()
+
+	rsShare := toResolutionSet(lg, share)
+	rsProprietary := toResolutionSet(lg, proprietary)
+	rsExpected := toResolutionSet(lg, append(share, proprietary...))
+
+	rsActual := JoinResolutionSets(rsShare, rsProprietary)
+	checkSame(rsActual, rsExpected, t)
+}
+
+func TestResolutionSet_Origins(t *testing.T) {
+	lg := newLicenseGraph()
+
+	rsShare := toResolutionSet(lg, share)
+
+	origins := make([]string, 0)
+	for _, target := range rsShare.Origins() {
+		origins = append(origins, target.Name())
+	}
+	sort.Strings(origins)
+	if len(origins) != 2 {
+		t.Errorf("unexpected number of origins: got %v with %d elements, want [\"bin1\", \"bin2\"] with 2 elements", origins, len(origins))
+	}
+	if origins[0] != "bin1" {
+		t.Errorf("unexpected origin at element 0: got %s, want \"bin1\"", origins[0])
+	}
+	if origins[1] != "bin2" {
+		t.Errorf("unexpected origin at element 0: got %s, want \"bin2\"", origins[0])
+	}
+}
+
+func TestResolutionSet_AttachedToTarget(t *testing.T) {
+	lg := newLicenseGraph()
+
+	rsShare := toResolutionSet(lg, share)
+
+	if rsShare.AttachesToTarget(newTestNode(lg, "binc")) {
+		t.Errorf("unexpected AttachedToTarget(\"binc\"): got true, want false")
+	}
+	if !rsShare.AttachesToTarget(newTestNode(lg, "image")) {
+		t.Errorf("unexpected AttachedToTarget(\"image\"): got false want true")
+	}
+}
+
+func TestResolutionSet_AnyByNameAttachToTarget(t *testing.T) {
+	lg := newLicenseGraph()
+
+	rs := toResolutionSet(lg, bottomUp)
+
+	pandp := ConditionNames{"permissive", "proprietary"}
+	pandn := ConditionNames{"permissive", "notice"}
+	p := ConditionNames{"proprietary"}
+	r := ConditionNames{"restricted"}
+
+	if rs.AnyByNameAttachToTarget(newTestNode(lg, "image"), pandp, p) {
+		t.Errorf("unexpected AnyByNameAttachToTarget(\"image\", \"proprietary\", \"permissive\"): want false, got true")
+	}
+	if !rs.AnyByNameAttachToTarget(newTestNode(lg, "binc"), p) {
+		t.Errorf("unexpected AnyByNameAttachToTarget(\"binc\", \"proprietary\"): want true, got false")
+	}
+	if !rs.AnyByNameAttachToTarget(newTestNode(lg, "image"), pandn) {
+		t.Errorf("unexpected AnyByNameAttachToTarget(\"image\", \"permissive\", \"notice\"): want true, got false")
+	}
+	if !rs.AnyByNameAttachToTarget(newTestNode(lg, "image"), r, pandn) {
+		t.Errorf("unexpected AnyByNameAttachToTarget(\"image\", \"restricted\", \"notice\"): want true, got false")
+	}
+	if !rs.AnyByNameAttachToTarget(newTestNode(lg, "image"), r, p) {
+		t.Errorf("unexpected AnyByNameAttachToTarget(\"image\", \"restricted\", \"proprietary\"): want true, got false")
+	}
+}
+
+func TestResolutionSet_AllByNameAttachToTarget(t *testing.T) {
+	lg := newLicenseGraph()
+
+	rs := toResolutionSet(lg, bottomUp)
+
+	pandp := ConditionNames{"permissive", "proprietary"}
+	pandn := ConditionNames{"permissive", "notice"}
+	p := ConditionNames{"proprietary"}
+	r := ConditionNames{"restricted"}
+
+	if rs.AllByNameAttachToTarget(newTestNode(lg, "image"), pandp, p) {
+		t.Errorf("unexpected AllByNameAttachToTarget(\"image\", \"proprietary\", \"permissive\"): want false, got true")
+	}
+	if !rs.AllByNameAttachToTarget(newTestNode(lg, "binc"), p) {
+		t.Errorf("unexpected AllByNameAttachToTarget(\"binc\", \"proprietary\"): want true, got false")
+	}
+	if !rs.AllByNameAttachToTarget(newTestNode(lg, "image"), pandn) {
+		t.Errorf("unexpected AllByNameAttachToTarget(\"image\", \"notice\"): want true, got false")
+	}
+	if !rs.AllByNameAttachToTarget(newTestNode(lg, "image"), r, pandn) {
+		t.Errorf("unexpected AllByNameAttachToTarget(\"image\", \"restricted\", \"notice\"): want true, got false")
+	}
+	if rs.AllByNameAttachToTarget(newTestNode(lg, "image"), r, p) {
+		t.Errorf("unexpected AllByNameAttachToTarget(\"image\", \"restricted\", \"proprietary\"): want false, got true")
+	}
+}
+
+func TestResolutionSet_AttachesToTarget(t *testing.T) {
+	lg := newLicenseGraph()
+
+	rsShare := toResolutionSet(lg, share)
+
+	if rsShare.AttachesToTarget(newTestNode(lg, "binc")) {
+		t.Errorf("unexpected hasTarget(\"binc\"): got true, want false")
+	}
+	if !rsShare.AttachesToTarget(newTestNode(lg, "image")) {
+		t.Errorf("unexpected AttachesToTarget(\"image\"): got false want true")
+	}
+}
diff --git a/tools/compliance/test_util.go b/tools/compliance/test_util.go
new file mode 100644
index 0000000..a183b90
--- /dev/null
+++ b/tools/compliance/test_util.go
@@ -0,0 +1,408 @@
+// Copyright 2021 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 compliance
+
+import (
+	"fmt"
+	"io"
+	"io/fs"
+	"sort"
+	"strings"
+	"testing"
+)
+
+const (
+	// AOSP starts a test metadata file for Android Apache-2.0 licensing.
+	AOSP = `` +
+		`package_name: "Android"
+license_kinds: "SPDX-license-identifier-Apache-2.0"
+license_conditions: "notice"
+`
+
+	// GPL starts a test metadata file for GPL 2.0 licensing.
+	GPL = `` +
+`package_name: "Free Software"
+license_kinds: "SPDX-license-identifier-GPL-2.0"
+license_conditions: "restricted"
+`
+
+	// Classpath starts a test metadata file for GPL 2.0 with classpath exception licensing.
+	Classpath = `` +
+`package_name: "Free Software"
+license_kinds: "SPDX-license-identifier-GPL-2.0-with-classpath-exception"
+license_conditions: "restricted"
+`
+
+	// DependentModule starts a test metadata file for a module in the same package as `Classpath`.
+	DependentModule = `` +
+`package_name: "Free Software"
+license_kinds: "SPDX-license-identifier-MIT"
+license_conditions: "notice"
+`
+
+	// LGPL starts a test metadata file for a module with LGPL 2.0 licensing.
+	LGPL = `` +
+`package_name: "Free Library"
+license_kinds: "SPDX-license-identifier-LGPL-2.0"
+license_conditions: "restricted"
+`
+
+	// MPL starts a test metadata file for a module with MPL 2.0 reciprical licensing.
+	MPL = `` +
+`package_name: "Reciprocal"
+license_kinds: "SPDX-license-identifier-MPL-2.0"
+license_conditions: "reciprocal"
+`
+
+	// MIT starts a test metadata file for a module with generic notice (MIT) licensing.
+	MIT = `` +
+`package_name: "Android"
+license_kinds: "SPDX-license-identifier-MIT"
+license_conditions: "notice"
+`
+
+	// Proprietary starts a test metadata file for a module with proprietary licensing.
+	Proprietary = `` +
+`package_name: "Android"
+license_kinds: "legacy_proprietary"
+license_conditions: "proprietary"
+`
+
+	// ByException starts a test metadata file for a module with by_exception_only licensing.
+	ByException = `` +
+`package_name: "Special"
+license_kinds: "legacy_by_exception_only"
+license_conditions: "by_exception_only"
+`
+
+)
+
+var (
+	// meta maps test file names to metadata file content without dependencies.
+	meta = map[string]string{
+		"apacheBin.meta_lic": AOSP,
+		"apacheLib.meta_lic": AOSP,
+		"apacheContainer.meta_lic": AOSP + "is_container: true\n",
+		"dependentModule.meta_lic": DependentModule,
+		"gplWithClasspathException.meta_lic": Classpath,
+		"gplBin.meta_lic": GPL,
+		"gplLib.meta_lic": GPL,
+		"gplContainer.meta_lic": GPL + "is_container: true\n",
+		"lgplBin.meta_lic": LGPL,
+		"lgplLib.meta_lic": LGPL,
+		"mitBin.meta_lic": MIT,
+		"mitLib.meta_lic": MIT,
+		"mplBin.meta_lic": MPL,
+		"mplLib.meta_lic": MPL,
+		"proprietary.meta_lic": Proprietary,
+		"by_exception.meta_lic": ByException,
+	}
+)
+
+// toConditionList converts a test data map of condition name to origin names into a ConditionList.
+func toConditionList(lg *LicenseGraph, conditions map[string][]string) ConditionList {
+	cl := make(ConditionList, 0)
+	for name, origins := range conditions {
+		for _, origin := range origins {
+			cl = append(cl, LicenseCondition{name, newTestNode(lg, origin)})
+		}
+	}
+	return cl
+}
+
+// newTestNode constructs a test node in the license graph.
+func newTestNode(lg *LicenseGraph, targetName string) *TargetNode {
+	if _, ok := lg.targets[targetName]; !ok {
+		lg.targets[targetName] = &TargetNode{name: targetName}
+	}
+	return lg.targets[targetName]
+}
+
+// testFS implements a test file system (fs.FS) simulated by a map from filename to []byte content.
+type testFS map[string][]byte
+
+// Open implements fs.FS.Open() to open a file based on the filename.
+func (fs *testFS) Open(name string) (fs.File, error) {
+	if _, ok := (*fs)[name]; !ok {
+		return nil, fmt.Errorf("unknown file %q", name)
+	}
+	return &testFile{fs, name, 0}, nil
+}
+
+// testFile implements a test file (fs.File) based on testFS above.
+type testFile struct {
+	fs   *testFS
+	name string
+	posn int
+}
+
+// Stat not implemented to obviate implementing fs.FileInfo.
+func (f *testFile) Stat() (fs.FileInfo, error) {
+	return nil, fmt.Errorf("unimplemented")
+}
+
+// Read copies bytes from the testFS map.
+func (f *testFile) Read(b []byte) (int, error) {
+	if f.posn < 0 {
+		return 0, fmt.Errorf("file not open: %q", f.name)
+	}
+	if f.posn >= len((*f.fs)[f.name]) {
+		return 0, io.EOF
+	}
+	n := copy(b, (*f.fs)[f.name][f.posn:])
+	f.posn += n
+	return n, nil
+}
+
+// Close marks the testFile as no longer in use.
+func (f *testFile) Close() error {
+	if f.posn < 0 {
+		return fmt.Errorf("file already closed: %q", f.name)
+	}
+	f.posn = -1
+	return nil
+}
+
+// edge describes test data edges to define test graphs.
+type edge struct {
+	target, dep string
+}
+
+// String returns a string representation of the edge.
+func (e edge) String() string {
+	return e.target + " -> " + e.dep
+}
+
+// byEdge orders edges by target then dep name then annotations.
+type byEdge []edge
+
+// Len returns the count of elements in the slice.
+func (l byEdge) Len() int      { return len(l) }
+
+// Swap rearranges 2 elements of the slice so that each occupies the other's
+// former position.
+func (l byEdge) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
+
+// Less returns true when the `i`th element is lexicographically less than
+// the `j`th element.
+func (l byEdge) Less(i, j int) bool {
+	if l[i].target == l[j].target {
+		return l[i].dep < l[j].dep
+	}
+	return l[i].target < l[j].target
+}
+
+
+// annotated describes annotated test data edges to define test graphs.
+type annotated struct {
+	target, dep string
+	annotations []string
+}
+
+func (e annotated) String() string {
+	if e.annotations != nil {
+		return e.target + " -> " + e.dep + " [" + strings.Join(e.annotations, ", ") + "]"
+	}
+	return e.target + " -> " + e.dep
+}
+
+func (e annotated) IsEqualTo(other annotated) bool {
+	if e.target != other.target {
+		return false
+	}
+	if e.dep != other.dep {
+		return false
+	}
+        if len(e.annotations) != len(other.annotations) {
+		return false
+	}
+	a1 := append([]string{}, e.annotations...)
+	a2 := append([]string{}, other.annotations...)
+	for i := 0; i < len(a1); i++ {
+		if a1[i] != a2[i] {
+			return false
+		}
+	}
+	return true
+}
+
+// toGraph converts a list of roots and a list of annotated edges into a test license graph.
+func toGraph(stderr io.Writer, roots []string, edges []annotated) (*LicenseGraph, error) {
+	deps := make(map[string][]annotated)
+	for _, root := range roots {
+		deps[root] = []annotated{}
+	}
+	for _, edge := range edges {
+		if prev, ok := deps[edge.target]; ok {
+			deps[edge.target] = append(prev, edge)
+		} else {
+			deps[edge.target] = []annotated{edge}
+		}
+		if _, ok := deps[edge.dep]; !ok {
+			deps[edge.dep] = []annotated{}
+		}
+	}
+	fs := make(testFS)
+	for file, edges := range deps {
+		body := meta[file]
+		for _, edge := range edges {
+			body += fmt.Sprintf("deps: {\n  file: %q\n", edge.dep)
+			for _, ann := range edge.annotations {
+				body += fmt.Sprintf("  annotations: %q\n", ann)
+			}
+			body += "}\n"
+		}
+		fs[file] = []byte(body)
+	}
+
+	return ReadLicenseGraph(&fs, stderr, roots)
+}
+
+
+// byAnnotatedEdge orders edges by target then dep name then annotations.
+type byAnnotatedEdge []annotated
+
+func (l byAnnotatedEdge) Len() int      { return len(l) }
+func (l byAnnotatedEdge) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
+func (l byAnnotatedEdge) Less(i, j int) bool {
+	if l[i].target == l[j].target {
+		if l[i].dep == l[j].dep {
+			ai := append([]string{}, l[i].annotations...)
+			aj := append([]string{}, l[j].annotations...)
+			sort.Strings(ai)
+			sort.Strings(aj)
+			for k := 0; k < len(ai) && k < len(aj); k++ {
+				if ai[k] == aj[k] {
+					continue
+				}
+				return ai[k] < aj[k]
+			}
+			return len(ai) < len(aj)
+		}
+		return l[i].dep < l[j].dep
+	}
+	return l[i].target < l[j].target
+}
+
+// res describes test data resolutions to define test resolution sets.
+type res struct {
+	attachesTo, actsOn, origin, condition string
+}
+
+// toResolutionSet converts a list of res test data into a test resolution set.
+func toResolutionSet(lg *LicenseGraph, data []res) *ResolutionSet {
+	rmap := make(map[*TargetNode]actionSet)
+	for _, r := range data {
+		attachesTo := newTestNode(lg, r.attachesTo)
+		actsOn := newTestNode(lg, r.actsOn)
+		origin := newTestNode(lg, r.origin)
+		if _, ok := rmap[attachesTo]; !ok {
+			rmap[attachesTo] = make(actionSet)
+		}
+		if _, ok := rmap[attachesTo][actsOn]; !ok {
+			rmap[attachesTo][actsOn] = newLicenseConditionSet()
+		}
+		rmap[attachesTo][actsOn].add(origin, r.condition)
+	}
+	return &ResolutionSet{rmap}
+}
+
+type confl struct {
+	sourceNode, share, privacy string
+}
+
+func toConflictList(lg *LicenseGraph, data []confl) []SourceSharePrivacyConflict {
+	result := make([]SourceSharePrivacyConflict, 0, len(data))
+	for _, c := range data {
+		fields := strings.Split(c.share, ":")
+		oshare := fields[0]
+		cshare := fields[1]
+		fields = strings.Split(c.privacy, ":")
+		oprivacy := fields[0]
+		cprivacy := fields[1]
+		result = append(result, SourceSharePrivacyConflict{
+				newTestNode(lg, c.sourceNode),
+				LicenseCondition{cshare, newTestNode(lg, oshare)},
+				LicenseCondition{cprivacy, newTestNode(lg, oprivacy)},
+			})
+	}
+	return result
+}
+
+// checkSameActions compares an actual action set to an expected action set for a test.
+func checkSameActions(lg *LicenseGraph, asActual, asExpected actionSet, t *testing.T) {
+	rsActual := ResolutionSet{make(map[*TargetNode]actionSet)}
+	rsExpected := ResolutionSet{make(map[*TargetNode]actionSet)}
+	testNode := newTestNode(lg, "test")
+	rsActual.resolutions[testNode] = asActual
+	rsExpected.resolutions[testNode] = asExpected
+	checkSame(&rsActual, &rsExpected, t)
+}
+
+// checkSame compares an actual resolution set to an expected resolution set for a test.
+func checkSame(rsActual, rsExpected *ResolutionSet, t *testing.T) {
+	expectedTargets := rsExpected.AttachesTo()
+	sort.Sort(expectedTargets)
+	for _, target := range expectedTargets {
+		if !rsActual.AttachesToTarget(target) {
+			t.Errorf("unexpected missing target: got AttachesToTarget(%q) is false in %s, want true in %s", target.name, rsActual, rsExpected)
+			continue
+		}
+		expectedRl := rsExpected.Resolutions(target)
+		sort.Sort(expectedRl)
+		actualRl := rsActual.Resolutions(target)
+		sort.Sort(actualRl)
+		if len(expectedRl) != len(actualRl) {
+			t.Errorf("unexpected number of resolutions attach to %q: got %s with %d elements, want %s with %d elements",
+				target.name, actualRl, len(actualRl), expectedRl, len(expectedRl))
+			continue
+		}
+		for i := 0; i < len(expectedRl); i++ {
+			if expectedRl[i].attachesTo.name != actualRl[i].attachesTo.name || expectedRl[i].actsOn.name != actualRl[i].actsOn.name {
+				t.Errorf("unexpected resolution attaches to %q at index %d: got %s, want %s",
+					target.name, i, actualRl[i].asString(), expectedRl[i].asString())
+				continue
+			}
+			expectedConditions := expectedRl[i].Resolves().AsList()
+			actualConditions := actualRl[i].Resolves().AsList()
+			sort.Sort(expectedConditions)
+			sort.Sort(actualConditions)
+			if len(expectedConditions) != len(actualConditions) {
+				t.Errorf("unexpected number of conditions apply to %q acting on %q: got %s with %d elements, want %s with %d elements",
+					target.name, expectedRl[i].actsOn.name,
+					actualConditions, len(actualConditions),
+					expectedConditions, len(expectedConditions))
+				continue
+			}
+			for j := 0; j < len(expectedConditions); j++ {
+				if expectedConditions[j] != actualConditions[j] {
+					t.Errorf("unexpected condition attached to %q acting on %q at index %d: got %s at index %d in %s, want %s in %s",
+						target.name, expectedRl[i].actsOn.name, i,
+						actualConditions[j].asString(":"), j, actualConditions,
+						expectedConditions[j].asString(":"), expectedConditions)
+				}
+			}
+		}
+
+	}
+	actualTargets := rsActual.AttachesTo()
+	sort.Sort(actualTargets)
+	for i, target := range actualTargets {
+		if !rsExpected.AttachesToTarget(target) {
+			t.Errorf("unexpected target: got %q element %d in AttachesTo() %s with %d elements in %s, want %s with %d elements in %s",
+				target.name, i, actualTargets, len(actualTargets), rsActual, expectedTargets, len(expectedTargets), rsExpected)
+		}
+	}
+}
diff --git a/tools/releasetools/build_image.py b/tools/releasetools/build_image.py
index 8a5d627..34aa1a6 100755
--- a/tools/releasetools/build_image.py
+++ b/tools/releasetools/build_image.py
@@ -651,6 +651,10 @@
   if not mkfs_output:
     mkfs_output = BuildImageMkfs(in_dir, prop_dict, out_file, target_out, fs_config)
 
+  # Update the image (eg filesystem size). This can be different eg if mkfs
+  # rounds the requested size down due to alignment.
+  prop_dict["image_size"] = common.sparse_img.GetImagePartitionSize(out_file)
+
   # Check if there's enough headroom space available for ext4 image.
   if "partition_headroom" in prop_dict and fs_type.startswith("ext4"):
     CheckHeadroom(mkfs_output, prop_dict)
diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py
index 1533030..64ac95a 100644
--- a/tools/releasetools/common.py
+++ b/tools/releasetools/common.py
@@ -68,6 +68,9 @@
     self.search_path = os.path.dirname(os.path.dirname(exec_path))
 
     self.signapk_path = "framework/signapk.jar"  # Relative to search_path
+    if not os.path.exists(os.path.join(self.search_path, self.signapk_path)):
+      if "ANDROID_HOST_OUT" in os.environ:
+        self.search_path = os.environ["ANDROID_HOST_OUT"]
     self.signapk_shared_library_path = "lib64"   # Relative to search_path
     self.extra_signapk_args = []
     self.java_path = "java"  # Use the one on the path by default.
@@ -973,6 +976,8 @@
         break
       except KeyError:
         logger.warning('Failed to read %s', prop_file)
+    if data == '':
+      logger.warning("Failed to read build.prop for partition {}".format(name))
     return data
 
   @staticmethod
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index e99152a..c21de14 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -1191,8 +1191,6 @@
     care_map_list = [x for x in ["care_map.pb", "care_map.txt"] if
                      "META/" + x in target_zip.namelist()]
 
-    # TODO(b/205541521) remove the workaround after root cause is fixed.
-    care_map_list = []
     # Adds care_map if either the protobuf format or the plain text one exists.
     if care_map_list:
       care_map_name = care_map_list[0]
@@ -1479,7 +1477,7 @@
     # Only check for existence of key file if using the default signer.
     # Because the custom signer might not need the key file AT all.
     # b/191704641
-    if not OPTIONS.signapk_path:
+    if not OPTIONS.payload_signer:
       private_key_path = OPTIONS.package_key + OPTIONS.private_key_suffix
       if not os.path.exists(private_key_path):
         raise common.ExternalError(
@@ -1487,6 +1485,11 @@
             " correct key path through -k option".format(
                 private_key_path)
         )
+      signapk_abs_path = os.path.join(
+          OPTIONS.search_path, OPTIONS.signapk_path)
+      if not os.path.exists(signapk_abs_path):
+        raise common.ExternalError(
+            "Failed to find sign apk binary {} in search path {}. Make sure the correct search path is passed via -p".format(OPTIONS.signapk_path, OPTIONS.search_path))
 
   if OPTIONS.source_info_dict:
     source_build_prop = OPTIONS.source_info_dict["build.prop"]
diff --git a/tools/signapk/src/com/android/signapk/SignApk.java b/tools/signapk/src/com/android/signapk/SignApk.java
index 8bf1005..232e119 100644
--- a/tools/signapk/src/com/android/signapk/SignApk.java
+++ b/tools/signapk/src/com/android/signapk/SignApk.java
@@ -64,12 +64,19 @@
 import java.nio.ByteOrder;
 import java.nio.charset.StandardCharsets;
 import java.security.GeneralSecurityException;
+import java.security.NoSuchAlgorithmException;
 import java.security.Key;
 import java.security.KeyFactory;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.KeyStore.PrivateKeyEntry;
 import java.security.PrivateKey;
 import java.security.Provider;
 import java.security.Security;
+import java.security.UnrecoverableEntryException;
+import java.security.UnrecoverableKeyException;
 import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
 import java.security.spec.InvalidKeySpecException;
@@ -286,6 +293,32 @@
         }
     }
 
+    private static KeyStore createKeyStore(String keyStoreName, String keyStorePin) throws
+            CertificateException,
+            IOException,
+            KeyStoreException,
+            NoSuchAlgorithmException {
+        KeyStore keyStore = KeyStore.getInstance(keyStoreName);
+        keyStore.load(null, keyStorePin == null ? null : keyStorePin.toCharArray());
+        return keyStore;
+    }
+
+    /** Get a PKCS#11 private key from keyStore */
+    private static PrivateKey loadPrivateKeyFromKeyStore(
+            final KeyStore keyStore, final String keyName, final String password)
+            throws CertificateException, KeyStoreException, NoSuchAlgorithmException,
+                    UnrecoverableKeyException, UnrecoverableEntryException {
+        final Key key = keyStore.getKey(keyName, password == null ? null : password.toCharArray());
+        final PrivateKeyEntry privateKeyEntry = (PrivateKeyEntry) keyStore.getEntry(keyName, null);
+        if (privateKeyEntry == null) {
+        throw new Error(
+            "Key "
+                + keyName
+                + " not found in the token provided by PKCS11 library!");
+        }
+        return privateKeyEntry.getPrivateKey();
+    }
+
     /**
      * Add a copy of the public key to the archive; this should
      * exactly match one of the files in
@@ -1022,6 +1055,8 @@
                            "[-a <alignment>] " +
                            "[--align-file-size] " +
                            "[-providerClass <className>] " +
+                           "[-loadPrivateKeysFromKeyStore <keyStoreName>]" +
+                           "[-keyStorePin <pin>]" +
                            "[--min-sdk-version <n>] " +
                            "[--disable-v2] " +
                            "[--enable-v4] " +
@@ -1044,6 +1079,8 @@
 
         boolean signWholeFile = false;
         String providerClass = null;
+        String keyStoreName = null;
+        String keyStorePin = null;
         int alignment = 4;
         boolean alignFileSize = false;
         Integer minSdkVersionOverride = null;
@@ -1062,6 +1099,18 @@
                 }
                 providerClass = args[++argstart];
                 ++argstart;
+            } else if ("-loadPrivateKeysFromKeyStore".equals(args[argstart])) {
+                if (argstart + 1 >= args.length) {
+                    usage();
+                }
+                keyStoreName = args[++argstart];
+                ++argstart;
+            } else if ("-keyStorePin".equals(args[argstart])) {
+                if (argstart + 1 >= args.length) {
+                    usage();
+                }
+                keyStorePin = args[++argstart];
+                ++argstart;
             } else if ("-a".equals(args[argstart])) {
                 alignment = Integer.parseInt(args[++argstart]);
                 ++argstart;
@@ -1142,11 +1191,21 @@
             // timestamp using the current timezone. We thus adjust the milliseconds since epoch
             // value to end up with MS-DOS timestamp of Jan 1 2009 00:00:00.
             timestamp -= TimeZone.getDefault().getOffset(timestamp);
-
+            KeyStore keyStore = null;
+            if (keyStoreName != null) {
+                keyStore = createKeyStore(keyStoreName, keyStorePin);
+            }
             PrivateKey[] privateKey = new PrivateKey[numKeys];
             for (int i = 0; i < numKeys; ++i) {
                 int argNum = argstart + i*2 + 1;
-                privateKey[i] = readPrivateKey(new File(args[argNum]));
+                if (keyStore == null) {
+                    privateKey[i] = readPrivateKey(new File(args[argNum]));
+                } else {
+                    String[] splits = args[argNum].split(":", 2);
+                    final String keyAlias = splits[0];
+                    final String password = splits.length > 1 ? splits[1] : null;
+                    privateKey[i] = loadPrivateKeyFromKeyStore(keyStore, keyAlias, password);
+                }
             }
             inputJar = new JarFile(new File(inputFilename), false);  // Don't verify.