Merge "Add a core configuration that disables the zygote."
diff --git a/Android.bp b/Android.bp
deleted file mode 100644
index ab2564e..0000000
--- a/Android.bp
+++ /dev/null
@@ -1,49 +0,0 @@
-//
-// 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: ["build_make_license"],
-}
-
-// Added automatically by a large-scale-change that took the approach of
-// 'apply every license found to every target'. While this makes sure we respect
-// every license restriction, it may not be entirely correct.
-//
-// e.g. GPL in an MIT project might only apply to the contrib/ directory.
-//
-// Please consider splitting the single license below into multiple licenses,
-// taking care not to lose any license_kind information, and overriding the
-// default license using the 'licenses: [...]' property on targets as needed.
-//
-// For unused files, consider creating a 'fileGroup' with "//visibility:private"
-// to attach the license to, and including a comment whether the files may be
-// used in the current project.
-// See: http://go/android-license-faq
-license {
- name: "build_make_license",
- visibility: [":__subpackages__"],
- license_kinds: [
- "SPDX-license-identifier-Apache-2.0",
- "SPDX-license-identifier-BSD",
- "SPDX-license-identifier-CC-BY",
- "SPDX-license-identifier-GPL",
- "SPDX-license-identifier-GPL-2.0",
- "SPDX-license-identifier-LGPL",
- "SPDX-license-identifier-MIT",
- "legacy_not_a_contribution",
- "legacy_restricted",
- ],
- // large-scale-change unable to identify any license_text files
-}
diff --git a/CleanSpec.mk b/CleanSpec.mk
index fff49b3..957da92 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -763,6 +763,16 @@
# Most of SOONG_HOST_OUT_EXECUTABLES has been moved to HOST_OUT_EXECUTABLES
$(call add-clean-step, rm -rf $(SOONG_HOST_OUT))
+# More of SOONG_HOST_OUT_EXECUTABLES has been moved to HOST_OUT_EXECUTABLES
+$(call add-clean-step, rm -rf $(SOONG_HOST_OUT))
+
+# 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/common/math.mk b/common/math.mk
index ec15f88..0271ea8 100644
--- a/common/math.mk
+++ b/common/math.mk
@@ -121,14 +121,26 @@
$(lastword $(filter $(1) $(2),$(__MATH_NUMBERS))))
endef
+# Returns the lesser of $1 or $2.
+define math_min
+$(strip $(call _math_check_valid,$(1)) $(call _math_check_valid,$(2)) \
+ $(firstword $(filter $(1) $(2),$(__MATH_NUMBERS))))
+endef
+
$(call math-expect-error,(call math_max),Argument missing)
$(call math-expect-error,(call math_max,1),Argument missing)
$(call math-expect-error,(call math_max,1 2,3),Multiple words in a single argument: 1 2)
+$(call math-expect-error,(call math_min,1,2 3),Multiple words in a single argument: 2 3)
$(call math-expect,(call math_max,0,1),1)
$(call math-expect,(call math_max,1,0),1)
$(call math-expect,(call math_max,1,1),1)
$(call math-expect,(call math_max,5,42),42)
$(call math-expect,(call math_max,42,5),42)
+$(call math-expect,(call math_min,0,1),0)
+$(call math-expect,(call math_min,1,0),0)
+$(call math-expect,(call math_min,1,1),1)
+$(call math-expect,(call math_min,7,32),7)
+$(call math-expect,(call math_min,32,7),7)
define math_gt_or_eq
$(if $(filter $(1),$(call math_max,$(1),$(2))),true)
diff --git a/core/Makefile b/core/Makefile
index 486188e..b9103ab 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -575,6 +575,8 @@
$(if $(PACKAGES.$(p).EXTERNAL_KEY),\
$(call _apkcerts_write_line,$(PACKAGES.$(p).STEM),EXTERNAL,,$(PACKAGES.$(p).COMPRESSED),$(PACKAGES.$(p).PARTITION),$@),\
$(call _apkcerts_write_line,$(PACKAGES.$(p).STEM),$(PACKAGES.$(p).CERTIFICATE),$(PACKAGES.$(p).PRIVATE_KEY),$(PACKAGES.$(p).COMPRESSED),$(PACKAGES.$(p).PARTITION),$@))))
+ $(if $(filter true,$(PRODUCT_SYSTEM_FSVERITY_GENERATE_METADATA)),\
+ $(call _apkcerts_write_line,$(notdir $(basename $(FSVERITY_APK_OUT))),$(FSVERITY_APK_KEY_PATH).x509.pem,$(FSVERITY_APK_KEY_PATH).pk8,,system,$@))
# In case value of PACKAGES is empty.
$(hide) touch $@
@@ -772,6 +774,7 @@
$(INSTALLED_FILES_FILE_ROOT): .KATI_IMPLICIT_OUTPUTS := $(INSTALLED_FILES_JSON_ROOT)
$(INSTALLED_FILES_FILE_ROOT) : $(INTERNAL_ROOT_FILES) $(FILESLIST) $(FILESLIST_UTIL)
@echo Installed file list: $@
+ mkdir -p $(TARGET_ROOT_OUT)
mkdir -p $(dir $@)
rm -f $@
$(FILESLIST) $(TARGET_ROOT_OUT) > $(@:.txt=.json)
@@ -810,7 +813,7 @@
$(FILESLIST_UTIL) -c $(@:.txt=.json) > $@
ifeq ($(HOST_OS),linux)
-$(call dist-for-goals, sdk win_sdk sdk_addon, $(INSTALLED_FILES_FILE_RAMDISK))
+$(call dist-for-goals, sdk sdk_addon, $(INSTALLED_FILES_FILE_RAMDISK))
endif
BUILT_RAMDISK_TARGET := $(PRODUCT_OUT)/ramdisk.img
@@ -896,8 +899,16 @@
INTERNAL_BOOTIMAGE_ARGS := \
$(addprefix --second ,$(INSTALLED_2NDBOOTLOADER_TARGET))
+INTERNAL_INIT_BOOT_IMAGE_ARGS :=
+
+INTERNAL_BOOT_HAS_RAMDISK :=
ifneq ($(BOARD_BUILD_SYSTEM_ROOT_IMAGE),true)
-INTERNAL_BOOTIMAGE_ARGS += --ramdisk $(INSTALLED_RAMDISK_TARGET)
+ ifneq ($(BUILDING_INIT_BOOT_IMAGE),true)
+ INTERNAL_BOOTIMAGE_ARGS += --ramdisk $(INSTALLED_RAMDISK_TARGET)
+ INTERNAL_BOOT_HAS_RAMDISK := true
+ else
+ INTERNAL_INIT_BOOT_IMAGE_ARGS += --ramdisk $(INSTALLED_RAMDISK_TARGET)
+ endif
endif
ifndef BUILDING_VENDOR_BOOT_IMAGE
@@ -943,21 +954,48 @@
--os_version $(PLATFORM_VERSION_LAST_STABLE) \
--os_patch_level $(PLATFORM_SECURITY_PATCH)
-ifdef BOARD_GKI_SIGNING_KEY_PATH
-ifndef BOARD_GKI_SIGNING_ALGORITHM
-$(error BOARD_GKI_SIGNING_ALGORITHM should be defined with BOARD_GKI_SIGNING_KEY_PATH)
-endif
-INTERNAL_MKBOOTIMG_GKI_SINGING_ARGS := \
- --gki_signing_key $(BOARD_GKI_SIGNING_KEY_PATH) \
- --gki_signing_algorithm $(BOARD_GKI_SIGNING_ALGORITHM) \
- --gki_signing_avbtool_path $(AVBTOOL)
-endif
+# $(1): image target to certify
+# $(2): out certificate target
+# $(3): image name
+# $(4): additional AVB arguments
+define generate_generic_boot_image_certificate
+ rm -rf "$(2)"
+ mkdir -p "$(dir $(2))"
+ $(GENERATE_GKI_CERTIFICATE) $(INTERNAL_GKI_CERTIFICATE_ARGS) \
+ --additional_avb_args "$(4)" \
+ --name "$(3)" --output "$(2)" "$(1)"
+endef
-# Using double quote to pass BOARD_GKI_SIGNING_SIGNATURE_ARGS as a single string
-# to MKBOOTIMG, although it may contain multiple args.
-ifdef BOARD_GKI_SIGNING_SIGNATURE_ARGS
-INTERNAL_MKBOOTIMG_GKI_SINGING_ARGS += \
- --gki_signing_signature_args "$(BOARD_GKI_SIGNING_SIGNATURE_ARGS)"
+INTERNAL_GKI_CERTIFICATE_ARGS :=
+INTERNAL_GKI_CERTIFICATE_DEPS :=
+INTERNAL_GENERIC_RAMDISK_BOOT_SIGNATURE :=
+ifdef BOARD_GKI_SIGNING_KEY_PATH
+ ifndef BOARD_GKI_SIGNING_ALGORITHM
+ $(error BOARD_GKI_SIGNING_ALGORITHM should be defined with BOARD_GKI_SIGNING_KEY_PATH)
+ endif
+
+ INTERNAL_GKI_CERTIFICATE_ARGS := \
+ --key "$(BOARD_GKI_SIGNING_KEY_PATH)" \
+ --algorithm "$(BOARD_GKI_SIGNING_ALGORITHM)" \
+ --avbtool "$(AVBTOOL)"
+
+ # Quote and pass BOARD_GKI_SIGNING_SIGNATURE_ARGS as a single string argument.
+ ifdef BOARD_GKI_SIGNING_SIGNATURE_ARGS
+ INTERNAL_GKI_CERTIFICATE_ARGS += --additional_avb_args "$(BOARD_GKI_SIGNING_SIGNATURE_ARGS)"
+ endif
+
+ INTERNAL_GKI_CERTIFICATE_DEPS := \
+ $(GENERATE_GKI_CERTIFICATE) \
+ $(BOARD_GKI_SIGNING_KEY_PATH) \
+ $(AVBTOOL)
+
+ ifdef INSTALLED_RAMDISK_TARGET
+ INTERNAL_GENERIC_RAMDISK_BOOT_SIGNATURE := \
+ $(call intermediates-dir-for,PACKAGING,generic_ramdisk)/boot_signature
+
+ $(INTERNAL_GENERIC_RAMDISK_BOOT_SIGNATURE): $(INSTALLED_RAMDISK_TARGET) $(INTERNAL_GKI_CERTIFICATE_DEPS)
+ $(call generate_generic_boot_image_certificate,$(INSTALLED_RAMDISK_TARGET),$@,generic_ramdisk,$(BOARD_AVB_INIT_BOOT_ADD_HASH_FOOTER_ARGS))
+ endif
endif
# Define these only if we are building boot
@@ -974,8 +1012,15 @@
# $1: boot image target
define build_boot_board_avb_enabled
- $(MKBOOTIMG) --kernel $(call bootimage-to-kernel,$(1)) $(INTERNAL_BOOTIMAGE_ARGS) $(INTERNAL_MKBOOTIMG_VERSION_ARGS) \
- $(INTERNAL_MKBOOTIMG_GKI_SINGING_ARGS) $(BOARD_MKBOOTIMG_ARGS) --output $(1)
+ $(eval kernel := $(call bootimage-to-kernel,$(1)))
+ $(if $(BOARD_GKI_SIGNING_KEY_PATH), \
+ $(eval kernel_signature := $(call intermediates-dir-for,PACKAGING,generic_kernel)/$(notdir $(kernel)).boot_signature) \
+ $(call generate_generic_boot_image_certificate,$(kernel),$(kernel_signature),generic_kernel,$(BOARD_AVB_BOOT_ADD_HASH_FOOTER_ARGS)) $(newline) \
+ $(if $(INTERNAL_BOOT_HAS_RAMDISK), \
+ cat $(INTERNAL_GENERIC_RAMDISK_BOOT_SIGNATURE) >> $(kernel_signature) $(newline)))
+ $(MKBOOTIMG) --kernel $(kernel) $(INTERNAL_BOOTIMAGE_ARGS) \
+ $(if $(BOARD_GKI_SIGNING_KEY_PATH),--boot_signature "$(kernel_signature)",$(INTERNAL_MKBOOTIMG_VERSION_ARGS)) \
+ $(BOARD_MKBOOTIMG_ARGS) --output $(1)
$(call assert-max-image-size,$(1),$(call get-hash-image-max-size,$(call get-bootimage-partition-size,$(1),boot)))
$(AVBTOOL) add_hash_footer \
--image $(1) \
@@ -984,12 +1029,15 @@
$(BOARD_AVB_BOOT_ADD_HASH_FOOTER_ARGS)
endef
-$(INSTALLED_BOOTIMAGE_TARGET): $(MKBOOTIMG) $(AVBTOOL) $(INTERNAL_BOOTIMAGE_FILES) $(BOARD_AVB_BOOT_KEY_PATH) $(BOARD_GKI_SIGNING_KEY_PATH)
+ifdef INTERNAL_BOOT_HAS_RAMDISK
+$(INSTALLED_BOOTIMAGE_TARGET): $(INTERNAL_GENERIC_RAMDISK_BOOT_SIGNATURE)
+endif
+$(INSTALLED_BOOTIMAGE_TARGET): $(MKBOOTIMG) $(AVBTOOL) $(INTERNAL_BOOTIMAGE_FILES) $(BOARD_AVB_BOOT_KEY_PATH) $(INTERNAL_GKI_CERTIFICATE_DEPS)
$(call pretty,"Target boot image: $@")
$(call build_boot_board_avb_enabled,$@)
.PHONY: bootimage-nodeps
-bootimage-nodeps: $(MKBOOTIMG) $(AVBTOOL) $(BOARD_AVB_BOOT_KEY_PATH) $(BOARD_GKI_SIGNING_KEY_PATH)
+bootimage-nodeps: $(MKBOOTIMG) $(AVBTOOL) $(BOARD_AVB_BOOT_KEY_PATH) $(INTERNAL_GKI_CERTIFICATE_DEPS)
@echo "make $@: ignoring dependencies"
$(foreach b,$(INSTALLED_BOOTIMAGE_TARGET),$(call build_boot_board_avb_enabled,$(b)))
@@ -1077,6 +1125,62 @@
my_installed_prebuilt_gki_apex :=
# -----------------------------------------------------------------
+# init boot image
+ifeq ($(BUILDING_INIT_BOOT_IMAGE),true)
+
+INSTALLED_INIT_BOOT_IMAGE_TARGET := $(PRODUCT_OUT)/init_boot.img
+$(INSTALLED_INIT_BOOT_IMAGE_TARGET): $(MKBOOTIMG) $(INSTALLED_RAMDISK_TARGET)
+
+ifdef BOARD_KERNEL_PAGESIZE
+ INTERNAL_INIT_BOOT_IMAGE_ARGS += --pagesize $(BOARD_KERNEL_PAGESIZE)
+endif
+
+ifeq ($(BOARD_AVB_ENABLE),true)
+$(INSTALLED_INIT_BOOT_IMAGE_TARGET): $(INTERNAL_GENERIC_RAMDISK_BOOT_SIGNATURE)
+$(INSTALLED_INIT_BOOT_IMAGE_TARGET): $(AVBTOOL) $(BOARD_AVB_INIT_BOOT_KEY_PATH)
+ $(call pretty,"Target init_boot image: $@")
+ $(MKBOOTIMG) $(INTERNAL_INIT_BOOT_IMAGE_ARGS) \
+ $(if $(BOARD_GKI_SIGNING_KEY_PATH),--boot_signature "$(INTERNAL_GENERIC_RAMDISK_BOOT_SIGNATURE)",$(INTERNAL_MKBOOTIMG_VERSION_ARGS)) \
+ $(BOARD_MKBOOTIMG_INIT_ARGS) --output "$@"
+ $(call assert-max-image-size,$@,$(BOARD_INIT_BOOT_IMAGE_PARTITION_SIZE))
+ $(AVBTOOL) add_hash_footer \
+ --image $@ \
+ --partition_size $(BOARD_INIT_BOOT_IMAGE_PARTITION_SIZE) \
+ --partition_name init_boot $(INTERNAL_AVB_INIT_BOOT_SIGNING_ARGS) \
+ $(BOARD_AVB_INIT_BOOT_ADD_HASH_FOOTER_ARGS)
+else
+$(INSTALLED_INIT_BOOT_IMAGE_TARGET):
+ $(call pretty,"Target init_boot image: $@")
+ $(MKBOOTIMG) $(INTERNAL_INIT_BOOT_IMAGE_ARGS) $(INTERNAL_MKBOOTIMG_VERSION_ARGS) $(BOARD_MKBOOTIMG_INIT_ARGS) --output $@
+ $(call assert-max-image-size,$@,$(BOARD_INIT_BOOT_IMAGE_PARTITION_SIZE))
+endif
+
+else # BUILDING_INIT_BOOT_IMAGE is not true
+
+ifdef BOARD_PREBUILT_INIT_BOOT_IMAGE
+INTERNAL_PREBUILT_INIT_BOOT_IMAGE := $(BOARD_PREBUILT_INIT_BOOT_IMAGE)
+INSTALLED_INIT_BOOT_IMAGE_TARGET := $(PRODUCT_OUT)/init_boot.img
+
+ifeq ($(BOARD_AVB_ENABLE),true)
+$(INSTALLED_INIT_BOOT_IMAGE_TARGET): $(INTERNAL_PREBUILT_INIT_BOOT_IMAGE) $(AVBTOOL) $(BOARD_AVB_INIT_BOOT_KEY_PATH)
+ cp $(INTERNAL_PREBUILT_INIT_BOOT_IMAGE) $@
+ $(AVBTOOL) add_hash_footer \
+ --image $@ \
+ --partition_size $(BOARD_INIT_BOOT_IMAGE_PARTITION_SIZE) \
+ --partition_name boot $(INTERNAL_AVB_INIT_BOOT_SIGNING_ARGS) \
+ $(BOARD_AVB_INIT_BOOT_ADD_HASH_FOOTER_ARGS)
+else
+$(INSTALLED_INIT_BOOT_IMAGE_TARGET): $(INTERNAL_PREBUILT_INIT_BOOT_IMAGE)
+ cp $(INTERNAL_PREBUILT_INIT_BOOT_IMAGE) $@
+endif # BOARD_AVB_ENABLE
+
+else # BOARD_PREBUILT_INIT_BOOT_IMAGE not defined
+INSTALLED_INIT_BOOT_IMAGE_TARGET :=
+endif # BOARD_PREBUILT_INIT_BOOT_IMAGE
+
+endif # BUILDING_INIT_BOOT_IMAGE is not true
+
+# -----------------------------------------------------------------
# vendor boot image
ifeq ($(BUILDING_VENDOR_BOOT_IMAGE),true)
@@ -1329,6 +1433,11 @@
target_odm_dlkm_notice_file_xml_gz := $(TARGET_OUT_INTERMEDIATES)/NOTICE_ODM_DLKM.xml.gz
installed_odm_dlkm_notice_xml_gz := $(TARGET_OUT_ODM_DLKM)/etc/NOTICE.xml.gz
+target_system_dlkm_notice_file_txt := $(TARGET_OUT_INTERMEDIATES)/NOTICE_SYSTEM_DLKM.txt
+target_system_dlkm_notice_file_xml := $(TARGET_OUT_INTERMEDIATES)/NOTICE_SYSTEM_DLKM.xml
+target_system_dlkm_notice_file_xml_gz := $(TARGET_OUT_INTERMEDIATES)/NOTICE_SYSTEM_DLKM.xml.gz
+installed_system_dlkm_notice_xml_gz := $(TARGET_OUT_SYSTEM_DLKM)/etc/NOTICE.xml.gz
+
# Notice files are copied to TARGET_OUT_NOTICE_FILES as a side-effect of their module
# being built. A notice xml file must depend on all modules that could potentially
# install a license file relevant to it.
@@ -1349,13 +1458,15 @@
license_modules_odm := $(filter $(TARGET_OUT_ODM)/%,$(license_modules))
license_modules_vendor_dlkm := $(filter $(TARGET_OUT_VENDOR_DLKM)/%,$(license_modules))
license_modules_odm_dlkm := $(filter $(TARGET_OUT_ODM_DLKM)/%,$(license_modules))
+license_modules_odm_dlkm := $(filter $(TARGET_OUT_SYSTEM_DLKM)/%,$(license_modules))
license_modules_agg := $(license_modules_system) \
$(license_modules_vendor) \
$(license_modules_product) \
$(license_modules_system_ext) \
$(license_modules_odm) \
$(license_modules_vendor_dlkm) \
- $(license_modules_odm_dlkm)
+ $(license_modules_odm_dlkm) \
+ $(license_modules_system_dlkm)
# targets used for debug symbols only and do not get copied to the device
license_modules_symbols_only := $(filter $(PRODUCT_OUT)/apex/%,$(license_modules))
@@ -1447,6 +1558,13 @@
$(TARGET_OUT_NOTICE_FILES), \
$(license_modules_odm_dlkm), \
$(exclude_target_dirs)))
+$(eval $(call combine-notice-files, xml_system_dlkm, \
+ $(target_system_dlkm_notice_file_txt), \
+ $(target_system_dlkm_notice_file_xml), \
+ "Notices for files contained in the system_dlkm filesystem image in this directory:", \
+ $(TARGET_OUT_NOTICE_FILES), \
+ $(license_modules_system_dlkm), \
+ $(exclude_target_dirs)))
$(target_notice_file_xml_gz): $(target_notice_file_xml) | $(MINIGZIP)
$(hide) $(MINIGZIP) -9 < $< > $@
@@ -1462,6 +1580,8 @@
$(hide) $(MINIGZIP) -9 < $< > $@
$(target_odm_dlkm_notice_file_xml_gz): $(target_odm_dlkm_notice_file_xml) | $(MINIGZIP)
$(hide) $(MINIGZIP) -9 < $< > $@
+$(target_system_dlkm_notice_file_xml_gz): $(target_system_dlkm_notice_file_xml) | $(MINIGZIP)
+ $(hide) $(MINIGZIP) -9 < $< > $@
$(installed_notice_html_or_xml_gz): $(target_notice_file_xml_gz)
$(copy-file-to-target)
$(installed_vendor_notice_xml_gz): $(target_vendor_notice_file_xml_gz)
@@ -1476,6 +1596,8 @@
$(copy-file-to-target)
$(installed_odm_dlkm_notice_xml_gz): $(target_odm_dlkm_notice_file_xml_gz)
$(copy-file-to-target)
+$(installed_system_dlkm_notice_xml_gz): $(target_system_dlkm_notice_file_xml_gz)
+ $(copy-file-to-target)
$(call declare-0p-target,$(target_notice_file_xml))
$(call declare-0p-target,$(target_notice_file_xml_gz))
@@ -1491,6 +1613,8 @@
$(call declare-0p-target,$(target_vendor_dlkm_notice_file_xml_gz))
$(call declare-0p-target,$(target_odm_dlkm_notice_file_xml))
$(call declare-0p-target,$(target_odm_dlkm_notice_file_xml_gz))
+$(call declare-0p-target,$(target_system_dlkm_notice_file_xml))
+$(call declare-0p-target,$(target_system_dlkm_notice_file_xml_gz))
$(call declare-0p-target,$(installed_notice_html_or_xml_gz))
$(call declare-0p-target,$(installed_vendor_notice_xml_gz))
$(call declare-0p-target,$(installed_product_notice_xml_gz))
@@ -1498,6 +1622,7 @@
$(call declare-0p-target,$(installed_odm_notice_xml_gz))
$(call declare-0p-target,$(installed_vendor_dlkm_notice_xml_gz))
$(call declare-0p-target,$(installed_odm_dlkm_notice_xml_gz))
+$(call declare-0p-target,$(installed_sysetm_dlkm_notice_xml_gz))
ALL_DEFAULT_INSTALLED_MODULES += $(installed_notice_html_or_xml_gz)
ALL_DEFAULT_INSTALLED_MODULES += $(installed_vendor_notice_xml_gz)
@@ -1506,6 +1631,7 @@
ALL_DEFAULT_INSTALLED_MODULES += $(installed_odm_notice_xml_gz)
ALL_DEFAULT_INSTALLED_MODULES += $(installed_vendor_dlkm_notice_xml_gz)
ALL_DEFAULT_INSTALLED_MODULES += $(installed_odm_dlkm_notice_xml_gz)
+ALL_DEFAULT_INSTALLED_MODULES += $(installed_system_dlkm_notice_xml_gz)
endif # PRODUCT_NOTICE_SPLIT
ALL_DEFAULT_INSTALLED_MODULES += $(installed_notice_html_or_xml_gz)
@@ -1583,8 +1709,10 @@
$(BOARD_SYSTEMIMAGE_FILE_SYSTEM_TYPE) \
$(BOARD_VENDOR_DLKMIMAGE_FILE_SYSTEM_TYPE) \
$(BOARD_ODM_DLKMIMAGE_FILE_SYSTEM_TYPE) \
+ $(BOARD_SYSTEM_DLKMIMAGE_FILE_SYSTEM_TYPE) \
,erofs),)
INTERNAL_USERIMAGES_DEPS += $(MKEROFSUSERIMG)
+BOARD_EROFS_COMPRESSOR ?= "lz4hc,9"
endif
ifneq ($(filter \
@@ -1595,6 +1723,7 @@
$(BOARD_SYSTEMIMAGE_FILE_SYSTEM_TYPE) \
$(BOARD_VENDOR_DLKMIMAGE_FILE_SYSTEM_TYPE) \
$(BOARD_ODM_DLKMIMAGE_FILE_SYSTEM_TYPE) \
+ $(BOARD_SYSTEM_DLKMIMAGE_FILE_SYSTEM_TYPE) \
,squashfs),)
INTERNAL_USERIMAGES_DEPS += $(MKSQUASHFSUSERIMG)
endif
@@ -1666,7 +1795,7 @@
endef
# $(1): the path of the output dictionary file
-# $(2): a subset of "system vendor cache userdata product system_ext oem odm vendor_dlkm odm_dlkm"
+# $(2): a subset of "system vendor cache userdata product system_ext oem odm vendor_dlkm odm_dlkm system_dlkm"
# $(3): additional "key=value" pairs to append to the dictionary file.
define generate-image-prop-dictionary
$(if $(filter $(2),system),\
@@ -1710,6 +1839,9 @@
$(if $(filter $(2),odm_dlkm),\
$(call add-common-ro-flags-to-image-props,odm_dlkm,$(1))
)
+$(if $(filter $(2),system_dlkm),\
+ $(call add-common-ro-flags-to-image-props,system_dlkm,$(1))
+)
$(if $(filter $(2),oem),\
$(if $(BOARD_OEMIMAGE_PARTITION_SIZE),$(hide) echo "oem_size=$(BOARD_OEMIMAGE_PARTITION_SIZE)" >> $(1))
$(if $(BOARD_OEMIMAGE_JOURNAL_SIZE),$(hide) echo "oem_journal_size=$(BOARD_OEMIMAGE_JOURNAL_SIZE)" >> $(1))
@@ -1742,6 +1874,7 @@
$(if $(PRODUCT_SYSTEM_EXT_VERITY_PARTITION),$(hide) echo "system_ext_verity_block_device=$(PRODUCT_SYSTEM_EXT_VERITY_PARTITION)" >> $(1))
$(if $(PRODUCT_VENDOR_DLKM_VERITY_PARTITION),$(hide) echo "vendor_dlkm_verity_block_device=$(PRODUCT_VENDOR_DLKM_VERITY_PARTITION)" >> $(1))
$(if $(PRODUCT_ODM_DLKM_VERITY_PARTITION),$(hide) echo "odm_dlkm_verity_block_device=$(PRODUCT_ODM_DLKM_VERITY_PARTITION)" >> $(1))
+$(if $(PRODUCT_SYSTEM_DLKM_VERITY_PARTITION),$(hide) echo "system_dlkm_verity_block_device=$(PRODUCT_SYSTEM_DLKM_VERITY_PARTITION)" >> $(1))
$(if $(PRODUCT_SUPPORTS_VBOOT),$(hide) echo "vboot=$(PRODUCT_SUPPORTS_VBOOT)" >> $(1))
$(if $(PRODUCT_SUPPORTS_VBOOT),$(hide) echo "vboot_key=$(PRODUCT_VBOOT_SIGNING_KEY)" >> $(1))
$(if $(PRODUCT_SUPPORTS_VBOOT),$(hide) echo "vboot_subkey=$(PRODUCT_VBOOT_SIGNING_SUBKEY)" >> $(1))
@@ -1806,6 +1939,14 @@
$(hide) echo "avb_odm_dlkm_key_path=$(BOARD_AVB_ODM_DLKM_KEY_PATH)" >> $(1)
$(hide) echo "avb_odm_dlkm_algorithm=$(BOARD_AVB_ODM_DLKM_ALGORITHM)" >> $(1)
$(hide) echo "avb_odm_dlkm_rollback_index_location=$(BOARD_AVB_ODM_DLKM_ROLLBACK_INDEX_LOCATION)" >> $(1)))
+$(if $(BOARD_AVB_ENABLE),$(hide) echo "avb_system_dlkm_hashtree_enable=$(BOARD_AVB_ENABLE)" >> $(1))
+$(if $(BOARD_AVB_ENABLE),\
+ $(hide) echo "avb_system_dlkm_add_hashtree_footer_args=$(BOARD_AVB_SYSTEM_DLKM_ADD_HASHTREE_FOOTER_ARGS)" >> $(1))
+$(if $(BOARD_AVB_ENABLE),\
+ $(if $(BOARD_AVB_SYSTEM_DLKM_KEY_PATH),\
+ $(hide) echo "avb_system_dlkm_key_path=$(BOARD_AVB_SYSTEM_DLKM_KEY_PATH)" >> $(1)
+ $(hide) echo "avb_system_dlkm_algorithm=$(BOARD_AVB_SYSTEM_DLKM_ALGORITHM)" >> $(1)
+ $(hide) echo "avb_system_dlkm_rollback_index_location=$(BOARD_SYSTEM_SYSTEM_DLKM_ROLLBACK_INDEX_LOCATION)" >> $(1)))
$(if $(filter true,$(BOARD_USES_RECOVERY_AS_BOOT)),\
$(hide) echo "recovery_as_boot=true" >> $(1))
$(if $(filter true,$(BOARD_BUILD_SYSTEM_ROOT_IMAGE)),\
@@ -1849,6 +1990,9 @@
ifdef BUILDING_ODM_DLKM_IMAGE
PROP_DICTIONARY_IMAGES += odm_dlkm
endif
+ifdef BUILDING_SYSTEM_DLKM_IMAGE
+ PROP_DICTIONARY_IMAGES += system_dlkm
+endif
define generate-userimage-prop-dictionary
$(call generate-image-prop-dictionary,$(1),$(PROP_DICTIONARY_IMAGES),$(2))
endef
@@ -1892,14 +2036,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
@@ -2051,6 +2199,9 @@
ifdef TARGET_RECOVERY_FSTAB
recovery_fstab := $(TARGET_RECOVERY_FSTAB)
+else ifdef TARGET_RECOVERY_FSTAB_GENRULE
+# Specifies a soong genrule module that generates an fstab.
+recovery_fstab := $(call intermediates-dir-for,ETC,$(TARGET_RECOVERY_FSTAB_GENRULE))/$(TARGET_RECOVERY_FSTAB_GENRULE)
else
recovery_fstab := $(strip $(wildcard $(TARGET_DEVICE_DIR)/recovery.fstab))
endif
@@ -2229,7 +2380,7 @@
$(INTERNAL_MKBOOTIMG_VERSION_ARGS) $(BOARD_RECOVERY_MKBOOTIMG_ARGS) \
--output $(1).unsigned, \
$(MKBOOTIMG) $(if $(strip $(2)),--kernel $(strip $(2))) $(INTERNAL_RECOVERYIMAGE_ARGS) \
- $(INTERNAL_MKBOOTIMG_VERSION_ARGS) $(INTERNAL_MKBOOTIMG_GKI_SINGING_ARGS) \
+ $(INTERNAL_MKBOOTIMG_VERSION_ARGS) \
$(BOARD_RECOVERY_MKBOOTIMG_ARGS) --output $(1))
$(if $(filter true,$(PRODUCT_SUPPORTS_BOOT_SIGNER)),\
$(if $(filter true,$(BOARD_USES_RECOVERY_AS_BOOT)),\
@@ -2258,9 +2409,6 @@
ifeq (true,$(BOARD_AVB_ENABLE))
recoveryimage-deps += $(AVBTOOL) $(BOARD_AVB_BOOT_KEY_PATH)
endif
-ifdef BOARD_GKI_SIGNING_KEY_PATH
- recoveryimage-deps += $(BOARD_GKI_SIGNING_KEY_PATH) $(AVBTOOL)
-endif
ifdef BOARD_INCLUDE_RECOVERY_DTBO
ifdef BOARD_PREBUILT_RECOVERY_DTBOIMAGE
recoveryimage-deps += $(BOARD_PREBUILT_RECOVERY_DTBOIMAGE)
@@ -2424,17 +2572,17 @@
define build-debug-bootimage-target
$(MKBOOTIMG) --kernel $(PRODUCT_OUT)/$(subst .img,,$(subst boot-debug,kernel,$(notdir $(1)))) \
$(INTERNAL_DEBUG_BOOTIMAGE_ARGS) $(INTERNAL_MKBOOTIMG_VERSION_ARGS) \
- $(INTERNAL_MKBOOTIMG_GKI_SINGING_ARGS) $(BOARD_MKBOOTIMG_ARGS) --output $1
+ $(BOARD_MKBOOTIMG_ARGS) --output $1
$(if $(BOARD_AVB_BOOT_KEY_PATH),$(call test-key-sign-bootimage,$1,boot-debug))
endef
# Depends on original boot.img and ramdisk-debug.img, to build the new boot-debug.img
-$(INSTALLED_DEBUG_BOOTIMAGE_TARGET): $(MKBOOTIMG) $(INSTALLED_BOOTIMAGE_TARGET) $(BOARD_GKI_SIGNING_KEY_PATH) $(AVBTOOL)
+$(INSTALLED_DEBUG_BOOTIMAGE_TARGET): $(MKBOOTIMG) $(INSTALLED_BOOTIMAGE_TARGET) $(AVBTOOL)
$(call pretty,"Target boot debug image: $@")
$(call build-debug-bootimage-target, $@)
.PHONY: bootimage_debug-nodeps
-bootimage_debug-nodeps: $(MKBOOTIMG) $(BOARD_GKI_SIGNING_KEY_PATH) $(AVBTOOL)
+bootimage_debug-nodeps: $(MKBOOTIMG) $(AVBTOOL)
echo "make $@: ignoring dependencies"
$(foreach b,$(INSTALLED_DEBUG_BOOTIMAGE_TARGET),$(call build-debug-bootimage-target,$b))
@@ -2601,17 +2749,17 @@
define build-boot-test-harness-target
$(MKBOOTIMG) --kernel $(PRODUCT_OUT)/$(subst .img,,$(subst boot-test-harness,kernel,$(notdir $(1)))) \
$(INTERNAL_TEST_HARNESS_BOOTIMAGE_ARGS) $(INTERNAL_MKBOOTIMG_VERSION_ARGS) \
- $(INTERNAL_MKBOOTIMG_GKI_SINGING_ARGS) $(BOARD_MKBOOTIMG_ARGS) --output $@
+ $(BOARD_MKBOOTIMG_ARGS) --output $@
$(if $(BOARD_AVB_BOOT_KEY_PATH),$(call test-key-sign-bootimage,$@,boot-test-harness))
endef
# Build the new boot-test-harness.img, based on boot-debug.img and ramdisk-test-harness.img.
-$(INSTALLED_TEST_HARNESS_BOOTIMAGE_TARGET): $(MKBOOTIMG) $(INSTALLED_DEBUG_BOOTIMAGE_TARGET) $(BOARD_GKI_SIGNING_KEY_PATH) $(AVBTOOL)
+$(INSTALLED_TEST_HARNESS_BOOTIMAGE_TARGET): $(MKBOOTIMG) $(INSTALLED_DEBUG_BOOTIMAGE_TARGET) $(AVBTOOL)
$(call pretty,"Target boot test harness image: $@")
$(call build-boot-test-harness-target,$@)
.PHONY: bootimage_test_harness-nodeps
-bootimage_test_harness-nodeps: $(MKBOOTIMG) $(BOARD_GKI_SIGNING_KEY_PATH) $(AVBTOOL)
+bootimage_test_harness-nodeps: $(MKBOOTIMG) $(AVBTOOL)
echo "make $@: ignoring dependencies"
$(foreach b,$(INSTALLED_TEST_HARNESS_BOOTIMAGE_TARGET),$(call build-boot-test-harness-target,$b))
@@ -2687,6 +2835,63 @@
# -----------------------------------------------------------------
# system image
+# FSVerity metadata generation
+# Generate fsverity metadata files (.fsv_meta) and build manifest
+# (system/etc/security/fsverity/BuildManifest.apk) BEFORE filtering systemimage files below
+ifeq ($(PRODUCT_SYSTEM_FSVERITY_GENERATE_METADATA),true)
+
+# Generate fsv_meta
+fsverity-metadata-targets := $(sort $(filter \
+ $(TARGET_OUT)/framework/% \
+ $(TARGET_OUT)/etc/boot-image.prof \
+ $(TARGET_OUT)/etc/dirty-image-objects \
+ $(TARGET_OUT)/etc/classpaths/%.pb, \
+ $(ALL_GENERATED_SOURCES) $(ALL_DEFAULT_INSTALLED_MODULES)))
+
+define fsverity-generate-metadata
+$(1).fsv_meta: PRIVATE_SRC := $(1)
+$(1).fsv_meta: PRIVATE_FSVERITY := $(HOST_OUT_EXECUTABLES)/fsverity
+$(1).fsv_meta: $(HOST_OUT_EXECUTABLES)/fsverity_metadata_generator $(HOST_OUT_EXECUTABLES)/fsverity $(1)
+ $$< --fsverity-path $$(PRIVATE_FSVERITY) --signature none \
+ --hash-alg sha256 --output $$@ $$(PRIVATE_SRC)
+endef
+
+$(foreach f,$(fsverity-metadata-targets),$(eval $(call fsverity-generate-metadata,$(f))))
+ALL_DEFAULT_INSTALLED_MODULES += $(addsuffix .fsv_meta,$(fsverity-metadata-targets))
+
+# Generate BuildManifest.apk
+FSVERITY_APK_KEY_PATH := $(DEFAULT_SYSTEM_DEV_CERTIFICATE)
+FSVERITY_APK_OUT := $(TARGET_OUT)/etc/security/fsverity/BuildManifest.apk
+FSVERITY_APK_MANIFEST_PATH := system/security/fsverity/AndroidManifest.xml
+$(FSVERITY_APK_OUT): PRIVATE_FSVERITY := $(HOST_OUT_EXECUTABLES)/fsverity
+$(FSVERITY_APK_OUT): PRIVATE_AAPT2 := $(HOST_OUT_EXECUTABLES)/aapt2
+$(FSVERITY_APK_OUT): PRIVATE_MIN_SDK_VERSION := $(DEFAULT_APP_TARGET_SDK)
+$(FSVERITY_APK_OUT): PRIVATE_VERSION_CODE := $(PLATFORM_SDK_VERSION)
+$(FSVERITY_APK_OUT): PRIVATE_VERSION_NAME := $(APPS_DEFAULT_VERSION_NAME)
+$(FSVERITY_APK_OUT): PRIVATE_APKSIGNER := $(HOST_OUT_EXECUTABLES)/apksigner
+$(FSVERITY_APK_OUT): PRIVATE_MANIFEST := $(FSVERITY_APK_MANIFEST_PATH)
+$(FSVERITY_APK_OUT): PRIVATE_FRAMEWORK_RES := $(call intermediates-dir-for,APPS,framework-res,,COMMON)/package-export.apk
+$(FSVERITY_APK_OUT): PRIVATE_KEY := $(FSVERITY_APK_KEY_PATH)
+$(FSVERITY_APK_OUT): PRIVATE_INPUTS := $(fsverity-metadata-targets)
+$(FSVERITY_APK_OUT): $(HOST_OUT_EXECUTABLES)/fsverity_manifest_generator \
+ $(HOST_OUT_EXECUTABLES)/fsverity $(HOST_OUT_EXECUTABLES)/aapt2 \
+ $(HOST_OUT_EXECUTABLES)/apksigner $(FSVERITY_APK_MANIFEST_PATH) \
+ $(FSVERITY_APK_KEY_PATH).x509.pem $(FSVERITY_APK_KEY_PATH).pk8 \
+ $(call intermediates-dir-for,APPS,framework-res,,COMMON)/package-export.apk \
+ $(fsverity-metadata-targets)
+ $< --fsverity-path $(PRIVATE_FSVERITY) --aapt2-path $(PRIVATE_AAPT2) \
+ --min-sdk-version $(PRIVATE_MIN_SDK_VERSION) \
+ --version-code $(PRIVATE_VERSION_CODE) \
+ --version-name $(PRIVATE_VERSION_NAME) \
+ --apksigner-path $(PRIVATE_APKSIGNER) --apk-key-path $(PRIVATE_KEY) \
+ --apk-manifest-path $(PRIVATE_MANIFEST) --framework-res $(PRIVATE_FRAMEWORK_RES) \
+ --output $@ \
+ --base-dir $(PRODUCT_OUT) $(PRIVATE_INPUTS)
+
+ALL_DEFAULT_INSTALLED_MODULES += $(FSVERITY_APK_OUT)
+
+endif # PRODUCT_SYSTEM_FSVERITY_GENERATE_METADATA
+
INTERNAL_SYSTEMIMAGE_FILES := $(sort $(filter $(TARGET_OUT)/%, \
$(ALL_GENERATED_SOURCES) \
$(ALL_DEFAULT_INSTALLED_MODULES)))
@@ -2706,6 +2911,19 @@
INTERNAL_SYSTEMIMAGE_FILES += $(call create-partition-compat-symlink,$(TARGET_OUT)/system_ext,/system_ext,system_ext.img)
endif
+# -----------------------------------------------------------------
+# system_dlkm partition image
+
+# Create symlinks for system_dlkm on devices with a system_dlkm partition:
+# /system/lib/modules -> /system_dlkm/lib/modules
+#
+# On devices with a system_dlkm partition,
+# - /system/lib/modules is a symlink to a directory that stores system DLKMs.
+# - The system_dlkm partition is mounted at /system_dlkm at runtime.
+ifdef BOARD_USES_SYSTEM_DLKMIMAGE
+ INTERNAL_SYSTEMIMAGE_FILES += $(call create-partition-compat-symlink,$(TARGET_OUT)/lib/modules,/system_dlkm/lib/modules,system_dlkm.img)
+endif
+
FULL_SYSTEMIMAGE_DEPS := $(INTERNAL_SYSTEMIMAGE_FILES) $(INTERNAL_USERIMAGES_DEPS)
# ASAN libraries in the system image - add dependency.
@@ -3397,6 +3615,60 @@
$(eval $(call copy-one-file,$(BOARD_PREBUILT_ODM_DLKMIMAGE),$(INSTALLED_ODM_DLKMIMAGE_TARGET)))
endif
+# -----------------------------------------------------------------
+# system_dlkm partition image
+
+ifdef BUILDING_SYSTEM_DLKM_IMAGE
+
+INTERNAL_SYSTEM_DLKMIMAGE_FILES := \
+ $(filter $(TARGET_OUT_SYSTEM_DLKM)/%,\
+ $(ALL_DEFAULT_INSTALLED_MODULES))
+
+INSTALLED_FILES_FILE_SYSTEM_DLKM := $(PRODUCT_OUT)/installed-files-system_dlkm.txt
+INSTALLED_FILES_JSON_SYSTEM_DLKM := $(INSTALLED_FILES_FILE_SYSTEM_DLKM:.txt=.json)
+$(INSTALLED_FILES_FILE_SYSTEM_DLKM): .KATI_IMPLICIT_OUTPUTS := $(INSTALLED_FILES_JSON_SYSTEM_DLKM)
+$(INSTALLED_FILES_FILE_SYSTEM_DLKM): $(INTERNAL_SYSTEM_DLKMIMAGE_FILES) $(FILESLIST) $(FILESLIST_UTIL)
+ @echo Installed file list: $@
+ mkdir -p $(dir $@)
+ if [ -d "$(BOARD_SYSTEM_DLKM_SRC)" ]; then rsync -rupE $(BOARD_SYSTEM_DLKM_SRC)/ $(TARGET_OUT_SYSTEM_DLKM); fi
+ rm -f $@
+ $(FILESLIST) $(TARGET_OUT_SYSTEM_DLKM) > $(@:.txt=.json)
+ $(FILESLIST_UTIL) -c $(@:.txt=.json) > $@
+
+system_dlkmimage_intermediates := \
+ $(call intermediates-dir-for,PACKAGING,system_dlkm)
+BUILT_SYSTEM_DLKMIMAGE_TARGET := $(PRODUCT_OUT)/system_dlkm.img
+define build-system_dlkmimage-target
+ $(call pretty,"Target system_dlkm fs image: $(INSTALLED_SYSTEM_DLKMIMAGE_TARGET)")
+ @mkdir -p $(TARGET_OUT_SYSTEM_DLKM)
+ @mkdir -p $(system_dlkmimage_intermediates) && rm -rf $(system_dlkmimage_intermediates)/system_dlkm_image_info.txt
+ $(call generate-image-prop-dictionary, $(system_dlkmimage_intermediates)/system_dlkm_image_info.txt, \
+ system_dlkm, skip_fsck=true)
+ PATH=$(INTERNAL_USERIMAGES_BINARY_PATHS):$$PATH \
+ $(BUILD_IMAGE) \
+ $(TARGET_OUT_SYSTEM_DLKM) $(system_dlkmimage_intermediates)/system_dlkm_image_info.txt \
+ $(INSTALLED_SYSTEM_DLKMIMAGE_TARGET) $(TARGET_OUT)
+ $(call assert-max-image-size,$(INSTALLED_SYSTEM_DLKMIMAGE_TARGET),$(BOARD_SYSTEM_DLKMIMAGE_PARTITION_SIZE))
+endef
+
+# We just build this directly to the install location.
+INSTALLED_SYSTEM_DLKMIMAGE_TARGET := $(BUILT_SYSTEM_DLKMIMAGE_TARGET)
+$(INSTALLED_SYSTEM_DLKMIMAGE_TARGET): \
+ $(INTERNAL_USERIMAGES_DEPS) \
+ $(INTERNAL_SYSTEM_DLKMIMAGE_FILES) \
+ $(INSTALLED_FILES_FILE_SYSTEM_DLKM)
+ $(build-system_dlkmimage-target)
+
+.PHONY: system_dlkmimage-nodeps sdnod
+system_dlkmimage-nodeps sdnod: | $(INTERNAL_USERIMAGES_DEPS)
+ $(build-system_dlkmimage-target)
+
+sync: $(INTERNAL_SYSTEM_DLKMIMAGE_FILES)
+
+else ifdef BOARD_PREBUILT_SYSTEM_DLKMIMAGE
+INSTALLED_SYSTEM_DLKMIMAGE_TARGET := $(PRODUCT_OUT)/system_dlkm.img
+$(eval $(call copy-one-file,$(BOARD_PREBUILT_SYSTEM_DLKMIMAGE),$(INSTALLED_SYSTEM_DLKMIMAGE_TARGET)))
+endif
# -----------------------------------------------------------------
# dtbo image
@@ -3425,21 +3697,21 @@
INTERNAL_PREBUILT_PVMFWIMAGE := packages/modules/Virtualization/pvmfw/pvmfw.img
ifdef BOARD_PREBUILT_PVMFWIMAGE
-BUILT_PVMFWIMAGE_TARGET := $(BOARD_PREBUILT_PVMFWIMAGE)
-else ifeq ($(BUILDING_PVMFW_IMAGE),true)
-BUILT_PVMFWIMAGE_TARGET := $(INTERNAL_PREBUILT_PVMFWIMAGE)
+PREBUILT_PVMFWIMAGE_TARGET := $(BOARD_PREBUILT_PVMFWIMAGE)
+else
+PREBUILT_PVMFWIMAGE_TARGET := $(INTERNAL_PREBUILT_PVMFWIMAGE)
endif
ifeq ($(BOARD_AVB_ENABLE),true)
-$(INSTALLED_PVMFWIMAGE_TARGET): $(BUILT_PVMFWIMAGE_TARGET) $(AVBTOOL) $(BOARD_AVB_PVMFW_KEY_PATH)
- cp $(BUILT_PVMFWIMAGE_TARGET) $@
+$(INSTALLED_PVMFWIMAGE_TARGET): $(PREBUILT_PVMFWIMAGE_TARGET) $(AVBTOOL) $(BOARD_AVB_PVMFW_KEY_PATH)
+ cp $< $@
$(AVBTOOL) add_hash_footer \
--image $@ \
--partition_size $(BOARD_PVMFWIMAGE_PARTITION_SIZE) \
--partition_name pvmfw $(INTERNAL_AVB_PVMFW_SIGNING_ARGS) \
$(BOARD_AVB_PVMFW_ADD_HASH_FOOTER_ARGS)
else
-$(eval $(call copy-one-file,$(BUILT_PVMFWIMAGE_TARGET),$(INSTALLED_PVMFWIMAGE_TARGET)))
+$(eval $(call copy-one-file,$(PREBUILT_PVMFWIMAGE_TARGET),$(INSTALLED_PVMFWIMAGE_TARGET)))
endif
endif # BOARD_USES_PVMFWIMAGE
@@ -3449,7 +3721,10 @@
# $(INSTALLED_VENDORIMAGE_TARGET)" for "system vendor".
# (1): list of partitions like "system", "vendor" or "system product system_ext".
define images-for-partitions
-$(strip $(foreach item,$(1),$(if $(filter $(item),system_other),$(INSTALLED_SYSTEMOTHERIMAGE_TARGET),$(INSTALLED_$(call to-upper,$(item))IMAGE_TARGET))))
+$(strip $(foreach item,$(1),\
+ $(if $(filter $(item),system_other),$(INSTALLED_SYSTEMOTHERIMAGE_TARGET),\
+ $(if $(filter $(item),init_boot),$(INSTALLED_INIT_BOOT_IMAGE_TARGET),\
+ $(INSTALLED_$(call to-upper,$(item))IMAGE_TARGET)))))
endef
# -----------------------------------------------------------------
@@ -3566,6 +3841,10 @@
--prop com.android.build.boot.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE) \
--prop com.android.build.boot.os_version:$(PLATFORM_VERSION_LAST_STABLE)
+BOARD_AVB_INIT_BOOT_ADD_HASH_FOOTER_ARGS += \
+ --prop com.android.build.init_boot.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE) \
+ --prop com.android.build.init_boot.os_version:$(PLATFORM_VERSION_LAST_STABLE)
+
BOARD_AVB_VENDOR_BOOT_ADD_HASH_FOOTER_ARGS += \
--prop com.android.build.vendor_boot.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE) \
@@ -3588,6 +3867,10 @@
--prop com.android.build.odm_dlkm.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE) \
--prop com.android.build.odm_dlkm.os_version:$(PLATFORM_VERSION_LAST_STABLE)
+BOARD_AVB_SYSTEM_DLKM_ADD_HASHTREE_FOOTER_ARGS += \
+ --prop com.android.build.system_dlkm.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE) \
+ --prop com.android.build.system_dlkm.os_version:$(PLATFORM_VERSION_LAST_STABLE)
+
BOARD_AVB_DTBO_ADD_HASH_FOOTER_ARGS += \
--prop com.android.build.dtbo.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE)
@@ -3595,11 +3878,21 @@
--prop com.android.build.pvmfw.fingerprint:$(BUILD_FINGERPRINT_FROM_FILE)
# The following vendor- and odm-specific images needs explicit SPL set per board.
+# TODO(b/210875415) Is this security_patch property used? Should it be removed from
+# boot.img when there is no platform ramdisk included in it?
ifdef BOOT_SECURITY_PATCH
BOARD_AVB_BOOT_ADD_HASH_FOOTER_ARGS += \
--prop com.android.build.boot.security_patch:$(BOOT_SECURITY_PATCH)
endif
+ifdef INIT_BOOT_SECURITY_PATCH
+BOARD_AVB_INIT_BOOT_ADD_HASH_FOOTER_ARGS += \
+ --prop com.android.build.init_boot.security_patch:$(INIT_BOOT_SECURITY_PATCH)
+else ifdef BOOT_SECURITY_PATCH
+BOARD_AVB_INIT_BOOT_ADD_HASH_FOOTER_ARGS += \
+ --prop com.android.build.init_boot.security_patch:$(BOOT_SECURITY_PATCH)
+endif
+
ifdef VENDOR_SECURITY_PATCH
BOARD_AVB_VENDOR_ADD_HASHTREE_FOOTER_ARGS += \
--prop com.android.build.vendor.security_patch:$(VENDOR_SECURITY_PATCH)
@@ -3620,12 +3913,25 @@
--prop com.android.build.odm_dlkm.security_patch:$(ODM_DLKM_SECURITY_PATCH)
endif
+ifdef SYSTEM_DLKM_SECURITY_PATCH
+BOARD_AVB_SYSTEM_DLKM_ADD_HASHTREE_FOOTER_ARGS += \
+ --prop com.android.build.system_dlkm.security_patch:$(SYSTEM_DLKM_SECURITY_PATCH)
+endif
+
ifdef PVMFW_SECURITY_PATCH
BOARD_AVB_PVMFW_ADD_HASH_FOOTER_ARGS += \
--prop com.android.build.pvmfw.security_patch:$(PVMFW_SECURITY_PATCH)
endif
+# For upgrading devices without a init_boot partition, the init_boot footer args
+# should fallback to boot partition footer.
+ifndef INSTALLED_INIT_BOOT_IMAGE_TARGET
+BOARD_AVB_BOOT_ADD_HASH_FOOTER_ARGS += \
+ $(BOARD_AVB_INIT_BOOT_ADD_HASH_FOOTER_ARGS)
+endif
+
BOOT_FOOTER_ARGS := BOARD_AVB_BOOT_ADD_HASH_FOOTER_ARGS
+INIT_BOOT_FOOTER_ARGS := BOARD_AVB_INIT_BOOT_ADD_HASH_FOOTER_ARGS
VENDOR_BOOT_FOOTER_ARGS := BOARD_AVB_VENDOR_BOOT_ADD_HASH_FOOTER_ARGS
DTBO_FOOTER_ARGS := BOARD_AVB_DTBO_ADD_HASH_FOOTER_ARGS
PVMFW_FOOTER_ARGS := BOARD_AVB_PVMFW_ADD_HASH_FOOTER_ARGS
@@ -3637,6 +3943,7 @@
ODM_FOOTER_ARGS := BOARD_AVB_ODM_ADD_HASHTREE_FOOTER_ARGS
VENDOR_DLKM_FOOTER_ARGS := BOARD_AVB_VENDOR_DLKM_ADD_HASHTREE_FOOTER_ARGS
ODM_DLKM_FOOTER_ARGS := BOARD_AVB_ODM_DLKM_ADD_HASHTREE_FOOTER_ARGS
+SYSTEM_DLKM_FOOTER_ARGS := BOARD_AVB_SYSTEM_DLKM_ADD_HASHTREE_FOOTER_ARGS
# Helper function that checks and sets required build variables for an AVB chained partition.
# $(1): the partition to enable AVB chain, e.g., boot or system or vbmeta_system.
@@ -3709,11 +4016,17 @@
$(eval $(call check-and-set-avb-args,boot))
endif
+ifdef INSTALLED_INIT_BOOT_IMAGE_TARGET
+$(eval $(call check-and-set-avb-args,init_boot))
+endif
+
ifdef INSTALLED_VENDOR_BOOTIMAGE_TARGET
$(eval $(call check-and-set-avb-args,vendor_boot))
endif
+ifdef INSTALLED_SYSTEMIMAGE_TARGET
$(eval $(call check-and-set-avb-args,system))
+endif
ifdef INSTALLED_VENDORIMAGE_TARGET
$(eval $(call check-and-set-avb-args,vendor))
@@ -3739,6 +4052,10 @@
$(eval $(call check-and-set-avb-args,odm_dlkm))
endif
+ifdef INSTALLED_SYSTEM_DLKMIMAGE_TARGET
+$(eval $(call check-and-set-avb-args,system_dlkm))
+endif
+
ifdef INSTALLED_DTBOIMAGE_TARGET
$(eval $(call check-and-set-avb-args,dtbo))
endif
@@ -3805,6 +4122,9 @@
$(if $(BOARD_AVB_BOOT_KEY_PATH),\
$(hide) $(AVBTOOL) extract_public_key --key $(BOARD_AVB_BOOT_KEY_PATH) \
--output $(1)/boot.avbpubkey)
+ $(if $(BOARD_AVB_INIT_BOOT_KEY_PATH),\
+ $(hide) $(AVBTOOL) extract_public_key --key $(BOARD_AVB_INIT_BOOT_KEY_PATH) \
+ --output $(1)/init_boot.avbpubkey)
$(if $(BOARD_AVB_VENDOR_BOOT_KEY_PATH),\
$(AVBTOOL) extract_public_key --key $(BOARD_AVB_VENDOR_BOOT_KEY_PATH) \
--output $(1)/vendor_boot.avbpubkey)
@@ -3829,6 +4149,9 @@
$(if $(BOARD_AVB_ODM_DLKM_KEY_PATH),\
$(hide) $(AVBTOOL) extract_public_key --key $(BOARD_AVB_ODM_DLKM_KEY_PATH) \
--output $(1)/odm_dlkm.avbpubkey)
+ $(if $(BOARD_AVB_SYSTEM_DLKM_KEY_PATH),\
+ $(hide) $(AVBTOOL) extract_public_key --key $(BOARD_AVB_SYSTEM_DLKM_KEY_PATH) \
+ --output $(1)/system_dlkm.avbpubkey)
$(if $(BOARD_AVB_DTBO_KEY_PATH),\
$(hide) $(AVBTOOL) extract_public_key --key $(BOARD_AVB_DTBO_KEY_PATH) \
--output $(1)/dtbo.avbpubkey)
@@ -3908,6 +4231,7 @@
$(INSTALLED_VBMETAIMAGE_TARGET): \
$(AVBTOOL) \
$(INSTALLED_BOOTIMAGE_TARGET) \
+ $(INSTALLED_INIT_BOOT_IMAGE_TARGET) \
$(INSTALLED_VENDOR_BOOTIMAGE_TARGET) \
$(INSTALLED_SYSTEMIMAGE_TARGET) \
$(INSTALLED_VENDORIMAGE_TARGET) \
@@ -3916,6 +4240,7 @@
$(INSTALLED_ODMIMAGE_TARGET) \
$(INSTALLED_VENDOR_DLKMIMAGE_TARGET) \
$(INSTALLED_ODM_DLKMIMAGE_TARGET) \
+ $(INSTALLED_SYSTEM_DLKMIMAGE_TARGET) \
$(INSTALLED_DTBOIMAGE_TARGET) \
$(INSTALLED_PVMFWIMAGE_TARGET) \
$(INSTALLED_CUSTOMIMAGES_TARGET) \
@@ -3947,11 +4272,12 @@
$(INTERNAL_ODMIMAGE_FILES) \
$(INTERNAL_VENDOR_DLKMIMAGE_FILES) \
$(INTERNAL_ODM_DLKMIMAGE_FILES) \
+ $(INTERNAL_SYSTEM_DLKMIMAGE_FILES) \
# -----------------------------------------------------------------
# Check VINTF of build
-# Note: vendor_dlkm and odm_dlkm does not have VINTF files.
+# Note: vendor_dlkm, odm_dlkm, and system_dlkm does not have VINTF files.
ifeq (,$(TARGET_BUILD_UNBUNDLED))
intermediates := $(call intermediates-dir-for,PACKAGING,check_vintf_all)
@@ -4325,6 +4651,7 @@
fec \
fsck.f2fs \
fs_config \
+ generate_gki_certificate \
generate_verity_key \
host_init_verifier \
img2simg \
@@ -4479,6 +4806,10 @@
else
echo "boot_images=$(foreach b,$(INSTALLED_BOOTIMAGE_TARGET),$(notdir $(b)))" >> $@
endif
+ifneq ($(INSTALLED_INIT_BOOT_IMAGE_TARGET),)
+ $(hide) echo "init_boot=true" >> $@
+ $(hide) echo "init_boot_size=$(BOARD_INIT_BOOT_IMAGE_PARTITION_SIZE)" >> $@
+endif
ifeq ($(BOARD_RAMDISK_USE_LZ4),true)
echo "lz4_ramdisks=true" >> $@
endif
@@ -4506,17 +4837,19 @@
endif
$(hide) echo "tool_extensions=$(tool_extensions)" >> $@
$(hide) echo "default_system_dev_certificate=$(DEFAULT_SYSTEM_DEV_CERTIFICATE)" >> $@
+ifdef PRODUCT_EXTRA_OTA_KEYS
+ $(hide) echo "extra_ota_keys=$(PRODUCT_EXTRA_OTA_KEYS)" >> $@
+endif
ifdef PRODUCT_EXTRA_RECOVERY_KEYS
$(hide) echo "extra_recovery_keys=$(PRODUCT_EXTRA_RECOVERY_KEYS)" >> $@
endif
$(hide) echo 'mkbootimg_args=$(BOARD_MKBOOTIMG_ARGS)' >> $@
$(hide) echo 'recovery_mkbootimg_args=$(BOARD_RECOVERY_MKBOOTIMG_ARGS)' >> $@
$(hide) echo 'mkbootimg_version_args=$(INTERNAL_MKBOOTIMG_VERSION_ARGS)' >> $@
+ $(hide) echo 'mkbootimg_init_args=$(BOARD_MKBOOTIMG_INIT_ARGS)' >> $@
ifdef BOARD_GKI_SIGNING_KEY_PATH
$(hide) echo 'gki_signing_key_path=$(BOARD_GKI_SIGNING_KEY_PATH)' >> $@
$(hide) echo 'gki_signing_algorithm=$(BOARD_GKI_SIGNING_ALGORITHM)' >> $@
-endif
-ifdef BOARD_GKI_SIGNING_SIGNATURE_ARGS
$(hide) echo 'gki_signing_signature_args=$(BOARD_GKI_SIGNING_SIGNATURE_ARGS)' >> $@
endif
$(hide) echo "multistage_support=1" >> $@
@@ -4552,6 +4885,12 @@
$(hide) echo "avb_boot_algorithm=$(BOARD_AVB_BOOT_ALGORITHM)" >> $@
$(hide) echo "avb_boot_rollback_index_location=$(BOARD_AVB_BOOT_ROLLBACK_INDEX_LOCATION)" >> $@
endif # BOARD_AVB_BOOT_KEY_PATH
+ $(hide) echo "avb_init_boot_add_hash_footer_args=$(BOARD_AVB_INIT_BOOT_ADD_HASH_FOOTER_ARGS)" >> $@
+ifdef BOARD_AVB_INIT_BOOT_KEY_PATH
+ $(hide) echo "avb_init_boot_key_path=$(BOARD_AVB_INIT_BOOT_KEY_PATH)" >> $@
+ $(hide) echo "avb_init_boot_algorithm=$(BOARD_AVB_INIT_BOOT_ALGORITHM)" >> $@
+ $(hide) echo "avb_init_boot_rollback_index_location=$(BOARD_AVB_INIT_BOOT_ROLLBACK_INDEX_LOCATION)" >> $@
+endif # BOARD_AVB_INIT_BOOT_KEY_PATH
echo "avb_vendor_boot_add_hash_footer_args=$(BOARD_AVB_VENDOR_BOOT_ADD_HASH_FOOTER_ARGS)" >> $@
ifdef BOARD_AVB_VENDOR_BOOT_KEY_PATH
echo "avb_vendor_boot_key_path=$(BOARD_AVB_VENDOR_BOOT_KEY_PATH)" >> $@
@@ -4708,13 +5047,16 @@
tool_extension := $(wildcard $(tool_extensions)/releasetools.py)
$(BUILT_TARGET_FILES_PACKAGE): PRIVATE_TOOL_EXTENSION := $(tool_extension)
+updaer_dep :=
ifeq ($(AB_OTA_UPDATER),true)
-updater_dep := system/update_engine/update_engine.conf
+updater_dep += system/update_engine/update_engine.conf
+updater_dep += external/zucchini/version_info.h
+updater_dep += $(HOST_OUT_SHARED_LIBRARIES)/liblz4.so
endif
# Build OTA tools if non-A/B is allowed
ifeq ($(TARGET_OTA_ALLOW_NON_AB),true)
-updater_dep := $(built_ota_tools)
+updater_dep += $(built_ota_tools)
endif
$(BUILT_TARGET_FILES_PACKAGE): $(updater_dep)
@@ -4768,13 +5110,17 @@
define filter-out-missing-odm_dlkm
$(if $(INSTALLED_ODM_DLKMIMAGE_TARGET),$(1),$(filter-out odm_dlkm,$(1)))
endef
-# Filter out vendor,vendor_dlkm,odm,odm_dlkm from the list for AOSP targets.
+define filter-out-missing-system_dlkm
+$(if $(INSTALLED_SYSTEM_DLKMIMAGE_TARGET),$(1),$(filter-out system_dlkm,$(1)))
+endef
+# Filter out vendor,vendor_dlkm,odm,odm_dlkm,system_dlkm from the list for AOSP targets.
# $(1): list
define filter-out-missing-partitions
$(call filter-out-missing-vendor,\
$(call filter-out-missing-vendor_dlkm,\
$(call filter-out-missing-odm,\
- $(call filter-out-missing-odm_dlkm,$(1)))))
+ $(call filter-out-missing-odm_dlkm,\
+ $(call filter-out-missing-system_dlkm,$(1))))))
endef
# Information related to dynamic partitions and virtual A/B. This information
@@ -4833,6 +5179,10 @@
# image.
ifdef BUILDING_SYSTEM_IMAGE
$(BUILT_TARGET_FILES_PACKAGE): $(FULL_SYSTEMIMAGE_DEPS)
+else
+ # releasetools may need the system build.prop even when building a
+ # system-image-less product.
+ $(BUILT_TARGET_FILES_PACKAGE): $(INSTALLED_BUILD_PROP_TARGET)
endif
ifdef BUILDING_USERDATA_IMAGE
@@ -4888,9 +5238,10 @@
$(BUILT_TARGET_FILES_PACKAGE): $(INSTALLED_SYSTEM_EXTIMAGE_TARGET)
endif
-ifdef BUILDING_BOOT_IMAGE
+ifneq (,$(BUILDING_BOOT_IMAGE)$(BUILDING_INIT_BOOT_IMAGE))
$(BUILT_TARGET_FILES_PACKAGE): $(INTERNAL_RAMDISK_FILES)
-endif
+endif # BUILDING_BOOT_IMAGE != "" || BUILDING_INIT_BOOT_IMAGE != ""
+
ifneq (,$(INTERNAL_PREBUILT_BOOTIMAGE) $(filter true,$(BOARD_COPY_BOOT_IMAGE_TO_TARGET_FILES)))
$(BUILT_TARGET_FILES_PACKAGE): $(INSTALLED_BOOTIMAGE_TARGET)
endif
@@ -4913,6 +5264,12 @@
$(BUILT_TARGET_FILES_PACKAGE): $(INSTALLED_ODM_DLKMIMAGE_TARGET)
endif
+ifdef BUILDING_SYSTEM_DLKM_IMAGE
+ $(BUILT_TARGET_FILES_PACKAGE): $(INTERNAL_SYSTEM_DLKMIMAGE_FILES)
+else ifdef BOARD_PREBUILT_SYSTEM_DLKMIMAGE
+ $(BUILT_TARGET_FILES_PACKAGE): $(INSTALLED_SYSTEM_DLKMIMAGE_TARGET)
+endif
+
ifeq ($(BUILD_QEMU_IMAGES),true)
MK_VBMETA_BOOT_KERNEL_CMDLINE_SH := device/generic/goldfish/tools/mk_vbmeta_boot_params.sh
$(BUILT_TARGET_FILES_PACKAGE): $(MK_VBMETA_BOOT_KERNEL_CMDLINE_SH)
@@ -4947,6 +5304,7 @@
$(PRODUCT_ODM_BASE_FS_PATH) \
$(PRODUCT_VENDOR_DLKM_BASE_FS_PATH) \
$(PRODUCT_ODM_DLKM_BASE_FS_PATH) \
+ $(PRODUCT_SYSTEM_DLKM_BASE_FS_PATH) \
$(LPMAKE) \
$(SELINUX_FC) \
$(INSTALLED_MISC_INFO_TARGET) \
@@ -5078,6 +5436,12 @@
@# Contents of the system image
$(hide) $(call package_files-copy-root, \
$(SYSTEMIMAGE_SOURCE_DIR),$(zip_root)/SYSTEM)
+else ifdef INSTALLED_BUILD_PROP_TARGET
+ @# Copy the system build.prop even if not building a system image
+ @# because add_img_to_target_files may need it to build other partition
+ @# images.
+ $(hide) mkdir -p "$(zip_root)/SYSTEM"
+ $(hide) cp "$(INSTALLED_BUILD_PROP_TARGET)" "$(patsubst $(TARGET_OUT)/%,$(zip_root)/SYSTEM/%,$(INSTALLED_BUILD_PROP_TARGET))"
endif
ifdef BUILDING_USERDATA_IMAGE
@# Contents of the data image
@@ -5114,6 +5478,11 @@
$(hide) $(call package_files-copy-root, \
$(TARGET_OUT_ODM_DLKM),$(zip_root)/ODM_DLKM)
endif
+ifdef BUILDING_SYSTEM_DLKM_IMAGE
+ @# Contents of the system_dlkm image
+ $(hide) $(call package_files-copy-root, \
+ $(TARGET_OUT_SYSTEM_DLKM),$(zip_root)/SYSTEM_DLKM)
+endif
ifdef BUILDING_SYSTEM_OTHER_IMAGE
@# Contents of the system_other image
$(hide) $(call package_files-copy-root, \
@@ -5174,6 +5543,10 @@
$(hide) cp $(PRODUCT_ODM_DLKM_BASE_FS_PATH) \
$(zip_root)/META/$(notdir $(PRODUCT_ODM_DLKM_BASE_FS_PATH))
endif
+ifneq ($(PRODUCT_SYSTEM_DLKM_BASE_FS_PATH),)
+ $(hide) cp $(PRODUCT_SYSTEM_DLKM_BASE_FS_PATH) \
+ $(zip_root)/META/$(notdir $(PRODUCT_SYSTEM_DLKM_BASE_FS_PATH))
+endif
ifeq ($(TARGET_OTA_ALLOW_NON_AB),true)
ifneq ($(INSTALLED_RECOVERYIMAGE_TARGET),)
$(hide) PATH=$(INTERNAL_USERIMAGES_BINARY_PATHS):$$PATH MKBOOTIMG=$(MKBOOTIMG) \
@@ -5183,6 +5556,8 @@
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) cp $(HOST_OUT_SHARED_LIBRARIES)/liblz4.so $(zip_root)/META/liblz4.so
$(hide) for part in $(strip $(AB_OTA_PARTITIONS)); do \
echo "$${part}" >> $(zip_root)/META/ab_partitions.txt; \
done
@@ -5211,8 +5586,13 @@
$(hide) mkdir -p $(zip_root)/IMAGES
$(hide) cp $(INSTALLED_SYSTEM_EXTIMAGE_TARGET) $(zip_root)/IMAGES/
endif
+ifdef BOARD_PREBUILT_INIT_BOOT_IMAGE
+ $(hide) mkdir -p $(zip_root)/PREBUILT_IMAGES
+ $(hide) cp $(INSTALLED_INIT_BOOT_IMAGE_TARGET) $(zip_root)/PREBUILT_IMAGES/
+endif
+
ifndef BOARD_PREBUILT_BOOTIMAGE
-ifneq (,$(INTERNAL_PREBUILT_BOOTIMAGE) $(filter true,$(BOARD_COPY_BOOT_IMAGE_TO_TARGET_FILES)))
+ifneq (,$(strip $(INTERNAL_PREBUILT_BOOTIMAGE) $(filter true,$(BOARD_COPY_BOOT_IMAGE_TO_TARGET_FILES))))
ifdef INSTALLED_BOOTIMAGE_TARGET
$(hide) mkdir -p $(zip_root)/IMAGES
$(hide) cp $(INSTALLED_BOOTIMAGE_TARGET) $(zip_root)/IMAGES/
@@ -5234,16 +5614,17 @@
$(hide) mkdir -p $(zip_root)/IMAGES
$(hide) cp $(INSTALLED_ODM_DLKMIMAGE_TARGET) $(zip_root)/IMAGES/
endif
+ifdef BOARD_PREBUILT_SYSTEM_DLKMIMAGE
+ $(hide) mkdir -p $(zip_root)/IMAGES
+ $(hide) cp $(INSTALLED_SYSTEM_DLKMIMAGE_TARGET) $(zip_root)/IMAGES/
+endif
ifdef BOARD_PREBUILT_DTBOIMAGE
$(hide) mkdir -p $(zip_root)/PREBUILT_IMAGES
$(hide) cp $(INSTALLED_DTBOIMAGE_TARGET) $(zip_root)/PREBUILT_IMAGES/
endif # BOARD_PREBUILT_DTBOIMAGE
-ifdef BOARD_PREBUILT_PVMFWIMAGE
+ifeq ($(BOARD_USES_PVMFWIMAGE),true)
$(hide) mkdir -p $(zip_root)/PREBUILT_IMAGES
$(hide) cp $(INSTALLED_PVMFWIMAGE_TARGET) $(zip_root)/PREBUILT_IMAGES/
-else ifeq ($(BUILDING_PVMFW_IMAGE),true)
- $(hide) mkdir -p $(zip_root)/IMAGES
- $(hide) cp $(INSTALLED_PVMFWIMAGE_TARGET) $(zip_root)/IMAGES/
endif
ifdef BOARD_PREBUILT_BOOTLOADER
$(hide) mkdir -p $(zip_root)/IMAGES
@@ -5282,12 +5663,22 @@
ifdef BUILDING_ODM_DLKM_IMAGE
$(hide) $(call fs_config,$(zip_root)/ODM_DLKM,odm_dlkm/) > $(zip_root)/META/odm_dlkm_filesystem_config.txt
endif
+ifdef BUILDING_SYSTEM_DLKM_IMAGE
+ $(hide) $(call fs_config,$(zip_root)/SYSTEM_DLKM,system_dlkm/) > $(zip_root)/META/system_dlkm_filesystem_config.txt
+endif
@# ROOT always contains the files for the root under normal boot.
$(hide) $(call fs_config,$(zip_root)/ROOT,) > $(zip_root)/META/root_filesystem_config.txt
ifeq ($(BOARD_USES_RECOVERY_AS_BOOT),true)
@# BOOT/RAMDISK exists and contains the ramdisk for recovery if using BOARD_USES_RECOVERY_AS_BOOT.
$(hide) $(call fs_config,$(zip_root)/BOOT/RAMDISK,) > $(zip_root)/META/boot_filesystem_config.txt
endif
+ifdef BUILDING_INIT_BOOT_IMAGE
+ $(hide) $(call package_files-copy-root, $(TARGET_RAMDISK_OUT),$(zip_root)/INIT_BOOT/RAMDISK)
+ $(hide) $(call fs_config,$(zip_root)/INIT_BOOT/RAMDISK,) > $(zip_root)/META/init_boot_filesystem_config.txt
+ifdef BOARD_KERNEL_PAGESIZE
+ $(hide) echo "$(BOARD_KERNEL_PAGESIZE)" > $(zip_root)/INIT_BOOT/pagesize
+endif # BOARD_KERNEL_PAGESIZE
+endif # BUILDING_INIT_BOOT_IMAGE
ifneq ($(INSTALLED_VENDOR_BOOTIMAGE_TARGET),)
$(call fs_config,$(zip_root)/VENDOR_BOOT/RAMDISK,) > $(zip_root)/META/vendor_boot_filesystem_config.txt
endif
@@ -5433,6 +5824,8 @@
ifeq ($(BUILD_OS),linux)
ifneq ($(DEX2OAT),)
dexpreopt_tools_deps := $(DEXPREOPT_GEN_DEPS) $(DEXPREOPT_GEN) $(AAPT2)
+dexpreopt_tools_deps += $(HOST_OUT_EXECUTABLES)/dexdump
+dexpreopt_tools_deps += $(HOST_OUT_EXECUTABLES)/oatdump
DEXPREOPT_TOOLS_ZIP := $(PRODUCT_OUT)/dexpreopt_tools.zip
$(DEXPREOPT_TOOLS_ZIP): $(dexpreopt_tools_deps)
$(DEXPREOPT_TOOLS_ZIP): PRIVATE_DEXPREOPT_TOOLS_DEPS := $(dexpreopt_tools_deps)
@@ -5599,6 +5992,7 @@
$(INSTALLED_SYSTEMIMAGE_TARGET) \
$(INSTALLED_RAMDISK_TARGET) \
$(INSTALLED_BOOTIMAGE_TARGET) \
+ $(INSTALLED_INIT_BOOT_IMAGE_TARGET) \
$(INSTALLED_USERDATAIMAGE_TARGET) \
$(INSTALLED_VENDORIMAGE_TARGET) \
$(INSTALLED_PRODUCTIMAGE_TARGET) \
@@ -5606,6 +6000,7 @@
$(INSTALLED_ODMIMAGE_TARGET) \
$(INSTALLED_VENDOR_DLKMIMAGE_TARGET) \
$(INSTALLED_ODM_DLKMIMAGE_TARGET) \
+ $(INSTALLED_SYSTEM_DLKMIMAGE_TARGET) \
$(updater_dep)
endif
$(PROGUARD_USAGE_ZIP): PRIVATE_LIST_FILE := $(call intermediates-dir-for,PACKAGING,proguard_usage.zip)/filelist
@@ -5860,6 +6255,16 @@
droidcore-unbundled: $(INSTALLED_QEMU_ODM_DLKMIMAGE)
endif
+ifdef INSTALLED_SYSTEM_DLKMIMAGE_TARGET
+INSTALLED_QEMU_SYSTEM_DLKMIMAGE := $(PRODUCT_OUT)/system_dlkm-qemu.img
+$(INSTALLED_QEMU_SYSTEM_DLKMIMAGE): $(INSTALLED_SYSTEM_DLKMIMAGE_TARGET) $(MK_QEMU_IMAGE_SH) $(SGDISK_HOST)
+ @echo Create system_dlkm-qemu.img
+ (export SGDISK=$(SGDISK_HOST); $(MK_QEMU_IMAGE_SH) $(INSTALLED_SYSTEM_DLKMIMAGE_TARGET))
+
+system_dlkmimage: $(INSTALLED_QEMU_SYSTEM_DLKMIMAGE)
+droidcore-unbundled: $(INSTALLED_QEMU_SYSTEM_DLKMIMAGE)
+endif
+
QEMU_VERIFIED_BOOT_PARAMS := $(PRODUCT_OUT)/VerifiedBootParams.textproto
$(QEMU_VERIFIED_BOOT_PARAMS): $(INSTALLED_VBMETAIMAGE_TARGET) $(INSTALLED_SYSTEMIMAGE_TARGET) \
$(MK_VBMETA_BOOT_KERNEL_CMDLINE_SH) $(AVBTOOL)
@@ -6091,6 +6496,10 @@
haiku: $(SOONG_FUZZ_PACKAGING_ARCH_MODULES) $(ALL_FUZZ_TARGETS)
$(call dist-for-goals,haiku,$(SOONG_FUZZ_PACKAGING_ARCH_MODULES))
+.PHONY: haiku-java
+haiku-java: $(SOONG_JAVA_FUZZ_PACKAGING_ARCH_MODULES) $(ALL_JAVA_FUZZ_TARGETS)
+$(call dist-for-goals,haiku-java,$(SOONG_JAVA_FUZZ_PACKAGING_ARCH_MODULES))
+
.PHONY: haiku-rust
haiku-rust: $(SOONG_RUST_FUZZ_PACKAGING_ARCH_MODULES) $(ALL_RUST_FUZZ_TARGETS)
$(call dist-for-goals,haiku-rust,$(SOONG_RUST_FUZZ_PACKAGING_ARCH_MODULES))
diff --git a/core/android_soong_config_vars.mk b/core/android_soong_config_vars.mk
index ee15b75..0befbfa 100644
--- a/core/android_soong_config_vars.mk
+++ b/core/android_soong_config_vars.mk
@@ -27,12 +27,21 @@
# Add variables to the namespace below:
$(call add_soong_config_var,ANDROID,TARGET_ENABLE_MEDIADRM_64)
+$(call add_soong_config_var,ANDROID,IS_TARGET_MIXED_SEPOLICY)
+ifeq ($(IS_TARGET_MIXED_SEPOLICY),true)
+$(call add_soong_config_var_value,ANDROID,MIXED_SEPOLICY_VERSION,$(BOARD_SEPOLICY_VERS))
+endif
$(call add_soong_config_var,ANDROID,BOARD_USES_ODMIMAGE)
$(call add_soong_config_var,ANDROID,BOARD_USES_RECOVERY_AS_BOOT)
$(call add_soong_config_var,ANDROID,BOARD_BUILD_SYSTEM_ROOT_IMAGE)
$(call add_soong_config_var,ANDROID,PRODUCT_INSTALL_DEBUG_POLICY_TO_SYSTEM_EXT)
-ifeq (,$(findstring com.google.android.conscrypt,$(PRODUCT_PACKAGES)))
+ifneq (,$(filter sdk win_sdk sdk_addon,$(MAKECMDGOALS)))
+ # The artifacts in the SDK zip are OK to build with prebuilt stubs enabled,
+ # even if prebuilt apexes are not enabled, because the system images in the
+ # SDK stub are not currently used (and will be removed: b/205008975).
+ MODULE_BUILD_FROM_SOURCE ?= false
+else ifeq (,$(findstring com.google.android.conscrypt,$(PRODUCT_PACKAGES)))
# Prebuilt module SDKs require prebuilt modules to work, and currently
# prebuilt modules are only provided for com.google.android.xxx. If we can't
# find one of them in PRODUCT_PACKAGES then assume com.android.xxx are in use,
@@ -83,7 +92,7 @@
# This sets the default for building ART APEXes from source rather than
# prebuilts (in packages/modules/ArtPrebuilt and prebuilt/module_sdk/art) in
# all other platform builds.
- SOONG_CONFIG_art_module_source_build ?= false
+ SOONG_CONFIG_art_module_source_build ?= true
endif
# Apex build mode variables
@@ -94,3 +103,10 @@
ifeq (true,$(MODULE_BUILD_FROM_SOURCE))
$(call add_soong_config_var_value,ANDROID,module_build_from_source,true)
endif
+
+# TODO(b/203088572): Remove when Java optimizations enabled by default for
+# SystemUI.
+$(call add_soong_config_var,ANDROID,SYSTEMUI_OPTIMIZE_JAVA)
+# 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 23a1616..cec7792 100644
--- a/core/base_rules.mk
+++ b/core/base_rules.mk
@@ -514,30 +514,26 @@
###########################################################
my_installed_symlinks :=
-my_default_test_module :=
-ifeq ($(use_testcase_folder),true)
-arch_dir := $($(my_prefix)$(LOCAL_2ND_ARCH_VAR_PREFIX)ARCH)
-my_default_test_module := $($(my_prefix)OUT_TESTCASES)/$(LOCAL_MODULE)/$(arch_dir)/$(my_installed_module_stem)
-arch_dir :=
-endif
ifneq (,$(LOCAL_SOONG_INSTALLED_MODULE))
# Soong already generated the copy rule, but make the installed location depend on the Make
# copy of the intermediates for now, as some rules that collect intermediates may expect
# them to exist.
$(LOCAL_INSTALLED_MODULE): $(LOCAL_BUILT_MODULE)
+
+ $(foreach symlink, $(LOCAL_SOONG_INSTALL_SYMLINKS), \
+ $(call declare-0p-target,$(symlink)))
+ $(my_all_targets) : | $(LOCAL_SOONG_INSTALL_SYMLINKS)
else ifneq (true,$(LOCAL_UNINSTALLABLE_MODULE))
- ifneq ($(LOCAL_INSTALLED_MODULE),$(my_default_test_module))
- $(LOCAL_INSTALLED_MODULE): PRIVATE_POST_INSTALL_CMD := $(LOCAL_POST_INSTALL_CMD)
- $(LOCAL_INSTALLED_MODULE): $(LOCAL_BUILT_MODULE)
+ $(LOCAL_INSTALLED_MODULE): PRIVATE_POST_INSTALL_CMD := $(LOCAL_POST_INSTALL_CMD)
+ $(LOCAL_INSTALLED_MODULE): $(LOCAL_BUILT_MODULE)
@echo "Install: $@"
- ifeq ($(LOCAL_MODULE_MAKEFILE),$(SOONG_ANDROID_MK))
+ ifeq ($(LOCAL_MODULE_MAKEFILE),$(SOONG_ANDROID_MK))
$(copy-file-or-link-to-new-target)
- else
+ else
$(copy-file-to-new-target)
- endif
- $(PRIVATE_POST_INSTALL_CMD)
endif
+ $(PRIVATE_POST_INSTALL_CMD)
# Rule to install the module's companion symlinks
my_installed_symlinks := $(addprefix $(my_module_path)/,$(LOCAL_MODULE_SYMLINKS) $(LOCAL_MODULE_SYMLINKS_$(my_32_64_bit_suffix)))
@@ -735,8 +731,9 @@
# The module itself.
$(foreach suite, $(LOCAL_COMPATIBILITY_SUITE), \
- $(eval my_compat_dist_$(suite) := $(foreach dir, $(call compatibility_suite_dirs,$(suite),$(arch_dir)), \
- $(LOCAL_BUILT_MODULE):$(dir)/$(my_installed_module_stem))) \
+ $(eval my_compat_dist_$(suite) := $(patsubst %:$(LOCAL_INSTALLED_MODULE),$(LOCAL_INSTALLED_MODULE):$(LOCAL_INSTALLED_MODULE),\
+ $(foreach dir, $(call compatibility_suite_dirs,$(suite),$(arch_dir)), \
+ $(LOCAL_BUILT_MODULE):$(dir)/$(my_installed_module_stem)))) \
$(eval my_compat_dist_config_$(suite) := ))
@@ -935,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))
@@ -980,6 +981,18 @@
ALL_MODULES.$(my_register_name).SYSTEM_SHARED_LIBS := \
$(ALL_MODULES.$(my_register_name).SYSTEM_SHARED_LIBS) $(LOCAL_SYSTEM_SHARED_LIBRARIES)
+ifdef LOCAL_TEST_DATA
+ # Export the list of targets that are handled as data inputs and required
+ # by tests at runtime. The LOCAL_TEST_DATA format is generated from below
+ # https://cs.android.com/android/platform/superproject/+/master:build/soong/android/androidmk.go;l=925-944;drc=master
+ # which format is like $(path):$(relative_file) but for module-info, only
+ # the string after ":" is needed.
+ ALL_MODULES.$(my_register_name).TEST_DATA := \
+ $(strip $(ALL_MODULES.$(my_register_name).TEST_DATA) \
+ $(foreach f, $(LOCAL_TEST_DATA),\
+ $(call word-colon,2,$(f))))
+endif
+
##########################################################################
## When compiling against the VNDK, add the .vendor or .product suffix to
## required modules.
@@ -1054,7 +1067,9 @@
endif
ALL_MODULES.$(my_register_name).FOR_HOST_CROSS := $(my_host_cross)
ALL_MODULES.$(my_register_name).MODULE_NAME := $(LOCAL_MODULE)
-ALL_MODULES.$(my_register_name).COMPATIBILITY_SUITES := $(LOCAL_COMPATIBILITY_SUITE)
+ALL_MODULES.$(my_register_name).COMPATIBILITY_SUITES := \
+ $(ALL_MODULES.$(my_register_name).COMPATIBILITY_SUITES) \
+ $(filter-out $(ALL_MODULES.$(my_register_name).COMPATIBILITY_SUITES),$(LOCAL_COMPATIBILITY_SUITE))
ALL_MODULES.$(my_register_name).TEST_CONFIG := $(test_config)
ALL_MODULES.$(my_register_name).EXTRA_TEST_CONFIGS := $(LOCAL_EXTRA_FULL_TEST_CONFIGS)
ALL_MODULES.$(my_register_name).TEST_MAINLINE_MODULES := $(LOCAL_TEST_MAINLINE_MODULES)
@@ -1071,7 +1086,7 @@
##########################################################
# Track module-level dependencies.
# Use $(LOCAL_MODULE) instead of $(my_register_name) to ignore module's bitness.
-ifdef RECORD_ALL_DEPS
+# (b/204397180) Unlock RECORD_ALL_DEPS was acknowledged reasonable for better Atest performance.
ALL_DEPS.MODULES += $(LOCAL_MODULE)
ALL_DEPS.$(LOCAL_MODULE).ALL_DEPS := $(sort \
$(ALL_DEPS.$(LOCAL_MODULE).ALL_DEPS) \
@@ -1088,7 +1103,6 @@
license_files := $(call find-parent-file,$(LOCAL_PATH),MODULE_LICENSE*)
ALL_DEPS.$(LOCAL_MODULE).LICENSE := $(sort $(ALL_DEPS.$(LOCAL_MODULE).LICENSE) $(license_files))
-endif
###########################################################
## Take care of my_module_tags
diff --git a/core/board_config.mk b/core/board_config.mk
index b3e6957..5fb007e 100644
--- a/core/board_config.mk
+++ b/core/board_config.mk
@@ -65,6 +65,7 @@
# File system variables
_board_strip_readonly_list += BOARD_FLASH_BLOCK_SIZE
_board_strip_readonly_list += BOARD_BOOTIMAGE_PARTITION_SIZE
+_board_strip_readonly_list += BOARD_INIT_BOOT_IMAGE_PARTITION_SIZE
_board_strip_readonly_list += BOARD_RECOVERYIMAGE_PARTITION_SIZE
_board_strip_readonly_list += BOARD_SYSTEMIMAGE_PARTITION_SIZE
_board_strip_readonly_list += BOARD_SYSTEMIMAGE_FILE_SYSTEM_TYPE
@@ -84,6 +85,8 @@
_board_strip_readonly_list += BOARD_VENDOR_DLKMIMAGE_FILE_SYSTEM_TYPE
_board_strip_readonly_list += BOARD_ODM_DLKMIMAGE_PARTITION_SIZE
_board_strip_readonly_list += BOARD_ODM_DLKMIMAGE_FILE_SYSTEM_TYPE
+_board_strip_readonly_list += BOARD_SYSTEM_DLKMIMAGE_PARTITION_SIZE
+_board_strip_readonly_list += BOARD_SYSTEM_DLKMIMAGE_FILE_SYSTEM_TYPE
_board_strip_readonly_list += BOARD_PVMFWIMAGE_PARTITION_SIZE
# Logical partitions related variables.
@@ -92,6 +95,7 @@
_board_strip_readonly_list += BOARD_ODMIMAGE_PARTITION_RESERVED_SIZE
_board_strip_readonly_list += BOARD_VENDOR_DLKMIMAGE_PARTITION_RESERVED_SIZE
_board_strip_readonly_list += BOARD_ODM_DLKMIMAGE_PARTITION_RESERVED_SIZE
+_board_strip_readonly_list += BOARD_SYSTEM_DLKMIMAGE_PARTITION_RESERVED_SIZE
_board_strip_readonly_list += BOARD_PRODUCTIMAGE_PARTITION_RESERVED_SIZE
_board_strip_readonly_list += BOARD_SYSTEM_EXTIMAGE_PARTITION_RESERVED_SIZE
_board_strip_readonly_list += BOARD_SUPER_PARTITION_SIZE
@@ -122,9 +126,49 @@
_board_strip_readonly_list += BOARD_MOVE_GSI_AVB_KEYS_TO_VENDOR_BOOT
_board_strip_readonly_list += BOARD_COPY_BOOT_IMAGE_TO_TARGET_FILES
+# Prebuilt image variables
+_board_strip_readonly_list += BOARD_PREBUILT_INIT_BOOT_IMAGE
+
# 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
+_board_strip_list += ODM_MANIFEST_SKUS
+
+
_build_broken_var_list := \
BUILD_BROKEN_DUP_RULES \
BUILD_BROKEN_DUP_SYSPROP \
@@ -185,16 +229,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 +236,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 +247,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 +274,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))")))
@@ -436,6 +468,25 @@
endif
.KATI_READONLY := BUILDING_BOOT_IMAGE
+# Are we building an init boot image
+BUILDING_INIT_BOOT_IMAGE :=
+ifeq ($(PRODUCT_BUILD_INIT_BOOT_IMAGE),)
+ ifeq ($(BOARD_USES_RECOVERY_AS_BOOT),true)
+ BUILDING_INIT_BOOT_IMAGE :=
+ else ifdef BOARD_PREBUILT_INIT_BOOT_IMAGE
+ BUILDING_INIT_BOOT_IMAGE :=
+ else ifdef BOARD_INIT_BOOT_IMAGE_PARTITION_SIZE
+ BUILDING_INIT_BOOT_IMAGE := true
+ endif
+else ifeq ($(PRODUCT_BUILD_INIT_BOOT_IMAGE),true)
+ ifeq ($(BOARD_USES_RECOVERY_AS_BOOT),true)
+ $(error PRODUCT_BUILD_INIT_BOOT_IMAGE is true, but so is BOARD_USES_RECOVERY_AS_BOOT. Use only one option.)
+ else
+ BUILDING_INIT_BOOT_IMAGE := true
+ endif
+endif
+.KATI_READONLY := BUILDING_INIT_BOOT_IMAGE
+
# Are we building a recovery image
BUILDING_RECOVERY_IMAGE :=
ifeq ($(PRODUCT_BUILD_RECOVERY_IMAGE),)
@@ -544,6 +595,11 @@
$(warning PRODUCT_BUILD_DEBUG_BOOT_IMAGE is true, but we're not building a boot image. \
Skip building the debug boot image.)
endif
+else ifdef BUILDING_INIT_BOOT_IMAGE
+ ifeq ($(PRODUCT_BUILD_DEBUG_BOOT_IMAGE),true)
+ $(warning PRODUCT_BUILD_DEBUG_BOOT_IMAGE is true, but we don't have a ramdisk in the boot image. \
+ Skip building the debug boot image.)
+ endif
else
ifeq ($(PRODUCT_BUILD_DEBUG_BOOT_IMAGE),)
BUILDING_DEBUG_BOOT_IMAGE := true
@@ -814,6 +870,40 @@
endif
.KATI_READONLY := BUILDING_ODM_DLKM_IMAGE
+###########################################
+# Now we can substitute with the real value of TARGET_COPY_OUT_SYSTEM_DLKM
+ifeq ($(TARGET_COPY_OUT_SYSTEM_DLKM),$(_system_dlkm_path_placeholder))
+ TARGET_COPY_OUT_SYSTEM_DLKM := $(TARGET_COPY_OUT_SYSTEM)/system_dlkm
+else ifeq ($(filter system_dlkm system/system_dlkm,$(TARGET_COPY_OUT_SYSTEM_DLKM)),)
+ $(error TARGET_COPY_OUT_SYSTEM_DLKM must be either 'system_dlkm' or 'system/system_dlkm', seeing '$(TARGET_COPY_OUT_ODM_DLKM)'.)
+endif
+PRODUCT_COPY_FILES := $(subst $(_system_dlkm_path_placeholder),$(TARGET_COPY_OUT_SYSTEM_DLKM),$(PRODUCT_COPY_FILES))
+
+BOARD_USES_SYSTEM_DLKMIMAGE :=
+ifdef BOARD_PREBUILT_SYSTEM_DLKMIMAGE
+ BOARD_USES_SYSTEM_DLKMIMAGE := true
+endif
+ifdef BOARD_SYSTEM_DLKMIMAGE_FILE_SYSTEM_TYPE
+ BOARD_USES_SYSTEM_DLKMIMAGE := true
+endif
+$(call check_image_config,system_dlkm)
+
+BUILDING_SYSTEM_DLKM_IMAGE :=
+ifeq ($(PRODUCT_BUILD_SYSTEM_DLKM_IMAGE),)
+ ifdef BOARD_SYSTEM_DLKMIMAGE_FILE_SYSTEM_TYPE
+ BUILDING_SYSTEM_DLKM_IMAGE := true
+ endif
+else ifeq ($(PRODUCT_BUILD_SYSTEM_DLKM_IMAGE),true)
+ BUILDING_SYSTEM_DLKM_IMAGE := true
+ ifndef BOARD_SYSTEM_DLKMIMAGE_FILE_SYSTEM_TYPE
+ $(error PRODUCT_BUILD_SYSTEM_DLKM_IMAGE set to true, but BOARD_SYSTEM_DLKMIMAGE_FILE_SYSTEM_TYPE not defined)
+ endif
+endif
+ifdef BOARD_PREBUILT_SYSTEM_DLKMIMAGE
+ BUILDING_SYSTEM_DLKM_IMAGE :=
+endif
+.KATI_READONLY := BUILDING_SYSTEM_DLKM_IMAGE
+
BOARD_USES_PVMFWIMAGE :=
ifdef BOARD_PREBUILT_PVMFWIMAGE
BOARD_USES_PVMFWIMAGE := true
diff --git a/core/clear_vars.mk b/core/clear_vars.mk
index 15db9a8..415334f 100644
--- a/core/clear_vars.mk
+++ b/core/clear_vars.mk
@@ -288,6 +288,7 @@
LOCAL_SOONG_INSTALL_SYMLINKS :=
LOCAL_SOONG_INSTALLED_MODULE :=
LOCAL_SOONG_JACOCO_REPORT_CLASSES_JAR :=
+LOCAL_SOONG_LICENSE_METADATA :=
LOCAL_SOONG_LINK_TYPE :=
LOCAL_SOONG_LINT_REPORTS :=
LOCAL_SOONG_PROGUARD_DICT :=
diff --git a/core/combo/HOST_CROSS_linux_bionic-arm64.mk b/core/combo/HOST_CROSS_linux_bionic-arm64.mk
deleted file mode 100644
index df6865f..0000000
--- a/core/combo/HOST_CROSS_linux_bionic-arm64.mk
+++ /dev/null
@@ -1,22 +0,0 @@
-#
-# Copyright (C) 2020 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# Configuration for builds hosted on linux_arm-arm64
-# Included by combo/select.mk
-
-define $(combo_var_prefix)transform-shared-lib-to-toc
-$(call _gen_toc_command_for_elf,$(1),$(2))
-endef
diff --git a/core/combo/HOST_CROSS_windows-x86.mk b/core/combo/HOST_CROSS_windows-x86.mk
deleted file mode 100644
index d924901..0000000
--- a/core/combo/HOST_CROSS_windows-x86.mk
+++ /dev/null
@@ -1,23 +0,0 @@
-#
-# Copyright (C) 2006 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.
-#
-
-# Settings to use MinGW as a cross-compiler under Linux
-# Included by combo/select.make
-
-define $(combo_var_prefix)transform-shared-lib-to-toc
-$(hide) $($(PRIVATE_2ND_ARCH_VAR_PREFIX)$(PRIVATE_PREFIX)OBJDUMP) -x $(1) | grep "^Name" | cut -f3 -d" " > $(2)
-$(hide) $($(PRIVATE_2ND_ARCH_VAR_PREFIX)$(PRIVATE_PREFIX)NM) -g -f p $(1) | cut -f1-2 -d" " >> $(2)
-endef
diff --git a/core/combo/HOST_CROSS_windows-x86_64.mk b/core/combo/HOST_CROSS_windows-x86_64.mk
deleted file mode 100644
index d924901..0000000
--- a/core/combo/HOST_CROSS_windows-x86_64.mk
+++ /dev/null
@@ -1,23 +0,0 @@
-#
-# Copyright (C) 2006 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.
-#
-
-# Settings to use MinGW as a cross-compiler under Linux
-# Included by combo/select.make
-
-define $(combo_var_prefix)transform-shared-lib-to-toc
-$(hide) $($(PRIVATE_2ND_ARCH_VAR_PREFIX)$(PRIVATE_PREFIX)OBJDUMP) -x $(1) | grep "^Name" | cut -f3 -d" " > $(2)
-$(hide) $($(PRIVATE_2ND_ARCH_VAR_PREFIX)$(PRIVATE_PREFIX)NM) -g -f p $(1) | cut -f1-2 -d" " >> $(2)
-endef
diff --git a/core/combo/select.mk b/core/combo/select.mk
index 33c8e6d..7617558 100644
--- a/core/combo/select.mk
+++ b/core/combo/select.mk
@@ -27,6 +27,13 @@
combo_var_prefix := $(combo_2nd_arch_prefix)$(combo_target)
# Set reasonable defaults for the various variables
+ifeq ($(combo_target),HOST_CROSS_)
+$(KATI_obsolete_var \
+ $(combo_var_prefix)GLOBAL_ARFLAGS \
+ $(combo_var_prefix)STATIC_LIB_SUFFIX \
+ $(combo_var_prefix)transform-shared-lib-to-toc \
+ ,HOST_CROSS builds are not supported in Make)
+else
$(combo_var_prefix)GLOBAL_ARFLAGS := crsPD -format=gnu
@@ -34,3 +41,5 @@
# Now include the combo for this specific target.
include $(BUILD_COMBOS)/$(combo_target)$(combo_os_arch).mk
+
+endif
diff --git a/core/config.mk b/core/config.mk
index e24e957..21ab707 100644
--- a/core/config.mk
+++ b/core/config.mk
@@ -304,7 +304,7 @@
endef
# soong_config_append appends to the value of the variable in the given Soong
-# config namespace. If the varabile does not exist, it will be defined. If the
+# config namespace. If the variable does not exist, it will be defined. If the
# namespace does not exist, it will be defined.
# $1 is the namespace, $2 is the variable name, $3 is the value
define soong_config_append
@@ -312,6 +312,14 @@
$(eval SOONG_CONFIG_$(strip $1)_$(strip $2):=$(SOONG_CONFIG_$(strip $1)_$(strip $2)) $3)
endef
+# soong_config_append gets to the value of the variable in the given Soong
+# config namespace. If the namespace or variables does not exist, an
+# empty string will be returned.
+# $1 is the namespace, $2 is the variable name
+define soong_config_get
+$(SOONG_CONFIG_$(strip $1)_$(strip $2))
+endef
+
# Set the extensions used for various packages
COMMON_PACKAGE_SUFFIX := .zip
COMMON_JAVA_PACKAGE_SUFFIX := .jar
@@ -569,6 +577,7 @@
MKBOOTFS := $(HOST_OUT_EXECUTABLES)/mkbootfs$(HOST_EXECUTABLE_SUFFIX)
MINIGZIP := $(HOST_OUT_EXECUTABLES)/minigzip$(HOST_EXECUTABLE_SUFFIX)
LZ4 := $(HOST_OUT_EXECUTABLES)/lz4$(HOST_EXECUTABLE_SUFFIX)
+GENERATE_GKI_CERTIFICATE := $(HOST_OUT_EXECUTABLES)/generate_gki_certificate$(HOST_EXECUTABLE_SUFFIX)
ifeq (,$(strip $(BOARD_CUSTOM_MKBOOTIMG)))
MKBOOTIMG := $(HOST_OUT_EXECUTABLES)/mkbootimg$(HOST_EXECUTABLE_SUFFIX)
else
@@ -588,6 +597,7 @@
FS_GET_STATS := $(HOST_OUT_EXECUTABLES)/fs_get_stats$(HOST_EXECUTABLE_SUFFIX)
MKEXTUSERIMG := $(HOST_OUT_EXECUTABLES)/mkuserimg_mke2fs
MKE2FS_CONF := system/extras/ext4_utils/mke2fs.conf
+MKEROFS := $(HOST_OUT_EXECUTABLES)/mkfs.erofs
MKEROFSUSERIMG := $(HOST_OUT_EXECUTABLES)/mkerofsimage.sh
MKSQUASHFSUSERIMG := $(HOST_OUT_EXECUTABLES)/mksquashfsimage.sh
MKF2FSUSERIMG := $(HOST_OUT_EXECUTABLES)/mkf2fsuserimg.sh
@@ -753,13 +763,16 @@
endif
.KATI_READONLY := BOARD_CURRENT_API_LEVEL_FOR_VENDOR_MODULES
-min_systemsdk_version := $(firstword $(BOARD_API_LEVEL) $(BOARD_SHIPPING_API_LEVEL) $(PRODUCT_SHIPPING_API_LEVEL))
-ifneq (,$(min_systemsdk_version))
-ifneq ($(call numbers_less_than,$(min_systemsdk_version),$(BOARD_SYSTEMSDK_VERSIONS)),)
- $(error BOARD_SYSTEMSDK_VERSIONS ($(BOARD_SYSTEMSDK_VERSIONS)) must all be greater than or equal to BOARD_API_LEVEL, BOARD_SHIPPING_API_LEVEL or PRODUCT_SHIPPING_API_LEVEL ($(min_systemsdk_version)))
-endif
-endif
ifdef PRODUCT_SHIPPING_API_LEVEL
+ board_api_level := $(firstword $(BOARD_API_LEVEL) $(BOARD_SHIPPING_API_LEVEL))
+ ifneq (,$(board_api_level))
+ min_systemsdk_version := $(call math_min,$(board_api_level),$(PRODUCT_SHIPPING_API_LEVEL))
+ else
+ min_systemsdk_version := $(PRODUCT_SHIPPING_API_LEVEL)
+ endif
+ ifneq ($(call numbers_less_than,$(min_systemsdk_version),$(BOARD_SYSTEMSDK_VERSIONS)),)
+ $(error BOARD_SYSTEMSDK_VERSIONS ($(BOARD_SYSTEMSDK_VERSIONS)) must all be greater than or equal to BOARD_API_LEVEL, BOARD_SHIPPING_API_LEVEL or PRODUCT_SHIPPING_API_LEVEL ($(min_systemsdk_version)))
+ endif
ifneq ($(call math_gt_or_eq,$(PRODUCT_SHIPPING_API_LEVEL),28),)
ifneq ($(TARGET_IS_64_BIT), true)
ifneq ($(TARGET_USES_64_BIT_BINDER), true)
@@ -820,14 +833,30 @@
sepolicy_major_vers :=
sepolicy_minor_vers :=
+# BOARD_SEPOLICY_VERS must take the format "NN.m" and contain the sepolicy
+# version identifier corresponding to the sepolicy on which the non-platform
+# policy is to be based. If unspecified, this will build against the current
+# public platform policy in tree
+ifndef BOARD_SEPOLICY_VERS
+# The default platform policy version.
+BOARD_SEPOLICY_VERS := $(PLATFORM_SEPOLICY_VERSION)
+endif
+
+ifeq ($(BOARD_SEPOLICY_VERS),$(PLATFORM_SEPOLICY_VERSION))
+IS_TARGET_MIXED_SEPOLICY :=
+else
+IS_TARGET_MIXED_SEPOLICY := true
+endif
+
+.KATI_READONLY := IS_TARGET_MIXED_SEPOLICY
+
# 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 \
@@ -900,6 +929,13 @@
endif
endif
+ifneq ($(BOARD_SYSTEM_DLKMIMAGE_PARTITION_SIZE),)
+ifneq ($(BOARD_SYSTEM_DLKMIMAGE_PARTITION_RESERVED_SIZE),)
+$(error Should not define BOARD_SYSTEM_DLKMIMAGE_PARTITION_SIZE and \
+ BOARD_SYSTEM_DLKMIMAGE_PARTITION_RESERVED_SIZE together)
+endif
+endif
+
ifneq ($(BOARD_PRODUCTIMAGE_PARTITION_SIZE),)
ifneq ($(BOARD_PRODUCTIMAGE_PARTITION_RESERVED_SIZE),)
$(error Should not define BOARD_PRODUCTIMAGE_PARTITION_SIZE and \
@@ -935,7 +971,7 @@
)
# BOARD_*_PARTITION_LIST: a list of the following tokens
-valid_super_partition_list := system vendor product system_ext odm vendor_dlkm odm_dlkm
+valid_super_partition_list := system vendor product system_ext odm vendor_dlkm odm_dlkm system_dlkm
$(foreach group,$(call to-upper,$(BOARD_SUPER_PARTITION_GROUPS)), \
$(if $(filter-out $(valid_super_partition_list),$(BOARD_$(group)_PARTITION_LIST)), \
$(error BOARD_$(group)_PARTITION_LIST contains invalid partition name \
@@ -1212,7 +1248,4 @@
DEFAULT_DATA_OUT_MODULES := ltp $(ltp_packages) $(kselftest_modules)
.KATI_READONLY := DEFAULT_DATA_OUT_MODULES
-# Make RECORD_ALL_DEPS readonly.
-RECORD_ALL_DEPS :=$= $(filter true,$(RECORD_ALL_DEPS))
-
include $(BUILD_SYSTEM)/dumpvar.mk
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..5c5b565 100644
--- a/core/envsetup.mk
+++ b/core/envsetup.mk
@@ -167,6 +167,10 @@
else
$(error Unsupported HOST_CROSS_OS $(HOST_CROSS_OS))
endif
+else ifeq ($(HOST_OS),darwin)
+ HOST_CROSS_OS := darwin
+ HOST_CROSS_ARCH := arm64
+ HOST_CROSS_2ND_ARCH :=
endif
ifeq ($(HOST_OS),)
@@ -255,6 +259,7 @@
# TARGET_COPY_OUT_* are all relative to the staging directory, ie PRODUCT_OUT.
# Define them here so they can be used in product config files.
TARGET_COPY_OUT_SYSTEM := system
+TARGET_COPY_OUT_SYSTEM_DLKM := system_dlkm
TARGET_COPY_OUT_SYSTEM_OTHER := system_other
TARGET_COPY_OUT_DATA := data
TARGET_COPY_OUT_ASAN := $(TARGET_COPY_OUT_DATA)/asan
@@ -274,6 +279,7 @@
_odm_path_placeholder := ||ODM-PATH-PH||
_vendor_dlkm_path_placeholder := ||VENDOR_DLKM-PATH-PH||
_odm_dlkm_path_placeholder := ||ODM_DLKM-PATH-PH||
+_system_dlkm_path_placeholder := ||SYSTEM_DLKM-PATH-PH||
TARGET_COPY_OUT_VENDOR := $(_vendor_path_placeholder)
TARGET_COPY_OUT_VENDOR_RAMDISK := vendor_ramdisk
TARGET_COPY_OUT_PRODUCT := $(_product_path_placeholder)
@@ -284,6 +290,7 @@
TARGET_COPY_OUT_ODM := $(_odm_path_placeholder)
TARGET_COPY_OUT_VENDOR_DLKM := $(_vendor_dlkm_path_placeholder)
TARGET_COPY_OUT_ODM_DLKM := $(_odm_dlkm_path_placeholder)
+TARGET_COPY_OUT_SYSTEM_DLKM := $(_system_dlkm_path_placeholder)
# Returns the non-sanitized version of the path provided in $1.
define get_non_asan_path
@@ -310,6 +317,22 @@
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.
+# Soong config variables are dumped as $(call soong_config_set) calls
+# instead of the raw variable values, because mk2rbc can't read the
+# raw ones.
+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_.*|TRACE_BEGIN_SOONG)$$"),\
+$(v) := $(strip $($(v)))$(newline))\
+$(foreach ns,$(SOONG_CONFIG_NAMESPACES),\
+$(foreach v,$(SOONG_CONFIG_$(ns)),\
+$$(call soong_config_set,$(ns),$(v),$(SOONG_CONFIG_$(ns)_$(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 +346,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 +364,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
@@ -817,6 +842,36 @@
$(TARGET_2ND_ARCH_VAR_PREFIX)TARGET_OUT_ODM_DLKM_APPS_PRIVILEGED \
, odm_dlkm should not contain any executables, libraries, or apps)
+TARGET_OUT_SYSTEM_DLKM := $(PRODUCT_OUT)/$(TARGET_COPY_OUT_SYSTEM_DLKM)
+
+# Unlike other partitions, system_dlkm should only contain kernel modules.
+TARGET_OUT_SYSTEM_DLKM_EXECUTABLES :=
+TARGET_OUT_SYSTEM_DLKM_OPTIONAL_EXECUTABLES :=
+TARGET_OUT_SYSTEM_DLKM_SHARED_LIBRARIES :=
+TARGET_OUT_SYSTEM_DLKM_RENDERSCRIPT_BITCODE :=
+TARGET_OUT_SYSTEM_DLKM_JAVA_LIBRARIES :=
+TARGET_OUT_SYSTEM_DLKM_APPS :=
+TARGET_OUT_SYSTEM_DLKM_APPS_PRIVILEGED :=
+$(TARGET_2ND_ARCH_VAR_PREFIX)TARGET_OUT_SYSTEM_DLKM_EXECUTABLES :=
+$(TARGET_2ND_ARCH_VAR_PREFIX)TARGET_OUT_SYSTEM_DLKM_SHARED_LIBRARIES :=
+$(TARGET_2ND_ARCH_VAR_PREFIX)TARGET_OUT_SYSTEM_DLKM_RENDERSCRIPT_BITCODE :=
+$(TARGET_2ND_ARCH_VAR_PREFIX)TARGET_OUT_SYSTEM_DLKM_APPS :=
+$(TARGET_2ND_ARCH_VAR_PREFIX)TARGET_OUT_SYSTEM_DLKM_APPS_PRIVILEGED :=
+$(KATI_obsolete_var \
+ TARGET_OUT_SYSTEM_DLKM_EXECUTABLES \
+ TARGET_OUT_SYSTEM_DLKM_OPTIONAL_EXECUTABLES \
+ TARGET_OUT_SYSTEM_DLKM_SHARED_LIBRARIES \
+ TARGET_OUT_SYSTEM_DLKM_RENDERSCRIPT_BITCODE \
+ TARGET_OUT_SYSTEM_DLKM_JAVA_LIBRARIES \
+ TARGET_OUT_SYSTEM_DLKM_APPS \
+ TARGET_OUT_SYSTEM_DLKM_APPS_PRIVILEGED \
+ $(TARGET_2ND_ARCH_VAR_PREFIX)TARGET_OUT_SYSTEM_DLKM_EXECUTABLES \
+ $(TARGET_2ND_ARCH_VAR_PREFIX)TARGET_OUT_SYSTEM_DLKM_SHARED_LIBRARIES \
+ $(TARGET_2ND_ARCH_VAR_PREFIX)TARGET_OUT_SYSTEM_DLKM_RENDERSCRIPT_BITCODE \
+ $(TARGET_2ND_ARCH_VAR_PREFIX)TARGET_OUT_SYSTEM_DLKM_APPS \
+ $(TARGET_2ND_ARCH_VAR_PREFIX)TARGET_OUT_SYSTEM_DLKM_APPS_PRIVILEGED \
+ , system_dlkm should not contain any executables, libraries, or apps)
+
TARGET_OUT_PRODUCT := $(PRODUCT_OUT)/$(TARGET_COPY_OUT_PRODUCT)
TARGET_OUT_PRODUCT_EXECUTABLES := $(TARGET_OUT_PRODUCT)/bin
.KATI_READONLY := TARGET_OUT_PRODUCT
@@ -929,6 +984,9 @@
TARGET_VENDOR_DEBUG_RAMDISK_OUT := $(PRODUCT_OUT)/$(TARGET_COPY_OUT_VENDOR_DEBUG_RAMDISK)
TARGET_TEST_HARNESS_RAMDISK_OUT := $(PRODUCT_OUT)/$(TARGET_COPY_OUT_TEST_HARNESS_RAMDISK)
+TARGET_SYSTEM_DLKM_OUT := $(PRODUCT_OUT)/$(TARGET_COPY_OUT_SYSTEM_DLKM)
+.KATI_READONLY := TARGET_SYSTEM_DLKM_OUT
+
TARGET_VENDOR_RAMDISK_OUT := $(PRODUCT_OUT)/$(TARGET_COPY_OUT_VENDOR_RAMDISK)
TARGET_ROOT_OUT := $(PRODUCT_OUT)/$(TARGET_COPY_OUT_ROOT)
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/force_aapt2.mk b/core/force_aapt2.mk
index 25b45e4..5f3182a 100644
--- a/core/force_aapt2.mk
+++ b/core/force_aapt2.mk
@@ -44,10 +44,3 @@
LOCAL_SDK_RES_VERSION := current
endif
-ifeq (,$(strip $(LOCAL_MANIFEST_FILE)$(LOCAL_FULL_MANIFEST_FILE)))
- ifeq (,$(wildcard $(LOCAL_PATH)/AndroidManifest.xml))
- # work around missing manifests by creating a default one
- LOCAL_FULL_MANIFEST_FILE := $(call local-intermediates-dir,COMMON)/DefaultManifest.xml
- $(call create-default-manifest-file,$(LOCAL_FULL_MANIFEST_FILE),$(call module-min-sdk-version))
- endif
-endif
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 1798ca8..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)
@@ -378,9 +383,8 @@
endif # USE_CORE_LIB_BOOTCLASSPATH
endif # !LOCAL_IS_HOST_MODULE
-ifdef RECORD_ALL_DEPS
+# (b/204397180) Record ALL_DEPS by default.
ALL_DEPS.$(LOCAL_MODULE).ALL_DEPS := $(ALL_DEPS.$(LOCAL_MODULE).ALL_DEPS) $(full_java_bootclasspath_libs)
-endif
# Export the SDK libs. The sdk library names listed in LOCAL_SDK_LIBRARIES are first exported.
# Then sdk library names exported from dependencies are all re-exported.
diff --git a/core/main.mk b/core/main.mk
index f7cf8de..e9cbc60 100644
--- a/core/main.mk
+++ b/core/main.mk
@@ -1209,7 +1209,8 @@
$(subst $(_odm_path_placeholder),$(TARGET_COPY_OUT_ODM),\
$(subst $(_vendor_dlkm_path_placeholder),$(TARGET_COPY_OUT_VENDOR_DLKM),\
$(subst $(_odm_dlkm_path_placeholder),$(TARGET_COPY_OUT_ODM_DLKM),\
- $(foreach p,$(1),$(call append-path,$(PRODUCT_OUT),$(p)$(2)))))))))
+ $(subst $(_system_dlkm_path_placeholder),$(TARGET_COPY_OUT_SYSTEM_DLKM),\
+ $(foreach p,$(1),$(call append-path,$(PRODUCT_OUT),$(p)$(2))))))))))
endef
# Returns modules included automatically as a result of certain BoardConfig
@@ -1594,6 +1595,9 @@
.PHONY: odm_dlkmimage
odm_dlkmimage: $(INSTALLED_ODM_DLKMIMAGE_TARGET)
+.PHONY: system_dlkmimage
+system_dlkmimage: $(INSTALLED_SYSTEM_DLKMIMAGE_TARGET)
+
.PHONY: systemotherimage
systemotherimage: $(INSTALLED_SYSTEMOTHERIMAGE_TARGET)
@@ -1603,6 +1607,9 @@
.PHONY: bootimage
bootimage: $(INSTALLED_BOOTIMAGE_TARGET)
+.PHONY: initbootimage
+initbootimage: $(INSTALLED_INIT_BOOT_IMAGE_TARGET)
+
ifeq (true,$(PRODUCT_EXPORT_BOOT_IMAGE_TO_DIST))
$(call dist-for-goals, bootimage, $(INSTALLED_BOOTIMAGE_TARGET))
endif
@@ -1629,6 +1636,7 @@
$(INSTALLED_SYSTEMIMAGE_TARGET) \
$(INSTALLED_RAMDISK_TARGET) \
$(INSTALLED_BOOTIMAGE_TARGET) \
+ $(INSTALLED_INIT_BOOT_IMAGE_TARGET) \
$(INSTALLED_RADIOIMAGE_TARGET) \
$(INSTALLED_DEBUG_RAMDISK_TARGET) \
$(INSTALLED_DEBUG_BOOTIMAGE_TARGET) \
@@ -1649,6 +1657,7 @@
$(INSTALLED_ODMIMAGE_TARGET) \
$(INSTALLED_VENDOR_DLKMIMAGE_TARGET) \
$(INSTALLED_ODM_DLKMIMAGE_TARGET) \
+ $(INSTALLED_SYSTEM_DLKMIMAGE_TARGET) \
$(INSTALLED_SUPERIMAGE_EMPTY_TARGET) \
$(INSTALLED_PRODUCTIMAGE_TARGET) \
$(INSTALLED_SYSTEMOTHERIMAGE_TARGET) \
@@ -1664,6 +1673,8 @@
$(INSTALLED_FILES_JSON_VENDOR_DLKM) \
$(INSTALLED_FILES_FILE_ODM_DLKM) \
$(INSTALLED_FILES_JSON_ODM_DLKM) \
+ $(INSTALLED_FILES_FILE_SYSTEM_DLKM) \
+ $(INSTALLED_FILES_JSON_SYSTEM_DLKM) \
$(INSTALLED_FILES_FILE_PRODUCT) \
$(INSTALLED_FILES_JSON_PRODUCT) \
$(INSTALLED_FILES_FILE_SYSTEM_EXT) \
@@ -1814,6 +1825,8 @@
$(INSTALLED_FILES_JSON_VENDOR_DLKM) \
$(INSTALLED_FILES_FILE_ODM_DLKM) \
$(INSTALLED_FILES_JSON_ODM_DLKM) \
+ $(INSTALLED_FILES_FILE_SYSTEM_DLKM) \
+ $(INSTALLED_FILES_JSON_SYSTEM_DLKM) \
$(INSTALLED_FILES_FILE_PRODUCT) \
$(INSTALLED_FILES_JSON_PRODUCT) \
$(INSTALLED_FILES_FILE_SYSTEM_EXT) \
@@ -1873,6 +1886,10 @@
)
endif
+ ifeq ($(PRODUCT_EXPORT_BOOT_IMAGE_TO_DIST),true)
+ $(call dist-for-goals, droidcore-unbundled, $(INSTALLED_BOOTIMAGE_TARGET))
+ endif
+
ifeq ($(BOARD_USES_RECOVERY_AS_BOOT),true)
$(call dist-for-goals, droidcore-unbundled, \
$(recovery_ramdisk) \
diff --git a/core/node_fns.mk b/core/node_fns.mk
index 8d20160..2243cd7 100644
--- a/core/node_fns.mk
+++ b/core/node_fns.mk
@@ -208,7 +208,7 @@
$(eval $(1).$(2).inherited := \
$(call get-inherited-nodes,$(1).$(2),$(3)))
- $(call _import-nodes-inner,$(1),$($(1).$(2).inherited),$(3))
+ $(call _import-nodes-inner,$(1),$($(1).$(2).inherited),$(3),$(4))
$(call _expand-inherited-values,$(1),$(2),$(3),$(4))
diff --git a/core/notice_files.mk b/core/notice_files.mk
index 36f2c8f..4edbbb8 100644
--- a/core/notice_files.mk
+++ b/core/notice_files.mk
@@ -125,16 +125,34 @@
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))
+
+ ifdef LOCAL_SOONG_LICENSE_METADATA
+ # Soong modules have already produced a license metadata file, copy it to where Make expects it.
+ $(eval $(call copy-one-file, $(LOCAL_SOONG_LICENSE_METADATA), $(module_license_metadata)))
+ else
+ # Make modules don't have enough information to produce a license metadata rule until after fix-notice-deps
+ # has been called, store the necessary information until later.
+ 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
endif
ifdef notice_file
@@ -196,6 +214,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..8199ad2 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
@@ -93,6 +97,14 @@
endif
include $(BUILD_SYSTEM)/force_aapt2.mk
+# validate that app contains a manifest file for aapt2
+ifeq (,$(strip $(LOCAL_MANIFEST_FILE)$(LOCAL_FULL_MANIFEST_FILE)))
+ ifeq (,$(wildcard $(LOCAL_PATH)/AndroidManifest.xml))
+ $(call pretty-error,App missing manifest file which is required by aapt2. \
+Provide a manifest file by either setting LOCAL_MANIFEST_FILE in Android.mk \
+or via a AndroidManifest.xml in this directory)
+ endif
+endif
# Process Support Library dependencies.
include $(BUILD_SYSTEM)/support_libraries.mk
diff --git a/core/product-graph.mk b/core/product-graph.mk
index f28ea3d..d425b22 100644
--- a/core/product-graph.mk
+++ b/core/product-graph.mk
@@ -126,6 +126,7 @@
$(hide) echo 'PRODUCT_CHARACTERISTICS=$(call get-product-var,$(1),PRODUCT_CHARACTERISTICS)' >> $$@
$(hide) echo 'PRODUCT_COPY_FILES=$(call get-product-var,$(1),PRODUCT_COPY_FILES)' >> $$@
$(hide) echo 'PRODUCT_OTA_PUBLIC_KEYS=$(call get-product-var,$(1),PRODUCT_OTA_PUBLIC_KEYS)' >> $$@
+ $(hide) echo 'PRODUCT_EXTRA_OTA_KEYS=$(call get-product-var,$(1),PRODUCT_EXTRA_OTA_KEYS)' >> $$@
$(hide) echo 'PRODUCT_EXTRA_RECOVERY_KEYS=$(call get-product-var,$(1),PRODUCT_EXTRA_RECOVERY_KEYS)' >> $$@
$(hide) echo 'PRODUCT_PACKAGE_OVERLAYS=$(call get-product-var,$(1),PRODUCT_PACKAGE_OVERLAYS)' >> $$@
$(hide) echo 'DEVICE_PACKAGE_OVERLAYS=$(call get-product-var,$(1),DEVICE_PACKAGE_OVERLAYS)' >> $$@
diff --git a/core/product.mk b/core/product.mk
index 23fb939..43724a8 100644
--- a/core/product.mk
+++ b/core/product.mk
@@ -183,6 +183,7 @@
# signing tools can substitute them for the test key embedded by
# default.
_product_list_vars += PRODUCT_OTA_PUBLIC_KEYS
+_product_list_vars += PRODUCT_EXTRA_OTA_KEYS
_product_list_vars += PRODUCT_EXTRA_RECOVERY_KEYS
# Should we use the default resources or add any product specific overlays
@@ -232,9 +233,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.
@@ -261,6 +268,7 @@
_product_single_value_vars += PRODUCT_ODM_VERITY_PARTITION
_product_single_value_vars += PRODUCT_VENDOR_DLKM_VERITY_PARTITION
_product_single_value_vars += PRODUCT_ODM_DLKM_VERITY_PARTITION
+_product_single_value_vars += PRODUCT_SYSTEM_DLKM_VERITY_PARTITION
_product_single_value_vars += PRODUCT_SYSTEM_SERVER_DEBUG_INFO
_product_single_value_vars += PRODUCT_OTHER_JAVA_DEBUG_INFO
@@ -275,10 +283,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
@@ -291,6 +299,7 @@
_product_single_value_vars += PRODUCT_ODM_BASE_FS_PATH
_product_single_value_vars += PRODUCT_VENDOR_DLKM_BASE_FS_PATH
_product_single_value_vars += PRODUCT_ODM_DLKM_BASE_FS_PATH
+_product_single_value_vars += PRODUCT_SYSTEM_DLKM_BASE_FS_PATH
# The first API level this product shipped with
_product_single_value_vars += PRODUCT_SHIPPING_API_LEVEL
@@ -384,11 +393,13 @@
_product_single_value_vars += PRODUCT_BUILD_ODM_IMAGE
_product_single_value_vars += PRODUCT_BUILD_VENDOR_DLKM_IMAGE
_product_single_value_vars += PRODUCT_BUILD_ODM_DLKM_IMAGE
+_product_single_value_vars += PRODUCT_BUILD_SYSTEM_DLKM_IMAGE
_product_single_value_vars += PRODUCT_BUILD_CACHE_IMAGE
_product_single_value_vars += PRODUCT_BUILD_RAMDISK_IMAGE
_product_single_value_vars += PRODUCT_BUILD_USERDATA_IMAGE
_product_single_value_vars += PRODUCT_BUILD_RECOVERY_IMAGE
_product_single_value_vars += PRODUCT_BUILD_BOOT_IMAGE
+_product_single_value_vars += PRODUCT_BUILD_INIT_BOOT_IMAGE
_product_single_value_vars += PRODUCT_BUILD_DEBUG_BOOT_IMAGE
_product_single_value_vars += PRODUCT_BUILD_VENDOR_BOOT_IMAGE
_product_single_value_vars += PRODUCT_BUILD_DEBUG_VENDOR_BOOT_IMAGE
@@ -437,9 +448,19 @@
# Install a copy of the debug policy to the system_ext partition, and allow
# init-second-stage to load debug policy from system_ext.
-# This option is only meant to be set by GSI products.
+# This option is only meant to be set by compliance GSI targets.
_product_single_value_vars += PRODUCT_INSTALL_DEBUG_POLICY_TO_SYSTEM_EXT
+# If set, metadata files for the following artifacts will be generated.
+# - system/framework/*.jar
+# - system/framework/oat/<arch>/*.{oat,vdex,art}
+# - system/etc/boot-image.prof
+# - system/etc/dirty-image-objects
+# One fsverity metadata container file per one input file will be generated in
+# system.img, with a suffix ".fsv_meta". e.g. a container file for
+# "/system/framework/foo.jar" will be "system/framework/foo.jar.fsv_meta".
+_product_single_value_vars += PRODUCT_SYSTEM_FSVERITY_GENERATE_METADATA
+
.KATI_READONLY := _product_single_value_vars _product_list_vars
_product_var_list :=$= $(_product_single_value_vars) $(_product_list_vars)
diff --git a/core/product_config.mk b/core/product_config.mk
index 6588b8e..15935ea 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
@@ -365,6 +381,7 @@
ENFORCE_SYSTEM_CERTIFICATE_ALLOW_LIST := $(PRODUCT_ARTIFACT_SYSTEM_CERTIFICATE_REQUIREMENT_ALLOW_LIST)
PRODUCT_OTA_PUBLIC_KEYS := $(sort $(PRODUCT_OTA_PUBLIC_KEYS))
+PRODUCT_EXTRA_OTA_KEYS := $(sort $(PRODUCT_EXTRA_OTA_KEYS))
PRODUCT_EXTRA_RECOVERY_KEYS := $(sort $(PRODUCT_EXTRA_RECOVERY_KEYS))
# Resolve and setup per-module dex-preopt configs.
@@ -396,16 +413,22 @@
_psmc_modules :=
# Reset ADB keys for non-debuggable builds
-ifeq (,$(filter eng userdebug,$(TARGET_BUILD_VARIANT)),)
+ifeq (,$(filter eng userdebug,$(TARGET_BUILD_VARIANT)))
PRODUCT_ADB_KEYS :=
endif
ifneq ($(filter-out 0 1,$(words $(PRODUCT_ADB_KEYS))),)
$(error Only one file may be in PRODUCT_ADB_KEYS: $(PRODUCT_ADB_KEYS))
endif
+# Show a warning wall of text if non-compliance-GSI products set this option.
ifdef PRODUCT_INSTALL_DEBUG_POLICY_TO_SYSTEM_EXT
- ifeq (,$(filter gsi_arm gsi_arm64 gsi_x86 gsi_x86_64,$(PRODUCT_NAME)))
- $(error Only GSI products are allowed to set PRODUCT_INSTALL_DEBUG_POLICY_TO_SYSTEM_EXT)
+ ifeq (,$(filter gsi_arm gsi_arm64 gsi_x86 gsi_x86_64 gsi_car_arm64 gsi_car_x86_64,$(PRODUCT_NAME)))
+ $(warning PRODUCT_INSTALL_DEBUG_POLICY_TO_SYSTEM_EXT is set but \
+ PRODUCT_NAME ($(PRODUCT_NAME)) doesn't look like a GSI for compliance \
+ testing. This is a special configuration for compliance GSI, so do make \
+ sure you understand the security implications before setting this \
+ option. If you don't know what this option does, then you probably \
+ shouldn't set this.)
endif
endif
@@ -543,6 +566,7 @@
ODM \
VENDOR_DLKM \
ODM_DLKM \
+ SYSTEM_DLKM \
CACHE \
RAMDISK \
USERDATA \
diff --git a/core/product_config.rbc b/core/product_config.rbc
index fe6ba7c..2820695 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
@@ -147,7 +158,7 @@
# Run this one, obtaining its configuration and child PCMs.
if _options.trace_modules:
- print("%d:" % n)
+ print("#%d: %s" % (n, name))
# Run PCM.
handle = __h_new()
@@ -156,7 +167,7 @@
# Now we know everything about this PCM, record it in 'configs'.
children = __h_inherited_modules(handle)
if _options.trace_modules:
- print(" ", " ".join(children.keys()))
+ print("# ", " ".join(children.keys()))
configs[name] = (pcm, __h_cfg(handle), children.keys(), False)
pcm_count = pcm_count + 1
@@ -180,9 +191,9 @@
fail("Ran %d modules but postfix tree has only %d entries" % (pcm_count, len(config_postfix)))
if _options.trace_modules:
- print("\n---Postfix---")
+ print("\n#---Postfix---")
for x in config_postfix:
- print(" ", x)
+ print("# ", x)
# Traverse the tree from the bottom, evaluating inherited values
for pcm_name in config_postfix:
@@ -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):
@@ -321,6 +343,11 @@
ns[var] += " " + value
+def _soong_config_get(g, nsname, var):
+ """Gets to the value of the variable in the namespace."""
+ return g.get(_soong_config_namespaces_key, {}).get(nsname, {}).get(var, None)
+
+
def _abspath(path):
"""Provided for compatibility, to be removed later."""
return path
@@ -356,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:
@@ -449,6 +476,12 @@
return sorted(["%s/%s:%s/%s" % (
from_dir, f, to_dir, f) for f in rblf_find_files(from_dir, pattern, only_files=1)])
+def _findstring(needle, haystack):
+ """Equivalent to GNU make's $(findstring)."""
+ if haystack.find(needle) < 0:
+ return ""
+ return needle
+
def _filter_out(pattern, text):
"""Return all the words from `text' that do not match any word in `pattern'.
@@ -480,6 +513,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."""
@@ -499,7 +539,25 @@
def _expand_wildcard(pattern):
"""Expands shell wildcard pattern."""
- return rblf_wildcard(pattern)
+ result = []
+ for word in __words(pattern):
+ result.extend(rblf_wildcard(word))
+ return result
+
+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."""
@@ -509,6 +567,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)
@@ -619,6 +689,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":
@@ -636,6 +707,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.
@@ -644,6 +717,7 @@
soong_config_namespace = _soong_config_namespace,
soong_config_append = _soong_config_append,
soong_config_set = _soong_config_set,
+ soong_config_get = _soong_config_get,
abspath = _abspath,
addprefix = _addprefix,
addsuffix = _addsuffix,
@@ -659,15 +733,18 @@
filter = _filter,
filter_out = _filter_out,
find_and_copy = _find_and_copy,
- init_globals = _init_globals,
+ findstring = _findstring,
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,
@@ -678,4 +755,5 @@
setdefault = _setdefault,
shell = rblf_shell,
warning = _mkwarning,
+ words = __words,
)
diff --git a/core/proguard.flags b/core/proguard.flags
index 50049cb..185275e 100644
--- a/core/proguard.flags
+++ b/core/proguard.flags
@@ -15,35 +15,24 @@
@**.VisibleForTesting *;
}
-# Understand the @Keep support annotation.
--keep class android.support.annotation.Keep
--keep class androidx.annotation.Keep
+# Understand the common @Keep annotation from various Android packages:
+# * android.support.annotation
+# * androidx.annotation
+# * com.android.internal.annotations
+-keep class **android**.annotation*.Keep
--keep @android.support.annotation.Keep class * {*;}
--keep @androidx.annotation.Keep class * {*;}
+-keep @**android**.annotation*.Keep class * { *; }
-keepclasseswithmembers class * {
- @android.support.annotation.Keep <methods>;
+ @**android**.annotation*.Keep <methods>;
}
-keepclasseswithmembers class * {
- @androidx.annotation.Keep <methods>;
+ @**android**.annotation*.Keep <fields>;
}
-keepclasseswithmembers class * {
- @android.support.annotation.Keep <fields>;
-}
-
--keepclasseswithmembers class * {
- @androidx.annotation.Keep <fields>;
-}
-
--keepclasseswithmembers class * {
- @android.support.annotation.Keep <init>(...);
-}
-
--keepclasseswithmembers class * {
- @androidx.annotation.Keep <init>(...);
+ @**android**.annotation*.Keep <init>(...);
}
-include proguard_basic_keeps.flags
diff --git a/core/robolectric_test_config_template.xml b/core/robolectric_test_config_template.xml
index e62175f..483b957 100644
--- a/core/robolectric_test_config_template.xml
+++ b/core/robolectric_test_config_template.xml
@@ -18,7 +18,7 @@
<option name="test-suite-tag" value="robolectric" />
<option name="test-suite-tag" value="robolectric-tests" />
- <option name="java-folder" value="prebuilts/jdk/jdk9/linux-x86/" />
+ <option name="java-folder" value="prebuilts/jdk/jdk11/linux-x86/" />
<option name="exclude-paths" value="java" />
<option name="use-robolectric-resources" value="true" />
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 ee06432..006e1dc 100644
--- a/core/soong_app_prebuilt.mk
+++ b/core/soong_app_prebuilt.mk
@@ -138,13 +138,6 @@
java-dex: $(LOCAL_SOONG_DEX_JAR)
-my_built_installed := $(foreach f,$(LOCAL_SOONG_BUILT_INSTALLED),\
- $(call word-colon,1,$(f)):$(PRODUCT_OUT)$(call word-colon,2,$(f)))
-my_installed := $(call copy-many-files, $(my_built_installed))
-ALL_MODULES.$(my_register_name).INSTALLED += $(my_installed)
-ALL_MODULES.$(my_register_name).BUILT_INSTALLED += $(my_built_installed)
-$(my_all_targets): $(my_installed)
-
# Copy test suite files.
ifdef LOCAL_COMPATIBILITY_SUITE
my_apks_to_install := $(foreach f,$(filter %.apk %.idsig,$(LOCAL_SOONG_BUILT_INSTALLED)),$(call word-colon,1,$(f)))
@@ -265,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..7f9dbe5 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))
@@ -174,6 +174,7 @@
$(call add_json_str, OdmPath, $(TARGET_COPY_OUT_ODM))
$(call add_json_str, VendorDlkmPath, $(TARGET_COPY_OUT_VENDOR_DLKM))
$(call add_json_str, OdmDlkmPath, $(TARGET_COPY_OUT_ODM_DLKM))
+$(call add_json_str, SystemDlkmPath, $(TARGET_COPY_OUT_SYSTEM_DLKM))
$(call add_json_str, ProductPath, $(TARGET_COPY_OUT_PRODUCT))
$(call add_json_str, SystemExtPath, $(TARGET_COPY_OUT_SYSTEM_EXT))
$(call add_json_bool, MinimizeJavaDebugInfo, $(filter true,$(PRODUCT_MINIMIZE_JAVA_DEBUG_INFO)))
@@ -189,11 +190,17 @@
$(call add_json_list, PgoAdditionalProfileDirs, $(PGO_ADDITIONAL_PROFILE_DIRS))
+$(call add_json_list, BoardPlatVendorPolicy, $(BOARD_PLAT_VENDOR_POLICY))
$(call add_json_list, BoardReqdMaskPolicy, $(BOARD_REQD_MASK_POLICY))
+$(call add_json_list, BoardSystemExtPublicPrebuiltDirs, $(BOARD_SYSTEM_EXT_PUBLIC_PREBUILT_DIRS))
+$(call add_json_list, BoardSystemExtPrivatePrebuiltDirs, $(BOARD_SYSTEM_EXT_PRIVATE_PREBUILT_DIRS))
+$(call add_json_list, BoardProductPublicPrebuiltDirs, $(BOARD_PRODUCT_PUBLIC_PREBUILT_DIRS))
+$(call add_json_list, BoardProductPrivatePrebuiltDirs, $(BOARD_PRODUCT_PRIVATE_PREBUILT_DIRS))
$(call add_json_list, BoardVendorSepolicyDirs, $(BOARD_VENDOR_SEPOLICY_DIRS) $(BOARD_SEPOLICY_DIRS))
$(call add_json_list, BoardOdmSepolicyDirs, $(BOARD_ODM_SEPOLICY_DIRS))
$(call add_json_list, BoardVendorDlkmSepolicyDirs, $(BOARD_VENDOR_DLKM_SEPOLICY_DIRS))
$(call add_json_list, BoardOdmDlkmSepolicyDirs, $(BOARD_ODM_DLKM_SEPOLICY_DIRS))
+$(call add_json_list, BoardSystemDlkmSepolicyDirs, $(BOARD_SYSTEM_DLKM_SEPOLICY_DIRS))
# TODO: BOARD_PLAT_* dirs only kept for compatibility reasons. Will be a hard error on API level 31
$(call add_json_list, SystemExtPublicSepolicyDirs, $(SYSTEM_EXT_PUBLIC_SEPOLICY_DIRS) $(BOARD_PLAT_PUBLIC_SEPOLICY_DIR))
$(call add_json_list, SystemExtPrivateSepolicyDirs, $(SYSTEM_EXT_PRIVATE_SEPOLICY_DIRS) $(BOARD_PLAT_PRIVATE_SEPOLICY_DIR))
@@ -202,6 +209,7 @@
$(call add_json_str, PlatformSepolicyVersion, $(PLATFORM_SEPOLICY_VERSION))
$(call add_json_str, TotSepolicyVersion, $(TOT_SEPOLICY_VERSION))
+$(call add_json_list, PlatformSepolicyCompatVersions, $(PLATFORM_SEPOLICY_COMPAT_VERSIONS))
$(call add_json_bool, Flatten_apex, $(filter true,$(TARGET_FLATTEN_APEX)))
$(call add_json_bool, ForceApexSymlinkOptimization, $(filter true,$(TARGET_FORCE_APEX_SYMLINK_OPTIMIZATION)))
@@ -271,6 +279,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 2c909ac..b819cdc 100644
--- a/core/soong_java_prebuilt.mk
+++ b/core/soong_java_prebuilt.mk
@@ -154,13 +154,7 @@
endif
endif # LOCAL_SOONG_DEX_JAR
-my_built_installed := $(foreach f,$(LOCAL_SOONG_BUILT_INSTALLED),\
- $(call word-colon,1,$(f)):$(PRODUCT_OUT)$(call word-colon,2,$(f)))
-my_installed := $(call copy-many-files, $(my_built_installed))
-ALL_MODULES.$(my_register_name).INSTALLED += $(my_installed)
-ALL_MODULES.$(my_register_name).BUILT_INSTALLED += $(my_built_installed)
ALL_MODULES.$(my_register_name).CLASSES_JAR := $(full_classes_jar)
-$(my_register_name): $(my_installed)
ifdef LOCAL_SOONG_AAR
ALL_MODULES.$(my_register_name).AAR := $(LOCAL_SOONG_AAR)
@@ -215,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/core/static_java_library.mk b/core/static_java_library.mk
index 7a87322..4053985 100644
--- a/core/static_java_library.mk
+++ b/core/static_java_library.mk
@@ -101,6 +101,13 @@
include $(BUILD_SYSTEM)/java_renderscript.mk
ifeq (true,$(need_compile_res))
+# work around missing manifests by creating a default one
+ifeq (,$(strip $(LOCAL_MANIFEST_FILE)$(LOCAL_FULL_MANIFEST_FILE)))
+ ifeq (,$(wildcard $(LOCAL_PATH)/AndroidManifest.xml))
+ LOCAL_FULL_MANIFEST_FILE := $(call local-intermediates-dir,COMMON)/DefaultManifest.xml
+ $(call create-default-manifest-file,$(LOCAL_FULL_MANIFEST_FILE),$(call module-min-sdk-version))
+ endif
+endif
include $(BUILD_SYSTEM)/android_manifest.mk
LOCAL_SDK_RES_VERSION:=$(strip $(LOCAL_SDK_RES_VERSION))
diff --git a/core/sysprop.mk b/core/sysprop.mk
index 1d38f8c..12ead6e 100644
--- a/core/sysprop.mk
+++ b/core/sysprop.mk
@@ -464,6 +464,20 @@
$(empty),\
$(empty)))
+# ----------------------------------------------------------------
+# system_dlkm/build.prop
+#
+
+INSTALLED_SYSTEM_DLKM_BUILD_PROP_TARGET := $(TARGET_OUT_SYSTEM_DLKM)/etc/build.prop
+$(eval $(call build-properties,\
+ system_dlkm,\
+ $(INSTALLED_SYSTEM_DLKM_BUILD_PROP_TARGET),\
+ $(empty),\
+ $(empty),\
+ $(empty),\
+ $(empty),\
+ $(empty)))
+
# -----------------------------------------------------------------
# system_ext/etc/build.prop
#
diff --git a/core/tasks/module-info.mk b/core/tasks/module-info.mk
index 5d5bfa8..aeeb403 100644
--- a/core/tasks/module-info.mk
+++ b/core/tasks/module-info.mk
@@ -23,6 +23,7 @@
'"classes_jar": [$(foreach w,$(sort $(ALL_MODULES.$(m).CLASSES_JAR)),"$(w)", )], ' \
'"test_mainline_modules": [$(foreach w,$(sort $(ALL_MODULES.$(m).TEST_MAINLINE_MODULES)),"$(w)", )], ' \
'"is_unit_test": "$(ALL_MODULES.$(m).IS_UNIT_TEST)", ' \
+ '"data": [$(foreach w,$(sort $(ALL_MODULES.$(m).TEST_DATA)),"$(w)", )], ' \
'},\n' \
) | sed -e 's/, *\]/]/g' -e 's/, *\}/ }/g' -e '$$s/,$$//' >> $@
$(hide) echo '}' >> $@
diff --git a/core/version_defaults.mk b/core/version_defaults.mk
index 1f1ec3f..051de62 100644
--- a/core/version_defaults.mk
+++ b/core/version_defaults.mk
@@ -85,7 +85,7 @@
# 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.
- PLATFORM_SECURITY_PATCH := 2021-11-05
+ PLATFORM_SECURITY_PATCH := 2022-01-05
endif
.KATI_READONLY := PLATFORM_SECURITY_PATCH
diff --git a/envsetup.sh b/envsetup.sh
index 4301d73..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
@@ -1883,16 +1882,6 @@
fi
}
-# Source necessary setup scripts needed to run the build with Remote Execution.
-function source_rbe() {
- local T=$(gettop)
-
- if [[ "x$USE_RBE" != "x" && "$USE_RBE" != "false" ]]; then
- . $T/build/make/rbesetup.sh --skip-envsetup
- fi
-}
-
validate_current_shell
source_vendorsetup
-source_rbe
addcompletions
diff --git a/help.sh b/help.sh
index 06a9056..e51adc1 100755
--- a/help.sh
+++ b/help.sh
@@ -52,6 +52,8 @@
Stands for "VendorDlkm, NO Dependencies"
odnod Quickly rebuild the odm_dlkm image from built packages
Stands for "OdmDlkm, NO Dependencies"
+ sdnod Quickly rebuild the system_dlkm image from built packages
+ Stands for "SystemDlkm, NO Dependencies"
So, for example, you could run:
diff --git a/target/board/BoardConfigGkiCommon.mk b/target/board/BoardConfigGkiCommon.mk
index c0f5db9..63ef2b4 100644
--- a/target/board/BoardConfigGkiCommon.mk
+++ b/target/board/BoardConfigGkiCommon.mk
@@ -16,11 +16,7 @@
# Enable GKI 2.0 signing.
BOARD_GKI_SIGNING_KEY_PATH := build/make/target/product/gsi/testkey_rsa2048.pem
BOARD_GKI_SIGNING_ALGORITHM := SHA256_RSA2048
-
-# The following is needed to allow release signing process appends more extra
-# args, e.g., passing --signing_helper_with_files from mkbootimg to avbtool.
-# See b/178559811 for more details.
-BOARD_GKI_SIGNING_SIGNATURE_ARGS := --prop foo:bar
+BOARD_GKI_SIGNING_SIGNATURE_ARGS :=
# Sets boot SPL.
BOOT_SECURITY_PATCH = $(PLATFORM_SECURITY_PATCH)
diff --git a/target/product/OWNERS b/target/product/OWNERS
index 82e6e88..b3d8998 100644
--- a/target/product/OWNERS
+++ b/target/product/OWNERS
@@ -2,4 +2,4 @@
# GSI
per-file gsi_release.mk = file:/target/product/gsi/OWNERS
-per-file gsi_keys.mk = file:/target/product/gsi/OWNERS
+per-file developer_gsi_keys.mk = file:/target/product/gsi/OWNERS
diff --git a/target/product/base_system.mk b/target/product/base_system.mk
index 68dd980..3d299fb 100644
--- a/target/product/base_system.mk
+++ b/target/product/base_system.mk
@@ -66,6 +66,7 @@
com.android.neuralnetworks \
com.android.scheduling \
com.android.sdkext \
+ com.android.sepolicy \
com.android.tethering \
com.android.tzdata \
com.android.wifi \
@@ -79,6 +80,7 @@
dnsmasq \
DownloadProvider \
dpm \
+ dump.erofs \
dumpstate \
dumpsys \
DynamicSystemInstallationService \
@@ -89,6 +91,7 @@
framework-minus-apex \
framework-res \
framework-sysconfig.xml \
+ fsck.erofs \
fsck_msdos \
fsverity-release-cert-der \
fs_config_files_system \
@@ -213,6 +216,7 @@
MediaProviderLegacy \
mediaserver \
mke2fs \
+ mkfs.erofs \
monkey \
mtpd \
ndc \
@@ -318,9 +322,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 +335,7 @@
lpdump \
minigzip \
mke2fs \
+ mkfs.erofs \
resize2fs \
sgdisk \
sqlite3 \
@@ -361,8 +368,8 @@
PRODUCT_PACKAGES_DEBUG := \
adb_keys \
arping \
+ com.android.sepolicy.cert-debug.der \
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 071edbf..3223002 100644
--- a/target/product/default_art_config.mk
+++ b/target/product/default_art_config.mk
@@ -14,6 +14,9 @@
# limitations under the License.
#
+# This file contains product config for the ART module that is common for
+# platform and unbundled builds.
+
ifeq ($(ART_APEX_JARS),)
$(error ART_APEX_JARS is empty; cannot initialize PRODUCT_BOOT_JARS variable)
endif
@@ -46,6 +49,7 @@
# APEX boot jars. Keep the list sorted by module names and then library names.
# Note: core-icu4j is moved back to PRODUCT_BOOT_JARS in product_config.mk at a later stage.
+# Note: For modules available in Q, DO NOT add new entries here.
PRODUCT_APEX_BOOT_JARS := \
com.android.appsearch:framework-appsearch \
com.android.conscrypt:conscrypt \
@@ -59,16 +63,34 @@
com.android.scheduling:framework-scheduling \
com.android.sdkext:framework-sdkextensions \
com.android.tethering:framework-connectivity \
+ com.android.tethering:framework-connectivity-tiramisu \
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.
+# Note: For modules available in Q, DO NOT add new entries here.
PRODUCT_APEX_SYSTEM_SERVER_JARS := \
com.android.appsearch:service-appsearch \
com.android.art:service-art \
com.android.media:service-media-s \
com.android.permission:service-permission \
+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.
+# Note: For modules available in Q, DO NOT add new entries here.
+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.bp b/target/product/gsi/Android.bp
index 88472eb..a8af9c4 100644
--- a/target/product/gsi/Android.bp
+++ b/target/product/gsi/Android.bp
@@ -14,11 +14,7 @@
package {
// See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "build_make_license"
- // to get the below license kinds:
- // legacy_restricted
- default_applicable_licenses: ["build_make_license"],
+ default_applicable_licenses: ["Android-Apache-2.0"],
}
filegroup {
diff --git a/target/product/gsi/Android.mk b/target/product/gsi/Android.mk
index 39848e5..85e551d 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) | sort | \
+ diff --old-line-format="Removed %L" \
--new-line-format="Added %L" \
--unchanged-line-format="" \
- $(LATEST_VNDK_LIB_LIST) $(INTERNAL_VNDK_LIB_LIST) \
+ <(cat $(LATEST_VNDK_LIB_LIST) | sort) - \
|| ( echo -e $(_vndk_check_failure_message); exit 1 ))
$(hide) mkdir -p $(dir $@)
$(hide) touch $@
@@ -63,8 +73,9 @@
# Script to update the latest VNDK lib list
include $(CLEAR_VARS)
LOCAL_MODULE := update-vndk-list.sh
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
LOCAL_MODULE_CLASS := EXECUTABLES
LOCAL_MODULE_STEM := $(LOCAL_MODULE)
LOCAL_IS_HOST_MODULE := true
@@ -84,9 +95,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 $@
@@ -156,8 +171,9 @@
include $(CLEAR_VARS)
LOCAL_MODULE := vndk_package
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
# Filter LLNDK libs moved to APEX to avoid pulling them into /system/LIB
LOCAL_REQUIRED_MODULES := \
$(filter-out $(LLNDK_MOVED_TO_APEX_LIBRARIES),$(LLNDK_LIBRARIES))
@@ -181,8 +197,9 @@
_vndk_versions += $(BOARD_VNDK_VERSION)
endif
LOCAL_MODULE := vndk_apex_snapshot_package
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
LOCAL_REQUIRED_MODULES := $(foreach vndk_ver,$(_vndk_versions),com.android.vndk.v$(vndk_ver))
include $(BUILD_PHONY_PACKAGE)
@@ -195,8 +212,9 @@
include $(CLEAR_VARS)
LOCAL_MODULE := gsi_skip_mount.cfg
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
LOCAL_MODULE_STEM := skip_mount.cfg
LOCAL_SRC_FILES := $(LOCAL_MODULE)
LOCAL_MODULE_CLASS := ETC
@@ -220,8 +238,9 @@
include $(CLEAR_VARS)
LOCAL_MODULE := init.gsi.rc
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
LOCAL_SRC_FILES := $(LOCAL_MODULE)
LOCAL_MODULE_CLASS := ETC
LOCAL_SYSTEM_EXT_MODULE := true
@@ -232,8 +251,9 @@
include $(CLEAR_VARS)
LOCAL_MODULE := init.vndk-nodef.rc
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
LOCAL_SRC_FILES := $(LOCAL_MODULE)
LOCAL_MODULE_CLASS := ETC
LOCAL_SYSTEM_EXT_MODULE := true
diff --git a/target/product/gsi/current.txt b/target/product/gsi/current.txt
index 399652c..f9c1f3d 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
@@ -32,10 +29,10 @@
VNDK-SP: android.hardware.graphics.mapper@3.0.so
VNDK-SP: android.hardware.graphics.mapper@4.0.so
VNDK-SP: android.hardware.renderscript@1.0.so
+VNDK-SP: android.hidl.safe_union@1.0.so
VNDK-SP: android.hidl.memory.token@1.0.so
VNDK-SP: android.hidl.memory@1.0-impl.so
VNDK-SP: android.hidl.memory@1.0.so
-VNDK-SP: android.hidl.safe_union@1.0.so
VNDK-SP: libRSCpuRef.so
VNDK-SP: libRSDriver.so
VNDK-SP: libRS_internal.so
@@ -59,17 +56,17 @@
VNDK-SP: libutils.so
VNDK-SP: libutilscallstack.so
VNDK-SP: libz.so
+VNDK-core: android.hardware.audio.common-V1-ndk.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.bluetooth.audio-V1-ndk.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 +74,42 @@
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.nfc-V1-ndk.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.dice-V1-ndk.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.hardware.wifi.supplicant-V1-ndk.so
VNDK-core: android.hidl.token@1.0-utils.so
VNDK-core: android.hidl.token@1.0.so
+VNDK-core: android.media.audio.common.types-V1-ndk.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/target/product/gsi_keys.mk b/target/product/gsi_keys.mk
deleted file mode 100644
index 5a814db..0000000
--- a/target/product/gsi_keys.mk
+++ /dev/null
@@ -1,22 +0,0 @@
-#
-# Copyright (C) 2019 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.
-#
-
-# Include GSI keys into first-stage ramdisk, so we can enable verified
-# boot when booting a GSI.
-PRODUCT_PACKAGES += \
- q-gsi.avbpubkey \
- r-gsi.avbpubkey \
- s-gsi.avbpubkey \
diff --git a/target/product/gsi_release.mk b/target/product/gsi_release.mk
index 94917f5..b1266ee 100644
--- a/target/product/gsi_release.mk
+++ b/target/product/gsi_release.mk
@@ -34,7 +34,7 @@
# GSI should always support up-to-date platform features.
# Keep this value at the latest API level to ensure latest build system
# default configs are applied.
-PRODUCT_SHIPPING_API_LEVEL := 30
+PRODUCT_SHIPPING_API_LEVEL := 31
# Enable dynamic partitions to facilitate mixing onto Cuttlefish
PRODUCT_USE_DYNAMIC_PARTITIONS := true
@@ -62,8 +62,12 @@
init.gsi.rc \
init.vndk-nodef.rc \
-# Support additional P, Q and R VNDK packages
-PRODUCT_EXTRA_VNDK_VERSIONS := 28 29 30
+# Support additional VNDK snapshots
+PRODUCT_EXTRA_VNDK_VERSIONS := \
+ 28 \
+ 29 \
+ 30 \
+ 31 \
# Do not build non-GSI partition images.
PRODUCT_BUILD_CACHE_IMAGE := false
diff --git a/target/product/handheld_system.mk b/target/product/handheld_system.mk
index b7a2d0d..3a59f6c 100644
--- a/target/product/handheld_system.mk
+++ b/target/product/handheld_system.mk
@@ -43,7 +43,6 @@
CameraExtensionsProxy \
CaptivePortalLogin \
CertInstaller \
- clatd \
DocumentsUI \
DownloadProviderUi \
EasterEgg \
diff --git a/target/product/runtime_libart.mk b/target/product/runtime_libart.mk
index 301605a..ee63757 100644
--- a/target/product/runtime_libart.mk
+++ b/target/product/runtime_libart.mk
@@ -61,7 +61,7 @@
apex_test_module := art-check-release-apex-gen-fakebin
endif
-ifeq (true,$(SOONG_CONFIG_art_module_source_build)
+ifeq (true,$(call soong_config_get,art_module,source_build))
PRODUCT_HOST_PACKAGES += $(apex_test_module)
endif
@@ -75,6 +75,14 @@
PRODUCT_PACKAGES += \
hiddenapi-package-whitelist.xml \
+ifeq (,$(TARGET_BUILD_UNBUNDLED))
+ # Don't depend on the framework boot image profile in unbundled builds where
+ # frameworks/base may not be present.
+ # TODO(b/179900989): We may not need this check once we stop using full
+ # platform products on the thin ART manifest branch.
+ PRODUCT_DEX_PREOPT_BOOT_IMAGE_PROFILE_LOCATION += frameworks/base/boot/boot-image-profile.txt
+endif
+
# The dalvik.vm.dexopt.thermal-cutoff property must contain one of the values
# listed here:
#
diff --git a/target/product/security/Android.bp b/target/product/security/Android.bp
index 99f7742..1e26d59 100644
--- a/target/product/security/Android.bp
+++ b/target/product/security/Android.bp
@@ -1,11 +1,7 @@
// AOSP test certificate
package {
// See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "build_make_license"
- // to get the below license kinds:
- // legacy_restricted
- default_applicable_licenses: ["build_make_license"],
+ default_applicable_licenses: ["Android-Apache-2.0"],
}
android_app_certificate {
diff --git a/target/product/security/Android.mk b/target/product/security/Android.mk
index cedad5b..ad25a92 100644
--- a/target/product/security/Android.mk
+++ b/target/product/security/Android.mk
@@ -5,8 +5,9 @@
include $(CLEAR_VARS)
LOCAL_MODULE := verity_key
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
LOCAL_SRC_FILES := $(LOCAL_MODULE)
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT)
@@ -26,8 +27,9 @@
ifneq ($(BOARD_BUILD_SYSTEM_ROOT_IMAGE),true)
include $(CLEAR_VARS)
LOCAL_MODULE := verity_key_ramdisk
- LOCAL_LICENSE_KINDS := legacy_restricted
- LOCAL_LICENSE_CONDITIONS := restricted
+ LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+ LOCAL_LICENSE_CONDITIONS := notice
+ LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
LOCAL_MODULE_CLASS := ETC
LOCAL_SRC_FILES := verity_key
LOCAL_MODULE_STEM := verity_key
@@ -41,8 +43,9 @@
ifneq ($(filter eng userdebug,$(TARGET_BUILD_VARIANT)),)
include $(CLEAR_VARS)
LOCAL_MODULE := adb_keys
- LOCAL_LICENSE_KINDS := legacy_restricted
- LOCAL_LICENSE_CONDITIONS := restricted
+ LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+ LOCAL_LICENSE_CONDITIONS := notice
+ LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT)
LOCAL_PREBUILT_MODULE_FILE := $(PRODUCT_ADB_KEYS)
@@ -57,15 +60,24 @@
include $(CLEAR_VARS)
LOCAL_MODULE := otacerts
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_STEM := otacerts.zip
LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/security
include $(BUILD_SYSTEM)/base_rules.mk
+
+extra_ota_keys := $(addsuffix .x509.pem,$(PRODUCT_EXTRA_OTA_KEYS))
+
$(LOCAL_BUILT_MODULE): PRIVATE_CERT := $(DEFAULT_SYSTEM_DEV_CERTIFICATE).x509.pem
-$(LOCAL_BUILT_MODULE): $(SOONG_ZIP) $(DEFAULT_SYSTEM_DEV_CERTIFICATE).x509.pem
- $(SOONG_ZIP) -o $@ -j -symlinks=false -f $(PRIVATE_CERT)
+$(LOCAL_BUILT_MODULE): PRIVATE_EXTRA_OTA_KEYS := $(extra_ota_keys)
+$(LOCAL_BUILT_MODULE): \
+ $(SOONG_ZIP) \
+ $(DEFAULT_SYSTEM_DEV_CERTIFICATE).x509.pem \
+ $(extra_ota_keys)
+ $(SOONG_ZIP) -o $@ -j -symlinks=false \
+ $(addprefix -f ,$(PRIVATE_CERT) $(PRIVATE_EXTRA_OTA_KEYS))
#######################################
@@ -73,14 +85,15 @@
include $(CLEAR_VARS)
LOCAL_MODULE := otacerts.recovery
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_STEM := otacerts.zip
LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/etc/security
include $(BUILD_SYSTEM)/base_rules.mk
-extra_recovery_keys := $(patsubst %,%.x509.pem,$(PRODUCT_EXTRA_RECOVERY_KEYS))
+extra_recovery_keys := $(addsuffix .x509.pem,$(PRODUCT_EXTRA_RECOVERY_KEYS))
$(LOCAL_BUILT_MODULE): PRIVATE_CERT := $(DEFAULT_SYSTEM_DEV_CERTIFICATE).x509.pem
$(LOCAL_BUILT_MODULE): PRIVATE_EXTRA_RECOVERY_KEYS := $(extra_recovery_keys)
@@ -89,4 +102,4 @@
$(DEFAULT_SYSTEM_DEV_CERTIFICATE).x509.pem \
$(extra_recovery_keys)
$(SOONG_ZIP) -o $@ -j -symlinks=false \
- $(foreach key_file, $(PRIVATE_CERT) $(PRIVATE_EXTRA_RECOVERY_KEYS), -f $(key_file))
+ $(addprefix -f ,$(PRIVATE_CERT) $(PRIVATE_EXTRA_RECOVERY_KEYS))
diff --git a/target/product/security/README b/target/product/security/README
index 4ad5236..2b161bb 100644
--- a/target/product/security/README
+++ b/target/product/security/README
@@ -16,7 +16,6 @@
development/tools/make_key shared '/C=US/ST=California/L=Mountain View/O=Android/OU=Android/CN=Android/emailAddress=android@android.com'
development/tools/make_key media '/C=US/ST=California/L=Mountain View/O=Android/OU=Android/CN=Android/emailAddress=android@android.com'
development/tools/make_key cts_uicc_2021 '/C=US/ST=California/L=Mountain View/O=Android/OU=Android/CN=Android/emailAddress=android@android.com'
- development/tools/make_key fsverity '/C=US/ST=California/L=Mountain View/O=Android/OU=Android/CN=Android/emailAddress=android@android.com'
signing using the openssl commandline (for boot/system images)
--------------------------------------------------------------
diff --git a/target/product/security/fsverity.pk8 b/target/product/security/fsverity.pk8
deleted file mode 100644
index 5bb69dc..0000000
--- a/target/product/security/fsverity.pk8
+++ /dev/null
Binary files differ
diff --git a/target/product/security/fsverity.x509.pem b/target/product/security/fsverity.x509.pem
deleted file mode 100644
index b29c711..0000000
--- a/target/product/security/fsverity.x509.pem
+++ /dev/null
@@ -1,24 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIECzCCAvOgAwIBAgIUDkPsN3C2kwiPnOnNZiHrK5S6oqowDQYJKoZIhvcNAQEL
-BQAwgZQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH
-DA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYDVQQLDAdBbmRy
-b2lkMRAwDgYDVQQDDAdBbmRyb2lkMSIwIAYJKoZIhvcNAQkBFhNhbmRyb2lkQGFu
-ZHJvaWQuY29tMB4XDTIxMTAxMjA0MzUyMFoXDTQ5MDIyNzA0MzUyMFowgZQxCzAJ
-BgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFp
-biBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYDVQQLDAdBbmRyb2lkMRAwDgYD
-VQQDDAdBbmRyb2lkMSIwIAYJKoZIhvcNAQkBFhNhbmRyb2lkQGFuZHJvaWQuY29t
-MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1N8ro0RTY2Cl91daJvjo
-tDLjHrwrzSAQaVpEXGddPJYs0m8ej3Oh7Hbo4+ju36CIjgH9xDgpOb9LeTUMSXLF
-9Rlkdhz4VJlvaQuYz10FoqkvQo2/IsD2pAq3EktOHexfXCG8fhdCaVkayAuKX5ou
-+RchZWCPwVhBx6fbpZeGhkFg6f7CwPSMEJ5DNtvHUieny8OwIbml0NILQjavP4nU
-GGJxkyKgodUYCdnOSE7FCUv875Op9e0ryTPvUZhKHPoRMe5enEgfq/WXVdqLhifF
-k6gYelcfq1bFRpwBm5KntX1b39V52vYUqXM8gD8Wy5RNo+aF0msJ6aBVcYeQsMlY
-4QIDAQABo1MwUTAdBgNVHQ4EFgQURbNJabjEzJ2CZzqIrX/ppnDM9l4wHwYDVR0j
-BBgwFoAURbNJabjEzJ2CZzqIrX/ppnDM9l4wDwYDVR0TAQH/BAUwAwEB/zANBgkq
-hkiG9w0BAQsFAAOCAQEAl3eEb9xzlwAG31WKorYzflvFLX+LSuVMN3FEcZBcCXsW
-+5QPfyvbJ2AgBzJmuH4XeGH0PebgLQN3PA4p9M0ZgXcHf4KBrSOMfpwUsFiTiD+z
-9KJxr4MTyXyFxO3rVlVCg/za0V8om2cRWsOb2TPRu8qeUSIT4yIj/pOXmz66b4xL
-5fKCuI7khRADCRnwyhPD9/f2/udB6qYx2MvDRchHMLqLvCzHJPS4gjhDTJJSo/st
-/GKqHWspHl5IbpRNlQci1ncc1RLub5gxPwlkIcNlOcziD+eYWeSn5B7v+5uIqxdP
-VY+WltSg4FEEzKFMjzfNpk1Uz+J6h2bi3VS0WZXdXQ==
------END CERTIFICATE-----
diff --git a/target/product/virtual_ab_ota/compression.mk b/target/product/virtual_ab_ota/compression.mk
index 8301047..88c58b8 100644
--- a/target/product/virtual_ab_ota/compression.mk
+++ b/target/product/virtual_ab_ota/compression.mk
@@ -17,6 +17,7 @@
$(call inherit-product, $(SRC_TARGET_DIR)/product/virtual_ab_ota/launch_with_vendor_ramdisk.mk)
PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.compression.enabled=true
+PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.userspace.snapshots.enabled=true
PRODUCT_VIRTUAL_AB_COMPRESSION := true
PRODUCT_PACKAGES += \
snapuserd.vendor_ramdisk \
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 3bb9b55..b82887f 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,25 @@
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(""))
+
+assert_eq(
+ ["build/make/tests/board.rbc", "build/make/tests/board_input_vars.rbc"],
+ rblf.expand_wildcard("build/make/tests/board*.rbc")
+)
+assert_eq(
+ ["build/make/tests/run.rbc", "build/make/tests/product.rbc"],
+ rblf.expand_wildcard("build/make/tests/run.rbc build/make/tests/product.rbc")
+)
+assert_eq(
+ ["build/make/tests/run.rbc"],
+ rblf.expand_wildcard("build/make/tests/run.rbc build/make/tests/nonexistent.rbc")
+)
+
+(globals, config, globals_base) = rblf.product_configuration("test/device", init, input_variables_init)
assert_eq(
{
"PRODUCT_COPY_FILES": [
@@ -92,5 +112,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/Android.bp b/tools/Android.bp
index 269e610..6601c60 100644
--- a/tools/Android.bp
+++ b/tools/Android.bp
@@ -14,41 +14,17 @@
package {
// See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "build_make_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- // SPDX-license-identifier-BSD
- // SPDX-license-identifier-CC-BY
- // SPDX-license-identifier-GPL
- // SPDX-license-identifier-MIT
- default_applicable_licenses: ["build_make_license"],
+ default_applicable_licenses: ["Android-Apache-2.0"],
}
python_binary_host {
name: "generate-self-extracting-archive",
srcs: ["generate-self-extracting-archive.py"],
- version: {
- py2: {
- enabled: true,
- },
- py3: {
- enabled: false,
- },
- },
}
python_binary_host {
name: "post_process_props",
srcs: ["post_process_props.py"],
- version: {
- py2: {
- enabled: false,
- },
- py3: {
- enabled: true,
- },
- },
}
python_test_host {
@@ -58,14 +34,6 @@
"post_process_props.py",
"test_post_process_props.py",
],
- version: {
- py2: {
- enabled: false,
- },
- py3: {
- enabled: true,
- },
- },
test_config: "post_process_props_unittest.xml",
test_suites: ["general-tests"],
}
@@ -73,14 +41,6 @@
python_binary_host {
name: "extract_kernel",
srcs: ["extract_kernel.py"],
- version: {
- py2: {
- enabled: false,
- },
- py3: {
- enabled: true,
- },
- },
}
genrule_defaults {
diff --git a/tools/BUILD.bazel b/tools/BUILD.bazel
index 75b0de6..3170820 100644
--- a/tools/BUILD.bazel
+++ b/tools/BUILD.bazel
@@ -8,7 +8,7 @@
srcs=["java-event-log-tags.py"],
deps=[":event_log_tags"],
visibility = ["//visibility:public"],
- python_version = "PY2",
+ python_version = "PY3",
)
py_binary(
@@ -16,5 +16,5 @@
srcs=["merge-event-log-tags.py"],
deps=[":event_log_tags"],
visibility = ["//visibility:public"],
- python_version = "PY2",
+ python_version = "PY3",
)
diff --git a/tools/acp/Android.bp b/tools/acp/Android.bp
index 78738b0..47b23b2 100644
--- a/tools/acp/Android.bp
+++ b/tools/acp/Android.bp
@@ -4,11 +4,7 @@
package {
// See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "build_make_license"
- // to get the below license kinds:
- // legacy_restricted
- default_applicable_licenses: ["build_make_license"],
+ default_applicable_licenses: ["Android-Apache-2.0"],
}
cc_binary_host {
diff --git a/tools/atree/Android.bp b/tools/atree/Android.bp
index 7906d8b..fdae3e0 100644
--- a/tools/atree/Android.bp
+++ b/tools/atree/Android.bp
@@ -4,11 +4,7 @@
package {
// See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "build_make_license"
- // to get the below license kinds:
- // legacy_restricted
- default_applicable_licenses: ["build_make_license"],
+ default_applicable_licenses: ["Android-Apache-2.0"],
}
cc_binary_host {
diff --git a/tools/compliance/Android.bp b/tools/compliance/Android.bp
new file mode 100644
index 0000000..d5965f8
--- /dev/null
+++ b/tools/compliance/Android.bp
@@ -0,0 +1,140 @@
+//
+// 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: "bom",
+ srcs: ["cmd/bom/bom.go"],
+ deps: ["compliance-module"],
+ testSrcs: ["cmd/bom/bom_test.go"],
+}
+
+blueprint_go_binary {
+ name: "checkshare",
+ srcs: ["cmd/checkshare/checkshare.go"],
+ deps: ["compliance-module"],
+ testSrcs: ["cmd/checkshare/checkshare_test.go"],
+}
+
+blueprint_go_binary {
+ name: "listshare",
+ srcs: ["cmd/listshare/listshare.go"],
+ deps: ["compliance-module"],
+ testSrcs: ["cmd/listshare/listshare_test.go"],
+}
+
+blueprint_go_binary {
+ name: "dumpgraph",
+ srcs: ["cmd/dumpgraph/dumpgraph.go"],
+ deps: ["compliance-module"],
+ testSrcs: ["cmd/dumpgraph/dumpgraph_test.go"],
+}
+
+blueprint_go_binary {
+ name: "dumpresolutions",
+ srcs: ["cmd/dumpresolutions/dumpresolutions.go"],
+ deps: ["compliance-module"],
+ testSrcs: ["cmd/dumpresolutions/dumpresolutions_test.go"],
+}
+
+blueprint_go_binary {
+ name: "htmlnotice",
+ srcs: ["cmd/htmlnotice/htmlnotice.go"],
+ deps: [
+ "compliance-module",
+ "blueprint-deptools",
+ ],
+ testSrcs: ["cmd/htmlnotice/htmlnotice_test.go"],
+}
+
+blueprint_go_binary {
+ name: "rtrace",
+ srcs: ["cmd/rtrace/rtrace.go"],
+ deps: ["compliance-module"],
+ testSrcs: ["cmd/rtrace/rtrace_test.go"],
+}
+
+blueprint_go_binary {
+ name: "shippedlibs",
+ srcs: ["cmd/shippedlibs/shippedlibs.go"],
+ deps: ["compliance-module"],
+ testSrcs: ["cmd/shippedlibs/shippedlibs_test.go"],
+}
+
+blueprint_go_binary {
+ name: "textnotice",
+ srcs: ["cmd/textnotice/textnotice.go"],
+ deps: [
+ "compliance-module",
+ "blueprint-deptools",
+ ],
+ testSrcs: ["cmd/textnotice/textnotice_test.go"],
+}
+
+blueprint_go_binary {
+ name: "xmlnotice",
+ srcs: ["cmd/xmlnotice/xmlnotice.go"],
+ deps: [
+ "compliance-module",
+ "blueprint-deptools",
+ ],
+ testSrcs: ["cmd/xmlnotice/xmlnotice_test.go"],
+}
+
+bootstrap_go_package {
+ name: "compliance-module",
+ srcs: [
+ "condition.go",
+ "conditionset.go",
+ "doc.go",
+ "graph.go",
+ "noticeindex.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: "android/soong/tools/compliance",
+}
diff --git a/tools/compliance/cmd/bom/bom.go b/tools/compliance/cmd/bom/bom.go
new file mode 100644
index 0000000..5363a59
--- /dev/null
+++ b/tools/compliance/cmd/bom/bom.go
@@ -0,0 +1,167 @@
+// 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"
+ "flag"
+ "fmt"
+ "io"
+ "io/fs"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "android/soong/tools/compliance"
+)
+
+var (
+ outputFile = flag.String("o", "-", "Where to write the bill of materials. (default stdout)")
+ stripPrefix = newMultiString("strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)")
+
+ failNoneRequested = fmt.Errorf("\nNo license metadata files requested")
+ failNoLicenses = fmt.Errorf("No licenses found")
+)
+
+type context struct {
+ stdout io.Writer
+ stderr io.Writer
+ rootFS fs.FS
+ stripPrefix []string
+}
+
+func (ctx context) strip(installPath string) string {
+ for _, prefix := range ctx.stripPrefix {
+ if strings.HasPrefix(installPath, prefix) {
+ p := strings.TrimPrefix(installPath, prefix)
+ if 0 == len(p) {
+ continue
+ }
+ return p
+ }
+ }
+ return installPath
+}
+
+func init() {
+ flag.Usage = func() {
+ fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...}
+
+Outputs a bill of materials. i.e. the list of installed paths.
+
+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)
+ }
+
+ if len(*outputFile) == 0 {
+ flag.Usage()
+ fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n")
+ os.Exit(2)
+ } else {
+ dir, err := filepath.Abs(filepath.Dir(*outputFile))
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err)
+ os.Exit(1)
+ }
+ fi, err := os.Stat(dir)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err)
+ os.Exit(1)
+ }
+ if !fi.IsDir() {
+ fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile)
+ os.Exit(1)
+ }
+ }
+
+ var ofile io.Writer
+ ofile = os.Stdout
+ if *outputFile != "-" {
+ ofile = &bytes.Buffer{}
+ }
+
+ ctx := &context{ofile, os.Stderr, os.DirFS("."), *stripPrefix}
+
+ err := billOfMaterials(ctx, flag.Args()...)
+ if err != nil {
+ if err == failNoneRequested {
+ flag.Usage()
+ }
+ fmt.Fprintf(os.Stderr, "%s\n", err.Error())
+ os.Exit(1)
+ }
+ if *outputFile != "-" {
+ err := os.WriteFile(*outputFile, ofile.(*bytes.Buffer).Bytes(), 0666)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "could not write output to %q: %s\n", *outputFile, err)
+ os.Exit(1)
+ }
+ }
+ os.Exit(0)
+}
+
+// billOfMaterials implements the bom utility.
+func billOfMaterials(ctx *context, 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(ctx.rootFS, ctx.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
+ }
+
+ // rs contains all notice resolutions.
+ rs := compliance.ResolveNotices(licenseGraph)
+
+ ni, err := compliance.IndexLicenseTexts(ctx.rootFS, licenseGraph, rs)
+ if err != nil {
+ return fmt.Errorf("Unable to read license text file(s) for %q: %v\n", files, err)
+ }
+
+ for path := range ni.InstallPaths() {
+ fmt.Fprintln(ctx.stdout, ctx.strip(path))
+ }
+ return nil
+}
diff --git a/tools/compliance/cmd/bom/bom_test.go b/tools/compliance/cmd/bom/bom_test.go
new file mode 100644
index 0000000..4a9889f
--- /dev/null
+++ b/tools/compliance/cmd/bom/bom_test.go
@@ -0,0 +1,319 @@
+// 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 (
+ "bufio"
+ "bytes"
+ "fmt"
+ "os"
+ "strings"
+ "testing"
+)
+
+func TestMain(m *testing.M) {
+ // Change into the parent directory before running the tests
+ // so they can find the testdata directory.
+ if err := os.Chdir(".."); err != nil {
+ fmt.Printf("failed to change to testdata directory: %s\n", err)
+ os.Exit(1)
+ }
+ os.Exit(m.Run())
+}
+
+func Test(t *testing.T) {
+ tests := []struct {
+ condition string
+ name string
+ roots []string
+ stripPrefix string
+ expectedOut []string
+ }{
+ {
+ condition: "firstparty",
+ name: "apex",
+ roots: []string{"highest.apex.meta_lic"},
+ stripPrefix: "out/target/product/fictional",
+ expectedOut: []string{
+ "/system/apex/highest.apex",
+ "/system/apex/highest.apex/bin/bin1",
+ "/system/apex/highest.apex/bin/bin2",
+ "/system/apex/highest.apex/lib/liba.so",
+ "/system/apex/highest.apex/lib/libb.so",
+ },
+ },
+ {
+ condition: "firstparty",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ stripPrefix: "out/target/product/fictional/data/",
+ expectedOut: []string{
+ "container.zip",
+ "container.zip/bin1",
+ "container.zip/bin2",
+ "container.zip/liba.so",
+ "container.zip/libb.so",
+ },
+ },
+ {
+ condition: "firstparty",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ stripPrefix: "out/target/product/fictional/bin/",
+ expectedOut: []string{"application"},
+ },
+ {
+ condition: "firstparty",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ stripPrefix: "out/target/product/fictional/system/",
+ expectedOut: []string{"bin/bin1"},
+ },
+ {
+ condition: "firstparty",
+ name: "library",
+ roots: []string{"lib/libd.so.meta_lic"},
+ stripPrefix: "out/target/product/fictional/system/",
+ expectedOut: []string{"lib/libd.so"},
+ },
+ {
+ condition: "notice",
+ name: "apex",
+ roots: []string{"highest.apex.meta_lic"},
+ expectedOut: []string{
+ "out/target/product/fictional/system/apex/highest.apex",
+ "out/target/product/fictional/system/apex/highest.apex/bin/bin1",
+ "out/target/product/fictional/system/apex/highest.apex/bin/bin2",
+ "out/target/product/fictional/system/apex/highest.apex/lib/liba.so",
+ "out/target/product/fictional/system/apex/highest.apex/lib/libb.so",
+ },
+ },
+ {
+ condition: "notice",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []string{
+ "out/target/product/fictional/data/container.zip",
+ "out/target/product/fictional/data/container.zip/bin1",
+ "out/target/product/fictional/data/container.zip/bin2",
+ "out/target/product/fictional/data/container.zip/liba.so",
+ "out/target/product/fictional/data/container.zip/libb.so",
+ },
+ },
+ {
+ condition: "notice",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []string{"out/target/product/fictional/bin/application"},
+ },
+ {
+ condition: "notice",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ expectedOut: []string{"out/target/product/fictional/system/bin/bin1"},
+ },
+ {
+ condition: "notice",
+ name: "library",
+ roots: []string{"lib/libd.so.meta_lic"},
+ expectedOut: []string{"out/target/product/fictional/system/lib/libd.so"},
+ },
+ {
+ condition: "reciprocal",
+ name: "apex",
+ roots: []string{"highest.apex.meta_lic"},
+ stripPrefix: "out/target/product/fictional/system/apex/",
+ expectedOut: []string{
+ "highest.apex",
+ "highest.apex/bin/bin1",
+ "highest.apex/bin/bin2",
+ "highest.apex/lib/liba.so",
+ "highest.apex/lib/libb.so",
+ },
+ },
+ {
+ condition: "reciprocal",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ stripPrefix: "out/target/product/fictional/data/",
+ expectedOut: []string{
+ "container.zip",
+ "container.zip/bin1",
+ "container.zip/bin2",
+ "container.zip/liba.so",
+ "container.zip/libb.so",
+ },
+ },
+ {
+ condition: "reciprocal",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ stripPrefix: "out/target/product/fictional/bin/",
+ expectedOut: []string{"application"},
+ },
+ {
+ condition: "reciprocal",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ stripPrefix: "out/target/product/fictional/system/",
+ expectedOut: []string{"bin/bin1"},
+ },
+ {
+ condition: "reciprocal",
+ name: "library",
+ roots: []string{"lib/libd.so.meta_lic"},
+ stripPrefix: "out/target/product/fictional/system/",
+ expectedOut: []string{"lib/libd.so"},
+ },
+ {
+ condition: "restricted",
+ name: "apex",
+ roots: []string{"highest.apex.meta_lic"},
+ stripPrefix: "out/target/product/fictional/system/apex/",
+ expectedOut: []string{
+ "highest.apex",
+ "highest.apex/bin/bin1",
+ "highest.apex/bin/bin2",
+ "highest.apex/lib/liba.so",
+ "highest.apex/lib/libb.so",
+ },
+ },
+ {
+ condition: "restricted",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ stripPrefix: "out/target/product/fictional/data/",
+ expectedOut: []string{
+ "container.zip",
+ "container.zip/bin1",
+ "container.zip/bin2",
+ "container.zip/liba.so",
+ "container.zip/libb.so",
+ },
+ },
+ {
+ condition: "restricted",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ stripPrefix: "out/target/product/fictional/bin/",
+ expectedOut: []string{"application"},
+ },
+ {
+ condition: "restricted",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ stripPrefix: "out/target/product/fictional/system/",
+ expectedOut: []string{"bin/bin1"},
+ },
+ {
+ condition: "restricted",
+ name: "library",
+ roots: []string{"lib/libd.so.meta_lic"},
+ stripPrefix: "out/target/product/fictional/system/",
+ expectedOut: []string{"lib/libd.so"},
+ },
+ {
+ condition: "proprietary",
+ name: "apex",
+ roots: []string{"highest.apex.meta_lic"},
+ stripPrefix: "out/target/product/fictional/system/apex/",
+ expectedOut: []string{
+ "highest.apex",
+ "highest.apex/bin/bin1",
+ "highest.apex/bin/bin2",
+ "highest.apex/lib/liba.so",
+ "highest.apex/lib/libb.so",
+ },
+ },
+ {
+ condition: "proprietary",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ stripPrefix: "out/target/product/fictional/data/",
+ expectedOut: []string{
+ "container.zip",
+ "container.zip/bin1",
+ "container.zip/bin2",
+ "container.zip/liba.so",
+ "container.zip/libb.so",
+ },
+ },
+ {
+ condition: "proprietary",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ stripPrefix: "out/target/product/fictional/bin/",
+ expectedOut: []string{"application"},
+ },
+ {
+ condition: "proprietary",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ stripPrefix: "out/target/product/fictional/system/",
+ expectedOut: []string{"bin/bin1"},
+ },
+ {
+ condition: "proprietary",
+ name: "library",
+ roots: []string{"lib/libd.so.meta_lic"},
+ stripPrefix: "out/target/product/fictional/system/",
+ expectedOut: []string{"lib/libd.so"},
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.condition+" "+tt.name, func(t *testing.T) {
+ stdout := &bytes.Buffer{}
+ stderr := &bytes.Buffer{}
+
+ rootFiles := make([]string, 0, len(tt.roots))
+ for _, r := range tt.roots {
+ rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
+ }
+
+ ctx := context{stdout, stderr, os.DirFS("."), []string{tt.stripPrefix}}
+
+ err := billOfMaterials(&ctx, rootFiles...)
+ if err != nil {
+ t.Fatalf("bom: error = %v, stderr = %v", err, stderr)
+ return
+ }
+ if stderr.Len() > 0 {
+ t.Errorf("bom: gotStderr = %v, want none", stderr)
+ }
+
+ t.Logf("got stdout: %s", stdout.String())
+
+ t.Logf("want stdout: %s", strings.Join(tt.expectedOut, "\n"))
+
+ out := bufio.NewScanner(stdout)
+ lineno := 0
+ for out.Scan() {
+ line := out.Text()
+ if strings.TrimLeft(line, " ") == "" {
+ continue
+ }
+ if len(tt.expectedOut) <= lineno {
+ t.Errorf("bom: unexpected output at line %d: got %q, want nothing (wanted %d lines)", lineno+1, line, len(tt.expectedOut))
+ } else if tt.expectedOut[lineno] != line {
+ t.Errorf("bom: unexpected output at line %d: got %q, want %q", lineno+1, line, tt.expectedOut[lineno])
+ }
+ lineno++
+ }
+ for ; lineno < len(tt.expectedOut); lineno++ {
+ t.Errorf("bom: missing output line %d: ended early, want %q", lineno+1, tt.expectedOut[lineno])
+ }
+ })
+ }
+}
diff --git a/tools/compliance/cmd/checkshare/checkshare.go b/tools/compliance/cmd/checkshare/checkshare.go
new file mode 100644
index 0000000..752d14b
--- /dev/null
+++ b/tools/compliance/cmd/checkshare/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 (
+ "flag"
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+ "sort"
+
+ "android/soong/tools/compliance"
+)
+
+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 that has a source privacy policy, and the license
+condition 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/checkshare_test.go b/tools/compliance/cmd/checkshare/checkshare_test.go
new file mode 100644
index 0000000..4589595
--- /dev/null
+++ b/tools/compliance/cmd/checkshare/checkshare_test.go
@@ -0,0 +1,297 @@
+// 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"
+ "os"
+ "strings"
+ "testing"
+)
+
+func TestMain(m *testing.M) {
+ // Change into the parent directory before running the tests
+ // so they can find the testdata directory.
+ if err := os.Chdir(".."); err != nil {
+ fmt.Printf("failed to change to testdata directory: %s\n", err)
+ os.Exit(1)
+ }
+ os.Exit(m.Run())
+}
+
+type outcome struct {
+ target string
+ privacyCondition string
+ shareCondition string
+}
+
+func (o *outcome) String() string {
+ return fmt.Sprintf("%s %s and must share from %s", o.target, o.privacyCondition, o.shareCondition)
+}
+
+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",
+ privacyCondition: "proprietary",
+ shareCondition: "restricted",
+ },
+ },
+ },
+ {
+ condition: "proprietary",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedStdout: "FAIL",
+ expectedOutcomes: outcomeList{
+ &outcome{
+ target: "testdata/proprietary/bin/bin2.meta_lic",
+ privacyCondition: "proprietary",
+ shareCondition: "restricted",
+ },
+ },
+ },
+ {
+ condition: "proprietary",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedStdout: "FAIL",
+ expectedOutcomes: outcomeList{
+ &outcome{
+ target: "testdata/proprietary/lib/liba.so.meta_lic",
+ privacyCondition: "proprietary",
+ 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",
+ privacyCondition: "proprietary",
+ 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],
+ privacyCondition: cFields[1],
+ shareCondition: cFields[6],
+ })
+ }
+ 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/dumpgraph.go b/tools/compliance/cmd/dumpgraph/dumpgraph.go
new file mode 100644
index 0000000..fa16b1b
--- /dev/null
+++ b/tools/compliance/cmd/dumpgraph/dumpgraph.go
@@ -0,0 +1,205 @@
+// 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 (
+ "flag"
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+ "sort"
+ "strings"
+
+ "android/soong/tools/compliance"
+)
+
+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 = newMultiString("strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)")
+
+ failNoneRequested = fmt.Errorf("\nNo license metadata files requested")
+ failNoLicenses = fmt.Errorf("No licenses found")
+)
+
+type context struct {
+ graphViz bool
+ labelConditions bool
+ stripPrefix []string
+}
+
+func (ctx context) strip(installPath string) string {
+ for _, prefix := range ctx.stripPrefix {
+ if strings.HasPrefix(installPath, prefix) {
+ p := strings.TrimPrefix(installPath, prefix)
+ if 0 == len(p) {
+ continue
+ }
+ return p
+ }
+ }
+ return installPath
+}
+
+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()
+ }
+}
+
+// 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{*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 := ctx.strip(target.Name())
+ 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/dumpgraph_test.go b/tools/compliance/cmd/dumpgraph/dumpgraph_test.go
new file mode 100644
index 0000000..67b2b40
--- /dev/null
+++ b/tools/compliance/cmd/dumpgraph/dumpgraph_test.go
@@ -0,0 +1,1269 @@
+// 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"
+ "os"
+ "strings"
+ "testing"
+)
+
+func TestMain(m *testing.M) {
+ // Change into the parent directory before running the tests
+ // so they can find the testdata directory.
+ if err := os.Chdir(".."); err != nil {
+ fmt.Printf("failed to change to testdata directory: %s\n", err)
+ os.Exit(1)
+ }
+ os.Exit(m.Run())
+}
+
+func Test_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: []string{"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: []string{"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: []string{"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: []string{"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: []string{"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: []string{"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: []string{"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: []string{"testdata/restricted/"}, labelConditions: true},
+ expectedOut: []string{
+ "bin/bin1.meta_lic:notice lib/liba.so.meta_lic:restricted_allows_dynamic_linking 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_allows_dynamic_linking 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: []string{"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: []string{"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: []string{"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: []string{"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: []string{"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: []string{"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: []string{"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: []string{"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: []string{"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: []string{"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_allows_dynamic_linking"),
+ 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: []string{"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: []string{"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 {", outList[outLine])
+ }
+ 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/dumpresolutions.go b/tools/compliance/cmd/dumpresolutions/dumpresolutions.go
new file mode 100644
index 0000000..9c5e972
--- /dev/null
+++ b/tools/compliance/cmd/dumpresolutions/dumpresolutions.go
@@ -0,0 +1,251 @@
+// 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 (
+ "flag"
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+ "sort"
+ "strings"
+
+ "android/soong/tools/compliance"
+)
+
+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 = newMultiString("strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)")
+
+ failNoneRequested = fmt.Errorf("\nNo license metadata files requested")
+ failNoLicenses = fmt.Errorf("No licenses found")
+)
+
+type context struct {
+ conditions []compliance.LicenseCondition
+ graphViz bool
+ labelConditions bool
+ stripPrefix []string
+}
+
+func (ctx context) strip(installPath string) string {
+ for _, prefix := range ctx.stripPrefix {
+ if strings.HasPrefix(installPath, prefix) {
+ p := strings.TrimPrefix(installPath, prefix)
+ if 0 == len(p) {
+ continue
+ }
+ return p
+ }
+ }
+ return installPath
+}
+
+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
+resolution for the union of the conditions. Otherwise, outputs the
+resolution for all conditions.
+
+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)
+ }
+
+ lcs := make([]compliance.LicenseCondition, 0, len(*conditions))
+ for _, name := range *conditions {
+ lcs = append(lcs, compliance.RecognizedConditionNames[name])
+ }
+ ctx := &context{
+ conditions: lcs,
+ 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) (*compliance.LicenseGraph, error) {
+ if len(files) < 1 {
+ return nil, failNoneRequested
+ }
+
+ // Read the license graph from the license metadata files (*.meta_lic).
+ licenseGraph, err := compliance.ReadLicenseGraph(os.DirFS("."), stderr, files)
+ if err != nil {
+ return nil, fmt.Errorf("Unable to read license metadata file(s) %q: %v\n", files, err)
+ }
+ if licenseGraph == nil {
+ return nil, failNoLicenses
+ }
+
+ compliance.ResolveTopDownConditions(licenseGraph)
+ cs := compliance.AllLicenseConditions
+ if len(ctx.conditions) > 0 {
+ cs = compliance.NewLicenseConditionSet()
+ for _, c := range ctx.conditions {
+ cs = cs.Plus(c)
+ }
+ }
+
+ resolutions := compliance.WalkResolutionsForCondition(licenseGraph, cs)
+
+ // 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 := ctx.strip(target.Name())
+ if ctx.labelConditions {
+ conditions := target.LicenseConditions().Names()
+ 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.
+ // `cnames` is the list of conditions to resolve.
+ outputResolution := func(tname, aname string, cnames []string) {
+ if ctx.graphViz {
+ // ... one edge per line labelled with \\n-separated annotations.
+ tNode := nodes[tname]
+ aNode := nodes[aname]
+ fmt.Fprintf(stdout, "\t%s -> %s [label=\"%s\"];\n", tNode, aNode, strings.Join(cnames, "\\n"))
+ } else {
+ // ... one edge per line with names in a colon-separated tuple.
+ fmt.Fprintf(stdout, "%s %s %s\n", tname, aname, strings.Join(cnames, ":"))
+ }
+ }
+
+ // 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 := resolutions.Resolutions(target)
+ sort.Sort(rl)
+ for _, r := range rl {
+ makeNode(r.ActsOn())
+ }
+ }
+ }
+
+ // Output the sorted targets.
+ for _, target := range targets {
+ var tname string
+ if ctx.graphViz {
+ tname = target.Name()
+ } else {
+ tname = targetOut(target, ":")
+ }
+
+ rl := 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(), ":")
+ }
+
+ // cnames accumulates the list of condition names originating at a single origin that apply to `target`.
+ cnames := r.Resolves().Names()
+
+ // Output 1 line for each attachesTo+actsOn combination.
+ outputResolution(tname, aname, 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 licenseGraph, nil
+}
diff --git a/tools/compliance/cmd/dumpresolutions/dumpresolutions_test.go b/tools/compliance/cmd/dumpresolutions/dumpresolutions_test.go
new file mode 100644
index 0000000..6fe1e8a
--- /dev/null
+++ b/tools/compliance/cmd/dumpresolutions/dumpresolutions_test.go
@@ -0,0 +1,3358 @@
+// 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"
+ "os"
+ "strings"
+ "testing"
+
+ "android/soong/tools/compliance"
+)
+
+func TestMain(m *testing.M) {
+ // Change into the parent directory before running the tests
+ // so they can find the testdata directory.
+ if err := os.Chdir(".."); err != nil {
+ fmt.Printf("failed to change to testdata directory: %s\n", err)
+ os.Exit(1)
+ }
+ os.Exit(m.Run())
+}
+
+func Test_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 notice",
+ "testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/lib/liba.so.meta_lic notice",
+ "testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/lib/libc.a.meta_lic notice",
+ "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 notice",
+ "testdata/firstparty/highest.apex.meta_lic testdata/firstparty/bin/bin2.meta_lic notice",
+ "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 notice",
+ "testdata/firstparty/highest.apex.meta_lic testdata/firstparty/lib/libb.so.meta_lic notice",
+ "testdata/firstparty/highest.apex.meta_lic testdata/firstparty/lib/libc.a.meta_lic notice",
+ "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 notice",
+ },
+ },
+ {
+ condition: "firstparty",
+ name: "apex_trimmed",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{stripPrefix: []string{"testdata/firstparty/"}},
+ expectedOut: []string{
+ "bin/bin1.meta_lic bin/bin1.meta_lic notice",
+ "bin/bin1.meta_lic lib/liba.so.meta_lic notice",
+ "bin/bin1.meta_lic lib/libc.a.meta_lic notice",
+ "bin/bin2.meta_lic bin/bin2.meta_lic notice",
+ "highest.apex.meta_lic bin/bin1.meta_lic notice",
+ "highest.apex.meta_lic bin/bin2.meta_lic notice",
+ "highest.apex.meta_lic highest.apex.meta_lic notice",
+ "highest.apex.meta_lic lib/liba.so.meta_lic notice",
+ "highest.apex.meta_lic lib/libb.so.meta_lic notice",
+ "highest.apex.meta_lic lib/libc.a.meta_lic notice",
+ "lib/liba.so.meta_lic lib/liba.so.meta_lic notice",
+ "lib/libb.so.meta_lic lib/libb.so.meta_lic notice",
+ },
+ },
+ {
+ condition: "firstparty",
+ name: "apex_trimmed_notice",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{
+ conditions: []compliance.LicenseCondition{compliance.NoticeCondition},
+ stripPrefix: []string{"testdata/firstparty/"},
+ },
+ expectedOut: []string{
+ "bin/bin1.meta_lic bin/bin1.meta_lic notice",
+ "bin/bin1.meta_lic lib/liba.so.meta_lic notice",
+ "bin/bin1.meta_lic lib/libc.a.meta_lic notice",
+ "bin/bin2.meta_lic bin/bin2.meta_lic notice",
+ "highest.apex.meta_lic bin/bin1.meta_lic notice",
+ "highest.apex.meta_lic bin/bin2.meta_lic notice",
+ "highest.apex.meta_lic highest.apex.meta_lic notice",
+ "highest.apex.meta_lic lib/liba.so.meta_lic notice",
+ "highest.apex.meta_lic lib/libb.so.meta_lic notice",
+ "highest.apex.meta_lic lib/libc.a.meta_lic notice",
+ "lib/liba.so.meta_lic lib/liba.so.meta_lic notice",
+ "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: compliance.ImpliesShared.AsList(),
+ stripPrefix: []string{"testdata/firstparty/"},
+ },
+ expectedOut: []string{},
+ },
+ {
+ condition: "firstparty",
+ name: "apex_trimmed_private",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{
+ conditions: compliance.ImpliesPrivate.AsList(),
+ stripPrefix: []string{"testdata/firstparty/"},
+ },
+ expectedOut: []string{},
+ },
+ {
+ condition: "firstparty",
+ name: "apex_trimmed_share_private",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{
+ conditions: append(compliance.ImpliesPrivate.AsList(), compliance.ImpliesShared.AsList()...),
+ stripPrefix: []string{"testdata/firstparty/"},
+ },
+ expectedOut: []string{},
+ },
+ {
+ condition: "firstparty",
+ name: "apex_trimmed_labelled",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{stripPrefix: []string{"testdata/firstparty/"}, labelConditions: true},
+ expectedOut: []string{
+ "bin/bin1.meta_lic:notice bin/bin1.meta_lic:notice notice",
+ "bin/bin1.meta_lic:notice lib/liba.so.meta_lic:notice notice",
+ "bin/bin1.meta_lic:notice lib/libc.a.meta_lic:notice notice",
+ "bin/bin2.meta_lic:notice bin/bin2.meta_lic:notice notice",
+ "highest.apex.meta_lic:notice bin/bin1.meta_lic:notice notice",
+ "highest.apex.meta_lic:notice bin/bin2.meta_lic:notice notice",
+ "highest.apex.meta_lic:notice highest.apex.meta_lic:notice notice",
+ "highest.apex.meta_lic:notice lib/liba.so.meta_lic:notice notice",
+ "highest.apex.meta_lic:notice lib/libb.so.meta_lic:notice notice",
+ "highest.apex.meta_lic:notice lib/libc.a.meta_lic:notice 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 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 notice",
+ "testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/lib/liba.so.meta_lic notice",
+ "testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/lib/libc.a.meta_lic notice",
+ "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 notice",
+ "testdata/firstparty/container.zip.meta_lic testdata/firstparty/bin/bin2.meta_lic notice",
+ "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 notice",
+ "testdata/firstparty/container.zip.meta_lic testdata/firstparty/lib/libb.so.meta_lic notice",
+ "testdata/firstparty/container.zip.meta_lic testdata/firstparty/lib/libc.a.meta_lic notice",
+ "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 notice",
+ },
+ },
+ {
+ condition: "firstparty",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []string{
+ "testdata/firstparty/application.meta_lic testdata/firstparty/application.meta_lic notice",
+ "testdata/firstparty/application.meta_lic testdata/firstparty/lib/liba.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 notice",
+ "testdata/firstparty/bin/bin1.meta_lic testdata/firstparty/lib/liba.so.meta_lic notice",
+ "testdata/firstparty/bin/bin1.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 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 notice",
+ "testdata/notice/bin/bin1.meta_lic testdata/notice/lib/liba.so.meta_lic notice",
+ "testdata/notice/bin/bin1.meta_lic testdata/notice/lib/libc.a.meta_lic notice",
+ "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 notice",
+ "testdata/notice/highest.apex.meta_lic testdata/notice/bin/bin2.meta_lic notice",
+ "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 notice",
+ "testdata/notice/highest.apex.meta_lic testdata/notice/lib/libb.so.meta_lic notice",
+ "testdata/notice/highest.apex.meta_lic testdata/notice/lib/libc.a.meta_lic notice",
+ "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 notice",
+ },
+ },
+ {
+ condition: "notice",
+ name: "apex_trimmed",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{stripPrefix: []string{"testdata/notice/"}},
+ expectedOut: []string{
+ "bin/bin1.meta_lic bin/bin1.meta_lic notice",
+ "bin/bin1.meta_lic lib/liba.so.meta_lic notice",
+ "bin/bin1.meta_lic lib/libc.a.meta_lic notice",
+ "bin/bin2.meta_lic bin/bin2.meta_lic notice",
+ "highest.apex.meta_lic bin/bin1.meta_lic notice",
+ "highest.apex.meta_lic bin/bin2.meta_lic notice",
+ "highest.apex.meta_lic highest.apex.meta_lic notice",
+ "highest.apex.meta_lic lib/liba.so.meta_lic notice",
+ "highest.apex.meta_lic lib/libb.so.meta_lic notice",
+ "highest.apex.meta_lic lib/libc.a.meta_lic notice",
+ "lib/liba.so.meta_lic lib/liba.so.meta_lic notice",
+ "lib/libb.so.meta_lic lib/libb.so.meta_lic notice",
+ },
+ },
+ {
+ condition: "notice",
+ name: "apex_trimmed_notice",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{
+ conditions: []compliance.LicenseCondition{compliance.NoticeCondition},
+ stripPrefix: []string{"testdata/notice/"},
+ },
+ expectedOut: []string{
+ "bin/bin1.meta_lic bin/bin1.meta_lic notice",
+ "bin/bin1.meta_lic lib/liba.so.meta_lic notice",
+ "bin/bin1.meta_lic lib/libc.a.meta_lic notice",
+ "bin/bin2.meta_lic bin/bin2.meta_lic notice",
+ "highest.apex.meta_lic bin/bin1.meta_lic notice",
+ "highest.apex.meta_lic bin/bin2.meta_lic notice",
+ "highest.apex.meta_lic highest.apex.meta_lic notice",
+ "highest.apex.meta_lic lib/liba.so.meta_lic notice",
+ "highest.apex.meta_lic lib/libb.so.meta_lic notice",
+ "highest.apex.meta_lic lib/libc.a.meta_lic notice",
+ "lib/liba.so.meta_lic lib/liba.so.meta_lic notice",
+ "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: compliance.ImpliesShared.AsList(),
+ stripPrefix: []string{"testdata/notice/"},
+ },
+ expectedOut: []string{},
+ },
+ {
+ condition: "notice",
+ name: "apex_trimmed_private",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{
+ conditions: compliance.ImpliesPrivate.AsList(),
+ stripPrefix: []string{"testdata/notice/"},
+ },
+ expectedOut: []string{},
+ },
+ {
+ condition: "notice",
+ name: "apex_trimmed_share_private",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{
+ conditions: append(compliance.ImpliesShared.AsList(), compliance.ImpliesPrivate.AsList()...),
+ stripPrefix: []string{"testdata/notice/"},
+ },
+ expectedOut: []string{},
+ },
+ {
+ condition: "notice",
+ name: "apex_trimmed_labelled",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{stripPrefix: []string{"testdata/notice/"}, labelConditions: true},
+ expectedOut: []string{
+ "bin/bin1.meta_lic:notice bin/bin1.meta_lic:notice notice",
+ "bin/bin1.meta_lic:notice lib/liba.so.meta_lic:notice notice",
+ "bin/bin1.meta_lic:notice lib/libc.a.meta_lic:notice notice",
+ "bin/bin2.meta_lic:notice bin/bin2.meta_lic:notice notice",
+ "highest.apex.meta_lic:notice bin/bin1.meta_lic:notice notice",
+ "highest.apex.meta_lic:notice bin/bin2.meta_lic:notice notice",
+ "highest.apex.meta_lic:notice highest.apex.meta_lic:notice notice",
+ "highest.apex.meta_lic:notice lib/liba.so.meta_lic:notice notice",
+ "highest.apex.meta_lic:notice lib/libb.so.meta_lic:notice notice",
+ "highest.apex.meta_lic:notice lib/libc.a.meta_lic:notice 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 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 notice",
+ "testdata/notice/bin/bin1.meta_lic testdata/notice/lib/liba.so.meta_lic notice",
+ "testdata/notice/bin/bin1.meta_lic testdata/notice/lib/libc.a.meta_lic notice",
+ "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 notice",
+ "testdata/notice/container.zip.meta_lic testdata/notice/bin/bin2.meta_lic notice",
+ "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 notice",
+ "testdata/notice/container.zip.meta_lic testdata/notice/lib/libb.so.meta_lic notice",
+ "testdata/notice/container.zip.meta_lic testdata/notice/lib/libc.a.meta_lic notice",
+ "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 notice",
+ },
+ },
+ {
+ condition: "notice",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []string{
+ "testdata/notice/application.meta_lic testdata/notice/application.meta_lic notice",
+ "testdata/notice/application.meta_lic testdata/notice/lib/liba.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 notice",
+ "testdata/notice/bin/bin1.meta_lic testdata/notice/lib/liba.so.meta_lic notice",
+ "testdata/notice/bin/bin1.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 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 notice",
+ "testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/lib/liba.so.meta_lic reciprocal",
+ "testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/lib/libc.a.meta_lic reciprocal",
+ "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 notice",
+ "testdata/reciprocal/highest.apex.meta_lic testdata/reciprocal/bin/bin2.meta_lic notice",
+ "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 reciprocal",
+ "testdata/reciprocal/highest.apex.meta_lic testdata/reciprocal/lib/libb.so.meta_lic notice",
+ "testdata/reciprocal/highest.apex.meta_lic testdata/reciprocal/lib/libc.a.meta_lic reciprocal",
+ "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 notice",
+ },
+ },
+ {
+ condition: "reciprocal",
+ name: "apex_trimmed",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{stripPrefix: []string{"testdata/reciprocal/"}},
+ expectedOut: []string{
+ "bin/bin1.meta_lic bin/bin1.meta_lic notice",
+ "bin/bin1.meta_lic lib/liba.so.meta_lic reciprocal",
+ "bin/bin1.meta_lic lib/libc.a.meta_lic reciprocal",
+ "bin/bin2.meta_lic bin/bin2.meta_lic notice",
+ "highest.apex.meta_lic bin/bin1.meta_lic notice",
+ "highest.apex.meta_lic bin/bin2.meta_lic notice",
+ "highest.apex.meta_lic highest.apex.meta_lic notice",
+ "highest.apex.meta_lic lib/liba.so.meta_lic reciprocal",
+ "highest.apex.meta_lic lib/libb.so.meta_lic notice",
+ "highest.apex.meta_lic lib/libc.a.meta_lic reciprocal",
+ "lib/liba.so.meta_lic lib/liba.so.meta_lic reciprocal",
+ "lib/libb.so.meta_lic lib/libb.so.meta_lic notice",
+ },
+ },
+ {
+ condition: "reciprocal",
+ name: "apex_trimmed_notice",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{
+ conditions: []compliance.LicenseCondition{compliance.NoticeCondition},
+ stripPrefix: []string{"testdata/reciprocal/"},
+ },
+ expectedOut: []string{
+ "bin/bin1.meta_lic bin/bin1.meta_lic notice",
+ "bin/bin2.meta_lic bin/bin2.meta_lic notice",
+ "highest.apex.meta_lic bin/bin1.meta_lic notice",
+ "highest.apex.meta_lic bin/bin2.meta_lic notice",
+ "highest.apex.meta_lic highest.apex.meta_lic notice",
+ "highest.apex.meta_lic lib/libb.so.meta_lic notice",
+ "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: compliance.ImpliesShared.AsList(),
+ stripPrefix: []string{"testdata/reciprocal/"},
+ },
+ expectedOut: []string{
+ "bin/bin1.meta_lic lib/liba.so.meta_lic reciprocal",
+ "bin/bin1.meta_lic lib/libc.a.meta_lic reciprocal",
+ "highest.apex.meta_lic lib/liba.so.meta_lic reciprocal",
+ "highest.apex.meta_lic lib/libc.a.meta_lic reciprocal",
+ "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: compliance.ImpliesPrivate.AsList(),
+ stripPrefix: []string{"testdata/reciprocal/"},
+ },
+ expectedOut: []string{},
+ },
+ {
+ condition: "reciprocal",
+ name: "apex_trimmed_share_private",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{
+ conditions: append(compliance.ImpliesShared.AsList(), compliance.ImpliesPrivate.AsList()...),
+ stripPrefix: []string{"testdata/reciprocal/"},
+ },
+ expectedOut: []string{
+ "bin/bin1.meta_lic lib/liba.so.meta_lic reciprocal",
+ "bin/bin1.meta_lic lib/libc.a.meta_lic reciprocal",
+ "highest.apex.meta_lic lib/liba.so.meta_lic reciprocal",
+ "highest.apex.meta_lic lib/libc.a.meta_lic reciprocal",
+ "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: []string{"testdata/reciprocal/"}, labelConditions: true},
+ expectedOut: []string{
+ "bin/bin1.meta_lic:notice bin/bin1.meta_lic:notice notice",
+ "bin/bin1.meta_lic:notice lib/liba.so.meta_lic:reciprocal reciprocal",
+ "bin/bin1.meta_lic:notice lib/libc.a.meta_lic:reciprocal reciprocal",
+ "bin/bin2.meta_lic:notice bin/bin2.meta_lic:notice notice",
+ "highest.apex.meta_lic:notice bin/bin1.meta_lic:notice notice",
+ "highest.apex.meta_lic:notice bin/bin2.meta_lic:notice notice",
+ "highest.apex.meta_lic:notice highest.apex.meta_lic:notice notice",
+ "highest.apex.meta_lic:notice lib/liba.so.meta_lic:reciprocal reciprocal",
+ "highest.apex.meta_lic:notice lib/libb.so.meta_lic:notice notice",
+ "highest.apex.meta_lic:notice lib/libc.a.meta_lic:reciprocal 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 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 notice",
+ "testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/lib/liba.so.meta_lic reciprocal",
+ "testdata/reciprocal/bin/bin1.meta_lic testdata/reciprocal/lib/libc.a.meta_lic reciprocal",
+ "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 notice",
+ "testdata/reciprocal/container.zip.meta_lic testdata/reciprocal/bin/bin2.meta_lic notice",
+ "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 reciprocal",
+ "testdata/reciprocal/container.zip.meta_lic testdata/reciprocal/lib/libb.so.meta_lic notice",
+ "testdata/reciprocal/container.zip.meta_lic testdata/reciprocal/lib/libc.a.meta_lic reciprocal",
+ "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 notice",
+ },
+ },
+ {
+ condition: "reciprocal",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []string{
+ "testdata/reciprocal/application.meta_lic testdata/reciprocal/application.meta_lic notice",
+ "testdata/reciprocal/application.meta_lic testdata/reciprocal/lib/liba.so.meta_lic reciprocal",
+ },
+ },
+ {
+ condition: "reciprocal",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ expectedOut: []string{
+ "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 reciprocal",
+ "testdata/reciprocal/bin/bin1.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 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 notice:restricted_allows_dynamic_linking",
+ "testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted_allows_dynamic_linking",
+ "testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/libc.a.meta_lic reciprocal:restricted_allows_dynamic_linking",
+ "testdata/restricted/bin/bin2.meta_lic testdata/restricted/bin/bin2.meta_lic notice:restricted",
+ "testdata/restricted/bin/bin2.meta_lic testdata/restricted/lib/libb.so.meta_lic restricted",
+ "testdata/restricted/highest.apex.meta_lic testdata/restricted/bin/bin1.meta_lic notice:restricted_allows_dynamic_linking",
+ "testdata/restricted/highest.apex.meta_lic testdata/restricted/bin/bin2.meta_lic notice:restricted",
+ "testdata/restricted/highest.apex.meta_lic testdata/restricted/highest.apex.meta_lic notice:restricted:restricted_allows_dynamic_linking",
+ "testdata/restricted/highest.apex.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted_allows_dynamic_linking",
+ "testdata/restricted/highest.apex.meta_lic testdata/restricted/lib/libb.so.meta_lic restricted",
+ "testdata/restricted/highest.apex.meta_lic testdata/restricted/lib/libc.a.meta_lic reciprocal:restricted_allows_dynamic_linking",
+ "testdata/restricted/lib/liba.so.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted_allows_dynamic_linking",
+ "testdata/restricted/lib/libb.so.meta_lic testdata/restricted/lib/libb.so.meta_lic restricted",
+ },
+ },
+ {
+ condition: "restricted",
+ name: "apex_trimmed",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{stripPrefix: []string{"testdata/restricted/"}},
+ expectedOut: []string{
+ "bin/bin1.meta_lic bin/bin1.meta_lic notice:restricted_allows_dynamic_linking",
+ "bin/bin1.meta_lic lib/liba.so.meta_lic restricted_allows_dynamic_linking",
+ "bin/bin1.meta_lic lib/libc.a.meta_lic reciprocal:restricted_allows_dynamic_linking",
+ "bin/bin2.meta_lic bin/bin2.meta_lic notice:restricted",
+ "bin/bin2.meta_lic lib/libb.so.meta_lic restricted",
+ "highest.apex.meta_lic bin/bin1.meta_lic notice:restricted_allows_dynamic_linking",
+ "highest.apex.meta_lic bin/bin2.meta_lic notice:restricted",
+ "highest.apex.meta_lic highest.apex.meta_lic notice:restricted:restricted_allows_dynamic_linking",
+ "highest.apex.meta_lic lib/liba.so.meta_lic restricted_allows_dynamic_linking",
+ "highest.apex.meta_lic lib/libb.so.meta_lic restricted",
+ "highest.apex.meta_lic lib/libc.a.meta_lic reciprocal:restricted_allows_dynamic_linking",
+ "lib/liba.so.meta_lic lib/liba.so.meta_lic restricted_allows_dynamic_linking",
+ "lib/libb.so.meta_lic lib/libb.so.meta_lic restricted",
+ },
+ },
+ {
+ condition: "restricted",
+ name: "apex_trimmed_notice",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{
+ conditions: []compliance.LicenseCondition{compliance.NoticeCondition},
+ stripPrefix: []string{"testdata/restricted/"},
+ },
+ expectedOut: []string{
+ "bin/bin1.meta_lic bin/bin1.meta_lic notice",
+ "bin/bin2.meta_lic bin/bin2.meta_lic notice",
+ "highest.apex.meta_lic bin/bin1.meta_lic notice",
+ "highest.apex.meta_lic bin/bin2.meta_lic notice",
+ "highest.apex.meta_lic highest.apex.meta_lic notice",
+ },
+ },
+ {
+ condition: "restricted",
+ name: "apex_trimmed_share",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{
+ conditions: compliance.ImpliesShared.AsList(),
+ stripPrefix: []string{"testdata/restricted/"},
+ },
+ expectedOut: []string{
+ "bin/bin1.meta_lic bin/bin1.meta_lic restricted_allows_dynamic_linking",
+ "bin/bin1.meta_lic lib/liba.so.meta_lic restricted_allows_dynamic_linking",
+ "bin/bin1.meta_lic lib/libc.a.meta_lic reciprocal:restricted_allows_dynamic_linking",
+ "bin/bin2.meta_lic bin/bin2.meta_lic restricted",
+ "bin/bin2.meta_lic lib/libb.so.meta_lic restricted",
+ "highest.apex.meta_lic bin/bin1.meta_lic restricted_allows_dynamic_linking",
+ "highest.apex.meta_lic bin/bin2.meta_lic restricted",
+ "highest.apex.meta_lic highest.apex.meta_lic restricted:restricted_allows_dynamic_linking",
+ "highest.apex.meta_lic lib/liba.so.meta_lic restricted_allows_dynamic_linking",
+ "highest.apex.meta_lic lib/libb.so.meta_lic restricted",
+ "highest.apex.meta_lic lib/libc.a.meta_lic reciprocal:restricted_allows_dynamic_linking",
+ "lib/liba.so.meta_lic lib/liba.so.meta_lic restricted_allows_dynamic_linking",
+ "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: compliance.ImpliesPrivate.AsList(),
+ stripPrefix: []string{"testdata/restricted/"},
+ },
+ expectedOut: []string{},
+ },
+ {
+ condition: "restricted",
+ name: "apex_trimmed_share_private",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{
+ conditions: append(compliance.ImpliesShared.AsList(), compliance.ImpliesPrivate.AsList()...),
+ stripPrefix: []string{"testdata/restricted/"},
+ },
+ expectedOut: []string{
+ "bin/bin1.meta_lic bin/bin1.meta_lic restricted_allows_dynamic_linking",
+ "bin/bin1.meta_lic lib/liba.so.meta_lic restricted_allows_dynamic_linking",
+ "bin/bin1.meta_lic lib/libc.a.meta_lic reciprocal:restricted_allows_dynamic_linking",
+ "bin/bin2.meta_lic bin/bin2.meta_lic restricted",
+ "bin/bin2.meta_lic lib/libb.so.meta_lic restricted",
+ "highest.apex.meta_lic bin/bin1.meta_lic restricted_allows_dynamic_linking",
+ "highest.apex.meta_lic bin/bin2.meta_lic restricted",
+ "highest.apex.meta_lic highest.apex.meta_lic restricted:restricted_allows_dynamic_linking",
+ "highest.apex.meta_lic lib/liba.so.meta_lic restricted_allows_dynamic_linking",
+ "highest.apex.meta_lic lib/libb.so.meta_lic restricted",
+ "highest.apex.meta_lic lib/libc.a.meta_lic reciprocal:restricted_allows_dynamic_linking",
+ "lib/liba.so.meta_lic lib/liba.so.meta_lic restricted_allows_dynamic_linking",
+ "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: []string{"testdata/restricted/"}, labelConditions: true},
+ expectedOut: []string{
+ "bin/bin1.meta_lic:notice bin/bin1.meta_lic:notice notice:restricted_allows_dynamic_linking",
+ "bin/bin1.meta_lic:notice lib/liba.so.meta_lic:restricted_allows_dynamic_linking restricted_allows_dynamic_linking",
+ "bin/bin1.meta_lic:notice lib/libc.a.meta_lic:reciprocal reciprocal:restricted_allows_dynamic_linking",
+ "bin/bin2.meta_lic:notice bin/bin2.meta_lic:notice notice:restricted",
+ "bin/bin2.meta_lic:notice lib/libb.so.meta_lic:restricted restricted",
+ "highest.apex.meta_lic:notice bin/bin1.meta_lic:notice notice:restricted_allows_dynamic_linking",
+ "highest.apex.meta_lic:notice bin/bin2.meta_lic:notice notice:restricted",
+ "highest.apex.meta_lic:notice highest.apex.meta_lic:notice notice:restricted:restricted_allows_dynamic_linking",
+ "highest.apex.meta_lic:notice lib/liba.so.meta_lic:restricted_allows_dynamic_linking restricted_allows_dynamic_linking",
+ "highest.apex.meta_lic:notice lib/libb.so.meta_lic:restricted restricted",
+ "highest.apex.meta_lic:notice lib/libc.a.meta_lic:reciprocal reciprocal:restricted_allows_dynamic_linking",
+ "lib/liba.so.meta_lic:restricted_allows_dynamic_linking lib/liba.so.meta_lic:restricted_allows_dynamic_linking restricted_allows_dynamic_linking",
+ "lib/libb.so.meta_lic:restricted lib/libb.so.meta_lic:restricted restricted",
+ },
+ },
+ {
+ condition: "restricted",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []string{
+ "testdata/restricted/bin/bin1.meta_lic testdata/restricted/bin/bin1.meta_lic notice:restricted_allows_dynamic_linking",
+ "testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted_allows_dynamic_linking",
+ "testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/libc.a.meta_lic reciprocal:restricted_allows_dynamic_linking",
+ "testdata/restricted/bin/bin2.meta_lic testdata/restricted/bin/bin2.meta_lic notice:restricted",
+ "testdata/restricted/bin/bin2.meta_lic testdata/restricted/lib/libb.so.meta_lic restricted",
+ "testdata/restricted/container.zip.meta_lic testdata/restricted/bin/bin1.meta_lic notice:restricted_allows_dynamic_linking",
+ "testdata/restricted/container.zip.meta_lic testdata/restricted/bin/bin2.meta_lic notice:restricted",
+ "testdata/restricted/container.zip.meta_lic testdata/restricted/container.zip.meta_lic notice:restricted:restricted_allows_dynamic_linking",
+ "testdata/restricted/container.zip.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted_allows_dynamic_linking",
+ "testdata/restricted/container.zip.meta_lic testdata/restricted/lib/libb.so.meta_lic restricted",
+ "testdata/restricted/container.zip.meta_lic testdata/restricted/lib/libc.a.meta_lic reciprocal:restricted_allows_dynamic_linking",
+ "testdata/restricted/lib/liba.so.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted_allows_dynamic_linking",
+ "testdata/restricted/lib/libb.so.meta_lic testdata/restricted/lib/libb.so.meta_lic restricted",
+ },
+ },
+ {
+ condition: "restricted",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []string{
+ "testdata/restricted/application.meta_lic testdata/restricted/application.meta_lic notice:restricted:restricted_allows_dynamic_linking",
+ "testdata/restricted/application.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted:restricted_allows_dynamic_linking",
+ },
+ },
+ {
+ condition: "restricted",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ expectedOut: []string{
+ "testdata/restricted/bin/bin1.meta_lic testdata/restricted/bin/bin1.meta_lic notice:restricted_allows_dynamic_linking",
+ "testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/liba.so.meta_lic restricted_allows_dynamic_linking",
+ "testdata/restricted/bin/bin1.meta_lic testdata/restricted/lib/libc.a.meta_lic reciprocal:restricted_allows_dynamic_linking",
+ },
+ },
+ {
+ 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 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 notice",
+ "testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/lib/liba.so.meta_lic proprietary:by_exception_only",
+ "testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/lib/libc.a.meta_lic proprietary:by_exception_only",
+ "testdata/proprietary/bin/bin2.meta_lic testdata/proprietary/bin/bin2.meta_lic restricted:proprietary:by_exception_only",
+ "testdata/proprietary/bin/bin2.meta_lic testdata/proprietary/lib/libb.so.meta_lic restricted",
+ "testdata/proprietary/highest.apex.meta_lic testdata/proprietary/bin/bin1.meta_lic notice",
+ "testdata/proprietary/highest.apex.meta_lic testdata/proprietary/bin/bin2.meta_lic restricted:proprietary:by_exception_only",
+ "testdata/proprietary/highest.apex.meta_lic testdata/proprietary/highest.apex.meta_lic notice:restricted",
+ "testdata/proprietary/highest.apex.meta_lic testdata/proprietary/lib/liba.so.meta_lic proprietary:by_exception_only",
+ "testdata/proprietary/highest.apex.meta_lic testdata/proprietary/lib/libb.so.meta_lic restricted",
+ "testdata/proprietary/highest.apex.meta_lic testdata/proprietary/lib/libc.a.meta_lic proprietary:by_exception_only",
+ "testdata/proprietary/lib/liba.so.meta_lic testdata/proprietary/lib/liba.so.meta_lic proprietary:by_exception_only",
+ "testdata/proprietary/lib/libb.so.meta_lic testdata/proprietary/lib/libb.so.meta_lic restricted",
+ },
+ },
+ {
+ condition: "proprietary",
+ name: "apex_trimmed",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{stripPrefix: []string{"testdata/proprietary/"}},
+ expectedOut: []string{
+ "bin/bin1.meta_lic bin/bin1.meta_lic notice",
+ "bin/bin1.meta_lic lib/liba.so.meta_lic proprietary:by_exception_only",
+ "bin/bin1.meta_lic lib/libc.a.meta_lic proprietary:by_exception_only",
+ "bin/bin2.meta_lic bin/bin2.meta_lic restricted:proprietary:by_exception_only",
+ "bin/bin2.meta_lic lib/libb.so.meta_lic restricted",
+ "highest.apex.meta_lic bin/bin1.meta_lic notice",
+ "highest.apex.meta_lic bin/bin2.meta_lic restricted:proprietary:by_exception_only",
+ "highest.apex.meta_lic highest.apex.meta_lic notice:restricted",
+ "highest.apex.meta_lic lib/liba.so.meta_lic proprietary:by_exception_only",
+ "highest.apex.meta_lic lib/libb.so.meta_lic restricted",
+ "highest.apex.meta_lic lib/libc.a.meta_lic proprietary:by_exception_only",
+ "lib/liba.so.meta_lic lib/liba.so.meta_lic proprietary:by_exception_only",
+ "lib/libb.so.meta_lic lib/libb.so.meta_lic restricted",
+ },
+ },
+ {
+ condition: "proprietary",
+ name: "apex_trimmed_notice",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{
+ conditions: []compliance.LicenseCondition{compliance.NoticeCondition},
+ stripPrefix: []string{"testdata/proprietary/"},
+ },
+ expectedOut: []string{
+ "bin/bin1.meta_lic bin/bin1.meta_lic notice",
+ "highest.apex.meta_lic bin/bin1.meta_lic notice",
+ "highest.apex.meta_lic highest.apex.meta_lic notice",
+ },
+ },
+ {
+ condition: "proprietary",
+ name: "apex_trimmed_share",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{
+ conditions: compliance.ImpliesShared.AsList(),
+ stripPrefix: []string{"testdata/proprietary/"},
+ },
+ expectedOut: []string{
+ "bin/bin2.meta_lic bin/bin2.meta_lic restricted",
+ "bin/bin2.meta_lic lib/libb.so.meta_lic restricted",
+ "highest.apex.meta_lic bin/bin2.meta_lic restricted",
+ "highest.apex.meta_lic highest.apex.meta_lic restricted",
+ "highest.apex.meta_lic lib/libb.so.meta_lic restricted",
+ "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: compliance.ImpliesPrivate.AsList(),
+ stripPrefix: []string{"testdata/proprietary/"},
+ },
+ expectedOut: []string{
+ "bin/bin1.meta_lic lib/liba.so.meta_lic proprietary",
+ "bin/bin1.meta_lic lib/libc.a.meta_lic proprietary",
+ "bin/bin2.meta_lic bin/bin2.meta_lic proprietary",
+ "highest.apex.meta_lic bin/bin2.meta_lic proprietary",
+ "highest.apex.meta_lic lib/liba.so.meta_lic proprietary",
+ "highest.apex.meta_lic lib/libc.a.meta_lic proprietary",
+ "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: append(compliance.ImpliesShared.AsList(), compliance.ImpliesPrivate.AsList()...),
+ stripPrefix: []string{"testdata/proprietary/"},
+ },
+ expectedOut: []string{
+ "bin/bin1.meta_lic lib/liba.so.meta_lic proprietary",
+ "bin/bin1.meta_lic lib/libc.a.meta_lic proprietary",
+ "bin/bin2.meta_lic bin/bin2.meta_lic restricted:proprietary",
+ "bin/bin2.meta_lic lib/libb.so.meta_lic restricted",
+ "highest.apex.meta_lic bin/bin2.meta_lic restricted:proprietary",
+ "highest.apex.meta_lic highest.apex.meta_lic restricted",
+ "highest.apex.meta_lic lib/liba.so.meta_lic proprietary",
+ "highest.apex.meta_lic lib/libb.so.meta_lic restricted",
+ "highest.apex.meta_lic lib/libc.a.meta_lic proprietary",
+ "lib/liba.so.meta_lic lib/liba.so.meta_lic proprietary",
+ "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: []string{"testdata/proprietary/"}, labelConditions: true},
+ expectedOut: []string{
+ "bin/bin1.meta_lic:notice bin/bin1.meta_lic:notice notice",
+ "bin/bin1.meta_lic:notice lib/liba.so.meta_lic:proprietary:by_exception_only proprietary:by_exception_only",
+ "bin/bin1.meta_lic:notice lib/libc.a.meta_lic:proprietary:by_exception_only proprietary:by_exception_only",
+ "bin/bin2.meta_lic:proprietary:by_exception_only bin/bin2.meta_lic:proprietary:by_exception_only restricted:proprietary:by_exception_only",
+ "bin/bin2.meta_lic:proprietary:by_exception_only lib/libb.so.meta_lic:restricted restricted",
+ "highest.apex.meta_lic:notice bin/bin1.meta_lic:notice notice",
+ "highest.apex.meta_lic:notice bin/bin2.meta_lic:proprietary:by_exception_only restricted:proprietary:by_exception_only",
+ "highest.apex.meta_lic:notice highest.apex.meta_lic:notice notice:restricted",
+ "highest.apex.meta_lic:notice lib/liba.so.meta_lic:proprietary:by_exception_only proprietary:by_exception_only",
+ "highest.apex.meta_lic:notice lib/libb.so.meta_lic:restricted restricted",
+ "highest.apex.meta_lic:notice lib/libc.a.meta_lic:proprietary:by_exception_only proprietary:by_exception_only",
+ "lib/liba.so.meta_lic:proprietary:by_exception_only lib/liba.so.meta_lic:proprietary:by_exception_only proprietary:by_exception_only",
+ "lib/libb.so.meta_lic:restricted lib/libb.so.meta_lic:restricted restricted",
+ },
+ },
+ {
+ condition: "proprietary",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []string{
+ "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 proprietary:by_exception_only",
+ "testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/lib/libc.a.meta_lic proprietary:by_exception_only",
+ "testdata/proprietary/bin/bin2.meta_lic testdata/proprietary/bin/bin2.meta_lic restricted:proprietary:by_exception_only",
+ "testdata/proprietary/bin/bin2.meta_lic testdata/proprietary/lib/libb.so.meta_lic restricted",
+ "testdata/proprietary/container.zip.meta_lic testdata/proprietary/bin/bin1.meta_lic notice",
+ "testdata/proprietary/container.zip.meta_lic testdata/proprietary/bin/bin2.meta_lic restricted:proprietary:by_exception_only",
+ "testdata/proprietary/container.zip.meta_lic testdata/proprietary/container.zip.meta_lic notice:restricted",
+ "testdata/proprietary/container.zip.meta_lic testdata/proprietary/lib/liba.so.meta_lic proprietary:by_exception_only",
+ "testdata/proprietary/container.zip.meta_lic testdata/proprietary/lib/libb.so.meta_lic restricted",
+ "testdata/proprietary/container.zip.meta_lic testdata/proprietary/lib/libc.a.meta_lic proprietary:by_exception_only",
+ "testdata/proprietary/lib/liba.so.meta_lic testdata/proprietary/lib/liba.so.meta_lic proprietary:by_exception_only",
+ "testdata/proprietary/lib/libb.so.meta_lic testdata/proprietary/lib/libb.so.meta_lic restricted",
+ },
+ },
+ {
+ condition: "proprietary",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []string{
+ "testdata/proprietary/application.meta_lic testdata/proprietary/application.meta_lic notice:restricted",
+ "testdata/proprietary/application.meta_lic testdata/proprietary/lib/liba.so.meta_lic restricted:proprietary:by_exception_only",
+ },
+ },
+ {
+ condition: "proprietary",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ expectedOut: []string{
+ "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 proprietary:by_exception_only",
+ "testdata/proprietary/bin/bin1.meta_lic testdata/proprietary/lib/libc.a.meta_lic proprietary:by_exception_only",
+ },
+ },
+ {
+ 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 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("dumpresoliutions: 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, *compliance.LicenseGraph) string
+ typeString() string
+}
+
+type targetMatcher struct {
+ target string
+ conditions []string
+}
+
+// newTestCondition constructs a test license condition in the license graph.
+func newTestCondition(lg *compliance.LicenseGraph, conditionName ...string) compliance.LicenseConditionSet {
+ cs := compliance.NewLicenseConditionSet()
+ for _, name := range conditionName {
+ cs = cs.Plus(compliance.RecognizedConditionNames[name])
+ }
+ if cs.IsEmpty() && len(conditionName) != 0 {
+ panic(fmt.Errorf("attempt to create unrecognized condition: %q", conditionName))
+ }
+ return cs
+}
+
+func (tm *targetMatcher) matchString(ctx *testContext, lg *compliance.LicenseGraph) string {
+ cs := newTestCondition(lg, tm.conditions...)
+ m := tm.target
+ if !cs.IsEmpty() {
+ m += "\\n" + strings.Join(cs.Names(), "\\n")
+ }
+ m = ctx.nodes[tm.target] + " [label=\"" + m + "\"];"
+ return m
+}
+
+func (tm *targetMatcher) typeString() string {
+ return "target"
+}
+
+type resolutionMatcher struct {
+ appliesTo string
+ actsOn string
+ conditions []string
+}
+
+func (rm *resolutionMatcher) matchString(ctx *testContext, lg *compliance.LicenseGraph) string {
+ cs := newTestCondition(lg, rm.conditions...)
+ return ctx.nodes[rm.appliesTo] + " -> " + ctx.nodes[rm.actsOn] +
+ " [label=\"" + strings.Join(cs.Names(), "\\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 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++
+ }
+ return &resolutionMatcher{appliesTo, actsOn, 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"),
+ matchResolution(
+ "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",
+ "notice"),
+ matchResolution(
+ "testdata/firstparty/bin/bin1.meta_lic",
+ "testdata/firstparty/lib/libc.a.meta_lic",
+ "notice"),
+ matchResolution(
+ "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",
+ "notice"),
+ matchResolution(
+ "testdata/firstparty/highest.apex.meta_lic",
+ "testdata/firstparty/bin/bin2.meta_lic",
+ "notice"),
+ matchResolution(
+ "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",
+ "notice"),
+ matchResolution(
+ "testdata/firstparty/highest.apex.meta_lic",
+ "testdata/firstparty/lib/libb.so.meta_lic",
+ "notice"),
+ matchResolution(
+ "testdata/firstparty/highest.apex.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",
+ "notice"),
+ matchResolution(
+ "testdata/firstparty/lib/libb.so.meta_lic",
+ "testdata/firstparty/lib/libb.so.meta_lic",
+ "notice"),
+ },
+ },
+ {
+ condition: "firstparty",
+ name: "apex_trimmed",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{stripPrefix: []string{"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",
+ "notice"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "lib/liba.so.meta_lic",
+ "notice"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "lib/libc.a.meta_lic",
+ "notice"),
+ matchResolution(
+ "bin/bin2.meta_lic",
+ "bin/bin2.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "bin/bin1.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "bin/bin2.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "highest.apex.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/liba.so.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/libb.so.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/libc.a.meta_lic",
+ "notice"),
+ matchResolution(
+ "lib/liba.so.meta_lic",
+ "lib/liba.so.meta_lic",
+ "notice"),
+ matchResolution(
+ "lib/libb.so.meta_lic",
+ "lib/libb.so.meta_lic",
+ "notice"),
+ },
+ },
+ {
+ condition: "firstparty",
+ name: "apex_trimmed_notice",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{
+ conditions: []compliance.LicenseCondition{compliance.NoticeCondition},
+ stripPrefix: []string{"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",
+ "notice"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "lib/liba.so.meta_lic",
+ "notice"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "lib/libc.a.meta_lic",
+ "notice"),
+ matchResolution(
+ "bin/bin2.meta_lic",
+ "bin/bin2.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "bin/bin1.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "bin/bin2.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "highest.apex.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/liba.so.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/libb.so.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/libc.a.meta_lic",
+ "notice"),
+ matchResolution(
+ "lib/liba.so.meta_lic",
+ "lib/liba.so.meta_lic",
+ "notice"),
+ matchResolution(
+ "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: compliance.ImpliesShared.AsList(),
+ stripPrefix: []string{"testdata/firstparty/"},
+ },
+ expectedOut: []getMatcher{},
+ },
+ {
+ condition: "firstparty",
+ name: "apex_trimmed_private",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{
+ conditions: compliance.ImpliesPrivate.AsList(),
+ stripPrefix: []string{"testdata/firstparty/"},
+ },
+ expectedOut: []getMatcher{},
+ },
+ {
+ condition: "firstparty",
+ name: "apex_trimmed_share_private",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{
+ conditions: compliance.ImpliesShared.Union(compliance.ImpliesPrivate).AsList(),
+ stripPrefix: []string{"testdata/firstparty/"},
+ },
+ expectedOut: []getMatcher{},
+ },
+ {
+ condition: "firstparty",
+ name: "apex_trimmed_labelled",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{stripPrefix: []string{"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"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "bin/bin1.meta_lic",
+ "notice"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "lib/liba.so.meta_lic",
+ "notice"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "lib/libc.a.meta_lic",
+ "notice"),
+ matchResolution(
+ "bin/bin2.meta_lic",
+ "bin/bin2.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "bin/bin1.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "bin/bin2.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "highest.apex.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/liba.so.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/libb.so.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/libc.a.meta_lic",
+ "notice"),
+ matchResolution(
+ "lib/liba.so.meta_lic",
+ "lib/liba.so.meta_lic",
+ "notice"),
+ matchResolution(
+ "lib/libb.so.meta_lic",
+ "lib/libb.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"),
+ matchResolution(
+ "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",
+ "notice"),
+ matchResolution(
+ "testdata/firstparty/bin/bin1.meta_lic",
+ "testdata/firstparty/lib/libc.a.meta_lic",
+ "notice"),
+ matchResolution(
+ "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",
+ "notice"),
+ matchResolution(
+ "testdata/firstparty/container.zip.meta_lic",
+ "testdata/firstparty/bin/bin2.meta_lic",
+ "notice"),
+ matchResolution(
+ "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",
+ "notice"),
+ matchResolution(
+ "testdata/firstparty/container.zip.meta_lic",
+ "testdata/firstparty/lib/libb.so.meta_lic",
+ "notice"),
+ matchResolution(
+ "testdata/firstparty/container.zip.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",
+ "notice"),
+ matchResolution(
+ "testdata/firstparty/lib/libb.so.meta_lic",
+ "testdata/firstparty/lib/libb.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"),
+ matchResolution(
+ "testdata/firstparty/application.meta_lic",
+ "testdata/firstparty/application.meta_lic",
+ "notice"),
+ matchResolution(
+ "testdata/firstparty/application.meta_lic",
+ "testdata/firstparty/lib/liba.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",
+ "notice"),
+ matchResolution(
+ "testdata/firstparty/bin/bin1.meta_lic",
+ "testdata/firstparty/lib/liba.so.meta_lic",
+ "notice"),
+ matchResolution(
+ "testdata/firstparty/bin/bin1.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",
+ "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"),
+ matchResolution(
+ "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",
+ "notice"),
+ matchResolution(
+ "testdata/notice/bin/bin1.meta_lic",
+ "testdata/notice/lib/libc.a.meta_lic",
+ "notice"),
+ matchResolution(
+ "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",
+ "notice"),
+ matchResolution(
+ "testdata/notice/highest.apex.meta_lic",
+ "testdata/notice/bin/bin2.meta_lic",
+ "notice"),
+ matchResolution(
+ "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",
+ "notice"),
+ matchResolution(
+ "testdata/notice/highest.apex.meta_lic",
+ "testdata/notice/lib/libb.so.meta_lic",
+ "notice"),
+ matchResolution(
+ "testdata/notice/highest.apex.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",
+ "notice"),
+ matchResolution(
+ "testdata/notice/lib/libb.so.meta_lic",
+ "testdata/notice/lib/libb.so.meta_lic",
+ "notice"),
+ },
+ },
+ {
+ condition: "notice",
+ name: "apex_trimmed",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{stripPrefix: []string{"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",
+ "notice"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "lib/liba.so.meta_lic",
+ "notice"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "lib/libc.a.meta_lic",
+ "notice"),
+ matchResolution(
+ "bin/bin2.meta_lic",
+ "bin/bin2.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "bin/bin1.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "bin/bin2.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "highest.apex.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/liba.so.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/libb.so.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/libc.a.meta_lic",
+ "notice"),
+ matchResolution(
+ "lib/liba.so.meta_lic",
+ "lib/liba.so.meta_lic",
+ "notice"),
+ matchResolution(
+ "lib/libb.so.meta_lic",
+ "lib/libb.so.meta_lic",
+ "notice"),
+ },
+ },
+ {
+ condition: "notice",
+ name: "apex_trimmed_notice",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{
+ conditions: []compliance.LicenseCondition{compliance.NoticeCondition},
+ stripPrefix: []string{"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",
+ "notice"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "lib/liba.so.meta_lic",
+ "notice"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "lib/libc.a.meta_lic",
+ "notice"),
+ matchResolution(
+ "bin/bin2.meta_lic",
+ "bin/bin2.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "bin/bin1.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "bin/bin2.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "highest.apex.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/liba.so.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/libb.so.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/libc.a.meta_lic",
+ "notice"),
+ matchResolution(
+ "lib/liba.so.meta_lic",
+ "lib/liba.so.meta_lic",
+ "notice"),
+ matchResolution(
+ "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: compliance.ImpliesShared.AsList(),
+ stripPrefix: []string{"testdata/notice/"},
+ },
+ expectedOut: []getMatcher{},
+ },
+ {
+ condition: "notice",
+ name: "apex_trimmed_private",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{
+ conditions: compliance.ImpliesPrivate.AsList(),
+ stripPrefix: []string{"testdata/notice/"},
+ },
+ expectedOut: []getMatcher{},
+ },
+ {
+ condition: "notice",
+ name: "apex_trimmed_share_private",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{
+ conditions: compliance.ImpliesShared.Union(compliance.ImpliesPrivate).AsList(),
+ stripPrefix: []string{"testdata/notice/"},
+ },
+ expectedOut: []getMatcher{},
+ },
+ {
+ condition: "notice",
+ name: "apex_trimmed_labelled",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{stripPrefix: []string{"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"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "bin/bin1.meta_lic",
+ "notice"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "lib/liba.so.meta_lic",
+ "notice"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "lib/libc.a.meta_lic",
+ "notice"),
+ matchResolution(
+ "bin/bin2.meta_lic",
+ "bin/bin2.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "bin/bin1.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "bin/bin2.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "highest.apex.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/liba.so.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/libb.so.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/libc.a.meta_lic",
+ "notice"),
+ matchResolution(
+ "lib/liba.so.meta_lic",
+ "lib/liba.so.meta_lic",
+ "notice"),
+ matchResolution(
+ "lib/libb.so.meta_lic",
+ "lib/libb.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"),
+ matchResolution(
+ "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",
+ "notice"),
+ matchResolution(
+ "testdata/notice/bin/bin1.meta_lic",
+ "testdata/notice/lib/libc.a.meta_lic",
+ "notice"),
+ matchResolution(
+ "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",
+ "notice"),
+ matchResolution(
+ "testdata/notice/container.zip.meta_lic",
+ "testdata/notice/bin/bin2.meta_lic",
+ "notice"),
+ matchResolution(
+ "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",
+ "notice"),
+ matchResolution(
+ "testdata/notice/container.zip.meta_lic",
+ "testdata/notice/lib/libb.so.meta_lic",
+ "notice"),
+ matchResolution(
+ "testdata/notice/container.zip.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",
+ "notice"),
+ matchResolution(
+ "testdata/notice/lib/libb.so.meta_lic",
+ "testdata/notice/lib/libb.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"),
+ matchResolution(
+ "testdata/notice/application.meta_lic",
+ "testdata/notice/application.meta_lic",
+ "notice"),
+ matchResolution(
+ "testdata/notice/application.meta_lic",
+ "testdata/notice/lib/liba.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",
+ "notice"),
+ matchResolution(
+ "testdata/notice/bin/bin1.meta_lic",
+ "testdata/notice/lib/liba.so.meta_lic",
+ "notice"),
+ matchResolution(
+ "testdata/notice/bin/bin1.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",
+ "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"),
+ matchResolution(
+ "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",
+ "reciprocal"),
+ matchResolution(
+ "testdata/reciprocal/bin/bin1.meta_lic",
+ "testdata/reciprocal/lib/libc.a.meta_lic",
+ "reciprocal"),
+ matchResolution(
+ "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",
+ "notice"),
+ matchResolution(
+ "testdata/reciprocal/highest.apex.meta_lic",
+ "testdata/reciprocal/bin/bin2.meta_lic",
+ "notice"),
+ matchResolution(
+ "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",
+ "reciprocal"),
+ matchResolution(
+ "testdata/reciprocal/highest.apex.meta_lic",
+ "testdata/reciprocal/lib/libb.so.meta_lic",
+ "notice"),
+ matchResolution(
+ "testdata/reciprocal/highest.apex.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",
+ "reciprocal"),
+ matchResolution(
+ "testdata/reciprocal/lib/libb.so.meta_lic",
+ "testdata/reciprocal/lib/libb.so.meta_lic",
+ "notice"),
+ },
+ },
+ {
+ condition: "reciprocal",
+ name: "apex_trimmed",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{stripPrefix: []string{"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"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "bin/bin1.meta_lic",
+ "notice"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "lib/liba.so.meta_lic",
+ "reciprocal"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "lib/libc.a.meta_lic",
+ "reciprocal"),
+ matchResolution(
+ "bin/bin2.meta_lic",
+ "bin/bin2.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "bin/bin1.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "bin/bin2.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "highest.apex.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/liba.so.meta_lic",
+ "reciprocal"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/libb.so.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/libc.a.meta_lic",
+ "reciprocal"),
+ matchResolution(
+ "lib/liba.so.meta_lic",
+ "lib/liba.so.meta_lic",
+ "reciprocal"),
+ matchResolution(
+ "lib/libb.so.meta_lic",
+ "lib/libb.so.meta_lic",
+ "notice"),
+ },
+ },
+ {
+ condition: "reciprocal",
+ name: "apex_trimmed_notice",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{
+ conditions: []compliance.LicenseCondition{compliance.NoticeCondition},
+ stripPrefix: []string{"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",
+ "notice"),
+ matchResolution(
+ "bin/bin2.meta_lic",
+ "bin/bin2.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "bin/bin1.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "bin/bin2.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "highest.apex.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/libb.so.meta_lic",
+ "notice"),
+ matchResolution(
+ "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: compliance.ImpliesShared.AsList(),
+ stripPrefix: []string{"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",
+ "reciprocal"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "lib/libc.a.meta_lic",
+ "reciprocal"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/liba.so.meta_lic",
+ "reciprocal"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/libc.a.meta_lic",
+ "reciprocal"),
+ matchResolution(
+ "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: compliance.ImpliesPrivate.AsList(),
+ stripPrefix: []string{"testdata/reciprocal/"},
+ },
+ expectedOut: []getMatcher{},
+ },
+ {
+ condition: "reciprocal",
+ name: "apex_trimmed_share_private",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{
+ conditions: compliance.ImpliesShared.Union(compliance.ImpliesPrivate).AsList(),
+ stripPrefix: []string{"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",
+ "reciprocal"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "lib/libc.a.meta_lic",
+ "reciprocal"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/liba.so.meta_lic",
+ "reciprocal"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/libc.a.meta_lic",
+ "reciprocal"),
+ matchResolution(
+ "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: []string{"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"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "bin/bin1.meta_lic",
+ "notice"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "lib/liba.so.meta_lic",
+ "reciprocal"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "lib/libc.a.meta_lic",
+ "reciprocal"),
+ matchResolution(
+ "bin/bin2.meta_lic",
+ "bin/bin2.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "bin/bin1.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "bin/bin2.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "highest.apex.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/liba.so.meta_lic",
+ "reciprocal"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/libb.so.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/libc.a.meta_lic",
+ "reciprocal"),
+ matchResolution(
+ "lib/liba.so.meta_lic",
+ "lib/liba.so.meta_lic",
+ "reciprocal"),
+ matchResolution(
+ "lib/libb.so.meta_lic",
+ "lib/libb.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"),
+ matchResolution(
+ "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",
+ "reciprocal"),
+ matchResolution(
+ "testdata/reciprocal/bin/bin1.meta_lic",
+ "testdata/reciprocal/lib/libc.a.meta_lic",
+ "reciprocal"),
+ matchResolution(
+ "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",
+ "notice"),
+ matchResolution(
+ "testdata/reciprocal/container.zip.meta_lic",
+ "testdata/reciprocal/bin/bin2.meta_lic",
+ "notice"),
+ matchResolution(
+ "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",
+ "reciprocal"),
+ matchResolution(
+ "testdata/reciprocal/container.zip.meta_lic",
+ "testdata/reciprocal/lib/libb.so.meta_lic",
+ "notice"),
+ matchResolution(
+ "testdata/reciprocal/container.zip.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",
+ "reciprocal"),
+ matchResolution(
+ "testdata/reciprocal/lib/libb.so.meta_lic",
+ "testdata/reciprocal/lib/libb.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"),
+ matchResolution(
+ "testdata/reciprocal/application.meta_lic",
+ "testdata/reciprocal/application.meta_lic",
+ "notice"),
+ matchResolution(
+ "testdata/reciprocal/application.meta_lic",
+ "testdata/reciprocal/lib/liba.so.meta_lic",
+ "reciprocal"),
+ },
+ },
+ {
+ 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",
+ "notice"),
+ matchResolution(
+ "testdata/reciprocal/bin/bin1.meta_lic",
+ "testdata/reciprocal/lib/liba.so.meta_lic",
+ "reciprocal"),
+ matchResolution(
+ "testdata/reciprocal/bin/bin1.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",
+ "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/highest.apex.meta_lic"),
+ matchResolution(
+ "testdata/restricted/bin/bin1.meta_lic",
+ "testdata/restricted/bin/bin1.meta_lic",
+ "restricted_allows_dynamic_linking",
+ "notice"),
+ matchResolution(
+ "testdata/restricted/bin/bin1.meta_lic",
+ "testdata/restricted/lib/liba.so.meta_lic",
+ "restricted_allows_dynamic_linking"),
+ matchResolution(
+ "testdata/restricted/bin/bin1.meta_lic",
+ "testdata/restricted/lib/libc.a.meta_lic",
+ "reciprocal",
+ "restricted_allows_dynamic_linking"),
+ matchResolution(
+ "testdata/restricted/bin/bin2.meta_lic",
+ "testdata/restricted/bin/bin2.meta_lic",
+ "restricted",
+ "notice"),
+ matchResolution(
+ "testdata/restricted/bin/bin2.meta_lic",
+ "testdata/restricted/lib/libb.so.meta_lic",
+ "restricted"),
+ matchResolution(
+ "testdata/restricted/highest.apex.meta_lic",
+ "testdata/restricted/bin/bin1.meta_lic",
+ "restricted_allows_dynamic_linking",
+ "notice"),
+ matchResolution(
+ "testdata/restricted/highest.apex.meta_lic",
+ "testdata/restricted/bin/bin2.meta_lic",
+ "restricted",
+ "notice"),
+ matchResolution(
+ "testdata/restricted/highest.apex.meta_lic",
+ "testdata/restricted/highest.apex.meta_lic",
+ "restricted",
+ "restricted_allows_dynamic_linking",
+ "notice"),
+ matchResolution(
+ "testdata/restricted/highest.apex.meta_lic",
+ "testdata/restricted/lib/liba.so.meta_lic",
+ "restricted_allows_dynamic_linking"),
+ matchResolution(
+ "testdata/restricted/highest.apex.meta_lic",
+ "testdata/restricted/lib/libb.so.meta_lic",
+ "restricted"),
+ matchResolution(
+ "testdata/restricted/highest.apex.meta_lic",
+ "testdata/restricted/lib/libc.a.meta_lic",
+ "reciprocal",
+ "restricted_allows_dynamic_linking"),
+ matchResolution(
+ "testdata/restricted/lib/liba.so.meta_lic",
+ "testdata/restricted/lib/liba.so.meta_lic",
+ "restricted_allows_dynamic_linking"),
+ matchResolution(
+ "testdata/restricted/lib/libb.so.meta_lic",
+ "testdata/restricted/lib/libb.so.meta_lic",
+ "restricted"),
+ },
+ },
+ {
+ condition: "restricted",
+ name: "apex_trimmed",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{stripPrefix: []string{"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",
+ "restricted_allows_dynamic_linking",
+ "notice"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "lib/liba.so.meta_lic",
+ "restricted_allows_dynamic_linking"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "lib/libc.a.meta_lic",
+ "reciprocal",
+ "restricted_allows_dynamic_linking"),
+ matchResolution(
+ "bin/bin2.meta_lic",
+ "bin/bin2.meta_lic",
+ "restricted",
+ "notice"),
+ matchResolution(
+ "bin/bin2.meta_lic",
+ "lib/libb.so.meta_lic",
+ "restricted"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "bin/bin1.meta_lic",
+ "restricted_allows_dynamic_linking",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "bin/bin2.meta_lic",
+ "restricted",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "highest.apex.meta_lic",
+ "restricted",
+ "restricted_allows_dynamic_linking",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/liba.so.meta_lic",
+ "restricted_allows_dynamic_linking"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/libb.so.meta_lic",
+ "restricted"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/libc.a.meta_lic",
+ "reciprocal",
+ "restricted_allows_dynamic_linking"),
+ matchResolution(
+ "lib/liba.so.meta_lic",
+ "lib/liba.so.meta_lic",
+ "restricted_allows_dynamic_linking"),
+ matchResolution(
+ "lib/libb.so.meta_lic",
+ "lib/libb.so.meta_lic",
+ "restricted"),
+ },
+ },
+ {
+ condition: "restricted",
+ name: "apex_trimmed_notice",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{
+ conditions: []compliance.LicenseCondition{compliance.NoticeCondition},
+ stripPrefix: []string{"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",
+ "notice"),
+ matchResolution(
+ "bin/bin2.meta_lic",
+ "bin/bin2.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "bin/bin1.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "bin/bin2.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "highest.apex.meta_lic",
+ "notice"),
+ },
+ },
+ {
+ condition: "restricted",
+ name: "apex_trimmed_share",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{
+ conditions: compliance.ImpliesShared.AsList(),
+ stripPrefix: []string{"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",
+ "restricted_allows_dynamic_linking"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "lib/liba.so.meta_lic",
+ "restricted_allows_dynamic_linking"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "lib/libc.a.meta_lic",
+ "reciprocal",
+ "restricted_allows_dynamic_linking"),
+ matchResolution(
+ "bin/bin2.meta_lic",
+ "bin/bin2.meta_lic",
+ "restricted"),
+ matchResolution(
+ "bin/bin2.meta_lic",
+ "lib/libb.so.meta_lic",
+ "restricted"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "bin/bin1.meta_lic",
+ "restricted_allows_dynamic_linking"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "bin/bin2.meta_lic",
+ "restricted"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "highest.apex.meta_lic",
+ "restricted",
+ "restricted_allows_dynamic_linking"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/liba.so.meta_lic",
+ "restricted_allows_dynamic_linking"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/libb.so.meta_lic",
+ "restricted"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/libc.a.meta_lic",
+ "reciprocal",
+ "restricted_allows_dynamic_linking"),
+ matchResolution(
+ "lib/liba.so.meta_lic",
+ "lib/liba.so.meta_lic",
+ "restricted_allows_dynamic_linking"),
+ matchResolution(
+ "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: compliance.ImpliesPrivate.AsList(),
+ stripPrefix: []string{"testdata/restricted/"},
+ },
+ expectedOut: []getMatcher{},
+ },
+ {
+ condition: "restricted",
+ name: "apex_trimmed_share_private",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{
+ conditions: compliance.ImpliesShared.Union(compliance.ImpliesPrivate).AsList(),
+ stripPrefix: []string{"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",
+ "restricted_allows_dynamic_linking"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "lib/liba.so.meta_lic",
+ "restricted_allows_dynamic_linking"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "lib/libc.a.meta_lic",
+ "reciprocal",
+ "restricted_allows_dynamic_linking"),
+ matchResolution(
+ "bin/bin2.meta_lic",
+ "bin/bin2.meta_lic",
+ "restricted"),
+ matchResolution(
+ "bin/bin2.meta_lic",
+ "lib/libb.so.meta_lic",
+ "restricted"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "bin/bin1.meta_lic",
+ "restricted_allows_dynamic_linking"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "bin/bin2.meta_lic",
+ "restricted"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "highest.apex.meta_lic",
+ "restricted",
+ "restricted_allows_dynamic_linking"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/liba.so.meta_lic",
+ "restricted_allows_dynamic_linking"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/libb.so.meta_lic",
+ "restricted"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/libc.a.meta_lic",
+ "reciprocal",
+ "restricted_allows_dynamic_linking"),
+ matchResolution(
+ "lib/liba.so.meta_lic",
+ "lib/liba.so.meta_lic",
+ "restricted_allows_dynamic_linking"),
+ matchResolution(
+ "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: []string{"testdata/restricted/"}, labelConditions: true},
+ expectedOut: []getMatcher{
+ matchTarget("bin/bin1.meta_lic", "notice"),
+ matchTarget("lib/liba.so.meta_lic", "restricted_allows_dynamic_linking"),
+ matchTarget("lib/libc.a.meta_lic", "reciprocal"),
+ matchTarget("bin/bin2.meta_lic", "notice"),
+ matchTarget("lib/libb.so.meta_lic", "restricted"),
+ matchTarget("highest.apex.meta_lic", "notice"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "bin/bin1.meta_lic",
+ "restricted_allows_dynamic_linking",
+ "notice"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "lib/liba.so.meta_lic",
+ "restricted_allows_dynamic_linking"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "lib/libc.a.meta_lic",
+ "reciprocal",
+ "restricted_allows_dynamic_linking"),
+ matchResolution(
+ "bin/bin2.meta_lic",
+ "bin/bin2.meta_lic",
+ "restricted",
+ "notice"),
+ matchResolution(
+ "bin/bin2.meta_lic",
+ "lib/libb.so.meta_lic",
+ "restricted"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "bin/bin1.meta_lic",
+ "restricted_allows_dynamic_linking",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "bin/bin2.meta_lic",
+ "restricted",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "highest.apex.meta_lic",
+ "restricted",
+ "restricted_allows_dynamic_linking",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/liba.so.meta_lic",
+ "restricted_allows_dynamic_linking"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/libb.so.meta_lic",
+ "restricted"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/libc.a.meta_lic",
+ "reciprocal",
+ "restricted_allows_dynamic_linking"),
+ matchResolution(
+ "lib/liba.so.meta_lic",
+ "lib/liba.so.meta_lic",
+ "restricted_allows_dynamic_linking"),
+ matchResolution(
+ "lib/libb.so.meta_lic",
+ "lib/libb.so.meta_lic",
+ "restricted"),
+ },
+ },
+ {
+ 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/container.zip.meta_lic"),
+ matchResolution(
+ "testdata/restricted/bin/bin1.meta_lic",
+ "testdata/restricted/bin/bin1.meta_lic",
+ "restricted_allows_dynamic_linking",
+ "notice"),
+ matchResolution(
+ "testdata/restricted/bin/bin1.meta_lic",
+ "testdata/restricted/lib/liba.so.meta_lic",
+ "restricted_allows_dynamic_linking"),
+ matchResolution(
+ "testdata/restricted/bin/bin1.meta_lic",
+ "testdata/restricted/lib/libc.a.meta_lic",
+ "reciprocal",
+ "restricted_allows_dynamic_linking"),
+ matchResolution(
+ "testdata/restricted/bin/bin2.meta_lic",
+ "testdata/restricted/bin/bin2.meta_lic",
+ "restricted",
+ "notice"),
+ matchResolution(
+ "testdata/restricted/bin/bin2.meta_lic",
+ "testdata/restricted/lib/libb.so.meta_lic",
+ "restricted"),
+ matchResolution(
+ "testdata/restricted/container.zip.meta_lic",
+ "testdata/restricted/bin/bin1.meta_lic",
+ "restricted_allows_dynamic_linking",
+ "notice"),
+ matchResolution(
+ "testdata/restricted/container.zip.meta_lic",
+ "testdata/restricted/bin/bin2.meta_lic",
+ "restricted",
+ "notice"),
+ matchResolution(
+ "testdata/restricted/container.zip.meta_lic",
+ "testdata/restricted/container.zip.meta_lic",
+ "restricted",
+ "restricted_allows_dynamic_linking",
+ "notice"),
+ matchResolution(
+ "testdata/restricted/container.zip.meta_lic",
+ "testdata/restricted/lib/liba.so.meta_lic",
+ "restricted_allows_dynamic_linking"),
+ matchResolution(
+ "testdata/restricted/container.zip.meta_lic",
+ "testdata/restricted/lib/libb.so.meta_lic",
+ "restricted"),
+ matchResolution(
+ "testdata/restricted/container.zip.meta_lic",
+ "testdata/restricted/lib/libc.a.meta_lic",
+ "reciprocal",
+ "restricted_allows_dynamic_linking"),
+ matchResolution(
+ "testdata/restricted/lib/liba.so.meta_lic",
+ "testdata/restricted/lib/liba.so.meta_lic",
+ "restricted_allows_dynamic_linking"),
+ matchResolution(
+ "testdata/restricted/lib/libb.so.meta_lic",
+ "testdata/restricted/lib/libb.so.meta_lic",
+ "restricted"),
+ },
+ },
+ {
+ 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"),
+ matchResolution(
+ "testdata/restricted/application.meta_lic",
+ "testdata/restricted/application.meta_lic",
+ "restricted",
+ "restricted_allows_dynamic_linking",
+ "notice"),
+ matchResolution(
+ "testdata/restricted/application.meta_lic",
+ "testdata/restricted/lib/liba.so.meta_lic",
+ "restricted_allows_dynamic_linking",
+ "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",
+ "restricted_allows_dynamic_linking",
+ "notice"),
+ matchResolution(
+ "testdata/restricted/bin/bin1.meta_lic",
+ "testdata/restricted/lib/liba.so.meta_lic",
+ "restricted_allows_dynamic_linking"),
+ matchResolution(
+ "testdata/restricted/bin/bin1.meta_lic",
+ "testdata/restricted/lib/libc.a.meta_lic",
+ "restricted_allows_dynamic_linking",
+ "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",
+ "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/highest.apex.meta_lic"),
+ matchResolution(
+ "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",
+ "by_exception_only",
+ "proprietary"),
+ matchResolution(
+ "testdata/proprietary/bin/bin1.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",
+ "restricted",
+ "by_exception_only",
+ "proprietary"),
+ matchResolution(
+ "testdata/proprietary/bin/bin2.meta_lic",
+ "testdata/proprietary/lib/libb.so.meta_lic",
+ "restricted"),
+ matchResolution(
+ "testdata/proprietary/highest.apex.meta_lic",
+ "testdata/proprietary/bin/bin1.meta_lic",
+ "notice"),
+ matchResolution(
+ "testdata/proprietary/highest.apex.meta_lic",
+ "testdata/proprietary/bin/bin2.meta_lic",
+ "restricted",
+ "by_exception_only",
+ "proprietary"),
+ matchResolution(
+ "testdata/proprietary/highest.apex.meta_lic",
+ "testdata/proprietary/highest.apex.meta_lic",
+ "restricted",
+ "notice"),
+ matchResolution(
+ "testdata/proprietary/highest.apex.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",
+ "restricted"),
+ matchResolution(
+ "testdata/proprietary/highest.apex.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",
+ "by_exception_only",
+ "proprietary"),
+ matchResolution(
+ "testdata/proprietary/lib/libb.so.meta_lic",
+ "testdata/proprietary/lib/libb.so.meta_lic",
+ "restricted"),
+ },
+ },
+ {
+ condition: "proprietary",
+ name: "apex_trimmed",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{stripPrefix: []string{"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",
+ "bin/bin1.meta_lic",
+ "notice"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "lib/liba.so.meta_lic",
+ "by_exception_only",
+ "proprietary"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "lib/libc.a.meta_lic",
+ "by_exception_only",
+ "proprietary"),
+ matchResolution(
+ "bin/bin2.meta_lic",
+ "bin/bin2.meta_lic",
+ "by_exception_only",
+ "restricted",
+ "proprietary"),
+ matchResolution(
+ "bin/bin2.meta_lic",
+ "lib/libb.so.meta_lic",
+ "restricted"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "bin/bin1.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "bin/bin2.meta_lic",
+ "restricted",
+ "by_exception_only",
+ "proprietary"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "highest.apex.meta_lic",
+ "restricted",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/liba.so.meta_lic",
+ "by_exception_only",
+ "proprietary"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/libb.so.meta_lic",
+ "restricted"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/libc.a.meta_lic",
+ "by_exception_only",
+ "proprietary"),
+ matchResolution(
+ "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",
+ "restricted"),
+ },
+ },
+ {
+ condition: "proprietary",
+ name: "apex_trimmed_notice",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{
+ conditions: []compliance.LicenseCondition{compliance.NoticeCondition},
+ stripPrefix: []string{"testdata/proprietary/"},
+ },
+ expectedOut: []getMatcher{
+ matchTarget("bin/bin1.meta_lic"),
+ matchTarget("highest.apex.meta_lic"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "bin/bin1.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "bin/bin1.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "highest.apex.meta_lic",
+ "notice"),
+ },
+ },
+ {
+ condition: "proprietary",
+ name: "apex_trimmed_share",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{
+ conditions: compliance.ImpliesShared.AsList(),
+ stripPrefix: []string{"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",
+ "restricted"),
+ matchResolution(
+ "bin/bin2.meta_lic",
+ "lib/libb.so.meta_lic",
+ "restricted"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "bin/bin2.meta_lic",
+ "restricted"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "highest.apex.meta_lic",
+ "restricted"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/libb.so.meta_lic",
+ "restricted"),
+ matchResolution(
+ "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: compliance.ImpliesPrivate.AsList(),
+ stripPrefix: []string{"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",
+ "proprietary"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "lib/libc.a.meta_lic",
+ "proprietary"),
+ matchResolution(
+ "bin/bin2.meta_lic",
+ "bin/bin2.meta_lic",
+ "proprietary"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "bin/bin2.meta_lic",
+ "proprietary"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/liba.so.meta_lic",
+ "proprietary"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/libc.a.meta_lic",
+ "proprietary"),
+ matchResolution(
+ "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: compliance.ImpliesShared.Union(compliance.ImpliesPrivate).AsList(),
+ stripPrefix: []string{"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",
+ "proprietary"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "lib/libc.a.meta_lic",
+ "proprietary"),
+ matchResolution(
+ "bin/bin2.meta_lic",
+ "bin/bin2.meta_lic",
+ "restricted",
+ "proprietary"),
+ matchResolution(
+ "bin/bin2.meta_lic",
+ "lib/libb.so.meta_lic",
+ "restricted"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "bin/bin2.meta_lic",
+ "restricted",
+ "proprietary"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "highest.apex.meta_lic",
+ "restricted"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/liba.so.meta_lic",
+ "proprietary"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/libb.so.meta_lic",
+ "restricted"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/libc.a.meta_lic",
+ "proprietary"),
+ matchResolution(
+ "lib/liba.so.meta_lic",
+ "lib/liba.so.meta_lic",
+ "proprietary"),
+ matchResolution(
+ "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: []string{"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("highest.apex.meta_lic", "notice"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "bin/bin1.meta_lic",
+ "notice"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "lib/liba.so.meta_lic",
+ "by_exception_only",
+ "proprietary"),
+ matchResolution(
+ "bin/bin1.meta_lic",
+ "lib/libc.a.meta_lic",
+ "by_exception_only",
+ "proprietary"),
+ matchResolution(
+ "bin/bin2.meta_lic",
+ "bin/bin2.meta_lic",
+ "restricted",
+ "by_exception_only",
+ "proprietary"),
+ matchResolution(
+ "bin/bin2.meta_lic",
+ "lib/libb.so.meta_lic",
+ "restricted"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "bin/bin1.meta_lic",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "bin/bin2.meta_lic",
+ "restricted",
+ "by_exception_only",
+ "proprietary"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "highest.apex.meta_lic",
+ "restricted",
+ "notice"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/liba.so.meta_lic",
+ "by_exception_only",
+ "proprietary"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/libb.so.meta_lic",
+ "restricted"),
+ matchResolution(
+ "highest.apex.meta_lic",
+ "lib/libc.a.meta_lic",
+ "by_exception_only",
+ "proprietary"),
+ matchResolution(
+ "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",
+ "restricted"),
+ },
+ },
+ {
+ 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/container.zip.meta_lic"),
+ matchResolution(
+ "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",
+ "by_exception_only",
+ "proprietary"),
+ matchResolution(
+ "testdata/proprietary/bin/bin1.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",
+ "restricted",
+ "by_exception_only",
+ "proprietary"),
+ matchResolution(
+ "testdata/proprietary/bin/bin2.meta_lic",
+ "testdata/proprietary/lib/libb.so.meta_lic",
+ "restricted"),
+ matchResolution(
+ "testdata/proprietary/container.zip.meta_lic",
+ "testdata/proprietary/bin/bin1.meta_lic",
+ "notice"),
+ matchResolution(
+ "testdata/proprietary/container.zip.meta_lic",
+ "testdata/proprietary/bin/bin2.meta_lic",
+ "restricted",
+ "by_exception_only",
+ "proprietary"),
+ matchResolution(
+ "testdata/proprietary/container.zip.meta_lic",
+ "testdata/proprietary/container.zip.meta_lic",
+ "restricted",
+ "notice"),
+ matchResolution(
+ "testdata/proprietary/container.zip.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",
+ "restricted"),
+ matchResolution(
+ "testdata/proprietary/container.zip.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",
+ "by_exception_only",
+ "proprietary"),
+ matchResolution(
+ "testdata/proprietary/lib/libb.so.meta_lic",
+ "testdata/proprietary/lib/libb.so.meta_lic",
+ "restricted"),
+ },
+ },
+ {
+ 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"),
+ matchResolution(
+ "testdata/proprietary/application.meta_lic",
+ "testdata/proprietary/application.meta_lic",
+ "notice",
+ "restricted"),
+ matchResolution(
+ "testdata/proprietary/application.meta_lic",
+ "testdata/proprietary/lib/liba.so.meta_lic",
+ "restricted",
+ "by_exception_only",
+ "proprietary"),
+ },
+ },
+ {
+ 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",
+ "notice"),
+ matchResolution(
+ "testdata/proprietary/bin/bin1.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",
+ "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",
+ "notice"),
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.condition+" "+tt.name, func(t *testing.T) {
+ ctx := &testContext{0, make(map[string]string)}
+
+ 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
+ lg, 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)
+ }
+
+ expectedOut := &bytes.Buffer{}
+ for _, eo := range tt.expectedOut {
+ m := eo(ctx)
+ expectedOut.WriteString(m.matchString(ctx, lg))
+ expectedOut.WriteString("\n")
+ }
+
+ outList := strings.Split(stdout.String(), "\n")
+ outLine := 0
+ if outList[outLine] != "strict digraph {" {
+ t.Errorf("dumpresolutions: got 1st line %v, want strict digraph {", outList[outLine])
+ }
+ 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/htmlnotice/htmlnotice.go b/tools/compliance/cmd/htmlnotice/htmlnotice.go
new file mode 100644
index 0000000..938bb7d
--- /dev/null
+++ b/tools/compliance/cmd/htmlnotice/htmlnotice.go
@@ -0,0 +1,253 @@
+// 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"
+ "compress/gzip"
+ "flag"
+ "fmt"
+ "html"
+ "io"
+ "io/fs"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "android/soong/tools/compliance"
+
+ "github.com/google/blueprint/deptools"
+)
+
+var (
+ outputFile = flag.String("o", "-", "Where to write the NOTICE text file. (default stdout)")
+ depsFile = flag.String("d", "", "Where to write the deps file")
+ includeTOC = flag.Bool("toc", true, "Whether to include a table of contents.")
+ stripPrefix = newMultiString("strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)")
+ title = flag.String("title", "", "The title of the notice file.")
+
+ failNoneRequested = fmt.Errorf("\nNo license metadata files requested")
+ failNoLicenses = fmt.Errorf("No licenses found")
+)
+
+type context struct {
+ stdout io.Writer
+ stderr io.Writer
+ rootFS fs.FS
+ includeTOC bool
+ stripPrefix []string
+ title string
+ deps *[]string
+}
+
+func (ctx context) strip(installPath string) string {
+ for _, prefix := range ctx.stripPrefix {
+ if strings.HasPrefix(installPath, prefix) {
+ p := strings.TrimPrefix(installPath, prefix)
+ if 0 == len(p) {
+ p = ctx.title
+ }
+ if 0 == len(p) {
+ continue
+ }
+ return p
+ }
+ }
+ return installPath
+}
+
+func init() {
+ flag.Usage = func() {
+ fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...}
+
+Outputs an html NOTICE.html or gzipped NOTICE.html.gz file if the -o filename
+ends with ".gz".
+
+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)
+ }
+
+ if len(*outputFile) == 0 {
+ flag.Usage()
+ fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n")
+ os.Exit(2)
+ } else {
+ dir, err := filepath.Abs(filepath.Dir(*outputFile))
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err)
+ os.Exit(1)
+ }
+ fi, err := os.Stat(dir)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err)
+ os.Exit(1)
+ }
+ if !fi.IsDir() {
+ fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile)
+ os.Exit(1)
+ }
+ }
+
+ var ofile io.Writer
+ var closer io.Closer
+ ofile = os.Stdout
+ var obuf *bytes.Buffer
+ if *outputFile != "-" {
+ obuf = &bytes.Buffer{}
+ ofile = obuf
+ }
+ if strings.HasSuffix(*outputFile, ".gz") {
+ ofile, _ = gzip.NewWriterLevel(obuf, gzip.BestCompression)
+ closer = ofile.(io.Closer)
+ }
+
+ var deps []string
+
+ ctx := &context{ofile, os.Stderr, os.DirFS("."), *includeTOC, *stripPrefix, *title, &deps}
+
+ err := htmlNotice(ctx, flag.Args()...)
+ if err != nil {
+ if err == failNoneRequested {
+ flag.Usage()
+ }
+ fmt.Fprintf(os.Stderr, "%s\n", err.Error())
+ os.Exit(1)
+ }
+ if closer != nil {
+ closer.Close()
+ }
+
+ if *outputFile != "-" {
+ err := os.WriteFile(*outputFile, obuf.Bytes(), 0666)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "could not write output to %q: %s\n", *outputFile, err)
+ os.Exit(1)
+ }
+ }
+ if *depsFile != "" {
+ err := deptools.WriteDepFile(*depsFile, *outputFile, deps)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "could not write deps to %q: %s\n", *depsFile, err)
+ os.Exit(1)
+ }
+ }
+ os.Exit(0)
+}
+
+// htmlNotice implements the htmlnotice utility.
+func htmlNotice(ctx *context, 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(ctx.rootFS, ctx.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
+ }
+
+ // rs contains all notice resolutions.
+ rs := compliance.ResolveNotices(licenseGraph)
+
+ ni, err := compliance.IndexLicenseTexts(ctx.rootFS, licenseGraph, rs)
+ if err != nil {
+ return fmt.Errorf("Unable to read license text file(s) for %q: %v\n", files, err)
+ }
+
+ fmt.Fprintln(ctx.stdout, "<!DOCTYPE html>")
+ fmt.Fprintln(ctx.stdout, "<html><head>")
+ fmt.Fprintln(ctx.stdout, "<style type=\"text/css\">")
+ fmt.Fprintln(ctx.stdout, "body { padding: 2px; margin: 0; }")
+ fmt.Fprintln(ctx.stdout, "ul { list-style-type: none; margin: 0; padding: 0; }")
+ fmt.Fprintln(ctx.stdout, "li { padding-left: 1em; }")
+ fmt.Fprintln(ctx.stdout, ".file-list { margin-left: 1em; }")
+ fmt.Fprintln(ctx.stdout, "</style>")
+ if 0 < len(ctx.title) {
+ fmt.Fprintf(ctx.stdout, "<title>%s</title>\n", html.EscapeString(ctx.title))
+ }
+ fmt.Fprintln(ctx.stdout, "</head>")
+ fmt.Fprintln(ctx.stdout, "<body>")
+
+ if 0 < len(ctx.title) {
+ fmt.Fprintf(ctx.stdout, " <h1>%s</h1>\n", html.EscapeString(ctx.title))
+ }
+ ids := make(map[string]string)
+ if ctx.includeTOC {
+ fmt.Fprintln(ctx.stdout, " <ul class=\"toc\">")
+ i := 0
+ for installPath := range ni.InstallPaths() {
+ id := fmt.Sprintf("id%d", i)
+ i++
+ ids[installPath] = id
+ fmt.Fprintf(ctx.stdout, " <li id=\"%s\"><strong>%s</strong>\n <ul>\n", id, html.EscapeString(ctx.strip(installPath)))
+ for _, h := range ni.InstallHashes(installPath) {
+ libs := ni.InstallHashLibs(installPath, h)
+ fmt.Fprintf(ctx.stdout, " <li><a href=\"#%s\">%s</a>\n", h.String(), html.EscapeString(strings.Join(libs, ", ")))
+ }
+ fmt.Fprintln(ctx.stdout, " </ul>")
+ }
+ fmt.Fprintln(ctx.stdout, " </ul><!-- toc -->")
+ }
+ for h := range ni.Hashes() {
+ fmt.Fprintln(ctx.stdout, " <hr>")
+ for _, libName := range ni.HashLibs(h) {
+ fmt.Fprintf(ctx.stdout, " <strong>%s</strong> used by:\n <ul class=\"file-list\">\n", html.EscapeString(libName))
+ for _, installPath := range ni.HashLibInstalls(h, libName) {
+ if id, ok := ids[installPath]; ok {
+ fmt.Fprintf(ctx.stdout, " <li><a href=\"#%s\">%s</a>\n", id, html.EscapeString(ctx.strip(installPath)))
+ } else {
+ fmt.Fprintf(ctx.stdout, " <li>%s\n", html.EscapeString(ctx.strip(installPath)))
+ }
+ }
+ fmt.Fprintf(ctx.stdout, " </ul>\n")
+ }
+ fmt.Fprintf(ctx.stdout, " </ul>\n <a id=\"%s\"/><pre class=\"license-text\">", h.String())
+ fmt.Fprintln(ctx.stdout, html.EscapeString(string(ni.HashText(h))))
+ fmt.Fprintln(ctx.stdout, " </pre><!-- license-text -->")
+ }
+ fmt.Fprintln(ctx.stdout, "</body></html>")
+
+ *ctx.deps = ni.InputNoticeFiles()
+
+ return nil
+}
diff --git a/tools/compliance/cmd/htmlnotice/htmlnotice_test.go b/tools/compliance/cmd/htmlnotice/htmlnotice_test.go
new file mode 100644
index 0000000..9863b6d
--- /dev/null
+++ b/tools/compliance/cmd/htmlnotice/htmlnotice_test.go
@@ -0,0 +1,916 @@
+// 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 (
+ "bufio"
+ "bytes"
+ "fmt"
+ "html"
+ "os"
+ "reflect"
+ "regexp"
+ "strings"
+ "testing"
+)
+
+var (
+ horizontalRule = regexp.MustCompile(`^\s*<hr>\s*$`)
+ bodyTag = regexp.MustCompile(`^\s*<body>\s*$`)
+ boilerPlate = regexp.MustCompile(`^\s*(?:<ul class="file-list">|<ul>|</.*)\s*$`)
+ tocTag = regexp.MustCompile(`^\s*<ul class="toc">\s*$`)
+ libraryName = regexp.MustCompile(`^\s*<strong>(.*)</strong>\s\s*used\s\s*by\s*:\s*$`)
+ licenseText = regexp.MustCompile(`^\s*<a id="[^"]{32}"/><pre class="license-text">(.*)$`)
+ titleTag = regexp.MustCompile(`^\s*<title>(.*)</title>\s*$`)
+ h1Tag = regexp.MustCompile(`^\s*<h1>(.*)</h1>\s*$`)
+ usedByTarget = regexp.MustCompile(`^\s*<li>(?:<a href="#id[0-9]+">)?((?:out/(?:[^/<]*/)+)[^/<]*)(?:</a>)?\s*$`)
+ installTarget = regexp.MustCompile(`^\s*<li id="id[0-9]+"><strong>(.*)</strong>\s*$`)
+ libReference = regexp.MustCompile(`^\s*<li><a href="#[^"]{32}">(.*)</a>\s*$`)
+)
+
+func TestMain(m *testing.M) {
+ // Change into the parent directory before running the tests
+ // so they can find the testdata directory.
+ if err := os.Chdir(".."); err != nil {
+ fmt.Printf("failed to change to testdata directory: %s\n", err)
+ os.Exit(1)
+ }
+ os.Exit(m.Run())
+}
+
+func Test(t *testing.T) {
+ tests := []struct {
+ condition string
+ name string
+ roots []string
+ includeTOC bool
+ stripPrefix string
+ title string
+ expectedOut []matcher
+ expectedDeps []string
+ }{
+ {
+ condition: "firstparty",
+ name: "apex",
+ roots: []string{"highest.apex.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"highest.apex"},
+ usedBy{"highest.apex/bin/bin1"},
+ usedBy{"highest.apex/bin/bin2"},
+ usedBy{"highest.apex/lib/liba.so"},
+ usedBy{"highest.apex/lib/libb.so"},
+ firstParty{},
+ },
+ expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+ },
+ {
+ condition: "firstparty",
+ name: "apex+toc",
+ roots: []string{"highest.apex.meta_lic"},
+ includeTOC: true,
+ expectedOut: []matcher{
+ toc{},
+ target{"highest.apex"},
+ uses{"Android"},
+ target{"highest.apex/bin/bin1"},
+ uses{"Android"},
+ target{"highest.apex/bin/bin2"},
+ uses{"Android"},
+ target{"highest.apex/lib/liba.so"},
+ uses{"Android"},
+ target{"highest.apex/lib/libb.so"},
+ uses{"Android"},
+ hr{},
+ library{"Android"},
+ usedBy{"highest.apex"},
+ usedBy{"highest.apex/bin/bin1"},
+ usedBy{"highest.apex/bin/bin2"},
+ usedBy{"highest.apex/lib/liba.so"},
+ usedBy{"highest.apex/lib/libb.so"},
+ firstParty{},
+ },
+ expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+ },
+ {
+ condition: "firstparty",
+ name: "apex-with-title",
+ roots: []string{"highest.apex.meta_lic"},
+ title: "Emperor",
+ expectedOut: []matcher{
+ pageTitle{"Emperor"},
+ hr{},
+ library{"Android"},
+ usedBy{"highest.apex"},
+ usedBy{"highest.apex/bin/bin1"},
+ usedBy{"highest.apex/bin/bin2"},
+ usedBy{"highest.apex/lib/liba.so"},
+ usedBy{"highest.apex/lib/libb.so"},
+ firstParty{},
+ },
+ expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+ },
+ {
+ condition: "firstparty",
+ name: "apex-with-title+toc",
+ roots: []string{"highest.apex.meta_lic"},
+ includeTOC: true,
+ title: "Emperor",
+ expectedOut: []matcher{
+ pageTitle{"Emperor"},
+ toc{},
+ target{"highest.apex"},
+ uses{"Android"},
+ target{"highest.apex/bin/bin1"},
+ uses{"Android"},
+ target{"highest.apex/bin/bin2"},
+ uses{"Android"},
+ target{"highest.apex/lib/liba.so"},
+ uses{"Android"},
+ target{"highest.apex/lib/libb.so"},
+ uses{"Android"},
+ hr{},
+ library{"Android"},
+ usedBy{"highest.apex"},
+ usedBy{"highest.apex/bin/bin1"},
+ usedBy{"highest.apex/bin/bin2"},
+ usedBy{"highest.apex/lib/liba.so"},
+ usedBy{"highest.apex/lib/libb.so"},
+ firstParty{},
+ },
+ expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+ },
+ {
+ condition: "firstparty",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"container.zip"},
+ usedBy{"container.zip/bin1"},
+ usedBy{"container.zip/bin2"},
+ usedBy{"container.zip/liba.so"},
+ usedBy{"container.zip/libb.so"},
+ firstParty{},
+ },
+ expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+ },
+ {
+ condition: "firstparty",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"application"},
+ firstParty{},
+ },
+ expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+ },
+ {
+ condition: "firstparty",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"bin/bin1"},
+ firstParty{},
+ },
+ expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+ },
+ {
+ condition: "firstparty",
+ name: "library",
+ roots: []string{"lib/libd.so.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"lib/libd.so"},
+ firstParty{},
+ },
+ expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+ },
+ {
+ condition: "notice",
+ name: "apex",
+ roots: []string{"highest.apex.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"highest.apex"},
+ usedBy{"highest.apex/bin/bin1"},
+ usedBy{"highest.apex/bin/bin2"},
+ usedBy{"highest.apex/lib/libb.so"},
+ firstParty{},
+ hr{},
+ library{"Device"},
+ usedBy{"highest.apex/bin/bin1"},
+ usedBy{"highest.apex/lib/liba.so"},
+ library{"External"},
+ usedBy{"highest.apex/bin/bin1"},
+ notice{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/notice/NOTICE_LICENSE",
+ },
+ },
+ {
+ condition: "notice",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"container.zip"},
+ usedBy{"container.zip/bin1"},
+ usedBy{"container.zip/bin2"},
+ usedBy{"container.zip/libb.so"},
+ firstParty{},
+ hr{},
+ library{"Device"},
+ usedBy{"container.zip/bin1"},
+ usedBy{"container.zip/liba.so"},
+ library{"External"},
+ usedBy{"container.zip/bin1"},
+ notice{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/notice/NOTICE_LICENSE",
+ },
+ },
+ {
+ condition: "notice",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"application"},
+ firstParty{},
+ hr{},
+ library{"Device"},
+ usedBy{"application"},
+ notice{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/notice/NOTICE_LICENSE",
+ },
+ },
+ {
+ condition: "notice",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"bin/bin1"},
+ firstParty{},
+ hr{},
+ library{"Device"},
+ usedBy{"bin/bin1"},
+ library{"External"},
+ usedBy{"bin/bin1"},
+ notice{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/notice/NOTICE_LICENSE",
+ },
+ },
+ {
+ condition: "notice",
+ name: "library",
+ roots: []string{"lib/libd.so.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"External"},
+ usedBy{"lib/libd.so"},
+ notice{},
+ },
+ expectedDeps: []string{"testdata/notice/NOTICE_LICENSE"},
+ },
+ {
+ condition: "reciprocal",
+ name: "apex",
+ roots: []string{"highest.apex.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"highest.apex"},
+ usedBy{"highest.apex/bin/bin1"},
+ usedBy{"highest.apex/bin/bin2"},
+ usedBy{"highest.apex/lib/libb.so"},
+ firstParty{},
+ hr{},
+ library{"Device"},
+ usedBy{"highest.apex/bin/bin1"},
+ usedBy{"highest.apex/lib/liba.so"},
+ library{"External"},
+ usedBy{"highest.apex/bin/bin1"},
+ reciprocal{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/reciprocal/RECIPROCAL_LICENSE",
+ },
+ },
+ {
+ condition: "reciprocal",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"container.zip"},
+ usedBy{"container.zip/bin1"},
+ usedBy{"container.zip/bin2"},
+ usedBy{"container.zip/libb.so"},
+ firstParty{},
+ hr{},
+ library{"Device"},
+ usedBy{"container.zip/bin1"},
+ usedBy{"container.zip/liba.so"},
+ library{"External"},
+ usedBy{"container.zip/bin1"},
+ reciprocal{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/reciprocal/RECIPROCAL_LICENSE",
+ },
+ },
+ {
+ condition: "reciprocal",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"application"},
+ firstParty{},
+ hr{},
+ library{"Device"},
+ usedBy{"application"},
+ reciprocal{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/reciprocal/RECIPROCAL_LICENSE",
+ },
+ },
+ {
+ condition: "reciprocal",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"bin/bin1"},
+ firstParty{},
+ hr{},
+ library{"Device"},
+ usedBy{"bin/bin1"},
+ library{"External"},
+ usedBy{"bin/bin1"},
+ reciprocal{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/reciprocal/RECIPROCAL_LICENSE",
+ },
+ },
+ {
+ condition: "reciprocal",
+ name: "library",
+ roots: []string{"lib/libd.so.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"External"},
+ usedBy{"lib/libd.so"},
+ notice{},
+ },
+ expectedDeps: []string{"testdata/notice/NOTICE_LICENSE"},
+ },
+ {
+ condition: "restricted",
+ name: "apex",
+ roots: []string{"highest.apex.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"highest.apex"},
+ usedBy{"highest.apex/bin/bin1"},
+ usedBy{"highest.apex/bin/bin2"},
+ firstParty{},
+ hr{},
+ library{"Android"},
+ usedBy{"highest.apex/bin/bin2"},
+ usedBy{"highest.apex/lib/libb.so"},
+ library{"Device"},
+ usedBy{"highest.apex/bin/bin1"},
+ usedBy{"highest.apex/lib/liba.so"},
+ restricted{},
+ hr{},
+ library{"External"},
+ usedBy{"highest.apex/bin/bin1"},
+ reciprocal{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/reciprocal/RECIPROCAL_LICENSE",
+ "testdata/restricted/RESTRICTED_LICENSE",
+ },
+ },
+ {
+ condition: "restricted",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"container.zip"},
+ usedBy{"container.zip/bin1"},
+ usedBy{"container.zip/bin2"},
+ firstParty{},
+ hr{},
+ library{"Android"},
+ usedBy{"container.zip/bin2"},
+ usedBy{"container.zip/libb.so"},
+ library{"Device"},
+ usedBy{"container.zip/bin1"},
+ usedBy{"container.zip/liba.so"},
+ restricted{},
+ hr{},
+ library{"External"},
+ usedBy{"container.zip/bin1"},
+ reciprocal{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/reciprocal/RECIPROCAL_LICENSE",
+ "testdata/restricted/RESTRICTED_LICENSE",
+ },
+ },
+ {
+ condition: "restricted",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"application"},
+ firstParty{},
+ hr{},
+ library{"Device"},
+ usedBy{"application"},
+ restricted{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/restricted/RESTRICTED_LICENSE",
+ },
+ },
+ {
+ condition: "restricted",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"bin/bin1"},
+ firstParty{},
+ hr{},
+ library{"Device"},
+ usedBy{"bin/bin1"},
+ restricted{},
+ hr{},
+ library{"External"},
+ usedBy{"bin/bin1"},
+ reciprocal{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/reciprocal/RECIPROCAL_LICENSE",
+ "testdata/restricted/RESTRICTED_LICENSE",
+ },
+ },
+ {
+ condition: "restricted",
+ name: "library",
+ roots: []string{"lib/libd.so.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"External"},
+ usedBy{"lib/libd.so"},
+ notice{},
+ },
+ expectedDeps: []string{"testdata/notice/NOTICE_LICENSE"},
+ },
+ {
+ condition: "proprietary",
+ name: "apex",
+ roots: []string{"highest.apex.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"highest.apex/bin/bin2"},
+ usedBy{"highest.apex/lib/libb.so"},
+ restricted{},
+ hr{},
+ library{"Android"},
+ usedBy{"highest.apex"},
+ usedBy{"highest.apex/bin/bin1"},
+ firstParty{},
+ hr{},
+ library{"Android"},
+ usedBy{"highest.apex/bin/bin2"},
+ library{"Device"},
+ usedBy{"highest.apex/bin/bin1"},
+ usedBy{"highest.apex/lib/liba.so"},
+ library{"External"},
+ usedBy{"highest.apex/bin/bin1"},
+ proprietary{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/proprietary/PROPRIETARY_LICENSE",
+ "testdata/restricted/RESTRICTED_LICENSE",
+ },
+ },
+ {
+ condition: "proprietary",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"container.zip/bin2"},
+ usedBy{"container.zip/libb.so"},
+ restricted{},
+ hr{},
+ library{"Android"},
+ usedBy{"container.zip"},
+ usedBy{"container.zip/bin1"},
+ firstParty{},
+ hr{},
+ library{"Android"},
+ usedBy{"container.zip/bin2"},
+ library{"Device"},
+ usedBy{"container.zip/bin1"},
+ usedBy{"container.zip/liba.so"},
+ library{"External"},
+ usedBy{"container.zip/bin1"},
+ proprietary{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/proprietary/PROPRIETARY_LICENSE",
+ "testdata/restricted/RESTRICTED_LICENSE",
+ },
+ },
+ {
+ condition: "proprietary",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"application"},
+ firstParty{},
+ hr{},
+ library{"Device"},
+ usedBy{"application"},
+ proprietary{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/proprietary/PROPRIETARY_LICENSE",
+ },
+ },
+ {
+ condition: "proprietary",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"bin/bin1"},
+ firstParty{},
+ hr{},
+ library{"Device"},
+ usedBy{"bin/bin1"},
+ library{"External"},
+ usedBy{"bin/bin1"},
+ proprietary{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/proprietary/PROPRIETARY_LICENSE",
+ },
+ },
+ {
+ condition: "proprietary",
+ name: "library",
+ roots: []string{"lib/libd.so.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"External"},
+ usedBy{"lib/libd.so"},
+ notice{},
+ },
+ expectedDeps: []string{"testdata/notice/NOTICE_LICENSE"},
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.condition+" "+tt.name, func(t *testing.T) {
+ stdout := &bytes.Buffer{}
+ stderr := &bytes.Buffer{}
+
+ rootFiles := make([]string, 0, len(tt.roots))
+ for _, r := range tt.roots {
+ rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
+ }
+
+ var deps []string
+
+ ctx := context{stdout, stderr, os.DirFS("."), tt.includeTOC, []string{tt.stripPrefix}, tt.title, &deps}
+
+ err := htmlNotice(&ctx, rootFiles...)
+ if err != nil {
+ t.Fatalf("htmlnotice: error = %v, stderr = %v", err, stderr)
+ return
+ }
+ if stderr.Len() > 0 {
+ t.Errorf("htmlnotice: gotStderr = %v, want none", stderr)
+ }
+
+ t.Logf("got stdout: %s", stdout.String())
+
+ t.Logf("want stdout: %s", matcherList(tt.expectedOut).String())
+
+ out := bufio.NewScanner(stdout)
+ lineno := 0
+ inBody := false
+ hasTitle := false
+ ttle, expectTitle := tt.expectedOut[0].(pageTitle)
+ for out.Scan() {
+ line := out.Text()
+ if strings.TrimLeft(line, " ") == "" {
+ continue
+ }
+ if !inBody {
+ if expectTitle {
+ if tl := checkTitle(line); 0 < len(tl) {
+ if tl != ttle.t {
+ t.Errorf("htmlnotice: unexpected title: got %q, want %q", tl, ttle.t)
+ }
+ hasTitle = true
+ }
+ }
+ if bodyTag.MatchString(line) {
+ inBody = true
+ if expectTitle && !hasTitle {
+ t.Errorf("htmlnotice: missing title: got no <title> tag, want <title>%s</title>", ttle.t)
+ }
+ }
+ continue
+ }
+ if boilerPlate.MatchString(line) {
+ continue
+ }
+ if len(tt.expectedOut) <= lineno {
+ t.Errorf("htmlnotice: unexpected output at line %d: got %q, want nothing (wanted %d lines)", lineno+1, line, len(tt.expectedOut))
+ } else if !tt.expectedOut[lineno].isMatch(line) {
+ t.Errorf("htmlnotice: unexpected output at line %d: got %q, want %q", lineno+1, line, tt.expectedOut[lineno].String())
+ }
+ lineno++
+ }
+ if !inBody {
+ t.Errorf("htmlnotice: missing body: got no <body> tag, want <body> tag followed by %s", matcherList(tt.expectedOut).String())
+ return
+ }
+ for ; lineno < len(tt.expectedOut); lineno++ {
+ t.Errorf("htmlnotice: missing output line %d: ended early, want %q", lineno+1, tt.expectedOut[lineno].String())
+ }
+
+ t.Logf("got deps: %q", deps)
+
+ t.Logf("want deps: %q", tt.expectedDeps)
+
+ if g, w := deps, tt.expectedDeps; !reflect.DeepEqual(g, w) {
+ t.Errorf("unexpected deps, wanted:\n%s\ngot:\n%s\n",
+ strings.Join(w, "\n"), strings.Join(g, "\n"))
+ }
+ })
+ }
+}
+
+func checkTitle(line string) string {
+ groups := titleTag.FindStringSubmatch(line)
+ if len(groups) != 2 {
+ return ""
+ }
+ return groups[1]
+}
+
+type matcher interface {
+ isMatch(line string) bool
+ String() string
+}
+
+type pageTitle struct {
+ t string
+}
+
+func (m pageTitle) isMatch(line string) bool {
+ groups := h1Tag.FindStringSubmatch(line)
+ if len(groups) != 2 {
+ return false
+ }
+ return groups[1] == html.EscapeString(m.t)
+}
+
+func (m pageTitle) String() string {
+ return " <h1>" + html.EscapeString(m.t) + "</h1>"
+}
+
+type toc struct{}
+
+func (m toc) isMatch(line string) bool {
+ return tocTag.MatchString(line)
+}
+
+func (m toc) String() string {
+ return ` <ul class="toc">`
+}
+
+type target struct {
+ name string
+}
+
+func (m target) isMatch(line string) bool {
+ groups := installTarget.FindStringSubmatch(line)
+ if len(groups) != 2 {
+ return false
+ }
+ return strings.HasPrefix(groups[1], "out/") && strings.HasSuffix(groups[1], "/"+html.EscapeString(m.name))
+}
+
+func (m target) String() string {
+ return ` <li id="id#"><strong>` + html.EscapeString(m.name) + `</strong>`
+}
+
+type uses struct {
+ name string
+}
+
+func (m uses) isMatch(line string) bool {
+ groups := libReference.FindStringSubmatch(line)
+ if len(groups) != 2 {
+ return false
+ }
+ return groups[1] == html.EscapeString(m.name)
+}
+
+func (m uses) String() string {
+ return ` <li><a href="#hash">` + html.EscapeString(m.name) + `</a>`
+}
+
+type hr struct{}
+
+func (m hr) isMatch(line string) bool {
+ return horizontalRule.MatchString(line)
+}
+
+func (m hr) String() string {
+ return " <hr>"
+}
+
+type library struct {
+ name string
+}
+
+func (m library) isMatch(line string) bool {
+ groups := libraryName.FindStringSubmatch(line)
+ if len(groups) != 2 {
+ return false
+ }
+ return groups[1] == html.EscapeString(m.name)
+}
+
+func (m library) String() string {
+ return " <strong>" + html.EscapeString(m.name) + "</strong> used by:"
+}
+
+type usedBy struct {
+ name string
+}
+
+func (m usedBy) isMatch(line string) bool {
+ groups := usedByTarget.FindStringSubmatch(line)
+ if len(groups) != 2 {
+ return false
+ }
+ return strings.HasPrefix(groups[1], "out/") && strings.HasSuffix(groups[1], "/"+html.EscapeString(m.name))
+}
+
+func (m usedBy) String() string {
+ return " <li>out/.../" + html.EscapeString(m.name)
+}
+
+func matchesText(line, text string) bool {
+ groups := licenseText.FindStringSubmatch(line)
+ if len(groups) != 2 {
+ return false
+ }
+ return groups[1] == html.EscapeString(text)
+}
+
+func expectedText(text string) string {
+ return ` <a href="#hash"/><pre class="license-text">` + html.EscapeString(text)
+}
+
+type firstParty struct{}
+
+func (m firstParty) isMatch(line string) bool {
+ return matchesText(line, "&&&First Party License&&&")
+}
+
+func (m firstParty) String() string {
+ return expectedText("&&&First Party License&&&")
+}
+
+type notice struct{}
+
+func (m notice) isMatch(line string) bool {
+ return matchesText(line, "%%%Notice License%%%")
+}
+
+func (m notice) String() string {
+ return expectedText("%%%Notice License%%%")
+}
+
+type reciprocal struct{}
+
+func (m reciprocal) isMatch(line string) bool {
+ return matchesText(line, "$$$Reciprocal License$$$")
+}
+
+func (m reciprocal) String() string {
+ return expectedText("$$$Reciprocal License$$$")
+}
+
+type restricted struct{}
+
+func (m restricted) isMatch(line string) bool {
+ return matchesText(line, "###Restricted License###")
+}
+
+func (m restricted) String() string {
+ return expectedText("###Restricted License###")
+}
+
+type proprietary struct{}
+
+func (m proprietary) isMatch(line string) bool {
+ return matchesText(line, "@@@Proprietary License@@@")
+}
+
+func (m proprietary) String() string {
+ return expectedText("@@@Proprietary License@@@")
+}
+
+type matcherList []matcher
+
+func (l matcherList) String() string {
+ var sb strings.Builder
+ for _, m := range l {
+ s := m.String()
+ if s[:3] == s[len(s)-3:] {
+ fmt.Fprintln(&sb)
+ }
+ fmt.Fprintf(&sb, "%s\n", s)
+ if s[:3] == s[len(s)-3:] {
+ fmt.Fprintln(&sb)
+ }
+ }
+ return sb.String()
+}
diff --git a/tools/compliance/cmd/listshare/listshare.go b/tools/compliance/cmd/listshare/listshare.go
new file mode 100644
index 0000000..030caa7
--- /dev/null
+++ b/tools/compliance/cmd/listshare/listshare.go
@@ -0,0 +1,120 @@
+// 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 (
+ "flag"
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+ "sort"
+ "strings"
+
+ "android/soong/tools/compliance"
+)
+
+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()
+ continue
+ }
+ presolution[p] = presolution[p].Union(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 {
+ if presolution[p].IsEmpty() {
+ fmt.Fprintf(stdout, "%s\n", p)
+ } else {
+ fmt.Fprintf(stdout, "%s,%s\n", p, strings.Join(presolution[p].Names(), ","))
+ }
+ }
+
+ return nil
+}
diff --git a/tools/compliance/cmd/listshare/listshare_test.go b/tools/compliance/cmd/listshare/listshare_test.go
new file mode 100644
index 0000000..91e9a43
--- /dev/null
+++ b/tools/compliance/cmd/listshare/listshare_test.go
@@ -0,0 +1,506 @@
+// 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"
+ "os"
+ "strings"
+ "testing"
+)
+
+func TestMain(m *testing.M) {
+ // Change into the parent directory before running the tests
+ // so they can find the testdata directory.
+ if err := os.Chdir(".."); err != nil {
+ fmt.Printf("failed to change to testdata directory: %s\n", err)
+ os.Exit(1)
+ }
+ os.Exit(m.Run())
+}
+
+func Test(t *testing.T) {
+ 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{"reciprocal"},
+ },
+ {
+ project: "static/library",
+ conditions: []string{
+ "reciprocal",
+ },
+ },
+ },
+ },
+ {
+ condition: "reciprocal",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []projectShare{
+ {
+ project: "device/library",
+ conditions: []string{"reciprocal"},
+ },
+ {
+ project: "static/library",
+ conditions: []string{
+ "reciprocal",
+ },
+ },
+ },
+ },
+ {
+ condition: "reciprocal",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []projectShare{
+ {
+ project: "device/library",
+ conditions: []string{"reciprocal"},
+ },
+ },
+ },
+ {
+ condition: "reciprocal",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ expectedOut: []projectShare{
+ {
+ project: "device/library",
+ conditions: []string{
+ "reciprocal",
+ },
+ },
+ {
+ project: "static/library",
+ conditions: []string{
+ "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{"restricted"},
+ },
+ {
+ project: "device/library",
+ conditions: []string{"restricted_allows_dynamic_linking"},
+ },
+ {
+ project: "dynamic/binary",
+ conditions: []string{"restricted"},
+ },
+ {
+ project: "highest/apex",
+ conditions: []string{
+ "restricted",
+ "restricted_allows_dynamic_linking",
+ },
+ },
+ {
+ project: "static/binary",
+ conditions: []string{
+ "restricted_allows_dynamic_linking",
+ },
+ },
+ {
+ project: "static/library",
+ conditions: []string{
+ "reciprocal",
+ "restricted_allows_dynamic_linking",
+ },
+ },
+ },
+ },
+ {
+ condition: "restricted",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []projectShare{
+ {
+ project: "base/library",
+ conditions: []string{"restricted"},
+ },
+ {
+ project: "container/zip",
+ conditions: []string{
+ "restricted",
+ "restricted_allows_dynamic_linking",
+ },
+ },
+ {
+ project: "device/library",
+ conditions: []string{"restricted_allows_dynamic_linking"},
+ },
+ {
+ project: "dynamic/binary",
+ conditions: []string{"restricted"},
+ },
+ {
+ project: "static/binary",
+ conditions: []string{
+ "restricted_allows_dynamic_linking",
+ },
+ },
+ {
+ project: "static/library",
+ conditions: []string{
+ "reciprocal",
+ "restricted_allows_dynamic_linking",
+ },
+ },
+ },
+ },
+ {
+ condition: "restricted",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []projectShare{
+ {
+ project: "device/library",
+ conditions: []string{
+ "restricted",
+ "restricted_allows_dynamic_linking",
+ },
+ },
+ {
+ project: "distributable/application",
+ conditions: []string{
+ "restricted",
+ "restricted_allows_dynamic_linking",
+ },
+ },
+ },
+ },
+ {
+ condition: "restricted",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ expectedOut: []projectShare{
+ {
+ project: "device/library",
+ conditions: []string{
+ "restricted_allows_dynamic_linking",
+ },
+ },
+ {
+ project: "static/binary",
+ conditions: []string{
+ "restricted_allows_dynamic_linking",
+ },
+ },
+ {
+ project: "static/library",
+ conditions: []string{
+ "reciprocal",
+ "restricted_allows_dynamic_linking",
+ },
+ },
+ },
+ },
+ {
+ 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{"restricted"},
+ },
+ {
+ project: "dynamic/binary",
+ conditions: []string{"restricted"},
+ },
+ {
+ project: "highest/apex",
+ conditions: []string{"restricted"},
+ },
+ },
+ },
+ {
+ condition: "proprietary",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []projectShare{
+ {
+ project: "base/library",
+ conditions: []string{"restricted"},
+ },
+ {
+ project: "container/zip",
+ conditions: []string{"restricted"},
+ },
+ {
+ project: "dynamic/binary",
+ conditions: []string{"restricted"},
+ },
+ },
+ },
+ {
+ condition: "proprietary",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []projectShare{
+ {
+ project: "device/library",
+ conditions: []string{"restricted"},
+ },
+ {
+ project: "distributable/application",
+ conditions: []string{"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{},
+ },
+ {
+ condition: "regressgpl1",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []projectShare{
+ {
+ project: "bin/threelibraries",
+ conditions: []string{"restricted"},
+ },
+ {
+ project: "container/zip",
+ conditions: []string{"restricted"},
+ },
+ },
+ },
+ {
+ condition: "regressgpl1",
+ name: "containerplus",
+ roots: []string{"container.zip.meta_lic", "lib/libapache.so.meta_lic", "lib/libc++.so.meta_lic"},
+ expectedOut: []projectShare{
+ {
+ project: "bin/threelibraries",
+ conditions: []string{"restricted"},
+ },
+ {
+ project: "container/zip",
+ conditions: []string{"restricted"},
+ },
+ {
+ project: "lib/apache",
+ conditions: []string{"restricted"},
+ },
+ {
+ project: "lib/c++",
+ conditions: []string{"restricted"},
+ },
+ },
+ },
+ {
+ condition: "regressgpl2",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []projectShare{
+ {
+ project: "bin/threelibraries",
+ conditions: []string{"restricted"},
+ },
+ {
+ project: "container/zip",
+ conditions: []string{"restricted"},
+ },
+ {
+ project: "lib/apache",
+ conditions: []string{"restricted"},
+ },
+ {
+ project: "lib/c++",
+ conditions: []string{"restricted"},
+ },
+ {
+ project: "lib/gpl",
+ conditions: []string{"restricted"},
+ },
+ },
+ },
+ {
+ condition: "regressgpl2",
+ name: "containerplus",
+ roots: []string{"container.zip.meta_lic", "lib/libapache.so.meta_lic", "lib/libc++.so.meta_lic"},
+ expectedOut: []projectShare{
+ {
+ project: "bin/threelibraries",
+ conditions: []string{"restricted"},
+ },
+ {
+ project: "container/zip",
+ conditions: []string{"restricted"},
+ },
+ {
+ project: "lib/apache",
+ conditions: []string{"restricted"},
+ },
+ {
+ project: "lib/c++",
+ conditions: []string{"restricted"},
+ },
+ {
+ project: "lib/gpl",
+ conditions: []string{"restricted"},
+ },
+ },
+ },
+ }
+ 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(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/rtrace/rtrace.go b/tools/compliance/cmd/rtrace/rtrace.go
new file mode 100644
index 0000000..7c63979
--- /dev/null
+++ b/tools/compliance/cmd/rtrace/rtrace.go
@@ -0,0 +1,198 @@
+// 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 (
+ "flag"
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+ "sort"
+ "strings"
+
+ "android/soong/tools/compliance"
+)
+
+var (
+ sources = newMultiString("rtrace", "Projects or metadata files to trace back from. (required; multiple allowed)")
+ stripPrefix = newMultiString("strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)")
+
+ failNoneRequested = fmt.Errorf("\nNo license metadata files requested")
+ failNoSources = fmt.Errorf("\nNo projects or metadata files to trace back from")
+ failNoLicenses = fmt.Errorf("No licenses found")
+)
+
+type context struct {
+ sources []string
+ stripPrefix []string
+}
+
+func (ctx context) strip(installPath string) string {
+ for _, prefix := range ctx.stripPrefix {
+ if strings.HasPrefix(installPath, prefix) {
+ p := strings.TrimPrefix(installPath, prefix)
+ if 0 == len(p) {
+ continue
+ }
+ return p
+ }
+ }
+ return installPath
+}
+
+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
+resolution for the union of the conditions. Otherwise, outputs the
+resolution for all conditions.
+
+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)
+ }
+
+ if len(*sources) == 0 {
+ flag.Usage()
+ fmt.Fprintf(os.Stderr, "\nMust specify at least 1 --rtrace source.\n")
+ os.Exit(2)
+ }
+
+ ctx := &context{
+ sources: *sources,
+ stripPrefix: *stripPrefix,
+ }
+ _, err := traceRestricted(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)
+}
+
+// traceRestricted implements the rtrace utility.
+func traceRestricted(ctx *context, stdout, stderr io.Writer, files ...string) (*compliance.LicenseGraph, error) {
+ if len(files) < 1 {
+ return nil, failNoneRequested
+ }
+
+ if len(ctx.sources) < 1 {
+ return nil, failNoSources
+ }
+
+ // Read the license graph from the license metadata files (*.meta_lic).
+ licenseGraph, err := compliance.ReadLicenseGraph(os.DirFS("."), stderr, files)
+ if err != nil {
+ return nil, fmt.Errorf("Unable to read license metadata file(s) %q: %v\n", files, err)
+ }
+ if licenseGraph == nil {
+ return nil, failNoLicenses
+ }
+
+ sourceMap := make(map[string]struct{})
+ for _, source := range ctx.sources {
+ sourceMap[source] = struct{}{}
+ }
+
+ compliance.TraceTopDownConditions(licenseGraph, func(tn *compliance.TargetNode) compliance.LicenseConditionSet {
+ if _, isPresent := sourceMap[tn.Name()]; isPresent {
+ return compliance.ImpliesRestricted
+ }
+ for _, project := range tn.Projects() {
+ if _, isPresent := sourceMap[project]; isPresent {
+ return compliance.ImpliesRestricted
+ }
+ }
+ return compliance.NewLicenseConditionSet()
+ })
+
+ // targetOut calculates the string to output for `target` adding `sep`-separated conditions as needed.
+ targetOut := func(target *compliance.TargetNode, sep string) string {
+ tOut := ctx.strip(target.Name())
+ return tOut
+ }
+
+ // outputResolution prints a resolution in the requested format to `stdout`, where one can read
+ // a resolution as `tname` resolves conditions named in `cnames`.
+ // `tname` is the name of the target the resolution traces back to.
+ // `cnames` is the list of conditions to resolve.
+ outputResolution := func(tname string, cnames []string) {
+ // ... one edge per line with names in a colon-separated tuple.
+ fmt.Fprintf(stdout, "%s %s\n", tname, strings.Join(cnames, ":"))
+ }
+
+ // Sort the resolutions by targetname for repeatability/stability.
+ actions := compliance.WalkResolutionsForCondition(licenseGraph, compliance.ImpliesShared).AllActions()
+ targets := make(compliance.TargetNodeList, 0, len(actions))
+ for tn := range actions {
+ if tn.LicenseConditions().MatchesAnySet(compliance.ImpliesRestricted) {
+ targets = append(targets, tn)
+ }
+ }
+ sort.Sort(targets)
+
+ // Output the sorted targets.
+ for _, target := range targets {
+ var tname string
+ tname = targetOut(target, ":")
+
+ // cnames accumulates the list of condition names originating at a single origin that apply to `target`.
+ cnames := target.LicenseConditions().Names()
+
+ // Output 1 line for each attachesTo+actsOn combination.
+ outputResolution(tname, cnames)
+ }
+ fmt.Fprintf(stdout, "restricted conditions trace to %d targets\n", len(targets))
+ if 0 == len(targets) {
+ fmt.Fprintln(stdout, " (check for typos in project names or metadata files)")
+ }
+ return licenseGraph, nil
+}
diff --git a/tools/compliance/cmd/rtrace/rtrace_test.go b/tools/compliance/cmd/rtrace/rtrace_test.go
new file mode 100644
index 0000000..a8eb884
--- /dev/null
+++ b/tools/compliance/cmd/rtrace/rtrace_test.go
@@ -0,0 +1,316 @@
+// 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"
+ "os"
+ "strings"
+ "testing"
+)
+
+func TestMain(m *testing.M) {
+ // Change into the parent directory before running the tests
+ // so they can find the testdata directory.
+ if err := os.Chdir(".."); err != nil {
+ fmt.Printf("failed to change to testdata directory: %s\n", err)
+ os.Exit(1)
+ }
+ os.Exit(m.Run())
+}
+
+func Test_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{},
+ },
+ {
+ condition: "firstparty",
+ name: "apex_trimmed",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{
+ sources: []string{"testdata/firstparty/bin/bin1.meta_lic"},
+ stripPrefix: []string{"testdata/firstparty/"},
+ },
+ expectedOut: []string{},
+ },
+ {
+ condition: "firstparty",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []string{},
+ },
+ {
+ condition: "firstparty",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []string{},
+ },
+ {
+ condition: "firstparty",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ expectedOut: []string{},
+ },
+ {
+ 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{},
+ },
+ {
+ condition: "notice",
+ name: "apex_trimmed",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{
+ sources: []string{"testdata/notice/bin/bin1.meta_lic"},
+ stripPrefix: []string{"testdata/notice/"},
+ },
+ expectedOut: []string{},
+ },
+ {
+ condition: "notice",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []string{},
+ },
+ {
+ condition: "notice",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []string{},
+ },
+ {
+ condition: "notice",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ expectedOut: []string{},
+ },
+ {
+ 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{},
+ },
+ {
+ condition: "reciprocal",
+ name: "apex_trimmed",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{
+ sources: []string{"testdata/reciprocal/bin/bin1.meta_lic"},
+ stripPrefix: []string{"testdata/reciprocal/"},
+ },
+ expectedOut: []string{},
+ },
+ {
+ condition: "reciprocal",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []string{},
+ },
+ {
+ condition: "reciprocal",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []string{},
+ },
+ {
+ condition: "reciprocal",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ expectedOut: []string{},
+ },
+ {
+ 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/lib/liba.so.meta_lic restricted_allows_dynamic_linking",
+ "testdata/restricted/lib/libb.so.meta_lic restricted",
+ },
+ },
+ {
+ condition: "restricted",
+ name: "apex_trimmed_bin1",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{
+ sources: []string{"testdata/restricted/bin/bin1.meta_lic"},
+ stripPrefix: []string{"testdata/restricted/"},
+ },
+ expectedOut: []string{"lib/liba.so.meta_lic restricted_allows_dynamic_linking"},
+ },
+ {
+ condition: "restricted",
+ name: "apex_trimmed_bin2",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{
+ sources: []string{"testdata/restricted/bin/bin2.meta_lic"},
+ stripPrefix: []string{"testdata/restricted/"},
+ },
+ expectedOut: []string{"lib/libb.so.meta_lic restricted"},
+ },
+ {
+ condition: "restricted",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []string{
+ "testdata/restricted/lib/liba.so.meta_lic restricted_allows_dynamic_linking",
+ "testdata/restricted/lib/libb.so.meta_lic restricted",
+ },
+ },
+ {
+ condition: "restricted",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []string{"testdata/restricted/lib/liba.so.meta_lic restricted_allows_dynamic_linking"},
+ },
+ {
+ condition: "restricted",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ expectedOut: []string{"testdata/restricted/lib/liba.so.meta_lic restricted_allows_dynamic_linking"},
+ },
+ {
+ 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/lib/libb.so.meta_lic restricted"},
+ },
+ {
+ condition: "proprietary",
+ name: "apex_trimmed_bin1",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{
+ sources: []string{"testdata/proprietary/bin/bin1.meta_lic"},
+ stripPrefix: []string{"testdata/proprietary/"},
+ },
+ expectedOut: []string{},
+ },
+ {
+ condition: "proprietary",
+ name: "apex_trimmed_bin2",
+ roots: []string{"highest.apex.meta_lic"},
+ ctx: context{
+ sources: []string{"testdata/proprietary/bin/bin2.meta_lic"},
+ stripPrefix: []string{"testdata/proprietary/"},
+ },
+ expectedOut: []string{"lib/libb.so.meta_lic restricted"},
+ },
+ {
+ condition: "proprietary",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []string{"testdata/proprietary/lib/libb.so.meta_lic restricted"},
+ },
+ {
+ condition: "proprietary",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []string{},
+ },
+ {
+ condition: "proprietary",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ expectedOut: []string{},
+ },
+ {
+ 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")
+ }
+ fmt.Fprintf(expectedOut, "restricted conditions trace to %d targets\n", len(tt.expectedOut))
+ if 0 == len(tt.expectedOut) {
+ fmt.Fprintln(expectedOut, " (check for typos in project names or metadata files)")
+ }
+
+ 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)
+ }
+ if len(tt.ctx.sources) < 1 {
+ tt.ctx.sources = rootFiles
+ }
+ _, err := traceRestricted(&tt.ctx, stdout, stderr, rootFiles...)
+ t.Logf("rtrace: stderr = %v", stderr)
+ t.Logf("rtrace: stdout = %v", stdout)
+ if err != nil {
+ t.Fatalf("rtrace: error = %v", err)
+ return
+ }
+ if stderr.Len() > 0 {
+ t.Errorf("rtrace: 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 startLine < len(outList) && startLine < len(expectedList) && outList[startLine] == expectedList[startLine] {
+ startLine++
+ }
+ t.Errorf("rtrace: 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/shippedlibs/shippedlibs.go b/tools/compliance/cmd/shippedlibs/shippedlibs.go
new file mode 100644
index 0000000..fddc489
--- /dev/null
+++ b/tools/compliance/cmd/shippedlibs/shippedlibs.go
@@ -0,0 +1,138 @@
+// 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"
+ "flag"
+ "fmt"
+ "io"
+ "io/fs"
+ "os"
+ "path/filepath"
+
+ "android/soong/tools/compliance"
+)
+
+var (
+ outputFile = flag.String("o", "-", "Where to write the library list. (default stdout)")
+
+ failNoneRequested = fmt.Errorf("\nNo license metadata files requested")
+ failNoLicenses = fmt.Errorf("No licenses found")
+)
+
+type context struct {
+ stdout io.Writer
+ stderr io.Writer
+ rootFS fs.FS
+}
+
+func init() {
+ flag.Usage = func() {
+ fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...}
+
+Outputs a list of libraries used in the shipped images.
+
+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)
+ }
+
+ if len(*outputFile) == 0 {
+ flag.Usage()
+ fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n")
+ os.Exit(2)
+ } else {
+ dir, err := filepath.Abs(filepath.Dir(*outputFile))
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err)
+ os.Exit(1)
+ }
+ fi, err := os.Stat(dir)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err)
+ os.Exit(1)
+ }
+ if !fi.IsDir() {
+ fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile)
+ os.Exit(1)
+ }
+ }
+
+ var ofile io.Writer
+ ofile = os.Stdout
+ if *outputFile != "-" {
+ ofile = &bytes.Buffer{}
+ }
+
+ ctx := &context{ofile, os.Stderr, os.DirFS(".")}
+
+ err := shippedLibs(ctx, flag.Args()...)
+ if err != nil {
+ if err == failNoneRequested {
+ flag.Usage()
+ }
+ fmt.Fprintf(os.Stderr, "%s\n", err.Error())
+ os.Exit(1)
+ }
+ if *outputFile != "-" {
+ err := os.WriteFile(*outputFile, ofile.(*bytes.Buffer).Bytes(), 0666)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "could not write output to %q: %s\n", *outputFile, err)
+ os.Exit(1)
+ }
+ }
+ os.Exit(0)
+}
+
+// shippedLibs implements the shippedlibs utility.
+func shippedLibs(ctx *context, 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(ctx.rootFS, ctx.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
+ }
+
+ // rs contains all notice resolutions.
+ rs := compliance.ResolveNotices(licenseGraph)
+
+ ni, err := compliance.IndexLicenseTexts(ctx.rootFS, licenseGraph, rs)
+ if err != nil {
+ return fmt.Errorf("Unable to read license text file(s) for %q: %v\n", files, err)
+ }
+
+ for lib := range ni.Libraries() {
+ fmt.Fprintln(ctx.stdout, lib)
+ }
+ return nil
+}
diff --git a/tools/compliance/cmd/shippedlibs/shippedlibs_test.go b/tools/compliance/cmd/shippedlibs/shippedlibs_test.go
new file mode 100644
index 0000000..b6aad6d
--- /dev/null
+++ b/tools/compliance/cmd/shippedlibs/shippedlibs_test.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 main
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "os"
+ "strings"
+ "testing"
+)
+
+func TestMain(m *testing.M) {
+ // Change into the parent directory before running the tests
+ // so they can find the testdata directory.
+ if err := os.Chdir(".."); err != nil {
+ fmt.Printf("failed to change to testdata directory: %s\n", err)
+ os.Exit(1)
+ }
+ os.Exit(m.Run())
+}
+
+func Test(t *testing.T) {
+ tests := []struct {
+ condition string
+ name string
+ roots []string
+ expectedOut []string
+ }{
+ {
+ condition: "firstparty",
+ name: "apex",
+ roots: []string{"highest.apex.meta_lic"},
+ expectedOut: []string{"Android"},
+ },
+ {
+ condition: "firstparty",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []string{"Android"},
+ },
+ {
+ condition: "firstparty",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []string{"Android"},
+ },
+ {
+ condition: "firstparty",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ expectedOut: []string{"Android"},
+ },
+ {
+ condition: "firstparty",
+ name: "library",
+ roots: []string{"lib/libd.so.meta_lic"},
+ expectedOut: []string{"Android"},
+ },
+ {
+ condition: "notice",
+ name: "apex",
+ roots: []string{"highest.apex.meta_lic"},
+ expectedOut: []string{"Android", "Device", "External"},
+ },
+ {
+ condition: "notice",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []string{"Android", "Device", "External"},
+ },
+ {
+ condition: "notice",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []string{"Android", "Device"},
+ },
+ {
+ condition: "notice",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ expectedOut: []string{"Android", "Device", "External"},
+ },
+ {
+ condition: "notice",
+ name: "library",
+ roots: []string{"lib/libd.so.meta_lic"},
+ expectedOut: []string{"External"},
+ },
+ {
+ condition: "reciprocal",
+ name: "apex",
+ roots: []string{"highest.apex.meta_lic"},
+ expectedOut: []string{"Android", "Device", "External"},
+ },
+ {
+ condition: "reciprocal",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []string{"Android", "Device", "External"},
+ },
+ {
+ condition: "reciprocal",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []string{"Android", "Device"},
+ },
+ {
+ condition: "reciprocal",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ expectedOut: []string{"Android", "Device", "External"},
+ },
+ {
+ condition: "reciprocal",
+ name: "library",
+ roots: []string{"lib/libd.so.meta_lic"},
+ expectedOut: []string{"External"},
+ },
+ {
+ condition: "restricted",
+ name: "apex",
+ roots: []string{"highest.apex.meta_lic"},
+ expectedOut: []string{"Android", "Device", "External"},
+ },
+ {
+ condition: "restricted",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []string{"Android", "Device", "External"},
+ },
+ {
+ condition: "restricted",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []string{"Android", "Device"},
+ },
+ {
+ condition: "restricted",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ expectedOut: []string{"Android", "Device", "External"},
+ },
+ {
+ condition: "restricted",
+ name: "library",
+ roots: []string{"lib/libd.so.meta_lic"},
+ expectedOut: []string{"External"},
+ },
+ {
+ condition: "proprietary",
+ name: "apex",
+ roots: []string{"highest.apex.meta_lic"},
+ expectedOut: []string{"Android", "Device", "External"},
+ },
+ {
+ condition: "proprietary",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []string{"Android", "Device", "External"},
+ },
+ {
+ condition: "proprietary",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []string{"Android", "Device"},
+ },
+ {
+ condition: "proprietary",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ expectedOut: []string{"Android", "Device", "External"},
+ },
+ {
+ condition: "proprietary",
+ name: "library",
+ roots: []string{"lib/libd.so.meta_lic"},
+ expectedOut: []string{"External"},
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.condition+" "+tt.name, func(t *testing.T) {
+ stdout := &bytes.Buffer{}
+ stderr := &bytes.Buffer{}
+
+ rootFiles := make([]string, 0, len(tt.roots))
+ for _, r := range tt.roots {
+ rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
+ }
+
+ ctx := context{stdout, stderr, os.DirFS(".")}
+
+ err := shippedLibs(&ctx, rootFiles...)
+ if err != nil {
+ t.Fatalf("shippedLibs: error = %v, stderr = %v", err, stderr)
+ return
+ }
+ if stderr.Len() > 0 {
+ t.Errorf("shippedLibs: gotStderr = %v, want none", stderr)
+ }
+
+ t.Logf("got stdout: %s", stdout.String())
+
+ t.Logf("want stdout: %s", strings.Join(tt.expectedOut, "\n"))
+
+ out := bufio.NewScanner(stdout)
+ lineno := 0
+ for out.Scan() {
+ line := out.Text()
+ if strings.TrimLeft(line, " ") == "" {
+ continue
+ }
+ if len(tt.expectedOut) <= lineno {
+ t.Errorf("shippedLibs: unexpected output at line %d: got %q, want nothing (wanted %d lines)", lineno+1, line, len(tt.expectedOut))
+ } else if tt.expectedOut[lineno] != line {
+ t.Errorf("shippedLibs: unexpected output at line %d: got %q, want %q", lineno+1, line, tt.expectedOut[lineno])
+ }
+ lineno++
+ }
+ for ; lineno < len(tt.expectedOut); lineno++ {
+ t.Errorf("shippedLibs: missing output line %d: ended early, want %q", lineno+1, tt.expectedOut[lineno])
+ }
+ })
+ }
+}
diff --git a/tools/compliance/cmd/testdata/README.md b/tools/compliance/cmd/testdata/README.md
new file mode 100644
index 0000000..07564c2
--- /dev/null
+++ b/tools/compliance/cmd/testdata/README.md
@@ -0,0 +1,326 @@
+## Test data
+
+Each non-regression directory under testdata/ defines a similar build graph.
+All have the same structure, but different versions of the graph have different
+license metadata.
+
+The regression* directories can have whatever structure is required for the
+specific test case.
+
+### 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
+
+```dot
+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/FIRST_PARTY_LICENSE b/tools/compliance/cmd/testdata/firstparty/FIRST_PARTY_LICENSE
new file mode 100644
index 0000000..a7e7e64
--- /dev/null
+++ b/tools/compliance/cmd/testdata/firstparty/FIRST_PARTY_LICENSE
@@ -0,0 +1 @@
+&&&First Party License&&&
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..ac3338f
--- /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: "testdata/firstparty/FIRST_PARTY_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..3007129
--- /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: "testdata/firstparty/FIRST_PARTY_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..89bc6a4
--- /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: "testdata/firstparty/FIRST_PARTY_LICENSE"
+is_container: false
+built: "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin2"
+installed: "out/target/product/fictional/system/bin/bin2"
+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..a81c764
--- /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: "testdata/firstparty/FIRST_PARTY_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..9f6a679
--- /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: "testdata/firstparty/FIRST_PARTY_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..abad5f1
--- /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: "testdata/firstparty/FIRST_PARTY_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..2985719
--- /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: "testdata/firstparty/FIRST_PARTY_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..e60ef73
--- /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: "testdata/firstparty/FIRST_PARTY_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..24d3f0d
--- /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: "testdata/firstparty/FIRST_PARTY_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..f7e537c
--- /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: "testdata/firstparty/FIRST_PARTY_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/NOTICE_LICENSE b/tools/compliance/cmd/testdata/notice/NOTICE_LICENSE
new file mode 100644
index 0000000..752b249
--- /dev/null
+++ b/tools/compliance/cmd/testdata/notice/NOTICE_LICENSE
@@ -0,0 +1 @@
+%%%Notice License%%%
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..8ce0a98
--- /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: "testdata/firstparty/FIRST_PARTY_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..6d173a4
--- /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: "testdata/firstparty/FIRST_PARTY_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..a9e9c71
--- /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: "testdata/firstparty/FIRST_PARTY_LICENSE"
+is_container: false
+built: "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin2"
+installed: "out/target/product/fictional/system/bin/bin2"
+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..bb9a3d5
--- /dev/null
+++ b/tools/compliance/cmd/testdata/notice/bin/bin3.meta_lic
@@ -0,0 +1,9 @@
+package_name: "Compiler"
+module_classes: "EXECUTABLES"
+projects: "standalone/binary"
+license_kinds: "SPDX-license-identifier-NCSA"
+license_conditions: "notice"
+license_texts: "testdata/notice/NOTICE_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/notice/container.zip.meta_lic b/tools/compliance/cmd/testdata/notice/container.zip.meta_lic
new file mode 100644
index 0000000..e9c0511
--- /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: "testdata/firstparty/FIRST_PARTY_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..2abb76e
--- /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: "testdata/firstparty/FIRST_PARTY_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..7fed5d7
--- /dev/null
+++ b/tools/compliance/cmd/testdata/notice/lib/liba.so.meta_lic
@@ -0,0 +1,9 @@
+package_name: "Device"
+projects: "device/library"
+license_kinds: "SPDX-license-identifier-BSD"
+license_conditions: "notice"
+license_texts: "testdata/notice/NOTICE_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/notice/lib/libb.so.meta_lic b/tools/compliance/cmd/testdata/notice/lib/libb.so.meta_lic
new file mode 100644
index 0000000..e60ef73
--- /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: "testdata/firstparty/FIRST_PARTY_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..8dbc41b
--- /dev/null
+++ b/tools/compliance/cmd/testdata/notice/lib/libc.a.meta_lic
@@ -0,0 +1,7 @@
+package_name: "External"
+projects: "static/library"
+license_kinds: "SPDX-license-identifier-MIT"
+license_conditions: "notice"
+license_texts: "testdata/notice/NOTICE_LICENSE"
+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..e6a060c
--- /dev/null
+++ b/tools/compliance/cmd/testdata/notice/lib/libd.so.meta_lic
@@ -0,0 +1,8 @@
+package_name: "External"
+projects: "dynamic/library"
+license_kinds: "SPDX-license-identifier-MIT"
+license_conditions: "notice"
+license_texts: "testdata/notice/NOTICE_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/proprietary/PROPRIETARY_LICENSE b/tools/compliance/cmd/testdata/proprietary/PROPRIETARY_LICENSE
new file mode 100644
index 0000000..5d0eb09
--- /dev/null
+++ b/tools/compliance/cmd/testdata/proprietary/PROPRIETARY_LICENSE
@@ -0,0 +1 @@
+@@@Proprietary License@@@
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..f307c5c
--- /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: "testdata/firstparty/FIRST_PARTY_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..e0394da
--- /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: "testdata/firstparty/FIRST_PARTY_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..da64aa6
--- /dev/null
+++ b/tools/compliance/cmd/testdata/proprietary/bin/bin2.meta_lic
@@ -0,0 +1,20 @@
+package_name: "Android"
+module_classes: "EXECUTABLES"
+projects: "dynamic/binary"
+license_kinds: "legacy_proprietary"
+license_conditions: "proprietary"
+license_conditions: "by_exception_only"
+license_texts: "testdata/proprietary/PROPRIETARY_LICENSE"
+is_container: false
+built: "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin2"
+installed: "out/target/product/fictional/system/bin/bin2"
+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..7ef14e9
--- /dev/null
+++ b/tools/compliance/cmd/testdata/proprietary/bin/bin3.meta_lic
@@ -0,0 +1,9 @@
+package_name: "Compiler"
+module_classes: "EXECUTABLES"
+projects: "standalone/binary"
+license_kinds: "SPDX-license-identifier-LGPL-2.0"
+license_conditions: "restricted"
+license_texts: "testdata/restricted/RESTRICTED_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/proprietary/container.zip.meta_lic b/tools/compliance/cmd/testdata/proprietary/container.zip.meta_lic
new file mode 100644
index 0000000..d6605f4
--- /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: "testdata/firstparty/FIRST_PARTY_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..27ced10
--- /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: "testdata/firstparty/FIRST_PARTY_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..ceb0f9f
--- /dev/null
+++ b/tools/compliance/cmd/testdata/proprietary/lib/liba.so.meta_lic
@@ -0,0 +1,10 @@
+package_name: "Device"
+projects: "device/library"
+license_kinds: "legacy_proprietary"
+license_conditions: "proprietary"
+license_conditions: "by_exception_only"
+license_texts: "testdata/proprietary/PROPRIETARY_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/proprietary/lib/libb.so.meta_lic b/tools/compliance/cmd/testdata/proprietary/lib/libb.so.meta_lic
new file mode 100644
index 0000000..739d357
--- /dev/null
+++ b/tools/compliance/cmd/testdata/proprietary/lib/libb.so.meta_lic
@@ -0,0 +1,9 @@
+package_name: "Android"
+projects: "base/library"
+license_kinds: "SPDX-license-identifier-GPL-2.0"
+license_conditions: "restricted"
+license_texts: "testdata/restricted/RESTRICTED_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/proprietary/lib/libc.a.meta_lic b/tools/compliance/cmd/testdata/proprietary/lib/libc.a.meta_lic
new file mode 100644
index 0000000..5440ea7
--- /dev/null
+++ b/tools/compliance/cmd/testdata/proprietary/lib/libc.a.meta_lic
@@ -0,0 +1,8 @@
+package_name: "External"
+projects: "static/library"
+license_kinds: "legacy_proprietary"
+license_conditions: "proprietary"
+license_conditions: "by_exception_only"
+license_texts: "testdata/proprietary/PROPRIETARY_LICENSE"
+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..e6a060c
--- /dev/null
+++ b/tools/compliance/cmd/testdata/proprietary/lib/libd.so.meta_lic
@@ -0,0 +1,8 @@
+package_name: "External"
+projects: "dynamic/library"
+license_kinds: "SPDX-license-identifier-MIT"
+license_conditions: "notice"
+license_texts: "testdata/notice/NOTICE_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/reciprocal/RECIPROCAL_LICENSE b/tools/compliance/cmd/testdata/reciprocal/RECIPROCAL_LICENSE
new file mode 100644
index 0000000..82c2019
--- /dev/null
+++ b/tools/compliance/cmd/testdata/reciprocal/RECIPROCAL_LICENSE
@@ -0,0 +1 @@
+$$$Reciprocal License$$$
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..60233cb
--- /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: "testdata/firstparty/FIRST_PARTY_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..54d552f
--- /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: "testdata/firstparty/FIRST_PARTY_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..a28cb91
--- /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: "testdata/firstparty/FIRST_PARTY_LICENSE"
+is_container: false
+built: "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin2"
+installed: "out/target/product/fictional/system/bin/bin2"
+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..bb9a3d5
--- /dev/null
+++ b/tools/compliance/cmd/testdata/reciprocal/bin/bin3.meta_lic
@@ -0,0 +1,9 @@
+package_name: "Compiler"
+module_classes: "EXECUTABLES"
+projects: "standalone/binary"
+license_kinds: "SPDX-license-identifier-NCSA"
+license_conditions: "notice"
+license_texts: "testdata/notice/NOTICE_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/reciprocal/container.zip.meta_lic b/tools/compliance/cmd/testdata/reciprocal/container.zip.meta_lic
new file mode 100644
index 0000000..feb08fe
--- /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: "testdata/firstparty/FIRST_PARTY_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..185d04a
--- /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: "testdata/firstparty/FIRST_PARTY_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..dd05155
--- /dev/null
+++ b/tools/compliance/cmd/testdata/reciprocal/lib/liba.so.meta_lic
@@ -0,0 +1,9 @@
+package_name: "Device"
+projects: "device/library"
+license_kinds: "SPDX-license-identifier-MPL"
+license_conditions: "reciprocal"
+license_texts: "testdata/reciprocal/RECIPROCAL_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/reciprocal/lib/libb.so.meta_lic b/tools/compliance/cmd/testdata/reciprocal/lib/libb.so.meta_lic
new file mode 100644
index 0000000..e60ef73
--- /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: "testdata/firstparty/FIRST_PARTY_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..f794305
--- /dev/null
+++ b/tools/compliance/cmd/testdata/reciprocal/lib/libc.a.meta_lic
@@ -0,0 +1,7 @@
+package_name: "External"
+projects: "static/library"
+license_kinds: "SPDX-license-identifier-MPL"
+license_conditions: "reciprocal"
+license_texts: "testdata/reciprocal/RECIPROCAL_LICENSE"
+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..e6a060c
--- /dev/null
+++ b/tools/compliance/cmd/testdata/reciprocal/lib/libd.so.meta_lic
@@ -0,0 +1,8 @@
+package_name: "External"
+projects: "dynamic/library"
+license_kinds: "SPDX-license-identifier-MIT"
+license_conditions: "notice"
+license_texts: "testdata/notice/NOTICE_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/regressgpl1/README.md b/tools/compliance/cmd/testdata/regressgpl1/README.md
new file mode 100644
index 0000000..6ab872d
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl1/README.md
@@ -0,0 +1,32 @@
+## Shipped versus non-shipped libraries with restricted license
+
+### Testdata build graph structure:
+
+A restricted licensed library sandwiched between a notice library and a notice
+binary. The source-code for the libraries only needs to be shared if shipped
+alongside the container with the binaries.
+
+```dot
+strict digraph {
+ rankdir=LR;
+ bin1 [label="bin/bin1.meta_lic\nnotice"];
+ bin2 [label="bin/bin2.meta_lic\nnotice"];
+ bin3 [label="bin/bin3.meta_lic\nnotice"];
+ container [label="container.zip.meta_lic\nnotice"];
+ libapache [label="lib/libapache.so.meta_lic\nnotice"];
+ libcxx [label="lib/libc++.so.meta_lic\nnotice"];
+ libgpl [label="lib/libgpl.so.meta_lic\nrestricted"];
+ container -> bin1[label="static"];
+ container -> bin2 [label="static"];
+ container -> bin3 [label="static"];
+ bin1 -> libcxx [label="dynamic"];
+ bin2 -> libapache [label="dynamic"];
+ bin2 -> libcxx [label="dynamic"];
+ bin3 -> libapache [label="dynamic"];
+ bin3 -> libcxx [label="dynamic"];
+ bin3 -> libgpl [label="dynamic"];
+ libapache -> libcxx [label="dynamic"];
+ libgpl -> libcxx [label="dynamic"];
+ {rank=same; container}
+}
+```
diff --git a/tools/compliance/cmd/testdata/regressgpl1/bin/bin1.meta_lic b/tools/compliance/cmd/testdata/regressgpl1/bin/bin1.meta_lic
new file mode 100644
index 0000000..4afd240
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl1/bin/bin1.meta_lic
@@ -0,0 +1,14 @@
+package_name: "Android"
+module_classes: "EXECUTABLES"
+projects: "bin/onelibrary"
+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/libc++.so"
+deps: {
+ file: "testdata/regressgpl1/lib/libc++.so.meta_lic"
+ annotations: "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/regressgpl1/bin/bin2.meta_lic b/tools/compliance/cmd/testdata/regressgpl1/bin/bin2.meta_lic
new file mode 100644
index 0000000..4e56fa3
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl1/bin/bin2.meta_lic
@@ -0,0 +1,19 @@
+package_name: "Android"
+module_classes: "EXECUTABLES"
+projects: "bin/twolibraries"
+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/bin2"
+installed: "out/target/product/fictional/system/bin/bin2"
+sources: "out/target/product/fictional/system/lib/libc++.so"
+sources: "out/target/product/fictional/system/lib/libapache.so"
+deps: {
+ file: "testdata/regressgpl1/lib/libc++.so.meta_lic"
+ annotations: "dynamic"
+}
+deps: {
+ file: "testdata/regressgpl1/lib/libapache.so.meta_lic"
+ annotations: "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/regressgpl1/bin/bin3.meta_lic b/tools/compliance/cmd/testdata/regressgpl1/bin/bin3.meta_lic
new file mode 100644
index 0000000..16290d3
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl1/bin/bin3.meta_lic
@@ -0,0 +1,23 @@
+package_name: "Compiler"
+module_classes: "EXECUTABLES"
+projects: "bin/threelibraries"
+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"
+sources: "out/target/product/fictional/system/lib/libc++.so"
+sources: "out/target/product/fictional/system/lib/libapache.so"
+sources: "out/target/product/fictional/system/lib/libgpl.so"
+deps: {
+ file: "testdata/regressgpl1/lib/libc++.so.meta_lic"
+ annotations: "dynamic"
+}
+deps: {
+ file: "testdata/regressgpl1/lib/libapache.so.meta_lic"
+ annotations: "dynamic"
+}
+deps: {
+ file: "testdata/regressgpl1/lib/libgpl.so.meta_lic"
+ annotations: "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/regressgpl1/container.zip.meta_lic b/tools/compliance/cmd/testdata/regressgpl1/container.zip.meta_lic
new file mode 100644
index 0000000..21b6d5a
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl1/container.zip.meta_lic
@@ -0,0 +1,32 @@
+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/bin/bin1"
+sources: "out/target/product/fictional/system/bin/bin2"
+sources: "out/target/product/fictional/system/bin/bin3"
+sources: "out/target/product/fictional/system/lib/libapache.so"
+deps: {
+ file: "testdata/regressgpl1/bin/bin1.meta_lic"
+ annotations: "static"
+}
+deps: {
+ file: "testdata/regressgpl1/bin/bin2.meta_lic"
+ annotations: "static"
+}
+deps: {
+ file: "testdata/regressgpl1/bin/bin3.meta_lic"
+ annotations: "static"
+}
diff --git a/tools/compliance/cmd/testdata/regressgpl1/lib/libapache.so.meta_lic b/tools/compliance/cmd/testdata/regressgpl1/lib/libapache.so.meta_lic
new file mode 100644
index 0000000..9184501
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl1/lib/libapache.so.meta_lic
@@ -0,0 +1,14 @@
+package_name: "Android"
+projects: "lib/apache"
+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/libapache.so"
+built: "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libapache.a"
+installed: "out/target/product/fictional/system/lib/libapache.so"
+sources: "out/target/product/fictional/system/lib/libc++.so"
+deps: {
+ file: "testdata/regressgpl1/lib/libc++.so.meta_lic"
+ annotations: "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/regressgpl1/lib/libc++.so.meta_lic b/tools/compliance/cmd/testdata/regressgpl1/lib/libc++.so.meta_lic
new file mode 100644
index 0000000..b789377
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl1/lib/libc++.so.meta_lic
@@ -0,0 +1,8 @@
+package_name: "Device"
+projects: "lib/c++"
+license_kinds: "SPDX-license-identifier-BSD"
+license_conditions: "notice"
+is_container: false
+built: "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libc++.so"
+built: "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libc++.a"
+installed: "out/target/product/fictional/system/lib/libc++.so"
diff --git a/tools/compliance/cmd/testdata/regressgpl1/lib/libgpl.so.meta_lic b/tools/compliance/cmd/testdata/regressgpl1/lib/libgpl.so.meta_lic
new file mode 100644
index 0000000..a3afa2b
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl1/lib/libgpl.so.meta_lic
@@ -0,0 +1,12 @@
+package_name: "External"
+projects: "lib/gpl"
+license_kinds: "SPDX-license-identifier-GPL-2.0"
+license_conditions: "restricted"
+is_container: false
+built: "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libgpl.so"
+installed: "out/target/product/fictional/system/lib/libgpl.so"
+sources: "out/target/product/fictional/system/lib/libc++.so"
+deps: {
+ file: "testdata/regressgpl1/lib/libc++.so.meta_lic"
+ annotations: "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/regressgpl2/README.md b/tools/compliance/cmd/testdata/regressgpl2/README.md
new file mode 100644
index 0000000..9da0f4d
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl2/README.md
@@ -0,0 +1,35 @@
+## Libraries shipped inside container with restricted license
+
+### Testdata build graph structure:
+
+A restricted licensed library sandwiched between a notice library and a notice
+binary. The source-code for the libraries needs to be shared when shipped as
+part of the container with the binaries.
+
+```dot
+strict digraph {
+ rankdir=LR;
+ bin1 [label="bin/bin1.meta_lic\nnotice"];
+ bin2 [label="bin/bin2.meta_lic\nnotice"];
+ bin3 [label="bin/bin3.meta_lic\nnotice"];
+ container [label="container.zip.meta_lic\nnotice"];
+ libapache [label="lib/libapache.so.meta_lic\nnotice"];
+ libcxx [label="lib/libc++.so.meta_lic\nnotice"];
+ libgpl [label="lib/libgpl.so.meta_lic\nrestricted"];
+ container -> bin1[label="static"];
+ container -> bin2 [label="static"];
+ container -> bin3 [label="static"];
+ container -> libapache [label="static"];
+ container -> libcxx [label="static"];
+ container -> libgpl [label="static"];
+ bin1 -> libcxx [label="dynamic"];
+ bin2 -> libapache [label="dynamic"];
+ bin2 -> libcxx [label="dynamic"];
+ bin3 -> libapache [label="dynamic"];
+ bin3 -> libcxx [label="dynamic"];
+ bin3 -> libgpl [label="dynamic"];
+ libapache -> libcxx [label="dynamic"];
+ libgpl -> libcxx [label="dynamic"];
+ {rank=same; container}
+}
+```
diff --git a/tools/compliance/cmd/testdata/regressgpl2/bin/bin1.meta_lic b/tools/compliance/cmd/testdata/regressgpl2/bin/bin1.meta_lic
new file mode 100644
index 0000000..839fd9c
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl2/bin/bin1.meta_lic
@@ -0,0 +1,14 @@
+package_name: "Android"
+module_classes: "EXECUTABLES"
+projects: "bin/onelibrary"
+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/libc++.so"
+deps: {
+ file: "testdata/regressgpl2/lib/libc++.so.meta_lic"
+ annotations: "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/regressgpl2/bin/bin2.meta_lic b/tools/compliance/cmd/testdata/regressgpl2/bin/bin2.meta_lic
new file mode 100644
index 0000000..96baaae
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl2/bin/bin2.meta_lic
@@ -0,0 +1,19 @@
+package_name: "Android"
+module_classes: "EXECUTABLES"
+projects: "bin/twolibraries"
+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/bin2"
+installed: "out/target/product/fictional/system/bin/bin2"
+sources: "out/target/product/fictional/system/lib/libc++.so"
+sources: "out/target/product/fictional/system/lib/libapache.so"
+deps: {
+ file: "testdata/regressgpl2/lib/libc++.so.meta_lic"
+ annotations: "dynamic"
+}
+deps: {
+ file: "testdata/regressgpl2/lib/libapache.so.meta_lic"
+ annotations: "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/regressgpl2/bin/bin3.meta_lic b/tools/compliance/cmd/testdata/regressgpl2/bin/bin3.meta_lic
new file mode 100644
index 0000000..3cd8602
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl2/bin/bin3.meta_lic
@@ -0,0 +1,23 @@
+package_name: "Compiler"
+module_classes: "EXECUTABLES"
+projects: "bin/threelibraries"
+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"
+sources: "out/target/product/fictional/system/lib/libc++.so"
+sources: "out/target/product/fictional/system/lib/libapache.so"
+sources: "out/target/product/fictional/system/lib/libgpl.so"
+deps: {
+ file: "testdata/regressgpl2/lib/libc++.so.meta_lic"
+ annotations: "dynamic"
+}
+deps: {
+ file: "testdata/regressgpl2/lib/libapache.so.meta_lic"
+ annotations: "dynamic"
+}
+deps: {
+ file: "testdata/regressgpl2/lib/libgpl.so.meta_lic"
+ annotations: "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/regressgpl2/container.zip.meta_lic b/tools/compliance/cmd/testdata/regressgpl2/container.zip.meta_lic
new file mode 100644
index 0000000..d32bf94
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl2/container.zip.meta_lic
@@ -0,0 +1,44 @@
+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/bin/bin1"
+sources: "out/target/product/fictional/system/bin/bin2"
+sources: "out/target/product/fictional/system/bin/bin3"
+sources: "out/target/product/fictional/system/lib/libapache.so"
+deps: {
+ file: "testdata/regressgpl2/bin/bin1.meta_lic"
+ annotations: "static"
+}
+deps: {
+ file: "testdata/regressgpl2/bin/bin2.meta_lic"
+ annotations: "static"
+}
+deps: {
+ file: "testdata/regressgpl2/bin/bin3.meta_lic"
+ annotations: "static"
+}
+deps: {
+ file: "testdata/regressgpl2/lib/libapache.so.meta_lic"
+ annotations: "static"
+}
+deps: {
+ file: "testdata/regressgpl2/lib/libc++.so.meta_lic"
+ annotations: "static"
+}
+deps: {
+ file: "testdata/regressgpl2/lib/libgpl.so.meta_lic"
+ annotations: "static"
+}
diff --git a/tools/compliance/cmd/testdata/regressgpl2/lib/libapache.so.meta_lic b/tools/compliance/cmd/testdata/regressgpl2/lib/libapache.so.meta_lic
new file mode 100644
index 0000000..ae47340
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl2/lib/libapache.so.meta_lic
@@ -0,0 +1,14 @@
+package_name: "Android"
+projects: "lib/apache"
+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/libapache.so"
+built: "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libapache.a"
+installed: "out/target/product/fictional/system/lib/libapache.so"
+sources: "out/target/product/fictional/system/lib/libc++.so"
+deps: {
+ file: "testdata/regressgpl2/lib/libc++.so.meta_lic"
+ annotations: "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/regressgpl2/lib/libc++.so.meta_lic b/tools/compliance/cmd/testdata/regressgpl2/lib/libc++.so.meta_lic
new file mode 100644
index 0000000..b789377
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl2/lib/libc++.so.meta_lic
@@ -0,0 +1,8 @@
+package_name: "Device"
+projects: "lib/c++"
+license_kinds: "SPDX-license-identifier-BSD"
+license_conditions: "notice"
+is_container: false
+built: "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libc++.so"
+built: "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libc++.a"
+installed: "out/target/product/fictional/system/lib/libc++.so"
diff --git a/tools/compliance/cmd/testdata/regressgpl2/lib/libgpl.so.meta_lic b/tools/compliance/cmd/testdata/regressgpl2/lib/libgpl.so.meta_lic
new file mode 100644
index 0000000..4e78697
--- /dev/null
+++ b/tools/compliance/cmd/testdata/regressgpl2/lib/libgpl.so.meta_lic
@@ -0,0 +1,12 @@
+package_name: "External"
+projects: "lib/gpl"
+license_kinds: "SPDX-license-identifier-GPL-2.0"
+license_conditions: "restricted"
+is_container: false
+built: "out/target/product/fictional/obj/SHARED_LIBRARIES/lib_intermediates/libgpl.so"
+installed: "out/target/product/fictional/system/lib/libgpl.so"
+sources: "out/target/product/fictional/system/lib/libc++.so"
+deps: {
+ file: "testdata/regressgpl2/lib/libc++.so.meta_lic"
+ annotations: "dynamic"
+}
diff --git a/tools/compliance/cmd/testdata/restricted/RESTRICTED_LICENSE b/tools/compliance/cmd/testdata/restricted/RESTRICTED_LICENSE
new file mode 100644
index 0000000..16a2819
--- /dev/null
+++ b/tools/compliance/cmd/testdata/restricted/RESTRICTED_LICENSE
@@ -0,0 +1 @@
+###Restricted License###
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..7ef536d
--- /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: "testdata/firstparty/FIRST_PARTY_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..ef0d0c0
--- /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: "testdata/firstparty/FIRST_PARTY_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..331d5ac
--- /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: "testdata/firstparty/FIRST_PARTY_LICENSE"
+is_container: false
+built: "out/target/product/fictional/obj/EXECUTABLES/bin_intermediates/bin2"
+installed: "out/target/product/fictional/system/bin/bin2"
+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..7ef14e9
--- /dev/null
+++ b/tools/compliance/cmd/testdata/restricted/bin/bin3.meta_lic
@@ -0,0 +1,9 @@
+package_name: "Compiler"
+module_classes: "EXECUTABLES"
+projects: "standalone/binary"
+license_kinds: "SPDX-license-identifier-LGPL-2.0"
+license_conditions: "restricted"
+license_texts: "testdata/restricted/RESTRICTED_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/restricted/container.zip.meta_lic b/tools/compliance/cmd/testdata/restricted/container.zip.meta_lic
new file mode 100644
index 0000000..47e0e24
--- /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: "testdata/firstparty/FIRST_PARTY_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..3042309
--- /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: "testdata/firstparty/FIRST_PARTY_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..a505d4a
--- /dev/null
+++ b/tools/compliance/cmd/testdata/restricted/lib/liba.so.meta_lic
@@ -0,0 +1,9 @@
+package_name: "Device"
+projects: "device/library"
+license_kinds: "SPDX-license-identifier-LGPL-2.0"
+license_conditions: "restricted"
+license_texts: "testdata/restricted/RESTRICTED_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/restricted/lib/libb.so.meta_lic b/tools/compliance/cmd/testdata/restricted/lib/libb.so.meta_lic
new file mode 100644
index 0000000..739d357
--- /dev/null
+++ b/tools/compliance/cmd/testdata/restricted/lib/libb.so.meta_lic
@@ -0,0 +1,9 @@
+package_name: "Android"
+projects: "base/library"
+license_kinds: "SPDX-license-identifier-GPL-2.0"
+license_conditions: "restricted"
+license_texts: "testdata/restricted/RESTRICTED_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/restricted/lib/libc.a.meta_lic b/tools/compliance/cmd/testdata/restricted/lib/libc.a.meta_lic
new file mode 100644
index 0000000..f794305
--- /dev/null
+++ b/tools/compliance/cmd/testdata/restricted/lib/libc.a.meta_lic
@@ -0,0 +1,7 @@
+package_name: "External"
+projects: "static/library"
+license_kinds: "SPDX-license-identifier-MPL"
+license_conditions: "reciprocal"
+license_texts: "testdata/reciprocal/RECIPROCAL_LICENSE"
+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..e6a060c
--- /dev/null
+++ b/tools/compliance/cmd/testdata/restricted/lib/libd.so.meta_lic
@@ -0,0 +1,8 @@
+package_name: "External"
+projects: "dynamic/library"
+license_kinds: "SPDX-license-identifier-MIT"
+license_conditions: "notice"
+license_texts: "testdata/notice/NOTICE_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/textnotice/textnotice.go b/tools/compliance/cmd/textnotice/textnotice.go
new file mode 100644
index 0000000..35c5c24
--- /dev/null
+++ b/tools/compliance/cmd/textnotice/textnotice.go
@@ -0,0 +1,209 @@
+// 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"
+ "compress/gzip"
+ "flag"
+ "fmt"
+ "io"
+ "io/fs"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "android/soong/tools/compliance"
+
+ "github.com/google/blueprint/deptools"
+)
+
+var (
+ outputFile = flag.String("o", "-", "Where to write the NOTICE text file. (default stdout)")
+ depsFile = flag.String("d", "", "Where to write the deps file")
+ stripPrefix = newMultiString("strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)")
+ title = flag.String("title", "", "The title of the notice file.")
+
+ failNoneRequested = fmt.Errorf("\nNo license metadata files requested")
+ failNoLicenses = fmt.Errorf("No licenses found")
+)
+
+type context struct {
+ stdout io.Writer
+ stderr io.Writer
+ rootFS fs.FS
+ stripPrefix []string
+ title string
+ deps *[]string
+}
+
+func (ctx context) strip(installPath string) string {
+ for _, prefix := range ctx.stripPrefix {
+ if strings.HasPrefix(installPath, prefix) {
+ p := strings.TrimPrefix(installPath, prefix)
+ if 0 == len(p) {
+ p = ctx.title
+ }
+ if 0 == len(p) {
+ continue
+ }
+ return p
+ }
+ }
+ return installPath
+}
+
+func init() {
+ flag.Usage = func() {
+ fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...}
+
+Outputs a text NOTICE file.
+
+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)
+ }
+
+ if len(*outputFile) == 0 {
+ flag.Usage()
+ fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n")
+ os.Exit(2)
+ } else {
+ dir, err := filepath.Abs(filepath.Dir(*outputFile))
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err)
+ os.Exit(1)
+ }
+ fi, err := os.Stat(dir)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err)
+ os.Exit(1)
+ }
+ if !fi.IsDir() {
+ fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile)
+ os.Exit(1)
+ }
+ }
+
+ var ofile io.Writer
+ var closer io.Closer
+ ofile = os.Stdout
+ var obuf *bytes.Buffer
+ if *outputFile != "-" {
+ obuf = &bytes.Buffer{}
+ ofile = obuf
+ }
+ if strings.HasSuffix(*outputFile, ".gz") {
+ ofile, _ = gzip.NewWriterLevel(obuf, gzip.BestCompression)
+ closer = ofile.(io.Closer)
+ }
+
+ var deps []string
+
+ ctx := &context{ofile, os.Stderr, os.DirFS("."), *stripPrefix, *title, &deps}
+
+ err := textNotice(ctx, flag.Args()...)
+ if err != nil {
+ if err == failNoneRequested {
+ flag.Usage()
+ }
+ fmt.Fprintf(os.Stderr, "%s\n", err.Error())
+ os.Exit(1)
+ }
+ if closer != nil {
+ closer.Close()
+ }
+
+ if *outputFile != "-" {
+ err := os.WriteFile(*outputFile, obuf.Bytes(), 0666)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "could not write output to %q: %s\n", *outputFile, err)
+ os.Exit(1)
+ }
+ }
+ if *depsFile != "" {
+ err := deptools.WriteDepFile(*depsFile, *outputFile, deps)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "could not write deps to %q: %s\n", *depsFile, err)
+ os.Exit(1)
+ }
+ }
+ os.Exit(0)
+}
+
+// textNotice implements the textNotice utility.
+func textNotice(ctx *context, 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(ctx.rootFS, ctx.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
+ }
+
+ // rs contains all notice resolutions.
+ rs := compliance.ResolveNotices(licenseGraph)
+
+ ni, err := compliance.IndexLicenseTexts(ctx.rootFS, licenseGraph, rs)
+ if err != nil {
+ return fmt.Errorf("Unable to read license text file(s) for %q: %v\n", files, err)
+ }
+
+ for h := range ni.Hashes() {
+ fmt.Fprintln(ctx.stdout, "==============================================================================")
+ for _, libName := range ni.HashLibs(h) {
+ fmt.Fprintf(ctx.stdout, "%s used by:\n", libName)
+ for _, installPath := range ni.HashLibInstalls(h, libName) {
+ fmt.Fprintf(ctx.stdout, " %s\n", ctx.strip(installPath))
+ }
+ fmt.Fprintln(ctx.stdout)
+ }
+ ctx.stdout.Write(ni.HashText(h))
+ fmt.Fprintln(ctx.stdout)
+ }
+
+ *ctx.deps = ni.InputNoticeFiles()
+
+ return nil
+}
diff --git a/tools/compliance/cmd/textnotice/textnotice_test.go b/tools/compliance/cmd/textnotice/textnotice_test.go
new file mode 100644
index 0000000..0ed3394
--- /dev/null
+++ b/tools/compliance/cmd/textnotice/textnotice_test.go
@@ -0,0 +1,716 @@
+// 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 (
+ "bufio"
+ "bytes"
+ "fmt"
+ "os"
+ "reflect"
+ "regexp"
+ "strings"
+ "testing"
+)
+
+var (
+ horizontalRule = regexp.MustCompile("^===[=]*===$")
+)
+
+func TestMain(m *testing.M) {
+ // Change into the parent directory before running the tests
+ // so they can find the testdata directory.
+ if err := os.Chdir(".."); err != nil {
+ fmt.Printf("failed to change to testdata directory: %s\n", err)
+ os.Exit(1)
+ }
+ os.Exit(m.Run())
+}
+
+func Test(t *testing.T) {
+ tests := []struct {
+ condition string
+ name string
+ roots []string
+ stripPrefix string
+ expectedOut []matcher
+ expectedDeps []string
+ }{
+ {
+ condition: "firstparty",
+ name: "apex",
+ roots: []string{"highest.apex.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"highest.apex"},
+ usedBy{"highest.apex/bin/bin1"},
+ usedBy{"highest.apex/bin/bin2"},
+ usedBy{"highest.apex/lib/liba.so"},
+ usedBy{"highest.apex/lib/libb.so"},
+ firstParty{},
+ },
+ expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+ },
+ {
+ condition: "firstparty",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"container.zip"},
+ usedBy{"container.zip/bin1"},
+ usedBy{"container.zip/bin2"},
+ usedBy{"container.zip/liba.so"},
+ usedBy{"container.zip/libb.so"},
+ firstParty{},
+ },
+ expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+ },
+ {
+ condition: "firstparty",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"application"},
+ firstParty{},
+ },
+ expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+ },
+ {
+ condition: "firstparty",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"bin/bin1"},
+ firstParty{},
+ },
+ expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+ },
+ {
+ condition: "firstparty",
+ name: "library",
+ roots: []string{"lib/libd.so.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"lib/libd.so"},
+ firstParty{},
+ },
+ expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+ },
+ {
+ condition: "notice",
+ name: "apex",
+ roots: []string{"highest.apex.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"highest.apex"},
+ usedBy{"highest.apex/bin/bin1"},
+ usedBy{"highest.apex/bin/bin2"},
+ usedBy{"highest.apex/lib/libb.so"},
+ firstParty{},
+ hr{},
+ library{"Device"},
+ usedBy{"highest.apex/bin/bin1"},
+ usedBy{"highest.apex/lib/liba.so"},
+ library{"External"},
+ usedBy{"highest.apex/bin/bin1"},
+ notice{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/notice/NOTICE_LICENSE",
+ },
+ },
+ {
+ condition: "notice",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"container.zip"},
+ usedBy{"container.zip/bin1"},
+ usedBy{"container.zip/bin2"},
+ usedBy{"container.zip/libb.so"},
+ firstParty{},
+ hr{},
+ library{"Device"},
+ usedBy{"container.zip/bin1"},
+ usedBy{"container.zip/liba.so"},
+ library{"External"},
+ usedBy{"container.zip/bin1"},
+ notice{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/notice/NOTICE_LICENSE",
+ },
+ },
+ {
+ condition: "notice",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"application"},
+ firstParty{},
+ hr{},
+ library{"Device"},
+ usedBy{"application"},
+ notice{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/notice/NOTICE_LICENSE",
+ },
+ },
+ {
+ condition: "notice",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"bin/bin1"},
+ firstParty{},
+ hr{},
+ library{"Device"},
+ usedBy{"bin/bin1"},
+ library{"External"},
+ usedBy{"bin/bin1"},
+ notice{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/notice/NOTICE_LICENSE",
+ },
+ },
+ {
+ condition: "notice",
+ name: "library",
+ roots: []string{"lib/libd.so.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"External"},
+ usedBy{"lib/libd.so"},
+ notice{},
+ },
+ expectedDeps: []string{"testdata/notice/NOTICE_LICENSE"},
+ },
+ {
+ condition: "reciprocal",
+ name: "apex",
+ roots: []string{"highest.apex.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"highest.apex"},
+ usedBy{"highest.apex/bin/bin1"},
+ usedBy{"highest.apex/bin/bin2"},
+ usedBy{"highest.apex/lib/libb.so"},
+ firstParty{},
+ hr{},
+ library{"Device"},
+ usedBy{"highest.apex/bin/bin1"},
+ usedBy{"highest.apex/lib/liba.so"},
+ library{"External"},
+ usedBy{"highest.apex/bin/bin1"},
+ reciprocal{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/reciprocal/RECIPROCAL_LICENSE",
+ },
+ },
+ {
+ condition: "reciprocal",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"container.zip"},
+ usedBy{"container.zip/bin1"},
+ usedBy{"container.zip/bin2"},
+ usedBy{"container.zip/libb.so"},
+ firstParty{},
+ hr{},
+ library{"Device"},
+ usedBy{"container.zip/bin1"},
+ usedBy{"container.zip/liba.so"},
+ library{"External"},
+ usedBy{"container.zip/bin1"},
+ reciprocal{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/reciprocal/RECIPROCAL_LICENSE",
+ },
+ },
+ {
+ condition: "reciprocal",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"application"},
+ firstParty{},
+ hr{},
+ library{"Device"},
+ usedBy{"application"},
+ reciprocal{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/reciprocal/RECIPROCAL_LICENSE",
+ },
+ },
+ {
+ condition: "reciprocal",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"bin/bin1"},
+ firstParty{},
+ hr{},
+ library{"Device"},
+ usedBy{"bin/bin1"},
+ library{"External"},
+ usedBy{"bin/bin1"},
+ reciprocal{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/reciprocal/RECIPROCAL_LICENSE",
+ },
+ },
+ {
+ condition: "reciprocal",
+ name: "library",
+ roots: []string{"lib/libd.so.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"External"},
+ usedBy{"lib/libd.so"},
+ notice{},
+ },
+ expectedDeps: []string{
+ "testdata/notice/NOTICE_LICENSE",
+ },
+ },
+ {
+ condition: "restricted",
+ name: "apex",
+ roots: []string{"highest.apex.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"highest.apex"},
+ usedBy{"highest.apex/bin/bin1"},
+ usedBy{"highest.apex/bin/bin2"},
+ firstParty{},
+ hr{},
+ library{"Android"},
+ usedBy{"highest.apex/bin/bin2"},
+ usedBy{"highest.apex/lib/libb.so"},
+ library{"Device"},
+ usedBy{"highest.apex/bin/bin1"},
+ usedBy{"highest.apex/lib/liba.so"},
+ restricted{},
+ hr{},
+ library{"External"},
+ usedBy{"highest.apex/bin/bin1"},
+ reciprocal{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/reciprocal/RECIPROCAL_LICENSE",
+ "testdata/restricted/RESTRICTED_LICENSE",
+ },
+ },
+ {
+ condition: "restricted",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"container.zip"},
+ usedBy{"container.zip/bin1"},
+ usedBy{"container.zip/bin2"},
+ firstParty{},
+ hr{},
+ library{"Android"},
+ usedBy{"container.zip/bin2"},
+ usedBy{"container.zip/libb.so"},
+ library{"Device"},
+ usedBy{"container.zip/bin1"},
+ usedBy{"container.zip/liba.so"},
+ restricted{},
+ hr{},
+ library{"External"},
+ usedBy{"container.zip/bin1"},
+ reciprocal{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/reciprocal/RECIPROCAL_LICENSE",
+ "testdata/restricted/RESTRICTED_LICENSE",
+ },
+ },
+ {
+ condition: "restricted",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"application"},
+ firstParty{},
+ hr{},
+ library{"Device"},
+ usedBy{"application"},
+ restricted{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/restricted/RESTRICTED_LICENSE",
+ },
+ },
+ {
+ condition: "restricted",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"bin/bin1"},
+ firstParty{},
+ hr{},
+ library{"Device"},
+ usedBy{"bin/bin1"},
+ restricted{},
+ hr{},
+ library{"External"},
+ usedBy{"bin/bin1"},
+ reciprocal{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/reciprocal/RECIPROCAL_LICENSE",
+ "testdata/restricted/RESTRICTED_LICENSE",
+ },
+ },
+ {
+ condition: "restricted",
+ name: "library",
+ roots: []string{"lib/libd.so.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"External"},
+ usedBy{"lib/libd.so"},
+ notice{},
+ },
+ expectedDeps: []string{"testdata/notice/NOTICE_LICENSE"},
+ },
+ {
+ condition: "proprietary",
+ name: "apex",
+ roots: []string{"highest.apex.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"highest.apex/bin/bin2"},
+ usedBy{"highest.apex/lib/libb.so"},
+ restricted{},
+ hr{},
+ library{"Android"},
+ usedBy{"highest.apex"},
+ usedBy{"highest.apex/bin/bin1"},
+ firstParty{},
+ hr{},
+ library{"Android"},
+ usedBy{"highest.apex/bin/bin2"},
+ library{"Device"},
+ usedBy{"highest.apex/bin/bin1"},
+ usedBy{"highest.apex/lib/liba.so"},
+ library{"External"},
+ usedBy{"highest.apex/bin/bin1"},
+ proprietary{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/proprietary/PROPRIETARY_LICENSE",
+ "testdata/restricted/RESTRICTED_LICENSE",
+ },
+ },
+ {
+ condition: "proprietary",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"container.zip/bin2"},
+ usedBy{"container.zip/libb.so"},
+ restricted{},
+ hr{},
+ library{"Android"},
+ usedBy{"container.zip"},
+ usedBy{"container.zip/bin1"},
+ firstParty{},
+ hr{},
+ library{"Android"},
+ usedBy{"container.zip/bin2"},
+ library{"Device"},
+ usedBy{"container.zip/bin1"},
+ usedBy{"container.zip/liba.so"},
+ library{"External"},
+ usedBy{"container.zip/bin1"},
+ proprietary{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/proprietary/PROPRIETARY_LICENSE",
+ "testdata/restricted/RESTRICTED_LICENSE",
+ },
+ },
+ {
+ condition: "proprietary",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"application"},
+ firstParty{},
+ hr{},
+ library{"Device"},
+ usedBy{"application"},
+ proprietary{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/proprietary/PROPRIETARY_LICENSE",
+ },
+ },
+ {
+ condition: "proprietary",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"Android"},
+ usedBy{"bin/bin1"},
+ firstParty{},
+ hr{},
+ library{"Device"},
+ usedBy{"bin/bin1"},
+ library{"External"},
+ usedBy{"bin/bin1"},
+ proprietary{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/proprietary/PROPRIETARY_LICENSE",
+ },
+ },
+ {
+ condition: "proprietary",
+ name: "library",
+ roots: []string{"lib/libd.so.meta_lic"},
+ expectedOut: []matcher{
+ hr{},
+ library{"External"},
+ usedBy{"lib/libd.so"},
+ notice{},
+ },
+ expectedDeps: []string{"testdata/notice/NOTICE_LICENSE"},
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.condition+" "+tt.name, func(t *testing.T) {
+ stdout := &bytes.Buffer{}
+ stderr := &bytes.Buffer{}
+
+ rootFiles := make([]string, 0, len(tt.roots))
+ for _, r := range tt.roots {
+ rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
+ }
+
+ var deps []string
+
+ ctx := context{stdout, stderr, os.DirFS("."), []string{tt.stripPrefix}, "", &deps}
+
+ err := textNotice(&ctx, rootFiles...)
+ if err != nil {
+ t.Fatalf("textnotice: error = %v, stderr = %v", err, stderr)
+ return
+ }
+ if stderr.Len() > 0 {
+ t.Errorf("textnotice: gotStderr = %v, want none", stderr)
+ }
+
+ t.Logf("got stdout: %s", stdout.String())
+
+ t.Logf("want stdout: %s", matcherList(tt.expectedOut).String())
+
+ out := bufio.NewScanner(stdout)
+ lineno := 0
+ for out.Scan() {
+ line := out.Text()
+ if strings.TrimLeft(line, " ") == "" {
+ continue
+ }
+ if len(tt.expectedOut) <= lineno {
+ t.Errorf("unexpected output at line %d: got %q, want nothing (wanted %d lines)", lineno+1, line, len(tt.expectedOut))
+ } else if !tt.expectedOut[lineno].isMatch(line) {
+ t.Errorf("unexpected output at line %d: got %q, want %q", lineno+1, line, tt.expectedOut[lineno].String())
+ }
+ lineno++
+ }
+ for ; lineno < len(tt.expectedOut); lineno++ {
+ t.Errorf("textnotice: missing output line %d: ended early, want %q", lineno+1, tt.expectedOut[lineno].String())
+ }
+
+ t.Logf("got deps: %q", deps)
+
+ t.Logf("want deps: %q", tt.expectedDeps)
+
+ if g, w := deps, tt.expectedDeps; !reflect.DeepEqual(g, w) {
+ t.Errorf("unexpected deps, wanted:\n%s\ngot:\n%s\n",
+ strings.Join(w, "\n"), strings.Join(g, "\n"))
+ }
+ })
+ }
+}
+
+type matcher interface {
+ isMatch(line string) bool
+ String() string
+}
+
+type hr struct{}
+
+func (m hr) isMatch(line string) bool {
+ return horizontalRule.MatchString(line)
+}
+
+func (m hr) String() string {
+ return " ================================================== "
+}
+
+type library struct {
+ name string
+}
+
+func (m library) isMatch(line string) bool {
+ return strings.HasPrefix(line, m.name+" ")
+}
+
+func (m library) String() string {
+ return m.name + " used by:"
+}
+
+type usedBy struct {
+ name string
+}
+
+func (m usedBy) isMatch(line string) bool {
+ return len(line) > 0 && line[0] == ' ' && strings.HasPrefix(strings.TrimLeft(line, " "), "out/") && strings.HasSuffix(line, "/"+m.name)
+}
+
+func (m usedBy) String() string {
+ return " out/.../" + m.name
+}
+
+type firstParty struct{}
+
+func (m firstParty) isMatch(line string) bool {
+ return strings.HasPrefix(strings.TrimLeft(line, " "), "&&&First Party License&&&")
+}
+
+func (m firstParty) String() string {
+ return "&&&First Party License&&&"
+}
+
+type notice struct{}
+
+func (m notice) isMatch(line string) bool {
+ return strings.HasPrefix(strings.TrimLeft(line, " "), "%%%Notice License%%%")
+}
+
+func (m notice) String() string {
+ return "%%%Notice License%%%"
+}
+
+type reciprocal struct{}
+
+func (m reciprocal) isMatch(line string) bool {
+ return strings.HasPrefix(strings.TrimLeft(line, " "), "$$$Reciprocal License$$$")
+}
+
+func (m reciprocal) String() string {
+ return "$$$Reciprocal License$$$"
+}
+
+type restricted struct{}
+
+func (m restricted) isMatch(line string) bool {
+ return strings.HasPrefix(strings.TrimLeft(line, " "), "###Restricted License###")
+}
+
+func (m restricted) String() string {
+ return "###Restricted License###"
+}
+
+type proprietary struct{}
+
+func (m proprietary) isMatch(line string) bool {
+ return strings.HasPrefix(strings.TrimLeft(line, " "), "@@@Proprietary License@@@")
+}
+
+func (m proprietary) String() string {
+ return "@@@Proprietary License@@@"
+}
+
+type matcherList []matcher
+
+func (l matcherList) String() string {
+ var sb strings.Builder
+ for _, m := range l {
+ s := m.String()
+ if s[:3] == s[len(s)-3:] {
+ fmt.Fprintln(&sb)
+ }
+ fmt.Fprintf(&sb, "%s\n", s)
+ if s[:3] == s[len(s)-3:] {
+ fmt.Fprintln(&sb)
+ }
+ }
+ return sb.String()
+}
diff --git a/tools/compliance/cmd/xmlnotice/xmlnotice.go b/tools/compliance/cmd/xmlnotice/xmlnotice.go
new file mode 100644
index 0000000..eb169f3
--- /dev/null
+++ b/tools/compliance/cmd/xmlnotice/xmlnotice.go
@@ -0,0 +1,220 @@
+// 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"
+ "compress/gzip"
+ "encoding/xml"
+ "flag"
+ "fmt"
+ "io"
+ "io/fs"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "android/soong/tools/compliance"
+
+ "github.com/google/blueprint/deptools"
+)
+
+var (
+ outputFile = flag.String("o", "-", "Where to write the NOTICE xml or xml.gz file. (default stdout)")
+ depsFile = flag.String("d", "", "Where to write the deps file")
+ stripPrefix = newMultiString("strip_prefix", "Prefix to remove from paths. i.e. path to root (multiple allowed)")
+ title = flag.String("title", "", "The title of the notice file.")
+
+ failNoneRequested = fmt.Errorf("\nNo license metadata files requested")
+ failNoLicenses = fmt.Errorf("No licenses found")
+)
+
+type context struct {
+ stdout io.Writer
+ stderr io.Writer
+ rootFS fs.FS
+ stripPrefix []string
+ title string
+ deps *[]string
+}
+
+func (ctx context) strip(installPath string) string {
+ for _, prefix := range ctx.stripPrefix {
+ if strings.HasPrefix(installPath, prefix) {
+ p := strings.TrimPrefix(installPath, prefix)
+ if 0 == len(p) {
+ p = ctx.title
+ }
+ if 0 == len(p) {
+ continue
+ }
+ return p
+ }
+ }
+ return installPath
+}
+
+func init() {
+ flag.Usage = func() {
+ fmt.Fprintf(os.Stderr, `Usage: %s {options} file.meta_lic {file.meta_lic...}
+
+Outputs an xml NOTICE.xml or gzipped NOTICE.xml.gz file if the -o filename ends
+with ".gz".
+
+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)
+ }
+
+ if len(*outputFile) == 0 {
+ flag.Usage()
+ fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n")
+ os.Exit(2)
+ } else {
+ dir, err := filepath.Abs(filepath.Dir(*outputFile))
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err)
+ os.Exit(1)
+ }
+ fi, err := os.Stat(dir)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err)
+ os.Exit(1)
+ }
+ if !fi.IsDir() {
+ fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile)
+ os.Exit(1)
+ }
+ }
+
+ var ofile io.Writer
+ var closer io.Closer
+ ofile = os.Stdout
+ var obuf *bytes.Buffer
+ if *outputFile != "-" {
+ obuf = &bytes.Buffer{}
+ ofile = obuf
+ }
+ if strings.HasSuffix(*outputFile, ".gz") {
+ ofile, _ = gzip.NewWriterLevel(obuf, gzip.BestCompression)
+ closer = ofile.(io.Closer)
+ }
+
+ var deps []string
+
+ ctx := &context{ofile, os.Stderr, os.DirFS("."), *stripPrefix, *title, &deps}
+
+ err := xmlNotice(ctx, flag.Args()...)
+ if err != nil {
+ if err == failNoneRequested {
+ flag.Usage()
+ }
+ fmt.Fprintf(os.Stderr, "%s\n", err.Error())
+ os.Exit(1)
+ }
+ if closer != nil {
+ closer.Close()
+ }
+
+ if *outputFile != "-" {
+ err := os.WriteFile(*outputFile, obuf.Bytes(), 0666)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "could not write output to %q: %s\n", *outputFile, err)
+ os.Exit(1)
+ }
+ }
+ if *depsFile != "" {
+ err := deptools.WriteDepFile(*depsFile, *outputFile, deps)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "could not write deps to %q: %s\n", *depsFile, err)
+ os.Exit(1)
+ }
+ }
+ os.Exit(0)
+}
+
+// xmlNotice implements the xmlnotice utility.
+func xmlNotice(ctx *context, 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(ctx.rootFS, ctx.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
+ }
+
+ // rs contains all notice resolutions.
+ rs := compliance.ResolveNotices(licenseGraph)
+
+ ni, err := compliance.IndexLicenseTexts(ctx.rootFS, licenseGraph, rs)
+ if err != nil {
+ return fmt.Errorf("Unable to read license text file(s) for %q: %v\n", files, err)
+ }
+
+ fmt.Fprintln(ctx.stdout, "<?xml version=\"1.0\" encoding=\"utf-8\"?>")
+ fmt.Fprintln(ctx.stdout, "<licenses>")
+
+ for installPath := range ni.InstallPaths() {
+ p := ctx.strip(installPath)
+ for _, h := range ni.InstallHashes(installPath) {
+ for _, lib := range ni.InstallHashLibs(installPath, h) {
+ fmt.Fprintf(ctx.stdout, "<file-name contentId=\"%s\" lib=\"", h.String())
+ xml.EscapeText(ctx.stdout, []byte(lib))
+ fmt.Fprintf(ctx.stdout, "\">")
+ xml.EscapeText(ctx.stdout, []byte(p))
+ fmt.Fprintln(ctx.stdout, "</file-name>")
+ }
+ }
+ }
+ for h := range ni.Hashes() {
+ fmt.Fprintf(ctx.stdout, "<file-content contentId=\"%s\"><![CDATA[", h)
+ xml.EscapeText(ctx.stdout, ni.HashText(h))
+ fmt.Fprintf(ctx.stdout, "]]></file-content>\n\n")
+ }
+ fmt.Fprintln(ctx.stdout, "</licenses>")
+
+ *ctx.deps = ni.InputNoticeFiles()
+
+ return nil
+}
diff --git a/tools/compliance/cmd/xmlnotice/xmlnotice_test.go b/tools/compliance/cmd/xmlnotice/xmlnotice_test.go
new file mode 100644
index 0000000..3a62438
--- /dev/null
+++ b/tools/compliance/cmd/xmlnotice/xmlnotice_test.go
@@ -0,0 +1,634 @@
+// 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 (
+ "bufio"
+ "bytes"
+ "encoding/xml"
+ "fmt"
+ "os"
+ "reflect"
+ "regexp"
+ "strings"
+ "testing"
+)
+
+var (
+ installTarget = regexp.MustCompile(`^<file-name contentId="[^"]{32}" lib="([^"]*)">([^<]+)</file-name>`)
+ licenseText = regexp.MustCompile(`^<file-content contentId="[^"]{32}"><![[]CDATA[[]([^]]*)[]][]]></file-content>`)
+)
+
+func TestMain(m *testing.M) {
+ // Change into the parent directory before running the tests
+ // so they can find the testdata directory.
+ if err := os.Chdir(".."); err != nil {
+ fmt.Printf("failed to change to testdata directory: %s\n", err)
+ os.Exit(1)
+ }
+ os.Exit(m.Run())
+}
+
+func Test(t *testing.T) {
+ tests := []struct {
+ condition string
+ name string
+ roots []string
+ stripPrefix string
+ expectedOut []matcher
+ expectedDeps []string
+ }{
+ {
+ condition: "firstparty",
+ name: "apex",
+ roots: []string{"highest.apex.meta_lic"},
+ expectedOut: []matcher{
+ target{"highest.apex", "Android"},
+ target{"highest.apex/bin/bin1", "Android"},
+ target{"highest.apex/bin/bin2", "Android"},
+ target{"highest.apex/lib/liba.so", "Android"},
+ target{"highest.apex/lib/libb.so", "Android"},
+ firstParty{},
+ },
+ expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+ },
+ {
+ condition: "firstparty",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []matcher{
+ target{"container.zip", "Android"},
+ target{"container.zip/bin1", "Android"},
+ target{"container.zip/bin2", "Android"},
+ target{"container.zip/liba.so", "Android"},
+ target{"container.zip/libb.so", "Android"},
+ firstParty{},
+ },
+ expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+ },
+ {
+ condition: "firstparty",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []matcher{
+ target{"application", "Android"},
+ firstParty{},
+ },
+ expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+ },
+ {
+ condition: "firstparty",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ expectedOut: []matcher{
+ target{"bin/bin1", "Android"},
+ firstParty{},
+ },
+ expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+ },
+ {
+ condition: "firstparty",
+ name: "library",
+ roots: []string{"lib/libd.so.meta_lic"},
+ expectedOut: []matcher{
+ target{"lib/libd.so", "Android"},
+ firstParty{},
+ },
+ expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"},
+ },
+ {
+ condition: "notice",
+ name: "apex",
+ roots: []string{"highest.apex.meta_lic"},
+ expectedOut: []matcher{
+ target{"highest.apex", "Android"},
+ target{"highest.apex/bin/bin1", "Android"},
+ target{"highest.apex/bin/bin1", "Device"},
+ target{"highest.apex/bin/bin1", "External"},
+ target{"highest.apex/bin/bin2", "Android"},
+ target{"highest.apex/lib/liba.so", "Device"},
+ target{"highest.apex/lib/libb.so", "Android"},
+ firstParty{},
+ notice{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/notice/NOTICE_LICENSE",
+ },
+ },
+ {
+ condition: "notice",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []matcher{
+ target{"container.zip", "Android"},
+ target{"container.zip/bin1", "Android"},
+ target{"container.zip/bin1", "Device"},
+ target{"container.zip/bin1", "External"},
+ target{"container.zip/bin2", "Android"},
+ target{"container.zip/liba.so", "Device"},
+ target{"container.zip/libb.so", "Android"},
+ firstParty{},
+ notice{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/notice/NOTICE_LICENSE",
+ },
+ },
+ {
+ condition: "notice",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []matcher{
+ target{"application", "Android"},
+ target{"application", "Device"},
+ firstParty{},
+ notice{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/notice/NOTICE_LICENSE",
+ },
+ },
+ {
+ condition: "notice",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ expectedOut: []matcher{
+ target{"bin/bin1", "Android"},
+ target{"bin/bin1", "Device"},
+ target{"bin/bin1", "External"},
+ firstParty{},
+ notice{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/notice/NOTICE_LICENSE",
+ },
+ },
+ {
+ condition: "notice",
+ name: "library",
+ roots: []string{"lib/libd.so.meta_lic"},
+ expectedOut: []matcher{
+ target{"lib/libd.so", "External"},
+ notice{},
+ },
+ expectedDeps: []string{"testdata/notice/NOTICE_LICENSE"},
+ },
+ {
+ condition: "reciprocal",
+ name: "apex",
+ roots: []string{"highest.apex.meta_lic"},
+ expectedOut: []matcher{
+ target{"highest.apex", "Android"},
+ target{"highest.apex/bin/bin1", "Android"},
+ target{"highest.apex/bin/bin1", "Device"},
+ target{"highest.apex/bin/bin1", "External"},
+ target{"highest.apex/bin/bin2", "Android"},
+ target{"highest.apex/lib/liba.so", "Device"},
+ target{"highest.apex/lib/libb.so", "Android"},
+ firstParty{},
+ reciprocal{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/reciprocal/RECIPROCAL_LICENSE",
+ },
+ },
+ {
+ condition: "reciprocal",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []matcher{
+ target{"container.zip", "Android"},
+ target{"container.zip/bin1", "Android"},
+ target{"container.zip/bin1", "Device"},
+ target{"container.zip/bin1", "External"},
+ target{"container.zip/bin2", "Android"},
+ target{"container.zip/liba.so", "Device"},
+ target{"container.zip/libb.so", "Android"},
+ firstParty{},
+ reciprocal{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/reciprocal/RECIPROCAL_LICENSE",
+ },
+ },
+ {
+ condition: "reciprocal",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []matcher{
+ target{"application", "Android"},
+ target{"application", "Device"},
+ firstParty{},
+ reciprocal{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/reciprocal/RECIPROCAL_LICENSE",
+ },
+ },
+ {
+ condition: "reciprocal",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ expectedOut: []matcher{
+ target{"bin/bin1", "Android"},
+ target{"bin/bin1", "Device"},
+ target{"bin/bin1", "External"},
+ firstParty{},
+ reciprocal{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/reciprocal/RECIPROCAL_LICENSE",
+ },
+ },
+ {
+ condition: "reciprocal",
+ name: "library",
+ roots: []string{"lib/libd.so.meta_lic"},
+ expectedOut: []matcher{
+ target{"lib/libd.so", "External"},
+ notice{},
+ },
+ expectedDeps: []string{"testdata/notice/NOTICE_LICENSE"},
+ },
+ {
+ condition: "restricted",
+ name: "apex",
+ roots: []string{"highest.apex.meta_lic"},
+ expectedOut: []matcher{
+ target{"highest.apex", "Android"},
+ target{"highest.apex/bin/bin1", "Android"},
+ target{"highest.apex/bin/bin1", "Device"},
+ target{"highest.apex/bin/bin1", "External"},
+ target{"highest.apex/bin/bin2", "Android"},
+ target{"highest.apex/bin/bin2", "Android"},
+ target{"highest.apex/lib/liba.so", "Device"},
+ target{"highest.apex/lib/libb.so", "Android"},
+ firstParty{},
+ restricted{},
+ reciprocal{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/reciprocal/RECIPROCAL_LICENSE",
+ "testdata/restricted/RESTRICTED_LICENSE",
+ },
+ },
+ {
+ condition: "restricted",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []matcher{
+ target{"container.zip", "Android"},
+ target{"container.zip/bin1", "Android"},
+ target{"container.zip/bin1", "Device"},
+ target{"container.zip/bin1", "External"},
+ target{"container.zip/bin2", "Android"},
+ target{"container.zip/bin2", "Android"},
+ target{"container.zip/liba.so", "Device"},
+ target{"container.zip/libb.so", "Android"},
+ firstParty{},
+ restricted{},
+ reciprocal{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/reciprocal/RECIPROCAL_LICENSE",
+ "testdata/restricted/RESTRICTED_LICENSE",
+ },
+ },
+ {
+ condition: "restricted",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []matcher{
+ target{"application", "Android"},
+ target{"application", "Device"},
+ firstParty{},
+ restricted{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/restricted/RESTRICTED_LICENSE",
+ },
+ },
+ {
+ condition: "restricted",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ expectedOut: []matcher{
+ target{"bin/bin1", "Android"},
+ target{"bin/bin1", "Device"},
+ target{"bin/bin1", "External"},
+ firstParty{},
+ restricted{},
+ reciprocal{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/reciprocal/RECIPROCAL_LICENSE",
+ "testdata/restricted/RESTRICTED_LICENSE",
+ },
+ },
+ {
+ condition: "restricted",
+ name: "library",
+ roots: []string{"lib/libd.so.meta_lic"},
+ expectedOut: []matcher{
+ target{"lib/libd.so", "External"},
+ notice{},
+ },
+ expectedDeps: []string{"testdata/notice/NOTICE_LICENSE"},
+ },
+ {
+ condition: "proprietary",
+ name: "apex",
+ roots: []string{"highest.apex.meta_lic"},
+ expectedOut: []matcher{
+ target{"highest.apex", "Android"},
+ target{"highest.apex/bin/bin1", "Android"},
+ target{"highest.apex/bin/bin1", "Device"},
+ target{"highest.apex/bin/bin1", "External"},
+ target{"highest.apex/bin/bin2", "Android"},
+ target{"highest.apex/bin/bin2", "Android"},
+ target{"highest.apex/lib/liba.so", "Device"},
+ target{"highest.apex/lib/libb.so", "Android"},
+ restricted{},
+ firstParty{},
+ proprietary{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/proprietary/PROPRIETARY_LICENSE",
+ "testdata/restricted/RESTRICTED_LICENSE",
+ },
+ },
+ {
+ condition: "proprietary",
+ name: "container",
+ roots: []string{"container.zip.meta_lic"},
+ expectedOut: []matcher{
+ target{"container.zip", "Android"},
+ target{"container.zip/bin1", "Android"},
+ target{"container.zip/bin1", "Device"},
+ target{"container.zip/bin1", "External"},
+ target{"container.zip/bin2", "Android"},
+ target{"container.zip/bin2", "Android"},
+ target{"container.zip/liba.so", "Device"},
+ target{"container.zip/libb.so", "Android"},
+ restricted{},
+ firstParty{},
+ proprietary{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/proprietary/PROPRIETARY_LICENSE",
+ "testdata/restricted/RESTRICTED_LICENSE",
+ },
+ },
+ {
+ condition: "proprietary",
+ name: "application",
+ roots: []string{"application.meta_lic"},
+ expectedOut: []matcher{
+ target{"application", "Android"},
+ target{"application", "Device"},
+ firstParty{},
+ proprietary{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/proprietary/PROPRIETARY_LICENSE",
+ },
+ },
+ {
+ condition: "proprietary",
+ name: "binary",
+ roots: []string{"bin/bin1.meta_lic"},
+ expectedOut: []matcher{
+ target{"bin/bin1", "Android"},
+ target{"bin/bin1", "Device"},
+ target{"bin/bin1", "External"},
+ firstParty{},
+ proprietary{},
+ },
+ expectedDeps: []string{
+ "testdata/firstparty/FIRST_PARTY_LICENSE",
+ "testdata/proprietary/PROPRIETARY_LICENSE",
+ },
+ },
+ {
+ condition: "proprietary",
+ name: "library",
+ roots: []string{"lib/libd.so.meta_lic"},
+ expectedOut: []matcher{
+ target{"lib/libd.so", "External"},
+ notice{},
+ },
+ expectedDeps: []string{"testdata/notice/NOTICE_LICENSE"},
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.condition+" "+tt.name, func(t *testing.T) {
+ stdout := &bytes.Buffer{}
+ stderr := &bytes.Buffer{}
+
+ rootFiles := make([]string, 0, len(tt.roots))
+ for _, r := range tt.roots {
+ rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
+ }
+
+ var deps []string
+
+ ctx := context{stdout, stderr, os.DirFS("."), []string{tt.stripPrefix}, "", &deps}
+
+ err := xmlNotice(&ctx, rootFiles...)
+ if err != nil {
+ t.Fatalf("xmlnotice: error = %v, stderr = %v", err, stderr)
+ return
+ }
+ if stderr.Len() > 0 {
+ t.Errorf("xmlnotice: gotStderr = %v, want none", stderr)
+ }
+
+ t.Logf("got stdout: %s", stdout.String())
+
+ t.Logf("want stdout: %s", matcherList(tt.expectedOut).String())
+
+ out := bufio.NewScanner(stdout)
+ lineno := 0
+ inBody := false
+ outOfBody := true
+ for out.Scan() {
+ line := out.Text()
+ if strings.TrimLeft(line, " ") == "" {
+ continue
+ }
+ if lineno == 0 && !inBody && `<?xml version="1.0" encoding="utf-8"?>` == line {
+ continue
+ }
+ if !inBody {
+ if "<licenses>" == line {
+ inBody = true
+ outOfBody = false
+ }
+ continue
+ } else if "</licenses>" == line {
+ outOfBody = true
+ continue
+ }
+
+ if len(tt.expectedOut) <= lineno {
+ t.Errorf("xmlnotice: unexpected output at line %d: got %q, want nothing (wanted %d lines)", lineno+1, line, len(tt.expectedOut))
+ } else if !tt.expectedOut[lineno].isMatch(line) {
+ t.Errorf("xmlnotice: unexpected output at line %d: got %q, want %q", lineno+1, line, tt.expectedOut[lineno].String())
+ }
+ lineno++
+ }
+ if !inBody {
+ t.Errorf("xmlnotice: missing <licenses> tag: got no <licenses> tag, want <licenses> tag on 2nd line")
+ }
+ if !outOfBody {
+ t.Errorf("xmlnotice: missing </licenses> tag: got no </licenses> tag, want </licenses> tag on last line")
+ }
+ for ; lineno < len(tt.expectedOut); lineno++ {
+ t.Errorf("xmlnotice: missing output line %d: ended early, want %q", lineno+1, tt.expectedOut[lineno].String())
+ }
+
+ t.Logf("got deps: %q", deps)
+
+ t.Logf("want deps: %q", tt.expectedDeps)
+
+ if g, w := deps, tt.expectedDeps; !reflect.DeepEqual(g, w) {
+ t.Errorf("unexpected deps, wanted:\n%s\ngot:\n%s\n",
+ strings.Join(w, "\n"), strings.Join(g, "\n"))
+ }
+ })
+ }
+}
+
+func escape(s string) string {
+ b := &bytes.Buffer{}
+ xml.EscapeText(b, []byte(s))
+ return b.String()
+}
+
+type matcher interface {
+ isMatch(line string) bool
+ String() string
+}
+
+type target struct {
+ name string
+ lib string
+}
+
+func (m target) isMatch(line string) bool {
+ groups := installTarget.FindStringSubmatch(line)
+ if len(groups) != 3 {
+ return false
+ }
+ return groups[1] == escape(m.lib) && strings.HasPrefix(groups[2], "out/") && strings.HasSuffix(groups[2], "/"+escape(m.name))
+}
+
+func (m target) String() string {
+ return `<file-name contentId="hash" lib="` + escape(m.lib) + `">` + escape(m.name) + `</file-name>`
+}
+
+func matchesText(line, text string) bool {
+ groups := licenseText.FindStringSubmatch(line)
+ if len(groups) != 2 {
+ return false
+ }
+ return groups[1] == escape(text + "\n")
+}
+
+func expectedText(text string) string {
+ return `<file-content contentId="hash"><![CDATA[` + escape(text + "\n") + `]]></file-content>`
+}
+
+type firstParty struct{}
+
+func (m firstParty) isMatch(line string) bool {
+ return matchesText(line, "&&&First Party License&&&")
+}
+
+func (m firstParty) String() string {
+ return expectedText("&&&First Party License&&&")
+}
+
+type notice struct{}
+
+func (m notice) isMatch(line string) bool {
+ return matchesText(line, "%%%Notice License%%%")
+}
+
+func (m notice) String() string {
+ return expectedText("%%%Notice License%%%")
+}
+
+type reciprocal struct{}
+
+func (m reciprocal) isMatch(line string) bool {
+ return matchesText(line, "$$$Reciprocal License$$$")
+}
+
+func (m reciprocal) String() string {
+ return expectedText("$$$Reciprocal License$$$")
+}
+
+type restricted struct{}
+
+func (m restricted) isMatch(line string) bool {
+ return matchesText(line, "###Restricted License###")
+}
+
+func (m restricted) String() string {
+ return expectedText("###Restricted License###")
+}
+
+type proprietary struct{}
+
+func (m proprietary) isMatch(line string) bool {
+ return matchesText(line, "@@@Proprietary License@@@")
+}
+
+func (m proprietary) String() string {
+ return expectedText("@@@Proprietary License@@@")
+}
+
+type matcherList []matcher
+
+func (l matcherList) String() string {
+ var sb strings.Builder
+ fmt.Fprintln(&sb, `<?xml version="1.0" encoding="utf-8"?>`)
+ fmt.Fprintln(&sb, `<licenses>`)
+ for _, m := range l {
+ s := m.String()
+ fmt.Fprintln(&sb, s)
+ if _, ok := m.(target); !ok {
+ fmt.Fprintln(&sb)
+ }
+ }
+ fmt.Fprintln(&sb, `/<licenses>`)
+ return sb.String()
+}
diff --git a/tools/compliance/condition.go b/tools/compliance/condition.go
new file mode 100644
index 0000000..cfe6f82
--- /dev/null
+++ b/tools/compliance/condition.go
@@ -0,0 +1,102 @@
+// 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"
+)
+
+// LicenseCondition identifies a recognized license condition by setting the
+// corresponding bit.
+type LicenseCondition uint16
+
+// LicenseConditionMask is a bitmask for the recognized license conditions.
+const LicenseConditionMask = LicenseCondition(0x3ff)
+
+const (
+ // UnencumberedCondition identifies public domain or public domain-
+ // like license that disclaims copyright.
+ UnencumberedCondition = LicenseCondition(0x0001)
+ // PermissiveCondition identifies a license without notice or other
+ // significant requirements.
+ PermissiveCondition = LicenseCondition(0x0002)
+ // NoticeCondition identifies a typical open-source license with only
+ // notice or attribution requirements.
+ NoticeCondition = LicenseCondition(0x0004)
+ // ReciprocalCondition identifies a license with requirement to share
+ // the module's source only.
+ ReciprocalCondition = LicenseCondition(0x0008)
+ // RestrictedCondition identifies a license with requirement to share
+ // all source code linked to the module's source.
+ RestrictedCondition = LicenseCondition(0x0010)
+ // RestrictedClasspathExceptionCondition identifies RestrictedCondition
+ // waived for dynamic linking from independent modules.
+ RestrictedClasspathExceptionCondition = LicenseCondition(0x0020)
+ // WeaklyRestrictedCondition identifies a RestrictedCondition waived
+ // for dynamic linking.
+ WeaklyRestrictedCondition = LicenseCondition(0x0040)
+ // ProprietaryCondition identifies a license with source privacy
+ // requirements.
+ ProprietaryCondition = LicenseCondition(0x0080)
+ // ByExceptionOnly identifies a license where policy requires product
+ // counsel review prior to use.
+ ByExceptionOnlyCondition = LicenseCondition(0x0100)
+ // NotAllowedCondition identifies a license with onerous conditions
+ // where policy prohibits use.
+ NotAllowedCondition = LicenseCondition(0x0200)
+)
+
+var (
+ // RecognizedConditionNames maps condition strings to LicenseCondition.
+ RecognizedConditionNames = map[string]LicenseCondition{
+ "unencumbered": UnencumberedCondition,
+ "permissive": PermissiveCondition,
+ "notice": NoticeCondition,
+ "reciprocal": ReciprocalCondition,
+ "restricted": RestrictedCondition,
+ "restricted_with_classpath_exception": RestrictedClasspathExceptionCondition,
+ "restricted_allows_dynamic_linking": WeaklyRestrictedCondition,
+ "proprietary": ProprietaryCondition,
+ "by_exception_only": ByExceptionOnlyCondition,
+ "not_allowed": NotAllowedCondition,
+ }
+)
+
+// Name returns the condition string corresponding to the LicenseCondition.
+func (lc LicenseCondition) Name() string {
+ switch lc {
+ case UnencumberedCondition:
+ return "unencumbered"
+ case PermissiveCondition:
+ return "permissive"
+ case NoticeCondition:
+ return "notice"
+ case ReciprocalCondition:
+ return "reciprocal"
+ case RestrictedCondition:
+ return "restricted"
+ case RestrictedClasspathExceptionCondition:
+ return "restricted_with_classpath_exception"
+ case WeaklyRestrictedCondition:
+ return "restricted_allows_dynamic_linking"
+ case ProprietaryCondition:
+ return "proprietary"
+ case ByExceptionOnlyCondition:
+ return "by_exception_only"
+ case NotAllowedCondition:
+ return "not_allowed"
+ }
+ panic(fmt.Errorf("unrecognized license condition: %04x", lc))
+}
diff --git a/tools/compliance/condition_test.go b/tools/compliance/condition_test.go
new file mode 100644
index 0000000..778ce4a
--- /dev/null
+++ b/tools/compliance/condition_test.go
@@ -0,0 +1,67 @@
+// 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 (
+ "testing"
+)
+
+func TestConditionSetHas(t *testing.T) {
+ impliesShare := ImpliesShared
+
+ t.Logf("testing with imliesShare=%04x", impliesShare)
+
+ if impliesShare.HasAny(NoticeCondition) {
+ t.Errorf("impliesShare.HasAny(\"notice\"=%04x) got true, want false", NoticeCondition)
+ }
+
+ if !impliesShare.HasAny(RestrictedCondition) {
+ t.Errorf("impliesShare.HasAny(\"restricted\"=%04x) got false, want true", RestrictedCondition)
+ }
+
+ if !impliesShare.HasAny(ReciprocalCondition) {
+ t.Errorf("impliesShare.HasAny(\"reciprocal\"=%04x) got false, want true", ReciprocalCondition)
+ }
+
+ if impliesShare.HasAny(LicenseCondition(0x0000)) {
+ t.Errorf("impliesShare.HasAny(nil=%04x) got true, want false", LicenseCondition(0x0000))
+ }
+}
+
+func TestConditionName(t *testing.T) {
+ for expected, condition := range RecognizedConditionNames {
+ actual := condition.Name()
+ if expected != actual {
+ t.Errorf("unexpected name for condition %04x: got %s, want %s", condition, actual, expected)
+ }
+ }
+}
+
+func TestConditionName_InvalidCondition(t *testing.T) {
+ panicked := false
+ var lc LicenseCondition
+ func() {
+ defer func() {
+ if err := recover(); err != nil {
+ panicked = true
+ }
+ }()
+ name := lc.Name()
+ t.Errorf("invalid condition unexpected name: got %s, wanted panic", name)
+ }()
+ if !panicked {
+ t.Errorf("no expected panic for %04x.Name(): got no panic, wanted panic", lc)
+ }
+}
diff --git a/tools/compliance/conditionset.go b/tools/compliance/conditionset.go
new file mode 100644
index 0000000..7a12ddc
--- /dev/null
+++ b/tools/compliance/conditionset.go
@@ -0,0 +1,189 @@
+// 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"
+)
+
+// LicenseConditionSet identifies sets of license conditions.
+type LicenseConditionSet LicenseCondition
+
+// AllLicenseConditions is the set of all recognized license conditions.
+const AllLicenseConditions = LicenseConditionSet(LicenseConditionMask)
+
+// NewLicenseConditionSet returns a set containing exactly the elements of
+// `conditions`.
+func NewLicenseConditionSet(conditions ...LicenseCondition) LicenseConditionSet {
+ cs := LicenseConditionSet(0x00)
+ for _, lc := range conditions {
+ cs |= LicenseConditionSet(lc)
+ }
+ return cs
+}
+
+// Plus returns a new set containing all of the elements of `cs` and all of the
+// `conditions`.
+func (cs LicenseConditionSet) Plus(conditions ...LicenseCondition) LicenseConditionSet {
+ result := cs
+ for _, lc := range conditions {
+ result |= LicenseConditionSet(lc)
+ }
+ return result
+}
+
+// Union returns a new set containing all of the elements of `cs` and all of the
+// elements of the `other` sets.
+func (cs LicenseConditionSet) Union(other ...LicenseConditionSet) LicenseConditionSet {
+ result := cs
+ for _, ls := range other {
+ result |= ls
+ }
+ return result
+}
+
+// MatchingAny returns the subset of `cs` equal to any of the `conditions`.
+func (cs LicenseConditionSet) MatchingAny(conditions ...LicenseCondition) LicenseConditionSet {
+ result := LicenseConditionSet(0x00)
+ for _, lc := range conditions {
+ result |= cs & LicenseConditionSet(lc)
+ }
+ return result
+}
+
+// MatchingAnySet returns the subset of `cs` that are members of any of the
+// `other` sets.
+func (cs LicenseConditionSet) MatchingAnySet(other ...LicenseConditionSet) LicenseConditionSet {
+ result := LicenseConditionSet(0x00)
+ for _, ls := range other {
+ result |= cs & ls
+ }
+ return result
+}
+
+// HasAny returns true when `cs` contains at least one of the `conditions`.
+func (cs LicenseConditionSet) HasAny(conditions ...LicenseCondition) bool {
+ for _, lc := range conditions {
+ if 0x0000 != (cs & LicenseConditionSet(lc)) {
+ return true
+ }
+ }
+ return false
+}
+
+// MatchesAnySet returns true when `cs` has a non-empty intersection with at
+// least one of the `other` condition sets.
+func (cs LicenseConditionSet) MatchesAnySet(other ...LicenseConditionSet) bool {
+ for _, ls := range other {
+ if 0x0000 != (cs & ls) {
+ return true
+ }
+ }
+ return false
+}
+
+// HasAll returns true when `cs` contains every one of the `conditions`.
+func (cs LicenseConditionSet) HasAll(conditions ...LicenseCondition) bool {
+ for _, lc := range conditions {
+ if 0x0000 == (cs & LicenseConditionSet(lc)) {
+ return false
+ }
+ }
+ return true
+}
+
+// MatchesEverySet returns true when `cs` has a non-empty intersection with
+// each of the `other` condition sets.
+func (cs LicenseConditionSet) MatchesEverySet(other ...LicenseConditionSet) bool {
+ for _, ls := range other {
+ if 0x0000 == (cs & ls) {
+ return false
+ }
+ }
+ return true
+}
+
+// Intersection returns the subset of `cs` that are members of every `other`
+// set.
+func (cs LicenseConditionSet) Intersection(other ...LicenseConditionSet) LicenseConditionSet {
+ result := cs
+ for _, ls := range other {
+ result &= ls
+ }
+ return result
+}
+
+// Minus returns the subset of `cs` that are not equaal to any `conditions`.
+func (cs LicenseConditionSet) Minus(conditions ...LicenseCondition) LicenseConditionSet {
+ result := cs
+ for _, lc := range conditions {
+ result &^= LicenseConditionSet(lc)
+ }
+ return result
+}
+
+// Difference returns the subset of `cs` that are not members of any `other`
+// set.
+func (cs LicenseConditionSet) Difference(other ...LicenseConditionSet) LicenseConditionSet {
+ result := cs
+ for _, ls := range other {
+ result &^= ls
+ }
+ return result
+}
+
+// Len returns the number of license conditions in the set.
+func (cs LicenseConditionSet) Len() int {
+ size := 0
+ for lc := LicenseConditionSet(0x01); 0x00 != (AllLicenseConditions & lc); lc <<= 1 {
+ if 0x00 != (cs & lc) {
+ size++
+ }
+ }
+ return size
+}
+
+// AsList returns an array of the license conditions in the set.
+func (cs LicenseConditionSet) AsList() []LicenseCondition {
+ result := make([]LicenseCondition, 0, cs.Len())
+ for lc := LicenseConditionSet(0x01); 0x00 != (AllLicenseConditions & lc); lc <<= 1 {
+ if 0x00 != (cs & lc) {
+ result = append(result, LicenseCondition(lc))
+ }
+ }
+ return result
+}
+
+// Names returns an array of the names of the license conditions in the set.
+func (cs LicenseConditionSet) Names() []string {
+ result := make([]string, 0, cs.Len())
+ for lc := LicenseConditionSet(0x01); 0x00 != (AllLicenseConditions & lc); lc <<= 1 {
+ if 0x00 != (cs & lc) {
+ result = append(result, LicenseCondition(lc).Name())
+ }
+ }
+ return result
+}
+
+// IsEmpty returns true when the set contains no license conditions.
+func (cs LicenseConditionSet) IsEmpty() bool {
+ return 0x00 == (cs & AllLicenseConditions)
+}
+
+// String returns a human-readable string representation of the set.
+func (cs LicenseConditionSet) String() string {
+ return fmt.Sprintf("{%s}", strings.Join(cs.Names(), "|"))
+}
diff --git a/tools/compliance/conditionset_test.go b/tools/compliance/conditionset_test.go
new file mode 100644
index 0000000..c91912f
--- /dev/null
+++ b/tools/compliance/conditionset_test.go
@@ -0,0 +1,657 @@
+// 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 (
+ "strings"
+ "testing"
+)
+
+func TestConditionSet(t *testing.T) {
+ tests := []struct {
+ name string
+ conditions []string
+ plus *[]string
+ minus *[]string
+ matchingAny map[string][]string
+ expected []string
+ }{
+ {
+ name: "empty",
+ conditions: []string{},
+ plus: &[]string{},
+ matchingAny: map[string][]string{
+ "notice": []string{},
+ "restricted": []string{},
+ "restricted|reciprocal": []string{},
+ },
+ expected: []string{},
+ },
+ {
+ name: "emptyminusnothing",
+ conditions: []string{},
+ minus: &[]string{},
+ matchingAny: map[string][]string{
+ "notice": []string{},
+ "restricted": []string{},
+ "restricted|reciprocal": []string{},
+ },
+ expected: []string{},
+ },
+ {
+ name: "emptyminusnotice",
+ conditions: []string{},
+ minus: &[]string{"notice"},
+ matchingAny: map[string][]string{
+ "notice": []string{},
+ "restricted": []string{},
+ "restricted|reciprocal": []string{},
+ },
+ expected: []string{},
+ },
+ {
+ name: "noticeonly",
+ conditions: []string{"notice"},
+ matchingAny: map[string][]string{
+ "notice": []string{"notice"},
+ "notice|proprietary": []string{"notice"},
+ "restricted": []string{},
+ },
+ expected: []string{"notice"},
+ },
+ {
+ name: "allnoticeonly",
+ conditions: []string{"notice"},
+ plus: &[]string{"notice"},
+ matchingAny: map[string][]string{
+ "notice": []string{"notice"},
+ "notice|proprietary": []string{"notice"},
+ "restricted": []string{},
+ },
+ expected: []string{"notice"},
+ },
+ {
+ name: "emptyplusnotice",
+ conditions: []string{},
+ plus: &[]string{"notice"},
+ matchingAny: map[string][]string{
+ "notice": []string{"notice"},
+ "notice|proprietary": []string{"notice"},
+ "restricted": []string{},
+ },
+ expected: []string{"notice"},
+ },
+ {
+ name: "everything",
+ conditions: []string{"unencumbered", "permissive", "notice", "reciprocal", "restricted", "proprietary"},
+ plus: &[]string{"restricted_with_classpath_exception", "restricted_allows_dynamic_linking", "by_exception_only", "not_allowed"},
+ matchingAny: map[string][]string{
+ "unencumbered": []string{"unencumbered"},
+ "permissive": []string{"permissive"},
+ "notice": []string{"notice"},
+ "reciprocal": []string{"reciprocal"},
+ "restricted": []string{"restricted"},
+ "restricted_with_classpath_exception": []string{"restricted_with_classpath_exception"},
+ "restricted_allows_dynamic_linking": []string{"restricted_allows_dynamic_linking"},
+ "proprietary": []string{"proprietary"},
+ "by_exception_only": []string{"by_exception_only"},
+ "not_allowed": []string{"not_allowed"},
+ "notice|proprietary": []string{"notice", "proprietary"},
+ },
+ expected: []string{
+ "unencumbered",
+ "permissive",
+ "notice",
+ "reciprocal",
+ "restricted",
+ "restricted_with_classpath_exception",
+ "restricted_allows_dynamic_linking",
+ "proprietary",
+ "by_exception_only",
+ "not_allowed",
+ },
+ },
+ {
+ name: "everythingplusminusnothing",
+ conditions: []string{
+ "unencumbered",
+ "permissive",
+ "notice",
+ "reciprocal",
+ "restricted",
+ "restricted_with_classpath_exception",
+ "restricted_allows_dynamic_linking",
+ "proprietary",
+ "by_exception_only",
+ "not_allowed",
+ },
+ plus: &[]string{},
+ minus: &[]string{},
+ matchingAny: map[string][]string{
+ "unencumbered|permissive|notice": []string{"unencumbered", "permissive", "notice"},
+ "restricted|reciprocal": []string{"reciprocal", "restricted"},
+ "proprietary|by_exception_only": []string{"proprietary", "by_exception_only"},
+ "not_allowed": []string{"not_allowed"},
+ },
+ expected: []string{
+ "unencumbered",
+ "permissive",
+ "notice",
+ "reciprocal",
+ "restricted",
+ "restricted_with_classpath_exception",
+ "restricted_allows_dynamic_linking",
+ "proprietary",
+ "by_exception_only",
+ "not_allowed",
+ },
+ },
+ {
+ name: "allbutone",
+ conditions: []string{"unencumbered", "permissive", "notice", "reciprocal", "restricted", "proprietary"},
+ plus: &[]string{"restricted_allows_dynamic_linking", "by_exception_only", "not_allowed"},
+ matchingAny: map[string][]string{
+ "unencumbered": []string{"unencumbered"},
+ "permissive": []string{"permissive"},
+ "notice": []string{"notice"},
+ "reciprocal": []string{"reciprocal"},
+ "restricted": []string{"restricted"},
+ "restricted_with_classpath_exception": []string{},
+ "restricted_allows_dynamic_linking": []string{"restricted_allows_dynamic_linking"},
+ "proprietary": []string{"proprietary"},
+ "by_exception_only": []string{"by_exception_only"},
+ "not_allowed": []string{"not_allowed"},
+ "notice|proprietary": []string{"notice", "proprietary"},
+ },
+ expected: []string{
+ "unencumbered",
+ "permissive",
+ "notice",
+ "reciprocal",
+ "restricted",
+ "restricted_allows_dynamic_linking",
+ "proprietary",
+ "by_exception_only",
+ "not_allowed",
+ },
+ },
+ {
+ name: "everythingminusone",
+ conditions: []string{
+ "unencumbered",
+ "permissive",
+ "notice",
+ "reciprocal",
+ "restricted",
+ "restricted_with_classpath_exception",
+ "restricted_allows_dynamic_linking",
+ "proprietary",
+ "by_exception_only",
+ "not_allowed",
+ },
+ minus: &[]string{"restricted_allows_dynamic_linking"},
+ matchingAny: map[string][]string{
+ "unencumbered": []string{"unencumbered"},
+ "permissive": []string{"permissive"},
+ "notice": []string{"notice"},
+ "reciprocal": []string{"reciprocal"},
+ "restricted": []string{"restricted"},
+ "restricted_with_classpath_exception": []string{"restricted_with_classpath_exception"},
+ "restricted_allows_dynamic_linking": []string{},
+ "proprietary": []string{"proprietary"},
+ "by_exception_only": []string{"by_exception_only"},
+ "not_allowed": []string{"not_allowed"},
+ "restricted|proprietary": []string{"restricted", "proprietary"},
+ },
+ expected: []string{
+ "unencumbered",
+ "permissive",
+ "notice",
+ "reciprocal",
+ "restricted",
+ "restricted_with_classpath_exception",
+ "proprietary",
+ "by_exception_only",
+ "not_allowed",
+ },
+ },
+ {
+ name: "everythingminuseverything",
+ conditions: []string{
+ "unencumbered",
+ "permissive",
+ "notice",
+ "reciprocal",
+ "restricted",
+ "restricted_with_classpath_exception",
+ "restricted_allows_dynamic_linking",
+ "proprietary",
+ "by_exception_only",
+ "not_allowed",
+ },
+ minus: &[]string{
+ "unencumbered",
+ "permissive",
+ "notice",
+ "reciprocal",
+ "restricted",
+ "restricted_with_classpath_exception",
+ "restricted_allows_dynamic_linking",
+ "proprietary",
+ "by_exception_only",
+ "not_allowed",
+ },
+ matchingAny: map[string][]string{
+ "unencumbered": []string{},
+ "permissive": []string{},
+ "notice": []string{},
+ "reciprocal": []string{},
+ "restricted": []string{},
+ "restricted_with_classpath_exception": []string{},
+ "restricted_allows_dynamic_linking": []string{},
+ "proprietary": []string{},
+ "by_exception_only": []string{},
+ "not_allowed": []string{},
+ "restricted|proprietary": []string{},
+ },
+ expected: []string{},
+ },
+ {
+ name: "restrictedplus",
+ conditions: []string{"restricted", "restricted_with_classpath_exception", "restricted_allows_dynamic_linking"},
+ plus: &[]string{"permissive", "notice", "restricted", "proprietary"},
+ matchingAny: map[string][]string{
+ "unencumbered": []string{},
+ "permissive": []string{"permissive"},
+ "notice": []string{"notice"},
+ "restricted": []string{"restricted"},
+ "restricted_with_classpath_exception": []string{"restricted_with_classpath_exception"},
+ "restricted_allows_dynamic_linking": []string{"restricted_allows_dynamic_linking"},
+ "proprietary": []string{"proprietary"},
+ "restricted|proprietary": []string{"restricted", "proprietary"},
+ "by_exception_only": []string{},
+ "proprietary|by_exception_only": []string{"proprietary"},
+ },
+ expected: []string{"permissive", "notice", "restricted", "restricted_with_classpath_exception", "restricted_allows_dynamic_linking", "proprietary"},
+ },
+ }
+ for _, tt := range tests {
+ toConditions := func(names []string) []LicenseCondition {
+ result := make([]LicenseCondition, 0, len(names))
+ for _, name := range names {
+ result = append(result, RecognizedConditionNames[name])
+ }
+ return result
+ }
+ populate := func() LicenseConditionSet {
+ testSet := NewLicenseConditionSet(toConditions(tt.conditions)...)
+ if tt.plus != nil {
+ testSet = testSet.Plus(toConditions(*tt.plus)...)
+ }
+ if tt.minus != nil {
+ testSet = testSet.Minus(toConditions(*tt.minus)...)
+ }
+ return testSet
+ }
+ populateSet := func() LicenseConditionSet {
+ testSet := NewLicenseConditionSet(toConditions(tt.conditions)...)
+ if tt.plus != nil {
+ testSet = testSet.Union(NewLicenseConditionSet(toConditions(*tt.plus)...))
+ }
+ if tt.minus != nil {
+ testSet = testSet.Difference(NewLicenseConditionSet(toConditions(*tt.minus)...))
+ }
+ return testSet
+ }
+ populatePlusSet := func() LicenseConditionSet {
+ testSet := NewLicenseConditionSet(toConditions(tt.conditions)...)
+ if tt.plus != nil {
+ testSet = testSet.Union(NewLicenseConditionSet(toConditions(*tt.plus)...))
+ }
+ if tt.minus != nil {
+ testSet = testSet.Minus(toConditions(*tt.minus)...)
+ }
+ return testSet
+ }
+ populateMinusSet := func() LicenseConditionSet {
+ testSet := NewLicenseConditionSet(toConditions(tt.conditions)...)
+ if tt.plus != nil {
+ testSet = testSet.Plus(toConditions(*tt.plus)...)
+ }
+ if tt.minus != nil {
+ testSet = testSet.Difference(NewLicenseConditionSet(toConditions(*tt.minus)...))
+ }
+ return testSet
+ }
+ checkMatching := func(cs LicenseConditionSet, t *testing.T) {
+ for data, expectedNames := range tt.matchingAny {
+ expectedConditions := toConditions(expectedNames)
+ expected := NewLicenseConditionSet(expectedConditions...)
+ actual := cs.MatchingAny(toConditions(strings.Split(data, "|"))...)
+ actualNames := actual.Names()
+
+ t.Logf("MatchingAny(%s): actual set %04x %s", data, actual, actual.String())
+ t.Logf("MatchingAny(%s): expected set %04x %s", data, expected, expected.String())
+
+ if actual != expected {
+ t.Errorf("MatchingAny(%s): got %04x, want %04x", data, actual, expected)
+ continue
+ }
+ if len(actualNames) != len(expectedNames) {
+ t.Errorf("len(MatchinAny(%s).Names()): got %d, want %d",
+ data, len(actualNames), len(expectedNames))
+ } else {
+ for i := 0; i < len(actualNames); i++ {
+ if actualNames[i] != expectedNames[i] {
+ t.Errorf("MatchingAny(%s).Names()[%d]: got %s, want %s",
+ data, i, actualNames[i], expectedNames[i])
+ break
+ }
+ }
+ }
+ actualConditions := actual.AsList()
+ if len(actualConditions) != len(expectedConditions) {
+ t.Errorf("len(MatchingAny(%s).AsList()): got %d, want %d",
+ data, len(actualNames), len(expectedNames))
+ } else {
+ for i := 0; i < len(actualNames); i++ {
+ if actualNames[i] != expectedNames[i] {
+ t.Errorf("MatchingAny(%s).AsList()[%d]: got %s, want %s",
+ data, i, actualNames[i], expectedNames[i])
+ break
+ }
+ }
+ }
+ }
+ }
+ checkMatchingSet := func(cs LicenseConditionSet, t *testing.T) {
+ for data, expectedNames := range tt.matchingAny {
+ expected := NewLicenseConditionSet(toConditions(expectedNames)...)
+ actual := cs.MatchingAnySet(NewLicenseConditionSet(toConditions(strings.Split(data, "|"))...))
+ actualNames := actual.Names()
+
+ t.Logf("MatchingAnySet(%s): actual set %04x %s", data, actual, actual.String())
+ t.Logf("MatchingAnySet(%s): expected set %04x %s", data, expected, expected.String())
+
+ if actual != expected {
+ t.Errorf("MatchingAnySet(%s): got %04x, want %04x", data, actual, expected)
+ continue
+ }
+ if len(actualNames) != len(expectedNames) {
+ t.Errorf("len(MatchingAnySet(%s).Names()): got %d, want %d",
+ data, len(actualNames), len(expectedNames))
+ } else {
+ for i := 0; i < len(actualNames); i++ {
+ if actualNames[i] != expectedNames[i] {
+ t.Errorf("MatchingAnySet(%s).Names()[%d]: got %s, want %s",
+ data, i, actualNames[i], expectedNames[i])
+ break
+ }
+ }
+ }
+ expectedConditions := toConditions(expectedNames)
+ actualConditions := actual.AsList()
+ if len(actualConditions) != len(expectedConditions) {
+ t.Errorf("len(MatchingAnySet(%s).AsList()): got %d, want %d",
+ data, len(actualNames), len(expectedNames))
+ } else {
+ for i := 0; i < len(actualNames); i++ {
+ if actualNames[i] != expectedNames[i] {
+ t.Errorf("MatchingAnySet(%s).AsList()[%d]: got %s, want %s",
+ data, i, actualNames[i], expectedNames[i])
+ break
+ }
+ }
+ }
+ }
+ }
+
+ checkExpected := func(actual LicenseConditionSet, t *testing.T) bool {
+ t.Logf("checkExpected{%s}", strings.Join(tt.expected, ", "))
+
+ expectedConditions := toConditions(tt.expected)
+ expected := NewLicenseConditionSet(expectedConditions...)
+
+ actualNames := actual.Names()
+
+ t.Logf("actual license condition set: %04x %s", actual, actual.String())
+ t.Logf("expected license condition set: %04x %s", expected, expected.String())
+
+ if actual != expected {
+ t.Errorf("checkExpected: got %04x, want %04x", actual, expected)
+ return false
+ }
+
+ if len(actualNames) != len(tt.expected) {
+ t.Errorf("len(actual.Names()): got %d, want %d", len(actualNames), len(tt.expected))
+ } else {
+ for i := 0; i < len(actualNames); i++ {
+ if actualNames[i] != tt.expected[i] {
+ t.Errorf("actual.Names()[%d]: got %s, want %s", i, actualNames[i], tt.expected[i])
+ break
+ }
+ }
+ }
+
+ actualConditions := actual.AsList()
+ if len(actualConditions) != len(expectedConditions) {
+ t.Errorf("len(actual.AsList()): got %d, want %d", len(actualConditions), len(expectedConditions))
+ } else {
+ for i := 0; i < len(actualConditions); i++ {
+ if actualConditions[i] != expectedConditions[i] {
+ t.Errorf("actual.AsList()[%d]: got %s, want %s",
+ i, actualConditions[i].Name(), expectedConditions[i].Name())
+ break
+ }
+ }
+ }
+
+ if len(tt.expected) == 0 {
+ if !actual.IsEmpty() {
+ t.Errorf("actual.IsEmpty(): got false, want true")
+ }
+ if actual.HasAny(expectedConditions...) {
+ t.Errorf("actual.HasAny(): got true, want false")
+ }
+ } else {
+ if actual.IsEmpty() {
+ t.Errorf("actual.IsEmpty(): got true, want false")
+ }
+ if !actual.HasAny(expectedConditions...) {
+ t.Errorf("actual.HasAny(all expected): got false, want true")
+ }
+ }
+ if !actual.HasAll(expectedConditions...) {
+ t.Errorf("actual.Hasll(all expected): want true, got false")
+ }
+ for _, expectedCondition := range expectedConditions {
+ if !actual.HasAny(expectedCondition) {
+ t.Errorf("actual.HasAny(%q): got false, want true", expectedCondition.Name())
+ }
+ if !actual.HasAll(expectedCondition) {
+ t.Errorf("actual.HasAll(%q): got false, want true", expectedCondition.Name())
+ }
+ }
+
+ notExpected := (AllLicenseConditions &^ expected)
+ notExpectedList := notExpected.AsList()
+ t.Logf("not expected license condition set: %04x %s", notExpected, notExpected.String())
+
+ if len(tt.expected) == 0 {
+ if actual.HasAny(append(expectedConditions, notExpectedList...)...) {
+ t.Errorf("actual.HasAny(all conditions): want false, got true")
+ }
+ } else {
+ if !actual.HasAny(append(expectedConditions, notExpectedList...)...) {
+ t.Errorf("actual.HasAny(all conditions): want true, got false")
+ }
+ }
+ if len(notExpectedList) == 0 {
+ if !actual.HasAll(append(expectedConditions, notExpectedList...)...) {
+ t.Errorf("actual.HasAll(all conditions): want true, got false")
+ }
+ } else {
+ if actual.HasAll(append(expectedConditions, notExpectedList...)...) {
+ t.Errorf("actual.HasAll(all conditions): want false, got true")
+ }
+ }
+ for _, unexpectedCondition := range notExpectedList {
+ if actual.HasAny(unexpectedCondition) {
+ t.Errorf("actual.HasAny(%q): got true, want false", unexpectedCondition.Name())
+ }
+ if actual.HasAll(unexpectedCondition) {
+ t.Errorf("actual.HasAll(%q): got true, want false", unexpectedCondition.Name())
+ }
+ }
+ return true
+ }
+
+ checkExpectedSet := func(actual LicenseConditionSet, t *testing.T) bool {
+ t.Logf("checkExpectedSet{%s}", strings.Join(tt.expected, ", "))
+
+ expectedConditions := toConditions(tt.expected)
+ expected := NewLicenseConditionSet(expectedConditions...)
+
+ actualNames := actual.Names()
+
+ t.Logf("actual license condition set: %04x %s", actual, actual.String())
+ t.Logf("expected license condition set: %04x %s", expected, expected.String())
+
+ if actual != expected {
+ t.Errorf("checkExpectedSet: got %04x, want %04x", actual, expected)
+ return false
+ }
+
+ if len(actualNames) != len(tt.expected) {
+ t.Errorf("len(actual.Names()): got %d, want %d", len(actualNames), len(tt.expected))
+ } else {
+ for i := 0; i < len(actualNames); i++ {
+ if actualNames[i] != tt.expected[i] {
+ t.Errorf("actual.Names()[%d]: got %s, want %s", i, actualNames[i], tt.expected[i])
+ break
+ }
+ }
+ }
+
+ actualConditions := actual.AsList()
+ if len(actualConditions) != len(expectedConditions) {
+ t.Errorf("len(actual.AsList()): got %d, want %d", len(actualConditions), len(expectedConditions))
+ } else {
+ for i := 0; i < len(actualConditions); i++ {
+ if actualConditions[i] != expectedConditions[i] {
+ t.Errorf("actual.AsList()[%d}: got %s, want %s",
+ i, actualConditions[i].Name(), expectedConditions[i].Name())
+ break
+ }
+ }
+ }
+
+ if len(tt.expected) == 0 {
+ if !actual.IsEmpty() {
+ t.Errorf("actual.IsEmpty(): got false, want true")
+ }
+ if actual.MatchesAnySet(expected) {
+ t.Errorf("actual.MatchesAnySet({}): got true, want false")
+ }
+ if actual.MatchesEverySet(expected, expected) {
+ t.Errorf("actual.MatchesEverySet({}, {}): want false, got true")
+ }
+ } else {
+ if actual.IsEmpty() {
+ t.Errorf("actual.IsEmpty(): got true, want false")
+ }
+ if !actual.MatchesAnySet(expected) {
+ t.Errorf("actual.MatchesAnySet({all expected}): want true, got false")
+ }
+ if !actual.MatchesEverySet(expected, expected) {
+ t.Errorf("actual.MatchesEverySet({all expected}, {all expected}): want true, got false")
+ }
+ }
+
+ notExpected := (AllLicenseConditions &^ expected)
+ t.Logf("not expected license condition set: %04x %s", notExpected, notExpected.String())
+
+ if len(tt.expected) == 0 {
+ if actual.MatchesAnySet(expected, notExpected) {
+ t.Errorf("empty actual.MatchesAnySet({expected}, {not expected}): want false, got true")
+ }
+ } else {
+ if !actual.MatchesAnySet(expected, notExpected) {
+ t.Errorf("actual.MatchesAnySet({expected}, {not expected}): want true, got false")
+ }
+ }
+ if actual.MatchesAnySet(notExpected) {
+ t.Errorf("actual.MatchesAnySet({not expected}): want false, got true")
+ }
+ if actual.MatchesEverySet(notExpected) {
+ t.Errorf("actual.MatchesEverySet({not expected}): want false, got true")
+ }
+ if actual.MatchesEverySet(expected, notExpected) {
+ t.Errorf("actual.MatchesEverySet({expected}, {not expected}): want false, got true")
+ }
+
+ if !actual.Difference(expected).IsEmpty() {
+ t.Errorf("actual.Difference({expected}).IsEmpty(): want true, got false")
+ }
+ if expected != actual.Intersection(expected) {
+ t.Errorf("expected == actual.Intersection({expected}): want true, got false (%04x != %04x)", expected, actual.Intersection(expected))
+ }
+ if actual != actual.Intersection(expected) {
+ t.Errorf("actual == actual.Intersection({expected}): want true, got false (%04x != %04x)", actual, actual.Intersection(expected))
+ }
+ return true
+ }
+
+ t.Run(tt.name, func(t *testing.T) {
+ cs := populate()
+ if checkExpected(cs, t) {
+ checkMatching(cs, t)
+ }
+ if checkExpectedSet(cs, t) {
+ checkMatchingSet(cs, t)
+ }
+ })
+
+ t.Run(tt.name+"_sets", func(t *testing.T) {
+ cs := populateSet()
+ if checkExpected(cs, t) {
+ checkMatching(cs, t)
+ }
+ if checkExpectedSet(cs, t) {
+ checkMatchingSet(cs, t)
+ }
+ })
+
+ t.Run(tt.name+"_plusset", func(t *testing.T) {
+ cs := populatePlusSet()
+ if checkExpected(cs, t) {
+ checkMatching(cs, t)
+ }
+ if checkExpectedSet(cs, t) {
+ checkMatchingSet(cs, t)
+ }
+ })
+
+ t.Run(tt.name+"_minusset", func(t *testing.T) {
+ cs := populateMinusSet()
+ if checkExpected(cs, t) {
+ checkMatching(cs, t)
+ }
+ if checkExpectedSet(cs, t) {
+ checkMatchingSet(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/go.mod b/tools/compliance/go.mod
new file mode 100644
index 0000000..61e2158
--- /dev/null
+++ b/tools/compliance/go.mod
@@ -0,0 +1,18 @@
+module android/soong/tools/compliance
+
+require google.golang.org/protobuf v0.0.0
+
+replace google.golang.org/protobuf v0.0.0 => ../../../../external/golang-protobuf
+
+require android/soong v0.0.0
+
+replace android/soong v0.0.0 => ../../../soong
+// Indirect deps from golang-protobuf
+exclude github.com/golang/protobuf v1.5.0
+
+replace github.com/google/go-cmp v0.5.5 => ../../../../external/go-cmp
+
+// Indirect dep from go-cmp
+exclude golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543
+
+go 1.18
diff --git a/tools/compliance/graph.go b/tools/compliance/graph.go
new file mode 100644
index 0000000..e73ab46
--- /dev/null
+++ b/tools/compliance/graph.go
@@ -0,0 +1,528 @@
+// 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 TargetEdgeList
+
+ // targets identifies, indexes, and describes the entire set of target node files.
+ /// (guarded by mu)
+ targets map[string]*TargetNode
+
+ // wgBU becomes non-nil when the bottom-up resolve begins and reaches 0
+ // (i.e. Wait() proceeds) when the bottom-up resolve completes. (guarded by mu)
+ wgBU *sync.WaitGroup
+
+ // wgTD becomes non-nil when the top-down resolve begins and reaches 0 (i.e. Wait()
+ // proceeds) when the top-down resolve completes. (guarded by mu)
+ wgTD *sync.WaitGroup
+
+ // 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
+}
+
+// Edges returns the list of edges in the graph. (unordered)
+func (lg *LicenseGraph) Edges() TargetEdgeList {
+ edges := make(TargetEdgeList, 0, len(lg.edges))
+ edges = append(edges, lg.edges...)
+ 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, target)
+ }
+ return targets
+}
+
+// compliance-only LicenseGraph methods
+
+// newLicenseGraph constructs a new, empty instance of LicenseGraph.
+func newLicenseGraph() *LicenseGraph {
+ return &LicenseGraph{
+ rootFiles: []string{},
+ targets: make(map[string]*TargetNode),
+ }
+}
+
+// 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 {
+ // target and dependency identify the nodes connected by the edge.
+ target, dependency *TargetNode
+
+ // annotations identifies the set of compliance-relevant annotations describing the edge.
+ annotations TargetEdgeAnnotations
+}
+
+// Target identifies the target that depends on the dependency.
+//
+// Target needs Dependency to build.
+func (e *TargetEdge) Target() *TargetNode {
+ return 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.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.annotations
+}
+
+// String returns a human-readable string representation of the edge.
+func (e *TargetEdge) String() string {
+ return fmt.Sprintf("%s -[%s]> %s", e.target.name, strings.Join(e.annotations.AsList(), ", "), e.dependency.name)
+}
+
+// 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 {
+ namei := l[i].target.name
+ namej := l[j].target.name
+ if namei == namej {
+ namei = l[i].dependency.name
+ namej = l[j].dependency.name
+ }
+ if namei == namej {
+ return l[i].annotations.Compare(l[j].annotations) < 0
+ }
+ return namei < namej
+}
+
+// TargetEdgePathSegment describes a single arc in a TargetPath associating the
+// edge with a context `ctx` defined by whatever process is creating the path.
+type TargetEdgePathSegment struct {
+ edge *TargetEdge
+ ctx interface{}
+}
+
+// Target identifies the target that depends on the dependency.
+//
+// Target needs Dependency to build.
+func (s TargetEdgePathSegment) Target() *TargetNode {
+ return s.edge.target
+}
+
+// Dependency identifies the target depended on by the target.
+//
+// Dependency builds without Target, but Target needs Dependency to build.
+func (s TargetEdgePathSegment) Dependency() *TargetNode {
+ return s.edge.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 (s TargetEdgePathSegment) Annotations() TargetEdgeAnnotations {
+ return s.edge.annotations
+}
+
+// Context returns the context associated with the path segment. The type and
+// value of the context defined by the process creating the path.
+func (s TargetEdgePathSegment) Context() interface{} {
+ return s.ctx
+}
+
+// String returns a human-readable string representation of the edge.
+func (s TargetEdgePathSegment) String() string {
+ return fmt.Sprintf("%s -[%s]> %s", s.edge.target.name, strings.Join(s.edge.annotations.AsList(), ", "), s.edge.dependency.name)
+}
+
+// TargetEdgePath describes a sequence of edges starting at a root and ending
+// at some final dependency.
+type TargetEdgePath []TargetEdgePathSegment
+
+// 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, ctx interface{}) {
+ if len(*p) == 0 {
+ *p = append(*p, TargetEdgePathSegment{edge, ctx})
+ return
+ }
+ if (*p)[len(*p)-1].edge.dependency != edge.target {
+ panic(fmt.Errorf("disjoint path %s does not end at %s", p.String(), edge.target.name))
+ }
+ *p = append(*p, TargetEdgePathSegment{edge, ctx})
+}
+
+// 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]
+}
+
+// Copy makes a new path with the same value.
+func (p *TargetEdgePath) Copy() *TargetEdgePath {
+ result := make(TargetEdgePath, 0, len(*p))
+ for _, e := range *p {
+ result = append(result, e)
+ }
+ return &result
+}
+
+// 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 _, s := range *p {
+ fmt.Fprintf(&sb, "%s -> ", s.edge.target.name)
+ }
+ lastSegment := (*p)[len(*p)-1]
+ fmt.Fprintf(&sb, "%s]", lastSegment.edge.dependency.name)
+ 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
+}
+
+// Dependencies returns the list of edges to dependencies of `tn`.
+func (tn *TargetNode) Dependencies() TargetEdgeList {
+ edges := make(TargetEdgeList, 0, len(tn.edges))
+ edges = append(edges, tn.edges...)
+ return edges
+}
+
+// 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 {
+ return tn.licenseConditions
+}
+
+// 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...)
+}
+
+// TargetFiles returns the list of files built or installed by the module or
+// target. (unordered)
+func (tn *TargetNode) TargetFiles() []string {
+ return append(tn.proto.Built, 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]struct{}
+}
+
+// newEdgeAnnotations creates a new instance of TargetEdgeAnnotations.
+func newEdgeAnnotations() TargetEdgeAnnotations {
+ return TargetEdgeAnnotations{make(map[string]struct{})}
+}
+
+// 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]struct{}
+}
+
+// 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
+}
+
+// String returns a human-readable string representation of the set.
+func (ts *TargetNodeSet) String() string {
+ return fmt.Sprintf("{%s}", strings.Join(ts.Names(), ", "))
+}
+
+// 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/noticeindex.go b/tools/compliance/noticeindex.go
new file mode 100644
index 0000000..7bebe3d
--- /dev/null
+++ b/tools/compliance/noticeindex.go
@@ -0,0 +1,607 @@
+// 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 (
+ "bufio"
+ "crypto/md5"
+ "fmt"
+ "io"
+ "io/fs"
+ "path/filepath"
+ "regexp"
+ "sort"
+ "strings"
+)
+
+const (
+ noProjectName = "\u2205"
+)
+
+var (
+ nameRegexp = regexp.MustCompile(`^\s*name\s*:\s*"(.*)"\s*$`)
+ descRegexp = regexp.MustCompile(`^\s*description\s*:\s*"(.*)"\s*$`)
+ versionRegexp = regexp.MustCompile(`^\s*version\s*:\s*"(.*)"\s*$`)
+ licensesPathRegexp = regexp.MustCompile(`licen[cs]es?/`)
+)
+
+// NoticeIndex transforms license metadata into license text hashes, library
+// names, and install paths indexing them for fast lookup/iteration.
+type NoticeIndex struct {
+ // lg identifies the license graph to which the index applies.
+ lg *LicenseGraph
+ // rs identifies the set of resolutions upon which the index is based.
+ rs ResolutionSet
+ // shipped identifies the set of target nodes shipped directly or as derivative works.
+ shipped *TargetNodeSet
+ // rootFS locates the root of the file system from which to read the files.
+ rootFS fs.FS
+ // hash maps license text filenames to content hashes
+ hash map[string]hash
+ // text maps content hashes to content
+ text map[hash][]byte
+ // hashLibInstall maps hashes to libraries to install paths.
+ hashLibInstall map[hash]map[string]map[string]struct{}
+ // installHashLib maps install paths to libraries to hashes.
+ installHashLib map[string]map[hash]map[string]struct{}
+ // libHash maps libraries to hashes.
+ libHash map[string]map[hash]struct{}
+ // targetHash maps target nodes to hashes.
+ targetHashes map[*TargetNode]map[hash]struct{}
+ // projectName maps project directory names to project name text.
+ projectName map[string]string
+ // files lists all the files accessed during indexing
+ files []string
+}
+
+// IndexLicenseTexts creates a hashed index of license texts for `lg` and `rs`
+// using the files rooted at `rootFS`.
+func IndexLicenseTexts(rootFS fs.FS, lg *LicenseGraph, rs ResolutionSet) (*NoticeIndex, error) {
+ if rs == nil {
+ rs = ResolveNotices(lg)
+ }
+ ni := &NoticeIndex{
+ lg: lg,
+ rs: rs,
+ shipped: ShippedNodes(lg),
+ rootFS: rootFS,
+ hash: make(map[string]hash),
+ text: make(map[hash][]byte),
+ hashLibInstall: make(map[hash]map[string]map[string]struct{}),
+ installHashLib: make(map[string]map[hash]map[string]struct{}),
+ libHash: make(map[string]map[hash]struct{}),
+ targetHashes: make(map[*TargetNode]map[hash]struct{}),
+ projectName: make(map[string]string),
+ }
+
+ // index adds all license texts for `tn` to the index.
+ index := func(tn *TargetNode) (map[hash]struct{}, error) {
+ if hashes, ok := ni.targetHashes[tn]; ok {
+ return hashes, nil
+ }
+ hashes := make(map[hash]struct{})
+ for _, text := range tn.LicenseTexts() {
+ if _, ok := ni.hash[text]; !ok {
+ err := ni.addText(text)
+ if err != nil {
+ return nil, err
+ }
+ }
+ hash := ni.hash[text]
+ if _, ok := hashes[hash]; !ok {
+ hashes[hash] = struct{}{}
+ }
+ }
+ ni.targetHashes[tn] = hashes
+ return hashes, nil
+ }
+
+ link := func(libName string, hashes map[hash]struct{}, installPaths []string) {
+ if _, ok := ni.libHash[libName]; !ok {
+ ni.libHash[libName] = make(map[hash]struct{})
+ }
+ for h := range hashes {
+ if _, ok := ni.hashLibInstall[h]; !ok {
+ ni.hashLibInstall[h] = make(map[string]map[string]struct{})
+ }
+ if _, ok := ni.libHash[libName][h]; !ok {
+ ni.libHash[libName][h] = struct{}{}
+ }
+ for _, installPath := range installPaths {
+ if _, ok := ni.installHashLib[installPath]; !ok {
+ ni.installHashLib[installPath] = make(map[hash]map[string]struct{})
+ ni.installHashLib[installPath][h] = make(map[string]struct{})
+ ni.installHashLib[installPath][h][libName] = struct{}{}
+ } else if _, ok = ni.installHashLib[installPath][h]; !ok {
+ ni.installHashLib[installPath][h] = make(map[string]struct{})
+ ni.installHashLib[installPath][h][libName] = struct{}{}
+ } else if _, ok = ni.installHashLib[installPath][h][libName]; !ok {
+ ni.installHashLib[installPath][h][libName] = struct{}{}
+ }
+ if _, ok := ni.hashLibInstall[h]; !ok {
+ ni.hashLibInstall[h] = make(map[string]map[string]struct{})
+ ni.hashLibInstall[h][libName] = make(map[string]struct{})
+ ni.hashLibInstall[h][libName][installPath] = struct{}{}
+ } else if _, ok = ni.hashLibInstall[h][libName]; !ok {
+ ni.hashLibInstall[h][libName] = make(map[string]struct{})
+ ni.hashLibInstall[h][libName][installPath] = struct{}{}
+ } else if _, ok = ni.hashLibInstall[h][libName][installPath]; !ok {
+ ni.hashLibInstall[h][libName][installPath] = struct{}{}
+ }
+ }
+ }
+ }
+
+ // returns error from walk below.
+ var err error
+
+ WalkTopDown(NoEdgeContext{}, lg, func(lg *LicenseGraph, tn *TargetNode, path TargetEdgePath) bool {
+ if err != nil {
+ return false
+ }
+ if !ni.shipped.Contains(tn) {
+ return false
+ }
+ installPaths := getInstallPaths(tn, path)
+ var hashes map[hash]struct{}
+ hashes, err = index(tn)
+ if err != nil {
+ return false
+ }
+ link(ni.getLibName(tn), hashes, installPaths)
+ if tn.IsContainer() {
+ return true
+ }
+
+ for _, r := range rs.Resolutions(tn) {
+ hashes, err = index(r.actsOn)
+ if err != nil {
+ return false
+ }
+ link(ni.getLibName(r.actsOn), hashes, installPaths)
+ }
+ return false
+ })
+
+ if err != nil {
+ return nil, err
+ }
+
+ return ni, nil
+}
+
+// Hashes returns an ordered channel of the hashed license texts.
+func (ni *NoticeIndex) Hashes() chan hash {
+ c := make(chan hash)
+ go func() {
+ libs := make([]string, 0, len(ni.libHash))
+ for libName := range ni.libHash {
+ libs = append(libs, libName)
+ }
+ sort.Strings(libs)
+ hashes := make(map[hash]struct{})
+ for _, libName := range libs {
+ hl := make([]hash, 0, len(ni.libHash[libName]))
+ for h := range ni.libHash[libName] {
+ if _, ok := hashes[h]; ok {
+ continue
+ }
+ hashes[h] = struct{}{}
+ hl = append(hl, h)
+ }
+ if len(hl) > 0 {
+ sort.Sort(hashList{ni, libName, "", &hl})
+ for _, h := range hl {
+ c <- h
+ }
+ }
+ }
+ close(c)
+ }()
+ return c
+}
+
+// InputNoticeFiles returns the list of files that were hashed during IndexLicenseTexts.
+func (ni *NoticeIndex) InputNoticeFiles() []string {
+ files := append([]string(nil), ni.files...)
+ sort.Strings(files)
+ return files
+}
+
+// HashLibs returns the ordered array of library names using the license text
+// hashed as `h`.
+func (ni *NoticeIndex) HashLibs(h hash) []string {
+ libs := make([]string, 0, len(ni.hashLibInstall[h]))
+ for libName := range ni.hashLibInstall[h] {
+ libs = append(libs, libName)
+ }
+ sort.Strings(libs)
+ return libs
+}
+
+// HashLibInstalls returns the ordered array of install paths referencing
+// library `libName` using the license text hashed as `h`.
+func (ni *NoticeIndex) HashLibInstalls(h hash, libName string) []string {
+ installs := make([]string, 0, len(ni.hashLibInstall[h][libName]))
+ for installPath := range ni.hashLibInstall[h][libName] {
+ installs = append(installs, installPath)
+ }
+ sort.Strings(installs)
+ return installs
+}
+
+// InstallPaths returns the ordered channel of indexed install paths.
+func (ni *NoticeIndex) InstallPaths() chan string {
+ c := make(chan string)
+ go func() {
+ paths := make([]string, 0, len(ni.installHashLib))
+ for path := range ni.installHashLib {
+ paths = append(paths, path)
+ }
+ sort.Strings(paths)
+ for _, installPath := range paths {
+ c <- installPath
+ }
+ close(c)
+ }()
+ return c
+}
+
+// InstallHashes returns the ordered array of hashes attached to `installPath`.
+func (ni *NoticeIndex) InstallHashes(installPath string) []hash {
+ result := make([]hash, 0, len(ni.installHashLib[installPath]))
+ for h := range ni.installHashLib[installPath] {
+ result = append(result, h)
+ }
+ if len(result) > 0 {
+ sort.Sort(hashList{ni, "", installPath, &result})
+ }
+ return result
+}
+
+// InstallHashLibs returns the ordered array of library names attached to
+// `installPath` as hash `h`.
+func (ni *NoticeIndex) InstallHashLibs(installPath string, h hash) []string {
+ result := make([]string, 0, len(ni.installHashLib[installPath][h]))
+ for libName := range ni.installHashLib[installPath][h] {
+ result = append(result, libName)
+ }
+ sort.Strings(result)
+ return result
+}
+
+// Libraries returns the ordered channel of indexed library names.
+func (ni *NoticeIndex) Libraries() chan string {
+ c := make(chan string)
+ go func() {
+ libs := make([]string, 0, len(ni.libHash))
+ for lib := range ni.libHash {
+ libs = append(libs, lib)
+ }
+ sort.Strings(libs)
+ for _, lib := range libs {
+ c <- lib
+ }
+ close(c)
+ }()
+ return c
+}
+
+// HashText returns the file content of the license text hashed as `h`.
+func (ni *NoticeIndex) HashText(h hash) []byte {
+ return ni.text[h]
+}
+
+// getLibName returns the name of the library associated with `noticeFor`.
+func (ni *NoticeIndex) getLibName(noticeFor *TargetNode) string {
+ // use name from METADATA if available
+ ln := ni.checkMetadata(noticeFor)
+ if len(ln) > 0 {
+ return ln
+ }
+ // use package_name: from license{} module if available
+ pn := noticeFor.PackageName()
+ if len(pn) > 0 {
+ return pn
+ }
+ for _, p := range noticeFor.Projects() {
+ if strings.HasPrefix(p, "prebuilts/") {
+ for _, licenseText := range noticeFor.LicenseTexts() {
+ if !strings.HasPrefix(licenseText, "prebuilts/") {
+ continue
+ }
+ for r, prefix := range SafePrebuiltPrefixes {
+ match := r.FindString(licenseText)
+ if len(match) == 0 {
+ continue
+ }
+ strip := SafePathPrefixes[prefix]
+ if strip {
+ // strip entire prefix
+ match = licenseText[len(match):]
+ } else {
+ // strip from prebuilts/ until safe prefix
+ match = licenseText[len(match)-len(prefix):]
+ }
+ // remove LICENSE or NOTICE or other filename
+ li := strings.LastIndex(match, "/")
+ if 0 < li {
+ match = match[:li]
+ }
+ // remove *licenses/ path segment and subdirectory if in path
+ if offsets := licensesPathRegexp.FindAllStringIndex(match, -1); offsets != nil && 0 < offsets[len(offsets)-1][0] {
+ match = match[:offsets[len(offsets)-1][0]]
+ li = strings.LastIndex(match, "/")
+ if 0 < li {
+ match = match[:li]
+ }
+ }
+ return match
+ }
+ break
+ }
+ }
+ for prefix, strip := range SafePathPrefixes {
+ if strings.HasPrefix(p, prefix) {
+ if strip {
+ return p[len(prefix):]
+ } else {
+ return p
+ }
+ }
+ }
+ }
+ // strip off [./]meta_lic from license metadata path and extract base name
+ n := noticeFor.name[:len(noticeFor.name)-9]
+ li := strings.LastIndex(n, "/")
+ if 0 < li {
+ n = n[li+1:]
+ }
+ return n
+}
+
+// checkMetadata tries to look up a library name from a METADATA file associated with `noticeFor`.
+func (ni *NoticeIndex) checkMetadata(noticeFor *TargetNode) string {
+ for _, p := range noticeFor.Projects() {
+ if name, ok := ni.projectName[p]; ok {
+ if name == noProjectName {
+ continue
+ }
+ return name
+ }
+ f, err := ni.rootFS.Open(filepath.Join(p, "METADATA"))
+ if err != nil {
+ ni.projectName[p] = noProjectName
+ continue
+ }
+ name := ""
+ description := ""
+ version := ""
+ s := bufio.NewScanner(f)
+ for s.Scan() {
+ line := s.Text()
+ m := nameRegexp.FindStringSubmatch(line)
+ if m != nil {
+ if 1 < len(m) && m[1] != "" {
+ name = m[1]
+ }
+ if version != "" {
+ break
+ }
+ continue
+ }
+ m = versionRegexp.FindStringSubmatch(line)
+ if m != nil {
+ if 1 < len(m) && m[1] != "" {
+ version = m[1]
+ }
+ if name != "" {
+ break
+ }
+ continue
+ }
+ m = descRegexp.FindStringSubmatch(line)
+ if m != nil {
+ if 1 < len(m) && m[1] != "" {
+ description = m[1]
+ }
+ }
+ }
+ _ = s.Err()
+ _ = f.Close()
+ if name != "" {
+ if version != "" {
+ if version[0] == 'v' || version[0] == 'V' {
+ ni.projectName[p] = name + "_" + version
+ } else {
+ ni.projectName[p] = name + "_v_" + version
+ }
+ } else {
+ ni.projectName[p] = name
+ }
+ return ni.projectName[p]
+ }
+ if description != "" {
+ ni.projectName[p] = description
+ return ni.projectName[p]
+ }
+ ni.projectName[p] = noProjectName
+ }
+ return ""
+}
+
+// addText reads and indexes the content of a license text file.
+func (ni *NoticeIndex) addText(file string) error {
+ f, err := ni.rootFS.Open(filepath.Clean(file))
+ if err != nil {
+ return fmt.Errorf("error opening license text file %q: %w", file, err)
+ }
+
+ // read the file
+ text, err := io.ReadAll(f)
+ if err != nil {
+ return fmt.Errorf("error reading license text file %q: %w", file, err)
+ }
+
+ hash := hash{fmt.Sprintf("%x", md5.Sum(text))}
+ ni.hash[file] = hash
+ if _, alreadyPresent := ni.text[hash]; !alreadyPresent {
+ ni.text[hash] = text
+ }
+
+ ni.files = append(ni.files, file)
+
+ return nil
+}
+
+// getInstallPaths returns the names of the used dependencies mapped to their
+// installed locations.
+func getInstallPaths(attachesTo *TargetNode, path TargetEdgePath) []string {
+ if len(path) == 0 {
+ installs := attachesTo.Installed()
+ if 0 == len(installs) {
+ installs = attachesTo.Built()
+ }
+ return installs
+ }
+
+ var getInstalls func(path TargetEdgePath) []string
+
+ getInstalls = func(path TargetEdgePath) []string {
+ // deps contains the output targets from the dependencies in the path
+ var deps []string
+ if len(path) > 1 {
+ // recursively get the targets from the sub-path skipping 1 path segment
+ deps = getInstalls(path[1:])
+ } else {
+ // stop recursion at 1 path segment
+ deps = path[0].Dependency().TargetFiles()
+ }
+ size := 0
+ prefixes := path[0].Target().TargetFiles()
+ installMap := path[0].Target().InstallMap()
+ sources := path[0].Target().Sources()
+ for _, dep := range deps {
+ found := false
+ for _, source := range sources {
+ if strings.HasPrefix(dep, source) {
+ found = true
+ break
+ }
+ }
+ if !found {
+ continue
+ }
+ for _, im := range installMap {
+ if strings.HasPrefix(dep, im.FromPath) {
+ size += len(prefixes)
+ break
+ }
+ }
+ }
+
+ installs := make([]string, 0, size)
+ for _, dep := range deps {
+ found := false
+ for _, source := range sources {
+ if strings.HasPrefix(dep, source) {
+ found = true
+ break
+ }
+ }
+ if !found {
+ continue
+ }
+ for _, im := range installMap {
+ if strings.HasPrefix(dep, im.FromPath) {
+ for _, prefix := range prefixes {
+ installs = append(installs, prefix+im.ContainerPath+dep[len(im.FromPath):])
+ }
+ break
+ }
+ }
+ }
+ return installs
+ }
+ allInstalls := getInstalls(path)
+ installs := path[0].Target().Installed()
+ if len(installs) == 0 {
+ return allInstalls
+ }
+ result := make([]string, 0, len(allInstalls))
+ for _, install := range allInstalls {
+ for _, prefix := range installs {
+ if strings.HasPrefix(install, prefix) {
+ result = append(result, install)
+ }
+ }
+ }
+ return result
+}
+
+// hash is an opaque string derived from md5sum.
+type hash struct {
+ key string
+}
+
+// String returns the hexadecimal representation of the hash.
+func (h hash) String() string {
+ return h.key
+}
+
+// hashList orders an array of hashes
+type hashList struct {
+ ni *NoticeIndex
+ libName string
+ installPath string
+ hashes *[]hash
+}
+
+// Len returns the count of elements in the slice.
+func (l hashList) Len() int { return len(*l.hashes) }
+
+// Swap rearranges 2 elements of the slice so that each occupies the other's
+// former position.
+func (l hashList) Swap(i, j int) { (*l.hashes)[i], (*l.hashes)[j] = (*l.hashes)[j], (*l.hashes)[i] }
+
+// Less returns true when the `i`th element is lexicographically less than
+// the `j`th element.
+func (l hashList) Less(i, j int) bool {
+ var insti, instj int
+ if 0 < len(l.libName) {
+ insti = len(l.ni.hashLibInstall[(*l.hashes)[i]][l.libName])
+ instj = len(l.ni.hashLibInstall[(*l.hashes)[j]][l.libName])
+ } else {
+ libsi := l.ni.InstallHashLibs(l.installPath, (*l.hashes)[i])
+ libsj := l.ni.InstallHashLibs(l.installPath, (*l.hashes)[j])
+ libsis := strings.Join(libsi, " ")
+ libsjs := strings.Join(libsj, " ")
+ if libsis != libsjs {
+ return libsis < libsjs
+ }
+ }
+ if insti == instj {
+ leni := len(l.ni.text[(*l.hashes)[i]])
+ lenj := len(l.ni.text[(*l.hashes)[j]])
+ if leni == lenj {
+ // all else equal, just order by hash value
+ return (*l.hashes)[i].key < (*l.hashes)[j].key
+ }
+ // put shortest texts first within same # of installs
+ return leni < lenj
+ }
+ // reverse order of # installs so that most popular appears first
+ return instj < insti
+}
diff --git a/tools/compliance/policy_policy.go b/tools/compliance/policy_policy.go
new file mode 100644
index 0000000..60bdf48
--- /dev/null
+++ b/tools/compliance/policy_policy.go
@@ -0,0 +1,289 @@
+// 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 (
+ // RecognizedAnnotations identifies the set of annotations that have
+ // meaning for compliance policy.
+ RecognizedAnnotations = map[string]string{
+ // used in readgraph.go to avoid creating 1000's of copies of the below 3 strings.
+ "static": "static",
+ "dynamic": "dynamic",
+ "toolchain": "toolchain",
+ }
+
+ // SafePathPrefixes maps the path prefixes presumed not to contain any
+ // proprietary or confidential pathnames to whether to strip the prefix
+ // from the path when used as the library name for notices.
+ SafePathPrefixes = map[string]bool{
+ "external/": true,
+ "art/": false,
+ "build/": false,
+ "cts/": false,
+ "dalvik/": false,
+ "developers/": false,
+ "development/": false,
+ "frameworks/": false,
+ "packages/": true,
+ "prebuilts/": false,
+ "sdk/": false,
+ "system/": false,
+ "test/": false,
+ "toolchain/": false,
+ "tools/": false,
+ }
+
+ // SafePrebuiltPrefixes maps the regular expression to match a prebuilt
+ // containing the path of a safe prefix to the safe prefix.
+ SafePrebuiltPrefixes = make(map[*regexp.Regexp]string)
+
+ // ImpliesUnencumbered lists the condition names representing an author attempt to disclaim copyright.
+ ImpliesUnencumbered = LicenseConditionSet(UnencumberedCondition)
+
+ // ImpliesPermissive lists the condition names representing copyrighted but "licensed without policy requirements".
+ ImpliesPermissive = LicenseConditionSet(PermissiveCondition)
+
+ // ImpliesNotice lists the condition names implying a notice or attribution policy.
+ ImpliesNotice = LicenseConditionSet(UnencumberedCondition | PermissiveCondition | NoticeCondition | ReciprocalCondition |
+ RestrictedCondition | RestrictedClasspathExceptionCondition | WeaklyRestrictedCondition |
+ ProprietaryCondition | ByExceptionOnlyCondition)
+
+ // ImpliesReciprocal lists the condition names implying a local source-sharing policy.
+ ImpliesReciprocal = LicenseConditionSet(ReciprocalCondition)
+
+ // Restricted lists the condition names implying an infectious source-sharing policy.
+ ImpliesRestricted = LicenseConditionSet(RestrictedCondition | RestrictedClasspathExceptionCondition | WeaklyRestrictedCondition)
+
+ // ImpliesProprietary lists the condition names implying a confidentiality policy.
+ ImpliesProprietary = LicenseConditionSet(ProprietaryCondition)
+
+ // ImpliesByExceptionOnly lists the condition names implying a policy for "license review and approval before use".
+ ImpliesByExceptionOnly = LicenseConditionSet(ProprietaryCondition | ByExceptionOnlyCondition)
+
+ // ImpliesPrivate lists the condition names implying a source-code privacy policy.
+ ImpliesPrivate = LicenseConditionSet(ProprietaryCondition)
+
+ // ImpliesShared lists the condition names implying a source-code sharing policy.
+ ImpliesShared = LicenseConditionSet(ReciprocalCondition | RestrictedCondition | RestrictedClasspathExceptionCondition | WeaklyRestrictedCondition)
+)
+
+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.*`)
+)
+
+func init() {
+ for prefix := range SafePathPrefixes {
+ if prefix == "prebuilts/" {
+ continue
+ }
+ r := regexp.MustCompile("^prebuilts/[^ ]*/" + prefix)
+ SafePrebuiltPrefixes[r] = prefix
+ }
+}
+
+// LicenseConditionSetFromNames returns a set containing the recognized `names` and
+// silently ignoring or discarding the unrecognized `names`.
+func LicenseConditionSetFromNames(tn *TargetNode, names ...string) LicenseConditionSet {
+ cs := NewLicenseConditionSet()
+ for _, name := range names {
+ if name == "restricted" {
+ if 0 == len(tn.LicenseKinds()) {
+ cs = cs.Plus(RestrictedCondition)
+ continue
+ }
+ hasLgpl := false
+ hasClasspath := false
+ hasGeneric := false
+ for _, kind := range tn.LicenseKinds() {
+ if strings.HasSuffix(kind, "-with-classpath-exception") {
+ cs = cs.Plus(RestrictedClasspathExceptionCondition)
+ hasClasspath = true
+ } else if anyLgpl.MatchString(kind) {
+ cs = cs.Plus(WeaklyRestrictedCondition)
+ hasLgpl = true
+ } else if versionedGpl.MatchString(kind) {
+ cs = cs.Plus(RestrictedCondition)
+ } else if genericGpl.MatchString(kind) {
+ hasGeneric = true
+ } else if kind == "legacy_restricted" || ccBySa.MatchString(kind) {
+ cs = cs.Plus(RestrictedCondition)
+ } else {
+ cs = cs.Plus(RestrictedCondition)
+ }
+ }
+ if hasGeneric && !hasLgpl && !hasClasspath {
+ cs = cs.Plus(RestrictedCondition)
+ }
+ continue
+ }
+ if lc, ok := RecognizedConditionNames[name]; ok {
+ cs |= LicenseConditionSet(lc)
+ }
+ }
+ return cs
+}
+
+// Resolution happens in three phases:
+//
+// 1. A bottom-up traversal propagates (restricted) license conditions up to
+// targets from dendencies as needed.
+//
+// 2. For each condition of interest, a top-down traversal propagates
+// (restricted) conditions down from targets into linked dependencies.
+//
+// 3. Finally, a walk of the shipped target nodes attaches resolutions to the
+// ancestor nodes from the root down to and including the first non-container.
+//
+// e.g. If a disk image contains a binary bin1 that links a library liba, the
+// notice requirement for liba gets attached to the disk image and to bin1.
+// Because liba doesn't actually get shipped as a separate artifact, but only
+// as bits in bin1, it has no actions 'attached' to it. The actions attached
+// to the image and to bin1 'act on' liba by providing notice.
+//
+// The behavior of the 3 phases gets controlled by the 3 functions below.
+//
+// The first function controls what happens during the bottom-up propagation.
+// Restricted conditions propagate up all non-toolchain dependencies; except,
+// some do not propagate up dynamic links, which may depend on whether the
+// modules are independent.
+//
+// The second function controls what happens during the top-down propagation.
+// Restricted conditions propagate down as above with the added caveat that
+// inherited restricted conditions do not propagate from pure aggregates to
+// their dependencies.
+//
+// The final function controls which conditions apply/get attached to ancestors
+// depending on the types of dependencies involved. All conditions apply across
+// normal derivation dependencies. No conditions apply across toolchain
+// dependencies. Some restricted conditions apply across dynamic link
+// dependencies.
+//
+// Not all restricted licenses are create equal. Some have special rules or
+// exceptions. e.g. LGPL or "with classpath excption".
+
+// depConditionsPropagatingToTarget returns the conditions which propagate up an
+// edge from dependency to target.
+//
+// This function sets the policy for the bottom-up propagation 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 depConditionsPropagatingToTarget(lg *LicenseGraph, e *TargetEdge, depConditions LicenseConditionSet, treatAsAggregate bool) LicenseConditionSet {
+ result := LicenseConditionSet(0x0000)
+ if edgeIsDerivation(e) {
+ result |= depConditions & ImpliesRestricted
+ return result
+ }
+ if !edgeIsDynamicLink(e) {
+ return result
+ }
+
+ result |= depConditions & LicenseConditionSet(RestrictedCondition)
+ if 0 != (depConditions&LicenseConditionSet(RestrictedClasspathExceptionCondition)) && !edgeNodesAreIndependentModules(e) {
+ result |= LicenseConditionSet(RestrictedClasspathExceptionCondition)
+ }
+ return result
+}
+
+// targetConditionsPropagatingToDep 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 targetConditionsPropagatingToDep(lg *LicenseGraph, e *TargetEdge, targetConditions LicenseConditionSet, treatAsAggregate bool, conditionsFn TraceConditions) LicenseConditionSet {
+ result := targetConditions
+
+ // reverse direction -- none of these apply to things depended-on, only to targets depending-on.
+ result = result.Minus(UnencumberedCondition, PermissiveCondition, NoticeCondition, ReciprocalCondition, ProprietaryCondition, ByExceptionOnlyCondition)
+
+ if !edgeIsDerivation(e) && !edgeIsDynamicLink(e) {
+ // target is not a derivative work of dependency and is not linked to dependency
+ result = result.Difference(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.
+ if !conditionsFn(e.target).MatchesAnySet(ImpliesRestricted) {
+ result = result.Difference(ImpliesRestricted)
+ }
+ return result
+ }
+ if edgeIsDerivation(e) {
+ return result
+ }
+ result = result.Minus(WeaklyRestrictedCondition)
+ if edgeNodesAreIndependentModules(e) {
+ result = result.Minus(RestrictedClasspathExceptionCondition)
+ }
+ return result
+}
+
+// conditionsAttachingAcrossEdge returns the subset of conditions in `universe`
+// that apply across edge `e`.
+//
+// This function sets the policy for attaching actions to ancestor nodes in the
+// final resolution walk.
+func conditionsAttachingAcrossEdge(lg *LicenseGraph, e *TargetEdge, universe LicenseConditionSet) LicenseConditionSet {
+ result := universe
+ if edgeIsDerivation(e) {
+ return result
+ }
+ if !edgeIsDynamicLink(e) {
+ return NewLicenseConditionSet()
+ }
+
+ result &= LicenseConditionSet(RestrictedCondition | RestrictedClasspathExceptionCondition)
+ if 0 != (result&LicenseConditionSet(RestrictedClasspathExceptionCondition)) && edgeNodesAreIndependentModules(e) {
+ result &= LicenseConditionSet(RestrictedCondition)
+ }
+ return result
+}
+
+// edgeIsDynamicLink returns true for edges representing shared libraries
+// linked dynamically at runtime.
+func edgeIsDynamicLink(e *TargetEdge) bool {
+ return 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.annotations.HasAnnotation("dynamic")
+ isToolchain := 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..27ce16c
--- /dev/null
+++ b/tools/compliance/policy_policy_test.go
@@ -0,0 +1,309 @@
+// 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{},
+ expectedTargetConditions: []string{},
+ },
+ {
+ name: "notice",
+ edge: annotated{"mitBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+ expectedDepActions: []string{},
+ expectedTargetConditions: []string{},
+ },
+ {
+ name: "fponlgpl",
+ edge: annotated{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"static"}},
+ expectedDepActions: []string{
+ "apacheBin.meta_lic:lgplLib.meta_lic:restricted_allows_dynamic_linking",
+ "lgplLib.meta_lic:lgplLib.meta_lic:restricted_allows_dynamic_linking",
+ },
+ 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_with_classpath_exception",
+ "gplWithClasspathException.meta_lic:gplWithClasspathException.meta_lic:restricted_with_classpath_exception",
+ },
+ expectedTargetConditions: []string{},
+ },
+ {
+ name: "dependentmodule",
+ edge: annotated{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+ expectedDepActions: []string{
+ "dependentModule.meta_lic:gplWithClasspathException.meta_lic:restricted_with_classpath_exception",
+ "gplWithClasspathException.meta_lic:gplWithClasspathException.meta_lic:restricted_with_classpath_exception",
+ },
+ expectedTargetConditions: []string{},
+ },
+
+ {
+ name: "lgplonfp",
+ edge: annotated{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+ expectedDepActions: []string{},
+ expectedTargetConditions: []string{"lgplBin.meta_lic:restricted_allows_dynamic_linking"},
+ },
+ {
+ 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{},
+ expectedTargetConditions: []string{"gplBin.meta_lic:restricted"},
+ },
+ {
+ name: "gplcontainer",
+ edge: annotated{"gplContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+ treatAsAggregate: true,
+ expectedDepActions: []string{},
+ 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: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: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{},
+ expectedTargetConditions: []string{"gplWithClasspathException.meta_lic:restricted_with_classpath_exception"},
+ },
+ {
+ name: "dependentmodulereverse",
+ edge: annotated{"gplWithClasspathException.meta_lic", "dependentModule.meta_lic", []string{"dynamic"}},
+ expectedDepActions: []string{},
+ expectedTargetConditions: []string{"gplWithClasspathException.meta_lic:restricted_with_classpath_exception"},
+ },
+ {
+ 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{},
+ expectedTargetConditions: []string{"gplBin.meta_lic:restricted"},
+ },
+ {
+ name: "noticeonb_e_o",
+ edge: annotated{"mitBin.meta_lic", "by_exception.meta_lic", []string{"static"}},
+ expectedDepActions: []string{},
+ expectedTargetConditions: []string{},
+ },
+ {
+ name: "b_e_oonnotice",
+ edge: annotated{"by_exception.meta_lic", "mitLib.meta_lic", []string{"static"}},
+ expectedDepActions: []string{},
+ expectedTargetConditions: []string{},
+ },
+ {
+ name: "noticeonrecip",
+ edge: annotated{"mitBin.meta_lic", "mplLib.meta_lic", []string{"static"}},
+ expectedDepActions: []string{},
+ expectedTargetConditions: []string{},
+ },
+ {
+ name: "reciponnotice",
+ edge: annotated{"mplBin.meta_lic", "mitLib.meta_lic", []string{"static"}},
+ expectedDepActions: []string{},
+ 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: %s", err)
+ return
+ }
+ edge := lg.Edges()[0]
+ // simulate a condition inherited from another edge/dependency.
+ otherTarget := ""
+ otherCondition := ""
+ var otn *TargetNode
+ if len(tt.otherCondition) > 0 {
+ fields := strings.Split(tt.otherCondition, ":")
+ otherTarget = fields[0]
+ otherCondition = fields[1]
+ otn = &TargetNode{name: otherTarget}
+ // other target must exist in graph
+ lg.targets[otherTarget] = otn
+ otn.licenseConditions = LicenseConditionSet(RecognizedConditionNames[otherCondition])
+ }
+ targets := make(map[string]*TargetNode)
+ targets[edge.target.name] = edge.target
+ targets[edge.dependency.name] = edge.dependency
+ if otn != nil {
+ targets[otn.name] = otn
+ }
+ if tt.expectedDepActions != nil {
+ t.Run("depConditionsPropagatingToTarget", func(t *testing.T) {
+ depConditions := edge.dependency.LicenseConditions()
+ if otherTarget != "" {
+ // simulate a sub-dependency's condition having already propagated up to dep and about to go to target
+ otherCs := otn.LicenseConditions()
+ depConditions |= otherCs
+ }
+ t.Logf("calculate target actions for edge=%s, dep conditions=%04x, treatAsAggregate=%v", edge.String(), depConditions, tt.treatAsAggregate)
+ csActual := depConditionsPropagatingToTarget(lg, edge, depConditions, tt.treatAsAggregate)
+ t.Logf("calculated target conditions as %04x{%s}", csActual, strings.Join(csActual.Names(), ", "))
+ csExpected := NewLicenseConditionSet()
+ for _, triple := range tt.expectedDepActions {
+ fields := strings.Split(triple, ":")
+ expectedConditions := NewLicenseConditionSet()
+ for _, cname := range fields[2:] {
+ expectedConditions = expectedConditions.Plus(RecognizedConditionNames[cname])
+ }
+ csExpected |= expectedConditions
+ }
+ t.Logf("expected target conditions as %04x{%s}", csExpected, strings.Join(csExpected.Names(), ", "))
+ if csActual != csExpected {
+ t.Errorf("unexpected license conditions: got %04x, want %04x", csActual, csExpected)
+ }
+ })
+ }
+ if tt.expectedTargetConditions != nil {
+ t.Run("targetConditionsPropagatingToDep", func(t *testing.T) {
+ targetConditions := edge.target.LicenseConditions()
+ if otherTarget != "" {
+ targetConditions = targetConditions.Union(otn.licenseConditions)
+ }
+ t.Logf("calculate dep conditions for edge=%s, target conditions=%v, treatAsAggregate=%v", edge.String(), targetConditions.Names(), tt.treatAsAggregate)
+ cs := targetConditionsPropagatingToDep(lg, edge, targetConditions, tt.treatAsAggregate, AllResolutions)
+ t.Logf("calculated dep conditions as %v", cs.Names())
+ actual := cs.Names()
+ sort.Strings(actual)
+ expected := make([]string, 0)
+ for _, expectedDepCondition := range tt.expectedTargetConditions {
+ expected = append(expected, strings.Split(expectedDepCondition, ":")[1])
+ }
+ sort.Strings(expected)
+ if len(actual) != len(expected) {
+ t.Errorf("unexpected number of target conditions: got %v with %d conditions, want %v with %d conditions",
+ actual, len(actual), expected, len(expected))
+ } else {
+ for i := 0; i < len(actual); i++ {
+ if actual[i] != expected[i] {
+ t.Errorf("unexpected target condition at element %d: got %q, want %q",
+ i, actual[i], expected[i])
+ }
+ }
+ }
+ })
+ }
+ })
+ }
+}
diff --git a/tools/compliance/policy_resolve.go b/tools/compliance/policy_resolve.go
new file mode 100644
index 0000000..d357aec
--- /dev/null
+++ b/tools/compliance/policy_resolve.go
@@ -0,0 +1,234 @@
+// 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 (
+ "sync"
+)
+
+var (
+ // AllResolutions is a TraceConditions function that resolves all
+ // unfiltered license conditions.
+ AllResolutions = TraceConditions(func(tn *TargetNode) LicenseConditionSet { return tn.licenseConditions })
+)
+
+// TraceConditions is a function that returns the conditions to trace for each
+// target node `tn`.
+type TraceConditions func(tn *TargetNode) LicenseConditionSet
+
+// 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.
+//
+// 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) {
+ TraceBottomUpConditions(lg, AllResolutions)
+}
+
+// TraceBottomUpConditions performs a bottom-up walk of the LicenseGraph
+// propagating trace conditions from `conditionsFn` up the graph as necessary
+// according to the properties of each edge and according to each license
+// condition in question.
+func TraceBottomUpConditions(lg *LicenseGraph, conditionsFn TraceConditions) {
+
+ // short-cut if already walked and cached
+ lg.mu.Lock()
+ wg := lg.wgBU
+
+ if wg != nil {
+ lg.mu.Unlock()
+ wg.Wait()
+ return
+ }
+ wg = &sync.WaitGroup{}
+ wg.Add(1)
+ lg.wgBU = wg
+ lg.mu.Unlock()
+
+ // amap identifes targets previously walked. (guarded by mu)
+ amap := make(map[*TargetNode]struct{})
+
+ // cmap identifies targets previously walked as pure aggregates. i.e. as containers
+ // (guarded by mu)
+ cmap := make(map[*TargetNode]struct{})
+ var mu sync.Mutex
+
+ var walk func(target *TargetNode, treatAsAggregate bool) LicenseConditionSet
+
+ walk = func(target *TargetNode, treatAsAggregate bool) LicenseConditionSet {
+ priorWalkResults := func() (LicenseConditionSet, bool) {
+ mu.Lock()
+ defer mu.Unlock()
+
+ if _, alreadyWalked := amap[target]; alreadyWalked {
+ if treatAsAggregate {
+ return target.resolution, true
+ }
+ if _, asAggregate := cmap[target]; !asAggregate {
+ return target.resolution, true
+ }
+ // previously walked in a pure aggregate context,
+ // needs to walk again in non-aggregate context
+ delete(cmap, target)
+ } else {
+ target.resolution |= conditionsFn(target)
+ amap[target] = struct{}{}
+ }
+ if treatAsAggregate {
+ cmap[target] = struct{}{}
+ }
+ return target.resolution, false
+ }
+ cs, alreadyWalked := priorWalkResults()
+ if alreadyWalked {
+ return cs
+ }
+
+ c := make(chan LicenseConditionSet, len(target.edges))
+ // add all the conditions from all the dependencies
+ for _, edge := range target.edges {
+ go func(edge *TargetEdge) {
+ // walk dependency to get its conditions
+ cs := walk(edge.dependency, treatAsAggregate && edge.dependency.IsContainer())
+
+ // turn those into the conditions that apply to the target
+ cs = depConditionsPropagatingToTarget(lg, edge, cs, treatAsAggregate)
+
+ c <- cs
+ }(edge)
+ }
+ for i := 0; i < len(target.edges); i++ {
+ cs |= <-c
+ }
+ mu.Lock()
+ target.resolution |= cs
+ mu.Unlock()
+
+ // return conditions up the tree
+ return cs
+ }
+
+ // walk each of the roots
+ for _, rname := range lg.rootFiles {
+ rnode := lg.targets[rname]
+ _ = walk(rnode, rnode.IsContainer())
+ }
+
+ wg.Done()
+}
+
+// ResolveTopDownCondtions performs a top-down walk of the LicenseGraph
+// propagating conditions from target to dependency.
+//
+// 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) {
+ TraceTopDownConditions(lg, AllResolutions)
+}
+
+// TraceTopDownCondtions performs a top-down walk of the LicenseGraph
+// propagating trace conditions returned by `conditionsFn` from target to
+// dependency.
+func TraceTopDownConditions(lg *LicenseGraph, conditionsFn TraceConditions) {
+
+ // short-cut if already walked and cached
+ lg.mu.Lock()
+ wg := lg.wgTD
+
+ if wg != nil {
+ lg.mu.Unlock()
+ wg.Wait()
+ return
+ }
+ wg = &sync.WaitGroup{}
+ wg.Add(1)
+ lg.wgTD = wg
+ lg.mu.Unlock()
+
+ // start with the conditions propagated up the graph
+ TraceBottomUpConditions(lg, conditionsFn)
+
+ // amap contains the set of targets already walked. (guarded by mu)
+ amap := make(map[*TargetNode]struct{})
+
+ // cmap contains the set of targets walked as pure aggregates. i.e. containers
+ // (guarded by mu)
+ cmap := make(map[*TargetNode]struct{})
+
+ // mu guards concurrent access to cmap
+ var mu sync.Mutex
+
+ var walk func(fnode *TargetNode, cs LicenseConditionSet, treatAsAggregate bool)
+
+ walk = func(fnode *TargetNode, cs LicenseConditionSet, treatAsAggregate bool) {
+ defer wg.Done()
+ mu.Lock()
+ fnode.resolution |= conditionsFn(fnode)
+ fnode.resolution |= cs
+ amap[fnode] = struct{}{}
+ if treatAsAggregate {
+ cmap[fnode] = struct{}{}
+ }
+ cs = fnode.resolution
+ mu.Unlock()
+ // for each dependency
+ for _, edge := range fnode.edges {
+ func(edge *TargetEdge) {
+ // dcs holds the dpendency conditions inherited from the target
+ dcs := targetConditionsPropagatingToDep(lg, edge, cs, treatAsAggregate, conditionsFn)
+ dnode := edge.dependency
+ mu.Lock()
+ defer mu.Unlock()
+ depcs := dnode.resolution
+ _, alreadyWalked := amap[dnode]
+ if !dcs.IsEmpty() && alreadyWalked {
+ if dcs.Difference(depcs).IsEmpty() {
+ // no new conditions
+
+ // pure aggregates never need walking a 2nd time with same conditions
+ if treatAsAggregate {
+ return
+ }
+ // non-aggregates don't need walking as non-aggregate a 2nd time
+ if _, asAggregate := cmap[dnode]; !asAggregate {
+ return
+ }
+ // previously walked as pure aggregate; need to re-walk as non-aggregate
+ delete(cmap, dnode)
+ }
+ }
+ // add the conditions to the dependency
+ wg.Add(1)
+ go walk(dnode, dcs, treatAsAggregate && dnode.IsContainer())
+ }(edge)
+ }
+ }
+
+ // walk each of the roots
+ for _, rname := range lg.rootFiles {
+ rnode := lg.targets[rname]
+ wg.Add(1)
+ // add the conditions to the root and its transitive closure
+ go walk(rnode, NewLicenseConditionSet(), rnode.IsContainer())
+ }
+ wg.Done()
+ wg.Wait()
+}
diff --git a/tools/compliance/policy_resolve_test.go b/tools/compliance/policy_resolve_test.go
new file mode 100644
index 0000000..f98e4cc
--- /dev/null
+++ b/tools/compliance/policy_resolve_test.go
@@ -0,0 +1,672 @@
+// 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 TestResolveBottomUpConditions(t *testing.T) {
+ tests := []struct {
+ name string
+ roots []string
+ edges []annotated
+ expectedActions []tcond
+ }{
+ {
+ name: "firstparty",
+ roots: []string{"apacheBin.meta_lic"},
+ edges: []annotated{
+ {"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+ },
+ expectedActions: []tcond{
+ {"apacheBin.meta_lic", "notice"},
+ {"apacheLib.meta_lic", "notice"},
+ },
+ },
+ {
+ name: "firstpartytool",
+ roots: []string{"apacheBin.meta_lic"},
+ edges: []annotated{
+ {"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"toolchain"}},
+ },
+ expectedActions: []tcond{
+ {"apacheBin.meta_lic", "notice"},
+ {"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"}},
+ },
+ expectedActions: []tcond{
+ {"apacheContainer.meta_lic", "notice"},
+ {"apacheBin.meta_lic", "notice"},
+ {"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"}},
+ },
+ expectedActions: []tcond{
+ {"apacheContainer.meta_lic", "notice"},
+ {"apacheBin.meta_lic", "notice"},
+ {"apacheLib.meta_lic", "notice"},
+ },
+ },
+ {
+ name: "firstpartydynamic",
+ roots: []string{"apacheBin.meta_lic"},
+ edges: []annotated{
+ {"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+ },
+ expectedActions: []tcond{
+ {"apacheBin.meta_lic", "notice"},
+ {"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"}},
+ },
+ expectedActions: []tcond{
+ {"apacheContainer.meta_lic", "notice"},
+ {"apacheBin.meta_lic", "notice"},
+ {"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"}},
+ },
+ expectedActions: []tcond{
+ {"apacheContainer.meta_lic", "notice"},
+ {"apacheBin.meta_lic", "notice"},
+ {"apacheLib.meta_lic", "notice"},
+ },
+ },
+ {
+ name: "restricted",
+ roots: []string{"apacheBin.meta_lic"},
+ edges: []annotated{
+ {"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}},
+ },
+ expectedActions: []tcond{
+ {"apacheBin.meta_lic", "notice|restricted"},
+ {"gplLib.meta_lic", "restricted"},
+ },
+ },
+ {
+ name: "restrictedtool",
+ roots: []string{"apacheBin.meta_lic"},
+ edges: []annotated{
+ {"apacheBin.meta_lic", "gplLib.meta_lic", []string{"toolchain"}},
+ },
+ expectedActions: []tcond{
+ {"apacheBin.meta_lic", "notice"},
+ {"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"}},
+ },
+ expectedActions: []tcond{
+ {"apacheContainer.meta_lic", "notice|restricted"},
+ {"apacheBin.meta_lic", "notice|restricted"},
+ {"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"}},
+ },
+ expectedActions: []tcond{
+ {"apacheContainer.meta_lic", "notice|restricted"},
+ {"apacheBin.meta_lic", "notice"},
+ {"gplLib.meta_lic", "restricted"},
+ },
+ },
+ {
+ name: "restricteddynamic",
+ roots: []string{"apacheBin.meta_lic"},
+ edges: []annotated{
+ {"apacheBin.meta_lic", "gplLib.meta_lic", []string{"dynamic"}},
+ },
+ expectedActions: []tcond{
+ {"apacheBin.meta_lic", "notice|restricted"},
+ {"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"}},
+ },
+ expectedActions: []tcond{
+ {"apacheContainer.meta_lic", "notice|restricted"},
+ {"apacheBin.meta_lic", "notice|restricted"},
+ {"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"}},
+ },
+ expectedActions: []tcond{
+ {"apacheContainer.meta_lic", "notice|restricted"},
+ {"apacheBin.meta_lic", "notice"},
+ {"gplLib.meta_lic", "restricted"},
+ },
+ },
+ {
+ name: "weakrestricted",
+ roots: []string{"apacheBin.meta_lic"},
+ edges: []annotated{
+ {"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"static"}},
+ },
+ expectedActions: []tcond{
+ {"apacheBin.meta_lic", "notice|restricted_allows_dynamic_linking"},
+ {"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
+ },
+ },
+ {
+ name: "weakrestrictedtool",
+ roots: []string{"apacheBin.meta_lic"},
+ edges: []annotated{
+ {"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"toolchain"}},
+ },
+ expectedActions: []tcond{
+ {"apacheBin.meta_lic", "notice"},
+ {"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
+ },
+ },
+ {
+ 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"}},
+ },
+ expectedActions: []tcond{
+ {"apacheContainer.meta_lic", "notice|restricted_allows_dynamic_linking"},
+ {"apacheBin.meta_lic", "notice|restricted_allows_dynamic_linking"},
+ {"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
+ },
+ },
+ {
+ 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"}},
+ },
+ expectedActions: []tcond{
+ {"apacheContainer.meta_lic", "notice|restricted_allows_dynamic_linking"},
+ {"apacheBin.meta_lic", "notice"},
+ {"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
+ },
+ },
+ {
+ name: "weakrestricteddynamic",
+ roots: []string{"apacheBin.meta_lic"},
+ edges: []annotated{
+ {"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}},
+ },
+ expectedActions: []tcond{
+ {"apacheBin.meta_lic", "notice"},
+ {"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
+ },
+ },
+ {
+ 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"}},
+ },
+ expectedActions: []tcond{
+ {"apacheContainer.meta_lic", "notice"},
+ {"apacheBin.meta_lic", "notice"},
+ {"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
+ },
+ },
+ {
+ 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"}},
+ },
+ expectedActions: []tcond{
+ {"apacheContainer.meta_lic", "notice"},
+ {"apacheBin.meta_lic", "notice"},
+ {"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
+ },
+ },
+ {
+ name: "classpath",
+ roots: []string{"apacheBin.meta_lic"},
+ edges: []annotated{
+ {"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}},
+ },
+ expectedActions: []tcond{
+ {"apacheBin.meta_lic", "notice|restricted_with_classpath_exception"},
+ {"gplWithClasspathException.meta_lic", "restricted_with_classpath_exception"},
+ },
+ },
+ {
+ name: "classpathdependent",
+ roots: []string{"dependentModule.meta_lic"},
+ edges: []annotated{
+ {"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}},
+ },
+ expectedActions: []tcond{
+ {"dependentModule.meta_lic", "notice|restricted_with_classpath_exception"},
+ {"gplWithClasspathException.meta_lic", "restricted_with_classpath_exception"},
+ },
+ },
+ {
+ name: "classpathdynamic",
+ roots: []string{"apacheBin.meta_lic"},
+ edges: []annotated{
+ {"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+ },
+ expectedActions: []tcond{
+ {"apacheBin.meta_lic", "notice"},
+ {"gplWithClasspathException.meta_lic", "restricted_with_classpath_exception"},
+ },
+ },
+ {
+ name: "classpathdependentdynamic",
+ roots: []string{"dependentModule.meta_lic"},
+ edges: []annotated{
+ {"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}},
+ },
+ expectedActions: []tcond{
+ {"dependentModule.meta_lic", "notice|restricted_with_classpath_exception"},
+ {"gplWithClasspathException.meta_lic", "restricted_with_classpath_exception"},
+ },
+ },
+ }
+ 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 %s, want no error", err)
+ return
+ }
+
+ logGraph(lg, t)
+
+ ResolveBottomUpConditions(lg)
+ actual := asActionList(lg)
+ sort.Sort(actual)
+ t.Logf("actual: %s", actual.String())
+
+ expected := toActionList(lg, tt.expectedActions)
+ sort.Sort(expected)
+ t.Logf("expected: %s", expected.String())
+
+ if len(actual) != len(expected) {
+ t.Errorf("unexpected number of actions: got %d, want %d", len(actual), len(expected))
+ return
+ }
+ for i := 0; i < len(actual); i++ {
+ if actual[i] != expected[i] {
+ t.Errorf("unexpected action at index %d: got %s, want %s", i, actual[i].String(), expected[i].String())
+ }
+ }
+ })
+ }
+}
+
+func TestResolveTopDownConditions(t *testing.T) {
+ tests := []struct {
+ name string
+ roots []string
+ edges []annotated
+ expectedActions []tcond
+ }{
+ {
+ name: "firstparty",
+ roots: []string{"apacheBin.meta_lic"},
+ edges: []annotated{
+ {"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+ },
+ expectedActions: []tcond{
+ {"apacheBin.meta_lic", "notice"},
+ {"apacheLib.meta_lic", "notice"},
+ },
+ },
+ {
+ name: "firstpartydynamic",
+ roots: []string{"apacheBin.meta_lic"},
+ edges: []annotated{
+ {"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}},
+ },
+ expectedActions: []tcond{
+ {"apacheBin.meta_lic", "notice"},
+ {"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"}},
+ },
+ expectedActions: []tcond{
+ {"apacheBin.meta_lic", "notice|restricted"},
+ {"mitLib.meta_lic", "notice|restricted"},
+ {"gplLib.meta_lic", "restricted"},
+ },
+ },
+ {
+ 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"}},
+ },
+ expectedActions: []tcond{
+ {"apacheBin.meta_lic", "notice"},
+ {"mitLib.meta_lic", "notice"},
+ {"gplBin.meta_lic", "restricted"},
+ },
+ },
+ {
+ 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"}},
+ },
+ expectedActions: []tcond{
+ {"apacheContainer.meta_lic", "notice|restricted"},
+ {"apacheBin.meta_lic", "notice|restricted"},
+ {"mitBin.meta_lic", "notice"},
+ {"gplLib.meta_lic", "restricted"},
+ {"mplLib.meta_lic", "reciprocal|restricted"},
+ {"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"}},
+ },
+ expectedActions: []tcond{
+ {"apacheContainer.meta_lic", "notice|restricted"},
+ {"apacheBin.meta_lic", "notice"},
+ {"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"}},
+ },
+ expectedActions: []tcond{
+ {"apacheBin.meta_lic", "notice|restricted"},
+ {"gplLib.meta_lic", "restricted"},
+ {"mitLib.meta_lic", "notice|restricted"},
+ },
+ },
+ {
+ 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"}},
+ },
+ expectedActions: []tcond{
+ {"apacheContainer.meta_lic", "notice|restricted"},
+ {"apacheBin.meta_lic", "notice|restricted"},
+ {"mitBin.meta_lic", "notice"},
+ {"gplLib.meta_lic", "restricted"},
+ {"mplLib.meta_lic", "reciprocal|restricted"},
+ {"mitLib.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"}},
+ },
+ expectedActions: []tcond{
+ {"apacheContainer.meta_lic", "notice|restricted"},
+ {"apacheBin.meta_lic", "notice"},
+ {"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"}},
+ },
+ expectedActions: []tcond{
+ {"apacheBin.meta_lic", "notice|restricted_allows_dynamic_linking"},
+ {"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
+ {"mitLib.meta_lic", "notice|restricted_allows_dynamic_linking"},
+ },
+ },
+ {
+ 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"}},
+ },
+ expectedActions: []tcond{
+ {"apacheBin.meta_lic", "notice"},
+ {"lgplBin.meta_lic", "restricted_allows_dynamic_linking"},
+ {"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"}},
+ },
+ expectedActions: []tcond{
+ {"apacheContainer.meta_lic", "notice|restricted_allows_dynamic_linking"},
+ {"apacheBin.meta_lic", "notice|restricted_allows_dynamic_linking"},
+ {"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
+ {"mitLib.meta_lic", "notice|restricted_allows_dynamic_linking"},
+ },
+ },
+ {
+ 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"}},
+ },
+ expectedActions: []tcond{
+ {"apacheContainer.meta_lic", "notice|restricted_allows_dynamic_linking"},
+ {"apacheBin.meta_lic", "notice"},
+ {"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
+ },
+ },
+ {
+ 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"}},
+ },
+ expectedActions: []tcond{
+ {"apacheBin.meta_lic", "notice"},
+ {"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
+ {"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"}},
+ },
+ expectedActions: []tcond{
+ {"apacheContainer.meta_lic", "notice"},
+ {"apacheBin.meta_lic", "notice"},
+ {"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
+ },
+ },
+ {
+ 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"}},
+ },
+ expectedActions: []tcond{
+ {"apacheContainer.meta_lic", "notice"},
+ {"apacheBin.meta_lic", "notice"},
+ {"lgplLib.meta_lic", "restricted_allows_dynamic_linking"},
+ },
+ },
+ {
+ 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"}},
+ },
+ expectedActions: []tcond{
+ {"apacheBin.meta_lic", "notice|restricted_with_classpath_exception"},
+ {"gplWithClasspathException.meta_lic", "restricted_with_classpath_exception"},
+ {"mitLib.meta_lic", "notice|restricted_with_classpath_exception"},
+ },
+ },
+ {
+ 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"}},
+ },
+ expectedActions: []tcond{
+ {"dependentModule.meta_lic", "notice|restricted_with_classpath_exception"},
+ {"gplWithClasspathException.meta_lic", "restricted_with_classpath_exception"},
+ {"mitLib.meta_lic", "notice|restricted_with_classpath_exception"},
+ },
+ },
+ {
+ 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"}},
+ },
+ expectedActions: []tcond{
+ {"apacheBin.meta_lic", "notice"},
+ {"gplWithClasspathException.meta_lic", "restricted_with_classpath_exception"},
+ {"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"}},
+ },
+ expectedActions: []tcond{
+ {"dependentModule.meta_lic", "notice|restricted_with_classpath_exception"},
+ {"gplWithClasspathException.meta_lic", "restricted_with_classpath_exception"},
+ {"mitLib.meta_lic", "notice|restricted_with_classpath_exception"},
+ },
+ },
+ }
+ 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 %s, want no error", err)
+ return
+ }
+
+ logGraph(lg, t)
+
+ ResolveTopDownConditions(lg)
+ actual := asActionList(lg)
+ sort.Sort(actual)
+ t.Logf("actual: %s", actual.String())
+
+ expected := toActionList(lg, tt.expectedActions)
+ sort.Sort(expected)
+ t.Logf("expected: %s", expected.String())
+
+ if len(actual) != len(expected) {
+ t.Errorf("unexpected number of actions: got %d, want %d", len(actual), len(expected))
+ return
+ }
+ for i := 0; i < len(actual); i++ {
+ if actual[i] != expected[i] {
+ t.Errorf("unexpected action at index %d: got %s, want %s", i, actual[i].String(), expected[i].String())
+ }
+ }
+ })
+ }
+}
diff --git a/tools/compliance/policy_resolvenotices.go b/tools/compliance/policy_resolvenotices.go
new file mode 100644
index 0000000..99f6d42
--- /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 {
+ ResolveTopDownConditions(lg)
+ return WalkResolutionsForCondition(lg, ImpliesNotice)
+}
diff --git a/tools/compliance/policy_resolvenotices_test.go b/tools/compliance/policy_resolvenotices_test.go
new file mode 100644
index 0000000..cd9dd71
--- /dev/null
+++ b/tools/compliance/policy_resolvenotices_test.go
@@ -0,0 +1,468 @@
+// 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", "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 %s, 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..2a7992e
--- /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 {
+ ResolveTopDownConditions(lg)
+ return WalkResolutionsForCondition(lg, ImpliesPrivate)
+}
diff --git a/tools/compliance/policy_resolveprivacy_test.go b/tools/compliance/policy_resolveprivacy_test.go
new file mode 100644
index 0000000..e8c953a
--- /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 %s, want no error", err)
+ return
+ }
+ expectedRs := toResolutionSet(lg, tt.expectedResolutions)
+ actualRs := ResolveSourcePrivacy(lg)
+ checkResolves(actualRs, expectedRs, t)
+ })
+ }
+}
diff --git a/tools/compliance/policy_resolveshare.go b/tools/compliance/policy_resolveshare.go
new file mode 100644
index 0000000..9b6a8bb
--- /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 {
+ ResolveTopDownConditions(lg)
+ return WalkResolutionsForCondition(lg, ImpliesShared)
+}
diff --git a/tools/compliance/policy_resolveshare_test.go b/tools/compliance/policy_resolveshare_test.go
new file mode 100644
index 0000000..c451b86
--- /dev/null
+++ b/tools/compliance/policy_resolveshare_test.go
@@ -0,0 +1,297 @@
+// 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"},
+ {"apacheLib.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"},
+ {"dependentModule.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 %s, want no error", err)
+ return
+ }
+ expectedRs := toResolutionSet(lg, tt.expectedResolutions)
+ actualRs := ResolveSourceSharing(lg)
+ checkResolves(actualRs, expectedRs, t)
+ })
+ }
+}
diff --git a/tools/compliance/policy_shareprivacyconflicts.go b/tools/compliance/policy_shareprivacyconflicts.go
new file mode 100644
index 0000000..279e179
--- /dev/null
+++ b/tools/compliance/policy_shareprivacyconflicts.go
@@ -0,0 +1,71 @@
+// 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 and must share from %s condition\n", conflict.SourceNode.name,
+ conflict.PrivacyCondition.Name(), conflict.ShareCondition.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 == other.ShareCondition &&
+ conflict.PrivacyCondition == other.PrivacyCondition
+}
+
+// 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 {
+
+ ResolveTopDownConditions(lg)
+ // combined is the combination of source-sharing and source privacy.
+ combined := WalkActionsForCondition(lg, ImpliesShared.Union(ImpliesPrivate))
+
+ // size is the size of the result
+ size := 0
+ for _, cs := range combined {
+ size += cs.Intersection(ImpliesShared).Len() * cs.Intersection(ImpliesPrivate).Len()
+ }
+ if size == 0 {
+ return nil
+ }
+ result := make([]SourceSharePrivacyConflict, 0, size)
+ for actsOn, cs := range combined {
+ pconditions := cs.Intersection(ImpliesPrivate).AsList()
+ ssconditions := cs.Intersection(ImpliesShared).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..069daa2
--- /dev/null
+++ b/tools/compliance/policy_shareprivacyconflicts_test.go
@@ -0,0 +1,123 @@
+// 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.Name() == l[j].ShareCondition.Name() {
+ return l[i].PrivacyCondition.Name() < l[j].PrivacyCondition.Name()
+ }
+ return l[i].ShareCondition.Name() < l[j].ShareCondition.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 %s, 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..75c8399
--- /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]struct{})
+
+ WalkTopDown(NoEdgeContext{}, 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].edge) {
+ return false
+ }
+ }
+ tset[tn] = struct{}{}
+ 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..3ae9b46
--- /dev/null
+++ b/tools/compliance/policy_shipped_test.go
@@ -0,0 +1,143 @@
+// 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 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 %s, want no error", err)
+ return
+ }
+ t.Logf("graph:")
+ for _, edge := range lg.Edges() {
+ t.Logf(" %s", edge.String())
+ }
+ expectedNodes := append([]string{}, tt.expectedNodes...)
+ nodeset := ShippedNodes(lg)
+ t.Logf("shipped node set: %s", nodeset.String())
+
+ actualNodes := nodeset.Names()
+ t.Logf("shipped nodes: [%s]", strings.Join(actualNodes, ", "))
+
+ sort.Strings(expectedNodes)
+ sort.Strings(actualNodes)
+
+ t.Logf("sorted nodes: [%s]", strings.Join(actualNodes, ", "))
+ t.Logf("expected nodes: [%s]", strings.Join(expectedNodes, ", "))
+ if len(expectedNodes) != len(actualNodes) {
+ t.Errorf("unexpected number of shipped nodes: %d nodes, want %d nodes",
+ len(actualNodes), len(expectedNodes))
+ return
+ }
+ for i := 0; i < len(actualNodes); i++ {
+ if expectedNodes[i] != actualNodes[i] {
+ t.Errorf("unexpected node at index %d: got %q, want %q",
+ i, actualNodes[i], expectedNodes[i])
+ }
+ }
+ })
+ }
+}
diff --git a/tools/compliance/policy_walk.go b/tools/compliance/policy_walk.go
new file mode 100644
index 0000000..f4d7bba
--- /dev/null
+++ b/tools/compliance/policy_walk.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
+
+// EdgeContextProvider is an interface for injecting edge-specific context
+// into walk paths.
+type EdgeContextProvider interface {
+ // Context returns the context for `edge` when added to `path`.
+ Context(lg *LicenseGraph, path TargetEdgePath, edge *TargetEdge) interface{}
+}
+
+// NoEdgeContext implements EdgeContextProvider for walks that use no context.
+type NoEdgeContext struct{}
+
+// Context returns nil.
+func (ctx NoEdgeContext) Context(lg *LicenseGraph, path TargetEdgePath, edge *TargetEdge) interface{} {
+ return nil
+}
+
+// ApplicableConditionsContext provides the subset of conditions in `universe`
+// that apply to each edge in a path.
+type ApplicableConditionsContext struct {
+ universe LicenseConditionSet
+}
+
+// Context returns the LicenseConditionSet applicable to the edge.
+func (ctx ApplicableConditionsContext) Context(lg *LicenseGraph, path TargetEdgePath, edge *TargetEdge) interface{} {
+ universe := ctx.universe
+ if len(path) > 0 {
+ universe = path[len(path)-1].ctx.(LicenseConditionSet)
+ }
+ return conditionsAttachingAcrossEdge(lg, edge, universe)
+}
+
+// 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(lg *LicenseGraph, target *TargetNode, path TargetEdgePath) bool
+
+// WalkTopDown does a top-down walk of `lg` calling `visit` and descending
+// into depenencies when `visit` returns true.
+func WalkTopDown(ctx EdgeContextProvider, lg *LicenseGraph, visit VisitNode) {
+ path := NewTargetEdgePath(32)
+
+ var walk func(fnode *TargetNode)
+ walk = func(fnode *TargetNode) {
+ visitChildren := visit(lg, fnode, *path)
+ if !visitChildren {
+ return
+ }
+ for _, edge := range fnode.edges {
+ var edgeContext interface{}
+ if ctx == nil {
+ edgeContext = nil
+ } else {
+ edgeContext = ctx.Context(lg, *path, edge)
+ }
+ path.Push(edge, edgeContext)
+ walk(edge.dependency)
+ path.Pop()
+ }
+ }
+
+ for _, r := range lg.rootFiles {
+ path.Clear()
+ walk(lg.targets[r])
+ }
+}
+
+// resolutionKey identifies results from walking a specific target for a
+// specific set of conditions.
+type resolutionKey struct {
+ target *TargetNode
+ cs LicenseConditionSet
+}
+
+// WalkResolutionsForCondition performs a top-down walk of the LicenseGraph
+// resolving all distributed works for `conditions`.
+func WalkResolutionsForCondition(lg *LicenseGraph, conditions LicenseConditionSet) ResolutionSet {
+ shipped := ShippedNodes(lg)
+
+ // rmap maps 'attachesTo' targets to the `actsOn` targets and applicable conditions
+ rmap := make(map[resolutionKey]ActionSet)
+
+ // cmap identifies previously walked target/condition pairs.
+ cmap := make(map[resolutionKey]struct{})
+
+ // result accumulates the resolutions to return.
+ result := make(ResolutionSet)
+ WalkTopDown(ApplicableConditionsContext{conditions}, lg, func(lg *LicenseGraph, tn *TargetNode, path TargetEdgePath) bool {
+ universe := conditions
+ if len(path) > 0 {
+ universe = path[len(path)-1].ctx.(LicenseConditionSet)
+ }
+
+ if universe.IsEmpty() {
+ return false
+ }
+ key := resolutionKey{tn, universe}
+
+ if _, alreadyWalked := cmap[key]; alreadyWalked {
+ pure := true
+ for _, p := range path {
+ target := p.Target()
+ tkey := resolutionKey{target, universe}
+ if _, ok := rmap[tkey]; !ok {
+ rmap[tkey] = make(ActionSet)
+ }
+ // attach prior walk outcome to ancestor
+ for actsOn, cs := range rmap[key] {
+ rmap[tkey][actsOn] = cs
+ }
+ // if prior walk produced results, copy results
+ // to ancestor.
+ if _, ok := result[tn]; ok && pure {
+ if _, ok := result[target]; !ok {
+ result[target] = make(ActionSet)
+ }
+ for actsOn, cs := range result[tn] {
+ result[target][actsOn] = cs
+ }
+ pure = target.IsContainer()
+ }
+ }
+ // if all ancestors are pure aggregates, attach
+ // matching prior walk conditions to self. Prior walk
+ // will not have done so if any ancestor was not an
+ // aggregate.
+ if pure {
+ match := rmap[key][tn].Intersection(universe)
+ if !match.IsEmpty() {
+ if _, ok := result[tn]; !ok {
+ result[tn] = make(ActionSet)
+ }
+ result[tn][tn] = match
+ }
+ }
+ return false
+ }
+ // no need to walk node or dependencies if not shipped
+ if !shipped.Contains(tn) {
+ return false
+ }
+ if _, ok := rmap[key]; !ok {
+ rmap[key] = make(ActionSet)
+ }
+ // add self to walk outcome
+ rmap[key][tn] = tn.resolution
+ cmap[key] = struct{}{}
+ cs := tn.resolution
+ if !cs.IsEmpty() {
+ cs = cs.Intersection(universe)
+ pure := true
+ for _, p := range path {
+ target := p.Target()
+ tkey := resolutionKey{target, universe}
+ if _, ok := rmap[tkey]; !ok {
+ rmap[tkey] = make(ActionSet)
+ }
+ // copy current node's action into ancestor
+ rmap[tkey][tn] = tn.resolution
+ // conditionally put matching conditions into
+ // result
+ if pure && !cs.IsEmpty() {
+ if _, ok := result[target]; !ok {
+ result[target] = make(ActionSet)
+ }
+ result[target][tn] = cs
+ pure = target.IsContainer()
+ }
+ }
+ // if all ancestors are pure aggregates, attach
+ // matching conditions to self.
+ if pure && !cs.IsEmpty() {
+ if _, ok := result[tn]; !ok {
+ result[tn] = make(ActionSet)
+ }
+ result[tn][tn] = cs
+ }
+ }
+ return true
+ })
+
+ return result
+}
+
+// WalkActionsForCondition performs a top-down walk of the LicenseGraph
+// resolving all distributed works for `conditions`.
+func WalkActionsForCondition(lg *LicenseGraph, conditions LicenseConditionSet) ActionSet {
+ shipped := ShippedNodes(lg)
+
+ // cmap identifies previously walked target/condition pairs.
+ cmap := make(map[resolutionKey]struct{})
+
+ // amap maps 'actsOn' targets to the applicable conditions
+ //
+ // amap is the resulting ActionSet
+ amap := make(ActionSet)
+ WalkTopDown(ApplicableConditionsContext{conditions}, lg, func(lg *LicenseGraph, tn *TargetNode, path TargetEdgePath) bool {
+ universe := conditions
+ if len(path) > 0 {
+ universe = path[len(path)-1].ctx.(LicenseConditionSet)
+ }
+ if universe.IsEmpty() {
+ return false
+ }
+ key := resolutionKey{tn, universe}
+ if _, ok := cmap[key]; ok {
+ return false
+ }
+ if !shipped.Contains(tn) {
+ return false
+ }
+ cs := universe.Intersection(tn.resolution)
+ if !cs.IsEmpty() {
+ if _, ok := amap[tn]; ok {
+ amap[tn] = cs
+ } else {
+ amap[tn] = amap[tn].Union(cs)
+ }
+ }
+ return true
+ })
+
+ return amap
+}
diff --git a/tools/compliance/policy_walk_test.go b/tools/compliance/policy_walk_test.go
new file mode 100644
index 0000000..92867f9
--- /dev/null
+++ b/tools/compliance/policy_walk_test.go
@@ -0,0 +1,1240 @@
+// 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 LicenseConditionSet
+ 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"},
+ {"apacheLib.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"},
+ {"dependentModule.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 %s, want no error", err)
+ return
+ }
+ expectedRs := toResolutionSet(lg, tt.expectedResolutions)
+ ResolveTopDownConditions(lg)
+ actualRs := WalkResolutionsForCondition(lg, tt.condition)
+ checkResolves(actualRs, expectedRs, t)
+ })
+ }
+}
+
+func TestWalkActionsForCondition(t *testing.T) {
+ tests := []struct {
+ name string
+ condition LicenseConditionSet
+ roots []string
+ edges []annotated
+ expectedActions []act
+ }{
+ {
+ name: "firstparty",
+ condition: ImpliesNotice,
+ roots: []string{"apacheBin.meta_lic"},
+ edges: []annotated{
+ {"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}},
+ },
+ expectedActions: []act{
+ {"apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"mitBin.meta_lic", "mitBin.meta_lic", "notice"},
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+ {"apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"},
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"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"}},
+ },
+ expectedActions: []act{},
+ },
+ {
+ name: "independentmodulestaticnotice",
+ condition: ImpliesNotice,
+ roots: []string{"apacheBin.meta_lic"},
+ edges: []annotated{
+ {"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}},
+ },
+ expectedActions: []act{
+ {"apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+ {"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"dependentModule.meta_lic", "dependentModule.meta_lic", "notice"},
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"},
+ {"apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"},
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+ {"apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"gplContainer.meta_lic", "gplContainer.meta_lic", "restricted"},
+ {"apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+ {"apacheLib.meta_lic", "gplContainer.meta_lic", "restricted"},
+ {"apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"gplContainer.meta_lic", "gplContainer.meta_lic", "restricted"},
+ {"apacheLib.meta_lic", "gplContainer.meta_lic", "restricted"},
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"},
+ {"apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
+ {"apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+ {"apacheLib.meta_lic", "apacheLib.meta_lic", "notice"},
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"},
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+ {"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"},
+ },
+ },
+ {
+ 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"}},
+ },
+ expectedActions: []act{
+ {"apacheBin.meta_lic", "gplLib.meta_lic", "restricted"},
+ {"apacheLib.meta_lic", "gplLib.meta_lic", "restricted"},
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+ {"apacheBin.meta_lic", "apacheBin.meta_lic", "notice"},
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"},
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"proprietary.meta_lic", "proprietary.meta_lic", "proprietary"},
+ {"proprietary.meta_lic", "gplLib.meta_lic", "restricted"},
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"gplLib.meta_lic", "gplLib.meta_lic", "restricted"},
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+ {"proprietary.meta_lic", "proprietary.meta_lic", "proprietary"},
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"gplBin.meta_lic", "gplBin.meta_lic", "restricted"},
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"mitBin.meta_lic", "mitBin.meta_lic", "notice"},
+ {"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"}},
+ },
+ expectedActions: []act{},
+ },
+ {
+ name: "noticeonb_e_ob_e_o",
+ condition: ImpliesByExceptionOnly,
+ roots: []string{"mitBin.meta_lic"},
+ edges: []annotated{
+ {"mitBin.meta_lic", "by_exception.meta_lic", []string{"static"}},
+ },
+ expectedActions: []act{
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"by_exception.meta_lic", "by_exception.meta_lic", "by_exception_only"},
+ {"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"}},
+ },
+ expectedActions: []act{},
+ },
+ {
+ 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"}},
+ },
+ expectedActions: []act{
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"mitBin.meta_lic", "mitBin.meta_lic", "notice"},
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"mplBin.meta_lic", "mplBin.meta_lic", "reciprocal"},
+ {"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"}},
+ },
+ expectedActions: []act{
+ {"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 %s, want no error", err)
+ return
+ }
+ expectedAs := toActionSet(lg, tt.expectedActions)
+ ResolveTopDownConditions(lg)
+ actualAs := WalkActionsForCondition(lg, tt.condition)
+ checkResolvesActions(lg, actualAs, expectedAs, t)
+ })
+ }
+}
diff --git a/tools/compliance/readgraph.go b/tools/compliance/readgraph.go
new file mode 100644
index 0000000..6f91e1c
--- /dev/null
+++ b/tools/compliance/readgraph.go
@@ -0,0 +1,268 @@
+// 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
+
+ // 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 LicenseGraph.
+ 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()
+ lg.targets[r.target.name] = r.target
+ recv.lg.mu.Unlock()
+ } else {
+ // finished -- nil the results channel
+ recv.results = nil
+ }
+ }
+ }
+
+ if lg != nil {
+ esize := 0
+ for _, tn := range lg.targets {
+ esize += len(tn.proto.Deps)
+ }
+ lg.edges = make(TargetEdgeList, 0, esize)
+ for _, tn := range lg.targets {
+ tn.licenseConditions = LicenseConditionSetFromNames(tn, tn.proto.LicenseConditions...)
+ err = addDependencies(lg, tn)
+ if err != nil {
+ return nil, fmt.Errorf("error indexing dependencies for %q: %w", tn.name, err)
+ }
+ tn.proto.Deps = []*license_metadata_proto.AnnotatedDependency{}
+ }
+ }
+ 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
+
+ // lg is the license graph the node belongs to.
+ lg *LicenseGraph
+
+ // edges identifies the dependencies of the target.
+ edges TargetEdgeList
+
+ // licenseConditions identifies the set of license conditions originating at the target node.
+ licenseConditions LicenseConditionSet
+
+ // resolution identifies the set of conditions resolved by acting on the target node.
+ resolution LicenseConditionSet
+}
+
+// addDependencies converts the proto AnnotatedDependencies into `edges`
+func addDependencies(lg *LicenseGraph, tn *TargetNode) error {
+ tn.edges = make(TargetEdgeList, 0, len(tn.proto.Deps))
+ for _, ad := range tn.proto.Deps {
+ dependency := ad.GetFile()
+ if len(dependency) == 0 {
+ return fmt.Errorf("missing dependency name")
+ }
+ dtn, ok := lg.targets[dependency]
+ if !ok {
+ return fmt.Errorf("unknown dependency name %q", dependency)
+ }
+ if dtn == nil {
+ return fmt.Errorf("nil dependency for name %q", dependency)
+ }
+ annotations := newEdgeAnnotations()
+ for _, a := range ad.Annotations {
+ // look up a common constant annotation string from a small map
+ // instead of creating 1000's of copies of the same 3 strings.
+ if ann, ok := RecognizedAnnotations[a]; ok {
+ annotations.annotations[ann] = struct{}{}
+ }
+ }
+ edge := &TargetEdge{tn, dtn, annotations}
+ lg.edges = append(lg.edges, edge)
+ tn.edges = append(tn.edges, edge)
+ }
+ 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, 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, fmt.Errorf("error reading license metadata %q: %w", file, err)}
+ return
+ }
+ f.Close()
+
+ tn := &TargetNode{lg: recv.lg, name: file}
+
+ err = prototext.Unmarshal(data, &tn.proto)
+ if err != nil {
+ recv.results <- &result{file, nil, fmt.Errorf("error license metadata %q: %w", file, err)}
+ return
+ }
+
+ // 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, nil}
+ recv.task <- true
+
+ // schedule tasks as necessary to read dependencies
+ for _, ad := range tn.proto.Deps {
+ dependency := ad.GetFile()
+ // decide, signal and record whether to schedule task in critical section
+ recv.lg.mu.Lock()
+ _, alreadyScheduled := recv.lg.targets[dependency]
+ if !alreadyScheduled {
+ recv.lg.targets[dependency] = nil
+ }
+ recv.lg.mu.Unlock()
+ // schedule task to read dependency file outside critical section
+ if !alreadyScheduled {
+ readFile(recv, 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..db52fb1
--- /dev/null
+++ b/tools/compliance/readgraph_test.go
@@ -0,0 +1,150 @@
+// 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 %s, want no error", err)
+ } else if !strings.Contains(err.Error(), tt.expectedError) {
+ t.Errorf("unexpected error: got %s, 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))
+ t.Logf("actualEdges:")
+ for _, edge := range actualEdges {
+ t.Logf(" %s", edge.String())
+ }
+ t.Logf("expectedEdges:")
+ for _, edge := range actualEdges {
+ t.Logf(" %s", edge.String())
+ }
+ if len(tt.expectedEdges) != len(actualEdges) {
+ t.Errorf("len(actualEdges): got %d, want %d", len(actualEdges), len(tt.expectedEdges))
+ } else {
+ for i := 0; i < len(actualEdges); i++ {
+ if tt.expectedEdges[i] != actualEdges[i] {
+ t.Errorf("actualEdges[%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)
+
+ t.Logf("actualTargets: %v", actualTargets)
+ t.Logf("expectedTargets: %v", tt.expectedTargets)
+
+ if len(tt.expectedTargets) != len(actualTargets) {
+ t.Errorf("len(actualTargets): got %d, want %d", len(actualTargets), len(tt.expectedTargets))
+ } else {
+ for i := 0; i < len(actualTargets); i++ {
+ if tt.expectedTargets[i] != actualTargets[i] {
+ t.Errorf("actualTargets[%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..acc61e2
--- /dev/null
+++ b/tools/compliance/resolution.go
@@ -0,0 +1,149 @@
+// 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
+}
+
+// asString returns a string representation of the resolution.
+func (r Resolution) asString() string {
+ var sb strings.Builder
+ names := r.cs.Names()
+ sort.Strings(names)
+ fmt.Fprintf(&sb, "%s -> %s{%s}", r.attachesTo.name, r.actsOn.name, strings.Join(names, ", "))
+ 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 = result.Union(r.cs)
+ }
+ return result
+}
+
+// ByName returns the sub-list of resolutions resolving conditions matching
+// `names`.
+func (rl ResolutionList) Matching(conditions LicenseConditionSet) ResolutionList {
+ result := make(ResolutionList, 0, rl.CountMatching(conditions))
+ for _, r := range rl {
+ if r.Resolves().MatchesAnySet(conditions) {
+ result = append(result, Resolution{r.attachesTo, r.actsOn, r.cs.MatchingAnySet(conditions)})
+ }
+ }
+ return result
+}
+
+// CountMatching returns the number of resolutions resolving conditions matching
+// `conditions`.
+func (rl ResolutionList) CountMatching(conditions LicenseConditionSet) int {
+ c := 0
+ for _, r := range rl {
+ if r.Resolves().MatchesAnySet(conditions) {
+ 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
+}
diff --git a/tools/compliance/resolutionset.go b/tools/compliance/resolutionset.go
new file mode 100644
index 0000000..7c8f333
--- /dev/null
+++ b/tools/compliance/resolutionset.go
@@ -0,0 +1,133 @@
+// 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"
+)
+
+// 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 attach both conditions to the binary to
+// act on the origin of each condition. By attaching 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.
+//
+// The action is defined by the context. In the above example, the action is
+// providing notice for the module acted on. In another context, the action
+// might be sharing the source-code or preserving the privacy of the module
+// acted on.
+type ResolutionSet map[*TargetNode]ActionSet
+
+// AttachesTo identifies the list of targets triggering action to resolve
+// conditions. (unordered)
+func (rs ResolutionSet) AttachesTo() TargetNodeList {
+ result := make(TargetNodeList, 0, len(rs))
+ for attachesTo := range rs {
+ result = append(result, attachesTo)
+ }
+ return result
+}
+
+// AttachesToTarget returns true if the set contains conditions that
+// are `attachedTo`.
+func (rs ResolutionSet) AttachesToTarget(target *TargetNode) bool {
+ _, isPresent := rs[target]
+ return isPresent
+}
+
+// Resolutions returns the list of resolutions that `attachedTo`
+// target must resolve. Returns empty list if no conditions apply.
+func (rs ResolutionSet) Resolutions(attachesTo *TargetNode) ResolutionList {
+ as, ok := rs[attachesTo]
+ if !ok {
+ return nil
+ }
+ result := make(ResolutionList, 0, len(as))
+ for actsOn, cs := range as {
+ result = append(result, Resolution{attachesTo, actsOn, cs})
+ }
+ return result
+}
+
+// AllActions returns the set of actions required to resolve the set omitting
+// the attachment.
+func (rs ResolutionSet) AllActions() ActionSet {
+ result := make(ActionSet)
+ for _, as := range rs {
+ for actsOn, cs := range as {
+ if _, ok := result[actsOn]; ok {
+ result[actsOn] = cs.Union(result[actsOn])
+ } else {
+ result[actsOn] = cs
+ }
+ }
+ }
+ return result
+}
+
+// String returns a human-readable string representation of the set.
+func (rs ResolutionSet) String() string {
+ var sb strings.Builder
+ fmt.Fprintf(&sb, "{")
+ sep := ""
+ for attachesTo, as := range rs {
+ fmt.Fprintf(&sb, "%s%s -> %s", sep, attachesTo.Name(), as.String())
+ sep = ", "
+ }
+ fmt.Fprintf(&sb, "}")
+ return sb.String()
+}
+
+// ActionSet identifies a set of targets to act on and the license conditions
+// the action will resolve.
+type ActionSet map[*TargetNode]LicenseConditionSet
+
+// String returns a human-readable string representation of the set.
+func (as ActionSet) String() string {
+ var sb strings.Builder
+ fmt.Fprintf(&sb, "{")
+ sep := ""
+ for actsOn, cs := range as {
+ fmt.Fprintf(&sb, "%s%s%s", sep, actsOn.Name(), cs.String())
+ sep = ", "
+ }
+ fmt.Fprintf(&sb, "}")
+ return sb.String()
+}
diff --git a/tools/compliance/resolutionset_test.go b/tools/compliance/resolutionset_test.go
new file mode 100644
index 0000000..89cdfeb
--- /dev/null
+++ b/tools/compliance/resolutionset_test.go
@@ -0,0 +1,136 @@
+// 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_AttachesTo(t *testing.T) {
+ lg := newLicenseGraph()
+
+ rsShare := toResolutionSet(lg, share)
+
+ t.Logf("checking resolution set %s", rsShare.String())
+
+ actual := rsShare.AttachesTo().Names()
+ sort.Strings(actual)
+
+ expected := []string{"bin1", "bin2", "image"}
+
+ t.Logf("actual rsShare: %v", actual)
+ t.Logf("expected rsShare: %v", expected)
+
+ if len(actual) != len(expected) {
+ t.Errorf("rsShare: wrong number of targets: got %d, want %d", len(actual), len(expected))
+ return
+ }
+ for i := 0; i < len(actual); i++ {
+ if actual[i] != expected[i] {
+ t.Errorf("rsShare: unexpected target at index %d: got %s, want %s", i, actual[i], expected[i])
+ }
+ }
+
+ rsPrivate := toResolutionSet(lg, proprietary)
+ actual = rsPrivate.AttachesTo().Names()
+ expected = []string{}
+
+ t.Logf("actual rsPrivate: %v", actual)
+ t.Logf("expected rsPrivate: %v", expected)
+
+ if len(actual) != len(expected) {
+ t.Errorf("rsPrivate: wrong number of targets: got %d, want %d", len(actual), len(expected))
+ return
+ }
+ for i := 0; i < len(actual); i++ {
+ if actual[i] != expected[i] {
+ t.Errorf("rsPrivate: unexpected target at index %d: got %s, want %s", i, actual[i], expected[i])
+ }
+ }
+}
+
+func TestResolutionSet_AttachesToTarget(t *testing.T) {
+ lg := newLicenseGraph()
+
+ rsShare := toResolutionSet(lg, share)
+
+ t.Logf("checking resolution set %s", rsShare.String())
+
+ if rsShare.AttachesToTarget(newTestNode(lg, "binc")) {
+ t.Errorf("actual.AttachesToTarget(\"binc\"): got true, want false")
+ }
+ if !rsShare.AttachesToTarget(newTestNode(lg, "image")) {
+ t.Errorf("actual.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..26d7461
--- /dev/null
+++ b/tools/compliance/test_util.go
@@ -0,0 +1,603 @@
+// 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,
+ }
+)
+
+// newTestNode constructs a test node in the license graph.
+func newTestNode(lg *LicenseGraph, targetName string) *TargetNode {
+ if tn, alreadyExists := lg.targets[targetName]; alreadyExists {
+ return tn
+ }
+ tn := &TargetNode{name: targetName}
+ lg.targets[targetName] = tn
+ return tn
+}
+
+// newTestCondition constructs a test license condition in the license graph.
+func newTestCondition(lg *LicenseGraph, targetName string, conditionName string) LicenseCondition {
+ tn := newTestNode(lg, targetName)
+ cl := LicenseConditionSetFromNames(tn, conditionName).AsList()
+ if len(cl) == 0 {
+ panic(fmt.Errorf("attempt to create unrecognized condition: %q", conditionName))
+ } else if len(cl) != 1 {
+ panic(fmt.Errorf("unexpected multiple conditions from condition name: %q: got %d, want 1", conditionName, len(cl)))
+ }
+ lc := cl[0]
+ tn.licenseConditions = tn.licenseConditions.Plus(lc)
+ return lc
+}
+
+// newTestConditionSet constructs a test license condition set in the license graph.
+func newTestConditionSet(lg *LicenseGraph, targetName string, conditionName []string) LicenseConditionSet {
+ tn := newTestNode(lg, targetName)
+ cs := LicenseConditionSetFromNames(tn, conditionName...)
+ if cs.IsEmpty() {
+ panic(fmt.Errorf("attempt to create unrecognized condition: %q", conditionName))
+ }
+ tn.licenseConditions = tn.licenseConditions.Union(cs)
+ return cs
+}
+
+// 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)
+}
+
+// logGraph outputs a representation of the graph to a test log.
+func logGraph(lg *LicenseGraph, t *testing.T) {
+ t.Logf("license graph:")
+ t.Logf(" targets:")
+ for _, target := range lg.Targets() {
+ t.Logf(" %s%s in package %q", target.Name(), target.LicenseConditions().String(), target.PackageName())
+ }
+ t.Logf(" /targets")
+ t.Logf(" edges:")
+ for _, edge := range lg.Edges() {
+ t.Logf(" %s", edge.String())
+ }
+ t.Logf(" /edges")
+ t.Logf("/license graph")
+}
+
+// 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
+}
+
+// act describes test data resolution actions to define test action sets.
+type act struct {
+ actsOn, origin, condition string
+}
+
+// String returns a human-readable string representing the test action.
+func (a act) String() string {
+ return fmt.Sprintf("%s{%s:%s}", a.actsOn, a.origin, a.condition)
+}
+
+// toActionSet converts a list of act test data into a test action set.
+func toActionSet(lg *LicenseGraph, data []act) ActionSet {
+ as := make(ActionSet)
+ for _, a := range data {
+ actsOn := newTestNode(lg, a.actsOn)
+ cs := newTestConditionSet(lg, a.origin, strings.Split(a.condition, "|"))
+ as[actsOn] = cs
+ }
+ return as
+}
+
+// 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(ResolutionSet)
+ for _, r := range data {
+ attachesTo := newTestNode(lg, r.attachesTo)
+ actsOn := newTestNode(lg, r.actsOn)
+ if _, ok := rmap[attachesTo]; !ok {
+ rmap[attachesTo] = make(ActionSet)
+ }
+ cs := newTestConditionSet(lg, r.origin, strings.Split(r.condition, ":"))
+ rmap[attachesTo][actsOn] |= cs
+ }
+ return rmap
+}
+
+// tcond associates a target name with '|' separated string conditions.
+type tcond struct {
+ target, conditions string
+}
+
+// action represents a single element of an ActionSet for testing.
+type action struct {
+ target *TargetNode
+ cs LicenseConditionSet
+}
+
+// String returns a human-readable string representation of the action.
+func (a action) String() string {
+ return fmt.Sprintf("%s%s", a.target.Name(), a.cs.String())
+}
+
+// actionList represents an array of actions and a total order defined by
+// target name followed by license condition set.
+type actionList []action
+
+// String returns a human-readable string representation of the list.
+func (l actionList) String() string {
+ var sb strings.Builder
+ fmt.Fprintf(&sb, "[")
+ sep := ""
+ for _, a := range l {
+ fmt.Fprintf(&sb, "%s%s", sep, a.String())
+ sep = ", "
+ }
+ fmt.Fprintf(&sb, "]")
+ return sb.String()
+}
+
+// Len returns the count of elements in the slice.
+func (l actionList) Len() int { return len(l) }
+
+// Swap rearranges 2 elements of the slice so that each occupies the other's
+// former position.
+func (l actionList) 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 actionList) Less(i, j int) bool {
+ if l[i].target == l[j].target {
+ return l[i].cs < l[j].cs
+ }
+ return l[i].target.Name() < l[j].target.Name()
+}
+
+// asActionList represents the resolved license conditions in a license graph
+// as an actionList for comparison in a test.
+func asActionList(lg *LicenseGraph) actionList {
+ result := make(actionList, 0, len(lg.targets))
+ for _, target := range lg.targets {
+ cs := target.resolution
+ if cs.IsEmpty() {
+ continue
+ }
+ result = append(result, action{target, cs})
+ }
+ return result
+}
+
+// toActionList converts an array of tcond into an actionList for comparison
+// in a test.
+func toActionList(lg *LicenseGraph, actions []tcond) actionList {
+ result := make(actionList, 0, len(actions))
+ for _, actn := range actions {
+ target := newTestNode(lg, actn.target)
+ cs := NewLicenseConditionSet()
+ for _, name := range strings.Split(actn.conditions, "|") {
+ lc, ok := RecognizedConditionNames[name]
+ if !ok {
+ panic(fmt.Errorf("Unrecognized test condition name: %q", name))
+ }
+ cs = cs.Plus(lc)
+ }
+ result = append(result, action{target, cs})
+ }
+ return result
+}
+
+// confl defines test data for a SourceSharePrivacyConflict as a target name,
+// source condition name, privacy condition name triple.
+type confl struct {
+ sourceNode, share, privacy string
+}
+
+// toConflictList converts confl test data into an array of
+// SourceSharePrivacyConflict for comparison in a test.
+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),
+ newTestCondition(lg, oshare, cshare),
+ newTestCondition(lg, oprivacy, cprivacy),
+ })
+ }
+ 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 := make(ResolutionSet)
+ rsExpected := make(ResolutionSet)
+ testNode := newTestNode(lg, "test")
+ rsActual[testNode] = asActual
+ rsExpected[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) {
+ t.Logf("actual resolution set: %s", rsActual.String())
+ t.Logf("expected resolution set: %s", rsExpected.String())
+
+ actualTargets := rsActual.AttachesTo()
+ sort.Sort(actualTargets)
+
+ expectedTargets := rsExpected.AttachesTo()
+ sort.Sort(expectedTargets)
+
+ t.Logf("actual targets: %s", actualTargets.String())
+ t.Logf("expected targets: %s", expectedTargets.String())
+
+ for _, target := range expectedTargets {
+ if !rsActual.AttachesToTarget(target) {
+ t.Errorf("unexpected missing target: got AttachesToTarget(%q) is false, want true", target.name)
+ 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: %d elements, %d elements",
+ target.name, len(actualRl), 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()
+ actualConditions := actualRl[i].Resolves()
+ if expectedConditions != actualConditions {
+ t.Errorf("unexpected conditions apply to %q acting on %q: got %04x with names %s, want %04x with names %s",
+ target.name, expectedRl[i].actsOn.name,
+ actualConditions, actualConditions.Names(),
+ expectedConditions, expectedConditions.Names())
+ continue
+ }
+ }
+
+ }
+ for _, target := range actualTargets {
+ if !rsExpected.AttachesToTarget(target) {
+ t.Errorf("unexpected extra target: got expected.AttachesTo(%q) is false, want true", target.name)
+ }
+ }
+}
+
+// checkResolvesActions compares an actual action set to an expected action set for a test verifying the actual set
+// resolves all of the expected conditions.
+func checkResolvesActions(lg *LicenseGraph, asActual, asExpected ActionSet, t *testing.T) {
+ rsActual := make(ResolutionSet)
+ rsExpected := make(ResolutionSet)
+ testNode := newTestNode(lg, "test")
+ rsActual[testNode] = asActual
+ rsExpected[testNode] = asExpected
+ checkResolves(rsActual, rsExpected, t)
+}
+
+// checkResolves compares an actual resolution set to an expected resolution set for a test verifying the actual set
+// resolves all of the expected conditions.
+func checkResolves(rsActual, rsExpected ResolutionSet, t *testing.T) {
+ t.Logf("actual resolution set: %s", rsActual.String())
+ t.Logf("expected resolution set: %s", rsExpected.String())
+
+ actualTargets := rsActual.AttachesTo()
+ sort.Sort(actualTargets)
+
+ expectedTargets := rsExpected.AttachesTo()
+ sort.Sort(expectedTargets)
+
+ t.Logf("actual targets: %s", actualTargets.String())
+ t.Logf("expected targets: %s", expectedTargets.String())
+
+ for _, target := range expectedTargets {
+ if !rsActual.AttachesToTarget(target) {
+ t.Errorf("unexpected missing target: got AttachesToTarget(%q) is false, want true", target.name)
+ 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: %d elements, %d elements",
+ target.name, len(actualRl), 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()
+ actualConditions := actualRl[i].Resolves()
+ if expectedConditions != (expectedConditions & actualConditions) {
+ t.Errorf("expected conditions missing from %q acting on %q: got %04x with names %s, want %04x with names %s",
+ target.name, expectedRl[i].actsOn.name,
+ actualConditions, actualConditions.Names(),
+ expectedConditions, expectedConditions.Names())
+ continue
+ }
+ }
+
+ }
+ for _, target := range actualTargets {
+ if !rsExpected.AttachesToTarget(target) {
+ t.Errorf("unexpected extra target: got expected.AttachesTo(%q) is false, want true", target.name)
+ }
+ }
+}
diff --git a/tools/droiddoc/Android.bp b/tools/droiddoc/Android.bp
index efd30c1..71d4939 100644
--- a/tools/droiddoc/Android.bp
+++ b/tools/droiddoc/Android.bp
@@ -14,15 +14,22 @@
package {
// See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "build_make_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- // SPDX-license-identifier-BSD
- // SPDX-license-identifier-CC-BY
- // SPDX-license-identifier-GPL
- // SPDX-license-identifier-MIT
- default_applicable_licenses: ["build_make_license"],
+ default_applicable_licenses: [
+ "Android-Apache-2.0",
+ "build_make_tools_droiddoc_license",
+ ],
+}
+
+license {
+ name: "build_make_tools_droiddoc_license",
+ package_name: "Android Droiddoc Templates",
+ license_kinds: [
+ "SPDX-license-identifier-BSD",
+ "SPDX-license-identifier-CC-BY-2.5",
+ "SPDX-license-identifier-GPL-3.0",
+ "SPDX-license-identifier-MIT",
+ ],
+ license_text: ["LICENSE"],
}
droiddoc_exported_dir {
diff --git a/tools/droiddoc/LICENSE b/tools/droiddoc/LICENSE
new file mode 100644
index 0000000..b591dde
--- /dev/null
+++ b/tools/droiddoc/LICENSE
@@ -0,0 +1,1095 @@
+-----------------------------------------------------
+microtemplate.js
+
+// Simple JavaScript Templating
+// John Resig - http://ejohn.org/ - MIT Licensed
+
+-----------------------------------------------------
+jquery-history.js
+
+/**
+ * jQuery history event v0.1
+ * Copyright (c) 2008 Tom Rodenberg <tarodenberg gmail com>
+ * Licensed under the GPL (http://www.gnu.org/licenses/gpl.html) license.
+ */
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
+
+-----------------------------------------------------
+yui-3.3.0-reset-min.css
+
+/*
+Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.com/yui/license.html
+version: 3.3.0
+build: 3167
+*/
+
+
+Software License Agreement (BSD License)
+Copyright (c) 2010, Yahoo! Inc.
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms, with or
+without modification, are permitted provided that the following conditions are
+met:
+
+ Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+ Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ Neither the name of Yahoo! Inc. nor the names of its contributors may be
+ used to endorse or promote products derived from this software without
+ specific prior written permission of Yahoo! Inc.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+Sources of Intellectual Property Included in the YUI Library
+
+YUI is issued by Yahoo! under the BSD license above. Below is a list of certain
+publicly available software that is the source of intellectual property in YUI,
+along with the licensing terms that pertain to thosesources of IP. This list is
+for informational purposes only and is not intended to represent an exhaustive
+list of third party contributions to the YUI.
+
+ Douglas Crockford's JSON parsing and stringifying methods: In the JSON
+ Utility, Douglas Crockford's JSON parsing and stringifying methods are
+ adapted from work published at JSON.org. The adapted work is in the public
+ domain.
+
+ Robert Penner's animation-easing algorithms: In the Animation Utility, YUI
+ makes use of Robert Penner's algorithms for easing.
+
+ Geoff Stearns's SWFObject: In the Charts Control and the Uploader versions
+ through 2.7.0, YUI makes use of Geoff Stearns's SWFObject v1.5 for Flash
+ Player detection and embedding. More information on SWFObject can be found
+ here (http://blog.deconcept.com/swfobject/). SWFObject is (c) 2007 Geoff
+ Stearns and is released under the MIT License
+ (http://www.opensource.org/licenses/mit-license.php).
+
+ Diego Perini's IEContentLoaded technique: The Event Utility employs a
+ technique developed by Diego Perini and licensed under GPL. YUI's use of
+ this technique is included under our BSD license with the author's
+ permission.
+
+
+From MIT license link above:
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----------------------------------------------------
+customizations.cs
+
+ Except as noted, this content is
+ licensed under <a href="http://creativecommons.org/licenses/by/2.5/">
+ Creative Commons Attribution 2.5</a>.
+
+
+Creative Commons
+Creative Commons Legal Code
+
+Attribution 2.5
+CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL
+SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT
+RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS.
+CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND
+DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE.
+
+License
+
+THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE
+COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY
+COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS
+AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
+
+BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE
+BOUND BY THE TERMS OF THIS LICENSE. THE LICENSOR GRANTS YOU THE RIGHTS
+CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND
+CONDITIONS.
+
+1. Definitions
+
+ "Collective Work" means a work, such as a periodical issue, anthology or
+ encyclopedia, in which the Work in its entirety in unmodified form, along
+ with a number of other contributions, constituting separate and independent
+ works in themselves, are assembled into a collective whole. A work that
+ constitutes a Collective Work will not be considered a Derivative Work (as
+ defined below) for the purposes of this License.
+
+ "Derivative Work" means a work based upon the Work or upon the Work and
+ other pre-existing works, such as a translation, musical arrangement,
+ dramatization, fictionalization, motion picture version, sound recording,
+ art reproduction, abridgment, condensation, or any other form in which the
+ Work may be recast, transformed, or adapted, except that a work that
+ constitutes a Collective Work will not be considered a Derivative Work for
+ the purpose of this License. For the avoidance of doubt, where the Work is
+ a musical composition or sound recording, the synchronization of the Work
+ in timed-relation with a moving image ("synching") will be considered a
+ Derivative Work for the purpose of this License.
+
+ "Licensor" means the individual or entity that offers the Work under the
+ terms of this License.
+
+ "Original Author" means the individual or entity who created the Work.
+
+ "Work" means the copyrightable work of authorship offered under the terms
+ of this License.
+
+ "You" means an individual or entity exercising rights under this License
+ who has not previously violated the terms of this License with respect to
+ the Work, or who has received express permission from the Licensor to
+ exercise rights under this License despite a previous violation.
+
+2. Fair Use Rights. Nothing in this license is intended to reduce, limit, or
+restrict any rights arising from fair use, first sale or other limitations on
+the exclusive rights of the copyright owner under copyright law or other
+applicable laws.
+
+3. License Grant. Subject to the terms and conditions of this License, Licensor
+hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the
+duration of the applicable copyright) license to exercise the rights in the
+Work as stated below:
+
+ to reproduce the Work, to incorporate the Work into one or more Collective
+ Works, and to reproduce the Work as incorporated in the Collective Works;
+
+ to create and reproduce Derivative Works;
+
+ to distribute copies or phonorecords of, display publicly, perform
+ publicly, and perform publicly by means of a digital audio transmission the
+ Work including as incorporated in Collective Works;
+
+ to distribute copies or phonorecords of, display publicly, perform
+ publicly, and perform publicly by means of a digital audio transmission
+ Derivative Works.
+
+ For the avoidance of doubt, where the work is a musical composition:
+ Performance Royalties Under Blanket Licenses. Licensor waives the
+ exclusive right to collect, whether individually or via a performance
+ rights society (e.g. ASCAP, BMI, SESAC), royalties for the public
+ performance or public digital performance (e.g. webcast) of the Work.
+
+ Mechanical Rights and Statutory Royalties. Licensor waives the
+ exclusive right to collect, whether individually or via a music rights
+ agency or designated agent (e.g. Harry Fox Agency), royalties for any
+ phonorecord You create from the Work ("cover version") and distribute,
+ subject to the compulsory license created by 17 USC Section 115 of the
+ US Copyright Act (or the equivalent in other jurisdictions).
+
+ Webcasting Rights and Statutory Royalties. For the avoidance of doubt,
+ where the Work is a sound recording, Licensor waives the exclusive right to
+ collect, whether individually or via a performance-rights society (e.g.
+ SoundExchange), royalties for the public digital performance (e.g. webcast)
+ of the Work, subject to the compulsory license created by 17 USC Section
+ 114 of the US Copyright Act (or the equivalent in other jurisdictions).
+
+The above rights may be exercised in all media and formats whether now known or
+hereafter devised. The above rights include the right to make such
+modifications as are technically necessary to exercise the rights in other
+media and formats. All rights not expressly granted by Licensor are hereby
+reserved.
+
+4. Restrictions.The license granted in Section 3 above is expressly made
+subject to and limited by the following restrictions:
+
+ You may distribute, publicly display, publicly perform, or publicly
+ digitally perform the Work only under the terms of this License, and You
+ must include a copy of, or the Uniform Resource Identifier for, this
+ License with every copy or phonorecord of the Work You distribute, publicly
+ display, publicly perform, or publicly digitally perform. You may not offer
+ or impose any terms on the Work that alter or restrict the terms of this
+ License or the recipients' exercise of the rights granted hereunder. You
+ may not sublicense the Work. You must keep intact all notices that refer to
+ this License and to the disclaimer of warranties. You may not distribute,
+ publicly display, publicly perform, or publicly digitally perform the Work
+ with any technological measures that control access or use of the Work in a
+ manner inconsistent with the terms of this License Agreement. The above
+ applies to the Work as incorporated in a Collective Work, but this does not
+ require the Collective Work apart from the Work itself to be made subject
+ to the terms of this License. If You create a Collective Work, upon notice
+ from any Licensor You must, to the extent practicable, remove from the
+ Collective Work any credit as required by clause 4(b), as requested. If You
+ create a Derivative Work, upon notice from any Licensor You must, to the
+ extent practicable, remove from the Derivative Work any credit as required
+ by clause 4(b), as requested.
+
+ If you distribute, publicly display, publicly perform, or publicly
+ digitally perform the Work or any Derivative Works or Collective Works, You
+ must keep intact all copyright notices for the Work and provide, reasonable
+ to the medium or means You are utilizing: (i) the name of the Original
+ Author (or pseudonym, if applicable) if supplied, and/or (ii) if the
+ Original Author and/or Licensor designate another party or parties (e.g. a
+ sponsor institute, publishing entity, journal) for attribution in
+ Licensor's copyright notice, terms of service or by other reasonable means,
+ the name of such party or parties; the title of the Work if supplied; to
+ the extent reasonably practicable, the Uniform Resource Identifier, if any,
+ that Licensor specifies to be associated with the Work, unless such URI
+ does not refer to the copyright notice or licensing information for the
+ Work; and in the case of a Derivative Work, a credit identifying the use of
+ the Work in the Derivative Work (e.g., "French translation of the Work by
+ Original Author," or "Screenplay based on original Work by Original
+ Author"). Such credit may be implemented in any reasonable manner;
+ provided, however, that in the case of a Derivative Work or Collective
+ Work, at a minimum such credit will appear where any other comparable
+ authorship credit appears and in a manner at least as prominent as such
+ other comparable authorship credit.
+
+5. Representations, Warranties and Disclaimer
+
+UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS
+THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND
+CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING,
+WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A
+PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS,
+ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE.
+SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH
+EXCLUSION MAY NOT APPLY TO YOU.
+
+6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN
+NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL,
+INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS
+LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+7. Termination
+
+ This License and the rights granted hereunder will terminate automatically
+ upon any breach by You of the terms of this License. Individuals or
+ entities who have received Derivative Works or Collective Works from You
+ under this License, however, will not have their licenses terminated
+ provided such individuals or entities remain in full compliance with those
+ licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of
+ this License.
+
+ Subject to the above terms and conditions, the license granted here is
+ perpetual (for the duration of the applicable copyright in the Work).
+ Notwithstanding the above, Licensor reserves the right to release the Work
+ under different license terms or to stop distributing the Work at any time;
+ provided, however that any such election will not serve to withdraw this
+ License (or any other license that has been, or is required to be, granted
+ under the terms of this License), and this License will continue in full
+ force and effect unless terminated as stated above.
+
+8. Miscellaneous
+
+ Each time You distribute or publicly digitally perform the Work or a
+ Collective Work, the Licensor offers to the recipient a license to the Work
+ on the same terms and conditions as the license granted to You under this
+ License.
+
+ Each time You distribute or publicly digitally perform a Derivative Work,
+ Licensor offers to the recipient a license to the original Work on the same
+ terms and conditions as the license granted to You under this License.
+
+ If any provision of this License is invalid or unenforceable under
+ applicable law, it shall not affect the validity or enforceability of the
+ remainder of the terms of this License, and without further action by the
+ parties to this agreement, such provision shall be reformed to the minimum
+ extent necessary to make such provision valid and enforceable.
+
+ No term or provision of this License shall be deemed waived and no breach
+ consented to unless such waiver or consent shall be in writing and signed
+ by the party to be charged with such waiver or consent.
+
+ This License constitutes the entire agreement between the parties with
+ respect to the Work licensed here. There are no understandings, agreements
+ or representations with respect to the Work not specified here. Licensor
+ shall not be bound by any additional provisions that may appear in any
+ communication from You. This License may not be modified without the mutual
+ written agreement of the Licensor and You.
+
+Creative Commons is not a party to this License, and makes no warranty
+whatsoever in connection with the Work. Creative Commons will not be liable to
+You or any party on any legal theory for any damages whatsoever, including
+without limitation any general, special, incidental or consequential damages
+arising in connection to this license. Notwithstanding the foregoing two (2)
+sentences, if Creative Commons has expressly identified itself as the Licensor
+hereunder, it shall have all rights and obligations of Licensor.
+
+Except for the limited purpose of indicating to the public that the Work is
+licensed under the CCPL, neither party will use the trademark "Creative
+Commons" or any related trademark or logo of Creative Commons without the prior
+written consent of Creative Commons. Any permitted use will be in compliance
+with Creative Commons' then-current trademark usage guidelines, as may be
+published on its website or otherwise made available upon request from time to
+time.
+
+Creative Commons may be contacted at https://creativecommons.org/.
+
+-----------------------------------------------------
+jquery-resizable.min.js
+
+/*
+ * jQuery JavaScript Library v1.3.2
+ * http://jquery.com/
+ *
+ * Copyright (c) 2009 John Resig
+ * Dual licensed under the MIT and GPL licenses.
+ * http://docs.jquery.com/License
+ *
+ * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009)
+ * Revision: 6246
+ */
+
+The MIT License (MIT)
+
+Copyright (c) 2009 John Resig
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----------------------------------------------------
+jquery-1.6.2.min.js
+
+/*!
+ * jQuery JavaScript Library v1.6.2
+ * http://jquery.com/
+ *
+ * Copyright 2011, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Thu Jun 30 14:16:56 2011 -0400
+ */
+
+The MIT License (MIT)
+
+Copyright (c) 2011 John Resig, and The Dojo Foundation
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/tools/event_log_tags.py b/tools/event_log_tags.py
index 35b2de0..a6ae9f1 100644
--- a/tools/event_log_tags.py
+++ b/tools/event_log_tags.py
@@ -55,12 +55,13 @@
if file_object is None:
try:
file_object = open(filename, "rb")
- except (IOError, OSError), e:
+ except (IOError, OSError) as e:
self.AddError(str(e))
return
try:
for self.linenum, line in enumerate(file_object):
+ line = line.decode('utf-8')
self.linenum += 1
line = re.sub('#.*$', '', line) # strip trailing comments
line = line.strip()
@@ -100,7 +101,7 @@
self.tags.append(Tag(tag, tagname, description,
self.filename, self.linenum))
- except (IOError, OSError), e:
+ except (IOError, OSError) as e:
self.AddError(str(e))
@@ -128,8 +129,8 @@
output_file = "<stdout>"
else:
out = open(output_file, "wb")
- out.write(data)
+ out.write(str.encode(data))
out.close()
- except (IOError, OSError), e:
- print >> sys.stderr, "failed to write %s: %s" % (output_file, e)
+ except (IOError, OSError) as e:
+ print("failed to write %s: %s" % (output_file, e), file=sys.stderr)
sys.exit(1)
diff --git a/tools/fs_config/Android.bp b/tools/fs_config/Android.bp
index 4544e07..8891a0a 100644
--- a/tools/fs_config/Android.bp
+++ b/tools/fs_config/Android.bp
@@ -14,11 +14,7 @@
package {
// See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "build_make_license"
- // to get the below license kinds:
- // legacy_restricted
- default_applicable_licenses: ["build_make_license"],
+ default_applicable_licenses: ["Android-Apache-2.0"],
}
bootstrap_go_package {
diff --git a/tools/fs_config/Android.mk b/tools/fs_config/Android.mk
index 63cb4eb..c36c3aa 100644
--- a/tools/fs_config/Android.mk
+++ b/tools/fs_config/Android.mk
@@ -42,13 +42,14 @@
vendor_capability_header := $(system_capability_header)
endif
-# List of supported vendor, oem, odm, vendor_dlkm and odm_dlkm Partitions
+# List of supported vendor, oem, odm, vendor_dlkm, odm_dlkm, and system_dlkm Partitions
fs_config_generate_extra_partition_list := $(strip \
$(if $(BOARD_USES_VENDORIMAGE)$(BOARD_VENDORIMAGE_FILE_SYSTEM_TYPE),vendor) \
$(if $(BOARD_USES_OEMIMAGE)$(BOARD_OEMIMAGE_FILE_SYSTEM_TYPE),oem) \
$(if $(BOARD_USES_ODMIMAGE)$(BOARD_ODMIMAGE_FILE_SYSTEM_TYPE),odm) \
$(if $(BOARD_USES_VENDOR_DLKMIMAGE)$(BOARD_VENDOR_DLKMIMAGE_FILE_SYSTEM_TYPE),vendor_dlkm) \
$(if $(BOARD_USES_ODM_DLKMIMAGE)$(BOARD_ODM_DLKMIMAGE_FILE_SYSTEM_TYPE),odm_dlkm) \
+ $(if $(BOARD_USES_SYSTEM_DLKMIMAGE)$(BOARD_SYSTEM_DLKMIMAGE_FILE_SYSTEM_TYPE),system_dlkm) \
)
##################################
@@ -57,8 +58,9 @@
include $(CLEAR_VARS)
LOCAL_MODULE := fs_config_dirs
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
LOCAL_REQUIRED_MODULES := \
fs_config_dirs_system \
fs_config_dirs_system_ext \
@@ -72,8 +74,9 @@
include $(CLEAR_VARS)
LOCAL_MODULE := fs_config_files
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
LOCAL_REQUIRED_MODULES := \
fs_config_files_system \
fs_config_files_system_ext \
@@ -88,8 +91,9 @@
include $(CLEAR_VARS)
LOCAL_MODULE := fs_config_dirs_system_ext
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
LOCAL_REQUIRED_MODULES := $(if $(BOARD_USES_SYSTEM_EXTIMAGE)$(BOARD_SYSTEM_EXTIMAGE_FILE_SYSTEM_TYPE),_fs_config_dirs_system_ext)
include $(BUILD_PHONY_PACKAGE)
@@ -100,8 +104,9 @@
include $(CLEAR_VARS)
LOCAL_MODULE := fs_config_files_system_ext
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
LOCAL_REQUIRED_MODULES := $(if $(BOARD_USES_SYSTEM_EXTIMAGE)$(BOARD_SYSTEM_EXTIMAGE_FILE_SYSTEM_TYPE),_fs_config_files_system_ext)
include $(BUILD_PHONY_PACKAGE)
@@ -112,8 +117,9 @@
include $(CLEAR_VARS)
LOCAL_MODULE := fs_config_dirs_product
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
LOCAL_REQUIRED_MODULES := $(if $(BOARD_USES_PRODUCTIMAGE)$(BOARD_PRODUCTIMAGE_FILE_SYSTEM_TYPE),_fs_config_dirs_product)
include $(BUILD_PHONY_PACKAGE)
@@ -124,8 +130,9 @@
include $(CLEAR_VARS)
LOCAL_MODULE := fs_config_files_product
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
LOCAL_REQUIRED_MODULES := $(if $(BOARD_USES_PRODUCTIMAGE)$(BOARD_PRODUCTIMAGE_FILE_SYSTEM_TYPE),_fs_config_files_product)
include $(BUILD_PHONY_PACKAGE)
@@ -136,8 +143,9 @@
include $(CLEAR_VARS)
LOCAL_MODULE := fs_config_dirs_nonsystem
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
LOCAL_REQUIRED_MODULES := $(foreach t,$(fs_config_generate_extra_partition_list),_fs_config_dirs_$(t))
include $(BUILD_PHONY_PACKAGE)
@@ -148,8 +156,9 @@
include $(CLEAR_VARS)
LOCAL_MODULE := fs_config_files_nonsystem
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
LOCAL_REQUIRED_MODULES := $(foreach t,$(fs_config_generate_extra_partition_list),_fs_config_files_$(t))
include $(BUILD_PHONY_PACKAGE)
@@ -160,8 +169,9 @@
include $(CLEAR_VARS)
LOCAL_MODULE := fs_config_dirs_system
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
LOCAL_MODULE_CLASS := ETC
LOCAL_INSTALLED_MODULE_STEM := fs_config_dirs
include $(BUILD_SYSTEM)/base_rules.mk
@@ -187,8 +197,9 @@
include $(CLEAR_VARS)
LOCAL_MODULE := fs_config_files_system
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
LOCAL_MODULE_CLASS := ETC
LOCAL_INSTALLED_MODULE_STEM := fs_config_files
include $(BUILD_SYSTEM)/base_rules.mk
@@ -215,8 +226,9 @@
include $(CLEAR_VARS)
LOCAL_MODULE := _fs_config_dirs_vendor
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
LOCAL_MODULE_CLASS := ETC
LOCAL_INSTALLED_MODULE_STEM := fs_config_dirs
LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR)/etc
@@ -241,8 +253,9 @@
include $(CLEAR_VARS)
LOCAL_MODULE := _fs_config_files_vendor
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
LOCAL_MODULE_CLASS := ETC
LOCAL_INSTALLED_MODULE_STEM := fs_config_files
LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR)/etc
@@ -270,8 +283,9 @@
include $(CLEAR_VARS)
LOCAL_MODULE := _fs_config_dirs_oem
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
LOCAL_MODULE_CLASS := ETC
LOCAL_INSTALLED_MODULE_STEM := fs_config_dirs
LOCAL_MODULE_PATH := $(TARGET_OUT_OEM)/etc
@@ -296,8 +310,9 @@
include $(CLEAR_VARS)
LOCAL_MODULE := _fs_config_files_oem
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
LOCAL_MODULE_CLASS := ETC
LOCAL_INSTALLED_MODULE_STEM := fs_config_files
LOCAL_MODULE_PATH := $(TARGET_OUT_OEM)/etc
@@ -325,8 +340,9 @@
include $(CLEAR_VARS)
LOCAL_MODULE := _fs_config_dirs_odm
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
LOCAL_MODULE_CLASS := ETC
LOCAL_INSTALLED_MODULE_STEM := fs_config_dirs
LOCAL_MODULE_PATH := $(TARGET_OUT_ODM)/etc
@@ -351,8 +367,9 @@
include $(CLEAR_VARS)
LOCAL_MODULE := _fs_config_files_odm
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
LOCAL_MODULE_CLASS := ETC
LOCAL_INSTALLED_MODULE_STEM := fs_config_files
LOCAL_MODULE_PATH := $(TARGET_OUT_ODM)/etc
@@ -380,8 +397,9 @@
include $(CLEAR_VARS)
LOCAL_MODULE := _fs_config_dirs_vendor_dlkm
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
LOCAL_MODULE_CLASS := ETC
LOCAL_INSTALLED_MODULE_STEM := fs_config_dirs
LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR_DLKM)/etc
@@ -406,8 +424,9 @@
include $(CLEAR_VARS)
LOCAL_MODULE := _fs_config_files_vendor_dlkm
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
LOCAL_MODULE_CLASS := ETC
LOCAL_INSTALLED_MODULE_STEM := fs_config_files
LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR_DLKM)/etc
@@ -435,8 +454,9 @@
include $(CLEAR_VARS)
LOCAL_MODULE := _fs_config_dirs_odm_dlkm
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
LOCAL_MODULE_CLASS := ETC
LOCAL_INSTALLED_MODULE_STEM := fs_config_dirs
LOCAL_MODULE_PATH := $(TARGET_OUT_ODM_DLKM)/etc
@@ -461,8 +481,9 @@
include $(CLEAR_VARS)
LOCAL_MODULE := _fs_config_files_odm_dlkm
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
LOCAL_MODULE_CLASS := ETC
LOCAL_INSTALLED_MODULE_STEM := fs_config_files
LOCAL_MODULE_PATH := $(TARGET_OUT_ODM_DLKM)/etc
@@ -482,6 +503,63 @@
endif
+ifneq ($(filter system_dlkm,$(fs_config_generate_extra_partition_list)),)
+##################################
+# Generate the system_dlkm/etc/fs_config_dirs binary file for the target
+# Add fs_config_dirs or fs_config_dirs_nonsystem to PRODUCT_PACKAGES
+# in the device make file to enable
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := _fs_config_dirs_system_dlkm
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
+LOCAL_MODULE_CLASS := ETC
+LOCAL_INSTALLED_MODULE_STEM := fs_config_dirs
+LOCAL_MODULE_PATH := $(TARGET_OUT_SYSTEM_DLKM)/etc
+include $(BUILD_SYSTEM)/base_rules.mk
+$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(vendor_android_filesystem_config)
+$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(vendor_capability_header)
+$(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN)
+$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(vendor_android_filesystem_config) $(vendor_capability_header)
+ @mkdir -p $(dir $@)
+ $< fsconfig \
+ --aid-header $(PRIVATE_ANDROID_FS_HDR) \
+ --capability-header $(PRIVATE_ANDROID_CAP_HDR) \
+ --partition system_dlkm \
+ --dirs \
+ --out_file $@ \
+ $(or $(PRIVATE_TARGET_FS_CONFIG_GEN),/dev/null)
+
+##################################
+# Generate the system_dlkm/etc/fs_config_files binary file for the target
+# Add fs_config_files or fs_config_files_nonsystem to PRODUCT_PACKAGES
+# in the device make file to enable
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := _fs_config_files_system_dlkm
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
+LOCAL_MODULE_CLASS := ETC
+LOCAL_INSTALLED_MODULE_STEM := fs_config_files
+LOCAL_MODULE_PATH := $(TARGET_OUT_SYSTEM_DLKM)/etc
+include $(BUILD_SYSTEM)/base_rules.mk
+$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_FS_HDR := $(vendor_android_filesystem_config)
+$(LOCAL_BUILT_MODULE): PRIVATE_ANDROID_CAP_HDR := $(vendor_capability_header)
+$(LOCAL_BUILT_MODULE): PRIVATE_TARGET_FS_CONFIG_GEN := $(TARGET_FS_CONFIG_GEN)
+$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/fs_config_generator.py $(TARGET_FS_CONFIG_GEN) $(vendor_android_filesystem_config) $(vendor_capability_header)
+ @mkdir -p $(dir $@)
+ $< fsconfig \
+ --aid-header $(PRIVATE_ANDROID_FS_HDR) \
+ --capability-header $(PRIVATE_ANDROID_CAP_HDR) \
+ --partition system_dlkm \
+ --files \
+ --out_file $@ \
+ $(or $(PRIVATE_TARGET_FS_CONFIG_GEN),/dev/null)
+
+endif
+
ifneq ($(BOARD_USES_PRODUCTIMAGE)$(BOARD_PRODUCTIMAGE_FILE_SYSTEM_TYPE),)
##################################
# Generate the product/etc/fs_config_dirs binary file for the target
@@ -490,8 +568,9 @@
include $(CLEAR_VARS)
LOCAL_MODULE := _fs_config_dirs_product
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
LOCAL_MODULE_CLASS := ETC
LOCAL_INSTALLED_MODULE_STEM := fs_config_dirs
LOCAL_MODULE_PATH := $(TARGET_OUT_PRODUCT)/etc
@@ -516,8 +595,9 @@
include $(CLEAR_VARS)
LOCAL_MODULE := _fs_config_files_product
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
LOCAL_MODULE_CLASS := ETC
LOCAL_INSTALLED_MODULE_STEM := fs_config_files
LOCAL_MODULE_PATH := $(TARGET_OUT_PRODUCT)/etc
@@ -544,8 +624,9 @@
include $(CLEAR_VARS)
LOCAL_MODULE := _fs_config_dirs_system_ext
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
LOCAL_MODULE_CLASS := ETC
LOCAL_INSTALLED_MODULE_STEM := fs_config_dirs
LOCAL_MODULE_PATH := $(TARGET_OUT_SYSTEM_EXT)/etc
@@ -570,8 +651,9 @@
include $(CLEAR_VARS)
LOCAL_MODULE := _fs_config_files_system_ext
-LOCAL_LICENSE_KINDS := legacy_restricted
-LOCAL_LICENSE_CONDITIONS := restricted
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := build/soong/licenses/LICENSE
LOCAL_MODULE_CLASS := ETC
LOCAL_INSTALLED_MODULE_STEM := fs_config_files
LOCAL_MODULE_PATH := $(TARGET_OUT_SYSTEM_EXT)/etc
diff --git a/tools/fs_get_stats/Android.bp b/tools/fs_get_stats/Android.bp
index 9457de4..0697999 100644
--- a/tools/fs_get_stats/Android.bp
+++ b/tools/fs_get_stats/Android.bp
@@ -1,10 +1,6 @@
package {
// See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "build_make_license"
- // to get the below license kinds:
- // legacy_restricted
- default_applicable_licenses: ["build_make_license"],
+ default_applicable_licenses: ["Android-Apache-2.0"],
}
cc_binary_host {
diff --git a/tools/generate-self-extracting-archive.py b/tools/generate-self-extracting-archive.py
index 5b0628d..c9f56cb 100755
--- a/tools/generate-self-extracting-archive.py
+++ b/tools/generate-self-extracting-archive.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#
# Copyright (C) 2019 The Android Open Source Project
#
@@ -120,7 +120,7 @@
def main(argv):
if len(argv) != 5:
- print 'generate-self-extracting-archive.py expects exactly 4 arguments'
+ print('generate-self-extracting-archive.py expects exactly 4 arguments')
sys.exit(1)
output_filename = argv[1]
@@ -134,11 +134,11 @@
license = license_file.read()
if not license:
- print 'License file was empty'
+ print('License file was empty')
sys.exit(1)
if 'SOFTWARE LICENSE AGREEMENT' not in license:
- print 'License does not look like a license'
+ print('License does not look like a license')
sys.exit(1)
comment_line = '# %s\n' % comment
diff --git a/tools/java-event-log-tags.py b/tools/java-event-log-tags.py
index 37cd712..4bd6d2b 100755
--- a/tools/java-event-log-tags.py
+++ b/tools/java-event-log-tags.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#
# Copyright (C) 2009 The Android Open Source Project
#
@@ -23,7 +23,7 @@
-h to display this usage message and exit.
"""
-import cStringIO
+from io import StringIO
import getopt
import os
import os.path
@@ -36,24 +36,24 @@
try:
opts, args = getopt.getopt(sys.argv[1:], "ho:")
-except getopt.GetoptError, err:
- print str(err)
- print __doc__
+except getopt.GetoptError as err:
+ print(str(err))
+ print(__doc__)
sys.exit(2)
for o, a in opts:
if o == "-h":
- print __doc__
+ print(__doc__)
sys.exit(2)
elif o == "-o":
output_file = a
else:
- print >> sys.stderr, "unhandled option %s" % (o,)
+ print("unhandled option %s" % (o,), file=sys.stderr)
sys.exit(1)
if len(args) != 1 and len(args) != 2:
- print "need one or two input files, not %d" % (len(args),)
- print __doc__
+ print("need one or two input files, not %d" % (len(args),))
+ print(__doc__)
sys.exit(1)
fn = args[0]
@@ -92,10 +92,10 @@
if tagfile.errors:
for fn, ln, msg in tagfile.errors:
- print >> sys.stderr, "%s:%d: error: %s" % (fn, ln, msg)
+ print("%s:%d: error: %s" % (fn, ln, msg), file=sys.stderr)
sys.exit(1)
-buffer = cStringIO.StringIO()
+buffer = StringIO()
buffer.write("/* This file is auto-generated. DO NOT MODIFY.\n"
" * Source file: %s\n"
" */\n\n" % (fn,))
diff --git a/tools/libhost/Android.bp b/tools/libhost/Android.bp
index a83f2e7..cd99af8 100644
--- a/tools/libhost/Android.bp
+++ b/tools/libhost/Android.bp
@@ -1,10 +1,6 @@
package {
// See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "build_make_license"
- // to get the below license kinds:
- // legacy_restricted
- default_applicable_licenses: ["build_make_license"],
+ default_applicable_licenses: ["Android-Apache-2.0"],
}
cc_library_host_static {
diff --git a/tools/merge-event-log-tags.py b/tools/merge-event-log-tags.py
index 64bad3f..292604c 100755
--- a/tools/merge-event-log-tags.py
+++ b/tools/merge-event-log-tags.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#
# Copyright (C) 2009 The Android Open Source Project
#
@@ -24,7 +24,7 @@
-h to display this usage message and exit.
"""
-import cStringIO
+from io import StringIO
import getopt
try:
import hashlib
@@ -48,21 +48,21 @@
try:
opts, args = getopt.getopt(sys.argv[1:], "ho:m:")
-except getopt.GetoptError, err:
- print str(err)
- print __doc__
+except getopt.GetoptError as err:
+ print(str(err))
+ print(__doc__)
sys.exit(2)
for o, a in opts:
if o == "-h":
- print __doc__
+ print(__doc__)
sys.exit(2)
elif o == "-o":
output_file = a
elif o == "-m":
pre_merged_file = a
else:
- print >> sys.stderr, "unhandled option %s" % (o,)
+ print("unhandled option %s" % (o,), file=sys.stderr)
sys.exit(1)
# Restrictions on tags:
@@ -133,12 +133,12 @@
if errors:
for fn, ln, msg in errors:
- print >> sys.stderr, "%s:%d: error: %s" % (fn, ln, msg)
+ print("%s:%d: error: %s" % (fn, ln, msg), file=sys.stderr)
sys.exit(1)
if warnings:
for fn, ln, msg in warnings:
- print >> sys.stderr, "%s:%d: warning: %s" % (fn, ln, msg)
+ print("%s:%d: warning: %s" % (fn, ln, msg), file=sys.stderr)
# Python's hash function (a) isn't great and (b) varies between
# versions of python. Using md5 is overkill here but is the same from
@@ -154,14 +154,14 @@
# If we were provided pre-merged tags (w/ the -m option), then don't
# ever try to allocate one, just fail if we don't have a number
-for name, t in sorted(by_tagname.iteritems()):
+for name, t in sorted(by_tagname.items()):
if t.tagnum is None:
if pre_merged_tags:
try:
t.tagnum = pre_merged_tags[t.tagname]
except KeyError:
- print >> sys.stderr, ("Error: Tag number not defined for tag `%s'."
- +" Have you done a full build?") % t.tagname
+ print("Error: Tag number not defined for tag `%s'. Have you done a full build?" % t.tagname,
+ file=sys.stderr)
sys.exit(1)
else:
while True:
@@ -174,8 +174,8 @@
# by_tagnum should be complete now; we've assigned numbers to all tags.
-buffer = cStringIO.StringIO()
-for n, t in sorted(by_tagnum.iteritems()):
+buffer = StringIO()
+for n, t in sorted(by_tagnum.items()):
if t.description:
buffer.write("%d %s %s\n" % (t.tagnum, t.tagname, t.description))
else:
diff --git a/tools/releasetools/Android.bp b/tools/releasetools/Android.bp
index fc588e4..7b2c290 100644
--- a/tools/releasetools/Android.bp
+++ b/tools/releasetools/Android.bp
@@ -50,6 +50,7 @@
],
libs: [
"releasetools_common",
+ "releasetools_fsverity_metadata_generator",
"releasetools_verity_utils",
],
required: [
@@ -100,14 +101,6 @@
python_library_host {
name: "ota_metadata_proto",
- version: {
- py2: {
- enabled: true,
- },
- py3: {
- enabled: true,
- },
- },
srcs: [
"ota_metadata.proto",
],
@@ -169,6 +162,7 @@
required: [
"brillo_update_payload",
"checkvintf",
+ "generate_gki_certificate",
"minigzip",
"lz4",
"toybox",
@@ -187,29 +181,15 @@
// Host libraries.
//
-python_defaults {
- name: "releasetools_library_defaults",
- version: {
- py2: {
- enabled: true,
- },
- py3: {
- enabled: true,
- },
- },
-}
-
python_library_host {
name: "releasetools_add_img_to_target_files",
defaults: [
- "releasetools_library_defaults",
"releasetools_add_img_to_target_files_defaults",
],
}
python_library_host {
name: "releasetools_apex_utils",
- defaults: ["releasetools_library_defaults"],
srcs: [
"apex_utils.py",
],
@@ -223,7 +203,6 @@
python_library_host {
name: "releasetools_build_image",
defaults: [
- "releasetools_library_defaults",
"releasetools_build_image_defaults",
],
}
@@ -231,7 +210,6 @@
python_library_host {
name: "releasetools_build_super_image",
defaults: [
- "releasetools_library_defaults",
"releasetools_build_super_image_defaults",
],
}
@@ -239,14 +217,12 @@
python_library_host {
name: "releasetools_check_target_files_vintf",
defaults: [
- "releasetools_library_defaults",
"releasetools_check_target_files_vintf_defaults",
],
}
python_library_host {
name: "releasetools_common",
- defaults: ["releasetools_library_defaults"],
srcs: [
"blockimgdiff.py",
"common.py",
@@ -261,6 +237,7 @@
"boot_signer",
"brotli",
"bsdiff",
+ "generate_gki_certificate",
"imgdiff",
"minigzip",
"lz4",
@@ -274,7 +251,6 @@
python_library_host {
name: "releasetools_img_from_target_files",
defaults: [
- "releasetools_library_defaults",
"releasetools_img_from_target_files_defaults",
],
}
@@ -282,14 +258,22 @@
python_library_host {
name: "releasetools_ota_from_target_files",
defaults: [
- "releasetools_library_defaults",
"releasetools_ota_from_target_files_defaults",
],
}
python_library_host {
+ name: "releasetools_fsverity_metadata_generator",
+ srcs: [
+ "fsverity_metadata_generator.py",
+ ],
+ libs: [
+ "fsverity_digests_proto_python",
+ ],
+}
+
+python_library_host {
name: "releasetools_verity_utils",
- defaults: ["releasetools_library_defaults"],
srcs: [
"verity_utils.py",
],
@@ -308,13 +292,8 @@
python_defaults {
name: "releasetools_binary_defaults",
version: {
- py2: {
- enabled: true,
- embedded_launcher: true,
- },
py3: {
- enabled: false,
- embedded_launcher: false,
+ embedded_launcher: true,
},
},
// TODO (b/140144201) Build imgdiff from releasetools_common
@@ -324,6 +303,7 @@
"brotli",
"bsdiff",
"deapexer",
+ "generate_gki_certificate",
"imgdiff",
"minigzip",
"lz4",
@@ -443,7 +423,6 @@
name: "releasetools_find_shareduid_violation",
defaults: [
"releasetools_find_shareduid_violation_defaults",
- "releasetools_library_defaults",
],
}
@@ -577,6 +556,32 @@
],
}
+python_binary_host {
+ name: "fsverity_manifest_generator",
+ srcs: [
+ "fsverity_manifest_generator.py",
+ ],
+ libs: [
+ "fsverity_digests_proto_python",
+ "releasetools_common",
+ ],
+ required: [
+ "aapt2",
+ "apksigner",
+ "fsverity",
+ ],
+}
+
+python_binary_host {
+ name: "fsverity_metadata_generator",
+ srcs: [
+ "fsverity_metadata_generator.py",
+ ],
+ required: [
+ "fsverity",
+ ],
+}
+
//
// Tests.
//
@@ -628,39 +633,9 @@
name: "releasetools_test",
defaults: ["releasetools_test_defaults"],
main: "test_utils.py",
- version: {
- py2: {
- enabled: true,
- // When using embedded launcher, atest will try (but may fail) to load libc++.so from
- // host, because the test executable won't be able to find the needed libs via its
- // runpath.
- embedded_launcher: false,
- },
- py3: {
- enabled: false,
- embedded_launcher: false,
- },
- },
- test_options: {
- unit_test: true,
- },
-}
-
-python_test_host {
- name: "releasetools_py3_test",
- defaults: ["releasetools_test_defaults"],
- main: "test_utils.py",
- test_suites: ["general-tests"],
- version: {
- py2: {
- enabled: false,
- embedded_launcher: false,
- },
- py3: {
- enabled: true,
- embedded_launcher: false,
- },
- },
+ // Don't use embedded_launcher, atest will try (but may fail) to load libc++.so from
+ // host, because the test executable won't be able to find the needed libs via its
+ // runpath.
test_options: {
unit_test: true,
},
diff --git a/tools/releasetools/OWNERS b/tools/releasetools/OWNERS
index 9962836..d0b8627 100644
--- a/tools/releasetools/OWNERS
+++ b/tools/releasetools/OWNERS
@@ -1,6 +1,7 @@
elsk@google.com
nhdo@google.com
xunchang@google.com
+zhangkelvin@google.com
per-file merge_*.py = danielnorman@google.com
diff --git a/tools/releasetools/add_img_to_target_files.py b/tools/releasetools/add_img_to_target_files.py
index 2a4b56b..88e6fd2 100644
--- a/tools/releasetools/add_img_to_target_files.py
+++ b/tools/releasetools/add_img_to_target_files.py
@@ -275,6 +275,21 @@
block_list=block_list)
return img.name
+def AddSystemDlkm(output_zip):
+ """Turn the contents of SystemDlkm into an system_dlkm image and store it in output_zip."""
+
+ img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "system_dlkm.img")
+ if os.path.exists(img.name):
+ logger.info("system_dlkm.img already exists; no need to rebuild...")
+ return img.name
+
+ block_list = OutputFile(
+ output_zip, OPTIONS.input_tmp, "IMAGES", "system_dlkm.map")
+ CreateImage(
+ OPTIONS.input_tmp, OPTIONS.info_dict, "system_dlkm", img,
+ block_list=block_list)
+ return img.name
+
def AddDtbo(output_zip):
"""Adds the DTBO image.
@@ -759,14 +774,16 @@
has_recovery = OPTIONS.info_dict.get("no_recovery") != "true"
has_boot = OPTIONS.info_dict.get("no_boot") != "true"
+ has_init_boot = OPTIONS.info_dict.get("init_boot") == "true"
has_vendor_boot = OPTIONS.info_dict.get("vendor_boot") == "true"
- # {vendor,odm,product,system_ext,vendor_dlkm,odm_dlkm, system, system_other}.img
+ # {vendor,odm,product,system_ext,vendor_dlkm,odm_dlkm, system_dlkm, system, system_other}.img
# can be built from source, or dropped into target_files.zip as a prebuilt blob.
has_vendor = HasPartition("vendor")
has_odm = HasPartition("odm")
has_vendor_dlkm = HasPartition("vendor_dlkm")
has_odm_dlkm = HasPartition("odm_dlkm")
+ has_system_dlkm = HasPartition("system_dlkm")
has_product = HasPartition("product")
has_system_ext = HasPartition("system_ext")
has_system = HasPartition("system")
@@ -819,6 +836,17 @@
if output_zip:
boot_image.AddToZip(output_zip)
+ if has_init_boot:
+ banner("init_boot")
+ init_boot_image = common.GetBootableImage(
+ "IMAGES/init_boot.img", "init_boot.img", OPTIONS.input_tmp, "INIT_BOOT")
+ if init_boot_image:
+ partitions['init_boot'] = os.path.join(OPTIONS.input_tmp, "IMAGES", "init_boot.img")
+ if not os.path.exists(partitions['init_boot']):
+ init_boot_image.WriteToDir(OPTIONS.input_tmp)
+ if output_zip:
+ init_boot_image.AddToZip(output_zip)
+
if has_vendor_boot:
banner("vendor_boot")
vendor_boot_image = common.GetVendorBootImage(
@@ -871,6 +899,7 @@
("odm", has_odm, AddOdm, []),
("vendor_dlkm", has_vendor_dlkm, AddVendorDlkm, []),
("odm_dlkm", has_odm_dlkm, AddOdmDlkm, []),
+ ("system_dlkm", has_system_dlkm, AddSystemDlkm, []),
("system_other", has_system_other, AddSystemOther, []),
)
for call in add_partition_calls:
diff --git a/tools/releasetools/build_image.py b/tools/releasetools/build_image.py
index 38104af..4b5846d 100755
--- a/tools/releasetools/build_image.py
+++ b/tools/releasetools/build_image.py
@@ -24,6 +24,7 @@
from __future__ import print_function
+import glob
import logging
import os
import os.path
@@ -447,7 +448,6 @@
return mkfs_output
-
def BuildImage(in_dir, prop_dict, out_file, target_out=None):
"""Builds an image for the files under in_dir and writes it to out_file.
@@ -578,6 +578,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)
@@ -589,7 +593,6 @@
if verity_image_builder:
verity_image_builder.Build(out_file)
-
def ImagePropFromGlobalDict(glob_dict, mount_point):
"""Build an image property dictionary from the global dictionary.
@@ -648,6 +651,7 @@
"oem",
"product",
"system",
+ "system_dlkm",
"system_ext",
"system_other",
"vendor",
@@ -770,6 +774,8 @@
copy_prop("partition_size", "vendor_dlkm_size")
elif mount_point == "odm_dlkm":
copy_prop("partition_size", "odm_dlkm_size")
+ elif mount_point == "system_dlkm":
+ copy_prop("partition_size", "system_dlkm_size")
elif mount_point == "product":
copy_prop("partition_size", "product_size")
elif mount_point == "system_ext":
@@ -813,6 +819,8 @@
mount_point = "vendor_dlkm"
elif image_filename == "odm_dlkm.img":
mount_point = "odm_dlkm"
+ elif image_filename == "system_dlkm.img":
+ mount_point = "system_dlkm"
elif image_filename == "oem.img":
mount_point = "oem"
elif image_filename == "product.img":
diff --git a/tools/releasetools/check_target_files_signatures.py b/tools/releasetools/check_target_files_signatures.py
index 6e02e4d..0f56fb9 100755
--- a/tools/releasetools/check_target_files_signatures.py
+++ b/tools/releasetools/check_target_files_signatures.py
@@ -65,10 +65,13 @@
# extra field anyway).
# Issue #14315: https://bugs.python.org/issue14315, fixed in Python 2.7.8 and
# Python 3.5.0 alpha 1.
+
+
class MyZipInfo(zipfile.ZipInfo):
def _decodeExtra(self):
pass
+
zipfile.ZipInfo = MyZipInfo
@@ -83,6 +86,7 @@
def AddProblem(msg):
+ logger.error(msg)
PROBLEMS.append(" ".join(PROBLEM_PREFIX) + " " + msg)
@@ -204,7 +208,7 @@
for info in apk.infolist():
filename = info.filename
if (filename.startswith("META-INF/") and
- info.filename.endswith((".DSA", ".RSA"))):
+ info.filename.endswith((".DSA", ".RSA"))):
pkcs7 = apk.read(filename)
cert = CertFromPKCS7(pkcs7, filename)
if not cert:
@@ -266,7 +270,7 @@
stdout=subprocess.PIPE)
manifest, err = p.communicate()
if err:
- AddProblem("failed to read manifest")
+ AddProblem("failed to read manifest " + full_filename)
return
self.shared_uid = None
@@ -279,15 +283,15 @@
name = m.group(1)
if name == "android:sharedUserId":
if self.shared_uid is not None:
- AddProblem("multiple sharedUserId declarations")
+ AddProblem("multiple sharedUserId declarations " + full_filename)
self.shared_uid = m.group(2)
elif name == "package":
if self.package is not None:
- AddProblem("multiple package declarations")
+ AddProblem("multiple package declarations " + full_filename)
self.package = m.group(2)
if self.package is None:
- AddProblem("no package declaration")
+ AddProblem("no package declaration " + full_filename)
class TargetFiles(object):
@@ -400,7 +404,12 @@
for _, digest in order:
print("%s:" % (ALL_CERTS.Get(digest),))
apks = by_digest[digest]
- apks.sort()
+ apks.sort(key=lambda x: x[0])
+ for i in range(1, len(apks)):
+ pkgname, apk = apks[i]
+ if pkgname == apks[i-1][0]:
+ print("Both {} and {} have same package name {}".format(
+ apk.filename, apks[i-1][1].filename, pkgname))
for _, apk in apks:
if apk.shared_uid:
print(" %-*s %-*s [%s]" % (self.max_fn_len, apk.filename,
@@ -527,8 +536,5 @@
try:
r = main(sys.argv[1:])
sys.exit(r)
- except common.ExternalError as e:
- print("\n ERROR: %s\n" % (e,))
- sys.exit(1)
finally:
common.Cleanup()
diff --git a/tools/releasetools/check_target_files_vintf.py b/tools/releasetools/check_target_files_vintf.py
index 213ae21..6fc79d2 100755
--- a/tools/releasetools/check_target_files_vintf.py
+++ b/tools/releasetools/check_target_files_vintf.py
@@ -46,7 +46,7 @@
'/product': ('PRODUCT', 'SYSTEM/product'),
'/odm': ('ODM', 'VENDOR/odm', 'SYSTEM/vendor/odm'),
'/system_ext': ('SYSTEM_EXT', 'SYSTEM/system_ext'),
- # vendor_dlkm and odm_dlkm does not have VINTF files.
+ # vendor_dlkm, odm_dlkm, and system_dlkm does not have VINTF files.
}
UNZIP_PATTERN = ['META/*', '*/build.prop']
diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py
index 1533030..686102a 100644
--- a/tools/releasetools/common.py
+++ b/tools/releasetools/common.py
@@ -68,8 +68,12 @@
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.aapt2_path = "aapt2"
self.java_path = "java" # Use the one on the path by default.
self.java_args = ["-Xmx2048m"] # The default JVM args.
self.android_jar_path = None
@@ -108,9 +112,9 @@
# descriptor into vbmeta.img. When adding a new entry here, the
# AVB_FOOTER_ARGS_BY_PARTITION in sign_target_files_apks need to be updated
# accordingly.
-AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'pvmfw', 'recovery',
+AVB_PARTITIONS = ('boot', 'init_boot', 'dtbo', 'odm', 'product', 'pvmfw', 'recovery',
'system', 'system_ext', 'vendor', 'vendor_boot',
- 'vendor_dlkm', 'odm_dlkm')
+ 'vendor_dlkm', 'odm_dlkm', 'system_dlkm')
# Chained VBMeta partitions.
AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
@@ -124,10 +128,11 @@
'odm',
'vendor_dlkm',
'odm_dlkm',
+ 'system_dlkm',
]
# Partitions with a build.prop file
-PARTITIONS_WITH_BUILD_PROP = PARTITIONS_WITH_CARE_MAP + ['boot']
+PARTITIONS_WITH_BUILD_PROP = PARTITIONS_WITH_CARE_MAP + ['boot', 'init_boot']
# See sysprop.mk. If file is moved, add new search paths here; don't remove
# existing search paths.
@@ -797,7 +802,7 @@
# Redirect {partition}_base_fs_file for each of the named partitions.
for part_name in ["system", "vendor", "system_ext", "product", "odm",
- "vendor_dlkm", "odm_dlkm"]:
+ "vendor_dlkm", "odm_dlkm", "system_dlkm"]:
key_name = part_name + "_base_fs_file"
if key_name not in d:
continue
@@ -932,9 +937,9 @@
def FromInputFile(input_file, name, placeholder_values=None, ramdisk_format=RamdiskFormat.LZ4):
"""Loads the build.prop file and builds the attributes."""
- if name == "boot":
+ if name in ("boot", "init_boot"):
data = PartitionBuildProps._ReadBootPropFile(
- input_file, ramdisk_format=ramdisk_format)
+ input_file, name, ramdisk_format=ramdisk_format)
else:
data = PartitionBuildProps._ReadPartitionPropFile(input_file, name)
@@ -943,15 +948,16 @@
return props
@staticmethod
- def _ReadBootPropFile(input_file, ramdisk_format):
+ def _ReadBootPropFile(input_file, partition_name, ramdisk_format):
"""
Read build.prop for boot image from input_file.
Return empty string if not found.
"""
+ image_path = 'IMAGES/' + partition_name + '.img'
try:
- boot_img = ExtractFromInputFile(input_file, 'IMAGES/boot.img')
+ boot_img = ExtractFromInputFile(input_file, image_path)
except KeyError:
- logger.warning('Failed to read IMAGES/boot.img')
+ logger.warning('Failed to read %s', image_path)
return ''
prop_file = GetBootImageBuildProp(boot_img, ramdisk_format=ramdisk_format)
if prop_file is None:
@@ -973,6 +979,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
@@ -1238,6 +1246,7 @@
"VENDOR_DLKM", "VENDOR/vendor_dlkm", "SYSTEM/vendor/vendor_dlkm"
],
"odm_dlkm": ["ODM_DLKM", "VENDOR/odm_dlkm", "SYSTEM/vendor/odm_dlkm"],
+ "system_dlkm": ["SYSTEM_DLKM", "SYSTEM/system_dlkm"],
}
partition_map = {}
for partition, subdirs in possible_subdirs.items():
@@ -1389,34 +1398,52 @@
return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
-def AppendGkiSigningArgs(cmd):
- """Append GKI signing arguments for mkbootimg."""
- # e.g., --gki_signing_key path/to/signing_key
- # --gki_signing_algorithm SHA256_RSA4096"
+def _HasGkiCertificationArgs():
+ return ("gki_signing_key_path" in OPTIONS.info_dict and
+ "gki_signing_algorithm" in OPTIONS.info_dict)
+
+def _GenerateGkiCertificate(image, image_name, partition_name):
key_path = OPTIONS.info_dict.get("gki_signing_key_path")
- # It's fine that a non-GKI boot.img has no gki_signing_key_path.
- if not key_path:
- return
+ algorithm = OPTIONS.info_dict.get("gki_signing_algorithm")
if not os.path.exists(key_path) and OPTIONS.search_path:
new_key_path = os.path.join(OPTIONS.search_path, key_path)
if os.path.exists(new_key_path):
key_path = new_key_path
- # Checks key_path exists, before appending --gki_signing_* args.
+ # Checks key_path exists, before processing --gki_signing_* args.
if not os.path.exists(key_path):
raise ExternalError(
'gki_signing_key_path: "{}" not found'.format(key_path))
- algorithm = OPTIONS.info_dict.get("gki_signing_algorithm")
- if key_path and algorithm:
- cmd.extend(["--gki_signing_key", key_path,
- "--gki_signing_algorithm", algorithm])
+ output_certificate = tempfile.NamedTemporaryFile()
+ cmd = [
+ "generate_gki_certificate",
+ "--name", image_name,
+ "--algorithm", algorithm,
+ "--key", key_path,
+ "--output", output_certificate.name,
+ image,
+ ]
- signature_args = OPTIONS.info_dict.get("gki_signing_signature_args")
- if signature_args:
- cmd.extend(["--gki_signing_signature_args", signature_args])
+ signature_args = OPTIONS.info_dict.get("gki_signing_signature_args", "")
+ signature_args = signature_args.strip()
+ if signature_args:
+ cmd.extend(["--additional_avb_args", signature_args])
+
+ args = OPTIONS.info_dict.get(
+ "avb_" + partition_name + "_add_hash_footer_args", "")
+ args = args.strip()
+ if args:
+ cmd.extend(["--additional_avb_args", args])
+
+ RunAndCheckOutput(cmd)
+
+ output_certificate.seek(os.SEEK_SET, 0)
+ data = output_certificate.read()
+ output_certificate.close()
+ return data
def BuildVBMeta(image_path, partitions, name, needed_partitions):
@@ -1534,12 +1561,16 @@
logger.info("Excluded kernel binary from recovery image.")
else:
kernel = "kernel"
+ elif partition_name == "init_boot":
+ pass
else:
kernel = image_name.replace("boot", "kernel")
kernel = kernel.replace(".img", "")
if kernel and not os.access(os.path.join(sourcedir, kernel), os.F_OK):
return None
+ kernel_path = os.path.join(sourcedir, kernel) if kernel else None
+
if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
return None
@@ -1554,8 +1585,8 @@
mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
cmd = [mkbootimg]
- if kernel:
- cmd += ["--kernel", os.path.join(sourcedir, kernel)]
+ if kernel_path is not None:
+ cmd.extend(["--kernel", kernel_path])
fn = os.path.join(sourcedir, "second")
if os.access(fn, os.F_OK):
@@ -1588,20 +1619,38 @@
# Fall back to "mkbootimg_args" for recovery image
# in case "recovery_mkbootimg_args" is not set.
args = info_dict.get("mkbootimg_args")
+ elif partition_name == "init_boot":
+ args = info_dict.get("mkbootimg_init_args")
else:
args = info_dict.get("mkbootimg_args")
if args and args.strip():
cmd.extend(shlex.split(args))
- args = info_dict.get("mkbootimg_version_args")
- if args and args.strip():
- cmd.extend(shlex.split(args))
+ boot_signature = None
+ if _HasGkiCertificationArgs():
+ # Certify GKI images.
+ boot_signature_bytes = b''
+ if kernel_path is not None:
+ boot_signature_bytes += _GenerateGkiCertificate(
+ kernel_path, "generic_kernel", "boot")
+ if has_ramdisk:
+ boot_signature_bytes += _GenerateGkiCertificate(
+ ramdisk_img.name, "generic_ramdisk", "init_boot")
+
+ if len(boot_signature_bytes) > 0:
+ boot_signature = tempfile.NamedTemporaryFile()
+ boot_signature.write(boot_signature_bytes)
+ boot_signature.flush()
+ cmd.extend(["--boot_signature", boot_signature.name])
+ else:
+ # Certified GKI boot/init_boot image mustn't set 'mkbootimg_version_args'.
+ args = info_dict.get("mkbootimg_version_args")
+ if args and args.strip():
+ cmd.extend(shlex.split(args))
if has_ramdisk:
cmd.extend(["--ramdisk", ramdisk_img.name])
- AppendGkiSigningArgs(cmd)
-
img_unsigned = None
if info_dict.get("vboot"):
img_unsigned = tempfile.NamedTemporaryFile()
@@ -1679,6 +1728,9 @@
ramdisk_img.close()
img.close()
+ if boot_signature is not None:
+ boot_signature.close()
+
return data
@@ -1689,8 +1741,8 @@
Args:
image_path: The full path of the image, e.g., /path/to/boot.img.
prebuilt_name: The prebuilt image name, e.g., boot.img, boot-5.4-gz.img,
- boot-5.10.img, recovery.img.
- partition_name: The partition name, e.g., 'boot' or 'recovery'.
+ boot-5.10.img, recovery.img or init_boot.img.
+ partition_name: The partition name, e.g., 'boot', 'init_boot' or 'recovery'.
info_dict: The information dict read from misc_info.txt.
"""
if info_dict is None:
@@ -1714,6 +1766,35 @@
RunAndCheckOutput(cmd)
+def HasRamdisk(partition_name, info_dict=None):
+ """Returns true/false to see if a bootable image should have a ramdisk.
+
+ Args:
+ partition_name: The partition name, e.g., 'boot', 'init_boot' or 'recovery'.
+ info_dict: The information dict read from misc_info.txt.
+ """
+ if info_dict is None:
+ info_dict = OPTIONS.info_dict
+
+ if partition_name != "boot":
+ return True # init_boot.img or recovery.img has a ramdisk.
+
+ if info_dict.get("recovery_as_boot") == "true":
+ return True # the recovery-as-boot boot.img has a RECOVERY ramdisk.
+
+ if info_dict.get("system_root_image") == "true":
+ # The ramdisk content is merged into the system.img, so there is NO
+ # ramdisk in the boot.img or boot-<kernel version>.img.
+ return False
+
+ if info_dict.get("init_boot") == "true":
+ # The ramdisk is moved to the init_boot.img, so there is NO
+ # ramdisk in the boot.img or boot-<kernel version>.img.
+ return False
+
+ return True
+
+
def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
info_dict=None, two_step_image=False):
"""Return a File object with the desired bootable image.
@@ -1735,23 +1816,18 @@
logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
return File.FromLocalFile(name, prebuilt_path)
+ partition_name = tree_subdir.lower()
prebuilt_path = os.path.join(unpack_dir, "PREBUILT_IMAGES", prebuilt_name)
if os.path.exists(prebuilt_path):
logger.info("Re-signing prebuilt %s from PREBUILT_IMAGES...", prebuilt_name)
signed_img = MakeTempFile()
shutil.copy(prebuilt_path, signed_img)
- partition_name = tree_subdir.lower()
_SignBootableImage(signed_img, prebuilt_name, partition_name, info_dict)
return File.FromLocalFile(name, signed_img)
logger.info("building image from target_files %s...", tree_subdir)
- # With system_root_image == "true", we don't pack ramdisk into the boot image.
- # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
- # for recovery.
- has_ramdisk = (info_dict.get("system_root_image") != "true" or
- prebuilt_name != "boot.img" or
- info_dict.get("recovery_as_boot") == "true")
+ has_ramdisk = HasRamdisk(partition_name, info_dict)
fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
@@ -2150,8 +2226,8 @@
def GetMinSdkVersion(apk_name):
"""Gets the minSdkVersion declared in the APK.
- It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
- This can be both a decimal number (API Level) or a codename.
+ It calls OPTIONS.aapt2_path to query the embedded minSdkVersion from the given
+ APK file. This can be both a decimal number (API Level) or a codename.
Args:
apk_name: The APK filename.
@@ -2163,7 +2239,7 @@
ExternalError: On failing to obtain the min SDK version.
"""
proc = Run(
- ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
+ [OPTIONS.aapt2_path, "dump", "badging", apk_name], stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdoutdata, stderrdata = proc.communicate()
if proc.returncode != 0:
@@ -2439,7 +2515,7 @@
opts, args = getopt.getopt(
argv, "hvp:s:x:" + extra_opts,
["help", "verbose", "path=", "signapk_path=",
- "signapk_shared_library_path=", "extra_signapk_args=",
+ "signapk_shared_library_path=", "extra_signapk_args=", "aapt2_path=",
"java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
"private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
"verity_signer_path=", "verity_signer_args=", "device_specific=",
@@ -2463,6 +2539,8 @@
OPTIONS.signapk_shared_library_path = a
elif o in ("--extra_signapk_args",):
OPTIONS.extra_signapk_args = shlex.split(a)
+ elif o in ("--aapt2_path",):
+ OPTIONS.aapt2_path = a
elif o in ("--java_path",):
OPTIONS.java_path = a
elif o in ("--java_args",):
@@ -3858,7 +3936,10 @@
disable_sparse = OPTIONS.info_dict.get(which + "_disable_sparse")
image_blocks = int(image_size) // 4096 - 1
- assert image_blocks > 0, "blocks for {} must be positive".format(which)
+ # It's OK for image_blocks to be 0, because care map ranges are inclusive.
+ # So 0-0 means "just block 0", which is valid.
+ assert image_blocks >= 0, "blocks for {} must be non-negative, image size: {}".format(
+ which, image_size)
# For sparse images, we will only check the blocks that are listed in the care
# map, i.e. the ones with meaningful data.
diff --git a/tools/releasetools/fsverity_manifest_generator.py b/tools/releasetools/fsverity_manifest_generator.py
new file mode 100644
index 0000000..b8184bc
--- /dev/null
+++ b/tools/releasetools/fsverity_manifest_generator.py
@@ -0,0 +1,115 @@
+#!/usr/bin/env python3
+#
+# Copyright 2022 Google Inc. All rights reserved.
+#
+# 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.
+
+"""
+`fsverity_manifest_generator` generates build manifest APK file containing
+digests of target files. The APK file is signed so the manifest inside the APK
+can be trusted.
+"""
+
+import argparse
+import common
+import os
+import subprocess
+import sys
+from fsverity_digests_pb2 import FSVerityDigests
+
+HASH_ALGORITHM = 'sha256'
+
+def _digest(fsverity_path, input_file):
+ cmd = [fsverity_path, 'digest', input_file]
+ cmd.extend(['--compact'])
+ cmd.extend(['--hash-alg', HASH_ALGORITHM])
+ out = subprocess.check_output(cmd, universal_newlines=True).strip()
+ return bytes(bytearray.fromhex(out))
+
+if __name__ == '__main__':
+ p = argparse.ArgumentParser()
+ p.add_argument(
+ '--output',
+ help='Path to the output manifest APK',
+ required=True)
+ p.add_argument(
+ '--fsverity-path',
+ help='path to the fsverity program',
+ required=True)
+ p.add_argument(
+ '--aapt2-path',
+ help='path to the aapt2 program',
+ required=True)
+ p.add_argument(
+ '--min-sdk-version',
+ help='minimum supported sdk version of the generated manifest apk',
+ required=True)
+ p.add_argument(
+ '--version-code',
+ help='version code for the generated manifest apk',
+ required=True)
+ p.add_argument(
+ '--version-name',
+ help='version name for the generated manifest apk',
+ required=True)
+ p.add_argument(
+ '--framework-res',
+ help='path to framework-res.apk',
+ required=True)
+ p.add_argument(
+ '--apksigner-path',
+ help='path to the apksigner program',
+ required=True)
+ p.add_argument(
+ '--apk-key-path',
+ help='path to the apk key',
+ required=True)
+ p.add_argument(
+ '--apk-manifest-path',
+ help='path to AndroidManifest.xml',
+ required=True)
+ p.add_argument(
+ '--base-dir',
+ help='directory to use as a relative root for the inputs',
+ required=True)
+ p.add_argument(
+ 'inputs',
+ nargs='+',
+ help='input file for the build manifest')
+ args = p.parse_args(sys.argv[1:])
+
+ digests = FSVerityDigests()
+ for f in sorted(args.inputs):
+ # f is a full path for now; make it relative so it starts with {mount_point}/
+ digest = digests.digests[os.path.relpath(f, args.base_dir)]
+ digest.digest = _digest(args.fsverity_path, f)
+ digest.hash_alg = HASH_ALGORITHM
+
+ temp_dir = common.MakeTempDir()
+
+ os.mkdir(os.path.join(temp_dir, "assets"))
+ metadata_path = os.path.join(temp_dir, "assets", "build_manifest.pb")
+ with open(metadata_path, "wb") as f:
+ f.write(digests.SerializeToString())
+
+ common.RunAndCheckOutput([args.aapt2_path, "link",
+ "-A", os.path.join(temp_dir, "assets"),
+ "-o", args.output,
+ "--min-sdk-version", args.min_sdk_version,
+ "--version-code", args.version_code,
+ "--version-name", args.version_name,
+ "-I", args.framework_res,
+ "--manifest", args.apk_manifest_path])
+ common.RunAndCheckOutput([args.apksigner_path, "sign", "--in", args.output,
+ "--cert", args.apk_key_path + ".x509.pem",
+ "--key", args.apk_key_path + ".pk8"])
diff --git a/tools/releasetools/fsverity_metadata_generator.py b/tools/releasetools/fsverity_metadata_generator.py
new file mode 100644
index 0000000..fa7cd39
--- /dev/null
+++ b/tools/releasetools/fsverity_metadata_generator.py
@@ -0,0 +1,244 @@
+#!/usr/bin/env python
+#
+# Copyright 2021 Google Inc. All rights reserved.
+#
+# 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.
+
+"""
+`fsverity_metadata_generator` generates fsverity metadata and signature to a
+container file
+
+This actually is a simple wrapper around the `fsverity` program. A file is
+signed by the program which produces the PKCS#7 signature file, merkle tree file
+, and the fsverity_descriptor file. Then the files are packed into a single
+output file so that the information about the signing stays together.
+
+Currently, the output of this script is used by `fd_server` which is the host-
+side backend of an authfs filesystem. `fd_server` uses this file in case when
+the underlying filesystem (ext4, etc.) on the device doesn't support the
+fsverity feature natively in which case the information is read directly from
+the filesystem using ioctl.
+"""
+
+import argparse
+import os
+import re
+import shutil
+import subprocess
+import sys
+import tempfile
+from struct import *
+
+class TempDirectory(object):
+ def __enter__(self):
+ self.name = tempfile.mkdtemp()
+ return self.name
+
+ def __exit__(self, *unused):
+ shutil.rmtree(self.name)
+
+class FSVerityMetadataGenerator:
+ def __init__(self, fsverity_path):
+ self._fsverity_path = fsverity_path
+
+ # Default values for some properties
+ self.set_hash_alg("sha256")
+ self.set_signature('none')
+
+ def set_key_format(self, key_format):
+ self._key_format = key_format
+
+ def set_key(self, key):
+ self._key = key
+
+ def set_cert(self, cert):
+ self._cert = cert
+
+ def set_hash_alg(self, hash_alg):
+ self._hash_alg = hash_alg
+
+ def set_signature(self, signature):
+ self._signature = signature
+
+ def _raw_signature(pkcs7_sig_file):
+ """ Extracts raw signature from DER formatted PKCS#7 detached signature file
+
+ Do that by parsing the ASN.1 tree to get the location of the signature
+ in the file and then read the portion.
+ """
+
+ # Note: there seems to be no public python API (even in 3p modules) that
+ # provides direct access to the raw signature at this moment. So, `openssl
+ # asn1parse` commandline tool is used instead.
+ cmd = ['openssl', 'asn1parse']
+ cmd.extend(['-inform', 'DER'])
+ cmd.extend(['-in', pkcs7_sig_file])
+ out = subprocess.check_output(cmd, universal_newlines=True)
+
+ # The signature is the last element in the tree
+ last_line = out.splitlines()[-1]
+ m = re.search('(\d+):.*hl=\s*(\d+)\s*l=\s*(\d+)\s*.*OCTET STRING', last_line)
+ if not m:
+ raise RuntimeError("Failed to parse asn1parse output: " + out)
+ offset = int(m.group(1))
+ header_len = int(m.group(2))
+ size = int(m.group(3))
+ with open(pkcs7_sig_file, 'rb') as f:
+ f.seek(offset + header_len)
+ return f.read(size)
+
+ def digest(self, input_file):
+ cmd = [self._fsverity_path, 'digest', input_file]
+ cmd.extend(['--compact'])
+ cmd.extend(['--hash-alg', self._hash_alg])
+ out = subprocess.check_output(cmd, universal_newlines=True).strip()
+ return bytes(bytearray.fromhex(out))
+
+ def generate(self, input_file, output_file=None):
+ if self._signature != 'none':
+ if not self._key:
+ raise RuntimeError("key must be specified.")
+ if not self._cert:
+ raise RuntimeError("cert must be specified.")
+
+ if not output_file:
+ output_file = input_file + '.fsv_meta'
+
+ with TempDirectory() as temp_dir:
+ self._do_generate(input_file, output_file, temp_dir)
+
+ def _do_generate(self, input_file, output_file, work_dir):
+ # temporary files
+ desc_file = os.path.join(work_dir, 'desc')
+ merkletree_file = os.path.join(work_dir, 'merkletree')
+ sig_file = os.path.join(work_dir, 'signature')
+
+ # run the fsverity util to create the temporary files
+ cmd = [self._fsverity_path]
+ if self._signature == 'none':
+ cmd.append('digest')
+ cmd.append(input_file)
+ else:
+ cmd.append('sign')
+ cmd.append(input_file)
+ cmd.append(sig_file)
+
+ # If key is DER, convert DER private key to PEM
+ if self._key_format == 'der':
+ pem_key = os.path.join(work_dir, 'key.pem')
+ key_cmd = ['openssl', 'pkcs8']
+ key_cmd.extend(['-inform', 'DER'])
+ key_cmd.extend(['-in', self._key])
+ key_cmd.extend(['-nocrypt'])
+ key_cmd.extend(['-out', pem_key])
+ subprocess.check_call(key_cmd)
+ else:
+ pem_key = self._key
+
+ cmd.extend(['--key', pem_key])
+ cmd.extend(['--cert', self._cert])
+ cmd.extend(['--hash-alg', self._hash_alg])
+ cmd.extend(['--block-size', '4096'])
+ cmd.extend(['--out-merkle-tree', merkletree_file])
+ cmd.extend(['--out-descriptor', desc_file])
+ subprocess.check_call(cmd, stdout=open(os.devnull, 'w'))
+
+ with open(output_file, 'wb') as out:
+ # 1. version
+ out.write(pack('<I', 1))
+
+ # 2. fsverity_descriptor
+ with open(desc_file, 'rb') as f:
+ out.write(f.read())
+
+ # 3. signature
+ SIG_TYPE_NONE = 0
+ SIG_TYPE_PKCS7 = 1
+ SIG_TYPE_RAW = 2
+ if self._signature == 'raw':
+ out.write(pack('<I', SIG_TYPE_RAW))
+ sig = self._raw_signature(sig_file)
+ out.write(pack('<I', len(sig)))
+ out.write(sig)
+ elif self._signature == 'pkcs7':
+ with open(sig_file, 'rb') as f:
+ out.write(pack('<I', SIG_TYPE_PKCS7))
+ sig = f.read()
+ out.write(pack('<I', len(sig)))
+ out.write(sig)
+ else:
+ out.write(pack('<I', SIG_TYPE_NONE))
+ out.write(pack('<I', 0))
+
+ # 4. merkle tree
+ with open(merkletree_file, 'rb') as f:
+ # merkle tree is placed at the next nearest page boundary to make
+ # mmapping possible
+ out.seek(next_page(out.tell()))
+ out.write(f.read())
+
+def next_page(n):
+ """ Returns the next nearest page boundary from `n` """
+ PAGE_SIZE = 4096
+ return (n + PAGE_SIZE - 1) // PAGE_SIZE * PAGE_SIZE
+
+if __name__ == '__main__':
+ p = argparse.ArgumentParser()
+ p.add_argument(
+ '--output',
+ help='output file. If omitted, print to <INPUT>.fsv_meta',
+ metavar='output',
+ default=None)
+ p.add_argument(
+ 'input',
+ help='input file to be signed')
+ p.add_argument(
+ '--key-format',
+ choices=['pem', 'der'],
+ default='der',
+ help='format of the input key. Default is der')
+ p.add_argument(
+ '--key',
+ help='PKCS#8 private key file')
+ p.add_argument(
+ '--cert',
+ help='x509 certificate file in PEM format')
+ p.add_argument(
+ '--hash-alg',
+ help='hash algorithm to use to build the merkle tree',
+ choices=['sha256', 'sha512'],
+ default='sha256')
+ p.add_argument(
+ '--signature',
+ help='format for signature',
+ choices=['none', 'raw', 'pkcs7'],
+ default='none')
+ p.add_argument(
+ '--fsverity-path',
+ help='path to the fsverity program',
+ required=True)
+ args = p.parse_args(sys.argv[1:])
+
+ generator = FSVerityMetadataGenerator(args.fsverity_path)
+ generator.set_signature(args.signature)
+ if args.signature == 'none':
+ if args.key or args.cert:
+ raise ValueError("When signature is none, key and cert can't be set")
+ else:
+ if not args.key or not args.cert:
+ raise ValueError("To generate signature, key and cert must be set")
+ generator.set_key(args.key)
+ generator.set_cert(args.cert)
+ generator.set_key_format(args.key_format)
+ generator.set_hash_alg(args.hash_alg)
+ generator.generate(args.input, args.output)
diff --git a/tools/releasetools/img_from_target_files.py b/tools/releasetools/img_from_target_files.py
index cbb51e1..0b2b187 100755
--- a/tools/releasetools/img_from_target_files.py
+++ b/tools/releasetools/img_from_target_files.py
@@ -124,7 +124,7 @@
for image_path in [name for name in namelist if name.startswith('IMAGES/')]:
image = os.path.basename(image_path)
- if OPTIONS.bootable_only and image not in('boot.img', 'recovery.img', 'bootloader'):
+ if OPTIONS.bootable_only and image not in('boot.img', 'recovery.img', 'bootloader', 'init_boot.img'):
continue
if not image.endswith('.img') and image != 'bootloader':
continue
diff --git a/tools/releasetools/merge_target_files.py b/tools/releasetools/merge_target_files.py
index 46ffdb7..da5e93f 100755
--- a/tools/releasetools/merge_target_files.py
+++ b/tools/releasetools/merge_target_files.py
@@ -255,6 +255,7 @@
'VENDOR/',
'VENDOR_DLKM/',
'ODM_DLKM/',
+ 'SYSTEM_DLKM/',
)
@@ -1618,8 +1619,9 @@
if not output_target_files:
return
- # Create the merged META/care_map.bp
- generate_care_map(partition_map.keys(), output_target_files_temp_dir)
+ # Create the merged META/care_map.pb if A/B update
+ if 'ab_update' in framework_misc_info_keys:
+ generate_care_map(partition_map.keys(), output_target_files_temp_dir)
output_zip = create_target_files_archive(output_target_files,
output_target_files_temp_dir,
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index ec703ab..88b9173 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -230,6 +230,13 @@
--compressor_types
A colon ':' separated list of compressors. Allowed values are bz2 and brotli.
+
+ --enable_zucchini
+ Whether to enable to zucchini feature. Will generate smaller OTA but uses more memory.
+
+ --enable_lz4diff
+ Whether to enable lz4diff feature. Will generate smaller OTA for EROFS but
+ uses more memory.
"""
from __future__ import print_function
@@ -299,6 +306,8 @@
OPTIONS.enable_vabc_xor = True
OPTIONS.force_minor_version = None
OPTIONS.compressor_types = None
+OPTIONS.enable_zucchini = True
+OPTIONS.enable_lz4diff = False
POSTINSTALL_CONFIG = 'META/postinstall_config.txt'
DYNAMIC_PARTITION_INFO = 'META/dynamic_partitions_info.txt'
@@ -307,15 +316,15 @@
# Files to be unzipped for target diffing purpose.
TARGET_DIFFING_UNZIP_PATTERN = ['BOOT', 'RECOVERY', 'SYSTEM/*', 'VENDOR/*',
'PRODUCT/*', 'SYSTEM_EXT/*', 'ODM/*',
- 'VENDOR_DLKM/*', 'ODM_DLKM/*']
+ 'VENDOR_DLKM/*', 'ODM_DLKM/*', 'SYSTEM_DLKM/*']
RETROFIT_DAP_UNZIP_PATTERN = ['OTA/super_*.img', AB_PARTITIONS]
# Images to be excluded from secondary payload. We essentially only keep
# 'system_other' and bootloader partitions.
SECONDARY_PAYLOAD_SKIPPED_IMAGES = [
'boot', 'dtbo', 'modem', 'odm', 'odm_dlkm', 'product', 'radio', 'recovery',
- 'system_ext', 'vbmeta', 'vbmeta_system', 'vbmeta_vendor', 'vendor',
- 'vendor_boot']
+ 'system_dlkm', 'system_ext', 'vbmeta', 'vbmeta_system', 'vbmeta_vendor',
+ 'vendor', 'vendor_boot']
class PayloadSigner(object):
@@ -1096,7 +1105,12 @@
if target_info.vendor_suppressed_vabc:
logger.info("Vendor suppressed VABC. Disabling")
OPTIONS.disable_vabc = True
- if not target_info.is_vabc_xor or OPTIONS.disable_vabc:
+
+ # Both source and target build need to support VABC XOR for us to use it.
+ # Source build's update_engine must be able to write XOR ops, and target
+ # build's snapuserd must be able to interpret XOR ops.
+ if not target_info.is_vabc_xor or OPTIONS.disable_vabc or \
+ (source_info is not None and not source_info.is_vabc_xor):
logger.info("VABC XOR Not supported, disabling")
OPTIONS.enable_vabc_xor = False
additional_args = []
@@ -1141,6 +1155,32 @@
partition_timestamps_flags = GeneratePartitionTimestampFlags(
metadata.postcondition.partition_state)
+ if not ota_utils.IsZucchiniCompatible(source_file, target_file):
+ OPTIONS.enable_zucchini = False
+
+ additional_args += ["--enable_zucchini",
+ str(OPTIONS.enable_zucchini).lower()]
+
+ if not ota_utils.IsLz4diffCompatible(source_file, target_file):
+ logger.warn(
+ "Source build doesn't support lz4diff, or source/target don't have compatible lz4diff versions. Disabling lz4diff.")
+ OPTIONS.enable_lz4diff = False
+
+ additional_args += ["--enable_lz4diff",
+ str(OPTIONS.enable_lz4diff).lower()]
+
+ if source_file and OPTIONS.enable_lz4diff:
+ input_tmp = common.UnzipTemp(source_file, ["META/liblz4.so"])
+ liblz4_path = os.path.join(input_tmp, "META", "liblz4.so")
+ assert os.path.exists(
+ liblz4_path), "liblz4.so not found in META/ dir of target file {}".format(liblz4_path)
+ logger.info("Enabling lz4diff %s", liblz4_path)
+ additional_args += ["--liblz4_path", liblz4_path]
+ erofs_compression_param = OPTIONS.target_info_dict.get(
+ "erofs_default_compressor")
+ assert erofs_compression_param is not None, "'erofs_default_compressor' not found in META/misc_info.txt of target build. This is required to enable lz4diff."
+ additional_args += ["--erofs_compression_param", erofs_compression_param]
+
if OPTIONS.disable_vabc:
additional_args += ["--disable_vabc", "true"]
if OPTIONS.enable_vabc_xor:
@@ -1321,59 +1361,68 @@
elif o == "--vabc_downgrade":
OPTIONS.vabc_downgrade = True
elif o == "--enable_vabc_xor":
+ assert a.lower() in ["true", "false"]
OPTIONS.enable_vabc_xor = a.lower() != "false"
elif o == "--force_minor_version":
OPTIONS.force_minor_version = a
elif o == "--compressor_types":
OPTIONS.compressor_types = a
+ elif o == "--enable_zucchini":
+ assert a.lower() in ["true", "false"]
+ OPTIONS.enable_zucchini = a.lower() != "false"
+ elif o == "--enable_lz4diff":
+ assert a.lower() in ["true", "false"]
+ OPTIONS.enable_lz4diff = a.lower() != "false"
else:
return False
return True
args = common.ParseOptions(argv, __doc__,
- extra_opts="b:k:i:d:e:t:2o:",
- extra_long_opts=[
- "package_key=",
- "incremental_from=",
- "full_radio",
- "full_bootloader",
- "wipe_user_data",
- "downgrade",
- "override_timestamp",
- "extra_script=",
- "worker_threads=",
- "two_step",
- "include_secondary",
- "no_signing",
- "block",
- "binary=",
- "oem_settings=",
- "oem_no_mount",
- "verify",
- "stash_threshold=",
- "log_diff=",
- "payload_signer=",
- "payload_signer_args=",
- "payload_signer_maximum_signature_size=",
- "payload_signer_key_size=",
- "extracted_input_target_files=",
- "skip_postinstall",
- "retrofit_dynamic_partitions",
- "skip_compatibility_check",
- "output_metadata_path=",
- "disable_fec_computation",
- "disable_verity_computation",
- "force_non_ab",
- "boot_variable_file=",
- "partial=",
- "custom_image=",
- "disable_vabc",
- "spl_downgrade",
- "vabc_downgrade",
- "enable_vabc_xor=",
- "force_minor_version=",
- "compressor_types=",
- ], extra_option_handler=option_handler)
+ extra_opts="b:k:i:d:e:t:2o:",
+ extra_long_opts=[
+ "package_key=",
+ "incremental_from=",
+ "full_radio",
+ "full_bootloader",
+ "wipe_user_data",
+ "downgrade",
+ "override_timestamp",
+ "extra_script=",
+ "worker_threads=",
+ "two_step",
+ "include_secondary",
+ "no_signing",
+ "block",
+ "binary=",
+ "oem_settings=",
+ "oem_no_mount",
+ "verify",
+ "stash_threshold=",
+ "log_diff=",
+ "payload_signer=",
+ "payload_signer_args=",
+ "payload_signer_maximum_signature_size=",
+ "payload_signer_key_size=",
+ "extracted_input_target_files=",
+ "skip_postinstall",
+ "retrofit_dynamic_partitions",
+ "skip_compatibility_check",
+ "output_metadata_path=",
+ "disable_fec_computation",
+ "disable_verity_computation",
+ "force_non_ab",
+ "boot_variable_file=",
+ "partial=",
+ "custom_image=",
+ "disable_vabc",
+ "spl_downgrade",
+ "vabc_downgrade",
+ "enable_vabc_xor=",
+ "force_minor_version=",
+ "compressor_types=",
+ "enable_zucchin=",
+ "enable_lz4diff=",
+ ], extra_option_handler=option_handler)
if len(args) != 2:
common.Usage(__doc__)
@@ -1473,13 +1522,23 @@
"build/make/target/product/security/testkey")
# Get signing keys
OPTIONS.key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
- private_key_path = OPTIONS.package_key + OPTIONS.private_key_suffix
- if not os.path.exists(private_key_path):
- raise common.ExternalError(
- "Private key {} doesn't exist. Make sure you passed the"
- " correct key path through -k option".format(
- private_key_path)
- )
+
+ # 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.payload_signer:
+ private_key_path = OPTIONS.package_key + OPTIONS.private_key_suffix
+ if not os.path.exists(private_key_path):
+ raise common.ExternalError(
+ "Private key {} doesn't exist. Make sure you passed the"
+ " 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"]
@@ -1531,8 +1590,5 @@
try:
common.CloseInheritedPipes()
main(sys.argv[1:])
- except common.ExternalError:
- logger.exception("\n ERROR:\n")
- sys.exit(1)
finally:
common.Cleanup()
diff --git a/tools/releasetools/ota_utils.py b/tools/releasetools/ota_utils.py
index 6c5fc05..6896f83 100644
--- a/tools/releasetools/ota_utils.py
+++ b/tools/releasetools/ota_utils.py
@@ -638,3 +638,60 @@
target_apex.source_version = source_apex_versions[name]
return target_apex_proto.SerializeToString()
+
+
+def IsLz4diffCompatible(source_file: str, target_file: str):
+ """Check whether lz4diff versions in two builds are compatible
+
+ Args:
+ source_file: Path to source build's target_file.zip
+ target_file: Path to target build's target_file.zip
+
+ Returns:
+ bool true if and only if lz4diff versions are compatible
+ """
+ if source_file is None or target_file is None:
+ return False
+ # Right now we enable lz4diff as long as source build has liblz4.so.
+ # In the future we might introduce version system to lz4diff as well.
+ if zipfile.is_zipfile(source_file):
+ with zipfile.ZipFile(source_file, "r") as zfp:
+ return "META/liblz4.so" in zfp.namelist()
+ else:
+ assert os.path.isdir(source_file)
+ return os.path.exists(os.path.join(source_file, "META", "liblz4.so"))
+
+
+def IsZucchiniCompatible(source_file: str, target_file: str):
+ """Check whether zucchini versions in two builds are compatible
+
+ Args:
+ source_file: Path to source build's target_file.zip
+ target_file: Path to target build's target_file.zip
+
+ Returns:
+ bool true if and only if zucchini versions are compatible
+ """
+ if source_file is None or target_file is None:
+ return False
+ assert os.path.exists(source_file)
+ assert os.path.exists(target_file)
+
+ assert zipfile.is_zipfile(source_file) or os.path.isdir(source_file)
+ assert zipfile.is_zipfile(target_file) or os.path.isdir(target_file)
+ _ZUCCHINI_CONFIG_ENTRY_NAME = "META/zucchini_config.txt"
+
+ def ReadEntry(path, entry):
+ # Read an entry inside a .zip file or extracted dir of .zip file
+ if zipfile.is_zipfile(path):
+ with zipfile.ZipFile(path, "r", allowZip64=True) as zfp:
+ if entry in zfp.namelist():
+ return zfp.read(entry).decode()
+ else:
+ entry_path = os.path.join(entry, path)
+ if os.path.exists(entry_path):
+ with open(entry_path, "r") as fp:
+ return fp.read()
+ else:
+ return ""
+ return ReadEntry(source_file, _ZUCCHINI_CONFIG_ENTRY_NAME) == ReadEntry(target_file, _ZUCCHINI_CONFIG_ENTRY_NAME)
diff --git a/tools/releasetools/sign_apex.py b/tools/releasetools/sign_apex.py
index 679f57a..66f5e05 100755
--- a/tools/releasetools/sign_apex.py
+++ b/tools/releasetools/sign_apex.py
@@ -141,7 +141,7 @@
signing_args=options.get('payload_extra_args'),
codename_to_api_level_map=options.get(
'codename_to_api_level_map', {}),
- sign_tool=options['sign_tool'])
+ sign_tool=options.get('sign_tool', None))
shutil.copyfile(signed_apex, args[1])
logger.info("done.")
diff --git a/tools/releasetools/sign_target_files_apks.py b/tools/releasetools/sign_target_files_apks.py
index 5626980..5b16d80 100755
--- a/tools/releasetools/sign_target_files_apks.py
+++ b/tools/releasetools/sign_target_files_apks.py
@@ -201,10 +201,12 @@
AVB_FOOTER_ARGS_BY_PARTITION = {
'boot': 'avb_boot_add_hash_footer_args',
+ 'init_boot': 'avb_init_boot_add_hash_footer_args',
'dtbo': 'avb_dtbo_add_hash_footer_args',
'product': 'avb_product_add_hashtree_footer_args',
'recovery': 'avb_recovery_add_hash_footer_args',
'system': 'avb_system_add_hashtree_footer_args',
+ 'system_dlkm': "avb_system_dlkm_add_hashtree_footer_args",
'system_ext': 'avb_system_ext_add_hashtree_footer_args',
'system_other': 'avb_system_other_add_hashtree_footer_args',
'odm': 'avb_odm_add_hashtree_footer_args',
@@ -519,9 +521,14 @@
compressed_extension):
# maxsize measures the maximum filename length, including the ones to be
# skipped.
- maxsize = max(
- [len(os.path.basename(i.filename)) for i in input_tf_zip.infolist()
- if GetApkFileInfo(i.filename, compressed_extension, [])[0]])
+ try:
+ maxsize = max(
+ [len(os.path.basename(i.filename)) for i in input_tf_zip.infolist()
+ if GetApkFileInfo(i.filename, compressed_extension, [])[0]])
+ except ValueError:
+ # Sets this to zero for targets without APK files, e.g., gki_arm64.
+ maxsize = 0
+
system_root_image = misc_info.get("system_root_image") == "true"
for info in input_tf_zip.infolist():
@@ -608,7 +615,7 @@
common.ZipWriteStr(output_tf_zip, out_info, new_data)
# Replace the certs in *mac_permissions.xml (there could be multiple, such
- # as {system,vendor}/etc/selinux/{plat,nonplat}_mac_permissions.xml).
+ # as {system,vendor}/etc/selinux/{plat,vendor}_mac_permissions.xml).
elif filename.endswith("mac_permissions.xml"):
print("Rewriting %s with new keys." % (filename,))
new_data = ReplaceCerts(data.decode())
@@ -882,14 +889,27 @@
except KeyError:
raise common.ExternalError("can't read META/otakeys.txt from input")
- extra_recovery_keys = misc_info.get("extra_recovery_keys")
- if extra_recovery_keys:
+ extra_ota_keys_info = misc_info.get("extra_ota_keys")
+ if extra_ota_keys_info:
+ extra_ota_keys = [OPTIONS.key_map.get(k, k) + ".x509.pem"
+ for k in extra_ota_keys_info.split()]
+ print("extra ota key(s): " + ", ".join(extra_ota_keys))
+ else:
+ extra_ota_keys = []
+ for k in extra_ota_keys:
+ if not os.path.isfile(k):
+ raise common.ExternalError(k + " does not exist or is not a file")
+
+ extra_recovery_keys_info = misc_info.get("extra_recovery_keys")
+ if extra_recovery_keys_info:
extra_recovery_keys = [OPTIONS.key_map.get(k, k) + ".x509.pem"
- for k in extra_recovery_keys.split()]
- if extra_recovery_keys:
- print("extra recovery-only key(s): " + ", ".join(extra_recovery_keys))
+ for k in extra_recovery_keys_info.split()]
+ print("extra recovery-only key(s): " + ", ".join(extra_recovery_keys))
else:
extra_recovery_keys = []
+ for k in extra_recovery_keys:
+ if not os.path.isfile(k):
+ raise common.ExternalError(k + " does not exist or is not a file")
mapped_keys = []
for k in keylist:
@@ -912,13 +932,20 @@
mapped_keys.append(mapped_devkey + ".x509.pem")
print("META/otakeys.txt has no keys; using %s for OTA package"
" verification." % (mapped_keys[0],))
+ for k in mapped_keys:
+ if not os.path.isfile(k):
+ raise common.ExternalError(k + " does not exist or is not a file")
otacerts = [info
for info in input_tf_zip.infolist()
if info.filename.endswith("/otacerts.zip")]
for info in otacerts:
- print("Rewriting OTA key:", info.filename, mapped_keys)
- WriteOtacerts(output_tf_zip, info.filename, mapped_keys)
+ if info.filename.startswith(("BOOT/", "RECOVERY/", "VENDOR_BOOT/")):
+ extra_keys = extra_recovery_keys
+ else:
+ extra_keys = extra_ota_keys
+ print("Rewriting OTA key:", info.filename, mapped_keys + extra_keys)
+ WriteOtacerts(output_tf_zip, info.filename, mapped_keys + extra_keys)
def ReplaceVerityPublicKey(output_zip, filename, key_path):
diff --git a/tools/releasetools/target_files_diff.py b/tools/releasetools/target_files_diff.py
index 4402c8d..fa94c5b 100755
--- a/tools/releasetools/target_files_diff.py
+++ b/tools/releasetools/target_files_diff.py
@@ -82,7 +82,7 @@
skip = True
break
if not skip:
- new.write(line)
+ new.write(line.encode())
def trim_install_recovery(original, new):
@@ -91,7 +91,7 @@
partition.
"""
for line in original:
- new.write(re.sub(r'[0-9a-f]{40}', '0'*40, line))
+ new.write(re.sub(r'[0-9a-f]{40}', '0'*40, line).encode())
def sort_file(original, new):
"""
@@ -101,7 +101,7 @@
lines = original.readlines()
lines.sort()
for line in lines:
- new.write(line)
+ new.write(line.encode())
# Map files to the functions that will modify them for diffing
REWRITE_RULES = {
@@ -148,7 +148,7 @@
if stdout == 'Binary files %s and %s differ' % (f1, f2):
print("%s: Binary files differ" % name, file=out_file)
else:
- for line in stdout.strip().split('\n'):
+ for line in stdout.strip().split(b'\n'):
print("%s: %s" % (name, line), file=out_file)
def recursiveDiff(prefix, dir1, dir2, out_file):
diff --git a/tools/releasetools/test_apex_utils.py b/tools/releasetools/test_apex_utils.py
index ed920f2..2aa6f6c 100644
--- a/tools/releasetools/test_apex_utils.py
+++ b/tools/releasetools/test_apex_utils.py
@@ -198,8 +198,9 @@
# pass `false` as a sign_tool to see the invocation error
with self.assertRaises(common.ExternalError) as cm:
- signer = apex_utils.ApexApkSigner(apex_path, None, None, sign_tool='false')
- signer.ProcessApexFile(apk_keys, self.payload_key)
+ signer = apex_utils.ApexApkSigner(
+ apex_path, None, None, sign_tool='false')
+ signer.ProcessApexFile(apk_keys, self.payload_key)
the_exception = cm.exception
- self.assertIn('Failed to run command \'[\'false\'', the_exception.message)
+ self.assertIn('Failed to run command \'[\'false\'', str(the_exception))
diff --git a/tools/releasetools/test_common.py b/tools/releasetools/test_common.py
index e42d417..7dd365f 100644
--- a/tools/releasetools/test_common.py
+++ b/tools/releasetools/test_common.py
@@ -1631,66 +1631,7 @@
self.assertEqual('3', chained_partition_args[1])
self.assertTrue(os.path.exists(chained_partition_args[2]))
- @test_utils.SkipIfExternalToolsUnavailable()
- def test_AppendGkiSigningArgs_NoSigningKeyPath(self):
- # A non-GKI boot.img has no gki_signing_key_path.
- common.OPTIONS.info_dict = {
- # 'gki_signing_key_path': pubkey,
- 'gki_signing_algorithm': 'SHA256_RSA4096',
- 'gki_signing_signature_args': '--prop foo:bar',
- }
-
- # Tests no --gki_signing_* args are appended if there is no
- # gki_signing_key_path.
- cmd = ['mkbootimg', '--header_version', '4']
- expected_cmd = ['mkbootimg', '--header_version', '4']
- common.AppendGkiSigningArgs(cmd)
- self.assertEqual(cmd, expected_cmd)
-
- def test_AppendGkiSigningArgs_NoSigningAlgorithm(self):
- pubkey = os.path.join(self.testdata_dir, 'testkey_gki.pem')
- with open(pubkey, 'wb') as f:
- f.write(b'\x00' * 100)
- self.assertTrue(os.path.exists(pubkey))
-
- # Tests no --gki_signing_* args are appended if there is no
- # gki_signing_algorithm.
- common.OPTIONS.info_dict = {
- 'gki_signing_key_path': pubkey,
- # 'gki_signing_algorithm': 'SHA256_RSA4096',
- 'gki_signing_signature_args': '--prop foo:bar',
- }
-
- cmd = ['mkbootimg', '--header_version', '4']
- expected_cmd = ['mkbootimg', '--header_version', '4']
- common.AppendGkiSigningArgs(cmd)
- self.assertEqual(cmd, expected_cmd)
-
- @test_utils.SkipIfExternalToolsUnavailable()
- def test_AppendGkiSigningArgs(self):
- pubkey = os.path.join(self.testdata_dir, 'testkey_gki.pem')
- with open(pubkey, 'wb') as f:
- f.write(b'\x00' * 100)
- self.assertTrue(os.path.exists(pubkey))
-
- common.OPTIONS.info_dict = {
- 'gki_signing_key_path': pubkey,
- 'gki_signing_algorithm': 'SHA256_RSA4096',
- 'gki_signing_signature_args': '--prop foo:bar',
- }
- cmd = ['mkbootimg', '--header_version', '4']
- common.AppendGkiSigningArgs(cmd)
-
- expected_cmd = [
- 'mkbootimg', '--header_version', '4',
- '--gki_signing_key', pubkey,
- '--gki_signing_algorithm', 'SHA256_RSA4096',
- '--gki_signing_signature_args', '--prop foo:bar'
- ]
- self.assertEqual(cmd, expected_cmd)
-
- @test_utils.SkipIfExternalToolsUnavailable()
- def test_AppendGkiSigningArgs_KeyPathNotFound(self):
+ def test_GenerateGkiCertificate_KeyPathNotFound(self):
pubkey = os.path.join(self.testdata_dir, 'no_testkey_gki.pem')
self.assertFalse(os.path.exists(pubkey))
@@ -1699,41 +1640,11 @@
'gki_signing_algorithm': 'SHA256_RSA4096',
'gki_signing_signature_args': '--prop foo:bar',
}
- cmd = ['mkbootimg', '--header_version', '4']
- self.assertRaises(common.ExternalError, common.AppendGkiSigningArgs, cmd)
+ test_file = tempfile.NamedTemporaryFile()
+ self.assertRaises(common.ExternalError, common._GenerateGkiCertificate,
+ test_file.name, 'generic_kernel', 'boot')
- @test_utils.SkipIfExternalToolsUnavailable()
- def test_AppendGkiSigningArgs_SearchKeyPath(self):
- pubkey = 'testkey_gki.pem'
- self.assertFalse(os.path.exists(pubkey))
-
- # Tests it should replace the pubkey with an existed key under
- # OPTIONS.search_path, i.e., os.path.join(OPTIONS.search_path, pubkey).
- search_path_dir = common.MakeTempDir()
- search_pubkey = os.path.join(search_path_dir, pubkey)
- with open(search_pubkey, 'wb') as f:
- f.write(b'\x00' * 100)
- self.assertTrue(os.path.exists(search_pubkey))
-
- common.OPTIONS.search_path = search_path_dir
- common.OPTIONS.info_dict = {
- 'gki_signing_key_path': pubkey,
- 'gki_signing_algorithm': 'SHA256_RSA4096',
- 'gki_signing_signature_args': '--prop foo:bar',
- }
- cmd = ['mkbootimg', '--header_version', '4']
- common.AppendGkiSigningArgs(cmd)
-
- expected_cmd = [
- 'mkbootimg', '--header_version', '4',
- '--gki_signing_key', search_pubkey,
- '--gki_signing_algorithm', 'SHA256_RSA4096',
- '--gki_signing_signature_args', '--prop foo:bar'
- ]
- self.assertEqual(cmd, expected_cmd)
-
- @test_utils.SkipIfExternalToolsUnavailable()
- def test_AppendGkiSigningArgs_SearchKeyPathNotFound(self):
+ def test_GenerateGkiCertificate_SearchKeyPathNotFound(self):
pubkey = 'no_testkey_gki.pem'
self.assertFalse(os.path.exists(pubkey))
@@ -1749,9 +1660,9 @@
'gki_signing_algorithm': 'SHA256_RSA4096',
'gki_signing_signature_args': '--prop foo:bar',
}
- cmd = ['mkbootimg', '--header_version', '4']
- self.assertRaises(common.ExternalError, common.AppendGkiSigningArgs, cmd)
-
+ test_file = tempfile.NamedTemporaryFile()
+ self.assertRaises(common.ExternalError, common._GenerateGkiCertificate,
+ test_file.name, 'generic_kernel', 'boot')
class InstallRecoveryScriptFormatTest(test_utils.ReleaseToolsTestCase):
"""Checks the format of install-recovery.sh.
diff --git a/tools/releasetools/test_sign_target_files_apks.py b/tools/releasetools/test_sign_target_files_apks.py
index 92dca9a..0f13add 100644
--- a/tools/releasetools/test_sign_target_files_apks.py
+++ b/tools/releasetools/test_sign_target_files_apks.py
@@ -62,6 +62,9 @@
'avb_boot_add_hash_footer_args':
('--prop com.android.build.boot.os_version:R '
'--prop com.android.build.boot.security_patch:2019-09-05'),
+ 'avb_init_boot_add_hash_footer_args':
+ ('--prop com.android.build.boot.os_version:R '
+ '--prop com.android.build.boot.security_patch:2019-09-05'),
'avb_system_add_hashtree_footer_args':
('--prop com.android.build.system.os_version:R '
'--prop com.android.build.system.security_patch:2019-09-05 '
@@ -77,6 +80,9 @@
'avb_boot_add_hash_footer_args':
('--prop com.android.build.boot.os_version:R '
'--prop com.android.build.boot.security_patch:2019-09-05'),
+ 'avb_init_boot_add_hash_footer_args':
+ ('--prop com.android.build.boot.os_version:R '
+ '--prop com.android.build.boot.security_patch:2019-09-05'),
'avb_system_add_hashtree_footer_args':
('--prop com.android.build.system.os_version:R '
'--prop com.android.build.system.security_patch:2019-09-05 '
diff --git a/tools/signapk/src/com/android/signapk/SignApk.java b/tools/signapk/src/com/android/signapk/SignApk.java
index 8bf1005..c127dbe 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;
@@ -197,26 +204,23 @@
* If a console doesn't exist, reads the password from stdin
* If a console exists, reads the password from console and returns it as a string.
*
- * @param keyFile The file containing the private key. Used to prompt the user.
+ * @param keyFileName Name of the file containing the private key. Used to prompt the user.
*/
- private static String readPassword(File keyFile) {
+ private static char[] readPassword(String keyFileName) {
Console console;
- char[] pwd;
if ((console = System.console()) == null) {
- System.out.print("Enter password for " + keyFile + " (password will not be hidden): ");
+ System.out.print(
+ "Enter password for " + keyFileName + " (password will not be hidden): ");
System.out.flush();
BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
try {
- return stdin.readLine();
+ String result = stdin.readLine();
+ return result == null ? null : result.toCharArray();
} catch (IOException ex) {
return null;
}
} else {
- if ((pwd = console.readPassword("[%s]", "Enter password for " + keyFile)) != null) {
- return String.valueOf(pwd);
- } else {
- return null;
- }
+ return console.readPassword("[%s]", "Enter password for " + keyFileName);
}
}
@@ -239,11 +243,8 @@
return null;
}
- char[] password = readPassword(keyFile).toCharArray();
-
SecretKeyFactory skFactory = SecretKeyFactory.getInstance(epkInfo.getAlgName());
- Key key = skFactory.generateSecret(new PBEKeySpec(password));
-
+ Key key = skFactory.generateSecret(new PBEKeySpec(readPassword(keyFile.getPath())));
Cipher cipher = Cipher.getInstance(epkInfo.getAlgName());
cipher.init(Cipher.DECRYPT_MODE, key, epkInfo.getAlgParameters());
@@ -286,6 +287,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)
+ throws CertificateException, KeyStoreException, NoSuchAlgorithmException,
+ UnrecoverableKeyException, UnrecoverableEntryException {
+ final Key key = keyStore.getKey(keyName, readPassword(keyName));
+ 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 +1049,8 @@
"[-a <alignment>] " +
"[--align-file-size] " +
"[-providerClass <className>] " +
+ "[-loadPrivateKeysFromKeyStore <keyStoreName>]" +
+ "[-keyStorePin <pin>]" +
"[--min-sdk-version <n>] " +
"[--disable-v2] " +
"[--enable-v4] " +
@@ -1044,6 +1073,8 @@
boolean signWholeFile = false;
String providerClass = null;
+ String keyStoreName = null;
+ String keyStorePin = null;
int alignment = 4;
boolean alignFileSize = false;
Integer minSdkVersionOverride = null;
@@ -1062,6 +1093,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 +1185,19 @@
// 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 {
+ final String keyAlias = args[argNum];
+ privateKey[i] = loadPrivateKeyFromKeyStore(keyStore, keyAlias);
+ }
}
inputJar = new JarFile(new File(inputFilename), false); // Don't verify.
diff --git a/tools/warn/html_writer.py b/tools/warn/html_writer.py
index ef173bc..3fa822a 100644
--- a/tools/warn/html_writer.py
+++ b/tools/warn/html_writer.py
@@ -63,6 +63,11 @@
from .severity import Severity
+# Report files with this number of warnings or more.
+LIMIT_WARNINGS_PER_FILE = 100
+# Report files/directories with this percentage of total warnings or more.
+LIMIT_PERCENT_WARNINGS = 1
+
HTML_HEAD_SCRIPTS = """\
<script type="text/javascript">
function expand(id) {
@@ -89,12 +94,13 @@
</script>
<style type="text/css">
th,td{border-collapse:collapse; border:1px solid black;}
- .button{color:blue;font-size:110%;font-weight:bolder;}
+ .button{color:blue;font-size:100%;font-weight:bolder;}
.bt{color:black;background-color:transparent;border:none;outline:none;
font-size:140%;font-weight:bolder;}
.c0{background-color:#e0e0e0;}
.c1{background-color:#d0d0d0;}
.t1{border-collapse:collapse; width:100%; border:1px solid black;}
+ .box{margin:5pt; padding:5pt; border:1px solid;}
</style>
<script src="https://www.gstatic.com/charts/loader.js"></script>
"""
@@ -287,14 +293,14 @@
# sort by project, severity, warn_id, warning_message
def emit_buttons(writer):
"""Write the button elements in HTML."""
- writer('<button class="button" onclick="expandCollapse(1);">'
+ writer('<p><button class="button" onclick="expandCollapse(1);">'
'Expand all warnings</button>\n'
'<button class="button" onclick="expandCollapse(0);">'
'Collapse all warnings</button>\n'
- '<button class="button" onclick="groupBySeverity();">'
+ '<p><button class="button" onclick="groupBySeverity();">'
'Group warnings by severity</button>\n'
'<button class="button" onclick="groupByProject();">'
- 'Group warnings by project</button><br>')
+ 'Group warnings by project</button>')
def all_patterns(category):
@@ -559,6 +565,11 @@
"""
+# Emit a JavaScript const number
+def emit_const_number(name, value, writer):
+ writer('const ' + name + ' = ' + str(value) + ';')
+
+
# Emit a JavaScript const string
def emit_const_string(name, value, writer):
writer('const ' + name + ' = "' + escape_string(value) + '";')
@@ -602,6 +613,8 @@
emit_const_string('FlagPlatform', flags.platform, writer)
emit_const_string('FlagURL', flags.url, writer)
emit_const_string('FlagSeparator', flags.separator, writer)
+ emit_const_number('LimitWarningsPerFile', LIMIT_WARNINGS_PER_FILE, writer)
+ emit_const_number('LimitPercentWarnings', LIMIT_PERCENT_WARNINGS, writer)
emit_const_string_array('SeverityColors', [s.color for s in Severity.levels],
writer)
emit_const_string_array('SeverityHeaders',
@@ -624,8 +637,8 @@
DRAW_TABLE_JAVASCRIPT = """
google.charts.load('current', {'packages':['table']});
-google.charts.setOnLoadCallback(drawTable);
-function drawTable() {
+google.charts.setOnLoadCallback(genTables);
+function genSelectedProjectsTable() {
var data = new google.visualization.DataTable();
data.addColumn('string', StatsHeader[0]);
for (var i=1; i<StatsHeader.length; i++) {
@@ -638,12 +651,167 @@
}
}
var table = new google.visualization.Table(
- document.getElementById('stats_table'));
+ document.getElementById('selected_projects_section'));
table.draw(data, {allowHtml: true, alternatingRowStyle: true});
}
+// Global TopDirs and TopFiles are computed later by genTables.
+window.TopDirs = [];
+window.TopFiles = [];
+function computeTopDirsFiles() {
+ var numWarnings = WarningMessages.length;
+ var warningsOfFiles = {};
+ var warningsOfDirs = {};
+ var subDirs = {};
+ function addOneWarning(map, key) {
+ map[key] = 1 + ((key in map) ? map[key] : 0);
+ }
+ for (var i = 0; i < numWarnings; i++) {
+ var file = WarningMessages[i].replace(/:.*/, "");
+ addOneWarning(warningsOfFiles, file);
+ var dirs = file.split("/");
+ var dir = dirs[0];
+ addOneWarning(warningsOfDirs, dir);
+ for (var d = 1; d < dirs.length - 1; d++) {
+ var subDir = dir + "/" + dirs[d];
+ if (!(dir in subDirs)) {
+ subDirs[dir] = {};
+ }
+ subDirs[dir][subDir] = 1;
+ dir = subDir;
+ addOneWarning(warningsOfDirs, dir);
+ }
+ }
+ var minDirWarnings = numWarnings*(LimitPercentWarnings/100);
+ var minFileWarnings = Math.min(LimitWarningsPerFile, minDirWarnings);
+ // Each row in TopDirs and TopFiles has
+ // [index, {v:<num_of_warnings>, f:<percent>}, file_or_dir_name]
+ function countWarnings(minWarnings, warningsOf, isDir) {
+ var rows = [];
+ for (var name in warningsOf) {
+ if (isDir && name in subDirs && Object.keys(subDirs[name]).length < 2) {
+ continue; // skip a directory if it has only one subdir
+ }
+ var count = warningsOf[name];
+ if (count >= minWarnings) {
+ name = isDir ? (name + "/...") : name;
+ var percent = (100*count/numWarnings).toFixed(1);
+ var countFormat = count + ' (' + percent + '%)';
+ rows.push([0, {v:count, f:countFormat}, name]);
+ }
+ }
+ rows.sort((a,b) => b[1].v - a[1].v);
+ for (var i=0; i<rows.length; i++) {
+ rows[i][0] = i;
+ }
+ return rows;
+ }
+ TopDirs = countWarnings(minDirWarnings, warningsOfDirs, true);
+ TopFiles = countWarnings(minFileWarnings, warningsOfFiles, false);
+}
+function genTopDirsFilesTables() {
+ computeTopDirsFiles();
+ function addTable(name, divName, rows, clickFunction) {
+ var data = new google.visualization.DataTable();
+ data.addColumn("number", "index"); // not shown in view
+ data.addColumn("number", "# of warnings");
+ data.addColumn("string", name);
+ data.addRows(rows);
+ var formatter = new google.visualization.PatternFormat(
+ '<p onclick="' + clickFunction + '({0})">{2}</p>');
+ formatter.format(data, [0, 1, 2], 2);
+ var view = new google.visualization.DataView(data);
+ view.setColumns([1,2]); // hide the index column
+ var table = new google.visualization.Table(
+ document.getElementById(divName));
+ table.draw(view, {allowHtml: true, alternatingRowStyle: true});
+ }
+ addTable("Directory", "top_dirs_table", TopDirs, "selectDir");
+ addTable("File", "top_files_table", TopFiles, "selectFile");
+}
+function selectDirFile(idx, rows, dirFile) {
+ if (rows.length <= idx) {
+ return;
+ }
+ var name = rows[idx][2];
+ var spanName = "selected_" + dirFile + "_name";
+ document.getElementById(spanName).innerHTML = name;
+ var divName = "selected_" + dirFile + "_warnings";
+ var numWarnings = rows[idx][1].v;
+ var prefix = name.replace(/\\.\\.\\.$/, "");
+ var data = new google.visualization.DataTable();
+ data.addColumn('string', numWarnings + ' warnings in ' + name);
+ var getWarningMessage = (FlagPlatform == "chrome")
+ ? ((x) => addURLToLine(WarningMessages[Warnings[x][2]],
+ WarningLinks[Warnings[x][3]]))
+ : ((x) => addURL(WarningMessages[Warnings[x][2]]));
+ for (var i = 0; i < Warnings.length; i++) {
+ if (WarningMessages[Warnings[i][2]].startsWith(prefix)) {
+ data.addRow([getWarningMessage(i)]);
+ }
+ }
+ var table = new google.visualization.Table(
+ document.getElementById(divName));
+ table.draw(data, {allowHtml: true, alternatingRowStyle: true});
+}
+function selectDir(idx) {
+ selectDirFile(idx, TopDirs, "directory")
+}
+function selectFile(idx) {
+ selectDirFile(idx, TopFiles, "file");
+}
+function genTables() {
+ genSelectedProjectsTable();
+ if (WarningMessages.length > 1) {
+ genTopDirsFilesTables();
+ }
+}
"""
+def dump_boxed_section(writer, func):
+ writer('<div class="box">')
+ func()
+ writer('</div>')
+
+
+def dump_section_header(writer, table_name, section_title):
+ writer('<h3><b><button id="' + table_name + '_mark" class="bt"\n' +
+ ' onclick="expand(\'' + table_name + '\');">⊕</button></b>\n' +
+ section_title + '</h3>')
+
+
+def dump_table_section(writer, table_name, section_title):
+ dump_section_header(writer, table_name, section_title)
+ writer('<div id="' + table_name + '" style="display:none;"></div>')
+
+
+def dump_dir_file_section(writer, dir_file, table_name, section_title):
+ section_name = 'top_' + dir_file + '_section'
+ dump_section_header(writer, section_name, section_title)
+ writer('<div id="' + section_name + '" style="display:none;">')
+ writer('<div id="' + table_name + '"></div>')
+ def subsection():
+ subsection_name = 'selected_' + dir_file + '_warnings'
+ subsection_title = ('Warnings in <span id="selected_' + dir_file +
+ '_name">(click a ' + dir_file +
+ ' in the above table)</span>')
+ dump_section_header(writer, subsection_name, subsection_title)
+ writer('<div id="' + subsection_name + '" style="display:none;"></div>')
+ dump_boxed_section(writer, subsection)
+ writer('</div>')
+
+
+# HTML output has the following major div elements:
+# selected_projects_section
+# top_directory_section
+# top_dirs_table
+# selected_directory_warnings
+# top_file_section
+# top_files_table
+# selected_file_warnings
+# all_warnings_section
+# warning_groups
+# fixed_warnings
def dump_html(flags, output_stream, warning_messages, warning_links,
warning_records, header_str, warn_patterns, project_names):
"""Dump the flags output to output_stream."""
@@ -651,20 +819,44 @@
dump_html_prologue('Warnings for ' + header_str, writer, warn_patterns,
project_names)
dump_stats(writer, warn_patterns)
- writer('<br><div id="stats_table"></div><br>')
- writer('\n<script>')
- emit_js_data(writer, flags, warning_messages, warning_links, warning_records,
- warn_patterns, project_names)
- writer(SCRIPTS_FOR_WARNING_GROUPS)
- writer('</script>')
- emit_buttons(writer)
- # Warning messages are grouped by severities or project names.
- writer('<br><div id="warning_groups"></div>')
- if flags.byproject:
- writer('<script>groupByProject();</script>')
- else:
- writer('<script>groupBySeverity();</script>')
- dump_fixed(writer, warn_patterns)
+ writer('<br><br>Press ⊕ to show section content,'
+ ' and ⊖ to hide the content.')
+ def section1():
+ dump_table_section(writer, 'selected_projects_section',
+ 'Number of warnings in preselected project directories')
+ def section2():
+ dump_dir_file_section(
+ writer, 'directory', 'top_dirs_table',
+ 'Directories with at least ' +
+ str(LIMIT_PERCENT_WARNINGS) + '% warnings')
+ def section3():
+ dump_dir_file_section(
+ writer, 'file', 'top_files_table',
+ 'Files with at least ' +
+ str(LIMIT_PERCENT_WARNINGS) + '% or ' +
+ str(LIMIT_WARNINGS_PER_FILE) + ' warnings')
+ def section4():
+ writer('<script>')
+ emit_js_data(writer, flags, warning_messages, warning_links,
+ warning_records, warn_patterns, project_names)
+ writer(SCRIPTS_FOR_WARNING_GROUPS)
+ writer('</script>')
+ dump_section_header(writer, 'all_warnings_section',
+ 'All warnings grouped by severities or projects')
+ writer('<div id="all_warnings_section" style="display:none;">')
+ emit_buttons(writer)
+ # Warning messages are grouped by severities or project names.
+ writer('<br><div id="warning_groups"></div>')
+ if flags.byproject:
+ writer('<script>groupByProject();</script>')
+ else:
+ writer('<script>groupBySeverity();</script>')
+ dump_fixed(writer, warn_patterns)
+ writer('</div>')
+ dump_boxed_section(writer, section1)
+ dump_boxed_section(writer, section2)
+ dump_boxed_section(writer, section3)
+ dump_boxed_section(writer, section4)
dump_html_epilogue(writer)
diff --git a/tools/warn/tidy_warn_patterns.py b/tools/warn/tidy_warn_patterns.py
index a5842ea..c138f1c 100644
--- a/tools/warn/tidy_warn_patterns.py
+++ b/tools/warn/tidy_warn_patterns.py
@@ -224,6 +224,9 @@
analyzer_warn_check('clang-analyzer-valist.Unterminated'),
analyzer_group_check('clang-analyzer-core.uninitialized'),
analyzer_group_check('clang-analyzer-deadcode'),
+ analyzer_warn_check('clang-analyzer-security.insecureAPI.bcmp'),
+ analyzer_warn_check('clang-analyzer-security.insecureAPI.bcopy'),
+ analyzer_warn_check('clang-analyzer-security.insecureAPI.bzero'),
analyzer_warn_check('clang-analyzer-security.insecureAPI.strcpy'),
analyzer_group_high('clang-analyzer-security.insecureAPI'),
analyzer_group_high('clang-analyzer-security'),
diff --git a/tools/zipalign/tests/src/align_test.cpp b/tools/zipalign/tests/src/align_test.cpp
index 96d4f73..ff45187 100644
--- a/tools/zipalign/tests/src/align_test.cpp
+++ b/tools/zipalign/tests/src/align_test.cpp
@@ -3,6 +3,7 @@
#include "ZipAlign.h"
+#include <filesystem>
#include <stdio.h>
#include <string>
@@ -16,9 +17,15 @@
return test_data_dir + filename;
}
+static std::string GetTempPath(const std::string& filename) {
+ std::filesystem::path temp_path = std::filesystem::path(testing::TempDir());
+ temp_path += filename;
+ return temp_path.string();
+}
+
TEST(Align, Unaligned) {
const std::string src = GetTestPath("unaligned.zip");
- const std::string dst = GetTestPath("unaligned_out.zip");
+ const std::string dst = GetTempPath("unaligned_out.zip");
int processed = process(src.c_str(), dst.c_str(), 4, true, false, 4096);
ASSERT_EQ(0, processed);
@@ -29,8 +36,8 @@
TEST(Align, DoubleAligment) {
const std::string src = GetTestPath("unaligned.zip");
- const std::string tmp = GetTestPath("da_aligned.zip");
- const std::string dst = GetTestPath("da_d_aligner.zip");
+ const std::string tmp = GetTempPath("da_aligned.zip");
+ const std::string dst = GetTempPath("da_d_aligner.zip");
int processed = process(src.c_str(), tmp.c_str(), 4, true, false, 4096);
ASSERT_EQ(0, processed);
@@ -60,7 +67,7 @@
// Directory.
TEST(Align, Holes) {
const std::string src = GetTestPath("holes.zip");
- const std::string dst = GetTestPath("holes_out.zip");
+ const std::string dst = GetTempPath("holes_out.zip");
int processed = process(src.c_str(), dst.c_str(), 4, true, false, 4096);
ASSERT_EQ(0, processed);
@@ -72,7 +79,7 @@
// Align a zip where LFH order and CD entries differ.
TEST(Align, DifferenteOrders) {
const std::string src = GetTestPath("diffOrders.zip");
- const std::string dst = GetTestPath("diffOrders_out.zip");
+ const std::string dst = GetTempPath("diffOrders_out.zip");
int processed = process(src.c_str(), dst.c_str(), 4, true, false, 4096);
ASSERT_EQ(0, processed);