Merge "Some changes to support SBOM generation for b build unbundled APEXs."
diff --git a/core/Makefile b/core/Makefile
index 622174f..14adbb5 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -17,6 +17,52 @@
SYSTEM_DLKM_NOTICE_DEPS :=
# -----------------------------------------------------------------
+# Release Config Flags
+
+# Create a summary file of build flags for each partition
+# $(1): build flags json file
+# $(2): flag names
+define generate-partition-build-flag-file
+$(eval $(strip $(1)): PRIVATE_OUT := $(strip $(1)))
+$(eval $(strip $(1)): PRIVATE_FLAG_NAMES := $(strip $(2)))
+$(strip $(1)):
+ mkdir -p $$(dir $$(PRIVATE_OUT))
+ ( \
+ echo '{' ; \
+ echo 'flags: [' ; \
+ $$(foreach flag, $$(PRIVATE_FLAG_NAMES), \
+ printf ' { "name": "%s", "value": "%s", ' \
+ '$$(flag)' \
+ '$$(_ALL_RELEASE_FLAGS.$$(flag).VALUE)' \
+ ; \
+ printf '"set": "%s", "default": "%s", "declared": "%s", }' \
+ '$$(_ALL_RELEASE_FLAGS.$$(flag).SET_IN)' \
+ '$$(_ALL_RELEASE_FLAGS.$$(flag).DEFAULT)' \
+ '$$(_ALL_RELEASE_FLAGS.$$(flag).DECLARED_IN)' \
+ ; \
+ printf '$$(if $$(filter $$(lastword $$(PRIVATE_FLAG_NAMES)),$$(flag)),,$$(comma))\n' ; \
+ ) \
+ echo "]" ; \
+ echo "}" \
+ ) >> $$(PRIVATE_OUT)
+endef
+
+$(foreach partition, $(_FLAG_PARTITIONS), \
+ $(eval BUILD_FLAG_SUMMARIES.$(partition) \
+ := $(TARGET_OUT_FLAGS)/$(partition)/etc/build_flags.json) \
+ $(eval $(call generate-partition-build-flag-file, \
+ $(BUILD_FLAG_SUMMARIES.$(partition)), \
+ $(_ALL_RELEASE_FLAGS.PARTITIONS.$(partition)) \
+ ) \
+ ) \
+)
+
+# TODO: Remove
+.PHONY: flag-files
+flag-files: $(foreach partition, $(_FLAG_PARTITIONS), \
+ $(TARGET_OUT_FLAGS)/$(partition)/etc/build_flags.json)
+
+# -----------------------------------------------------------------
# Define rules to copy PRODUCT_COPY_FILES defined by the product.
# PRODUCT_COPY_FILES contains words like <source file>:<dest file>[:<owner>].
# <dest file> is relative to $(PRODUCT_OUT), so it should look like,
@@ -530,6 +576,24 @@
$(call copy-many-files,$(call module-load-list-copy-paths,$(call intermediates-dir-for,PACKAGING,vendor_charger_module_list$(_sep)$(_kver)),$(BOARD_VENDOR_CHARGER_KERNEL_MODULES$(_sep)$(_kver)),$(BOARD_VENDOR_CHARGER_KERNEL_MODULES_LOAD$(_sep)$(_kver)),modules.load.charger,$(TARGET_OUT_VENDOR))))
endef
+# $(1): kernel module directory name (top is an out of band value for no directory)
+define build-vendor-ramdisk-charger-load
+$(if $(filter top,$(1)),\
+ $(eval _kver :=)$(eval _sep :=),\
+ $(eval _kver := $(1))$(eval _sep :=_))\
+ $(if $(BOARD_VENDOR_RAMDISK_CHARGER_KERNEL_MODULES_LOAD$(_sep)$(_kver)),\
+ $(call copy-many-files,$(call module-load-list-copy-paths,$(call intermediates-dir-for,PACKAGING,vendor_ramdisk_charger_module_list$(_sep)$(_kver)),$(BOARD_VENDOR_RAMDISK_KERNEL_MODULES$(_sep)$(_kver)),$(BOARD_VENDOR_RAMDISK_CHARGER_KERNEL_MODULES_LOAD$(_sep)$(_kver)),modules.load.charger,$(TARGET_VENDOR_RAMDISK_OUT))))
+endef
+
+# $(1): kernel module directory name (top is an out of band value for no directory)
+define build-vendor-kernel-ramdisk-charger-load
+$(if $(filter top,$(1)),\
+ $(eval _kver :=)$(eval _sep :=),\
+ $(eval _kver := $(1))$(eval _sep :=_))\
+ $(if $(BOARD_VENDOR_KERNEL_RAMDISK_CHARGER_KERNEL_MODULES_LOAD$(_sep)$(_kver)),\
+ $(call copy-many-files,$(call module-load-list-copy-paths,$(call intermediates-dir-for,PACKAGING,vendor_kernel_ramdisk_charger_module_list$(_sep)$(_kver)),$(BOARD_VENDOR_KERNEL_RAMDISK_KERNEL_MODULES$(_sep)$(_kver)),$(BOARD_VENDOR_KERNEL_RAMDISK_CHARGER_KERNEL_MODULES_LOAD$(_sep)$(_kver)),modules.load.charger,$(TARGET_VENDOR_KERNEL_RAMDISK_OUT))))
+endef
+
ifneq ($(BUILDING_VENDOR_BOOT_IMAGE),true)
# If there is no vendor boot partition, store vendor ramdisk kernel modules in the
# boot ramdisk.
@@ -595,6 +659,8 @@
$(eval ALL_DEFAULT_INSTALLED_MODULES += $(call build-vendor-kernel-ramdisk-recovery-load,$(kmd))) \
$(eval ALL_DEFAULT_INSTALLED_MODULES += $(call build-image-kernel-modules-dir,VENDOR,$(if $(filter true,$(BOARD_USES_VENDOR_DLKMIMAGE)),$(TARGET_OUT_VENDOR_DLKM),$(TARGET_OUT_VENDOR)),vendor,modules.load,$(VENDOR_STRIPPED_MODULE_STAGING_DIR),$(kmd),$(BOARD_SYSTEM_KERNEL_MODULES),system)) \
$(eval ALL_DEFAULT_INSTALLED_MODULES += $(call build-vendor-charger-load,$(kmd))) \
+ $(eval ALL_DEFAULT_INSTALLED_MODULES += $(call build-vendor-ramdisk-charger-load,$(kmd))) \
+ $(eval ALL_DEFAULT_INSTALLED_MODULES += $(call build-vendor-kernel-ramdisk-charger-load,$(kmd))) \
$(eval ALL_DEFAULT_INSTALLED_MODULES += $(call build-image-kernel-modules-dir,ODM,$(if $(filter true,$(BOARD_USES_ODM_DLKMIMAGE)),$(TARGET_OUT_ODM_DLKM),$(TARGET_OUT_ODM)),odm,modules.load,,$(kmd))) \
$(eval ALL_DEFAULT_INSTALLED_MODULES += $(call build-image-kernel-modules-dir,SYSTEM,$(if $(filter true,$(BOARD_USES_SYSTEM_DLKMIMAGE)),$(TARGET_OUT_SYSTEM_DLKM),$(TARGET_OUT_SYSTEM)),system,modules.load,,$(kmd))) \
$(if $(filter true,$(BOARD_USES_RECOVERY_AS_BOOT)),\
@@ -5151,6 +5217,7 @@
lz4 \
make_f2fs \
make_f2fs_casefold \
+ merge_ota \
merge_target_files \
minigzip \
mk_combined_img \
diff --git a/core/base_rules.mk b/core/base_rules.mk
index c453469..65e80fb 100644
--- a/core/base_rules.mk
+++ b/core/base_rules.mk
@@ -190,18 +190,6 @@
$(call pretty-error,unusual tags: $(filter-out tests optional samples,$(my_module_tags)))
endif
-# Add implicit tags.
-#
-# If the local directory or one of its parents contains a MODULE_LICENSE_GPL
-# file, tag the module as "gnu". Search for "*_GPL*", "*_LGPL*" and "*_MPL*"
-# so that we can also find files like MODULE_LICENSE_GPL_AND_AFL
-#
-gpl_license_file := $(call find-parent-file,$(LOCAL_PATH),MODULE_LICENSE*_GPL* MODULE_LICENSE*_MPL* MODULE_LICENSE*_LGPL*)
-ifneq ($(gpl_license_file),)
- my_module_tags += gnu
- ALL_GPL_MODULE_LICENSE_FILES += $(gpl_license_file)
-endif
-
LOCAL_MODULE_CLASS := $(strip $(LOCAL_MODULE_CLASS))
ifneq ($(words $(LOCAL_MODULE_CLASS)),1)
$(error $(LOCAL_PATH): LOCAL_MODULE_CLASS must contain exactly one word, not "$(LOCAL_MODULE_CLASS)")
diff --git a/core/config.mk b/core/config.mk
index 396aad0..e272389 100644
--- a/core/config.mk
+++ b/core/config.mk
@@ -271,7 +271,7 @@
# Ex: $(call add_soong_config_namespace,acme)
define add_soong_config_namespace
-$(eval SOONG_CONFIG_NAMESPACES += $1) \
+$(eval SOONG_CONFIG_NAMESPACES += $(strip $1)) \
$(eval SOONG_CONFIG_$(strip $1) :=)
endef
@@ -281,8 +281,8 @@
# $1 is the namespace. $2 is the list of variables.
# Ex: $(call add_soong_config_var,acme,COOL_FEATURE_A COOL_FEATURE_B)
define add_soong_config_var
-$(eval SOONG_CONFIG_$(strip $1) += $2) \
-$(foreach v,$(strip $2),$(eval SOONG_CONFIG_$(strip $1)_$v := $($v)))
+$(eval SOONG_CONFIG_$(strip $1) += $(strip $2)) \
+$(foreach v,$(strip $2),$(eval SOONG_CONFIG_$(strip $1)_$v := $(strip $($v))))
endef
# The add_soong_config_var_value function defines a make variable and also adds
@@ -291,7 +291,7 @@
# Ex: $(call add_soong_config_var_value,acme,COOL_FEATURE,true)
define add_soong_config_var_value
-$(eval $2 := $3) \
+$(eval $(strip $2) := $(strip $3)) \
$(call add_soong_config_var,$1,$2)
endef
@@ -299,8 +299,8 @@
#
# internal utility to define a namespace and a variable in it.
define soong_config_define_internal
-$(if $(filter $1,$(SOONG_CONFIG_NAMESPACES)),,$(eval SOONG_CONFIG_NAMESPACES:=$(SOONG_CONFIG_NAMESPACES) $1)) \
-$(if $(filter $2,$(SOONG_CONFIG_$(strip $1))),,$(eval SOONG_CONFIG_$(strip $1):=$(SOONG_CONFIG_$(strip $1)) $2))
+$(if $(filter $1,$(SOONG_CONFIG_NAMESPACES)),,$(eval SOONG_CONFIG_NAMESPACES:=$(SOONG_CONFIG_NAMESPACES) $(strip $1))) \
+$(if $(filter $2,$(SOONG_CONFIG_$(strip $1))),,$(eval SOONG_CONFIG_$(strip $1):=$(SOONG_CONFIG_$(strip $1)) $(strip $2)))
endef
# soong_config_set defines the variable in the given Soong config namespace
@@ -309,7 +309,7 @@
# Ex: $(call soong_config_set,acme,COOL_FEATURE,true)
define soong_config_set
$(call soong_config_define_internal,$1,$2) \
-$(eval SOONG_CONFIG_$(strip $1)_$(strip $2):=$3)
+$(eval SOONG_CONFIG_$(strip $1)_$(strip $2):=$(strip $3))
endef
# soong_config_append appends to the value of the variable in the given Soong
@@ -318,7 +318,7 @@
# $1 is the namespace, $2 is the variable name, $3 is the value
define soong_config_append
$(call soong_config_define_internal,$1,$2) \
-$(eval SOONG_CONFIG_$(strip $1)_$(strip $2):=$(SOONG_CONFIG_$(strip $1)_$(strip $2)) $3)
+$(eval SOONG_CONFIG_$(strip $1)_$(strip $2):=$(SOONG_CONFIG_$(strip $1)_$(strip $2)) $(strip $3))
endef
# soong_config_append gets to the value of the variable in the given Soong
diff --git a/core/definitions.mk b/core/definitions.mk
index e4cee7a..7697211 100644
--- a/core/definitions.mk
+++ b/core/definitions.mk
@@ -75,9 +75,6 @@
# All findbugs xml files
ALL_FINDBUGS_FILES:=
-# GPL module license files
-ALL_GPL_MODULE_LICENSE_FILES:=
-
# Packages with certificate violation
CERTIFICATE_VIOLATION_MODULES :=
@@ -897,7 +894,8 @@
endef
###########################################################
-## Declare license dependencies $(2) for non-module target $(1)
+## Declare license dependencies $(2) with optional colon-separated
+## annotations for non-module target $(1)
###########################################################
define declare-license-deps
$(strip \
@@ -909,7 +907,8 @@
endef
###########################################################
-## Declare license dependencies $(2) for non-module container-type target $(1)
+## Declare license dependencies $(2) with optional colon-separated
+## annotations for non-module container-type target $(1)
##
## Container-type targets are targets like .zip files that
## merely aggregate other files.
diff --git a/core/dex_preopt_odex_install.mk b/core/dex_preopt_odex_install.mk
index cb16321..7165bea 100644
--- a/core/dex_preopt_odex_install.mk
+++ b/core/dex_preopt_odex_install.mk
@@ -84,12 +84,13 @@
ifndef LOCAL_DEX_PREOPT_GENERATE_PROFILE
# If LOCAL_DEX_PREOPT_GENERATE_PROFILE is not defined, default it based on the existence of the
# profile class listing. TODO: Use product specific directory here.
- my_classes_directory := $(PRODUCT_DEX_PREOPT_PROFILE_DIR)
- LOCAL_DEX_PREOPT_PROFILE := $(my_classes_directory)/$(LOCAL_MODULE).prof
+ ifdef PRODUCT_DEX_PREOPT_PROFILE_DIR
+ LOCAL_DEX_PREOPT_PROFILE := $(PRODUCT_DEX_PREOPT_PROFILE_DIR)/$(LOCAL_MODULE).prof
- ifneq (,$(wildcard $(LOCAL_DEX_PREOPT_PROFILE)))
- my_process_profile := true
- my_profile_is_text_listing :=
+ ifneq (,$(wildcard $(LOCAL_DEX_PREOPT_PROFILE)))
+ my_process_profile := true
+ my_profile_is_text_listing :=
+ endif
endif
else
my_process_profile := $(LOCAL_DEX_PREOPT_GENERATE_PROFILE)
diff --git a/core/envsetup.mk b/core/envsetup.mk
index e3300fc..8887ddc 100644
--- a/core/envsetup.mk
+++ b/core/envsetup.mk
@@ -41,6 +41,11 @@
$(eval _starlark_results :=)
endef
+# ---------------------------------------------------------------
+# Release config
+include $(BUILD_SYSTEM)/release_config.mk
+
+# ---------------------------------------------------------------
# defines ALL_VERSIONS
$(call run-starlark,build/make/core/all_versions.bzl)
@@ -350,6 +355,7 @@
RBC_PRODUCT_CONFIG \
RBC_BOARD_CONFIG \
SOONG_% \
+ TARGET_RELEASE \
TOPDIR \
TRACE_BEGIN_SOONG \
USER)
@@ -564,6 +570,8 @@
TARGET_OUT_NOTICE_FILES := $(TARGET_OUT_INTERMEDIATES)/NOTICE_FILES
TARGET_OUT_FAKE := $(PRODUCT_OUT)/fake_packages
TARGET_OUT_TESTCASES := $(PRODUCT_OUT)/testcases
+TARGET_OUT_FLAGS := $(TARGET_OUT_INTERMEDIATES)/FLAGS
+
.KATI_READONLY := \
TARGET_OUT_EXECUTABLES \
TARGET_OUT_OPTIONAL_EXECUTABLES \
@@ -577,7 +585,8 @@
TARGET_OUT_ETC \
TARGET_OUT_NOTICE_FILES \
TARGET_OUT_FAKE \
- TARGET_OUT_TESTCASES
+ TARGET_OUT_TESTCASES \
+ TARGET_OUT_FLAGS
ifeq ($(SANITIZE_LITE),true)
# When using SANITIZE_LITE, APKs must not be packaged with sanitized libraries, as they will not
diff --git a/core/product_config.rbc b/core/product_config.rbc
index e594894..921f068 100644
--- a/core/product_config.rbc
+++ b/core/product_config.rbc
@@ -379,11 +379,7 @@
def _soong_config_set(g, nsname, var, value):
"""Assigns the value to the variable in the namespace."""
_soong_config_namespace(g, nsname)
- if type(value) == "string":
- # Trim right spaces, because in make the variable is set in an $(eval),
- # which will ignore trailing spaces.
- value = value.rstrip(" ")
- g[_soong_config_namespaces_key][nsname][var]=value
+ g[_soong_config_namespaces_key][nsname][var]=_mkstrip(value)
def _soong_config_append(g, nsname, var, value):
"""Appends to the value of the variable in the namespace."""
@@ -391,9 +387,9 @@
ns = g[_soong_config_namespaces_key][nsname]
oldv = ns.get(var)
if oldv == None:
- ns[var] = value
+ ns[var] = _mkstrip(value)
else:
- ns[var] += " " + value
+ ns[var] += " " + _mkstrip(value)
def _soong_config_get(g, nsname, var):
diff --git a/core/release_config.mk b/core/release_config.mk
new file mode 100644
index 0000000..fdfc6a0
--- /dev/null
+++ b/core/release_config.mk
@@ -0,0 +1,222 @@
+# Copyright (C) 2023 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.
+
+# Partitions that get build system flag summaries
+_FLAG_PARTITIONS := system vendor system_ext product
+
+# All possible release flags. Defined in the build_flags.mk files
+# throughout the tree
+_ALL_RELEASE_FLAGS :=
+
+# -----------------------------------------------------------------
+# Choose the flag files
+# Do this first, because we're going to unset TARGET_RELEASE before
+# including anyone, so they don't start making conditionals based on it.
+
+# If this is a google source tree, restrict it to only the one file
+# which has OWNERS control. If it isn't let others define their own.
+# TODO: Remove wildcard for build/release one when all branch manifests
+# have updated.
+config_map_files := $(wildcard build/release/release_config_map.mk) \
+ $(if $(wildcard vendor/google/release/release_config_map.mk), \
+ vendor/google/release/release_config_map.mk, \
+ $(sort \
+ $(wildcard device/*/release/release_config_map.mk) \
+ $(wildcard device/*/*/release/release_config_map.mk) \
+ $(wildcard vendor/*/release/release_config_map.mk) \
+ $(wildcard vendor/*/*/release/release_config_map.mk) \
+ ) \
+ )
+
+# $1 config name
+# $2 release config files
+define declare-release-config
+ $(eval # No duplicates)
+ $(if $(filter $(_all_release_configs), $(strip $(1))), \
+ $(error declare-release-config: config $(strip $(1)) declared in: $(_included) Previously declared here: $(_all_release_configs.$(strip $(1)).DECLARED_IN)) \
+ )
+ $(eval # Must have release config files)
+ $(if $(strip $(2)),, \
+ $(error declare-release-config: config $(strip $(1)) must have release config files) \
+ )
+ $(eval _all_release_configs := $(sort $(_all_release_configs) $(strip $(1))))
+ $(eval _all_release_configs.$(strip $(1)).DECLARED_IN := $(_included))
+ $(eval _all_release_configs.$(strip $(1)).FILES := $(strip $(2)))
+endef
+
+# Include the config map files
+$(foreach f, $(config_map_files), \
+ $(eval _included := $(f)) \
+ $(eval include $(f)) \
+)
+
+# If TARGET_RELEASE is set, fail if there is no matching release config
+# If it isn't set, no release config files will be included and all flags
+# will get their default values.
+ifneq ($(TARGET_RELEASE),)
+ifeq ($(filter $(_all_release_configs), $(TARGET_RELEASE)),)
+ $(error No release config found for TARGET_RELEASE: $(TARGET_RELEASE). Available releases are: $(_all_release_configs))
+else
+ # Choose flag files
+ # Don't sort this, use it in the order they gave us.
+ _release_config_files := $(_all_release_configs.$(TARGET_RELEASE).FILES)
+endif
+else
+# Useful for finding scripts etc that aren't passing or setting TARGET_RELEASE
+ifneq ($(FAIL_IF_NO_RELEASE_CONFIG),)
+ $(error FAIL_IF_NO_RELEASE_CONFIG was set and TARGET_RELEASE was not)
+endif
+_release_config_files :=
+endif
+
+# Unset variables so they can't use it
+define declare-release-config
+$(error declare-release-config can only be called from inside release_config_map.mk files)
+endef
+
+# TODO: Remove this check after enough people have sourced lunch that we don't
+# need to worry about it trying to do get_build_vars TARGET_RELEASE. Maybe after ~9/2023
+ifneq ($(CALLED_FROM_SETUP),true)
+define TARGET_RELEASE
+$(error TARGET_RELEASE may not be accessed directly. Use individual flags.)
+endef
+else
+TARGET_RELEASE:=
+endif
+.KATI_READONLY := TARGET_RELEASE
+
+$(foreach config, $(_all_release_configs), \
+ $(eval _all_release_configs.$(config).DECLARED_IN:= ) \
+ $(eval _all_release_configs.$(config).FILES:= ) \
+)
+_all_release_configs:=
+config_map_files:=
+
+# -----------------------------------------------------------------
+# Declare the flags
+
+# $1 partition(s)
+# $2 flag name. Must start with RELEASE_
+# $3 default. True or false
+define declare-build-flag
+ $(if $(filter-out all $(_FLAG_PARTITIONS), $(strip $(1))), \
+ $(error declare-build-flag: invalid partitions: $(strip $(1))) \
+ )
+ $(if $(and $(filter all,$(strip $(1))),$(filter-out all, $(strip $(1)))), \
+ $(error declare-build-flag: "all" can't be combined with other partitions: $(strip $(1))), \
+ $(eval declare-build-flag.partition := $(_FLAG_PARTITIONS)) \
+ )
+ $(if $(filter-out RELEASE_%, $(strip $(2))), \
+ $(error declare-build-flag: Release flag names must start with RELEASE_: $(strip $(2))) \
+ )
+ $(eval _ALL_RELEASE_FLAGS += $(strip $(2)))
+ $(foreach partition, $(declare-build-flag.partition), \
+ $(eval _ALL_RELEASE_FLAGS.PARTITIONS.$(partition) := $(sort \
+ $(_ALL_RELEASE_FLAGS.PARTITIONS.$(partition)) $(strip $(2)))) \
+ )
+ $(eval _ALL_RELEASE_FLAGS.$(strip $(2)).PARTITIONS := $(declare-build-flag.partition))
+ $(eval _ALL_RELEASE_FLAGS.$(strip $(2)).DEFAULT := $(strip $(3)))
+ $(eval _ALL_RELEASE_FLAGS.$(strip $(2)).DECLARED_IN := $(_included))
+ $(eval _ALL_RELEASE_FLAGS.$(strip $(2)).VALUE := $(strip $(3)))
+ $(eval _ALL_RELEASE_FLAGS.$(strip $(2)).SET_IN := $(_included))
+ $(eval declare-build-flag.partition:=)
+endef
+
+
+# Choose the files
+# If this is a google source tree, restrict it to only the one file
+# which has OWNERS control. If it isn't let others define their own.
+flag_declaration_files := $(wildcard build/release/build_flags.mk) \
+ $(if $(wildcard vendor/google/release/build_flags.mk), \
+ vendor/google/release/build_flags.mk, \
+ $(sort \
+ $(wildcard device/*/release/build_flags.mk) \
+ $(wildcard device/*/*/release/build_flags.mk) \
+ $(wildcard vendor/*/release/build_flags.mk) \
+ $(wildcard vendor/*/*/release/build_flags.mk) \
+ ) \
+ )
+
+# Include the files
+$(foreach f, $(flag_declaration_files), \
+ $(eval _included := $(f)) \
+ $(eval include $(f)) \
+)
+
+# Don't let anyone declare build flags after here
+define declare-build-flag
+$(error declare-build-flag can only be called from inside flag definition files.)
+endef
+
+# No more flags from here on
+.KATI_READONLY := _ALL_RELEASE_FLAGS
+
+# -----------------------------------------------------------------
+# Set the flags
+
+# $(1): Flag name. Must start with RELEASE_ and have been defined by declare-build-flag
+# $(2): Value. True or false
+define set-build-flag
+ $(if $(filter-out $(_ALL_RELEASE_FLAGS), $(strip $(1))), \
+ $(error set-build-flag: Undeclared build flag: $(strip $(1))) \
+ )
+ $(eval _ALL_RELEASE_FLAGS.$(strip $(1)).VALUE := $(strip $(2)))
+ $(eval _ALL_RELEASE_FLAGS.$(strip $(1)).SET_IN := $(_included))
+endef
+
+# This writes directly to a file so that the version never exists in make for
+# people to write conditionals upon.
+define set-release-version
+ $(eval _RELEASE_VERSION := $(strip $(1)))
+endef
+
+# Include the files (if there are any)
+ifneq ($(strip $(_release_config_files)),)
+ $(foreach f, $(_release_config_files), \
+ $(eval _included := $(f)) \
+ $(eval include $(f)) \
+ )
+else
+ # No TARGET_RELEASE means release version 0
+ $(call set-release-version, 0)
+endif
+
+
+ifeq ($(_RELEASE_VERSION)),)
+ $(error No release config file called set-release-version. Included files were: $(_release_config_files))
+endif
+
+# Don't let anyone declare build flags after here
+define set-build-flag
+$(error set-build-flag can only be called from inside release config files.)
+endef
+
+# Don't let anyone set the release version after here
+define set-release-version
+$(error set-release-version can only be called from inside release config files.)
+endef
+
+# Set the flag values, and don't allow any one to modify them.
+$(foreach flag, $(_ALL_RELEASE_FLAGS), \
+ $(eval $(flag) := $(_ALL_RELEASE_FLAGS.$(flag).VALUE)) \
+ $(eval .KATI_READONLY := $(flag)) \
+)
+
+
+# -----------------------------------------------------------------
+# Clear out vars
+flag_declaration_files:=
+flag_files:=
+_included:=
+_release_config_files:=
diff --git a/core/soong_config.mk b/core/soong_config.mk
index 034d044..8b9ba4f 100644
--- a/core/soong_config.mk
+++ b/core/soong_config.mk
@@ -319,6 +319,9 @@
$(call add_json_str, ProductBrand, $(PRODUCT_BRAND))
$(call add_json_list, BuildVersionTags, $(BUILD_VERSION_TAGS))
+$(call add_json_str, ReleaseVersion, $(_RELEASE_VERSION))
+$(call add_json_list, ReleaseDeviceConfigValueSets, $(RELEASE_DEVICE_CONFIG_VALUE_SETS))
+
$(call json_end)
$(file >$(SOONG_VARIABLES).tmp,$(json_contents))
diff --git a/core/tasks/collect_gpl_sources.mk b/core/tasks/collect_gpl_sources.mk
deleted file mode 100644
index 9e9ab8e..0000000
--- a/core/tasks/collect_gpl_sources.mk
+++ /dev/null
@@ -1,29 +0,0 @@
-# Copyright (C) 2011 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.
-
-# The rule below doesn't have dependenices on the files that it copies,
-# so manually generate into a PACKAGING intermediate dir, which is wiped
-# in installclean between incremental builds on build servers.
-gpl_source_tgz := $(call intermediates-dir-for,PACKAGING,gpl_source)/gpl_source.tgz
-
-ALL_GPL_MODULE_LICENSE_FILES := $(sort $(ALL_GPL_MODULE_LICENSE_FILES))
-
-# FORCE since we can't know whether any of the sources changed
-$(gpl_source_tgz): PRIVATE_PATHS := $(sort $(patsubst %/, %, $(dir $(ALL_GPL_MODULE_LICENSE_FILES))))
-$(gpl_source_tgz) : $(ALL_GPL_MODULE_LICENSE_FILES)
- @echo Package GPL sources: $@
- $(hide) tar cfz $@ --exclude ".git*" $(PRIVATE_PATHS)
-
-# Dist the tgz only if we are doing a full build
-$(call dist-for-goals,droidcore-unbundled,$(gpl_source_tgz))
diff --git a/core/tasks/tools/compatibility.mk b/core/tasks/tools/compatibility.mk
index c770b34..8ae2a9a 100644
--- a/core/tasks/tools/compatibility.mk
+++ b/core/tasks/tools/compatibility.mk
@@ -44,10 +44,16 @@
# The JDK to package into the test suite zip file. Always package the linux JDK.
test_suite_jdk_dir := $(ANDROID_JAVA_HOME)/../linux-x86
+ifndef test_suite_jdk_files
+ # This file gets included many times, so make sure we only run the $(shell) once.
+ # Otherwise it will slow down every build due to all copies of it being rerun when kati
+ # checks the stamp file.
+ test_suite_jdk_files :=$= $(shell find $(test_suite_jdk_dir) -type f | sort)
+endif
test_suite_jdk := $(call intermediates-dir-for,PACKAGING,$(test_suite_name)_jdk,HOST)/jdk.zip
$(test_suite_jdk): PRIVATE_JDK_DIR := $(test_suite_jdk_dir)
$(test_suite_jdk): PRIVATE_SUBDIR := $(test_suite_subdir)
-$(test_suite_jdk): $(shell find $(test_suite_jdk_dir) -type f | sort)
+$(test_suite_jdk): $(test_suite_jdk_files)
$(test_suite_jdk): $(SOONG_ZIP)
$(SOONG_ZIP) -o $@ -P $(PRIVATE_SUBDIR)/jdk -C $(PRIVATE_JDK_DIR) -D $(PRIVATE_JDK_DIR) -sha256
diff --git a/envsetup.sh b/envsetup.sh
index 916344c..ef48249 100644
--- a/envsetup.sh
+++ b/envsetup.sh
@@ -842,7 +842,7 @@
export TARGET_PRODUCT=$(get_build_var TARGET_PRODUCT)
export TARGET_BUILD_VARIANT=$(get_build_var TARGET_BUILD_VARIANT)
if [ -n "$release" ]; then
- export TARGET_RELEASE=$(get_build_var TARGET_RELEASE)
+ export TARGET_RELEASE=$release
else
unset TARGET_RELEASE
fi
diff --git a/target/product/base_system.mk b/target/product/base_system.mk
index 0f5b8a4..d65e5a4 100644
--- a/target/product/base_system.mk
+++ b/target/product/base_system.mk
@@ -226,7 +226,7 @@
mtpd \
ndc \
netd \
- NetworkStackNext \
+ NetworkStack \
odsign \
org.apache.http.legacy \
otacerts \
diff --git a/tools/aconfig/protos/aconfig.proto b/tools/aconfig/protos/aconfig.proto
index a3b1fec..9d36a9e 100644
--- a/tools/aconfig/protos/aconfig.proto
+++ b/tools/aconfig/protos/aconfig.proto
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License
-// This is the schema definition for of Aconfig files. Modifications need to be
-// either backwards compatible, or include updates to all Aconfig files in the
+// This is the schema definition for aconfig files. Modifications need to be
+// either backwards compatible, or include updates to all aconfig files in the
// Android tree.
syntax = "proto2";
@@ -32,40 +32,33 @@
READ_WRITE = 2;
}
-// aconfig input messages: configuration and override data
+// aconfig input messages: flag declarations and values
-message flag_value {
- required flag_state state = 1;
- required flag_permission permission = 2;
- optional uint32 since = 3;
-}
-
-message flag_definition {
+message flag_declaration {
required string name = 1;
required string description = 2;
- repeated flag_value value = 3;
};
-message namespace {
+message flag_declarations {
required string namespace = 1;
- repeated flag_definition flag = 2;
+ repeated flag_declaration flag = 2;
};
-message flag_override {
+message flag_value {
required string namespace = 1;
required string name = 2;
required flag_state state = 3;
required flag_permission permission = 4;
};
-message flag_overrides {
- repeated flag_override flag_override = 1;
+message flag_values {
+ repeated flag_value flag_value = 1;
};
-// aconfig output messages: parsed and verified configuration and override data
+// aconfig output messages: parsed and verified flag declarations and values
message tracepoint {
- // path to config or override file releative to $TOP
+ // path to declaration or value file relative to $TOP
required string source = 1;
required flag_state state = 2;
required flag_permission permission = 3;
diff --git a/tools/aconfig/src/aconfig.rs b/tools/aconfig/src/aconfig.rs
index 8fe82b6..b9fa324 100644
--- a/tools/aconfig/src/aconfig.rs
+++ b/tools/aconfig/src/aconfig.rs
@@ -14,15 +14,14 @@
* limitations under the License.
*/
-use anyhow::{anyhow, Context, Error, Result};
+use anyhow::{anyhow, bail, Context, Error, Result};
use protobuf::{Enum, EnumOrUnknown};
use serde::{Deserialize, Serialize};
use crate::cache::{Cache, Item, Tracepoint};
use crate::protos::{
- ProtoFlagDefinition, ProtoFlagDefinitionValue, ProtoFlagOverride, ProtoFlagOverrides,
- ProtoFlagPermission, ProtoFlagState, ProtoNamespace, ProtoParsedFlag, ProtoParsedFlags,
- ProtoTracepoint,
+ ProtoFlagDeclaration, ProtoFlagDeclarations, ProtoFlagPermission, ProtoFlagState,
+ ProtoFlagValue, ProtoFlagValues, ProtoParsedFlag, ProtoParsedFlags, ProtoTracepoint,
};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)]
@@ -80,164 +79,95 @@
}
#[derive(Debug, PartialEq, Eq)]
-pub struct Value {
- state: FlagState,
- permission: Permission,
- since: Option<u32>,
-}
-
-#[allow(dead_code)] // only used in unit tests
-impl Value {
- pub fn new(state: FlagState, permission: Permission, since: u32) -> Value {
- Value { state, permission, since: Some(since) }
- }
-
- pub fn default(state: FlagState, permission: Permission) -> Value {
- Value { state, permission, since: None }
- }
-}
-
-impl TryFrom<ProtoFlagDefinitionValue> for Value {
- type Error = Error;
-
- fn try_from(proto: ProtoFlagDefinitionValue) -> Result<Self, Self::Error> {
- let Some(proto_state) = proto.state else {
- return Err(anyhow!("missing 'state' field"));
- };
- let state = proto_state.try_into()?;
- let Some(proto_permission) = proto.permission else {
- return Err(anyhow!("missing 'permission' field"));
- };
- let permission = proto_permission.try_into()?;
- Ok(Value { state, permission, since: proto.since })
- }
-}
-
-#[derive(Debug, PartialEq, Eq)]
-pub struct Flag {
+pub struct FlagDeclaration {
pub name: String,
pub description: String,
-
- // ordered by Value.since; guaranteed to contain at least one item (the default value, with
- // since == None)
- pub values: Vec<Value>,
}
-impl Flag {
+impl FlagDeclaration {
#[allow(dead_code)] // only used in unit tests
- pub fn try_from_text_proto(text_proto: &str) -> Result<Flag> {
- let proto: ProtoFlagDefinition = crate::protos::try_from_text_proto(text_proto)
+ pub fn try_from_text_proto(text_proto: &str) -> Result<FlagDeclaration> {
+ let proto: ProtoFlagDeclaration = crate::protos::try_from_text_proto(text_proto)
.with_context(|| text_proto.to_owned())?;
proto.try_into()
}
-
- pub fn resolve(&self, build_id: u32) -> (FlagState, Permission) {
- let mut state = self.values[0].state;
- let mut permission = self.values[0].permission;
- for candidate in self.values.iter().skip(1) {
- let since = candidate.since.expect("invariant: non-defaults values have Some(since)");
- if since <= build_id {
- state = candidate.state;
- permission = candidate.permission;
- }
- }
- (state, permission)
- }
}
-impl TryFrom<ProtoFlagDefinition> for Flag {
+impl TryFrom<ProtoFlagDeclaration> for FlagDeclaration {
type Error = Error;
- fn try_from(proto: ProtoFlagDefinition) -> Result<Self, Self::Error> {
+ fn try_from(proto: ProtoFlagDeclaration) -> Result<Self, Self::Error> {
let Some(name) = proto.name else {
- return Err(anyhow!("missing 'name' field"));
+ bail!("missing 'name' field");
};
let Some(description) = proto.description else {
- return Err(anyhow!("missing 'description' field"));
+ bail!("missing 'description' field");
};
- if proto.value.is_empty() {
- return Err(anyhow!("missing 'value' field"));
- }
-
- let mut values: Vec<Value> = vec![];
- for proto_value in proto.value.into_iter() {
- let v: Value = proto_value.try_into()?;
- if values.iter().any(|w| v.since == w.since) {
- let msg = match v.since {
- None => format!("flag {}: multiple default values", name),
- Some(x) => format!("flag {}: multiple values for since={}", name, x),
- };
- return Err(anyhow!(msg));
- }
- values.push(v);
- }
- values.sort_by_key(|v| v.since);
-
- Ok(Flag { name, description, values })
+ Ok(FlagDeclaration { name, description })
}
}
#[derive(Debug, PartialEq, Eq)]
-pub struct Namespace {
+pub struct FlagDeclarations {
pub namespace: String,
- pub flags: Vec<Flag>,
+ pub flags: Vec<FlagDeclaration>,
}
-impl Namespace {
- pub fn try_from_text_proto(text_proto: &str) -> Result<Namespace> {
- let proto: ProtoNamespace = crate::protos::try_from_text_proto(text_proto)
+impl FlagDeclarations {
+ pub fn try_from_text_proto(text_proto: &str) -> Result<FlagDeclarations> {
+ let proto: ProtoFlagDeclarations = crate::protos::try_from_text_proto(text_proto)
.with_context(|| text_proto.to_owned())?;
let Some(namespace) = proto.namespace else {
- return Err(anyhow!("missing 'namespace' field"));
+ bail!("missing 'namespace' field");
};
let mut flags = vec![];
for proto_flag in proto.flag.into_iter() {
flags.push(proto_flag.try_into()?);
}
- Ok(Namespace { namespace, flags })
+ Ok(FlagDeclarations { namespace, flags })
}
}
#[derive(Debug, PartialEq, Eq)]
-pub struct Override {
+pub struct FlagValue {
pub namespace: String,
pub name: String,
pub state: FlagState,
pub permission: Permission,
}
-impl Override {
+impl FlagValue {
#[allow(dead_code)] // only used in unit tests
- pub fn try_from_text_proto(text_proto: &str) -> Result<Override> {
- let proto: ProtoFlagOverride = crate::protos::try_from_text_proto(text_proto)?;
+ pub fn try_from_text_proto(text_proto: &str) -> Result<FlagValue> {
+ let proto: ProtoFlagValue = crate::protos::try_from_text_proto(text_proto)?;
proto.try_into()
}
- pub fn try_from_text_proto_list(text_proto: &str) -> Result<Vec<Override>> {
- let proto: ProtoFlagOverrides = crate::protos::try_from_text_proto(text_proto)?;
- proto.flag_override.into_iter().map(|proto_flag| proto_flag.try_into()).collect()
+ pub fn try_from_text_proto_list(text_proto: &str) -> Result<Vec<FlagValue>> {
+ let proto: ProtoFlagValues = crate::protos::try_from_text_proto(text_proto)?;
+ proto.flag_value.into_iter().map(|proto_flag| proto_flag.try_into()).collect()
}
}
-impl TryFrom<ProtoFlagOverride> for Override {
+impl TryFrom<ProtoFlagValue> for FlagValue {
type Error = Error;
- fn try_from(proto: ProtoFlagOverride) -> Result<Self, Self::Error> {
+ fn try_from(proto: ProtoFlagValue) -> Result<Self, Self::Error> {
let Some(namespace) = proto.namespace else {
- return Err(anyhow!("missing 'namespace' field"));
+ bail!("missing 'namespace' field");
};
let Some(name) = proto.name else {
- return Err(anyhow!("missing 'name' field"));
+ bail!("missing 'name' field");
};
let Some(proto_state) = proto.state else {
- return Err(anyhow!("missing 'state' field"));
+ bail!("missing 'state' field");
};
let state = proto_state.try_into()?;
let Some(proto_permission) = proto.permission else {
- return Err(anyhow!("missing 'permission' field"));
+ bail!("missing 'permission' field");
};
let permission = proto_permission.try_into()?;
- Ok(Override { namespace, name, state, permission })
+ Ok(FlagValue { namespace, name, state, permission })
}
}
@@ -282,29 +212,16 @@
#[test]
fn test_flag_try_from_text_proto() {
- let expected = Flag {
+ let expected = FlagDeclaration {
name: "1234".to_owned(),
description: "Description of the flag".to_owned(),
- values: vec![
- Value::default(FlagState::Disabled, Permission::ReadOnly),
- Value::new(FlagState::Enabled, Permission::ReadWrite, 8),
- ],
};
let s = r#"
name: "1234"
description: "Description of the flag"
- value {
- state: DISABLED
- permission: READ_ONLY
- }
- value {
- state: ENABLED
- permission: READ_WRITE
- since: 8
- }
"#;
- let actual = Flag::try_from_text_proto(s).unwrap();
+ let actual = FlagDeclaration::try_from_text_proto(s).unwrap();
assert_eq!(expected, actual);
}
@@ -313,52 +230,24 @@
fn test_flag_try_from_text_proto_bad_input() {
let s = r#"
name: "a"
- description: "Description of the flag"
"#;
- let error = Flag::try_from_text_proto(s).unwrap_err();
- assert_eq!(format!("{:?}", error), "missing 'value' field");
-
- let s = r#"
- description: "Description of the flag"
- value {
- state: ENABLED
- permission: READ_ONLY
- }
- "#;
- let error = Flag::try_from_text_proto(s).unwrap_err();
+ let error = FlagDeclaration::try_from_text_proto(s).unwrap_err();
assert!(format!("{:?}", error).contains("Message not initialized"));
let s = r#"
- name: "a"
description: "Description of the flag"
- value {
- state: ENABLED
- permission: READ_ONLY
- }
- value {
- state: ENABLED
- permission: READ_ONLY
- }
"#;
- let error = Flag::try_from_text_proto(s).unwrap_err();
- assert_eq!(format!("{:?}", error), "flag a: multiple default values");
+ let error = FlagDeclaration::try_from_text_proto(s).unwrap_err();
+ assert!(format!("{:?}", error).contains("Message not initialized"));
}
#[test]
fn test_namespace_try_from_text_proto() {
- let expected = Namespace {
+ let expected = FlagDeclarations {
namespace: "ns".to_owned(),
flags: vec![
- Flag {
- name: "a".to_owned(),
- description: "A".to_owned(),
- values: vec![Value::default(FlagState::Enabled, Permission::ReadOnly)],
- },
- Flag {
- name: "b".to_owned(),
- description: "B".to_owned(),
- values: vec![Value::default(FlagState::Disabled, Permission::ReadWrite)],
- },
+ FlagDeclaration { name: "a".to_owned(), description: "A".to_owned() },
+ FlagDeclaration { name: "b".to_owned(), description: "B".to_owned() },
],
};
@@ -367,28 +256,20 @@
flag {
name: "a"
description: "A"
- value {
- state: ENABLED
- permission: READ_ONLY
- }
}
flag {
name: "b"
description: "B"
- value {
- state: DISABLED
- permission: READ_WRITE
- }
}
"#;
- let actual = Namespace::try_from_text_proto(s).unwrap();
+ let actual = FlagDeclarations::try_from_text_proto(s).unwrap();
assert_eq!(expected, actual);
}
#[test]
- fn test_override_try_from_text_proto_list() {
- let expected = Override {
+ fn test_flag_declaration_try_from_text_proto_list() {
+ let expected = FlagValue {
namespace: "ns".to_owned(),
name: "1234".to_owned(),
state: FlagState::Enabled,
@@ -401,32 +282,8 @@
state: ENABLED
permission: READ_ONLY
"#;
- let actual = Override::try_from_text_proto(s).unwrap();
+ let actual = FlagValue::try_from_text_proto(s).unwrap();
assert_eq!(expected, actual);
}
-
- #[test]
- fn test_flag_resolve() {
- let flag = Flag {
- name: "a".to_owned(),
- description: "A".to_owned(),
- values: vec![
- Value::default(FlagState::Disabled, Permission::ReadOnly),
- Value::new(FlagState::Disabled, Permission::ReadWrite, 10),
- Value::new(FlagState::Enabled, Permission::ReadOnly, 20),
- Value::new(FlagState::Enabled, Permission::ReadWrite, 30),
- ],
- };
- assert_eq!((FlagState::Disabled, Permission::ReadOnly), flag.resolve(0));
- assert_eq!((FlagState::Disabled, Permission::ReadOnly), flag.resolve(9));
- assert_eq!((FlagState::Disabled, Permission::ReadWrite), flag.resolve(10));
- assert_eq!((FlagState::Disabled, Permission::ReadWrite), flag.resolve(11));
- assert_eq!((FlagState::Disabled, Permission::ReadWrite), flag.resolve(19));
- assert_eq!((FlagState::Enabled, Permission::ReadOnly), flag.resolve(20));
- assert_eq!((FlagState::Enabled, Permission::ReadOnly), flag.resolve(21));
- assert_eq!((FlagState::Enabled, Permission::ReadOnly), flag.resolve(29));
- assert_eq!((FlagState::Enabled, Permission::ReadWrite), flag.resolve(30));
- assert_eq!((FlagState::Enabled, Permission::ReadWrite), flag.resolve(10_000));
- }
}
diff --git a/tools/aconfig/src/cache.rs b/tools/aconfig/src/cache.rs
index 4b46c42..c546f7b 100644
--- a/tools/aconfig/src/cache.rs
+++ b/tools/aconfig/src/cache.rs
@@ -14,13 +14,16 @@
* limitations under the License.
*/
-use anyhow::{anyhow, Result};
+use anyhow::{bail, ensure, Result};
use serde::{Deserialize, Serialize};
use std::io::{Read, Write};
-use crate::aconfig::{Flag, FlagState, Override, Permission};
+use crate::aconfig::{FlagDeclaration, FlagState, FlagValue, Permission};
use crate::commands::Source;
+const DEFAULT_FLAG_STATE: FlagState = FlagState::Disabled;
+const DEFAULT_FLAG_PERMISSION: Permission = Permission::ReadWrite;
+
#[derive(Serialize, Deserialize, Debug)]
pub struct Tracepoint {
pub source: Source,
@@ -44,14 +47,14 @@
#[derive(Serialize, Deserialize, Debug)]
pub struct Cache {
- build_id: u32,
namespace: String,
items: Vec<Item>,
}
impl Cache {
- pub fn new(build_id: u32, namespace: String) -> Cache {
- Cache { build_id, namespace, items: vec![] }
+ pub fn new(namespace: String) -> Result<Cache> {
+ ensure!(!namespace.is_empty(), "empty namespace");
+ Ok(Cache { namespace, items: vec![] })
}
pub fn read_from_reader(reader: impl Read) -> Result<Cache> {
@@ -62,40 +65,54 @@
serde_json::to_writer(writer, self).map_err(|e| e.into())
}
- pub fn add_flag(&mut self, source: Source, flag: Flag) -> Result<()> {
- if self.items.iter().any(|item| item.name == flag.name) {
- return Err(anyhow!(
- "failed to add flag {} from {}: flag already defined",
- flag.name,
- source,
- ));
- }
- let (state, permission) = flag.resolve(self.build_id);
+ pub fn add_flag_declaration(
+ &mut self,
+ source: Source,
+ declaration: FlagDeclaration,
+ ) -> Result<()> {
+ ensure!(!declaration.name.is_empty(), "empty flag name");
+ ensure!(!declaration.description.is_empty(), "empty flag description");
+ ensure!(
+ self.items.iter().all(|item| item.name != declaration.name),
+ "failed to declare flag {} from {}: flag already declared",
+ declaration.name,
+ source
+ );
self.items.push(Item {
namespace: self.namespace.clone(),
- name: flag.name.clone(),
- description: flag.description,
- state,
- permission,
- trace: vec![Tracepoint { source, state, permission }],
+ name: declaration.name.clone(),
+ description: declaration.description,
+ state: DEFAULT_FLAG_STATE,
+ permission: DEFAULT_FLAG_PERMISSION,
+ trace: vec![Tracepoint {
+ source,
+ state: DEFAULT_FLAG_STATE,
+ permission: DEFAULT_FLAG_PERMISSION,
+ }],
});
Ok(())
}
- pub fn add_override(&mut self, source: Source, override_: Override) -> Result<()> {
- if override_.namespace != self.namespace {
- // TODO: print warning?
- return Ok(());
- }
- let Some(existing_item) = self.items.iter_mut().find(|item| item.name == override_.name) else {
- return Err(anyhow!("failed to override flag {}: unknown flag", override_.name));
+ pub fn add_flag_value(&mut self, source: Source, value: FlagValue) -> Result<()> {
+ ensure!(!value.namespace.is_empty(), "empty flag namespace");
+ ensure!(!value.name.is_empty(), "empty flag name");
+ ensure!(
+ value.namespace == self.namespace,
+ "failed to set values for flag {}/{} from {}: expected namespace {}",
+ value.namespace,
+ value.name,
+ source,
+ self.namespace
+ );
+ let Some(existing_item) = self.items.iter_mut().find(|item| item.name == value.name) else {
+ bail!("failed to set values for flag {}/{} from {}: flag not declared", value.namespace, value.name, source);
};
- existing_item.state = override_.state;
- existing_item.permission = override_.permission;
+ existing_item.state = value.state;
+ existing_item.permission = value.permission;
existing_item.trace.push(Tracepoint {
source,
- state: override_.state,
- permission: override_.permission,
+ state: value.state,
+ permission: value.permission,
});
Ok(())
}
@@ -107,54 +124,51 @@
pub fn into_iter(self) -> impl Iterator<Item = Item> {
self.items.into_iter()
}
+
+ pub fn namespace(&self) -> &str {
+ debug_assert!(!self.namespace.is_empty());
+ &self.namespace
+ }
}
#[cfg(test)]
mod tests {
use super::*;
- use crate::aconfig::{FlagState, Permission, Value};
+ use crate::aconfig::{FlagState, Permission};
#[test]
- fn test_add_flag() {
- let mut cache = Cache::new(1, "ns".to_string());
+ fn test_add_flag_declaration() {
+ let mut cache = Cache::new("ns".to_string()).unwrap();
cache
- .add_flag(
+ .add_flag_declaration(
Source::File("first.txt".to_string()),
- Flag {
- name: "foo".to_string(),
- description: "desc".to_string(),
- values: vec![Value::default(FlagState::Enabled, Permission::ReadOnly)],
- },
+ FlagDeclaration { name: "foo".to_string(), description: "desc".to_string() },
)
.unwrap();
let error = cache
- .add_flag(
+ .add_flag_declaration(
Source::File("second.txt".to_string()),
- Flag {
- name: "foo".to_string(),
- description: "desc".to_string(),
- values: vec![Value::default(FlagState::Disabled, Permission::ReadOnly)],
- },
+ FlagDeclaration { name: "foo".to_string(), description: "desc".to_string() },
)
.unwrap_err();
assert_eq!(
&format!("{:?}", error),
- "failed to add flag foo from second.txt: flag already defined"
+ "failed to declare flag foo from second.txt: flag already declared"
);
}
#[test]
- fn test_add_override() {
+ fn test_add_flag_value() {
fn check(cache: &Cache, name: &str, expected: (FlagState, Permission)) -> bool {
let item = cache.iter().find(|&item| item.name == name).unwrap();
item.state == expected.0 && item.permission == expected.1
}
- let mut cache = Cache::new(1, "ns".to_string());
+ let mut cache = Cache::new("ns".to_string()).unwrap();
let error = cache
- .add_override(
+ .add_flag_value(
Source::Memory,
- Override {
+ FlagValue {
namespace: "ns".to_string(),
name: "foo".to_string(),
state: FlagState::Enabled,
@@ -162,39 +176,36 @@
},
)
.unwrap_err();
- assert_eq!(&format!("{:?}", error), "failed to override flag foo: unknown flag");
+ assert_eq!(
+ &format!("{:?}", error),
+ "failed to set values for flag ns/foo from <memory>: flag not declared"
+ );
cache
- .add_flag(
+ .add_flag_declaration(
Source::File("first.txt".to_string()),
- Flag {
- name: "foo".to_string(),
- description: "desc".to_string(),
- values: vec![Value::default(FlagState::Enabled, Permission::ReadOnly)],
- },
+ FlagDeclaration { name: "foo".to_string(), description: "desc".to_string() },
)
.unwrap();
- dbg!(&cache);
- assert!(check(&cache, "foo", (FlagState::Enabled, Permission::ReadOnly)));
+ assert!(check(&cache, "foo", (DEFAULT_FLAG_STATE, DEFAULT_FLAG_PERMISSION)));
cache
- .add_override(
+ .add_flag_value(
Source::Memory,
- Override {
+ FlagValue {
namespace: "ns".to_string(),
name: "foo".to_string(),
state: FlagState::Disabled,
- permission: Permission::ReadWrite,
+ permission: Permission::ReadOnly,
},
)
.unwrap();
- dbg!(&cache);
- assert!(check(&cache, "foo", (FlagState::Disabled, Permission::ReadWrite)));
+ assert!(check(&cache, "foo", (FlagState::Disabled, Permission::ReadOnly)));
cache
- .add_override(
+ .add_flag_value(
Source::Memory,
- Override {
+ FlagValue {
namespace: "ns".to_string(),
name: "foo".to_string(),
state: FlagState::Enabled,
@@ -205,17 +216,81 @@
assert!(check(&cache, "foo", (FlagState::Enabled, Permission::ReadWrite)));
// different namespace -> no-op
- cache
- .add_override(
+ let error = cache
+ .add_flag_value(
Source::Memory,
- Override {
+ FlagValue {
namespace: "some-other-namespace".to_string(),
name: "foo".to_string(),
state: FlagState::Enabled,
permission: Permission::ReadOnly,
},
)
- .unwrap();
+ .unwrap_err();
+ assert_eq!(&format!("{:?}", error), "failed to set values for flag some-other-namespace/foo from <memory>: expected namespace ns");
assert!(check(&cache, "foo", (FlagState::Enabled, Permission::ReadWrite)));
}
+
+ #[test]
+ fn test_reject_empty_cache_namespace() {
+ Cache::new("".to_string()).unwrap_err();
+ }
+
+ #[test]
+ fn test_reject_empty_flag_declaration_fields() {
+ let mut cache = Cache::new("ns".to_string()).unwrap();
+
+ let error = cache
+ .add_flag_declaration(
+ Source::Memory,
+ FlagDeclaration { name: "".to_string(), description: "Description".to_string() },
+ )
+ .unwrap_err();
+ assert_eq!(&format!("{:?}", error), "empty flag name");
+
+ let error = cache
+ .add_flag_declaration(
+ Source::Memory,
+ FlagDeclaration { name: "foo".to_string(), description: "".to_string() },
+ )
+ .unwrap_err();
+ assert_eq!(&format!("{:?}", error), "empty flag description");
+ }
+
+ #[test]
+ fn test_reject_empty_flag_value_files() {
+ let mut cache = Cache::new("ns".to_string()).unwrap();
+ cache
+ .add_flag_declaration(
+ Source::Memory,
+ FlagDeclaration { name: "foo".to_string(), description: "desc".to_string() },
+ )
+ .unwrap();
+
+ let error = cache
+ .add_flag_value(
+ Source::Memory,
+ FlagValue {
+ namespace: "".to_string(),
+ name: "foo".to_string(),
+ state: FlagState::Enabled,
+ permission: Permission::ReadOnly,
+ },
+ )
+ .unwrap_err();
+ assert_eq!(&format!("{:?}", error), "empty flag namespace");
+
+ let error = cache
+ .add_flag_value(
+ Source::Memory,
+ FlagValue {
+ namespace: "ns".to_string(),
+ name: "".to_string(),
+ state: FlagState::Enabled,
+ permission: Permission::ReadOnly,
+ },
+ )
+ .unwrap_err();
+ assert_eq!(&format!("{:?}", error), "empty flag name");
+ }
}
diff --git a/tools/aconfig/src/codegen_java.rs b/tools/aconfig/src/codegen_java.rs
index 9d52cce..476a89d 100644
--- a/tools/aconfig/src/codegen_java.rs
+++ b/tools/aconfig/src/codegen_java.rs
@@ -16,27 +16,25 @@
use anyhow::Result;
use serde::Serialize;
+use std::path::PathBuf;
use tinytemplate::TinyTemplate;
use crate::aconfig::{FlagState, Permission};
use crate::cache::{Cache, Item};
+use crate::commands::OutputFile;
-pub struct GeneratedFile {
- pub file_content: String,
- pub file_name: String,
-}
-
-pub fn generate_java_code(cache: &Cache) -> Result<GeneratedFile> {
+pub fn generate_java_code(cache: &Cache) -> Result<OutputFile> {
let class_elements: Vec<ClassElement> = cache.iter().map(create_class_element).collect();
let readwrite = class_elements.iter().any(|item| item.readwrite);
- let namespace = uppercase_first_letter(
- cache.iter().find(|item| !item.namespace.is_empty()).unwrap().namespace.as_str(),
- );
- let context = Context { namespace: namespace.clone(), readwrite, class_elements };
+ let namespace = cache.namespace();
+ let context = Context { namespace: namespace.to_string(), readwrite, class_elements };
let mut template = TinyTemplate::new();
template.add_template("java_code_gen", include_str!("../templates/java.template"))?;
- let file_content = template.render("java_code_gen", &context)?;
- Ok(GeneratedFile { file_content, file_name: format!("{}.java", namespace) })
+ let contents = template.render("java_code_gen", &context)?;
+ let mut path: PathBuf = namespace.split('.').collect();
+ // TODO: Allow customization of the java class name
+ path.push("Flags.java");
+ Ok(OutputFile { contents: contents.into(), path })
}
#[derive(Serialize)]
@@ -69,74 +67,70 @@
}
}
-fn uppercase_first_letter(s: &str) -> String {
- s.chars()
- .enumerate()
- .map(
- |(index, ch)| {
- if index == 0 {
- ch.to_ascii_uppercase()
- } else {
- ch.to_ascii_lowercase()
- }
- },
- )
- .collect()
-}
-
#[cfg(test)]
mod tests {
use super::*;
- use crate::aconfig::{Flag, Value};
+ use crate::aconfig::{FlagDeclaration, FlagValue};
use crate::commands::Source;
#[test]
fn test_generate_java_code() {
- let namespace = "TeSTFlaG";
- let mut cache = Cache::new(1, namespace.to_string());
+ let namespace = "com.example";
+ let mut cache = Cache::new(namespace.to_string()).unwrap();
cache
- .add_flag(
+ .add_flag_declaration(
Source::File("test.txt".to_string()),
- Flag {
+ FlagDeclaration {
name: "test".to_string(),
description: "buildtime enable".to_string(),
- values: vec![Value::default(FlagState::Enabled, Permission::ReadOnly)],
},
)
.unwrap();
cache
- .add_flag(
+ .add_flag_declaration(
Source::File("test2.txt".to_string()),
- Flag {
+ FlagDeclaration {
name: "test2".to_string(),
description: "runtime disable".to_string(),
- values: vec![Value::default(FlagState::Disabled, Permission::ReadWrite)],
},
)
.unwrap();
- let expect_content = "package com.android.aconfig;
+ cache
+ .add_flag_value(
+ Source::Memory,
+ FlagValue {
+ namespace: namespace.to_string(),
+ name: "test".to_string(),
+ state: FlagState::Disabled,
+ permission: Permission::ReadOnly,
+ },
+ )
+ .unwrap();
+ let expect_content = r#"package com.example;
import android.provider.DeviceConfig;
- public final class Testflag {
+ public final class Flags {
public static boolean test() {
- return true;
+ return false;
}
public static boolean test2() {
return DeviceConfig.getBoolean(
- \"Testflag\",
- \"test2__test2\",
+ "com.example",
+ "test2__test2",
false
);
}
}
- ";
- let expected_file_name = format!("{}.java", uppercase_first_letter(namespace));
- let generated_file = generate_java_code(&cache).unwrap();
- assert_eq!(expected_file_name, generated_file.file_name);
- assert_eq!(expect_content.replace(' ', ""), generated_file.file_content.replace(' ', ""));
+ "#;
+ let file = generate_java_code(&cache).unwrap();
+ assert_eq!("com/example/Flags.java", file.path.to_str().unwrap());
+ assert_eq!(
+ expect_content.replace(' ', ""),
+ String::from_utf8(file.contents).unwrap().replace(' ', "")
+ );
}
}
diff --git a/tools/aconfig/src/commands.rs b/tools/aconfig/src/commands.rs
index 1487e72..324f7d5 100644
--- a/tools/aconfig/src/commands.rs
+++ b/tools/aconfig/src/commands.rs
@@ -20,10 +20,11 @@
use serde::{Deserialize, Serialize};
use std::fmt;
use std::io::Read;
+use std::path::PathBuf;
-use crate::aconfig::{Namespace, Override};
+use crate::aconfig::{FlagDeclarations, FlagValue};
use crate::cache::Cache;
-use crate::codegen_java::{generate_java_code, GeneratedFile};
+use crate::codegen_java::generate_java_code;
use crate::protos::ProtoParsedFlags;
#[derive(Serialize, Deserialize, Clone, Debug)]
@@ -47,72 +48,77 @@
pub reader: Box<dyn Read>,
}
-pub fn create_cache(
- build_id: u32,
- namespace: &str,
- aconfigs: Vec<Input>,
- overrides: Vec<Input>,
-) -> Result<Cache> {
- let mut cache = Cache::new(build_id, namespace.to_owned());
+pub struct OutputFile {
+ pub path: PathBuf, // relative to some root directory only main knows about
+ pub contents: Vec<u8>,
+}
- for mut input in aconfigs {
+pub fn create_cache(
+ namespace: &str,
+ declarations: Vec<Input>,
+ values: Vec<Input>,
+) -> Result<Cache> {
+ let mut cache = Cache::new(namespace.to_owned())?;
+
+ for mut input in declarations {
let mut contents = String::new();
input.reader.read_to_string(&mut contents)?;
- let ns = Namespace::try_from_text_proto(&contents)
+ let dec_list = FlagDeclarations::try_from_text_proto(&contents)
.with_context(|| format!("Failed to parse {}", input.source))?;
ensure!(
- namespace == ns.namespace,
+ namespace == dec_list.namespace,
"Failed to parse {}: expected namespace {}, got {}",
input.source,
namespace,
- ns.namespace
+ dec_list.namespace
);
- for flag in ns.flags.into_iter() {
- cache.add_flag(input.source.clone(), flag)?;
+ for d in dec_list.flags.into_iter() {
+ cache.add_flag_declaration(input.source.clone(), d)?;
}
}
- for mut input in overrides {
+ for mut input in values {
let mut contents = String::new();
input.reader.read_to_string(&mut contents)?;
- let overrides = Override::try_from_text_proto_list(&contents)
+ let values_list = FlagValue::try_from_text_proto_list(&contents)
.with_context(|| format!("Failed to parse {}", input.source))?;
- for override_ in overrides {
- cache.add_override(input.source.clone(), override_)?;
+ for v in values_list {
+ // TODO: warn about flag values that do not take effect?
+ let _ = cache.add_flag_value(input.source.clone(), v);
}
}
Ok(cache)
}
-pub fn generate_code(cache: &Cache) -> Result<GeneratedFile> {
+pub fn generate_code(cache: &Cache) -> Result<OutputFile> {
generate_java_code(cache)
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)]
-pub enum Format {
+pub enum DumpFormat {
Text,
Debug,
Protobuf,
}
-pub fn dump_cache(cache: Cache, format: Format) -> Result<Vec<u8>> {
+pub fn dump_cache(cache: Cache, format: DumpFormat) -> Result<Vec<u8>> {
match format {
- Format::Text => {
+ DumpFormat::Text => {
let mut lines = vec![];
for item in cache.iter() {
lines.push(format!("{}: {:?}\n", item.name, item.state));
}
Ok(lines.concat().into())
}
- Format::Debug => {
+ DumpFormat::Debug => {
let mut lines = vec![];
for item in cache.iter() {
lines.push(format!("{:?}\n", item));
}
Ok(lines.concat().into())
}
- Format::Protobuf => {
+ DumpFormat::Protobuf => {
let parsed_flags: ProtoParsedFlags = cache.into();
let mut output = vec![];
parsed_flags.write_to_vec(&mut output)?;
@@ -132,31 +138,23 @@
flag {
name: "a"
description: "Description of a"
- value {
- state: ENABLED
- permission: READ_WRITE
- }
}
flag {
name: "b"
description: "Description of b"
- value {
- state: ENABLED
- permission: READ_ONLY
- }
}
"#;
- let aconfigs = vec![Input { source: Source::Memory, reader: Box::new(s.as_bytes()) }];
+ let declarations = vec![Input { source: Source::Memory, reader: Box::new(s.as_bytes()) }];
let o = r#"
- flag_override {
+ flag_value {
namespace: "ns"
name: "a"
state: DISABLED
permission: READ_ONLY
}
"#;
- let overrides = vec![Input { source: Source::Memory, reader: Box::new(o.as_bytes()) }];
- create_cache(1, "ns", aconfigs, overrides).unwrap()
+ let values = vec![Input { source: Source::Memory, reader: Box::new(o.as_bytes()) }];
+ create_cache("ns", declarations, values).unwrap()
}
#[test]
@@ -170,7 +168,7 @@
#[test]
fn test_dump_text_format() {
let cache = create_test_cache();
- let bytes = dump_cache(cache, Format::Text).unwrap();
+ let bytes = dump_cache(cache, DumpFormat::Text).unwrap();
let text = std::str::from_utf8(&bytes).unwrap();
assert!(text.contains("a: Disabled"));
}
@@ -181,7 +179,7 @@
use protobuf::Message;
let cache = create_test_cache();
- let bytes = dump_cache(cache, Format::Protobuf).unwrap();
+ let bytes = dump_cache(cache, DumpFormat::Protobuf).unwrap();
let actual = ProtoParsedFlags::parse_from_bytes(&bytes).unwrap();
assert_eq!(
@@ -194,12 +192,12 @@
assert_eq!(item.namespace(), "ns");
assert_eq!(item.name(), "b");
assert_eq!(item.description(), "Description of b");
- assert_eq!(item.state(), ProtoFlagState::ENABLED);
- assert_eq!(item.permission(), ProtoFlagPermission::READ_ONLY);
+ assert_eq!(item.state(), ProtoFlagState::DISABLED);
+ assert_eq!(item.permission(), ProtoFlagPermission::READ_WRITE);
let mut tp = ProtoTracepoint::new();
tp.set_source("<memory>".to_string());
- tp.set_state(ProtoFlagState::ENABLED);
- tp.set_permission(ProtoFlagPermission::READ_ONLY);
+ tp.set_state(ProtoFlagState::DISABLED);
+ tp.set_permission(ProtoFlagPermission::READ_WRITE);
assert_eq!(item.trace, vec![tp]);
}
}
diff --git a/tools/aconfig/src/main.rs b/tools/aconfig/src/main.rs
index f29186a..e1e9166 100644
--- a/tools/aconfig/src/main.rs
+++ b/tools/aconfig/src/main.rs
@@ -16,11 +16,13 @@
//! `aconfig` is a build time tool to manage build time configurations, such as feature flags.
-use anyhow::Result;
+use anyhow::{anyhow, ensure, Result};
use clap::{builder::ArgAction, builder::EnumValueParser, Arg, ArgMatches, Command};
+use core::any::Any;
use std::fs;
use std::io;
use std::io::Write;
+use std::path::{Path, PathBuf};
mod aconfig;
mod cache;
@@ -29,22 +31,16 @@
mod protos;
use crate::cache::Cache;
-use commands::{Input, Source};
+use commands::{DumpFormat, Input, OutputFile, Source};
fn cli() -> Command {
Command::new("aconfig")
.subcommand_required(true)
.subcommand(
Command::new("create-cache")
- .arg(
- Arg::new("build-id")
- .long("build-id")
- .value_parser(clap::value_parser!(u32))
- .required(true),
- )
.arg(Arg::new("namespace").long("namespace").required(true))
- .arg(Arg::new("aconfig").long("aconfig").action(ArgAction::Append))
- .arg(Arg::new("override").long("override").action(ArgAction::Append))
+ .arg(Arg::new("declarations").long("declarations").action(ArgAction::Append))
+ .arg(Arg::new("values").long("values").action(ArgAction::Append))
.arg(Arg::new("cache").long("cache").required(true)),
)
.subcommand(
@@ -58,13 +54,22 @@
.arg(
Arg::new("format")
.long("format")
- .value_parser(EnumValueParser::<commands::Format>::new())
+ .value_parser(EnumValueParser::<commands::DumpFormat>::new())
.default_value("text"),
)
.arg(Arg::new("out").long("out").default_value("-")),
)
}
+fn get_required_arg<'a, T>(matches: &'a ArgMatches, arg_name: &str) -> Result<&'a T>
+where
+ T: Any + Clone + Send + Sync + 'static,
+{
+ matches
+ .get_one::<T>(arg_name)
+ .ok_or(anyhow!("internal error: required argument '{}' not found", arg_name))
+}
+
fn open_zero_or_more_files(matches: &ArgMatches, arg_name: &str) -> Result<Vec<Input>> {
let mut opened_files = vec![];
for path in matches.get_many::<String>(arg_name).unwrap_or_default() {
@@ -74,38 +79,50 @@
Ok(opened_files)
}
+fn write_output_file_realtive_to_dir(root: &Path, output_file: &OutputFile) -> Result<()> {
+ ensure!(
+ root.is_dir(),
+ "output directory {} does not exist or is not a directory",
+ root.display()
+ );
+ let path = root.join(output_file.path.clone());
+ let parent = path
+ .parent()
+ .ok_or(anyhow!("unable to locate parent of output file {}", path.display()))?;
+ fs::create_dir_all(parent)?;
+ let mut file = fs::File::create(path)?;
+ file.write_all(&output_file.contents)?;
+ Ok(())
+}
+
fn main() -> Result<()> {
let matches = cli().get_matches();
match matches.subcommand() {
Some(("create-cache", sub_matches)) => {
- let build_id = *sub_matches.get_one::<u32>("build-id").unwrap();
- let namespace = sub_matches.get_one::<String>("namespace").unwrap();
- let aconfigs = open_zero_or_more_files(sub_matches, "aconfig")?;
- let overrides = open_zero_or_more_files(sub_matches, "override")?;
- let cache = commands::create_cache(build_id, namespace, aconfigs, overrides)?;
- let path = sub_matches.get_one::<String>("cache").unwrap();
+ let namespace = get_required_arg::<String>(sub_matches, "namespace")?;
+ let declarations = open_zero_or_more_files(sub_matches, "declarations")?;
+ let values = open_zero_or_more_files(sub_matches, "values")?;
+ let cache = commands::create_cache(namespace, declarations, values)?;
+ let path = get_required_arg::<String>(sub_matches, "cache")?;
let file = fs::File::create(path)?;
cache.write_to_writer(file)?;
}
Some(("create-java-lib", sub_matches)) => {
- let path = sub_matches.get_one::<String>("cache").unwrap();
+ let path = get_required_arg::<String>(sub_matches, "cache")?;
let file = fs::File::open(path)?;
let cache = Cache::read_from_reader(file)?;
- let out = sub_matches.get_one::<String>("out").unwrap();
- let generated_file = commands::generate_code(&cache).unwrap();
- fs::write(
- format!("{}/{}", out, generated_file.file_name),
- generated_file.file_content,
- )?;
+ let dir = PathBuf::from(get_required_arg::<String>(sub_matches, "out")?);
+ let generated_file = commands::generate_code(&cache)?;
+ write_output_file_realtive_to_dir(&dir, &generated_file)?;
}
Some(("dump", sub_matches)) => {
- let path = sub_matches.get_one::<String>("cache").unwrap();
+ let path = get_required_arg::<String>(sub_matches, "cache")?;
let file = fs::File::open(path)?;
let cache = Cache::read_from_reader(file)?;
- let format = sub_matches.get_one("format").unwrap();
+ let format = get_required_arg::<DumpFormat>(sub_matches, "format")?;
let output = commands::dump_cache(cache, *format)?;
- let path = sub_matches.get_one::<String>("out").unwrap();
- let mut file: Box<dyn Write> = if path == "-" {
+ let path = get_required_arg::<String>(sub_matches, "out")?;
+ let mut file: Box<dyn Write> = if *path == "-" {
Box::new(io::stdout())
} else {
Box::new(fs::File::create(path)?)
diff --git a/tools/aconfig/src/protos.rs b/tools/aconfig/src/protos.rs
index 5965a09..cb75692 100644
--- a/tools/aconfig/src/protos.rs
+++ b/tools/aconfig/src/protos.rs
@@ -28,19 +28,16 @@
// ---- When building with the Android tool-chain ----
#[cfg(not(feature = "cargo"))]
-pub use aconfig_protos::aconfig::Namespace as ProtoNamespace;
+pub use aconfig_protos::aconfig::Flag_declaration as ProtoFlagDeclaration;
#[cfg(not(feature = "cargo"))]
-pub use aconfig_protos::aconfig::Flag_value as ProtoFlagDefinitionValue;
+pub use aconfig_protos::aconfig::Flag_declarations as ProtoFlagDeclarations;
#[cfg(not(feature = "cargo"))]
-pub use aconfig_protos::aconfig::Flag_definition as ProtoFlagDefinition;
+pub use aconfig_protos::aconfig::Flag_value as ProtoFlagValue;
#[cfg(not(feature = "cargo"))]
-pub use aconfig_protos::aconfig::Flag_overrides as ProtoFlagOverrides;
-
-#[cfg(not(feature = "cargo"))]
-pub use aconfig_protos::aconfig::Flag_override as ProtoFlagOverride;
+pub use aconfig_protos::aconfig::Flag_values as ProtoFlagValues;
#[cfg(not(feature = "cargo"))]
pub use aconfig_protos::aconfig::Flag_permission as ProtoFlagPermission;
@@ -62,19 +59,16 @@
include!(concat!(env!("OUT_DIR"), "/aconfig_proto/mod.rs"));
#[cfg(feature = "cargo")]
-pub use aconfig::Namespace as ProtoNamespace;
+pub use aconfig::Flag_declaration as ProtoFlagDeclaration;
#[cfg(feature = "cargo")]
-pub use aconfig::Flag_value as ProtoFlagDefinitionValue;
+pub use aconfig::Flag_declarations as ProtoFlagDeclarations;
#[cfg(feature = "cargo")]
-pub use aconfig::Flag_definition as ProtoFlagDefinition;
+pub use aconfig::Flag_value as ProtoFlagValue;
#[cfg(feature = "cargo")]
-pub use aconfig::Flag_overrides as ProtoFlagOverrides;
-
-#[cfg(feature = "cargo")]
-pub use aconfig::Flag_override as ProtoFlagOverride;
+pub use aconfig::Flag_values as ProtoFlagValues;
#[cfg(feature = "cargo")]
pub use aconfig::Flag_permission as ProtoFlagPermission;
diff --git a/tools/aconfig/templates/java.template b/tools/aconfig/templates/java.template
index 3854579..89da18b 100644
--- a/tools/aconfig/templates/java.template
+++ b/tools/aconfig/templates/java.template
@@ -1,8 +1,8 @@
-package com.android.aconfig;
+package {namespace};
{{ if readwrite }}
import android.provider.DeviceConfig;
{{ endif }}
-public final class {namespace} \{
+public final class Flags \{
{{ for item in class_elements}}
public static boolean {item.method_name}() \{
{{ if item.readwrite- }}
diff --git a/tools/releasetools/add_img_to_target_files.py b/tools/releasetools/add_img_to_target_files.py
index ac3271b..8d660f8 100644
--- a/tools/releasetools/add_img_to_target_files.py
+++ b/tools/releasetools/add_img_to_target_files.py
@@ -65,7 +65,7 @@
import ota_metadata_pb2
import rangelib
import sparse_img
-
+from concurrent.futures import ThreadPoolExecutor
from apex_utils import GetApexInfoFromTargetFiles
from common import ZipDelete, PARTITIONS_WITH_CARE_MAP, ExternalError, RunAndCheckOutput, IsSparseImage, MakeTempFile, ZipWrite
@@ -1083,8 +1083,15 @@
("system_dlkm", has_system_dlkm, AddSystemDlkm, []),
("system_other", has_system_other, AddSystemOther, []),
)
- for call in add_partition_calls:
- add_partition(*call)
+ # If output_zip exists, each add_partition_calls writes bytes to the same output_zip,
+ # which is not thread-safe. So, run them in serial if output_zip exists.
+ if output_zip:
+ for call in add_partition_calls:
+ add_partition(*call)
+ else:
+ with ThreadPoolExecutor(max_workers=len(add_partition_calls)) as executor:
+ for future in [executor.submit(add_partition, *call) for call in add_partition_calls]:
+ future.result()
AddApexInfo(output_zip)
diff --git a/tools/releasetools/img_from_target_files.py b/tools/releasetools/img_from_target_files.py
index f8bdd81..7c27ef7 100755
--- a/tools/releasetools/img_from_target_files.py
+++ b/tools/releasetools/img_from_target_files.py
@@ -118,6 +118,7 @@
entries = [
'OTA/android-info.txt:android-info.txt',
+ 'META/fastboot-info.txt:fastboot-info.txt',
]
with zipfile.ZipFile(input_file) as input_zip:
namelist = input_zip.namelist()
diff --git a/tools/releasetools/merge_ota.py b/tools/releasetools/merge_ota.py
index 7d3d3a3..441312c 100644
--- a/tools/releasetools/merge_ota.py
+++ b/tools/releasetools/merge_ota.py
@@ -14,6 +14,7 @@
import argparse
import logging
+import shlex
import struct
import sys
import update_payload
@@ -34,6 +35,7 @@
logger = logging.getLogger(__name__)
CARE_MAP_ENTRY = "care_map.pb"
+APEX_INFO_ENTRY = "apex_info.pb"
def WriteDataBlob(payload: Payload, outfp: BinaryIO, read_size=1024*64):
@@ -188,6 +190,22 @@
f"OTA {partition_to_ota[part].name} and {payload.name} have duplicating partition {part}")
partition_to_ota[part] = payload
+def ApexInfo(file_paths):
+ if len(file_paths) > 1:
+ logger.info("More than one target file specified, will ignore "
+ "apex_info.pb (if any)")
+ return None
+ with zipfile.ZipFile(file_paths[0], "r", allowZip64=True) as zfp:
+ if APEX_INFO_ENTRY in zfp.namelist():
+ apex_info_bytes = zfp.read(APEX_INFO_ENTRY)
+ return apex_info_bytes
+ return None
+
+def ParseSignerArgs(args):
+ if args is None:
+ return None
+ return shlex.split(args)
+
def main(argv):
parser = argparse.ArgumentParser(description='Merge multiple partial OTAs')
parser.add_argument('packages', type=str, nargs='+',
@@ -196,6 +214,13 @@
help='Paths to private key for signing payload')
parser.add_argument('--search_path', type=str,
help='Search path for framework/signapk.jar')
+ parser.add_argument('--payload_signer', type=str,
+ help='Path to custom payload signer')
+ parser.add_argument('--payload_signer_args', type=ParseSignerArgs,
+ help='Arguments for payload signer if necessary')
+ parser.add_argument('--payload_signer_maximum_signature_size', type=str,
+ help='Maximum signature size (in bytes) that would be '
+ 'generated by the given payload signer')
parser.add_argument('--output', type=str,
help='Paths to output merged ota', required=True)
parser.add_argument('--metadata_ota', type=str,
@@ -203,6 +228,9 @@
parser.add_argument('--private_key_suffix', type=str,
help='Suffix to be appended to package_key path', default=".pk8")
parser.add_argument('-v', action="store_true", help="Enable verbose logging", dest="verbose")
+ parser.epilog = ('This tool can also be used to resign a regular OTA. For a single regular OTA, '
+ 'apex_info.pb will be written to output. When merging multiple OTAs, '
+ 'apex_info.pb will not be written.')
args = parser.parse_args(argv[1:])
file_paths = args.packages
@@ -225,6 +253,13 @@
merged_manifest = MergeManifests(payloads)
+ # Get signing keys
+ key_passwords = common.GetKeyPasswords([args.package_key])
+
+ generator = PayloadGenerator()
+
+ apex_info_bytes = ApexInfo(file_paths)
+
with tempfile.NamedTemporaryFile() as unsigned_payload:
WriteHeaderAndManifest(merged_manifest, unsigned_payload)
ConcatBlobs(payloads, unsigned_payload)
@@ -236,20 +271,31 @@
if args.package_key:
logger.info("Signing payload...")
- signer = PayloadSigner(args.package_key, args.private_key_suffix)
+ # TODO: remove OPTIONS when no longer used as fallback in payload_signer
+ common.OPTIONS.payload_signer_args = None
+ common.OPTIONS.payload_signer_maximum_signature_size = None
+ signer = PayloadSigner(args.package_key, args.private_key_suffix,
+ key_passwords[args.package_key],
+ payload_signer=args.payload_signer,
+ payload_signer_args=args.payload_signer_args,
+ payload_signer_maximum_signature_size=args.payload_signer_maximum_signature_size)
generator.payload_file = unsigned_payload.name
generator.Sign(signer)
logger.info("Payload size: %d", os.path.getsize(generator.payload_file))
logger.info("Writing to %s", args.output)
+
key_passwords = common.GetKeyPasswords([args.package_key])
with tempfile.NamedTemporaryFile(prefix="signed_ota", suffix=".zip") as signed_ota:
with zipfile.ZipFile(signed_ota, "w") as zfp:
generator.WriteToZip(zfp)
care_map_bytes = MergeCareMap(args.packages)
if care_map_bytes:
- zfp.writestr(CARE_MAP_ENTRY, care_map_bytes)
+ common.ZipWriteStr(zfp, CARE_MAP_ENTRY, care_map_bytes)
+ if apex_info_bytes:
+ logger.info("Writing %s", APEX_INFO_ENTRY)
+ common.ZipWriteStr(zfp, APEX_INFO_ENTRY, apex_info_bytes)
AddOtaMetadata(signed_ota.name, metadata_ota,
args.output, args.package_key, key_passwords[args.package_key])
return 0
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index 04ef5ef..afbe81a 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -525,8 +525,7 @@
def ParseInfoDict(target_file_path):
- with zipfile.ZipFile(target_file_path, 'r', allowZip64=True) as zfp:
- return common.LoadInfoDict(zfp)
+ return common.LoadInfoDict(target_file_path)
def GetTargetFilesZipForCustomVABCCompression(input_file, vabc_compression_param):
diff --git a/tools/releasetools/payload_signer.py b/tools/releasetools/payload_signer.py
index 4f342ac..9933aef 100644
--- a/tools/releasetools/payload_signer.py
+++ b/tools/releasetools/payload_signer.py
@@ -36,11 +36,16 @@
(OPTIONS.package_key) and calls openssl for the signing works.
"""
- def __init__(self, package_key=None, private_key_suffix=None, pw=None, payload_signer=None):
+ def __init__(self, package_key=None, private_key_suffix=None, pw=None, payload_signer=None,
+ payload_signer_args=None, payload_signer_maximum_signature_size=None):
if package_key is None:
package_key = OPTIONS.package_key
if private_key_suffix is None:
private_key_suffix = OPTIONS.private_key_suffix
+ if payload_signer_args is None:
+ payload_signer_args = OPTIONS.payload_signer_args
+ if payload_signer_maximum_signature_size is None:
+ payload_signer_maximum_signature_size = OPTIONS.payload_signer_maximum_signature_size
if payload_signer is None:
# Prepare the payload signing key.
@@ -59,10 +64,10 @@
signing_key)
else:
self.signer = payload_signer
- self.signer_args = OPTIONS.payload_signer_args
- if OPTIONS.payload_signer_maximum_signature_size:
+ self.signer_args = payload_signer_args
+ if payload_signer_maximum_signature_size:
self.maximum_signature_size = int(
- OPTIONS.payload_signer_maximum_signature_size)
+ payload_signer_maximum_signature_size)
else:
# The legacy config uses RSA2048 keys.
logger.warning("The maximum signature size for payload signer is not"
diff --git a/tools/sbom/generate-sbom.py b/tools/sbom/generate-sbom.py
index 1e98699..2415f7e 100755
--- a/tools/sbom/generate-sbom.py
+++ b/tools/sbom/generate-sbom.py
@@ -263,8 +263,8 @@
def get_sbom_fragments(installed_file_metadata, metadata_file_path):
"""Return SPDX fragment of source/prebuilt packages, which usually contains a SOURCE/PREBUILT
- package, a UPSTREAM package if it's a source package and a external SBOM document reference if
- it's a prebuilt package with sbom_ref defined in its METADATA file.
+ package, a UPSTREAM package and an external SBOM document reference if sbom_ref defined in its
+ METADATA file.
See go/android-spdx and go/android-sbom-gen for more details.
"""
@@ -301,25 +301,33 @@
prebuilt_package = sbom_data.Package(id=prebuilt_package_id,
name=name,
download_location=sbom_data.VALUE_NONE,
- version=args.build_version,
+ version=version if version else args.build_version,
supplier='Organization: ' + args.product_mfr)
- packages.append(prebuilt_package)
- if metadata_file_path:
- metadata_proto = metadata_file_protos[metadata_file_path]
- if metadata_proto.third_party.WhichOneof('sbom') == 'sbom_ref':
- sbom_url = metadata_proto.third_party.sbom_ref.url
- sbom_checksum = metadata_proto.third_party.sbom_ref.checksum
- upstream_element_id = metadata_proto.third_party.sbom_ref.element_id
- if sbom_url and sbom_checksum and upstream_element_id:
- doc_ref_id = f'DocumentRef-{PKG_UPSTREAM}-{encode_for_spdxid(name)}'
- external_doc_ref = sbom_data.DocumentExternalReference(id=doc_ref_id,
- uri=sbom_url,
- checksum=sbom_checksum)
- relationships.append(
- sbom_data.Relationship(id1=prebuilt_package_id,
- relationship=sbom_data.RelationshipType.VARIANT_OF,
- id2=doc_ref_id + ':' + upstream_element_id))
+ upstream_package_id = new_package_id(name, PKG_UPSTREAM)
+ upstream_package = sbom_data.Package(id=upstream_package_id, name=name, version = version,
+ supplier=('Organization: ' + homepage) if homepage else sbom_data.VALUE_NOASSERTION,
+ download_location=download_location)
+ packages += [prebuilt_package, upstream_package]
+ relationships.append(sbom_data.Relationship(id1=prebuilt_package_id,
+ relationship=sbom_data.RelationshipType.VARIANT_OF,
+ id2=upstream_package_id))
+
+ if metadata_file_path:
+ metadata_proto = metadata_file_protos[metadata_file_path]
+ if metadata_proto.third_party.WhichOneof('sbom') == 'sbom_ref':
+ sbom_url = metadata_proto.third_party.sbom_ref.url
+ sbom_checksum = metadata_proto.third_party.sbom_ref.checksum
+ upstream_element_id = metadata_proto.third_party.sbom_ref.element_id
+ if sbom_url and sbom_checksum and upstream_element_id:
+ doc_ref_id = f'DocumentRef-{PKG_UPSTREAM}-{encode_for_spdxid(name)}'
+ external_doc_ref = sbom_data.DocumentExternalReference(id=doc_ref_id,
+ uri=sbom_url,
+ checksum=sbom_checksum)
+ relationships.append(
+ sbom_data.Relationship(id1=upstream_package_id,
+ relationship=sbom_data.RelationshipType.VARIANT_OF,
+ id2=doc_ref_id + ':' + upstream_element_id))
return external_doc_ref, packages, relationships