Merge "Add 16K artifacts to PREBUILT_IMAGES/ dir of target_files"
diff --git a/core/Makefile b/core/Makefile
index bd04e4a..1d5f661 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -1008,7 +1008,7 @@
BUILT_RAMDISK_16K_TARGET := $(PRODUCT_OUT)/ramdisk_16k.img
RAMDISK_16K_STAGING_DIR := $(call intermediates-dir-for,PACKAGING,depmod_ramdisk_16k)
-$(BUILT_RAMDISK_16K_TARGET): $(DEPMOD)
+$(BUILT_RAMDISK_16K_TARGET): $(DEPMOD) $(MKBOOTFS)
$(BUILT_RAMDISK_16K_TARGET): $(call copy-many-files,$(foreach file,$(BOARD_KERNEL_MODULES_16K),$(file):$(RAMDISK_16K_STAGING_DIR)/lib/modules/0.0/$(notdir $(file))))
$(DEPMOD) -b $(RAMDISK_16K_STAGING_DIR) 0.0
for MODULE in $(BOARD_KERNEL_MODULES_16K); do \
diff --git a/core/config.mk b/core/config.mk
index e272389..f503764 100644
--- a/core/config.mk
+++ b/core/config.mk
@@ -1230,16 +1230,7 @@
RSCOMPAT_32BIT_ONLY_API_LEVELS := 8 9 10 11 12 13 14 15 16 17 18 19 20
RSCOMPAT_NO_USAGEIO_API_LEVELS := 8 9 10 11 12 13
-# Add BUILD_NUMBER to apps default version name if it's unbundled build.
-ifdef TARGET_BUILD_APPS
-TARGET_BUILD_WITH_APPS_VERSION_NAME := true
-endif
-
-ifdef TARGET_BUILD_WITH_APPS_VERSION_NAME
-APPS_DEFAULT_VERSION_NAME := $(PLATFORM_VERSION)-$(BUILD_NUMBER_FROM_FILE)
-else
APPS_DEFAULT_VERSION_NAME := $(PLATFORM_VERSION)
-endif
# ANDROID_WARNING_ALLOWED_PROJECTS is generated by build/soong.
define find_warning_allowed_projects
diff --git a/core/distdir.mk b/core/distdir.mk
index bce8e7f..032d1b7 100644
--- a/core/distdir.mk
+++ b/core/distdir.mk
@@ -45,6 +45,18 @@
$(eval _all_dist_goal_output_pairs += $$(goal):$$(dst))))
endef
+define add_file_name_tag_suffix
+$(basename $(notdir $1))-FILE_NAME_TAG_PLACEHOLDER$(suffix $1)
+endef
+
+# This function appends suffix FILE_NAME_TAG_PLACEHOLDER from the input file
+# $(1): a list of goals (e.g. droid, sdk, ndk). These must be PHONY
+# $(2): the dist files to add to those goals.
+define dist-for-goals-with-filenametag
+$(if $(strip $(2)), \
+ $(foreach file,$(2), \
+ $(call dist-for-goals,$(1),$(file):$(call add_file_name_tag_suffix,$(file)))))
+endef
.PHONY: shareprojects
define __share-projects-rule
@@ -209,4 +221,4 @@
fi))
endef
-.KATI_READONLY := dist-for-goals dist-write-file
+.KATI_READONLY := dist-for-goals dist-write-file dist-for-goals-with-filenametag
diff --git a/core/envsetup.mk b/core/envsetup.mk
index 860ce79..8887ddc 100644
--- a/core/envsetup.mk
+++ b/core/envsetup.mk
@@ -30,14 +30,13 @@
# In order to avoid running starlark every time the stamp file is checked, we use
# $(KATI_shell_no_rerun). Then, to make sure that we actually do rerun kati when
# modifying the starlark files, we add the starlark files to the kati stamp file with
-# $(KATI_extra_file_deps). This behavior can be modified by passing a list of starlark files
-# to exclude from the dependency list as $(2)
+# $(KATI_extra_file_deps).
define run-starlark
$(eval _starlark_results := $(OUT_DIR)/starlark_results/$(subst /,_,$(1)).mk)
$(KATI_shell_no_rerun mkdir -p $(OUT_DIR)/starlark_results && $(OUT_DIR)/rbcrun --mode=make $(1) >$(_starlark_results) && touch -t 200001010000 $(_starlark_results))
$(if $(filter-out 0,$(.SHELLSTATUS)),$(error Starlark failed to run))
$(eval include $(_starlark_results))
-$(KATI_extra_file_deps $(filter-out $(2),$(LOADED_STARLARK_FILES)))
+$(KATI_extra_file_deps $(LOADED_STARLARK_FILES))
$(eval LOADED_STARLARK_FILES :=)
$(eval _starlark_results :=)
endef
diff --git a/core/main.mk b/core/main.mk
index a747967..0f1368f 100644
--- a/core/main.mk
+++ b/core/main.mk
@@ -1390,7 +1390,7 @@
$(CUSTOM_MODULES) \
)
-# Dedpulicate compatibility suite dist files across modules and packages before
+# Deduplicate compatibility suite dist files across modules and packages before
# copying them to their requested locations. Assign the eval result to an unused
# var to prevent Make from trying to make a sense of it.
_unused := $(call copy-many-files, $(sort $(ALL_COMPATIBILITY_DIST_FILES)))
diff --git a/core/release_config.bzl b/core/release_config.bzl
deleted file mode 100644
index e73c90f..0000000
--- a/core/release_config.bzl
+++ /dev/null
@@ -1,90 +0,0 @@
-# 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 = [
- "product",
- "system",
- "system_ext",
- "vendor",
-]
-
-def _combine_dicts_no_duplicate_keys(dicts):
- result = {}
- for d in dicts:
- for k, v in d.items():
- if k in result:
- fail("Duplicate key: " + k)
- result[k] = v
- return result
-
-def release_config(target_release, flag_definitions, config_maps, fail_if_no_release_config = True):
- result = {
- "_ALL_RELEASE_FLAGS": [flag.name for flag in flag_definitions],
- }
- all_flags = {}
- for flag in flag_definitions:
- if sorted(dir(flag)) != ["default", "name", "partitions"]:
- fail("Flag structs must contain 3 fields: name, partitions, and default")
- if not flag.partitions:
- fail("At least 1 partition is required")
- for partition in flag.partitions:
- if partition == "all":
- if len(flag.partitions) > 1:
- fail("\"all\" can't be combined with other partitions: " + str(flag.partitions))
- elif partition not in _flag_partitions:
- fail("Invalid partition: " + flag.partition + ", allowed partitions: " + str(_flag_partitions))
- if not flag.name.startswith("RELEASE_"):
- fail("Release flag names must start with RELEASE_")
- if " " in flag.name or "\t" in flag.name or "\n" in flag.name:
- fail("Flag names must not contain whitespace.")
- if flag.name in all_flags:
- fail("Duplicate declaration of flag " + flag.name)
- all_flags[flag.name] = True
-
- default = flag.default
- if type(default) == "bool":
- default = "true" if default else ""
-
- result["_ALL_RELEASE_FLAGS." + flag.name + ".PARTITIONS"] = flag.partitions
- result["_ALL_RELEASE_FLAGS." + flag.name + ".DEFAULT"] = default
- result["_ALL_RELEASE_FLAGS." + flag.name + ".VALUE"] = default
-
- # 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.
- if target_release:
- config_map = _combine_dicts_no_duplicate_keys(config_maps)
- if target_release not in config_map:
- fail("No release config found for TARGET_RELEASE: " + target_release + ". Available releases are: " + str(config_map.keys()))
- release_config = config_map[target_release]
- if sorted(dir(release_config)) != ["flags", "release_version"]:
- fail("A release config must be a struct with a flags and release_version fields")
- result["_RELEASE_VERSION"] = release_config.release_version
- for flag in release_config.flags:
- if sorted(dir(release_config)) != ["name", "value"]:
- fail("A flag be a struct with name and value fields")
- if flag.name not in all_flags:
- fail("Undeclared build flag: " + flag.name)
- value = flag.value
- if type(value) == "bool":
- value = "true" if value else ""
- result["_ALL_RELEASE_FLAGS." + flag.name + ".VALUE"] = value
- elif fail_if_no_release_config:
- fail("FAIL_IF_NO_RELEASE_CONFIG was set and TARGET_RELEASE was not")
- else:
- # No TARGET_RELEASE means release version 0
- result["_RELEASE_VERSION"] = 0
-
- return result
diff --git a/core/release_config.mk b/core/release_config.mk
index 621c7d1..fdfc6a0 100644
--- a/core/release_config.mk
+++ b/core/release_config.mk
@@ -12,45 +12,78 @@
# 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.
-flag_declaration_files := $(wildcard build/release/build_flags.bzl) \
- $(if $(wildcard vendor/google/release/build_flags.bzl), \
- vendor/google/release/build_flags.bzl, \
+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/build_flags.bzl) \
- $(wildcard device/*/*/release/build_flags.bzl) \
- $(wildcard vendor/*/release/build_flags.bzl) \
- $(wildcard vendor/*/*/release/build_flags.bzl) \
- ) \
- )
-config_map_files := $(wildcard build/release/release_config_map.bzl) \
- $(if $(wildcard vendor/google/release/release_config_map.bzl), \
- vendor/google/release/release_config_map.bzl, \
- $(sort \
- $(wildcard device/*/release/release_config_map.bzl) \
- $(wildcard device/*/*/release/release_config_map.bzl) \
- $(wildcard vendor/*/release/release_config_map.bzl) \
- $(wildcard vendor/*/*/release/release_config_map.bzl) \
+ $(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) \
) \
)
-# Because starlark can't find files with $(wildcard), write an entrypoint starlark script that
-# contains the result of the above wildcards for the starlark code to use.
-filename_to_starlark=$(subst /,_,$(subst .,_,$(1)))
-_c:=load("//build/make/core/release_config.bzl", "release_config")
-_c+=$(foreach f,$(flag_declaration_files),$(newline)load("//$(f)", flags_$(call filename_to_starlark,$(f)) = "flags"))
-_c+=$(foreach f,$(config_map_files),$(newline)load("//$(f)", config_maps_$(call filename_to_starlark,$(f)) = "config_maps"))
-_c+=$(newline)all_flags = [] $(foreach f,$(flag_declaration_files),+ flags_$(call filename_to_starlark,$(f)))
-_c+=$(newline)all_config_maps = [$(foreach f,$(config_map_files),config_maps_$(call filename_to_starlark,$(f))$(comma))]
-_c+=$(newline)target_release = "$(TARGET_RELEASE)"
-_c+=$(newline)fail_if_no_release_config = True if "$(FAIL_IF_NO_RELEASE_CONFIG)" else False
-_c+=$(newline)variables_to_export_to_make = release_config(target_release, all_flags, all_config_maps, fail_if_no_release_config)
-$(file >$(OUT_DIR)/release_config_entrypoint.bzl,$(_c))
-_c:=
-filename_to_starlark:=
+# $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
@@ -63,7 +96,127 @@
endif
.KATI_READONLY := TARGET_RELEASE
-# Exclude the entrypoint file as a dependency (by passing it as the 2nd argument) so that we don't
-# rerun kati every build. Kati will replay the $(file) command that generates it every build,
-# updating its timestamp.
-$(call run-starlark,$(OUT_DIR)/release_config_entrypoint.bzl,$(OUT_DIR)/release_config_entrypoint.bzl)
+$(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/tools/aconfig/src/cache.rs b/tools/aconfig/src/cache.rs
index c546f7b..30810fa 100644
--- a/tools/aconfig/src/cache.rs
+++ b/tools/aconfig/src/cache.rs
@@ -51,72 +51,42 @@
items: Vec<Item>,
}
-impl Cache {
- pub fn new(namespace: String) -> Result<Cache> {
- ensure!(!namespace.is_empty(), "empty namespace");
- Ok(Cache { namespace, items: vec![] })
+// TODO: replace this function with Iterator.is_sorted_by_key(...)) when that API becomes stable
+fn iter_is_sorted_by_key<'a, T: 'a, F, K>(iter: impl Iterator<Item = &'a T>, f: F) -> bool
+where
+ F: FnMut(&'a T) -> K,
+ K: PartialOrd<K>,
+{
+ let mut last: Option<K> = None;
+ for current in iter.map(f) {
+ if let Some(l) = last {
+ if l > current {
+ return false;
+ }
+ }
+ last = Some(current);
}
+ true
+}
+impl Cache {
pub fn read_from_reader(reader: impl Read) -> Result<Cache> {
- serde_json::from_reader(reader).map_err(|e| e.into())
+ let cache: Cache = serde_json::from_reader(reader)?;
+ ensure!(
+ iter_is_sorted_by_key(cache.iter(), |item| &item.name),
+ "internal error: flags in cache file not sorted"
+ );
+ Ok(cache)
}
pub fn write_to_writer(&self, writer: impl Write) -> Result<()> {
+ ensure!(
+ iter_is_sorted_by_key(self.iter(), |item| &item.name),
+ "internal error: flags in cache file not sorted"
+ );
serde_json::to_writer(writer, self).map_err(|e| e.into())
}
- 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: 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_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 = value.state;
- existing_item.permission = value.permission;
- existing_item.trace.push(Tracepoint {
- source,
- state: value.state,
- permission: value.permission,
- });
- Ok(())
- }
-
pub fn iter(&self) -> impl Iterator<Item = &Item> {
self.items.iter()
}
@@ -131,6 +101,80 @@
}
}
+#[derive(Debug)]
+pub struct CacheBuilder {
+ cache: Cache,
+}
+
+impl CacheBuilder {
+ pub fn new(namespace: String) -> Result<CacheBuilder> {
+ ensure!(!namespace.is_empty(), "empty namespace");
+ let cache = Cache { namespace, items: vec![] };
+ Ok(CacheBuilder { cache })
+ }
+
+ pub fn add_flag_declaration(
+ &mut self,
+ source: Source,
+ declaration: FlagDeclaration,
+ ) -> Result<&mut CacheBuilder> {
+ ensure!(!declaration.name.is_empty(), "empty flag name");
+ ensure!(!declaration.description.is_empty(), "empty flag description");
+ ensure!(
+ self.cache.items.iter().all(|item| item.name != declaration.name),
+ "failed to declare flag {} from {}: flag already declared",
+ declaration.name,
+ source
+ );
+ self.cache.items.push(Item {
+ namespace: self.cache.namespace.clone(),
+ 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(self)
+ }
+
+ pub fn add_flag_value(
+ &mut self,
+ source: Source,
+ value: FlagValue,
+ ) -> Result<&mut CacheBuilder> {
+ ensure!(!value.namespace.is_empty(), "empty flag namespace");
+ ensure!(!value.name.is_empty(), "empty flag name");
+ ensure!(
+ value.namespace == self.cache.namespace,
+ "failed to set values for flag {}/{} from {}: expected namespace {}",
+ value.namespace,
+ value.name,
+ source,
+ self.cache.namespace
+ );
+ let Some(existing_item) = self.cache.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 = value.state;
+ existing_item.permission = value.permission;
+ existing_item.trace.push(Tracepoint {
+ source,
+ state: value.state,
+ permission: value.permission,
+ });
+ Ok(self)
+ }
+
+ pub fn build(mut self) -> Cache {
+ self.cache.items.sort_by_cached_key(|item| item.name.clone());
+ self.cache
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -138,14 +182,14 @@
#[test]
fn test_add_flag_declaration() {
- let mut cache = Cache::new("ns".to_string()).unwrap();
- cache
+ let mut builder = CacheBuilder::new("ns".to_string()).unwrap();
+ builder
.add_flag_declaration(
Source::File("first.txt".to_string()),
FlagDeclaration { name: "foo".to_string(), description: "desc".to_string() },
)
.unwrap();
- let error = cache
+ let error = builder
.add_flag_declaration(
Source::File("second.txt".to_string()),
FlagDeclaration { name: "foo".to_string(), description: "desc".to_string() },
@@ -155,17 +199,26 @@
&format!("{:?}", error),
"failed to declare flag foo from second.txt: flag already declared"
);
+ builder
+ .add_flag_declaration(
+ Source::File("first.txt".to_string()),
+ FlagDeclaration { name: "bar".to_string(), description: "desc".to_string() },
+ )
+ .unwrap();
+
+ let cache = builder.build();
+
+ // check flags are sorted by name
+ assert_eq!(
+ cache.into_iter().map(|item| item.name).collect::<Vec<_>>(),
+ vec!["bar".to_string(), "foo".to_string()]
+ );
}
#[test]
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("ns".to_string()).unwrap();
- let error = cache
+ let mut builder = CacheBuilder::new("ns".to_string()).unwrap();
+ let error = builder
.add_flag_value(
Source::Memory,
FlagValue {
@@ -181,15 +234,14 @@
"failed to set values for flag ns/foo from <memory>: flag not declared"
);
- cache
+ builder
.add_flag_declaration(
Source::File("first.txt".to_string()),
FlagDeclaration { name: "foo".to_string(), description: "desc".to_string() },
)
.unwrap();
- assert!(check(&cache, "foo", (DEFAULT_FLAG_STATE, DEFAULT_FLAG_PERMISSION)));
- cache
+ builder
.add_flag_value(
Source::Memory,
FlagValue {
@@ -200,9 +252,8 @@
},
)
.unwrap();
- assert!(check(&cache, "foo", (FlagState::Disabled, Permission::ReadOnly)));
- cache
+ builder
.add_flag_value(
Source::Memory,
FlagValue {
@@ -213,10 +264,9 @@
},
)
.unwrap();
- assert!(check(&cache, "foo", (FlagState::Enabled, Permission::ReadWrite)));
// different namespace -> no-op
- let error = cache
+ let error = builder
.add_flag_value(
Source::Memory,
FlagValue {
@@ -228,19 +278,23 @@
)
.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)));
+
+ let cache = builder.build();
+ let item = cache.iter().find(|&item| item.name == "foo").unwrap();
+ assert_eq!(FlagState::Enabled, item.state);
+ assert_eq!(Permission::ReadWrite, item.permission);
}
#[test]
fn test_reject_empty_cache_namespace() {
- Cache::new("".to_string()).unwrap_err();
+ CacheBuilder::new("".to_string()).unwrap_err();
}
#[test]
fn test_reject_empty_flag_declaration_fields() {
- let mut cache = Cache::new("ns".to_string()).unwrap();
+ let mut builder = CacheBuilder::new("ns".to_string()).unwrap();
- let error = cache
+ let error = builder
.add_flag_declaration(
Source::Memory,
FlagDeclaration { name: "".to_string(), description: "Description".to_string() },
@@ -248,7 +302,7 @@
.unwrap_err();
assert_eq!(&format!("{:?}", error), "empty flag name");
- let error = cache
+ let error = builder
.add_flag_declaration(
Source::Memory,
FlagDeclaration { name: "foo".to_string(), description: "".to_string() },
@@ -259,15 +313,15 @@
#[test]
fn test_reject_empty_flag_value_files() {
- let mut cache = Cache::new("ns".to_string()).unwrap();
- cache
+ let mut builder = CacheBuilder::new("ns".to_string()).unwrap();
+ builder
.add_flag_declaration(
Source::Memory,
FlagDeclaration { name: "foo".to_string(), description: "desc".to_string() },
)
.unwrap();
- let error = cache
+ let error = builder
.add_flag_value(
Source::Memory,
FlagValue {
@@ -280,7 +334,7 @@
.unwrap_err();
assert_eq!(&format!("{:?}", error), "empty flag namespace");
- let error = cache
+ let error = builder
.add_flag_value(
Source::Memory,
FlagValue {
@@ -293,4 +347,11 @@
.unwrap_err();
assert_eq!(&format!("{:?}", error), "empty flag name");
}
+
+ #[test]
+ fn test_iter_is_sorted_by_key() {
+ assert!(iter_is_sorted_by_key(["a", "b", "c"].iter(), |s| s));
+ assert!(iter_is_sorted_by_key(Vec::<&str>::new().iter(), |s| s));
+ assert!(!iter_is_sorted_by_key(["a", "c", "b"].iter(), |s| s));
+ }
}
diff --git a/tools/aconfig/src/codegen_cpp.rs b/tools/aconfig/src/codegen_cpp.rs
index cb266f1..2aeea6a 100644
--- a/tools/aconfig/src/codegen_cpp.rs
+++ b/tools/aconfig/src/codegen_cpp.rs
@@ -64,13 +64,14 @@
mod tests {
use super::*;
use crate::aconfig::{FlagDeclaration, FlagState, FlagValue, Permission};
+ use crate::cache::CacheBuilder;
use crate::commands::Source;
#[test]
fn test_cpp_codegen_build_time_flag_only() {
let namespace = "my_namespace";
- let mut cache = Cache::new(namespace.to_string()).unwrap();
- cache
+ let mut builder = CacheBuilder::new(namespace.to_string()).unwrap();
+ builder
.add_flag_declaration(
Source::File("aconfig_one.txt".to_string()),
FlagDeclaration {
@@ -78,8 +79,7 @@
description: "buildtime disable".to_string(),
},
)
- .unwrap();
- cache
+ .unwrap()
.add_flag_value(
Source::Memory,
FlagValue {
@@ -89,8 +89,7 @@
permission: Permission::ReadOnly,
},
)
- .unwrap();
- cache
+ .unwrap()
.add_flag_declaration(
Source::File("aconfig_two.txt".to_string()),
FlagDeclaration {
@@ -98,8 +97,7 @@
description: "buildtime enable".to_string(),
},
)
- .unwrap();
- cache
+ .unwrap()
.add_flag_value(
Source::Memory,
FlagValue {
@@ -110,6 +108,7 @@
},
)
.unwrap();
+ let cache = builder.build();
let expect_content = r#"#ifndef my_namespace_HEADER_H
#define my_namespace_HEADER_H
#include "my_namespace.h"
@@ -144,8 +143,8 @@
#[test]
fn test_cpp_codegen_runtime_flag() {
let namespace = "my_namespace";
- let mut cache = Cache::new(namespace.to_string()).unwrap();
- cache
+ let mut builder = CacheBuilder::new(namespace.to_string()).unwrap();
+ builder
.add_flag_declaration(
Source::File("aconfig_one.txt".to_string()),
FlagDeclaration {
@@ -153,8 +152,7 @@
description: "buildtime disable".to_string(),
},
)
- .unwrap();
- cache
+ .unwrap()
.add_flag_declaration(
Source::File("aconfig_two.txt".to_string()),
FlagDeclaration {
@@ -162,8 +160,7 @@
description: "runtime enable".to_string(),
},
)
- .unwrap();
- cache
+ .unwrap()
.add_flag_value(
Source::Memory,
FlagValue {
@@ -174,6 +171,7 @@
},
)
.unwrap();
+ let cache = builder.build();
let expect_content = r#"#ifndef my_namespace_HEADER_H
#define my_namespace_HEADER_H
#include "my_namespace.h"
diff --git a/tools/aconfig/src/codegen_java.rs b/tools/aconfig/src/codegen_java.rs
index 476a89d..733b1c5 100644
--- a/tools/aconfig/src/codegen_java.rs
+++ b/tools/aconfig/src/codegen_java.rs
@@ -71,13 +71,14 @@
mod tests {
use super::*;
use crate::aconfig::{FlagDeclaration, FlagValue};
+ use crate::cache::CacheBuilder;
use crate::commands::Source;
#[test]
fn test_generate_java_code() {
let namespace = "com.example";
- let mut cache = Cache::new(namespace.to_string()).unwrap();
- cache
+ let mut builder = CacheBuilder::new(namespace.to_string()).unwrap();
+ builder
.add_flag_declaration(
Source::File("test.txt".to_string()),
FlagDeclaration {
@@ -85,8 +86,7 @@
description: "buildtime enable".to_string(),
},
)
- .unwrap();
- cache
+ .unwrap()
.add_flag_declaration(
Source::File("test2.txt".to_string()),
FlagDeclaration {
@@ -94,8 +94,7 @@
description: "runtime disable".to_string(),
},
)
- .unwrap();
- cache
+ .unwrap()
.add_flag_value(
Source::Memory,
FlagValue {
@@ -106,6 +105,7 @@
},
)
.unwrap();
+ let cache = builder.build();
let expect_content = r#"package com.example;
import android.provider.DeviceConfig;
diff --git a/tools/aconfig/src/commands.rs b/tools/aconfig/src/commands.rs
index 0bdb0b5..22de331 100644
--- a/tools/aconfig/src/commands.rs
+++ b/tools/aconfig/src/commands.rs
@@ -23,7 +23,7 @@
use std::path::PathBuf;
use crate::aconfig::{FlagDeclarations, FlagValue};
-use crate::cache::Cache;
+use crate::cache::{Cache, CacheBuilder};
use crate::codegen_cpp::generate_cpp_code;
use crate::codegen_java::generate_java_code;
use crate::protos::ProtoParsedFlags;
@@ -59,7 +59,7 @@
declarations: Vec<Input>,
values: Vec<Input>,
) -> Result<Cache> {
- let mut cache = Cache::new(namespace.to_owned())?;
+ let mut builder = CacheBuilder::new(namespace.to_owned())?;
for mut input in declarations {
let mut contents = String::new();
@@ -74,7 +74,7 @@
dec_list.namespace
);
for d in dec_list.flags.into_iter() {
- cache.add_flag_declaration(input.source.clone(), d)?;
+ builder.add_flag_declaration(input.source.clone(), d)?;
}
}
@@ -85,11 +85,11 @@
.with_context(|| format!("Failed to parse {}", input.source))?;
for v in values_list {
// TODO: warn about flag values that do not take effect?
- let _ = cache.add_flag_value(input.source.clone(), v);
+ let _ = builder.add_flag_value(input.source.clone(), v);
}
}
- Ok(cache)
+ Ok(builder.build())
}
pub fn create_java_lib(cache: &Cache) -> Result<OutputFile> {
@@ -107,29 +107,35 @@
Protobuf,
}
-pub fn dump_cache(cache: Cache, format: DumpFormat) -> Result<Vec<u8>> {
- match format {
- DumpFormat::Text => {
- let mut lines = vec![];
- for item in cache.iter() {
- lines.push(format!("{}: {:?}\n", item.name, item.state));
+pub fn dump_cache(mut caches: Vec<Cache>, format: DumpFormat) -> Result<Vec<u8>> {
+ let mut output = Vec::new();
+ caches.sort_by_cached_key(|cache| cache.namespace().to_string());
+ for cache in caches.into_iter() {
+ match format {
+ DumpFormat::Text => {
+ let mut lines = vec![];
+ for item in cache.iter() {
+ lines.push(format!(
+ "{}/{}: {:?} {:?}\n",
+ item.namespace, item.name, item.state, item.permission
+ ));
+ }
+ output.append(&mut lines.concat().into());
}
- Ok(lines.concat().into())
- }
- DumpFormat::Debug => {
- let mut lines = vec![];
- for item in cache.iter() {
- lines.push(format!("{:?}\n", item));
+ DumpFormat::Debug => {
+ let mut lines = vec![];
+ for item in cache.iter() {
+ lines.push(format!("{:?}\n", item));
+ }
+ output.append(&mut lines.concat().into());
}
- Ok(lines.concat().into())
- }
- DumpFormat::Protobuf => {
- let parsed_flags: ProtoParsedFlags = cache.into();
- let mut output = vec![];
- parsed_flags.write_to_vec(&mut output)?;
- Ok(output)
+ DumpFormat::Protobuf => {
+ let parsed_flags: ProtoParsedFlags = cache.into();
+ parsed_flags.write_to_vec(&mut output)?;
+ }
}
}
+ Ok(output)
}
#[cfg(test)]
@@ -137,9 +143,9 @@
use super::*;
use crate::aconfig::{FlagState, Permission};
- fn create_test_cache() -> Cache {
+ fn create_test_cache_ns1() -> Cache {
let s = r#"
- namespace: "ns"
+ namespace: "ns1"
flag {
name: "a"
description: "Description of a"
@@ -152,28 +158,49 @@
let declarations = vec![Input { source: Source::Memory, reader: Box::new(s.as_bytes()) }];
let o = r#"
flag_value {
- namespace: "ns"
+ namespace: "ns1"
name: "a"
state: DISABLED
permission: READ_ONLY
}
"#;
let values = vec![Input { source: Source::Memory, reader: Box::new(o.as_bytes()) }];
- create_cache("ns", declarations, values).unwrap()
+ create_cache("ns1", declarations, values).unwrap()
+ }
+
+ fn create_test_cache_ns2() -> Cache {
+ let s = r#"
+ namespace: "ns2"
+ flag {
+ name: "c"
+ description: "Description of c"
+ }
+ "#;
+ let declarations = vec![Input { source: Source::Memory, reader: Box::new(s.as_bytes()) }];
+ let o = r#"
+ flag_value {
+ namespace: "ns2"
+ name: "c"
+ state: DISABLED
+ permission: READ_ONLY
+ }
+ "#;
+ let values = vec![Input { source: Source::Memory, reader: Box::new(o.as_bytes()) }];
+ create_cache("ns2", declarations, values).unwrap()
}
#[test]
fn test_create_cache() {
- let cache = create_test_cache(); // calls create_cache
- let item = cache.iter().find(|&item| item.name == "a").unwrap();
+ let caches = create_test_cache_ns1(); // calls create_cache
+ let item = caches.iter().find(|&item| item.name == "a").unwrap();
assert_eq!(FlagState::Disabled, item.state);
assert_eq!(Permission::ReadOnly, item.permission);
}
#[test]
fn test_dump_text_format() {
- let cache = create_test_cache();
- let bytes = dump_cache(cache, DumpFormat::Text).unwrap();
+ let caches = vec![create_test_cache_ns1()];
+ let bytes = dump_cache(caches, DumpFormat::Text).unwrap();
let text = std::str::from_utf8(&bytes).unwrap();
assert!(text.contains("a: Disabled"));
}
@@ -183,8 +210,8 @@
use crate::protos::{ProtoFlagPermission, ProtoFlagState, ProtoTracepoint};
use protobuf::Message;
- let cache = create_test_cache();
- let bytes = dump_cache(cache, DumpFormat::Protobuf).unwrap();
+ let caches = vec![create_test_cache_ns1()];
+ let bytes = dump_cache(caches, DumpFormat::Protobuf).unwrap();
let actual = ProtoParsedFlags::parse_from_bytes(&bytes).unwrap();
assert_eq!(
@@ -194,7 +221,7 @@
let item =
actual.parsed_flag.iter().find(|item| item.name == Some("b".to_string())).unwrap();
- assert_eq!(item.namespace(), "ns");
+ assert_eq!(item.namespace(), "ns1");
assert_eq!(item.name(), "b");
assert_eq!(item.description(), "Description of b");
assert_eq!(item.state(), ProtoFlagState::DISABLED);
@@ -205,4 +232,23 @@
tp.set_permission(ProtoFlagPermission::READ_WRITE);
assert_eq!(item.trace, vec![tp]);
}
+
+ #[test]
+ fn test_dump_multiple_caches() {
+ let caches = vec![create_test_cache_ns1(), create_test_cache_ns2()];
+ let bytes = dump_cache(caches, DumpFormat::Protobuf).unwrap();
+ let dump = ProtoParsedFlags::parse_from_bytes(&bytes).unwrap();
+ assert_eq!(
+ dump.parsed_flag
+ .iter()
+ .map(|parsed_flag| format!("{}/{}", parsed_flag.namespace(), parsed_flag.name()))
+ .collect::<Vec<_>>(),
+ vec!["ns1/a".to_string(), "ns1/b".to_string(), "ns2/c".to_string()]
+ );
+
+ let caches = vec![create_test_cache_ns2(), create_test_cache_ns1()];
+ let bytes = dump_cache(caches, DumpFormat::Protobuf).unwrap();
+ let dump_reversed_input = ProtoParsedFlags::parse_from_bytes(&bytes).unwrap();
+ assert_eq!(dump, dump_reversed_input);
+ }
}
diff --git a/tools/aconfig/src/main.rs b/tools/aconfig/src/main.rs
index 6db5948..d02307d 100644
--- a/tools/aconfig/src/main.rs
+++ b/tools/aconfig/src/main.rs
@@ -56,7 +56,7 @@
)
.subcommand(
Command::new("dump")
- .arg(Arg::new("cache").long("cache").required(true))
+ .arg(Arg::new("cache").long("cache").action(ArgAction::Append).required(true))
.arg(
Arg::new("format")
.long("format")
@@ -130,11 +130,14 @@
write_output_file_realtive_to_dir(&dir, &generated_file)?;
}
Some(("dump", sub_matches)) => {
- let path = get_required_arg::<String>(sub_matches, "cache")?;
- let file = fs::File::open(path)?;
- let cache = Cache::read_from_reader(file)?;
+ let mut caches = Vec::new();
+ for path in sub_matches.get_many::<String>("cache").unwrap_or_default() {
+ let file = fs::File::open(path)?;
+ let cache = Cache::read_from_reader(file)?;
+ caches.push(cache);
+ }
let format = get_required_arg::<DumpFormat>(sub_matches, "format")?;
- let output = commands::dump_cache(cache, *format)?;
+ let output = commands::dump_cache(caches, *format)?;
let path = get_required_arg::<String>(sub_matches, "out")?;
let mut file: Box<dyn Write> = if *path == "-" {
Box::new(io::stdout())