diff --git a/core/clear_vars.mk b/core/clear_vars.mk index 6909275faa..5f16363614 100644 --- a/core/clear_vars.mk +++ b/core/clear_vars.mk @@ -159,6 +159,10 @@ LOCAL_JNI_SHARED_LIBRARIES_ABI:= LOCAL_CERTIFICATE_LINEAGE:= LOCAL_LDFLAGS:= LOCAL_LDLIBS:= +LOCAL_LICENSE_CONDITIONS:= +LOCAL_LICENSE_KINDS:= +LOCAL_LICENSE_INSTALL_MAP:= +LOCAL_LICENSE_PACKAGE_NAME:= LOCAL_LOGTAGS_FILES:= LOCAL_MANIFEST_FILE:= LOCAL_MANIFEST_INSTRUMENTATION_FOR:= @@ -170,6 +174,7 @@ LOCAL_MODULE_HOST_ARCH:= LOCAL_MODULE_HOST_ARCH_WARN:= LOCAL_MODULE_HOST_CROSS_ARCH:= LOCAL_MODULE_HOST_OS:= +LOCAL_MODULE_IS_CONTAINER:= LOCAL_MODULE_OWNER:= LOCAL_MODULE_PATH:= LOCAL_MODULE_RELATIVE_PATH := diff --git a/core/definitions.mk b/core/definitions.mk index 02303400d8..4300efe175 100644 --- a/core/definitions.mk +++ b/core/definitions.mk @@ -524,6 +524,96 @@ define reverse-list $(if $(1),$(call reverse-list,$(wordlist 2,$(words $(1)),$(1)))) $(firstword $(1)) endef +########################################################### +## Sometimes a notice dependency will reference an unadorned +## module name that only appears in ALL_MODULES adorned with +## an ARCH suffix or a `host_cross_` prefix. +## +## After all of the modules are processed in base_rules.mk, +## replace all such dependencies with every matching adorned +## module name. +########################################################### + +define fix-notice-deps +$(strip \ + $(eval _all_module_refs := \ + $(sort \ + $(foreach m,$(sort $(ALL_MODULES)), \ + $(ALL_MODULES.$(m).NOTICE_DEPS) \ + ) \ + ) \ + ) \ + $(foreach m, $(_all_module_refs), \ + $(eval _lookup.$(m) := \ + $(sort \ + $(if $(strip $(ALL_MODULES.$(m).PATH)), \ + $(m), \ + $(filter $(m)_32 $(m)_64 host_cross_$(m) host_cross_$(m)_32 host_cross_$(m)_64, $(ALL_MODULES)) \ + ) \ + ) \ + ) \ + ) \ + $(foreach m, $(ALL_MODULES), \ + $(eval ALL_MODULES.$(m).NOTICE_DEPS := \ + $(sort \ + $(foreach d,$(ALL_MODULES.$(m).NOTICE_DEPS), \ + $(_lookup.$(d)) \ + ) \ + ) \ + ) \ + ) \ +) +endef + +########################################################### +## Target directory for license metadata files. +########################################################### +define license-metadata-dir +$(call generated-sources-dir-for,META,lic,) +endef + +########################################################### +## License metadata build rule for my_register_name $1 +########################################################### +define license-metadata-rule +$(strip $(eval _dir := $(call license-metadata-dir))) +$(strip $(eval _deps := $(sort $(filter-out $(_dir)/$(1).meta_lic,$(foreach d,$(ALL_MODULES.$(1).NOTICE_DEPS), $(_dir)/$(d).meta_lic))))) +$(foreach b,$(sort $(ALL_MODULES.$(1).BUILT) $(ALL_MODULES.$(1).INSTALLED)), +$(_dir)/$(b).meta_module :: + mkdir -p $$(dir $$@) + echo $(_dir)/$(1).meta_lic >> $$@ + sort -u $$@ -o $$@ + +) +$(_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 := $(sort $(ALL_MODULES.$(1).NOTICES)) +$(_dir)/$(1).meta_lic: PRIVATE_NOTICE_DEPS := $(_deps) +$(_dir)/$(1).meta_lic: PRIVATE_TARGETS := $(sort $(ALL_MODULES.$(1).BUILT) $(ALL_MODULES.$(1).INSTALLED)) +$(_dir)/$(1).meta_lic: PRIVATE_IS_CONTAINER := $(sort $(ALL_MODULES.$(1).IS_CONTAINER)) +$(_dir)/$(1).meta_lic: PRIVATE_PACKAGE_NAME := $(ALL_MODULES.$(1).LICENSE_PACKAGE_NAME) +$(_dir)/$(1).meta_lic: PRIVATE_INSTALL_MAP := $(sort $(ALL_MODULES.$(1).LICENSE_INSTALL_MAP)) +$(_dir)/$(1).meta_lic : $(_deps) $(ALL_MODULES.$(1).NOTICES) $(foreach b,$(sort $(ALL_MODULES.$(1).BUILT) $(ALL_MODULES.$(1).INSTALLED)), $(_dir)/$(b).meta_module) build/make/tools/build-license-metadata.sh + rm -f $$@ + mkdir -p $$(dir $$@) + build/make/tools/build-license-metadata.sh -k $$(PRIVATE_KINDS) -c $$(PRIVATE_CONDITIONS) -n $$(PRIVATE_NOTICES) -d $$(PRIVATE_NOTICE_DEPS) -m $$(PRIVATE_INSTALL_MAP) -t $$(PRIVATE_TARGETS) $$(if $$(filter-out false,$$(PRIVATE_IS_CONTAINER)),-is_container) -p $$(PRIVATE_PACKAGE_NAME) -o $$@ + +$(1) : $(_dir)/$(1).meta_lic + +$(if $(ALL_MODULES.$(1).INSTALLED_NOTICE_FILE),$(ALL_MODULES.$(1).INSTALLED_NOTICE_FILE) : $(_dir)/$(1).meta_lic) + +.PHONY: $(1).meta_lic +$(1).meta_lic : $(_dir)/$(1).meta_lic + +endef + +########################################################### +## Declares a license metadata build rule for ALL_MODULES +########################################################### +define build-license-metadata +$(foreach m,$(ALL_MODULES),$(eval $(call license-metadata-rule,$(m)))) +endef + ########################################################### ## Returns correct _idfPrefix from the list: ## { HOST, HOST_CROSS, TARGET } diff --git a/core/main.mk b/core/main.mk index 8857b5d172..fb13093576 100644 --- a/core/main.mk +++ b/core/main.mk @@ -1404,6 +1404,17 @@ modules_to_install := $(sort $(ALL_DEFAULT_INSTALLED_MODULES)) ALL_DEFAULT_INSTALLED_MODULES := +# Some notice deps refer to module names without prefix or arch suffix where +# only the variants with them get built. +# fix-notice-deps replaces those unadorned module names with every built variant. +$(call fix-notice-deps) + +# Create a license metadata rule per module. Could happen in base_rules.mk or +# notice_files.mk; except, it has to happen after fix-notice-deps to avoid +# missing dependency errors. +$(call build-license-metadata) + + # These are additional goals that we build, in order to make sure that there # is as little code as possible in the tree that doesn't build. modules_to_check := $(foreach m,$(ALL_MODULES),$(ALL_MODULES.$(m).CHECKED)) diff --git a/core/notice_files.mk b/core/notice_files.mk index 0430007c30..89f822b000 100644 --- a/core/notice_files.mk +++ b/core/notice_files.mk @@ -9,6 +9,32 @@ else notice_file:=$(strip $(wildcard $(LOCAL_PATH)/LICENSE $(LOCAL_PATH)/LICENCE $(LOCAL_PATH)/NOTICE)) endif +ifneq (,$(strip $(LOCAL_LICENSE_PACKAGE_NAME))) +license_package_name:=$(strip $(LOCAL_LICENSE_PACKAGE_NAME)) +else ifdef my_register_name +license_package_name:=$(my_register_name) +else +license_package_name:=$(strip $(LOCAL_MODULE)) +endif + +ifneq (,$(strip $(LOCAL_LICENSE_INSTALL_MAP))) +install_map:=$(strip $(LOCAL_LICENSE_INSTALL_MAP)) +else +install_map:= +endif + +ifneq (,$(strip $(LOCAL_LICENSE_KINDS))) +license_kinds:=$(strip $(LOCAL_LICENSE_KINDS)) +else +license_kinds:=legacy_by_exception_only +endif + +ifneq (,$(strip $(LOCAL_LICENSE_CONDITIONS))) +license_conditions:=$(strip $(LOCAL_LICENSE_CONDITIONS)) +else +license_conditions:=by_exception_only +endif + ifeq ($(LOCAL_MODULE_CLASS),GYP) # We ignore NOTICE files for modules of type GYP. notice_file := @@ -40,10 +66,64 @@ endif installed_notice_file := +is_container:=$(strip $(LOCAL_MODULE_IS_CONTAINER)) +ifeq (,$(is_container)) +ifneq (,$(strip $(filter %.zip %.tar %.tgz %.tar.gz %.apk %.img %.srcszip %.apex, $(LOCAL_BUILT_MODULE)))) +is_container:=true +else +is_container:=false +endif +else ifneq (,$(strip $(filter-out true false,$(is_container)))) +$(error Unrecognized value '$(is_container)' for LOCAL_MODULE_IS_CONTAINER) +endif + +ifeq (true,$(is_container)) +# Include shared libraries' notices for "container" types, but not for binaries etc. +notice_deps := \ + $(sort \ + $(LOCAL_REQUIRED_MODULES) \ + $(LOCAL_STATIC_LIBRARIES) \ + $(LOCAL_WHOLE_STATIC_LIBRARIES) \ + $(LOCAL_SHARED_LIBRARIES) \ + $(LOCAL_DYLIB_LIBRARIES) \ + $(LOCAL_RLIB_LIBRARIES) \ + $(LOCAL_PROC_MACRO_LIBRARIES) \ + $(LOCAL_HEADER_LIBRARIES) \ + $(LOCAL_STATIC_JAVA_LIBRARIES) \ + $(LOCAL_JAVA_LIBRARIES) \ + $(LOCAL_JNI_SHARED_LIBRARIES) \ + ) +else +notice_deps := \ + $(sort \ + $(LOCAL_REQUIRED_MODULES) \ + $(LOCAL_STATIC_LIBRARIES) \ + $(LOCAL_WHOLE_STATIC_LIBRARIES) \ + $(LOCAL_RLIB_LIBRARIES) \ + $(LOCAL_PROC_MACRO_LIBRARIES) \ + $(LOCAL_HEADER_LIBRARIES) \ + $(LOCAL_STATIC_JAVA_LIBRARIES) \ + ) +endif +ifeq ($(LOCAL_IS_HOST_MODULE),true) +notice_deps := $(sort $(notice_deps) $(LOCAL_HOST_REQUIRED_MODULES)) +else +notice_deps := $(sort $(notice_deps) $(LOCAL_TARGET_REQUIRED_MODULES)) +endif + +ifdef my_register_name +ALL_MODULES.$(my_register_name).LICENSE_PACKAGE_NAME := $(strip $(license_package_name)) +ALL_MODULES.$(my_register_name).LICENSE_KINDS := $(sort $(ALL_MODULES.$(my_register_name).LICENSE_KINDS) $(license_kinds)) +ALL_MODULES.$(my_register_name).LICENSE_CONDITIONS := $(sort $(ALL_MODULES.$(my_register_name).LICENSE_CONDITIONS) $(license_conditions)) +ALL_MODULES.$(my_register_name).LICENSE_INSTALL_MAP := $(sort $(ALL_MODULES.$(my_register_name).LICENSE_INSTALL_MAP) $(install_map)) +ALL_MODULES.$(my_register_name).NOTICE_DEPS := $(sort $(ALL_MODULES.$(my_register_name).NOTICE_DEPS) $(notice_deps)) +ALL_MODULES.$(my_register_name).IS_CONTAINER := $(sort $(ALL_MODULES.$(my_register_name).IS_CONTAINER) $(is_container)) +endif + ifdef notice_file ifdef my_register_name -ALL_MODULES.$(my_register_name).NOTICES := $(ALL_MODULES.$(my_register_name).NOTICES) $(notice_file) +ALL_MODULES.$(my_register_name).NOTICES := $(sort $(ALL_MODULES.$(my_register_name).NOTICES) $(notice_file)) endif # This relies on the name of the directory in PRODUCT_OUT matching where @@ -87,8 +167,6 @@ else # Soong produces uninstallable *.sdk shared libraries for embedding in APKs. module_installed_filename := \ $(patsubst $(PRODUCT_OUT)/%,%,$($(LOCAL_2ND_ARCH_VAR_PREFIX)$(my_prefix)OUT_SHARED_LIBRARIES))/$(notdir $(LOCAL_BUILT_MODULE)) - else - $(error Cannot determine where to install NOTICE file for $(LOCAL_MODULE)) endif # JAVA_LIBRARIES endif # STATIC_LIBRARIES endif @@ -101,12 +179,17 @@ module_installed_filename := $(patsubst $(HOST_CROSS_OUT)/%,%,$(module_installed installed_notice_file := $($(my_prefix)OUT_NOTICE_FILES)/src/$(module_installed_filename).txt +ifdef my_register_name +ALL_MODULES.$(my_register_name).INSTALLED_NOTICE_FILE := $(installed_notice_file) +endif + $(installed_notice_file): PRIVATE_INSTALLED_MODULE := $(module_installed_filename) +$(installed_notice_file) : PRIVATE_NOTICES := $(notice_file) $(installed_notice_file): $(notice_file) @echo Notice file: $< -- $@ $(hide) mkdir -p $(dir $@) - $(hide) awk 'FNR==1 && NR > 1 {print "\n"} {print}' $^ > $@ + $(hide) awk 'FNR==1 && NR > 1 {print "\n"} {print}' $(PRIVATE_NOTICES) > $@ ifdef LOCAL_INSTALLED_MODULE # Make LOCAL_INSTALLED_MODULE depend on NOTICE files if they exist diff --git a/tools/build-license-metadata.sh b/tools/build-license-metadata.sh new file mode 100755 index 0000000000..8df7d2d369 --- /dev/null +++ b/tools/build-license-metadata.sh @@ -0,0 +1,343 @@ +#!/bin/bash + +set -u + +ME=$(basename $0) + +USAGE="Usage: ${ME} {options} + +Builds a license metadata specification and outputs it to stdout or {outfile}. + +The available options are: + +-k kind... license kinds +-c condition... license conditions +-p package... license package name +-n notice... license notice file +-d dependency... license metadata file dependency +-t target... targets +-m target:installed... map dependent targets to their installed names +-is_container preserved dependent target name when given +-o outfile output file +" + +# Global flag variables +license_kinds= +license_conditions= +license_package_name= +license_notice= +license_deps= +targets= +installmap= +is_container=false +ofile= + + +# Global variables +declare -A depfiles +effective_conditions= +declare -A depModules + +# work around bug where "${#array[@]}" traps if never set +depfiles["x"]=808 # set an arbitrary index +unset depfiles["x"] # delete it to make depfiles empty again +depModules["x"]=808 # set an arbitrary index +unset depModules["x"] # delete it to make depModules empty again + + +# Exits with a message. +# +# When the exit status is 2, assumes a usage error and outputs the usage message +# to stderr before outputting the specific error message to stderr. +# +# Parameters: +# Optional numeric exit status (defaults to 2, i.e. a usage error.) +# Remaining args treated as an error message sent to stderr. +function die() { + local status=2 + case "${1:-}" in *[^0-9]*) ;; *) status="$1"; shift ;; esac + case "${status}" in 2) echo "${USAGE}" >&2; echo >&2 ;; esac + if [ -n "$*" ]; then + echo -e "$*\n" >&2 + fi + exit $status +} + + +# Sets the flag variables based on the command-line. +# +# invoke with: process_args "$@" +function process_args() { + local curr_flag= + local name + local val + while [ "$#" -gt '0' ]; do + case "${1}" in + -h) + echo "${USAGE}" + exit 0 + ;; + -k) + curr_flag=kind + ;; + -c) + curr_flag=condition + ;; + -p) + curr_flag=package + ;; + -n) + curr_flag=notice + ;; + -d) + curr_flag=dependency + ;; + -t) + curr_flag=target + ;; + -m) + curr_flag=installmap + ;; + -o) + curr_flag=ofile + ;; + -is_container) + curr_flag= + is_container=true + ;; + -*) + die "Unknown flag: \"${1}\"" + ;; + *) + case "${curr_flag}" in + kind) + license_kinds="${license_kinds}${license_kinds:+ }${1}" + ;; + condition) + license_conditions="${license_conditions}${license_conditions:+ }${1}" + ;; + package) + license_package_name="${license_package_name}${license_package_name:+ }${1}" + ;; + notice) + license_notice="${license_notice}${license_notice:+ }${1}" + ;; + dependency) + license_deps="${license_deps}${license_deps:+ }${1}" + ;; + target) + targets="${targets}${targets:+ }${1}" + ;; + installmap) + installmap="${installmap}${installmap:+ }${1}" + ;; + ofile) + if [ -n "${ofile}" ]; then + die "Output file -o appears twice as \"${ofile}\" and \"${1}\"" + fi + ofile="${1}" + ;; + *) + die "Must precede argument \"${1}\" with type flag." + ;; + esac + ;; + esac + shift + done +} + +# Reads a license metadata file from stdin, and outputs the named dependencies. +# +# No parameters. +function extract_deps() { + awk '$1 == "dep_name:" { sub(/^"/, "", $2); sub(/"$/, "", $2); print $2; }' +} + +# Populates the `depfiles` associative array mapping dependencies to license +# metadata content. +# +# Starting with the dependencies enumerated in `license_deps`, calculates the +# transitive closure of all dependencies mapping the name of each license +# metadata file to its content. +# +# Dependency names ending in `.meta_module` indirectly reference license +# metadata with 1 license metadata filename per line. +# +# No parameters; no output. +function read_deps() { + local newdeps=$( + for d in ${license_deps}; do + case "${d}" in + *.meta_module) cat "${d}" ;; + *) echo "${d}" ;; + esac + done | sort -u + ) + local alldeps= + local deps= + local content= + local mod= + local dep= + while [ "${#newdeps}" -gt '0' ]; do + deps="${newdeps}" + newdeps= + for dep in ${deps}; do + content=$(cat ${dep}) + depfiles["${dep}"]="${content}" + alldeps="${alldeps}${alldeps:+ }"$(echo "${content}" | extract_deps) + done + alldeps=$(for d in ${alldeps}; do echo "${d}"; done | sort -u) + for d in ${alldeps}; do + deps=$( + case "${d}" in + *.meta_module) cat"${d}" ;; + *) echo "${d}" ;; + esac + ) + for mod in ${deps}; do + if [ -z "${depfiles[${mod}]+isset}" ]; then + newdeps="${newdeps}${newdeps:+ }${mod}" + fi + done + done + alldeps= + done +} + +# Returns the effective license conditions for the current license metadata. +# +# If a module is restricted or links in a restricted module, the effective +# license has a restricted condition. +function calculate_effective_conditions() { + local conditions="${license_conditions}" + local condition + case "${license_conditions}" in + *restricted*) : do nothing ;; + *) + for d in "${!depfiles[@]}"; do + condition=$( + echo "${depfiles[${d}]}" | \ + awk '$1 == "effective_condition:" { + $1 = "" + print + }' \ + ) + case "${condition}" in + *restricted*) + conditions="${conditions}${conditions:+ }restricted" + break + ;; + esac + done + ;; + esac + echo "${conditions}" +} + + +process_args "$@" + +if [ -n "${ofile}" ]; then + # truncate the output file before appending results + : >"${ofile}" +else + ofile=/dev/stdout +fi + +# spit out the license metadata file content +( + echo 'license_package_name: "'${license_package_name}'"' + for kind in ${license_kinds}; do + echo 'license_kind: "'${kind}'"' + done + for condition in ${license_conditions}; do + echo 'license_condition: "'${condition}'"' + done + for f in ${license_notice}; do + echo 'license_text: "'${f}'"' + done + echo "is_container: ${is_container}" + for t in ${targets}; do + echo 'target: "'${t}'"' + done + for m in ${installmap}; do + echo 'install_map: "'${m}'"' + done +) >>"${ofile}" +read_deps +for dep in "${!depfiles[@]}"; do + m=$(expr "${dep}" : '^.*[/]\([^/]*\)[.]meta_lic$') + depModules["${m}"]=true +done +effective_conditions=$(calculate_effective_conditions) +for condition in ${effective_conditions}; do + echo 'effective_condition: "'${condition}'"' +done >>"${ofile}" +for dep in "${!depfiles[@]}"; do + echo 'dep {' + echo "${depfiles[${dep}]}" | \ + awk -v name="${dep}" ' + function strip_type() { + $1 = "" + sub(/^\s*/, "") + } + BEGIN { + print " dep_name: " name + } + $1 == "license_package_name:" { + strip_type() + print " dep_package_name: "$0 + } + $1 == "dep_name:" { + print " dep_sub_dep: "$2 + } + $1 == "license_kind:" { + print " dep_license_kind: "$2 + } + $1 == "license_condition:" { + print " dep_license_condition: "$2 + } + $1 == "is_container:" { + print " dep_is_container: "$2 + } + $1 == "license_text:" { + strip_type() + print " dep_license_text: "$0 + } + $1 == "target:" { + print " dep_target: "$2 + } + $1 == "install_map:" { + print " dep_install_map: "$2 + } + ' + # The restricted license kind is contagious to all linked dependencies. + dep_conditions=$(echo $( + echo "${depfiles[${dep}]}" | awk ' + $1 == "effective_condition:" { + $1 = "" + sub(/^\s*/, "") + gsub(/"/, "") + print + } + ' + )) + for condition in ${dep_conditions}; do + echo ' dep_effective_condition: "'${condition}'"' + done + if ! ${is_container}; then + case "${dep_conditions}" in + *restricted*) : already restricted -- nothing to inherit ;; + *) + case "${effective_conditions}" in + *restricted*) + # "contagious" restricted infects everything linked to restricted + echo ' dep_effective_condition: "restricted"' + ;; + esac + ;; + esac + fi + echo '}' +done >>"${ofile}"