Merge "Support Soong-generated module-info.json entries" into main
diff --git a/core/Makefile b/core/Makefile
index 4a1cda1..6edac1a 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -4600,10 +4600,18 @@
--prop com.android.build.pvmfw.security_patch:$(PVMFW_SECURITY_PATCH)
endif
-# Append avbpubkey of microdroid-vendor partition into vendor_boot partition.
-ifdef MICRODROID_VENDOR_AVBKEY
-BOARD_AVB_VENDOR_BOOT_ADD_HASH_FOOTER_ARGS += \
- --prop_from_file com.android.build.microdroid-vendor.avbpubkey:$(MICRODROID_VENDOR_AVBKEY)
+# Append root digest of microdroid-vendor partition's hashtree descriptor into vendor partition.
+ifdef MICRODROID_VENDOR_IMAGE_MODULE
+MICRODROID_VENDOR_IMAGE := \
+ $(call intermediates-dir-for,ETC,$(MICRODROID_VENDOR_IMAGE_MODULE))/$(MICRODROID_VENDOR_IMAGE_MODULE)
+MICRODROID_VENDOR_ROOT_DIGEST := $(PRODUCT_OUT)/microdroid_vendor_root_digest
+BOARD_AVB_VENDOR_ADD_HASHTREE_FOOTER_ARGS += \
+ --prop_from_file com.android.build.microdroid-vendor.root_digest:$(MICRODROID_VENDOR_ROOT_DIGEST)
+$(MICRODROID_VENDOR_ROOT_DIGEST): $(AVBTOOL) $(MICRODROID_VENDOR_IMAGE)
+ $(AVBTOOL) print_partition_digests \
+ --image $(MICRODROID_VENDOR_IMAGE) \
+ | tr -d '\n' | sed -E 's/.*: //g' > $@
+$(INSTALLED_VENDORIMAGE_TARGET): $(MICRODROID_VENDOR_ROOT_DIGEST)
endif
BOOT_FOOTER_ARGS := BOARD_AVB_BOOT_ADD_HASH_FOOTER_ARGS
@@ -6750,7 +6758,7 @@
$(BUILT_TARGET_FILES_PACKAGE): $(BUILT_TARGET_FILES_DIR)
@echo "Packaging target files: $@"
- $(hide) $(SOONG_ZIP) -d -o $@ -C $(zip_root) -r $@.list
+ $(hide) $(SOONG_ZIP) -d -o $@ -C $(zip_root) -r $@.list -sha256
.PHONY: target-files-package
target-files-package: $(BUILT_TARGET_FILES_PACKAGE)
diff --git a/core/config.mk b/core/config.mk
index f8a9879..4d79e2b 100644
--- a/core/config.mk
+++ b/core/config.mk
@@ -110,6 +110,7 @@
$(KATI_obsolete_var BUILD_BROKEN_ENG_DEBUG_TAGS)
$(KATI_obsolete_export It is a global setting. See $(CHANGES_URL)#export_keyword)
$(KATI_obsolete_var BUILD_BROKEN_ANDROIDMK_EXPORTS)
+$(KATI_obsolete_var PRODUCT_NOTICE_SPLIT_OVERRIDE,Stop using this, keep calm, and carry on.)
$(KATI_obsolete_var PRODUCT_STATIC_BOOT_CONTROL_HAL,Use shared library module instead. See $(CHANGES_URL)#PRODUCT_STATIC_BOOT_CONTROL_HAL)
$(KATI_obsolete_var \
ARCH_ARM_HAVE_ARMV7A \
@@ -777,16 +778,9 @@
PRODUCT_FULL_TREBLE := true
endif
-# TODO(b/69865032): Make PRODUCT_NOTICE_SPLIT the default behavior and remove
-# references to it here and below.
-ifdef PRODUCT_NOTICE_SPLIT_OVERRIDE
- $(error PRODUCT_NOTICE_SPLIT_OVERRIDE cannot be set.)
-endif
-
requirements := \
PRODUCT_TREBLE_LINKER_NAMESPACES \
- PRODUCT_ENFORCE_VINTF_MANIFEST \
- PRODUCT_NOTICE_SPLIT
+ PRODUCT_ENFORCE_VINTF_MANIFEST
# If it is overriden, then the requirement override is taken, otherwise it's
# PRODUCT_FULL_TREBLE
@@ -799,12 +793,20 @@
PRODUCT_FULL_TREBLE_OVERRIDE ?=
$(foreach req,$(requirements),$(eval $(req)_OVERRIDE ?=))
+# used to be a part of PRODUCT_FULL_TREBLE, but now always set it
+PRODUCT_NOTICE_SPLIT := true
+
# TODO(b/114488870): disallow PRODUCT_FULL_TREBLE_OVERRIDE from being used.
.KATI_READONLY := \
PRODUCT_FULL_TREBLE_OVERRIDE \
$(foreach req,$(requirements),$(req)_OVERRIDE) \
$(requirements) \
PRODUCT_FULL_TREBLE \
+ PRODUCT_NOTICE_SPLIT \
+
+ifneq ($(PRODUCT_FULL_TREBLE),true)
+ $(warning This device does not have Treble enabled. This is unsafe.)
+endif
$(KATI_obsolete_var $(foreach req,$(requirements),$(req)_OVERRIDE) \
,This should be referenced without the _OVERRIDE suffix.)
@@ -890,40 +892,23 @@
# SEPolicy versions
-# PLATFORM_SEPOLICY_VERSION is a number of the form "NN.m" with "NN" mapping to
-# PLATFORM_SDK_VERSION and "m" as a minor number which allows for SELinux
-# changes independent of PLATFORM_SDK_VERSION. This value will be set to
-# 10000.0 to represent tip-of-tree development that is inherently unstable and
-# thus designed not to work with any shipping vendor policy. This is similar in
-# spirit to how DEFAULT_APP_TARGET_SDK is set.
-# The minor version ('m' component) must be updated every time a platform release
-# is made which breaks compatibility with the previous platform sepolicy version,
-# not just on every increase in PLATFORM_SDK_VERSION. The minor version should
-# be reset to 0 on every bump of the PLATFORM_SDK_VERSION.
-sepolicy_major_vers := 34
-sepolicy_minor_vers := 0
+# PLATFORM_SEPOLICY_VERSION is a number of the form "YYYYMM.0" with "YYYYMM"
+# mapping to vFRC version. This value will be set to 1000000.0 to represent
+# tip-of-tree development that is inherently unstable and thus designed not to
+# work with any shipping vendor policy. This is similar in spirit to how
+# DEFAULT_APP_TARGET_SDK is set.
+sepolicy_vers := $(BOARD_API_LEVEL).0
-ifneq ($(sepolicy_major_vers), $(PLATFORM_SDK_VERSION))
-$(error sepolicy_major_version does not match PLATFORM_SDK_VERSION, please update.)
-endif
-
-TOT_SEPOLICY_VERSION := 10000.0
-ifneq (REL,$(PLATFORM_VERSION_CODENAME))
- PLATFORM_SEPOLICY_VERSION := $(TOT_SEPOLICY_VERSION)
+TOT_SEPOLICY_VERSION := 1000000.0
+ifeq (true,$(BOARD_API_LEVEL_FROZEN))
+ PLATFORM_SEPOLICY_VERSION := $(sepolicy_vers)
else
- PLATFORM_SEPOLICY_VERSION := $(join $(addsuffix .,$(sepolicy_major_vers)), $(sepolicy_minor_vers))
+ PLATFORM_SEPOLICY_VERSION := $(TOT_SEPOLICY_VERSION)
endif
-sepolicy_major_vers :=
-sepolicy_minor_vers :=
+sepolicy_vers :=
-# BOARD_SEPOLICY_VERS must take the format "NN.m" and contain the sepolicy
-# version identifier corresponding to the sepolicy on which the non-platform
-# policy is to be based. If unspecified, this will build against the current
-# public platform policy in tree
-ifndef BOARD_SEPOLICY_VERS
-# The default platform policy version.
BOARD_SEPOLICY_VERS := $(PLATFORM_SEPOLICY_VERSION)
-endif
+.KATI_READONLY := PLATFORM_SEPOLICY_VERSION BOARD_SEPOLICY_VERS
# A list of SEPolicy versions, besides PLATFORM_SEPOLICY_VERSION, that the framework supports.
PLATFORM_SEPOLICY_COMPAT_VERSIONS := $(filter-out $(PLATFORM_SEPOLICY_VERSION), \
diff --git a/envsetup.sh b/envsetup.sh
index cc808d2..212ed9f 100644
--- a/envsetup.sh
+++ b/envsetup.sh
@@ -62,7 +62,7 @@
invocations of 'm' etc.
- tapas: tapas [<App1> <App2> ...] [arm|x86|arm64|x86_64] [eng|userdebug|user]
Sets up the build environment for building unbundled apps (APKs).
-- banchan: banchan <module1> [<module2> ...] \
+- banchan: banchan <module1> [<module2> ...] \\
[arm|x86|arm64|riscv64|x86_64|arm64_only|x86_64only] [eng|userdebug|user]
Sets up the build environment for building unbundled modules (APEXes).
- croot: Changes directory to the top of the tree, or a subdirectory thereof.
diff --git a/target/product/OWNERS b/target/product/OWNERS
index 008e4a2..48d3f2a 100644
--- a/target/product/OWNERS
+++ b/target/product/OWNERS
@@ -1,4 +1,4 @@
-per-file runtime_libart.mk = calin@google.com, mast@google.com, ngeoffray@google.com, oth@google.com, rpl@google.com, vmarko@google.com
+per-file runtime_libart.mk = mast@google.com, ngeoffray@google.com, rpl@google.com, vmarko@google.com
# GSI
per-file gsi_release.mk = file:/target/product/gsi/OWNERS
@@ -7,4 +7,4 @@
# Android Go
per-file go_defaults.mk = gkaiser@google.com, kushg@google.com, rajekumar@google.com
per-file go_defaults_512.mk = gkaiser@google.com, kushg@google.com, rajekumar@google.com
-per-file go_defaults_common.mk = gkaiser@google.com, kushg@google.com, rajekumar@google.com
\ No newline at end of file
+per-file go_defaults_common.mk = gkaiser@google.com, kushg@google.com, rajekumar@google.com
diff --git a/tools/aconfig/Android.bp b/tools/aconfig/Android.bp
index 82bfa7e..37be2dd 100644
--- a/tools/aconfig/Android.bp
+++ b/tools/aconfig/Android.bp
@@ -44,7 +44,6 @@
protos: ["protos/aconfig.proto"],
crate_name: "aconfig_protos",
source_stem: "aconfig_protos",
- use_protobuf3: true,
host_supported: true,
}
@@ -174,6 +173,12 @@
mode: "test",
}
+cc_aconfig_library {
+ name: "aconfig_test_cpp_library_exported_variant",
+ aconfig_declarations: "aconfig.test.flags",
+ mode: "exported",
+}
+
cc_test {
name: "aconfig.test.cpp",
srcs: [
@@ -204,6 +209,21 @@
test_suites: ["general-tests"],
}
+cc_test {
+ name: "aconfig.test.cpp.exported_mode",
+ srcs: [
+ "tests/aconfig_exported_mode_test.cpp",
+ ],
+ static_libs: [
+ "aconfig_test_cpp_library_exported_variant",
+ "libgmock",
+ ],
+ shared_libs: [
+ "server_configurable_flags",
+ ],
+ test_suites: ["general-tests"],
+}
+
rust_aconfig_library {
name: "libaconfig_test_rust_library",
crate_name: "aconfig_test_rust_library",
@@ -238,3 +258,21 @@
],
test_suites: ["general-tests"],
}
+
+rust_aconfig_library {
+ name: "libaconfig_test_rust_library_with_exported_mode",
+ crate_name: "aconfig_test_rust_library",
+ aconfig_declarations: "aconfig.test.flags",
+ mode: "exported",
+}
+
+rust_test {
+ name: "aconfig.exported_mode.test.rust",
+ srcs: [
+ "tests/aconfig_exported_mode_test.rs"
+ ],
+ rustlibs: [
+ "libaconfig_test_rust_library_with_exported_mode",
+ ],
+ test_suites: ["general-tests"],
+}
diff --git a/tools/aconfig/TEST_MAPPING b/tools/aconfig/TEST_MAPPING
index e29918f..de8d932 100644
--- a/tools/aconfig/TEST_MAPPING
+++ b/tools/aconfig/TEST_MAPPING
@@ -24,6 +24,10 @@
"name": "aconfig.test"
},
{
+ // aconfig Java integration tests (host)
+ "name": "AconfigJavaHostTest"
+ },
+ {
// aconfig Java integration tests
"name": "aconfig.test.java"
},
@@ -36,14 +40,22 @@
"name": "aconfig.test.cpp.test_mode"
},
{
- // aconfig C++ integration tests (production mode auto-generated code)
+ // aconfig C++ integration tests (exported mode auto-generated code)
+ "name": "aconfig.test.cpp.exported_mode"
+ },
+ {
+ // aconfig Rust integration tests (production mode auto-generated code)
"name": "aconfig.prod_mode.test.rust"
},
{
- // aconfig C++ integration tests (test mode auto-generated code)
+ // aconfig Rust integration tests (test mode auto-generated code)
"name": "aconfig.test_mode.test.rust"
},
{
+ // aconfig Rust integration tests (exported mode auto-generated code)
+ "name": "aconfig.exported_mode.test.rust"
+ },
+ {
// printflags unit tests
"name": "printflags.test"
}
diff --git a/tools/aconfig/src/codegen/cpp.rs b/tools/aconfig/src/codegen/cpp.rs
index 568302d..d6bebba 100644
--- a/tools/aconfig/src/codegen/cpp.rs
+++ b/tools/aconfig/src/codegen/cpp.rs
@@ -20,7 +20,8 @@
use tinytemplate::TinyTemplate;
use crate::codegen;
-use crate::commands::{CodegenMode, OutputFile};
+use crate::codegen::CodegenMode;
+use crate::commands::OutputFile;
use crate::protos::{ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag};
pub fn generate_cpp_code<I>(
@@ -49,7 +50,9 @@
has_fixed_read_only,
readwrite,
readwrite_count,
- for_test: codegen_mode == CodegenMode::Test,
+ is_test_mode: codegen_mode == CodegenMode::Test,
+ is_prod_mode: codegen_mode == CodegenMode::Production,
+ is_exported_mode: codegen_mode == CodegenMode::Exported,
class_elements,
};
@@ -92,7 +95,9 @@
pub has_fixed_read_only: bool,
pub readwrite: bool,
pub readwrite_count: i32,
- pub for_test: bool,
+ pub is_test_mode: bool,
+ pub is_prod_mode: bool,
+ pub is_exported_mode: bool,
pub class_elements: Vec<ClassElement>,
}
@@ -149,6 +154,10 @@
#define COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO true
#endif
+#ifndef COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO_EXPORTED
+#define COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO_EXPORTED true
+#endif
+
#ifdef __cplusplus
#include <memory>
@@ -169,6 +178,8 @@
virtual bool enabled_fixed_ro() = 0;
+ virtual bool enabled_fixed_ro_exported() = 0;
+
virtual bool enabled_ro() = 0;
virtual bool enabled_ro_exported() = 0;
@@ -198,6 +209,10 @@
return COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO;
}
+inline bool enabled_fixed_ro_exported() {
+ return COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO_EXPORTED;
+}
+
inline bool enabled_ro() {
return true;
}
@@ -225,6 +240,8 @@
bool com_android_aconfig_test_enabled_fixed_ro();
+bool com_android_aconfig_test_enabled_fixed_ro_exported();
+
bool com_android_aconfig_test_enabled_ro();
bool com_android_aconfig_test_enabled_ro_exported();
@@ -270,6 +287,10 @@
virtual void enabled_fixed_ro(bool val) = 0;
+ virtual bool enabled_fixed_ro_exported() = 0;
+
+ virtual void enabled_fixed_ro_exported(bool val) = 0;
+
virtual bool enabled_ro() = 0;
virtual void enabled_ro(bool val) = 0;
@@ -327,6 +348,14 @@
provider_->enabled_fixed_ro(val);
}
+inline bool enabled_fixed_ro_exported() {
+ return provider_->enabled_fixed_ro_exported();
+}
+
+inline void enabled_fixed_ro_exported(bool val) {
+ provider_->enabled_fixed_ro_exported(val);
+}
+
inline bool enabled_ro() {
return provider_->enabled_ro();
}
@@ -380,6 +409,10 @@
void set_com_android_aconfig_test_enabled_fixed_ro(bool val);
+bool com_android_aconfig_test_enabled_fixed_ro_exported();
+
+void set_com_android_aconfig_test_enabled_fixed_ro_exported(bool val);
+
bool com_android_aconfig_test_enabled_ro();
void set_com_android_aconfig_test_enabled_ro(bool val);
@@ -402,6 +435,56 @@
"#;
+ const EXPORTED_EXPORTED_HEADER_EXPECTED: &str = r#"
+#pragma once
+
+#ifdef __cplusplus
+
+#include <memory>
+
+namespace com::android::aconfig::test {
+
+class flag_provider_interface {
+public:
+ virtual ~flag_provider_interface() = default;
+
+ virtual bool disabled_rw_exported() = 0;
+
+ virtual bool enabled_fixed_ro_exported() = 0;
+
+ virtual bool enabled_ro_exported() = 0;
+};
+
+extern std::unique_ptr<flag_provider_interface> provider_;
+
+inline bool disabled_rw_exported() {
+ return provider_->disabled_rw_exported();
+}
+
+inline bool enabled_fixed_ro_exported() {
+ return provider_->enabled_fixed_ro_exported();
+}
+
+inline bool enabled_ro_exported() {
+ return provider_->enabled_ro_exported();
+}
+
+}
+
+extern "C" {
+#endif // __cplusplus
+
+bool com_android_aconfig_test_disabled_rw_exported();
+
+bool com_android_aconfig_test_enabled_fixed_ro_exported();
+
+bool com_android_aconfig_test_enabled_ro_exported();
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+"#;
+
const PROD_SOURCE_FILE_EXPECTED: &str = r#"
#include "com_android_aconfig_test.h"
#include <server_configurable_flags/get_flags.h>
@@ -450,6 +533,10 @@
return COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO;
}
+ virtual bool enabled_fixed_ro_exported() override {
+ return COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO_EXPORTED;
+ }
+
virtual bool enabled_ro() override {
return true;
}
@@ -496,6 +583,10 @@
return COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO;
}
+bool com_android_aconfig_test_enabled_fixed_ro_exported() {
+ return COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO_EXPORTED;
+}
+
bool com_android_aconfig_test_enabled_ro() {
return true;
}
@@ -601,6 +692,19 @@
overrides_["enabled_fixed_ro"] = val;
}
+ virtual bool enabled_fixed_ro_exported() override {
+ auto it = overrides_.find("enabled_fixed_ro_exported");
+ if (it != overrides_.end()) {
+ return it->second;
+ } else {
+ return true;
+ }
+ }
+
+ virtual void enabled_fixed_ro_exported(bool val) override {
+ overrides_["enabled_fixed_ro_exported"] = val;
+ }
+
virtual bool enabled_ro() override {
auto it = overrides_.find("enabled_ro");
if (it != overrides_.end()) {
@@ -697,6 +801,13 @@
com::android::aconfig::test::enabled_fixed_ro(val);
}
+bool com_android_aconfig_test_enabled_fixed_ro_exported() {
+ return com::android::aconfig::test::enabled_fixed_ro_exported();
+}
+
+void set_com_android_aconfig_test_enabled_fixed_ro_exported(bool val) {
+ com::android::aconfig::test::enabled_fixed_ro_exported(val);
+}
bool com_android_aconfig_test_enabled_ro() {
return com::android::aconfig::test::enabled_ro();
@@ -733,6 +844,68 @@
"#;
+ const EXPORTED_SOURCE_FILE_EXPECTED: &str = r#"
+#include "com_android_aconfig_test.h"
+#include <server_configurable_flags/get_flags.h>
+#include <vector>
+
+namespace com::android::aconfig::test {
+
+ class flag_provider : public flag_provider_interface {
+ public:
+ virtual bool disabled_rw_exported() override {
+ if (cache_[0] == -1) {
+ cache_[0] = server_configurable_flags::GetServerConfigurableFlag(
+ "aconfig_flags.aconfig_test",
+ "com.android.aconfig.test.disabled_rw_exported",
+ "false") == "true";
+ }
+ return cache_[0];
+ }
+
+ virtual bool enabled_fixed_ro_exported() override {
+ if (cache_[1] == -1) {
+ cache_[1] = server_configurable_flags::GetServerConfigurableFlag(
+ "aconfig_flags.aconfig_test",
+ "com.android.aconfig.test.enabled_fixed_ro_exported",
+ "false") == "true";
+ }
+ return cache_[1];
+ }
+
+ virtual bool enabled_ro_exported() override {
+ if (cache_[2] == -1) {
+ cache_[2] = server_configurable_flags::GetServerConfigurableFlag(
+ "aconfig_flags.aconfig_test",
+ "com.android.aconfig.test.enabled_ro_exported",
+ "false") == "true";
+ }
+ return cache_[2];
+ }
+
+ private:
+ std::vector<int8_t> cache_ = std::vector<int8_t>(3, -1);
+ };
+
+ std::unique_ptr<flag_provider_interface> provider_ =
+ std::make_unique<flag_provider>();
+}
+
+bool com_android_aconfig_test_disabled_rw_exported() {
+ return com::android::aconfig::test::disabled_rw_exported();
+}
+
+bool com_android_aconfig_test_enabled_fixed_ro_exported() {
+ return com::android::aconfig::test::enabled_fixed_ro_exported();
+}
+
+bool com_android_aconfig_test_enabled_ro_exported() {
+ return com::android::aconfig::test::enabled_ro_exported();
+}
+
+
+"#;
+
const READ_ONLY_EXPORTED_PROD_HEADER_EXPECTED: &str = r#"
#pragma once
@@ -854,12 +1027,11 @@
expected_header: &str,
expected_src: &str,
) {
- let generated = generate_cpp_code(
- crate::test::TEST_PACKAGE,
- parsed_flags.parsed_flag.into_iter(),
- mode,
- )
- .unwrap();
+ let modified_parsed_flags =
+ crate::commands::modify_parsed_flags_based_on_mode(parsed_flags, mode).unwrap();
+ let generated =
+ generate_cpp_code(crate::test::TEST_PACKAGE, modified_parsed_flags.into_iter(), mode)
+ .unwrap();
let mut generated_files_map = HashMap::new();
for file in generated {
generated_files_map.insert(
@@ -912,6 +1084,17 @@
}
#[test]
+ fn test_generate_cpp_code_for_exported() {
+ let parsed_flags = crate::test::parse_test_flags();
+ test_generate_cpp_code(
+ parsed_flags,
+ CodegenMode::Exported,
+ EXPORTED_EXPORTED_HEADER_EXPECTED,
+ EXPORTED_SOURCE_FILE_EXPECTED,
+ );
+ }
+
+ #[test]
fn test_generate_cpp_code_for_read_only_prod() {
let parsed_flags = crate::test::parse_read_only_test_flags();
test_generate_cpp_code(
diff --git a/tools/aconfig/src/codegen/java.rs b/tools/aconfig/src/codegen/java.rs
index 1d5dabf..a02a7e2 100644
--- a/tools/aconfig/src/codegen/java.rs
+++ b/tools/aconfig/src/codegen/java.rs
@@ -14,14 +14,15 @@
* limitations under the License.
*/
-use anyhow::{anyhow, Result};
+use anyhow::Result;
use serde::Serialize;
use std::collections::{BTreeMap, BTreeSet};
use std::path::PathBuf;
use tinytemplate::TinyTemplate;
use crate::codegen;
-use crate::commands::{CodegenMode, OutputFile};
+use crate::codegen::CodegenMode;
+use crate::commands::OutputFile;
use crate::protos::{ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag};
pub fn generate_java_code<I>(
@@ -34,8 +35,6 @@
{
let flag_elements: Vec<FlagElement> =
parsed_flags_iter.map(|pf| create_flag_element(package, &pf)).collect();
- let exported_flag_elements: Vec<FlagElement> =
- flag_elements.iter().filter(|elem| elem.exported).cloned().collect();
let namespace_flags = gen_flags_by_namespace(&flag_elements);
let properties_set: BTreeSet<String> =
flag_elements.iter().map(|fe| format_property_name(&fe.device_config_namespace)).collect();
@@ -44,13 +43,8 @@
let runtime_lookup_required =
flag_elements.iter().any(|elem| elem.is_read_write) || library_exported;
- if library_exported && exported_flag_elements.is_empty() {
- return Err(anyhow!("exported library contains no exported flags"));
- }
-
let context = Context {
flag_elements,
- exported_flag_elements,
namespace_flags,
is_test_mode,
runtime_lookup_required,
@@ -109,7 +103,6 @@
#[derive(Serialize)]
struct Context {
pub flag_elements: Vec<FlagElement>,
- pub exported_flag_elements: Vec<FlagElement>,
pub namespace_flags: Vec<NamespaceFlags>,
pub is_test_mode: bool,
pub runtime_lookup_required: bool,
@@ -133,7 +126,6 @@
pub is_read_write: bool,
pub method_name: String,
pub properties: String,
- pub exported: bool,
}
fn create_flag_element(package: &str, pf: &ProtoParsedFlag) -> FlagElement {
@@ -147,7 +139,6 @@
is_read_write: pf.permission() == ProtoFlagPermission::READ_WRITE,
method_name: format_java_method_name(pf.name()),
properties: format_property_name(pf.namespace()),
- exported: pf.is_exported.unwrap_or(false),
}
}
@@ -202,6 +193,9 @@
boolean enabledFixedRo();
@com.android.aconfig.annotations.AssumeTrueForR8
@UnsupportedAppUsage
+ boolean enabledFixedRoExported();
+ @com.android.aconfig.annotations.AssumeTrueForR8
+ @UnsupportedAppUsage
boolean enabledRo();
@com.android.aconfig.annotations.AssumeTrueForR8
@UnsupportedAppUsage
@@ -228,6 +222,8 @@
/** @hide */
public static final String FLAG_ENABLED_FIXED_RO = "com.android.aconfig.test.enabled_fixed_ro";
/** @hide */
+ public static final String FLAG_ENABLED_FIXED_RO_EXPORTED = "com.android.aconfig.test.enabled_fixed_ro_exported";
+ /** @hide */
public static final String FLAG_ENABLED_RO = "com.android.aconfig.test.enabled_ro";
/** @hide */
public static final String FLAG_ENABLED_RO_EXPORTED = "com.android.aconfig.test.enabled_ro_exported";
@@ -258,6 +254,11 @@
}
@com.android.aconfig.annotations.AssumeTrueForR8
@UnsupportedAppUsage
+ public static boolean enabledFixedRoExported() {
+ return FEATURE_FLAGS.enabledFixedRoExported();
+ }
+ @com.android.aconfig.annotations.AssumeTrueForR8
+ @UnsupportedAppUsage
public static boolean enabledRo() {
return FEATURE_FLAGS.enabledRo();
}
@@ -310,6 +311,11 @@
}
@Override
@UnsupportedAppUsage
+ public boolean enabledFixedRoExported() {
+ return getValue(Flags.FLAG_ENABLED_FIXED_RO_EXPORTED);
+ }
+ @Override
+ @UnsupportedAppUsage
public boolean enabledRo() {
return getValue(Flags.FLAG_ENABLED_RO);
}
@@ -348,6 +354,7 @@
Map.entry(Flags.FLAG_DISABLED_RW_EXPORTED, false),
Map.entry(Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE, false),
Map.entry(Flags.FLAG_ENABLED_FIXED_RO, false),
+ Map.entry(Flags.FLAG_ENABLED_FIXED_RO_EXPORTED, false),
Map.entry(Flags.FLAG_ENABLED_RO, false),
Map.entry(Flags.FLAG_ENABLED_RO_EXPORTED, false),
Map.entry(Flags.FLAG_ENABLED_RW, false)
@@ -359,12 +366,12 @@
#[test]
fn test_generate_java_code_production() {
let parsed_flags = crate::test::parse_test_flags();
- let generated_files = generate_java_code(
- crate::test::TEST_PACKAGE,
- parsed_flags.parsed_flag.into_iter(),
- CodegenMode::Production,
- )
- .unwrap();
+ let mode = CodegenMode::Production;
+ let modified_parsed_flags =
+ crate::commands::modify_parsed_flags_based_on_mode(parsed_flags, mode).unwrap();
+ let generated_files =
+ generate_java_code(crate::test::TEST_PACKAGE, modified_parsed_flags.into_iter(), mode)
+ .unwrap();
let expect_flags_content = EXPECTED_FLAG_COMMON_CONTENT.to_string()
+ r#"
private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl();
@@ -463,6 +470,11 @@
}
@Override
@UnsupportedAppUsage
+ public boolean enabledFixedRoExported() {
+ return true;
+ }
+ @Override
+ @UnsupportedAppUsage
public boolean enabledRo() {
return true;
}
@@ -512,12 +524,12 @@
#[test]
fn test_generate_java_code_exported() {
let parsed_flags = crate::test::parse_test_flags();
- let generated_files = generate_java_code(
- crate::test::TEST_PACKAGE,
- parsed_flags.parsed_flag.into_iter(),
- CodegenMode::Exported,
- )
- .unwrap();
+ let mode = CodegenMode::Exported;
+ let modified_parsed_flags =
+ crate::commands::modify_parsed_flags_based_on_mode(parsed_flags, mode).unwrap();
+ let generated_files =
+ generate_java_code(crate::test::TEST_PACKAGE, modified_parsed_flags.into_iter(), mode)
+ .unwrap();
let expect_flags_content = r#"
package com.android.aconfig.test;
@@ -528,6 +540,8 @@
/** @hide */
public static final String FLAG_DISABLED_RW_EXPORTED = "com.android.aconfig.test.disabled_rw_exported";
/** @hide */
+ public static final String FLAG_ENABLED_FIXED_RO_EXPORTED = "com.android.aconfig.test.enabled_fixed_ro_exported";
+ /** @hide */
public static final String FLAG_ENABLED_RO_EXPORTED = "com.android.aconfig.test.enabled_ro_exported";
@UnsupportedAppUsage
@@ -535,6 +549,10 @@
return FEATURE_FLAGS.disabledRwExported();
}
@UnsupportedAppUsage
+ public static boolean enabledFixedRoExported() {
+ return FEATURE_FLAGS.enabledFixedRoExported();
+ }
+ @UnsupportedAppUsage
public static boolean enabledRoExported() {
return FEATURE_FLAGS.enabledRoExported();
}
@@ -551,6 +569,8 @@
@UnsupportedAppUsage
boolean disabledRwExported();
@UnsupportedAppUsage
+ boolean enabledFixedRoExported();
+ @UnsupportedAppUsage
boolean enabledRoExported();
}
"#;
@@ -564,8 +584,8 @@
/** @hide */
public final class FeatureFlagsImpl implements FeatureFlags {
private static boolean aconfig_test_is_cached = false;
- private static boolean other_namespace_is_cached = false;
private static boolean disabledRwExported = false;
+ private static boolean enabledFixedRoExported = false;
private static boolean enabledRoExported = false;
@@ -574,6 +594,8 @@
Properties properties = DeviceConfig.getProperties("aconfig_test");
disabledRwExported =
properties.getBoolean("com.android.aconfig.test.disabled_rw_exported", false);
+ enabledFixedRoExported =
+ properties.getBoolean("com.android.aconfig.test.enabled_fixed_ro_exported", false);
enabledRoExported =
properties.getBoolean("com.android.aconfig.test.enabled_ro_exported", false);
} catch (NullPointerException e) {
@@ -589,22 +611,6 @@
aconfig_test_is_cached = true;
}
- private void load_overrides_other_namespace() {
- try {
- Properties properties = DeviceConfig.getProperties("other_namespace");
- } catch (NullPointerException e) {
- throw new RuntimeException(
- "Cannot read value from namespace other_namespace "
- + "from DeviceConfig. It could be that the code using flag "
- + "executed before SettingsProvider initialization. Please use "
- + "fixed read-only flag by adding is_fixed_read_only: true in "
- + "flag declaration.",
- e
- );
- }
- other_namespace_is_cached = true;
- }
-
@Override
@UnsupportedAppUsage
public boolean disabledRwExported() {
@@ -616,6 +622,15 @@
@Override
@UnsupportedAppUsage
+ public boolean enabledFixedRoExported() {
+ if (!aconfig_test_is_cached) {
+ load_overrides_aconfig_test();
+ }
+ return enabledFixedRoExported;
+ }
+
+ @Override
+ @UnsupportedAppUsage
public boolean enabledRoExported() {
if (!aconfig_test_is_cached) {
load_overrides_aconfig_test();
@@ -642,6 +657,11 @@
}
@Override
@UnsupportedAppUsage
+ public boolean enabledFixedRoExported() {
+ return getValue(Flags.FLAG_ENABLED_FIXED_RO_EXPORTED);
+ }
+ @Override
+ @UnsupportedAppUsage
public boolean enabledRoExported() {
return getValue(Flags.FLAG_ENABLED_RO_EXPORTED);
}
@@ -666,6 +686,7 @@
private Map<String, Boolean> mFlagMap = new HashMap<>(
Map.ofEntries(
Map.entry(Flags.FLAG_DISABLED_RW_EXPORTED, false),
+ Map.entry(Flags.FLAG_ENABLED_FIXED_RO_EXPORTED, false),
Map.entry(Flags.FLAG_ENABLED_RO_EXPORTED, false)
)
);
@@ -703,12 +724,12 @@
#[test]
fn test_generate_java_code_test() {
let parsed_flags = crate::test::parse_test_flags();
- let generated_files = generate_java_code(
- crate::test::TEST_PACKAGE,
- parsed_flags.parsed_flag.into_iter(),
- CodegenMode::Test,
- )
- .unwrap();
+ let mode = CodegenMode::Test;
+ let modified_parsed_flags =
+ crate::commands::modify_parsed_flags_based_on_mode(parsed_flags, mode).unwrap();
+ let generated_files =
+ generate_java_code(crate::test::TEST_PACKAGE, modified_parsed_flags.into_iter(), mode)
+ .unwrap();
let expect_flags_content = EXPECTED_FLAG_COMMON_CONTENT.to_string()
+ r#"
@@ -759,6 +780,12 @@
}
@Override
@UnsupportedAppUsage
+ public boolean enabledFixedRoExported() {
+ throw new UnsupportedOperationException(
+ "Method is not implemented.");
+ }
+ @Override
+ @UnsupportedAppUsage
public boolean enabledRo() {
throw new UnsupportedOperationException(
"Method is not implemented.");
diff --git a/tools/aconfig/src/codegen/mod.rs b/tools/aconfig/src/codegen/mod.rs
index abc27c6..476d2b3 100644
--- a/tools/aconfig/src/codegen/mod.rs
+++ b/tools/aconfig/src/codegen/mod.rs
@@ -19,6 +19,7 @@
pub mod rust;
use anyhow::{ensure, Result};
+use clap::ValueEnum;
pub fn is_valid_name_ident(s: &str) -> bool {
// Identifiers must match [a-z][a-z0-9_]*, except consecutive underscores are not allowed
@@ -52,6 +53,23 @@
Ok(format!("{}.{}", package, flag_name))
}
+#[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)]
+pub enum CodegenMode {
+ Exported,
+ Production,
+ Test,
+}
+
+impl std::fmt::Display for CodegenMode {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ match self {
+ CodegenMode::Exported => write!(f, "exported"),
+ CodegenMode::Production => write!(f, "production"),
+ CodegenMode::Test => write!(f, "test"),
+ }
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
diff --git a/tools/aconfig/src/codegen/rust.rs b/tools/aconfig/src/codegen/rust.rs
index 7aafddb..56cb311 100644
--- a/tools/aconfig/src/codegen/rust.rs
+++ b/tools/aconfig/src/codegen/rust.rs
@@ -19,7 +19,8 @@
use tinytemplate::TinyTemplate;
use crate::codegen;
-use crate::commands::{CodegenMode, OutputFile};
+use crate::codegen::CodegenMode;
+use crate::commands::OutputFile;
use crate::protos::{ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag};
pub fn generate_rust_code<I>(
@@ -45,9 +46,7 @@
match codegen_mode {
CodegenMode::Production => include_str!("../../templates/rust_prod.template"),
CodegenMode::Test => include_str!("../../templates/rust_test.template"),
- CodegenMode::Exported => {
- todo!("exported mode not yet supported for rust, see b/313894653.")
- }
+ CodegenMode::Exported => include_str!("../../templates/rust_exported.template"),
},
)?;
let contents = template.render("rust_code_gen", &context)?;
@@ -153,6 +152,11 @@
true
}
+ /// query flag enabled_fixed_ro_exported
+ pub fn enabled_fixed_ro_exported(&self) -> bool {
+ true
+ }
+
/// query flag enabled_ro
pub fn enabled_ro(&self) -> bool {
true
@@ -202,6 +206,12 @@
true
}
+/// query flag enabled_fixed_ro_exported
+#[inline(always)]
+pub fn enabled_fixed_ro_exported() -> bool {
+ true
+}
+
/// query flag enabled_ro
#[inline(always)]
pub fn enabled_ro() -> bool {
@@ -302,6 +312,18 @@
self.overrides.insert("enabled_fixed_ro", val);
}
+ /// query flag enabled_fixed_ro_exported
+ pub fn enabled_fixed_ro_exported(&self) -> bool {
+ self.overrides.get("enabled_fixed_ro_exported").copied().unwrap_or(
+ true
+ )
+ }
+
+ /// set flag enabled_fixed_ro_exported
+ pub fn set_enabled_fixed_ro_exported(&mut self, val: bool) {
+ self.overrides.insert("enabled_fixed_ro_exported", val);
+ }
+
/// query flag enabled_ro
pub fn enabled_ro(&self) -> bool {
self.overrides.get("enabled_ro").copied().unwrap_or(
@@ -412,6 +434,18 @@
PROVIDER.lock().unwrap().set_enabled_fixed_ro(val);
}
+/// query flag enabled_fixed_ro_exported
+#[inline(always)]
+pub fn enabled_fixed_ro_exported() -> bool {
+ PROVIDER.lock().unwrap().enabled_fixed_ro_exported()
+}
+
+/// set flag enabled_fixed_ro_exported
+#[inline(always)]
+pub fn set_enabled_fixed_ro_exported(val: bool) {
+ PROVIDER.lock().unwrap().set_enabled_fixed_ro_exported(val);
+}
+
/// query flag enabled_ro
#[inline(always)]
pub fn enabled_ro() -> bool {
@@ -454,14 +488,79 @@
}
"#;
+ const EXPORTED_EXPECTED: &str = r#"
+//! codegenerated rust flag lib
+
+/// flag provider
+pub struct FlagProvider;
+
+lazy_static::lazy_static! {
+ /// flag value cache for disabled_rw_exported
+ static ref CACHED_disabled_rw_exported: bool = flags_rust::GetServerConfigurableFlag(
+ "aconfig_flags.aconfig_test",
+ "com.android.aconfig.test.disabled_rw_exported",
+ "false") == "true";
+
+ /// flag value cache for enabled_fixed_ro_exported
+ static ref CACHED_enabled_fixed_ro_exported: bool = flags_rust::GetServerConfigurableFlag(
+ "aconfig_flags.aconfig_test",
+ "com.android.aconfig.test.enabled_fixed_ro_exported",
+ "false") == "true";
+
+ /// flag value cache for enabled_ro_exported
+ static ref CACHED_enabled_ro_exported: bool = flags_rust::GetServerConfigurableFlag(
+ "aconfig_flags.aconfig_test",
+ "com.android.aconfig.test.enabled_ro_exported",
+ "false") == "true";
+
+}
+
+impl FlagProvider {
+ /// query flag disabled_rw_exported
+ pub fn disabled_rw_exported(&self) -> bool {
+ *CACHED_disabled_rw_exported
+ }
+
+ /// query flag enabled_fixed_ro_exported
+ pub fn enabled_fixed_ro_exported(&self) -> bool {
+ *CACHED_enabled_fixed_ro_exported
+ }
+
+ /// query flag enabled_ro_exported
+ pub fn enabled_ro_exported(&self) -> bool {
+ *CACHED_enabled_ro_exported
+ }
+}
+
+/// flag provider
+pub static PROVIDER: FlagProvider = FlagProvider;
+
+/// query flag disabled_rw_exported
+#[inline(always)]
+pub fn disabled_rw_exported() -> bool {
+ PROVIDER.disabled_rw_exported()
+}
+
+/// query flag enabled_fixed_ro_exported
+#[inline(always)]
+pub fn enabled_fixed_ro_exported() -> bool {
+ PROVIDER.enabled_fixed_ro_exported()
+}
+
+/// query flag enabled_ro_exported
+#[inline(always)]
+pub fn enabled_ro_exported() -> bool {
+ PROVIDER.enabled_ro_exported()
+}
+"#;
+
fn test_generate_rust_code(mode: CodegenMode) {
let parsed_flags = crate::test::parse_test_flags();
- let generated = generate_rust_code(
- crate::test::TEST_PACKAGE,
- parsed_flags.parsed_flag.into_iter(),
- mode,
- )
- .unwrap();
+ let modified_parsed_flags =
+ crate::commands::modify_parsed_flags_based_on_mode(parsed_flags, mode).unwrap();
+ let generated =
+ generate_rust_code(crate::test::TEST_PACKAGE, modified_parsed_flags.into_iter(), mode)
+ .unwrap();
assert_eq!("src/lib.rs", format!("{}", generated.path.display()));
assert_eq!(
None,
@@ -469,8 +568,7 @@
match mode {
CodegenMode::Production => PROD_EXPECTED,
CodegenMode::Test => TEST_EXPECTED,
- CodegenMode::Exported =>
- todo!("exported mode not yet supported for rust, see b/313894653."),
+ CodegenMode::Exported => EXPORTED_EXPECTED,
},
&String::from_utf8(generated.contents).unwrap()
)
@@ -486,4 +584,9 @@
fn test_generate_rust_code_for_test() {
test_generate_rust_code(CodegenMode::Test);
}
+
+ #[test]
+ fn test_generate_rust_code_for_exported() {
+ test_generate_rust_code(CodegenMode::Exported);
+ }
}
diff --git a/tools/aconfig/src/commands.rs b/tools/aconfig/src/commands.rs
index 87905fd..a121b5e 100644
--- a/tools/aconfig/src/commands.rs
+++ b/tools/aconfig/src/commands.rs
@@ -15,14 +15,16 @@
*/
use anyhow::{bail, ensure, Context, Result};
-use clap::ValueEnum;
+use itertools::Itertools;
use protobuf::Message;
+use std::collections::HashMap;
use std::io::Read;
use std::path::PathBuf;
use crate::codegen::cpp::generate_cpp_code;
use crate::codegen::java::generate_java_code;
use crate::codegen::rust::generate_rust_code;
+use crate::codegen::CodegenMode;
use crate::dump::{DumpFormat, DumpPredicate};
use crate::protos::{
ParsedFlagExt, ProtoFlagMetadata, ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag,
@@ -188,41 +190,37 @@
Ok(output)
}
-#[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)]
-pub enum CodegenMode {
- Production,
- Test,
- Exported,
-}
-
pub fn create_java_lib(mut input: Input, codegen_mode: CodegenMode) -> Result<Vec<OutputFile>> {
let parsed_flags = input.try_parse_flags()?;
- let filtered_parsed_flags = filter_parsed_flags(parsed_flags, codegen_mode);
- let Some(package) = find_unique_package(&filtered_parsed_flags) else {
+ let modified_parsed_flags = modify_parsed_flags_based_on_mode(parsed_flags, codegen_mode)?;
+ let Some(package) = find_unique_package(&modified_parsed_flags) else {
bail!("no parsed flags, or the parsed flags use different packages");
};
let package = package.to_string();
- generate_java_code(&package, filtered_parsed_flags.into_iter(), codegen_mode)
+ let _flag_ids = assign_flag_ids(&package, modified_parsed_flags.iter())?;
+ generate_java_code(&package, modified_parsed_flags.into_iter(), codegen_mode)
}
pub fn create_cpp_lib(mut input: Input, codegen_mode: CodegenMode) -> Result<Vec<OutputFile>> {
let parsed_flags = input.try_parse_flags()?;
- let filtered_parsed_flags = filter_parsed_flags(parsed_flags, codegen_mode);
- let Some(package) = find_unique_package(&filtered_parsed_flags) else {
+ let modified_parsed_flags = modify_parsed_flags_based_on_mode(parsed_flags, codegen_mode)?;
+ let Some(package) = find_unique_package(&modified_parsed_flags) else {
bail!("no parsed flags, or the parsed flags use different packages");
};
let package = package.to_string();
- generate_cpp_code(&package, filtered_parsed_flags.into_iter(), codegen_mode)
+ let _flag_ids = assign_flag_ids(&package, modified_parsed_flags.iter())?;
+ generate_cpp_code(&package, modified_parsed_flags.into_iter(), codegen_mode)
}
pub fn create_rust_lib(mut input: Input, codegen_mode: CodegenMode) -> Result<OutputFile> {
let parsed_flags = input.try_parse_flags()?;
- let filtered_parsed_flags = filter_parsed_flags(parsed_flags, codegen_mode);
- let Some(package) = find_unique_package(&filtered_parsed_flags) else {
+ let modified_parsed_flags = modify_parsed_flags_based_on_mode(parsed_flags, codegen_mode)?;
+ let Some(package) = find_unique_package(&modified_parsed_flags) else {
bail!("no parsed flags, or the parsed flags use different packages");
};
let package = package.to_string();
- generate_rust_code(&package, filtered_parsed_flags.into_iter(), codegen_mode)
+ let _flag_ids = assign_flag_ids(&package, modified_parsed_flags.iter())?;
+ generate_rust_code(&package, modified_parsed_flags.into_iter(), codegen_mode)
}
pub fn create_storage(caches: Vec<Input>, container: &str) -> Result<Vec<OutputFile>> {
@@ -323,16 +321,48 @@
Some(container)
}
-fn filter_parsed_flags(
+pub fn modify_parsed_flags_based_on_mode(
parsed_flags: ProtoParsedFlags,
codegen_mode: CodegenMode,
-) -> Vec<ProtoParsedFlag> {
- match codegen_mode {
- CodegenMode::Exported => {
- parsed_flags.parsed_flag.into_iter().filter(|pf| pf.is_exported()).collect()
- }
- _ => parsed_flags.parsed_flag,
+) -> Result<Vec<ProtoParsedFlag>> {
+ fn exported_mode_flag_modifier(mut parsed_flag: ProtoParsedFlag) -> ProtoParsedFlag {
+ parsed_flag.set_state(ProtoFlagState::DISABLED);
+ parsed_flag.set_permission(ProtoFlagPermission::READ_WRITE);
+ parsed_flag.set_is_fixed_read_only(false);
+ parsed_flag
}
+
+ let modified_parsed_flags: Vec<_> = match codegen_mode {
+ CodegenMode::Exported => parsed_flags
+ .parsed_flag
+ .into_iter()
+ .filter(|pf| pf.is_exported())
+ .map(exported_mode_flag_modifier)
+ .collect(),
+ CodegenMode::Production | CodegenMode::Test => {
+ parsed_flags.parsed_flag.into_iter().collect()
+ }
+ };
+ if modified_parsed_flags.is_empty() {
+ bail!("{codegen_mode} library contains no {codegen_mode} flags");
+ }
+
+ Ok(modified_parsed_flags)
+}
+
+fn assign_flag_ids<'a, I>(package: &str, parsed_flags_iter: I) -> Result<HashMap<String, u32>>
+where
+ I: Iterator<Item = &'a ProtoParsedFlag> + Clone,
+{
+ assert!(parsed_flags_iter.clone().tuple_windows().all(|(a, b)| a.name() <= b.name()));
+ let mut flag_ids = HashMap::new();
+ for (id_to_assign, pf) in (0_u32..).zip(parsed_flags_iter) {
+ if package != pf.package() {
+ return Err(anyhow::anyhow!("encountered a flag not in current package"));
+ }
+ flag_ids.insert(pf.name().to_string(), id_to_assign);
+ }
+ Ok(flag_ids)
}
#[cfg(test)]
@@ -366,9 +396,9 @@
assert_eq!(ProtoFlagState::ENABLED, enabled_ro.trace[2].state());
assert_eq!(ProtoFlagPermission::READ_ONLY, enabled_ro.trace[2].permission());
- assert_eq!(8, parsed_flags.parsed_flag.len());
+ assert_eq!(9, parsed_flags.parsed_flag.len());
for pf in parsed_flags.parsed_flag.iter() {
- if pf.name() == "enabled_fixed_ro" {
+ if pf.name().starts_with("enabled_fixed_ro") {
continue;
}
let first = pf.trace.first().unwrap();
@@ -597,24 +627,13 @@
let bytes =
dump_parsed_flags(vec![input, input2], DumpFormat::Textproto, &[], true).unwrap();
let text = std::str::from_utf8(&bytes).unwrap();
- assert_eq!(crate::test::TEST_FLAGS_TEXTPROTO.trim(), text.trim());
- }
-
- #[test]
- fn test_filter_parsed_flags() {
- let mut input = parse_test_flags_as_input();
- let parsed_flags = input.try_parse_flags().unwrap();
-
- let filtered_parsed_flags =
- filter_parsed_flags(parsed_flags.clone(), CodegenMode::Exported);
- assert_eq!(2, filtered_parsed_flags.len());
-
- let filtered_parsed_flags =
- filter_parsed_flags(parsed_flags.clone(), CodegenMode::Production);
- assert_eq!(8, filtered_parsed_flags.len());
-
- let filtered_parsed_flags = filter_parsed_flags(parsed_flags.clone(), CodegenMode::Test);
- assert_eq!(8, filtered_parsed_flags.len());
+ assert_eq!(
+ None,
+ crate::test::first_significant_code_diff(
+ crate::test::TEST_FLAGS_TEXTPROTO.trim(),
+ text.trim()
+ )
+ );
}
fn parse_test_flags_as_input() -> Input {
@@ -624,4 +643,55 @@
let reader = Box::new(cursor);
Input { source: "test.data".to_string(), reader }
}
+
+ #[test]
+ fn test_modify_parsed_flags_based_on_mode_prod() {
+ let parsed_flags = crate::test::parse_test_flags();
+ let p_parsed_flags =
+ modify_parsed_flags_based_on_mode(parsed_flags.clone(), CodegenMode::Production)
+ .unwrap();
+ assert_eq!(parsed_flags.parsed_flag.len(), p_parsed_flags.len());
+ for (i, item) in p_parsed_flags.iter().enumerate() {
+ assert!(parsed_flags.parsed_flag[i].eq(item));
+ }
+ }
+
+ #[test]
+ fn test_modify_parsed_flags_based_on_mode_exported() {
+ let parsed_flags = crate::test::parse_test_flags();
+ let p_parsed_flags =
+ modify_parsed_flags_based_on_mode(parsed_flags, CodegenMode::Exported).unwrap();
+ assert_eq!(3, p_parsed_flags.len());
+ for flag in p_parsed_flags.iter() {
+ assert_eq!(ProtoFlagState::DISABLED, flag.state());
+ assert_eq!(ProtoFlagPermission::READ_WRITE, flag.permission());
+ assert!(!flag.is_fixed_read_only());
+ assert!(flag.is_exported());
+ }
+
+ let mut parsed_flags = crate::test::parse_test_flags();
+ parsed_flags.parsed_flag.retain(|pf| !pf.is_exported());
+ let error =
+ modify_parsed_flags_based_on_mode(parsed_flags, CodegenMode::Exported).unwrap_err();
+ assert_eq!("exported library contains no exported flags", format!("{:?}", error));
+ }
+
+ #[test]
+ fn test_assign_flag_ids() {
+ let parsed_flags = crate::test::parse_test_flags();
+ let package = find_unique_package(&parsed_flags.parsed_flag).unwrap().to_string();
+ let flag_ids = assign_flag_ids(&package, parsed_flags.parsed_flag.iter()).unwrap();
+ let expected_flag_ids = HashMap::from([
+ (String::from("disabled_ro"), 0_u32),
+ (String::from("disabled_rw"), 1_u32),
+ (String::from("disabled_rw_exported"), 2_u32),
+ (String::from("disabled_rw_in_other_namespace"), 3_u32),
+ (String::from("enabled_fixed_ro"), 4_u32),
+ (String::from("enabled_fixed_ro_exported"), 5_u32),
+ (String::from("enabled_ro"), 6_u32),
+ (String::from("enabled_ro_exported"), 7_u32),
+ (String::from("enabled_rw"), 8_u32),
+ ]);
+ assert_eq!(flag_ids, expected_flag_ids);
+ }
}
diff --git a/tools/aconfig/src/dump.rs b/tools/aconfig/src/dump.rs
index f2b3687..37368ee 100644
--- a/tools/aconfig/src/dump.rs
+++ b/tools/aconfig/src/dump.rs
@@ -36,17 +36,6 @@
// protobuf formats
"protobuf" => Ok(Self::Protobuf),
"textproto" => Ok(Self::Textproto),
-
- // old formats now implemented as aliases to custom format
- "text" => Ok(Self::Custom(
- "{fully_qualified_name} [{container}]: {permission} + {state}".to_owned(),
- )),
- "verbose" => Ok(Self::Custom(
- "{fully_qualified_name} [{container}]: {permission} + {state} ({trace:paths})"
- .to_owned(),
- )),
- "bool" => Ok(Self::Custom("{fully_qualified_name}={state:bool}".to_owned())),
-
// custom format
_ => Ok(Self::Custom(value.to_owned())),
}
@@ -197,6 +186,10 @@
Ok(Box::new(move |flag: &ProtoParsedFlag| flag.container() == expected))
}
// metadata: not supported yet
+ "fully_qualified_name" => {
+ let expected = arg.to_owned();
+ Ok(Box::new(move |flag: &ProtoParsedFlag| flag.fully_qualified_name() == expected))
+ }
_ => Err(anyhow!(error_msg)),
}
}
@@ -266,20 +259,6 @@
"{fully_qualified_name}={permission} + {state}",
"com.android.aconfig.test.enabled_ro=READ_ONLY + ENABLED"
);
-
- // aliases
- assert_dump_parsed_flags_custom_format_contains!(
- "text",
- "com.android.aconfig.test.enabled_ro [system]: READ_ONLY + ENABLED"
- );
- assert_dump_parsed_flags_custom_format_contains!(
- "verbose",
- "com.android.aconfig.test.enabled_ro [system]: READ_ONLY + ENABLED (tests/test.aconfig, tests/first.values, tests/second.values)"
- );
- assert_dump_parsed_flags_custom_format_contains!(
- "bool",
- "com.android.aconfig.test.enabled_ro=true"
- );
}
#[test]
@@ -341,6 +320,7 @@
"com.android.aconfig.test.disabled_rw_exported",
"com.android.aconfig.test.disabled_rw_in_other_namespace",
"com.android.aconfig.test.enabled_fixed_ro",
+ "com.android.aconfig.test.enabled_fixed_ro_exported",
"com.android.aconfig.test.enabled_ro",
"com.android.aconfig.test.enabled_ro_exported",
"com.android.aconfig.test.enabled_rw",
@@ -360,6 +340,7 @@
"state:ENABLED",
&[
"com.android.aconfig.test.enabled_fixed_ro",
+ "com.android.aconfig.test.enabled_fixed_ro_exported",
"com.android.aconfig.test.enabled_ro",
"com.android.aconfig.test.enabled_ro_exported",
"com.android.aconfig.test.enabled_rw",
@@ -370,6 +351,7 @@
&[
"com.android.aconfig.test.disabled_ro",
"com.android.aconfig.test.enabled_fixed_ro",
+ "com.android.aconfig.test.enabled_fixed_ro_exported",
"com.android.aconfig.test.enabled_ro",
"com.android.aconfig.test.enabled_ro_exported",
]
@@ -377,12 +359,16 @@
// trace: not supported yet
assert_create_filter_predicate!(
"is_fixed_read_only:true",
- &["com.android.aconfig.test.enabled_fixed_ro"]
+ &[
+ "com.android.aconfig.test.enabled_fixed_ro",
+ "com.android.aconfig.test.enabled_fixed_ro_exported",
+ ]
);
assert_create_filter_predicate!(
"is_exported:true",
&[
"com.android.aconfig.test.disabled_rw_exported",
+ "com.android.aconfig.test.enabled_fixed_ro_exported",
"com.android.aconfig.test.enabled_ro_exported",
]
);
@@ -394,6 +380,7 @@
"com.android.aconfig.test.disabled_rw_exported",
"com.android.aconfig.test.disabled_rw_in_other_namespace",
"com.android.aconfig.test.enabled_fixed_ro",
+ "com.android.aconfig.test.enabled_fixed_ro_exported",
"com.android.aconfig.test.enabled_ro",
"com.android.aconfig.test.enabled_ro_exported",
"com.android.aconfig.test.enabled_rw",
@@ -401,11 +388,18 @@
);
// metadata: not supported yet
+ // synthesized fields
+ assert_create_filter_predicate!(
+ "fully_qualified_name:com.android.aconfig.test.disabled_rw",
+ &["com.android.aconfig.test.disabled_rw"]
+ );
+
// multiple sub filters
assert_create_filter_predicate!(
"permission:READ_ONLY+state:ENABLED",
&[
"com.android.aconfig.test.enabled_fixed_ro",
+ "com.android.aconfig.test.enabled_fixed_ro_exported",
"com.android.aconfig.test.enabled_ro",
"com.android.aconfig.test.enabled_ro_exported",
]
diff --git a/tools/aconfig/src/main.rs b/tools/aconfig/src/main.rs
index fcc5ea5..6c4e241 100644
--- a/tools/aconfig/src/main.rs
+++ b/tools/aconfig/src/main.rs
@@ -30,12 +30,13 @@
mod protos;
mod storage;
+use codegen::CodegenMode;
use dump::DumpFormat;
#[cfg(test)]
mod test;
-use commands::{CodegenMode, Input, OutputFile};
+use commands::{Input, OutputFile};
const HELP_DUMP_FILTER: &str = r#"
Limit which flags to output. If multiple --filter arguments are provided, the output will be
@@ -69,7 +70,7 @@
.arg(
Arg::new("mode")
.long("mode")
- .value_parser(EnumValueParser::<commands::CodegenMode>::new())
+ .value_parser(EnumValueParser::<CodegenMode>::new())
.default_value("production"),
),
)
@@ -80,7 +81,7 @@
.arg(
Arg::new("mode")
.long("mode")
- .value_parser(EnumValueParser::<commands::CodegenMode>::new())
+ .value_parser(EnumValueParser::<CodegenMode>::new())
.default_value("production"),
),
)
@@ -91,7 +92,7 @@
.arg(
Arg::new("mode")
.long("mode")
- .value_parser(EnumValueParser::<commands::CodegenMode>::new())
+ .value_parser(EnumValueParser::<CodegenMode>::new())
.default_value("production"),
),
)
@@ -106,15 +107,23 @@
.arg(Arg::new("out").long("out").default_value("-")),
)
.subcommand(
- Command::new("dump")
+ Command::new("dump-cache")
+ .alias("dump")
.arg(Arg::new("cache").long("cache").action(ArgAction::Append))
.arg(
Arg::new("format")
.long("format")
.value_parser(|s: &str| DumpFormat::try_from(s))
- .default_value("text"),
+ .default_value(
+ "{fully_qualified_name} [{container}]: {permission} + {state}",
+ ),
)
- .arg(Arg::new("filter").long("filter").action(ArgAction::Append).help(HELP_DUMP_FILTER.trim()))
+ .arg(
+ Arg::new("filter")
+ .long("filter")
+ .action(ArgAction::Append)
+ .help(HELP_DUMP_FILTER.trim()),
+ )
.arg(Arg::new("dedup").long("dedup").num_args(0).action(ArgAction::SetTrue))
.arg(Arg::new("out").long("out").default_value("-")),
)
@@ -254,7 +263,7 @@
let path = get_required_arg::<String>(sub_matches, "out")?;
write_output_to_file_or_stdout(path, &output)?;
}
- Some(("dump", sub_matches)) => {
+ Some(("dump-cache", sub_matches)) => {
let input = open_zero_or_more_files(sub_matches, "cache")?;
let format = get_required_arg::<DumpFormat>(sub_matches, "format")
.context("failed to dump previously parsed flags")?;
diff --git a/tools/aconfig/src/storage/mod.rs b/tools/aconfig/src/storage/mod.rs
index 90e05f5..686f9ae 100644
--- a/tools/aconfig/src/storage/mod.rs
+++ b/tools/aconfig/src/storage/mod.rs
@@ -14,11 +14,41 @@
* limitations under the License.
*/
-use anyhow::Result;
-use std::collections::{HashMap, HashSet};
+pub mod package_table;
+
+use anyhow::{anyhow, Result};
+use std::collections::{hash_map::DefaultHasher, HashMap, HashSet};
+use std::hash::{Hash, Hasher};
+use std::path::PathBuf;
use crate::commands::OutputFile;
use crate::protos::{ProtoParsedFlag, ProtoParsedFlags};
+use crate::storage::package_table::PackageTable;
+
+pub const FILE_VERSION: u32 = 1;
+
+pub const HASH_PRIMES: [u32; 29] = [
+ 7, 13, 29, 53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593, 49157, 98317, 196613, 393241,
+ 786433, 1572869, 3145739, 6291469, 12582917, 25165843, 50331653, 100663319, 201326611,
+ 402653189, 805306457, 1610612741,
+];
+
+/// Get the right hash table size given number of entries in the table. Use a
+/// load factor of 0.5 for performance.
+pub fn get_table_size(entries: u32) -> Result<u32> {
+ HASH_PRIMES
+ .iter()
+ .find(|&&num| num >= 2 * entries)
+ .copied()
+ .ok_or(anyhow!("Number of packages is too large"))
+}
+
+/// Get the corresponding bucket index given the key and number of buckets
+pub fn get_bucket_index<T: Hash>(val: &T, num_buckets: u32) -> u32 {
+ let mut s = DefaultHasher::new();
+ val.hash(&mut s);
+ (s.finish() % num_buckets as u64) as u32
+}
pub struct FlagPackage<'a> {
pub package_name: &'a str,
@@ -52,7 +82,7 @@
{
// group flags by package
let mut packages: Vec<FlagPackage<'a>> = Vec::new();
- let mut package_index: HashMap<&'a str, usize> = HashMap::new();
+ let mut package_index: HashMap<&str, usize> = HashMap::new();
for parsed_flags in parsed_flags_vec_iter {
for parsed_flag in parsed_flags.parsed_flag.iter() {
let index = *(package_index.entry(parsed_flag.package()).or_insert(packages.len()));
@@ -76,14 +106,21 @@
}
pub fn generate_storage_files<'a, I>(
- _containser: &str,
+ container: &str,
parsed_flags_vec_iter: I,
) -> Result<Vec<OutputFile>>
where
I: Iterator<Item = &'a ProtoParsedFlags>,
{
- let _packages = group_flags_by_package(parsed_flags_vec_iter);
- Ok(vec![])
+ let packages = group_flags_by_package(parsed_flags_vec_iter);
+
+ // create and serialize package map
+ let package_table = PackageTable::new(container, &packages)?;
+ let package_table_file_path = PathBuf::from("package.map");
+ let package_table_file =
+ OutputFile { contents: package_table.as_bytes(), path: package_table_file_path };
+
+ Ok(vec![package_table_file])
}
#[cfg(test)]
@@ -91,6 +128,21 @@
use super::*;
use crate::Input;
+ /// Read and parse bytes as u32
+ pub fn read_u32_from_bytes(buf: &[u8], head: &mut usize) -> Result<u32> {
+ let val = u32::from_le_bytes(buf[*head..*head + 4].try_into()?);
+ *head += 4;
+ Ok(val)
+ }
+
+ /// Read and parse bytes as string
+ pub fn read_str_from_bytes(buf: &[u8], head: &mut usize) -> Result<String> {
+ let num_bytes = read_u32_from_bytes(buf, head)? as usize;
+ let val = String::from_utf8(buf[*head..*head + num_bytes].to_vec())?;
+ *head += num_bytes;
+ Ok(val)
+ }
+
pub fn parse_all_test_flags() -> Vec<ProtoParsedFlags> {
let aconfig_files = [
(
@@ -108,6 +160,11 @@
"storage_test_2.aconfig",
include_bytes!("../../tests/storage_test_2.aconfig").as_slice(),
),
+ (
+ "com.android.aconfig.storage.test_4",
+ "storage_test_4.aconfig",
+ include_bytes!("../../tests/storage_test_4.aconfig").as_slice(),
+ ),
];
aconfig_files
@@ -143,7 +200,7 @@
}
}
- assert_eq!(packages.len(), 2);
+ assert_eq!(packages.len(), 3);
assert_eq!(packages[0].package_name, "com.android.aconfig.storage.test_1");
assert_eq!(packages[0].package_id, 0);
@@ -162,5 +219,12 @@
assert!(packages[1].flag_names.contains("disabled_ro"));
assert!(packages[1].flag_names.contains("enabled_fixed_ro"));
assert_eq!(packages[1].boolean_offset, 10);
+
+ assert_eq!(packages[2].package_name, "com.android.aconfig.storage.test_4");
+ assert_eq!(packages[2].package_id, 2);
+ assert_eq!(packages[2].flag_names.len(), 2);
+ assert!(packages[2].flag_names.contains("enabled_ro"));
+ assert!(packages[2].flag_names.contains("enabled_fixed_ro"));
+ assert_eq!(packages[2].boolean_offset, 16);
}
}
diff --git a/tools/aconfig/src/storage/package_table.rs b/tools/aconfig/src/storage/package_table.rs
new file mode 100644
index 0000000..940c5b2
--- /dev/null
+++ b/tools/aconfig/src/storage/package_table.rs
@@ -0,0 +1,299 @@
+/*
+ * 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.
+ */
+
+use crate::storage::{self, FlagPackage};
+use anyhow::Result;
+
+#[derive(PartialEq, Debug)]
+pub struct PackageTableHeader {
+ pub version: u32,
+ pub container: String,
+ pub file_size: u32,
+ pub num_packages: u32,
+ pub bucket_offset: u32,
+ pub node_offset: u32,
+}
+
+impl PackageTableHeader {
+ fn new(container: &str, num_packages: u32) -> Self {
+ Self {
+ version: storage::FILE_VERSION,
+ container: String::from(container),
+ file_size: 0,
+ num_packages,
+ bucket_offset: 0,
+ node_offset: 0,
+ }
+ }
+
+ fn as_bytes(&self) -> Vec<u8> {
+ let mut result = Vec::new();
+ result.extend_from_slice(&self.version.to_le_bytes());
+ let container_bytes = self.container.as_bytes();
+ result.extend_from_slice(&(container_bytes.len() as u32).to_le_bytes());
+ result.extend_from_slice(container_bytes);
+ result.extend_from_slice(&self.file_size.to_le_bytes());
+ result.extend_from_slice(&self.num_packages.to_le_bytes());
+ result.extend_from_slice(&self.bucket_offset.to_le_bytes());
+ result.extend_from_slice(&self.node_offset.to_le_bytes());
+ result
+ }
+}
+
+#[derive(PartialEq, Debug)]
+pub struct PackageTableNode {
+ pub package_name: String,
+ pub package_id: u32,
+ pub boolean_offset: u32,
+ pub next_offset: Option<u32>,
+ pub bucket_index: u32,
+}
+
+impl PackageTableNode {
+ fn new(package: &FlagPackage, num_buckets: u32) -> Self {
+ let bucket_index =
+ storage::get_bucket_index(&package.package_name.to_string(), num_buckets);
+ Self {
+ package_name: String::from(package.package_name),
+ package_id: package.package_id,
+ boolean_offset: package.boolean_offset,
+ next_offset: None,
+ bucket_index,
+ }
+ }
+
+ fn as_bytes(&self) -> Vec<u8> {
+ let mut result = Vec::new();
+ let name_bytes = self.package_name.as_bytes();
+ result.extend_from_slice(&(name_bytes.len() as u32).to_le_bytes());
+ result.extend_from_slice(name_bytes);
+ result.extend_from_slice(&self.package_id.to_le_bytes());
+ result.extend_from_slice(&self.boolean_offset.to_le_bytes());
+ result.extend_from_slice(&self.next_offset.unwrap_or(0).to_le_bytes());
+ result
+ }
+}
+
+#[derive(PartialEq, Debug)]
+pub struct PackageTable {
+ pub header: PackageTableHeader,
+ pub buckets: Vec<Option<u32>>,
+ pub nodes: Vec<PackageTableNode>,
+}
+
+impl PackageTable {
+ pub fn new(container: &str, packages: &[FlagPackage]) -> Result<Self> {
+ // create table
+ let num_packages = packages.len() as u32;
+ let num_buckets = storage::get_table_size(num_packages)?;
+ let mut table = Self {
+ header: PackageTableHeader::new(container, num_packages),
+ buckets: vec![None; num_buckets as usize],
+ nodes: packages.iter().map(|pkg| PackageTableNode::new(pkg, num_buckets)).collect(),
+ };
+
+ // initialize all header fields
+ table.header.bucket_offset = table.header.as_bytes().len() as u32;
+ table.header.node_offset = table.header.bucket_offset + num_buckets * 4;
+ table.header.file_size = table.header.node_offset
+ + table.nodes.iter().map(|x| x.as_bytes().len()).sum::<usize>() as u32;
+
+ // sort nodes by bucket index for efficiency
+ table.nodes.sort_by(|a, b| a.bucket_index.cmp(&b.bucket_index));
+
+ // fill all node offset
+ let mut offset = table.header.node_offset;
+ for i in 0..table.nodes.len() {
+ let node_bucket_idx = table.nodes[i].bucket_index;
+ let next_node_bucket_idx = if i + 1 < table.nodes.len() {
+ Some(table.nodes[i + 1].bucket_index)
+ } else {
+ None
+ };
+
+ if table.buckets[node_bucket_idx as usize].is_none() {
+ table.buckets[node_bucket_idx as usize] = Some(offset);
+ }
+ offset += table.nodes[i].as_bytes().len() as u32;
+
+ if let Some(index) = next_node_bucket_idx {
+ if index == node_bucket_idx {
+ table.nodes[i].next_offset = Some(offset);
+ }
+ }
+ }
+
+ Ok(table)
+ }
+
+ pub fn as_bytes(&self) -> Vec<u8> {
+ [
+ self.header.as_bytes(),
+ self.buckets.iter().map(|v| v.unwrap_or(0).to_le_bytes()).collect::<Vec<_>>().concat(),
+ self.nodes.iter().map(|v| v.as_bytes()).collect::<Vec<_>>().concat(),
+ ]
+ .concat()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::storage::{
+ group_flags_by_package, tests::parse_all_test_flags, tests::read_str_from_bytes,
+ tests::read_u32_from_bytes,
+ };
+
+ impl PackageTableHeader {
+ // test only method to deserialize back into the header struct
+ fn from_bytes(bytes: &[u8]) -> Result<Self> {
+ let mut head = 0;
+ Ok(Self {
+ version: read_u32_from_bytes(bytes, &mut head)?,
+ container: read_str_from_bytes(bytes, &mut head)?,
+ file_size: read_u32_from_bytes(bytes, &mut head)?,
+ num_packages: read_u32_from_bytes(bytes, &mut head)?,
+ bucket_offset: read_u32_from_bytes(bytes, &mut head)?,
+ node_offset: read_u32_from_bytes(bytes, &mut head)?,
+ })
+ }
+ }
+
+ impl PackageTableNode {
+ // test only method to deserialize back into the node struct
+ fn from_bytes(bytes: &[u8], num_buckets: u32) -> Result<Self> {
+ let mut head = 0;
+ let mut node = Self {
+ package_name: read_str_from_bytes(bytes, &mut head)?,
+ package_id: read_u32_from_bytes(bytes, &mut head)?,
+ boolean_offset: read_u32_from_bytes(bytes, &mut head)?,
+ next_offset: match read_u32_from_bytes(bytes, &mut head)? {
+ 0 => None,
+ val => Some(val),
+ },
+ bucket_index: 0,
+ };
+ node.bucket_index = storage::get_bucket_index(&node.package_name, num_buckets);
+ Ok(node)
+ }
+ }
+
+ impl PackageTable {
+ // test only method to deserialize back into the table struct
+ fn from_bytes(bytes: &[u8]) -> Result<Self> {
+ let header = PackageTableHeader::from_bytes(bytes)?;
+ let num_packages = header.num_packages;
+ let num_buckets = storage::get_table_size(num_packages)?;
+ let mut head = header.as_bytes().len();
+ let buckets = (0..num_buckets)
+ .map(|_| match read_u32_from_bytes(bytes, &mut head).unwrap() {
+ 0 => None,
+ val => Some(val),
+ })
+ .collect();
+ let nodes = (0..num_packages)
+ .map(|_| {
+ let node = PackageTableNode::from_bytes(&bytes[head..], num_buckets).unwrap();
+ head += node.as_bytes().len();
+ node
+ })
+ .collect();
+
+ let table = Self { header, buckets, nodes };
+ Ok(table)
+ }
+ }
+
+ pub fn create_test_package_table() -> Result<PackageTable> {
+ let caches = parse_all_test_flags();
+ let packages = group_flags_by_package(caches.iter());
+ PackageTable::new("system", &packages)
+ }
+
+ #[test]
+ // this test point locks down the table creation and each field
+ fn test_table_contents() {
+ let package_table = create_test_package_table();
+ assert!(package_table.is_ok());
+
+ let header: &PackageTableHeader = &package_table.as_ref().unwrap().header;
+ let expected_header = PackageTableHeader {
+ version: storage::FILE_VERSION,
+ container: String::from("system"),
+ file_size: 208,
+ num_packages: 3,
+ bucket_offset: 30,
+ node_offset: 58,
+ };
+ assert_eq!(header, &expected_header);
+
+ let buckets: &Vec<Option<u32>> = &package_table.as_ref().unwrap().buckets;
+ let expected: Vec<Option<u32>> = vec![Some(58), None, None, Some(108), None, None, None];
+ assert_eq!(buckets, &expected);
+
+ let nodes: &Vec<PackageTableNode> = &package_table.as_ref().unwrap().nodes;
+ assert_eq!(nodes.len(), 3);
+ let first_node_expected = PackageTableNode {
+ package_name: String::from("com.android.aconfig.storage.test_2"),
+ package_id: 1,
+ boolean_offset: 10,
+ next_offset: None,
+ bucket_index: 0,
+ };
+ assert_eq!(nodes[0], first_node_expected);
+ let second_node_expected = PackageTableNode {
+ package_name: String::from("com.android.aconfig.storage.test_1"),
+ package_id: 0,
+ boolean_offset: 0,
+ next_offset: Some(158),
+ bucket_index: 3,
+ };
+ assert_eq!(nodes[1], second_node_expected);
+ let third_node_expected = PackageTableNode {
+ package_name: String::from("com.android.aconfig.storage.test_4"),
+ package_id: 2,
+ boolean_offset: 16,
+ next_offset: None,
+ bucket_index: 3,
+ };
+ assert_eq!(nodes[2], third_node_expected);
+ }
+
+ #[test]
+ // this test point locks down the table serialization
+ fn test_serialization() {
+ let package_table = create_test_package_table();
+ assert!(package_table.is_ok());
+ let package_table = package_table.unwrap();
+
+ let header: &PackageTableHeader = &package_table.header;
+ let reinterpreted_header = PackageTableHeader::from_bytes(&header.as_bytes());
+ assert!(reinterpreted_header.is_ok());
+ assert_eq!(header, &reinterpreted_header.unwrap());
+
+ let nodes: &Vec<PackageTableNode> = &package_table.nodes;
+ let num_buckets = storage::get_table_size(header.num_packages).unwrap();
+ for node in nodes.iter() {
+ let reinterpreted_node = PackageTableNode::from_bytes(&node.as_bytes(), num_buckets);
+ assert!(reinterpreted_node.is_ok());
+ assert_eq!(node, &reinterpreted_node.unwrap());
+ }
+
+ let reinterpreted_table = PackageTable::from_bytes(&package_table.as_bytes());
+ assert!(reinterpreted_table.is_ok());
+ assert_eq!(&package_table, &reinterpreted_table.unwrap());
+ }
+}
diff --git a/tools/aconfig/src/test.rs b/tools/aconfig/src/test.rs
index 309cb28..cbb95b8 100644
--- a/tools/aconfig/src/test.rs
+++ b/tools/aconfig/src/test.rs
@@ -145,6 +145,31 @@
}
parsed_flag {
package: "com.android.aconfig.test"
+ name: "enabled_fixed_ro_exported"
+ namespace: "aconfig_test"
+ description: "This flag is fixed ENABLED + READ_ONLY and exported"
+ bug: "111"
+ state: ENABLED
+ permission: READ_ONLY
+ trace {
+ source: "tests/test.aconfig"
+ state: DISABLED
+ permission: READ_ONLY
+ }
+ trace {
+ source: "tests/first.values"
+ state: ENABLED
+ permission: READ_ONLY
+ }
+ is_fixed_read_only: true
+ is_exported: true
+ container: "system"
+ metadata {
+ purpose: PURPOSE_UNSPECIFIED
+ }
+}
+parsed_flag {
+ package: "com.android.aconfig.test"
name: "enabled_ro"
namespace: "aconfig_test"
description: "This flag is ENABLED + READ_ONLY"
diff --git a/tools/aconfig/templates/FakeFeatureFlagsImpl.java.template b/tools/aconfig/templates/FakeFeatureFlagsImpl.java.template
index 8010b88..933d6a7 100644
--- a/tools/aconfig/templates/FakeFeatureFlagsImpl.java.template
+++ b/tools/aconfig/templates/FakeFeatureFlagsImpl.java.template
@@ -12,23 +12,11 @@
}
{{ for item in flag_elements}}
-{{ if library_exported }}
-
-{{ if item.exported }}
@Override
@UnsupportedAppUsage
public boolean {item.method_name}() \{
return getValue(Flags.FLAG_{item.flag_name_constant_suffix});
}
-{{ endif }}
-
-{{ else }}
- @Override
- @UnsupportedAppUsage
- public boolean {item.method_name}() \{
- return getValue(Flags.FLAG_{item.flag_name_constant_suffix});
- }
-{{ endif }}
{{ endfor}}
public void setFlag(String flagName, boolean value) \{
if (!this.mFlagMap.containsKey(flagName)) \{
@@ -52,20 +40,11 @@
}
private Map<String, Boolean> mFlagMap = new HashMap<>(
- {{ if library_exported }}
- Map.ofEntries(
- {{-for item in exported_flag_elements}}
- Map.entry(Flags.FLAG_{item.flag_name_constant_suffix}, false)
- {{ -if not @last }},{{ endif }}
- {{ -endfor }}
- )
- {{ else }}
Map.ofEntries(
{{-for item in flag_elements}}
Map.entry(Flags.FLAG_{item.flag_name_constant_suffix}, false)
{{ -if not @last }},{{ endif }}
{{ -endfor }}
)
- {{ endif }}
);
}
diff --git a/tools/aconfig/templates/FeatureFlags.java.template b/tools/aconfig/templates/FeatureFlags.java.template
index 180f882..5e67b13 100644
--- a/tools/aconfig/templates/FeatureFlags.java.template
+++ b/tools/aconfig/templates/FeatureFlags.java.template
@@ -5,15 +5,10 @@
/** @hide */
public interface FeatureFlags \{
{{ for item in flag_elements }}
-{{ if library_exported }}
-
-{{ if item.exported }}
+{{ -if library_exported }}
@UnsupportedAppUsage
boolean {item.method_name}();
-{{ endif }}
-
-{{ else }}
-
+{{ -else }}
{{ -if not item.is_read_write }}
{{ -if item.default_value }}
@com.android.aconfig.annotations.AssumeTrueForR8
@@ -23,7 +18,6 @@
{{ endif }}
@UnsupportedAppUsage
boolean {item.method_name}();
-
{{ endif }}
-{{ endfor }}
+{{ -endfor }}
}
diff --git a/tools/aconfig/templates/FeatureFlagsImpl.java.template b/tools/aconfig/templates/FeatureFlagsImpl.java.template
index 7a52ceb..28baa41 100644
--- a/tools/aconfig/templates/FeatureFlagsImpl.java.template
+++ b/tools/aconfig/templates/FeatureFlagsImpl.java.template
@@ -1,54 +1,42 @@
package {package_name};
// TODO(b/303773055): Remove the annotation after access issue is resolved.
import android.compat.annotation.UnsupportedAppUsage;
-{{ if not is_test_mode }}
-{{ if runtime_lookup_required- }}
+{{ -if not is_test_mode }}
+{{ -if runtime_lookup_required }}
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
{{ endif }}
/** @hide */
public final class FeatureFlagsImpl implements FeatureFlags \{
-{{- if runtime_lookup_required }}
-{{- for namespace_with_flags in namespace_flags }}
+{{ -if runtime_lookup_required }}
+{{ -for namespace_with_flags in namespace_flags }}
private static boolean {namespace_with_flags.namespace}_is_cached = false;
-{{- endfor- }}
+{{ -endfor- }}
{{ for flag in flag_elements }}
-{{ if library_exported }}
-{{ if flag.exported }}
+{{ -if library_exported }}
private static boolean {flag.method_name} = false;
-{{ endif }}
-
-{{ else }}
-
+{{ -else }}
{{- if flag.is_read_write }}
private static boolean {flag.method_name} = {flag.default_value};
{{- endif- }}
-{{ endif }}
-{{ endfor }}
-
+{{ -endif }}
+{{ -endfor }}
{{ for namespace_with_flags in namespace_flags }}
private void load_overrides_{namespace_with_flags.namespace}() \{
try \{
Properties properties = DeviceConfig.getProperties("{namespace_with_flags.namespace}");
-
- {{- for flag in namespace_with_flags.flags }}
- {{ if library_exported }}
-
- {{ if flag.exported }}
+{{ -for flag in namespace_with_flags.flags }}
+{{ -if library_exported }}
{flag.method_name} =
properties.getBoolean("{flag.device_config_flag}", false);
- {{ endif }}
-
- {{ else }}
-
- {{ if flag.is_read_write }}
+{{ -else }}
+{{ -if flag.is_read_write }}
{flag.method_name} =
properties.getBoolean("{flag.device_config_flag}", {flag.default_value});
- {{ endif }}
-
- {{ endif }}
- {{ endfor }}
+{{ -endif }}
+{{ -endif }}
+{{ -endfor }}
} catch (NullPointerException e) \{
throw new RuntimeException(
"Cannot read value from namespace {namespace_with_flags.namespace} "
@@ -62,37 +50,27 @@
{namespace_with_flags.namespace}_is_cached = true;
}
{{ endfor- }}
-{{ endif- }}
-
-{{ for flag in flag_elements }}
-{{ if library_exported }}
-
-{{ if flag.exported }}
+{{ -endif }}{#- end of runtime_lookup_required #}
+{{ -for flag in flag_elements }}
@Override
@UnsupportedAppUsage
public boolean {flag.method_name}() \{
+{{ -if library_exported }}
if (!{flag.device_config_namespace}_is_cached) \{
load_overrides_{flag.device_config_namespace}();
}
return {flag.method_name};
- }
-{{ endif }}
-
-{{ else }}
- @Override
- @UnsupportedAppUsage
- public boolean {flag.method_name}() \{
- {{ -if flag.is_read_write }}
+{{ -else }}
+{{ -if flag.is_read_write }}
if (!{flag.device_config_namespace}_is_cached) \{
load_overrides_{flag.device_config_namespace}();
}
return {flag.method_name};
- {{ else }}
+{{ -else }}
return {flag.default_value};
- {{ endif- }}
+{{ -endif- }}
+{{ -endif }}
}
-{{ endif }}
-
{{ endfor }}
}
{{ else }}
diff --git a/tools/aconfig/templates/Flags.java.template b/tools/aconfig/templates/Flags.java.template
index 9f4c52f..34b8189 100644
--- a/tools/aconfig/templates/Flags.java.template
+++ b/tools/aconfig/templates/Flags.java.template
@@ -5,42 +5,30 @@
/** @hide */
public final class Flags \{
-{{- for item in flag_elements}}
- {{ if library_exported }}
- {{ if item.exported }}
+{{ -for item in flag_elements}}
/** @hide */
public static final String FLAG_{item.flag_name_constant_suffix} = "{item.device_config_flag}";
- {{ endif }}
- {{ else }}
- /** @hide */
- public static final String FLAG_{item.flag_name_constant_suffix} = "{item.device_config_flag}";
- {{ endif }}
{{- endfor }}
-{{ for item in flag_elements}}
+{{ -for item in flag_elements}}
{{ if library_exported }}
-
-{{ if item.exported }}
@UnsupportedAppUsage
public static boolean {item.method_name}() \{
return FEATURE_FLAGS.{item.method_name}();
}
-{{ endif }}
-
-{{ else }}
-
+{{ -else }}
{{ -if not item.is_read_write }}
{{ -if item.default_value }}
@com.android.aconfig.annotations.AssumeTrueForR8
{{ -else }}
@com.android.aconfig.annotations.AssumeFalseForR8
-{{ -endif- }}
-{{ endif }}
+{{ -endif }}
+{{ -endif }}
@UnsupportedAppUsage
public static boolean {item.method_name}() \{
return FEATURE_FLAGS.{item.method_name}();
}
-{{ endif }}
-{{ endfor }}
+{{ -endif }}
+{{ -endfor }}
{{ -if is_test_mode }}
public static void setFeatureFlags(FeatureFlags featureFlags) \{
Flags.FEATURE_FLAGS = featureFlags;
@@ -49,7 +37,8 @@
public static void unsetFeatureFlags() \{
Flags.FEATURE_FLAGS = null;
}
-{{ endif }}
+{{ -endif }}
+
private static FeatureFlags FEATURE_FLAGS{{ -if not is_test_mode }} = new FeatureFlagsImpl(){{ -endif- }};
}
diff --git a/tools/aconfig/templates/cpp_exported_header.template b/tools/aconfig/templates/cpp_exported_header.template
index 377295d..6b6daa7 100644
--- a/tools/aconfig/templates/cpp_exported_header.template
+++ b/tools/aconfig/templates/cpp_exported_header.template
@@ -1,18 +1,16 @@
#pragma once
-{{ if not for_test- }}
+{{ if not is_test_mode- }}
{{ if has_fixed_read_only- }}
#ifndef {package_macro}
#define {package_macro}(FLAG) {package_macro}_##FLAG
#endif
{{ for item in class_elements }}
-
-{{ if item.is_fixed_read_only- }}
+{{ -if item.is_fixed_read_only }}
#ifndef {package_macro}_{item.flag_macro}
#define {package_macro}_{item.flag_macro} {item.default_value}
#endif
{{ -endif }}
-
{{ -endfor }}
{{ -endif }}
{{ -endif }}
@@ -26,15 +24,15 @@
class flag_provider_interface \{
public:
virtual ~flag_provider_interface() = default;
- {{ for item in class_elements}}
+ {{ -for item in class_elements}}
virtual bool {item.flag_name}() = 0;
- {{ if for_test- }}
+ {{ -if is_test_mode }}
virtual void {item.flag_name}(bool val) = 0;
{{ -endif }}
{{ -endfor }}
- {{ if for_test }}
+ {{ -if is_test_mode }}
virtual void reset_flags() \{}
{{ -endif }}
};
@@ -43,29 +41,35 @@
{{ for item in class_elements}}
inline bool {item.flag_name}() \{
- {{ if for_test- }}
+ {{ -if is_test_mode }}
return provider_->{item.flag_name}();
- {{ -else- }}
- {{ if item.readwrite- }}
+ {{ -else }}
+ {{ -if is_prod_mode }}
+ {{ -if item.readwrite }}
return provider_->{item.flag_name}();
- {{ -else- }}
- {{ if item.is_fixed_read_only- }}
+ {{ -else }}
+ {{ -if item.is_fixed_read_only }}
return {package_macro}_{item.flag_macro};
- {{ -else- }}
+ {{ -else }}
return {item.default_value};
{{ -endif }}
{{ -endif }}
+ {{ -else }}
+ {{ -if is_exported_mode }}
+ return provider_->{item.flag_name}();
+ {{ -endif }}
+ {{ -endif }}
{{ -endif }}
}
-{{ if for_test- }}
+{{ -if is_test_mode }}
inline void {item.flag_name}(bool val) \{
provider_->{item.flag_name}(val);
}
{{ -endif }}
{{ -endfor }}
-{{ if for_test- }}
+{{ -if is_test_mode }}
inline void reset_flags() \{
return provider_->reset_flags();
}
@@ -79,12 +83,12 @@
{{ for item in class_elements }}
bool {header}_{item.flag_name}();
-{{ if for_test- }}
+{{ -if is_test_mode }}
void set_{header}_{item.flag_name}(bool val);
{{ -endif }}
{{ -endfor }}
-{{ if for_test- }}
+{{ -if is_test_mode }}
void {header}_reset_flags();
{{ -endif }}
diff --git a/tools/aconfig/templates/cpp_source_file.template b/tools/aconfig/templates/cpp_source_file.template
index fbbfedc..4aec540 100644
--- a/tools/aconfig/templates/cpp_source_file.template
+++ b/tools/aconfig/templates/cpp_source_file.template
@@ -2,9 +2,8 @@
{{ if readwrite- }}
#include <server_configurable_flags/get_flags.h>
-{{ -endif }}
-
-{{ if for_test- }}
+{{ endif }}
+{{ if is_test_mode }}
#include <unordered_map>
#include <string>
{{ -else- }}
@@ -15,7 +14,7 @@
namespace {cpp_namespace} \{
-{{ if for_test- }}
+{{ if is_test_mode }}
class flag_provider : public flag_provider_interface \{
private:
std::unordered_map<std::string, bool> overrides_;
@@ -25,7 +24,7 @@
: overrides_()
\{}
- {{ for item in class_elements }}
+{{ for item in class_elements }}
virtual bool {item.flag_name}() override \{
auto it = overrides_.find("{item.flag_name}");
if (it != overrides_.end()) \{
@@ -36,7 +35,7 @@
"aconfig_flags.{item.device_config_namespace}",
"{item.device_config_flag}",
"{item.default_value}") == "true";
- {{ -else- }}
+ {{ -else }}
return {item.default_value};
{{ -endif }}
}
@@ -45,7 +44,7 @@
virtual void {item.flag_name}(bool val) override \{
overrides_["{item.flag_name}"] = val;
}
- {{ -endfor }}
+{{ endfor }}
virtual void reset_flags() override \{
overrides_.clear();
@@ -57,9 +56,11 @@
class flag_provider : public flag_provider_interface \{
public:
- {{ for item in class_elements }}
+ {{ -for item in class_elements }}
+
virtual bool {item.flag_name}() override \{
- {{ if item.readwrite- }}
+ {{ -if is_prod_mode }}
+ {{ -if item.readwrite }}
if (cache_[{item.readwrite_idx}] == -1) \{
cache_[{item.readwrite_idx}] = server_configurable_flags::GetServerConfigurableFlag(
"aconfig_flags.{item.device_config_namespace}",
@@ -67,15 +68,26 @@
"{item.default_value}") == "true";
}
return cache_[{item.readwrite_idx}];
- {{ -else- }}
- {{ if item.is_fixed_read_only }}
+ {{ -else }}
+ {{ -if item.is_fixed_read_only }}
return {package_macro}_{item.flag_macro};
- {{ -else- }}
+ {{ -else }}
return {item.default_value};
{{ -endif }}
{{ -endif }}
+ {{ -else- }}
+ {{ -if is_exported_mode }}
+ if (cache_[{item.readwrite_idx}] == -1) \{
+ cache_[{item.readwrite_idx}] = server_configurable_flags::GetServerConfigurableFlag(
+ "aconfig_flags.{item.device_config_namespace}",
+ "{item.device_config_flag}",
+ "false") == "true";
+ }
+ return cache_[{item.readwrite_idx}];
+ {{ -endif }}
+ {{ -endif }}
}
- {{ endfor }}
+ {{ -endfor }}
{{ if readwrite- }}
private:
std::vector<int8_t> cache_ = std::vector<int8_t>({readwrite_count}, -1);
@@ -91,29 +103,35 @@
{{ for item in class_elements }}
bool {header}_{item.flag_name}() \{
- {{ if for_test- }}
+ {{ -if is_test_mode }}
return {cpp_namespace}::{item.flag_name}();
- {{ -else- }}
- {{ if item.readwrite- }}
+ {{ -else }}
+ {{ -if is_prod_mode }}
+ {{ -if item.readwrite }}
return {cpp_namespace}::{item.flag_name}();
- {{ -else- }}
- {{ if item.is_fixed_read_only }}
+ {{ -else }}
+ {{ -if item.is_fixed_read_only }}
return {package_macro}_{item.flag_macro};
- {{ -else- }}
+ {{ -else }}
return {item.default_value};
{{ -endif }}
{{ -endif }}
+ {{ -else }}
+ {{ -if is_exported_mode }}
+ return {cpp_namespace}::{item.flag_name}();
+ {{ -endif }}
+ {{ -endif }}
{{ -endif }}
}
-{{ if for_test- }}
+{{ -if is_test_mode }}
void set_{header}_{item.flag_name}(bool val) \{
{cpp_namespace}::{item.flag_name}(val);
}
{{ -endif }}
-{{ endfor-}}
+{{ endfor }}
-{{ if for_test }}
+{{ -if is_test_mode }}
void {header}_reset_flags() \{
{cpp_namespace}::reset_flags();
}
diff --git a/tools/aconfig/templates/rust_exported.template b/tools/aconfig/templates/rust_exported.template
new file mode 100644
index 0000000..110f2d4
--- /dev/null
+++ b/tools/aconfig/templates/rust_exported.template
@@ -0,0 +1,35 @@
+//! codegenerated rust flag lib
+
+/// flag provider
+pub struct FlagProvider;
+
+lazy_static::lazy_static! \{
+{{ for flag in template_flags }}
+ /// flag value cache for {flag.name}
+ static ref CACHED_{flag.name}: bool = flags_rust::GetServerConfigurableFlag(
+ "aconfig_flags.{flag.device_config_namespace}",
+ "{flag.device_config_flag}",
+ "false") == "true";
+{{ endfor }}
+}
+
+impl FlagProvider \{
+{{ for flag in template_flags }}
+ /// query flag {flag.name}
+ pub fn {flag.name}(&self) -> bool \{
+ *CACHED_{flag.name}
+ }
+{{ endfor }}
+
+}
+
+/// flag provider
+pub static PROVIDER: FlagProvider = FlagProvider;
+
+{{ for flag in template_flags }}
+/// query flag {flag.name}
+#[inline(always)]
+pub fn {flag.name}() -> bool \{
+ PROVIDER.{flag.name}()
+}
+{{ endfor }}
diff --git a/tools/aconfig/templates/rust_prod.template b/tools/aconfig/templates/rust_prod.template
index 30ea646..f9a2829 100644
--- a/tools/aconfig/templates/rust_prod.template
+++ b/tools/aconfig/templates/rust_prod.template
@@ -3,32 +3,32 @@
/// flag provider
pub struct FlagProvider;
-{{ if has_readwrite - }}
+{{ if has_readwrite- }}
lazy_static::lazy_static! \{
- {{ for flag in template_flags }}
- {{ if flag.readwrite -}}
+{{ -for flag in template_flags }}
+ {{ -if flag.readwrite }}
/// flag value cache for {flag.name}
static ref CACHED_{flag.name}: bool = flags_rust::GetServerConfigurableFlag(
"aconfig_flags.{flag.device_config_namespace}",
"{flag.device_config_flag}",
"{flag.default_value}") == "true";
{{ -endif }}
- {{ endfor }}
+{{ -endfor }}
}
{{ -endif }}
impl FlagProvider \{
- {{ for flag in template_flags }}
+{{ for flag in template_flags }}
/// query flag {flag.name}
pub fn {flag.name}(&self) -> bool \{
- {{ if flag.readwrite -}}
+ {{ -if flag.readwrite }}
*CACHED_{flag.name}
- {{ -else- }}
+ {{ -else }}
{flag.default_value}
{{ -endif }}
}
- {{ endfor }}
+{{ endfor }}
}
@@ -38,10 +38,10 @@
{{ for flag in template_flags }}
/// query flag {flag.name}
#[inline(always)]
-{{ if flag.readwrite -}}
+{{ -if flag.readwrite }}
pub fn {flag.name}() -> bool \{
PROVIDER.{flag.name}()
-{{ -else- }}
+{{ -else }}
pub fn {flag.name}() -> bool \{
{flag.default_value}
{{ -endif }}
diff --git a/tools/aconfig/templates/rust_test.template b/tools/aconfig/templates/rust_test.template
index fd1229b..d01f40a 100644
--- a/tools/aconfig/templates/rust_test.template
+++ b/tools/aconfig/templates/rust_test.template
@@ -9,7 +9,7 @@
}
impl FlagProvider \{
- {{ for flag in template_flags }}
+{{ for flag in template_flags }}
/// query flag {flag.name}
pub fn {flag.name}(&self) -> bool \{
self.overrides.get("{flag.name}").copied().unwrap_or(
@@ -28,7 +28,7 @@
pub fn set_{flag.name}(&mut self, val: bool) \{
self.overrides.insert("{flag.name}", val);
}
- {{ endfor }}
+{{ endfor }}
/// clear all flag overrides
pub fn reset_flags(&mut self) \{
diff --git a/tools/aconfig/tests/AconfigTest.java b/tools/aconfig/tests/AconfigTest.java
index bb993c4..b2deca0 100644
--- a/tools/aconfig/tests/AconfigTest.java
+++ b/tools/aconfig/tests/AconfigTest.java
@@ -33,17 +33,13 @@
@Test
public void testEnabledReadOnlyFlag() {
assertEquals("com.android.aconfig.test.disabled_rw", FLAG_DISABLED_RW);
- // TODO: change to assertTrue(enabledRo()) when the build supports reading tests/*.values
- // (currently all flags are assigned the default READ_ONLY + DISABLED)
- assertFalse(enabledRo());
+ assertTrue(enabledRo());
}
@Test
public void testEnabledFixedReadOnlyFlag() {
assertEquals("com.android.aconfig.test.enabled_fixed_ro", FLAG_ENABLED_FIXED_RO);
- // TODO: change to assertTrue(enabledFixedRo()) when the build supports reading tests/*.values
- // (currently all flags are assigned the default READ_ONLY + DISABLED)
- assertFalse(enabledFixedRo());
+ assertTrue(enabledFixedRo());
}
@Test
@@ -55,9 +51,7 @@
@Test
public void testEnabledReadWriteFlag() {
assertEquals("com.android.aconfig.test.enabled_rw", FLAG_ENABLED_RW);
- // TODO: change to assertTrue(enabledRw()) when the build supports reading tests/*.values
- // (currently all flags are assigned the default READ_ONLY + DISABLED)
- assertFalse(enabledRw());
+ assertTrue(enabledRw());
}
@Test
diff --git a/tools/aconfig/tests/aconfig_exported_mode_test.cpp b/tools/aconfig/tests/aconfig_exported_mode_test.cpp
new file mode 100644
index 0000000..d6eab43
--- /dev/null
+++ b/tools/aconfig/tests/aconfig_exported_mode_test.cpp
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+#include "com_android_aconfig_test.h"
+#include "gtest/gtest.h"
+
+using namespace com::android::aconfig::test;
+
+TEST(AconfigTest, TestDisabledRwExportedFlag) {
+ ASSERT_FALSE(com_android_aconfig_test_disabled_rw_exported());
+ ASSERT_FALSE(provider_->disabled_rw_exported());
+ ASSERT_FALSE(disabled_rw_exported());
+}
+
+TEST(AconfigTest, TestEnabledFixedRoExportedFlag) {
+ // TODO: change to assertTrue(enabledFixedRoExported()) when the build supports reading tests/*.values
+ ASSERT_FALSE(com_android_aconfig_test_enabled_fixed_ro_exported());
+ ASSERT_FALSE(provider_->enabled_fixed_ro_exported());
+ ASSERT_FALSE(enabled_fixed_ro_exported());
+}
+
+TEST(AconfigTest, TestEnabledRoExportedFlag) {
+ // TODO: change to assertTrue(enabledRoExported()) when the build supports reading tests/*.values
+ ASSERT_FALSE(com_android_aconfig_test_enabled_ro_exported());
+ ASSERT_FALSE(provider_->enabled_ro_exported());
+ ASSERT_FALSE(enabled_ro_exported());
+}
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
\ No newline at end of file
diff --git a/tools/aconfig/tests/aconfig_exported_mode_test.rs b/tools/aconfig/tests/aconfig_exported_mode_test.rs
new file mode 100644
index 0000000..4b48047
--- /dev/null
+++ b/tools/aconfig/tests/aconfig_exported_mode_test.rs
@@ -0,0 +1,7 @@
+#[cfg(not(feature = "cargo"))]
+#[test]
+fn test_flags() {
+ assert!(!aconfig_test_rust_library::disabled_rw_exported());
+ assert!(!aconfig_test_rust_library::enabled_fixed_ro_exported());
+ assert!(!aconfig_test_rust_library::enabled_ro_exported());
+}
diff --git a/tools/aconfig/tests/aconfig_prod_mode_test.rs b/tools/aconfig/tests/aconfig_prod_mode_test.rs
index 950c441..e1fb8e5 100644
--- a/tools/aconfig/tests/aconfig_prod_mode_test.rs
+++ b/tools/aconfig/tests/aconfig_prod_mode_test.rs
@@ -3,7 +3,6 @@
fn test_flags() {
assert!(!aconfig_test_rust_library::disabled_ro());
assert!(!aconfig_test_rust_library::disabled_rw());
- // TODO: Fix template to not default both disabled and enabled to false
- assert!(!aconfig_test_rust_library::enabled_ro());
- assert!(!aconfig_test_rust_library::enabled_rw());
+ assert!(aconfig_test_rust_library::enabled_ro());
+ assert!(aconfig_test_rust_library::enabled_rw());
}
diff --git a/tools/aconfig/tests/aconfig_test.cpp b/tools/aconfig/tests/aconfig_test.cpp
index 52651e4..0dec481 100644
--- a/tools/aconfig/tests/aconfig_test.cpp
+++ b/tools/aconfig/tests/aconfig_test.cpp
@@ -26,11 +26,9 @@
}
TEST(AconfigTest, TestEnabledReadOnlyFlag) {
- // TODO: change to assertTrue(enabledRo()) when the build supports reading tests/*.values
- // (currently all flags are assigned the default READ_ONLY + DISABLED)
- ASSERT_FALSE(com_android_aconfig_test_enabled_ro());
- ASSERT_FALSE(provider_->enabled_ro());
- ASSERT_FALSE(enabled_ro());
+ ASSERT_TRUE(com_android_aconfig_test_enabled_ro());
+ ASSERT_TRUE(provider_->enabled_ro());
+ ASSERT_TRUE(enabled_ro());
}
TEST(AconfigTest, TestDisabledReadWriteFlag) {
@@ -40,19 +38,15 @@
}
TEST(AconfigTest, TestEnabledReadWriteFlag) {
- // TODO: change to assertTrue(enabledRo()) when the build supports reading tests/*.values
- // (currently all flags are assigned the default READ_ONLY + DISABLED)
- ASSERT_FALSE(com_android_aconfig_test_enabled_rw());
- ASSERT_FALSE(provider_->enabled_rw());
- ASSERT_FALSE(enabled_rw());
+ ASSERT_TRUE(com_android_aconfig_test_enabled_rw());
+ ASSERT_TRUE(provider_->enabled_rw());
+ ASSERT_TRUE(enabled_rw());
}
TEST(AconfigTest, TestEnabledFixedReadOnlyFlag) {
- // TODO: change to assertTrue(enabledFixedRo()) when the build supports reading tests/*.values
- // (currently all flags are assigned the default READ_ONLY + DISABLED)
- ASSERT_FALSE(com_android_aconfig_test_enabled_fixed_ro());
- ASSERT_FALSE(provider_->enabled_fixed_ro());
- ASSERT_FALSE(enabled_fixed_ro());
+ ASSERT_TRUE(com_android_aconfig_test_enabled_fixed_ro());
+ ASSERT_TRUE(provider_->enabled_fixed_ro());
+ ASSERT_TRUE(enabled_fixed_ro());
}
int main(int argc, char** argv) {
diff --git a/tools/aconfig/tests/aconfig_test_mode_test.rs b/tools/aconfig/tests/aconfig_test_mode_test.rs
index 3f56d2c..a08fbab 100644
--- a/tools/aconfig/tests/aconfig_test_mode_test.rs
+++ b/tools/aconfig/tests/aconfig_test_mode_test.rs
@@ -3,22 +3,21 @@
fn test_flags() {
assert!(!aconfig_test_rust_library::disabled_ro());
assert!(!aconfig_test_rust_library::disabled_rw());
- // TODO: Fix template to not default both disabled and enabled to false
- assert!(!aconfig_test_rust_library::enabled_ro());
- assert!(!aconfig_test_rust_library::enabled_rw());
+ assert!(aconfig_test_rust_library::enabled_ro());
+ assert!(aconfig_test_rust_library::enabled_rw());
aconfig_test_rust_library::set_disabled_ro(true);
assert!(aconfig_test_rust_library::disabled_ro());
aconfig_test_rust_library::set_disabled_rw(true);
assert!(aconfig_test_rust_library::disabled_rw());
- aconfig_test_rust_library::set_enabled_ro(true);
- assert!(aconfig_test_rust_library::enabled_ro());
- aconfig_test_rust_library::set_enabled_rw(true);
- assert!(aconfig_test_rust_library::enabled_rw());
+ aconfig_test_rust_library::set_enabled_ro(false);
+ assert!(!aconfig_test_rust_library::enabled_ro());
+ aconfig_test_rust_library::set_enabled_rw(false);
+ assert!(!aconfig_test_rust_library::enabled_rw());
aconfig_test_rust_library::reset_flags();
assert!(!aconfig_test_rust_library::disabled_ro());
assert!(!aconfig_test_rust_library::disabled_rw());
- assert!(!aconfig_test_rust_library::enabled_ro());
- assert!(!aconfig_test_rust_library::enabled_rw());
+ assert!(aconfig_test_rust_library::enabled_ro());
+ assert!(aconfig_test_rust_library::enabled_rw());
}
diff --git a/tools/aconfig/tests/aconfig_test_test_variant.cpp b/tools/aconfig/tests/aconfig_test_test_variant.cpp
index 8a745c5..b3cf710 100644
--- a/tools/aconfig/tests/aconfig_test_test_variant.cpp
+++ b/tools/aconfig/tests/aconfig_test_test_variant.cpp
@@ -33,11 +33,9 @@
}
TEST_F(AconfigTest, TestEnabledReadOnlyFlag) {
- // TODO: change to assertTrue(enabledRo()) when the build supports reading tests/*.values
- // (currently all flags are assigned the default READ_ONLY + DISABLED)
- ASSERT_FALSE(com_android_aconfig_test_enabled_ro());
- ASSERT_FALSE(provider_->enabled_ro());
- ASSERT_FALSE(enabled_ro());
+ ASSERT_TRUE(com_android_aconfig_test_enabled_ro());
+ ASSERT_TRUE(provider_->enabled_ro());
+ ASSERT_TRUE(enabled_ro());
}
TEST_F(AconfigTest, TestDisabledReadWriteFlag) {
@@ -47,19 +45,15 @@
}
TEST_F(AconfigTest, TestEnabledReadWriteFlag) {
- // TODO: change to assertTrue(enabledRo()) when the build supports reading tests/*.values
- // (currently all flags are assigned the default READ_ONLY + DISABLED)
- ASSERT_FALSE(com_android_aconfig_test_enabled_rw());
- ASSERT_FALSE(provider_->enabled_rw());
- ASSERT_FALSE(enabled_rw());
+ ASSERT_TRUE(com_android_aconfig_test_enabled_rw());
+ ASSERT_TRUE(provider_->enabled_rw());
+ ASSERT_TRUE(enabled_rw());
}
TEST_F(AconfigTest, TestEnabledFixedReadOnlyFlag) {
- // TODO: change to assertTrue(enabledFixedRo()) when the build supports reading tests/*.values
- // (currently all flags are assigned the default READ_ONLY + DISABLED)
- ASSERT_FALSE(com_android_aconfig_test_enabled_fixed_ro());
- ASSERT_FALSE(provider_->enabled_fixed_ro());
- ASSERT_FALSE(enabled_fixed_ro());
+ ASSERT_TRUE(com_android_aconfig_test_enabled_fixed_ro());
+ ASSERT_TRUE(provider_->enabled_fixed_ro());
+ ASSERT_TRUE(enabled_fixed_ro());
}
TEST_F(AconfigTest, OverrideFlagValue) {
@@ -70,14 +64,14 @@
TEST_F(AconfigTest, ResetFlagValue) {
ASSERT_FALSE(disabled_ro());
- ASSERT_FALSE(enabled_ro());
- disabled_ro(true);
- enabled_ro(true);
- ASSERT_TRUE(disabled_ro());
ASSERT_TRUE(enabled_ro());
+ disabled_ro(true);
+ enabled_ro(false);
+ ASSERT_TRUE(disabled_ro());
+ ASSERT_FALSE(enabled_ro());
reset_flags();
ASSERT_FALSE(disabled_ro());
- ASSERT_FALSE(enabled_ro());
+ ASSERT_TRUE(enabled_ro());
}
int main(int argc, char** argv) {
diff --git a/tools/aconfig/tests/first.values b/tools/aconfig/tests/first.values
index 731ce84..2efb463 100644
--- a/tools/aconfig/tests/first.values
+++ b/tools/aconfig/tests/first.values
@@ -40,3 +40,9 @@
state: DISABLED
permission: READ_WRITE
}
+flag_value {
+ package: "com.android.aconfig.test"
+ name: "enabled_fixed_ro_exported"
+ state: ENABLED
+ permission: READ_ONLY
+}
diff --git a/tools/aconfig/tests/storage_test_4.aconfig b/tools/aconfig/tests/storage_test_4.aconfig
new file mode 100644
index 0000000..333fe09
--- /dev/null
+++ b/tools/aconfig/tests/storage_test_4.aconfig
@@ -0,0 +1,17 @@
+package: "com.android.aconfig.storage.test_4"
+container: "system"
+
+flag {
+ name: "enabled_ro"
+ namespace: "aconfig_test"
+ description: "This flag is ENABLED + READ_ONLY"
+ bug: "abc"
+}
+
+flag {
+ name: "enabled_fixed_ro"
+ namespace: "aconfig_test"
+ description: "This flag is fixed READ_ONLY + ENABLED"
+ bug: ""
+ is_fixed_read_only: true
+}
diff --git a/tools/aconfig/tests/test.aconfig b/tools/aconfig/tests/test.aconfig
index 014bced..c11508a 100644
--- a/tools/aconfig/tests/test.aconfig
+++ b/tools/aconfig/tests/test.aconfig
@@ -78,3 +78,12 @@
bug: "111"
is_exported: true
}
+
+flag {
+ name: "enabled_fixed_ro_exported"
+ namespace: "aconfig_test"
+ description: "This flag is fixed ENABLED + READ_ONLY and exported"
+ bug: "111"
+ is_fixed_read_only: true
+ is_exported: true
+}
\ No newline at end of file
diff --git a/tools/perf/benchmarks b/tools/perf/benchmarks
new file mode 100755
index 0000000..df4c87b
--- /dev/null
+++ b/tools/perf/benchmarks
@@ -0,0 +1,709 @@
+#!/usr/bin/env python3
+# 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.
+
+import sys
+if __name__ == "__main__":
+ sys.dont_write_bytecode = True
+
+import argparse
+import dataclasses
+import datetime
+import json
+import os
+import pathlib
+import random
+import re
+import shutil
+import subprocess
+import time
+import uuid
+
+import pretty
+import utils
+
+
+class FatalError(Exception):
+ def __init__(self):
+ pass
+
+
+class OptionsError(Exception):
+ def __init__(self, message):
+ self.message = message
+
+
+@dataclasses.dataclass(frozen=True)
+class Lunch:
+ "Lunch combination"
+
+ target_product: str
+ "TARGET_PRODUCT"
+
+ target_release: str
+ "TARGET_RELEASE"
+
+ target_build_variant: str
+ "TARGET_BUILD_VARIANT"
+
+ def ToDict(self):
+ return {
+ "TARGET_PRODUCT": self.target_product,
+ "TARGET_RELEASE": self.target_release,
+ "TARGET_BUILD_VARIANT": self.target_build_variant,
+ }
+
+ def Combine(self):
+ return f"{self.target_product}-{self.target_release}-{self.target_build_variant}"
+
+
+@dataclasses.dataclass(frozen=True)
+class Change:
+ "A change that we make to the tree, and how to undo it"
+ label: str
+ "String to print in the log when the change is made"
+
+ change: callable
+ "Function to change the source tree"
+
+ undo: callable
+ "Function to revert the source tree to its previous condition in the most minimal way possible."
+
+
+@dataclasses.dataclass(frozen=True)
+class Benchmark:
+ "Something we measure"
+
+ id: str
+ "Short ID for the benchmark, for the command line"
+
+ title: str
+ "Title for reports"
+
+ change: Change
+ "Source tree modification for the benchmark that will be measured"
+
+ modules: list[str]
+ "Build modules to build on soong command line"
+
+ preroll: int
+ "Number of times to run the build command to stabilize"
+
+ postroll: int
+ "Number of times to run the build command after reverting the action to stabilize"
+
+
+@dataclasses.dataclass(frozen=True)
+class FileSnapshot:
+ "Snapshot of a file's contents."
+
+ filename: str
+ "The file that was snapshottened"
+
+ contents: str
+ "The contents of the file"
+
+ def write(self):
+ "Write the contents back to the file"
+ with open(self.filename, "w") as f:
+ f.write(self.contents)
+
+
+def Snapshot(filename):
+ """Return a FileSnapshot with the file's current contents."""
+ with open(filename) as f:
+ contents = f.read()
+ return FileSnapshot(filename, contents)
+
+
+def Clean():
+ """Remove the out directory."""
+ def remove_out():
+ if os.path.exists("out"):
+ shutil.rmtree("out")
+ return Change(label="Remove out", change=remove_out, undo=lambda: None)
+
+
+def NoChange():
+ """No change to the source tree."""
+ return Change(label="No change", change=lambda: None, undo=lambda: None)
+
+
+def Create(filename):
+ "Create an action to create `filename`. The parent directory must exist."
+ def create():
+ with open(filename, "w") as f:
+ pass
+ def delete():
+ os.remove(filename)
+ return Change(
+ label=f"Create {filename}",
+ change=create,
+ undo=delete,
+ )
+
+
+def Modify(filename, contents, before=None):
+ """Create an action to modify `filename` by appending the result of `contents`
+ before the last instances of `before` in the file.
+
+ Raises an error if `before` doesn't appear in the file.
+ """
+ orig = Snapshot(filename)
+ if before:
+ index = orig.contents.rfind(before)
+ if index < 0:
+ report_error(f"{filename}: Unable to find string '{before}' for modify operation.")
+ raise FatalError()
+ else:
+ index = len(orig.contents)
+ modified = FileSnapshot(filename, orig.contents[:index] + contents() + orig.contents[index:])
+ if False:
+ print(f"Modify: {filename}")
+ x = orig.contents.replace("\n", "\n ORIG")
+ print(f" ORIG {x}")
+ x = modified.contents.replace("\n", "\n MODIFIED")
+ print(f" MODIFIED {x}")
+
+ return Change(
+ label="Modify " + filename,
+ change=lambda: modified.write(),
+ undo=lambda: orig.write()
+ )
+
+def AddJavaField(filename, prefix):
+ return Modify(filename,
+ lambda: f"{prefix} static final int BENCHMARK = {random.randint(0, 1000000)};\n",
+ before="}")
+
+
+def Comment(prefix, suffix=""):
+ return lambda: prefix + " " + str(uuid.uuid4()) + suffix
+
+
+class BenchmarkReport():
+ "Information about a run of the benchmark"
+
+ lunch: Lunch
+ "lunch combo"
+
+ benchmark: Benchmark
+ "The benchmark object."
+
+ iteration: int
+ "Which iteration of the benchmark"
+
+ log_dir: str
+ "Path the the log directory, relative to the root of the reports directory"
+
+ preroll_duration_ns: [int]
+ "Durations of the in nanoseconds."
+
+ duration_ns: int
+ "Duration of the measured portion of the benchmark in nanoseconds."
+
+ postroll_duration_ns: [int]
+ "Durations of the postrolls in nanoseconds."
+
+ complete: bool
+ "Whether the benchmark made it all the way through the postrolls."
+
+ def __init__(self, lunch, benchmark, iteration, log_dir):
+ self.lunch = lunch
+ self.benchmark = benchmark
+ self.iteration = iteration
+ self.log_dir = log_dir
+ self.preroll_duration_ns = []
+ self.duration_ns = -1
+ self.postroll_duration_ns = []
+ self.complete = False
+
+ def ToDict(self):
+ return {
+ "lunch": self.lunch.ToDict(),
+ "id": self.benchmark.id,
+ "title": self.benchmark.title,
+ "modules": self.benchmark.modules,
+ "change": self.benchmark.change.label,
+ "iteration": self.iteration,
+ "log_dir": self.log_dir,
+ "preroll_duration_ns": self.preroll_duration_ns,
+ "duration_ns": self.duration_ns,
+ "postroll_duration_ns": self.postroll_duration_ns,
+ "complete": self.complete,
+ }
+
+class Runner():
+ """Runs the benchmarks."""
+
+ def __init__(self, options):
+ self._options = options
+ self._reports = []
+ self._complete = False
+
+ def Run(self):
+ """Run all of the user-selected benchmarks."""
+ # Clean out the log dir or create it if necessary
+ prepare_log_dir(self._options.LogDir())
+
+ try:
+ for lunch in self._options.Lunches():
+ print(lunch)
+ for benchmark in self._options.Benchmarks():
+ for iteration in range(self._options.Iterations()):
+ self._run_benchmark(lunch, benchmark, iteration)
+ self._complete = True
+ finally:
+ self._write_summary()
+
+
+ def _run_benchmark(self, lunch, benchmark, iteration):
+ """Run a single benchmark."""
+ benchmark_log_subdir = self._log_dir(lunch, benchmark, iteration)
+ benchmark_log_dir = self._options.LogDir().joinpath(benchmark_log_subdir)
+
+ sys.stderr.write(f"STARTING BENCHMARK: {benchmark.id}\n")
+ sys.stderr.write(f" lunch: {lunch.Combine()}\n")
+ sys.stderr.write(f" iteration: {iteration}\n")
+ sys.stderr.write(f" benchmark_log_dir: {benchmark_log_dir}\n")
+
+ report = BenchmarkReport(lunch, benchmark, iteration, benchmark_log_subdir)
+ self._reports.append(report)
+
+ # Preroll builds
+ for i in range(benchmark.preroll):
+ ns = self._run_build(lunch, benchmark_log_dir.joinpath(f"pre_{i}"), benchmark.modules)
+ report.preroll_duration_ns.append(ns)
+
+ sys.stderr.write(f"PERFORMING CHANGE: {benchmark.change.label}\n")
+ if not self._options.DryRun():
+ benchmark.change.change()
+ try:
+
+ # Measured build
+ ns = self._run_build(lunch, benchmark_log_dir.joinpath("measured"), benchmark.modules)
+ report.duration_ns = ns
+
+ dist_one = self._options.DistOne()
+ if dist_one:
+ # If we're disting just one benchmark, save the logs and we can stop here.
+ self._dist(dist_one)
+ else:
+ # Postroll builds
+ for i in range(benchmark.preroll):
+ ns = self._run_build(lunch, benchmark_log_dir.joinpath(f"post_{i}"),
+ benchmark.modules)
+ report.postroll_duration_ns.append(ns)
+
+ finally:
+ # Always undo, even if we crashed or the build failed and we stopped.
+ sys.stderr.write(f"UNDOING CHANGE: {benchmark.change.label}\n")
+ if not self._options.DryRun():
+ benchmark.change.undo()
+
+ self._write_summary()
+ sys.stderr.write(f"FINISHED BENCHMARK: {benchmark.id}\n")
+
+ def _log_dir(self, lunch, benchmark, iteration):
+ """Construct the log directory fir a benchmark run."""
+ path = f"{lunch.Combine()}/{benchmark.id}"
+ # Zero pad to the correct length for correct alpha sorting
+ path += ("/%0" + str(len(str(self._options.Iterations()))) + "d") % iteration
+ return path
+
+ def _run_build(self, lunch, build_log_dir, modules):
+ """Builds the modules. Saves interesting log files to log_dir. Raises FatalError
+ if the build fails.
+ """
+ sys.stderr.write(f"STARTING BUILD {modules}\n")
+
+ before_ns = time.perf_counter_ns()
+ if not self._options.DryRun():
+ cmd = [
+ "build/soong/soong_ui.bash",
+ "--build-mode",
+ "--all-modules",
+ f"--dir={self._options.root}",
+ ] + modules
+ env = dict(os.environ)
+ env["TARGET_PRODUCT"] = lunch.target_product
+ env["TARGET_RELEASE"] = lunch.target_release
+ env["TARGET_BUILD_VARIANT"] = lunch.target_build_variant
+ returncode = subprocess.call(cmd, env=env)
+ if returncode != 0:
+ report_error(f"Build failed: {' '.join(cmd)}")
+ raise FatalError()
+
+ after_ns = time.perf_counter_ns()
+
+ # TODO: Copy some log files.
+
+ sys.stderr.write(f"FINISHED BUILD {modules}\n")
+
+ return after_ns - before_ns
+
+ def _dist(self, dist_dir):
+ out_dir = pathlib.Path("out")
+ dest_dir = pathlib.Path(dist_dir).joinpath("logs")
+ os.makedirs(dest_dir, exist_ok=True)
+ basenames = [
+ "build.trace.gz",
+ "soong.log",
+ "soong_build_metrics.pb",
+ "soong_metrics",
+ ]
+ for base in basenames:
+ src = out_dir.joinpath(base)
+ if src.exists():
+ sys.stderr.write(f"DIST: copied {src} to {dest_dir}\n")
+ shutil.copy(src, dest_dir)
+
+ def _write_summary(self):
+ # Write the results, even if the build failed or we crashed, including
+ # whether we finished all of the benchmarks.
+ data = {
+ "start_time": self._options.Timestamp().isoformat(),
+ "branch": self._options.Branch(),
+ "tag": self._options.Tag(),
+ "benchmarks": [report.ToDict() for report in self._reports],
+ "complete": self._complete,
+ }
+ with open(self._options.LogDir().joinpath("summary.json"), "w", encoding="utf-8") as f:
+ json.dump(data, f, indent=2, sort_keys=True)
+
+
+def benchmark_table(benchmarks):
+ rows = [("ID", "DESCRIPTION", "REBUILD"),]
+ rows += [(benchmark.id, benchmark.title, " ".join(benchmark.modules)) for benchmark in
+ benchmarks]
+ return rows
+
+
+def prepare_log_dir(directory):
+ if os.path.exists(directory):
+ # If it exists and isn't a directory, fail.
+ if not os.path.isdir(directory):
+ report_error(f"Log directory already exists but isn't a directory: {directory}")
+ raise FatalError()
+ # Make sure the directory is empty. Do this rather than deleting it to handle
+ # symlinks cleanly.
+ for filename in os.listdir(directory):
+ entry = os.path.join(directory, filename)
+ if os.path.isdir(entry):
+ shutil.rmtree(entry)
+ else:
+ os.unlink(entry)
+ else:
+ # Create it
+ os.makedirs(directory)
+
+
+class Options():
+ def __init__(self):
+ self._had_error = False
+
+ # Wall time clock when we started
+ self._timestamp = datetime.datetime.now(datetime.timezone.utc)
+
+ # Move to the root of the tree right away. Everything must happen from there.
+ self.root = utils.get_root()
+ if not self.root:
+ report_error("Unable to find root of tree from cwd.")
+ raise FatalError()
+ os.chdir(self.root)
+
+ # Initialize the Benchmarks. Note that this pre-loads all of the files, etc.
+ # Doing all that here forces us to fail fast if one of them can't load a required
+ # file, at the cost of a small startup speed. Don't make this do something slow
+ # like scan the whole tree.
+ self._init_benchmarks()
+
+ # Argument parsing
+ epilog = f"""
+benchmarks:
+{pretty.FormatTable(benchmark_table(self._benchmarks), prefix=" ")}
+"""
+
+ parser = argparse.ArgumentParser(
+ prog="benchmarks",
+ allow_abbrev=False, # Don't let people write unsupportable scripts.
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ epilog=epilog,
+ description="Run build system performance benchmarks.")
+ self.parser = parser
+
+ parser.add_argument("--log-dir",
+ help="Directory for logs. Default is $TOP/../benchmarks/.")
+ parser.add_argument("--dated-logs", action="store_true",
+ help="Append timestamp to log dir.")
+ parser.add_argument("-n", action="store_true", dest="dry_run",
+ help="Dry run. Don't run the build commands but do everything else.")
+ parser.add_argument("--tag",
+ help="Variant of the run, for when there are multiple perf runs.")
+ parser.add_argument("--lunch", nargs="*",
+ help="Lunch combos to test")
+ parser.add_argument("--iterations", type=int, default=1,
+ help="Number of iterations of each test to run.")
+ parser.add_argument("--branch", type=str,
+ help="Specify branch. Otherwise a guess will be made based on repo.")
+ parser.add_argument("--benchmark", nargs="*", default=[b.id for b in self._benchmarks],
+ metavar="BENCHMARKS",
+ help="Benchmarks to run. Default suite will be run if omitted.")
+ parser.add_argument("--dist-one", action="store_true",
+ help="Copy logs and metrics to the given dist dir. Requires that only"
+ + " one benchmark be supplied. Postroll steps will be skipped.")
+
+ self._args = parser.parse_args()
+
+ self._branch = self._branch()
+ self._log_dir = self._log_dir()
+ self._lunches = self._lunches()
+
+ # Validate the benchmark ids
+ all_ids = [benchmark.id for benchmark in self._benchmarks]
+ bad_ids = [id for id in self._args.benchmark if id not in all_ids]
+ if bad_ids:
+ for id in bad_ids:
+ self._error(f"Invalid benchmark: {id}")
+
+ # --dist-one requires that only one benchmark be supplied
+ if self._args.dist_one and len(self.Benchmarks()) != 1:
+ self._error("--dist-one requires that exactly one --benchmark.")
+
+ if self._had_error:
+ raise FatalError()
+
+ def Timestamp(self):
+ return self._timestamp
+
+ def _branch(self):
+ """Return the branch, either from the command line or by guessing from repo."""
+ if self._args.branch:
+ return self._args.branch
+ try:
+ branch = subprocess.check_output(f"cd {self.root}/.repo/manifests"
+ + " && git rev-parse --abbrev-ref --symbolic-full-name @{u}",
+ shell=True, encoding="utf-8")
+ return branch.strip().split("/")[-1]
+ except subprocess.CalledProcessError as ex:
+ report_error("Can't get branch from .repo dir. Specify --branch argument")
+ report_error(str(ex))
+ raise FatalError()
+
+ def Branch(self):
+ return self._branch
+
+ def _log_dir(self):
+ "The log directory to use, based on the current options"
+ if self._args.log_dir:
+ d = pathlib.Path(self._args.log_dir).resolve().absolute()
+ else:
+ d = self.root.joinpath("..", utils.DEFAULT_REPORT_DIR)
+ if self._args.dated_logs:
+ d = d.joinpath(self._timestamp.strftime('%Y-%m-%d'))
+ d = d.joinpath(self._branch)
+ if self._args.tag:
+ d = d.joinpath(self._args.tag)
+ return d.resolve().absolute()
+
+ def LogDir(self):
+ return self._log_dir
+
+ def Benchmarks(self):
+ return [b for b in self._benchmarks if b.id in self._args.benchmark]
+
+ def Tag(self):
+ return self._args.tag
+
+ def DryRun(self):
+ return self._args.dry_run
+
+ def _lunches(self):
+ def parse_lunch(lunch):
+ parts = lunch.split("-")
+ if len(parts) != 3:
+ raise OptionsError(f"Invalid lunch combo: {lunch}")
+ return Lunch(parts[0], parts[1], parts[2])
+ # If they gave lunch targets on the command line use that
+ if self._args.lunch:
+ result = []
+ # Split into Lunch objects
+ for lunch in self._args.lunch:
+ try:
+ result.append(parse_lunch(lunch))
+ except OptionsError as ex:
+ self._error(ex.message)
+ return result
+ # Use whats in the environment
+ product = os.getenv("TARGET_PRODUCT")
+ release = os.getenv("TARGET_RELEASE")
+ variant = os.getenv("TARGET_BUILD_VARIANT")
+ if (not product) or (not release) or (not variant):
+ # If they didn't give us anything, fail rather than guessing. There's no good
+ # default for AOSP.
+ self._error("No lunch combo specified. Either pass --lunch argument or run lunch.")
+ return []
+ return [Lunch(product, release, variant),]
+
+ def Lunches(self):
+ return self._lunches
+
+ def Iterations(self):
+ return self._args.iterations
+
+ def DistOne(self):
+ return self._args.dist_one
+
+ def _init_benchmarks(self):
+ """Initialize the list of benchmarks."""
+ # Assumes that we've already chdired to the root of the tree.
+ self._benchmarks = [
+ Benchmark(id="full",
+ title="Full build",
+ change=Clean(),
+ modules=["droid"],
+ preroll=0,
+ postroll=3,
+ ),
+ Benchmark(id="nochange",
+ title="No change",
+ change=NoChange(),
+ modules=["droid"],
+ preroll=2,
+ postroll=3,
+ ),
+ Benchmark(id="unreferenced",
+ title="Create unreferenced file",
+ change=Create("bionic/unreferenced.txt"),
+ modules=["droid"],
+ preroll=1,
+ postroll=2,
+ ),
+ Benchmark(id="modify_bp",
+ title="Modify Android.bp",
+ change=Modify("bionic/libc/Android.bp", Comment("//")),
+ modules=["droid"],
+ preroll=1,
+ postroll=3,
+ ),
+ Benchmark(id="modify_stdio",
+ title="Modify stdio.cpp",
+ change=Modify("bionic/libc/stdio/stdio.cpp", Comment("//")),
+ modules=["libc"],
+ preroll=1,
+ postroll=2,
+ ),
+ Benchmark(id="modify_adbd",
+ title="Modify adbd",
+ change=Modify("packages/modules/adb/daemon/main.cpp", Comment("//")),
+ modules=["adbd"],
+ preroll=1,
+ postroll=2,
+ ),
+ Benchmark(id="services_private_field",
+ title="Add private field to ActivityManagerService.java",
+ change=AddJavaField("frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java",
+ "private"),
+ modules=["services"],
+ preroll=1,
+ postroll=2,
+ ),
+ Benchmark(id="services_public_field",
+ title="Add public field to ActivityManagerService.java",
+ change=AddJavaField("frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java",
+ "/** @hide */ public"),
+ modules=["services"],
+ preroll=1,
+ postroll=2,
+ ),
+ Benchmark(id="services_api",
+ title="Add API to ActivityManagerService.javaa",
+ change=AddJavaField("frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java",
+ "@android.annotation.SuppressLint(\"UnflaggedApi\") public"),
+ modules=["services"],
+ preroll=1,
+ postroll=2,
+ ),
+ Benchmark(id="framework_private_field",
+ title="Add private field to Settings.java",
+ change=AddJavaField("frameworks/base/core/java/android/provider/Settings.java",
+ "private"),
+ modules=["framework-minus-apex"],
+ preroll=1,
+ postroll=2,
+ ),
+ Benchmark(id="framework_public_field",
+ title="Add public field to Settings.java",
+ change=AddJavaField("frameworks/base/core/java/android/provider/Settings.java",
+ "/** @hide */ public"),
+ modules=["framework-minus-apex"],
+ preroll=1,
+ postroll=2,
+ ),
+ Benchmark(id="framework_api",
+ title="Add API to Settings.java",
+ change=AddJavaField("frameworks/base/core/java/android/provider/Settings.java",
+ "@android.annotation.SuppressLint(\"UnflaggedApi\") public"),
+ modules=["framework-minus-apex"],
+ preroll=1,
+ postroll=2,
+ ),
+ Benchmark(id="modify_framework_resource",
+ title="Modify framework resource",
+ change=Modify("frameworks/base/core/res/res/values/config.xml",
+ lambda: str(uuid.uuid4()),
+ before="</string>"),
+ modules=["framework-minus-apex"],
+ preroll=1,
+ postroll=2,
+ ),
+ Benchmark(id="add_framework_resource",
+ title="Add framework resource",
+ change=Modify("frameworks/base/core/res/res/values/config.xml",
+ lambda: f"<string name=\"BENCHMARK\">{uuid.uuid4()}</string>",
+ before="</resources>"),
+ modules=["framework-minus-apex"],
+ preroll=1,
+ postroll=2,
+ ),
+ Benchmark(id="add_systemui_field",
+ title="Add SystemUI field",
+ change=AddJavaField("frameworks/base/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java",
+ "public"),
+ modules=["SystemUI"],
+ preroll=1,
+ postroll=2,
+ ),
+ ]
+
+ def _error(self, message):
+ report_error(message)
+ self._had_error = True
+
+
+def report_error(message):
+ sys.stderr.write(f"error: {message}\n")
+
+
+def main(argv):
+ try:
+ options = Options()
+ runner = Runner(options)
+ runner.Run()
+ except FatalError:
+ sys.stderr.write(f"FAILED\n")
+
+
+if __name__ == "__main__":
+ main(sys.argv)
diff --git a/tools/perf/format_benchmarks b/tools/perf/format_benchmarks
new file mode 100755
index 0000000..c01aa76
--- /dev/null
+++ b/tools/perf/format_benchmarks
@@ -0,0 +1,199 @@
+#!/usr/bin/env python3
+# 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.
+
+import sys
+if __name__ == "__main__":
+ sys.dont_write_bytecode = True
+
+import argparse
+import dataclasses
+import datetime
+import json
+import os
+import pathlib
+import statistics
+import zoneinfo
+
+import pretty
+import utils
+
+# TODO:
+# - Flag if the last postroll build was more than 15 seconds or something. That's
+# an indicator that something is amiss.
+# - Add a mode to print all of the values for multi-iteration runs
+# - Add a flag to reorder the tags
+# - Add a flag to reorder the headers in order to show grouping more clearly.
+
+
+def FindSummaries(args):
+ def find_summaries(directory):
+ return [str(p.resolve()) for p in pathlib.Path(directory).glob("**/summary.json")]
+ if not args:
+ # If they didn't give an argument, use the default dir
+ root = utils.get_root()
+ if not root:
+ return []
+ return find_summaries(root.joinpath("..", utils.DEFAULT_REPORT_DIR))
+ results = list()
+ for arg in args:
+ if os.path.isfile(arg):
+ # If it's a file add that
+ results.append(arg)
+ elif os.path.isdir(arg):
+ # If it's a directory, find all of the files there
+ results += find_summaries(arg)
+ else:
+ sys.stderr.write(f"Invalid summary argument: {arg}\n")
+ sys.exit(1)
+ return sorted(list(results))
+
+
+def LoadSummary(filename):
+ with open(filename) as f:
+ return json.load(f)
+
+# Columns:
+# Date
+# Branch
+# Tag
+# --
+# Lunch
+# Rows:
+# Benchmark
+
+@dataclasses.dataclass(frozen=True)
+class Key():
+ pass
+
+class Column():
+ def __init__(self):
+ pass
+
+def lunch_str(d):
+ "Convert a lunch dict to a string"
+ return f"{d['TARGET_PRODUCT']}-{d['TARGET_RELEASE']}-{d['TARGET_BUILD_VARIANT']}"
+
+def group_by(l, key):
+ "Return a list of tuples, grouped by key, sorted by key"
+ result = {}
+ for item in l:
+ result.setdefault(key(item), []).append(item)
+ return [(k, v) for k, v in result.items()]
+
+
+class Table:
+ def __init__(self):
+ self._data = {}
+ self._rows = []
+ self._cols = []
+
+ def Set(self, column_key, row_key, data):
+ self._data[(column_key, row_key)] = data
+ if not column_key in self._cols:
+ self._cols.append(column_key)
+ if not row_key in self._rows:
+ self._rows.append(row_key)
+
+ def Write(self, out):
+ table = []
+ # Expand the column items
+ for row in zip(*self._cols):
+ if row.count(row[0]) == len(row):
+ continue
+ table.append([""] + [col for col in row])
+ if table:
+ table.append(pretty.SEPARATOR)
+ # Populate the data
+ for row in self._rows:
+ table.append([str(row)] + [str(self._data.get((col, row), "")) for col in self._cols])
+ out.write(pretty.FormatTable(table))
+
+
+def format_duration_sec(ns):
+ "Format a duration in ns to second precision"
+ sec = round(ns / 1000000000)
+ h, sec = divmod(sec, 60*60)
+ m, sec = divmod(sec, 60)
+ result = ""
+ if h > 0:
+ result += f"{h:2d}h "
+ if h > 0 or m > 0:
+ result += f"{m:2d}m "
+ return result + f"{sec:2d}s"
+
+
+def main(argv):
+ parser = argparse.ArgumentParser(
+ prog="format_benchmarks",
+ allow_abbrev=False, # Don't let people write unsupportable scripts.
+ description="Print analysis tables for benchmarks")
+
+ parser.add_argument("--tags", nargs="*",
+ help="The tags to print, in order.")
+
+ parser.add_argument("summaries", nargs="*",
+ help="A summary.json file or a directory in which to look for summaries.")
+
+ args = parser.parse_args()
+
+ # Load the summaries
+ summaries = [(s, LoadSummary(s)) for s in FindSummaries(args.summaries)]
+
+ # Convert to MTV time
+ for filename, s in summaries:
+ dt = datetime.datetime.fromisoformat(s["start_time"])
+ dt = dt.astimezone(zoneinfo.ZoneInfo("America/Los_Angeles"))
+ s["datetime"] = dt
+ s["date"] = datetime.date(dt.year, dt.month, dt.day)
+
+ # Filter out tags we don't want
+ if args.tags:
+ summaries = [(f, s) for f, s in summaries if s.get("tag", "") in args.tags]
+
+ # If they supplied tags, sort in that order, otherwise sort by tag
+ if args.tags:
+ tagsort = lambda tag: args.tags.index(tag)
+ else:
+ tagsort = lambda tag: tag
+
+ # Sort the summaries
+ summaries.sort(key=lambda s: (s[1]["date"], s[1]["branch"], tagsort(s[1]["tag"])))
+
+ # group the benchmarks by column and iteration
+ def bm_key(b):
+ return (
+ lunch_str(b["lunch"]),
+ )
+ for filename, summary in summaries:
+ summary["columns"] = [(key, group_by(bms, lambda b: b["id"])) for key, bms
+ in group_by(summary["benchmarks"], bm_key)]
+
+ # Build the table
+ table = Table()
+ for filename, summary in summaries:
+ for key, column in summary["columns"]:
+ for id, cell in column:
+ duration_ns = statistics.median([b["duration_ns"] for b in cell])
+ table.Set(tuple([summary["date"].strftime("YYYY-MM-DD"),
+ summary["branch"],
+ summary["tag"]]
+ + list(key)),
+ cell[0]["title"], format_duration_sec(duration_ns))
+
+ table.Write(sys.stdout)
+
+if __name__ == "__main__":
+ main(sys.argv)
+
diff --git a/tools/perf/pretty.py b/tools/perf/pretty.py
new file mode 100644
index 0000000..1b59098
--- /dev/null
+++ b/tools/perf/pretty.py
@@ -0,0 +1,52 @@
+# 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.
+
+# Formatting utilities
+
+class Sentinel():
+ pass
+
+SEPARATOR = Sentinel()
+
+def FormatTable(data, prefix=""):
+ """Pretty print a table.
+
+ Prefixes each row with `prefix`.
+ """
+ if not data:
+ return ""
+ widths = [max([len(x) if x else 0 for x in col]) for col
+ in zip(*[d for d in data if not isinstance(d, Sentinel)])]
+ result = ""
+ colsep = " "
+ for row in data:
+ result += prefix
+ if row == SEPARATOR:
+ for w in widths:
+ result += "-" * w
+ result += colsep
+ result += "\n"
+ else:
+ for i in range(len(row)):
+ cell = row[i] if row[i] else ""
+ if i != 0:
+ result += " " * (widths[i] - len(cell))
+ result += cell
+ if i == 0:
+ result += " " * (widths[i] - len(cell))
+ result += colsep
+ result += "\n"
+ return result
+
+
diff --git a/tools/perf/utils.py b/tools/perf/utils.py
new file mode 100644
index 0000000..08e393f
--- /dev/null
+++ b/tools/perf/utils.py
@@ -0,0 +1,30 @@
+# 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.
+
+import os
+import pathlib
+
+DEFAULT_REPORT_DIR = "benchmarks"
+
+def get_root():
+ top_dir = os.environ.get("ANDROID_BUILD_TOP")
+ if top_dir:
+ return pathlib.Path(top_dir).resolve()
+ d = pathlib.Path.cwd()
+ while True:
+ if d.joinpath("build", "soong", "soong_ui.bash").exists():
+ return d.resolve().absolute()
+ d = d.parent
+ if d == pathlib.Path("/"):
+ return None
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index 4a5facd..b65764b 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -1038,7 +1038,11 @@
partition_timestamps_flags = []
# Enforce a max timestamp this payload can be applied on top of.
if OPTIONS.downgrade:
- max_timestamp = source_info.GetBuildProp("ro.build.date.utc")
+ # When generating ota between merged target-files, partition build date can
+ # decrease in target, at the same time as ro.build.date.utc increases,
+ # so always pick largest value.
+ max_timestamp = max(source_info.GetBuildProp("ro.build.date.utc"),
+ str(metadata.postcondition.timestamp))
partition_timestamps_flags = GeneratePartitionTimestampFlagsDowngrade(
metadata.precondition.partition_state,
metadata.postcondition.partition_state
diff --git a/tools/releasetools/ota_utils.py b/tools/releasetools/ota_utils.py
index ddd2d36..048a497 100644
--- a/tools/releasetools/ota_utils.py
+++ b/tools/releasetools/ota_utils.py
@@ -364,26 +364,66 @@
# Only incremental OTAs are allowed to reach here.
assert OPTIONS.incremental_source is not None
+ # used for logging upon errors
+ log_downgrades = []
+ log_upgrades = []
+
post_timestamp = target_info.GetBuildProp("ro.build.date.utc")
pre_timestamp = source_info.GetBuildProp("ro.build.date.utc")
- is_downgrade = int(post_timestamp) < int(pre_timestamp)
+ if int(post_timestamp) < int(pre_timestamp):
+ logger.info(f"ro.build.date.utc pre timestamp: {pre_timestamp}, "
+ f"post timestamp: {post_timestamp}. Downgrade detected.")
+ log_downgrades.append(f"ro.build.date.utc pre: {pre_timestamp} post: {post_timestamp}")
+ else:
+ logger.info(f"ro.build.date.utc pre timestamp: {pre_timestamp}, "
+ f"post timestamp: {post_timestamp}.")
+ log_upgrades.append(f"ro.build.date.utc pre: {pre_timestamp} post: {post_timestamp}")
+
+ # When merging system and vendor target files, it is not enough
+ # to check ro.build.date.utc, the timestamp for each partition must
+ # be checked.
+ if source_info.is_ab:
+ ab_partitions = set(source_info.get("ab_partitions"))
+ for partition in sorted(set(PARTITIONS_WITH_BUILD_PROP) & ab_partitions):
+
+ partition_prop = source_info.get('{}.build.prop'.format(partition))
+ # Skip if the partition is missing, or it doesn't have a build.prop
+ if not partition_prop or not partition_prop.build_props:
+ continue
+ partition_prop = target_info.get('{}.build.prop'.format(partition))
+ # Skip if the partition is missing, or it doesn't have a build.prop
+ if not partition_prop or not partition_prop.build_props:
+ continue
+
+ post_timestamp = target_info.GetPartitionBuildProp(
+ 'ro.build.date.utc', partition)
+ pre_timestamp = source_info.GetPartitionBuildProp(
+ 'ro.build.date.utc', partition)
+ if int(post_timestamp) < int(pre_timestamp):
+ logger.info(f"Partition {partition} pre timestamp: {pre_timestamp}, "
+ f"post time: {post_timestamp}. Downgrade detected.")
+ log_downgrades.append(f"{partition} pre: {pre_timestamp} post: {post_timestamp}")
+ else:
+ logger.info(f"Partition {partition} pre timestamp: {pre_timestamp}, "
+ f"post timestamp: {post_timestamp}.")
+ log_upgrades.append(f"{partition} pre: {pre_timestamp} post: {post_timestamp}")
if OPTIONS.spl_downgrade:
metadata_proto.spl_downgrade = True
if OPTIONS.downgrade:
- if not is_downgrade:
+ if len(log_downgrades) == 0:
raise RuntimeError(
"--downgrade or --override_timestamp specified but no downgrade "
- "detected: pre: %s, post: %s" % (pre_timestamp, post_timestamp))
+ "detected. Current values for ro.build.date.utc: " + ', '.join(log_upgrades))
metadata_proto.downgrade = True
else:
- if is_downgrade:
+ if len(log_downgrades) != 0:
raise RuntimeError(
- "Downgrade detected based on timestamp check: pre: %s, post: %s. "
+ "Downgrade detected based on timestamp check in ro.build.date.utc. "
"Need to specify --override_timestamp OR --downgrade to allow "
- "building the incremental." % (pre_timestamp, post_timestamp))
-
+ "building the incremental. Downgrades detected for: "
+ + ', '.join(log_downgrades))
def ComputeRuntimeBuildInfos(default_build_info, boot_variable_values):
"""Returns a set of build info objects that may exist during runtime."""
diff --git a/tools/releasetools/test_ota_from_target_files.py b/tools/releasetools/test_ota_from_target_files.py
index ad0f7a8..d1e76b9 100644
--- a/tools/releasetools/test_ota_from_target_files.py
+++ b/tools/releasetools/test_ota_from_target_files.py
@@ -163,6 +163,20 @@
'oem_fingerprint_properties': 'ro.product.device ro.product.brand',
}
+ TEST_TARGET_VENDOR_INFO_DICT = common.PartitionBuildProps.FromDictionary(
+ 'vendor', {
+ 'ro.vendor.build.date.utc' : '87654321',
+ 'ro.product.vendor.device':'vendor-device',
+ 'ro.vendor.build.fingerprint': 'build-fingerprint-vendor'}
+ )
+
+ TEST_SOURCE_VENDOR_INFO_DICT = common.PartitionBuildProps.FromDictionary(
+ 'vendor', {
+ 'ro.vendor.build.date.utc' : '12345678',
+ 'ro.product.vendor.device':'vendor-device',
+ 'ro.vendor.build.fingerprint': 'build-fingerprint-vendor'}
+ )
+
def setUp(self):
self.testdata_dir = test_utils.get_testdata_dir()
self.assertTrue(os.path.exists(self.testdata_dir))
@@ -351,6 +365,13 @@
source_info['build.prop'].build_props['ro.build.date.utc'],
target_info['build.prop'].build_props['ro.build.date.utc'])
+ @staticmethod
+ def _test_GetPackageMetadata_swapVendorBuildTimestamps(target_info, source_info):
+ (target_info['vendor.build.prop'].build_props['ro.vendor.build.date.utc'],
+ source_info['vendor.build.prop'].build_props['ro.vendor.build.date.utc']) = (
+ source_info['vendor.build.prop'].build_props['ro.vendor.build.date.utc'],
+ target_info['vendor.build.prop'].build_props['ro.vendor.build.date.utc'])
+
def test_GetPackageMetadata_unintentionalDowngradeDetected(self):
target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT)
source_info_dict = copy.deepcopy(self.TEST_SOURCE_INFO_DICT)
@@ -363,6 +384,24 @@
self.assertRaises(RuntimeError, self.GetLegacyOtaMetadata, target_info,
source_info)
+ def test_GetPackageMetadata_unintentionalVendorDowngradeDetected(self):
+ target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT)
+ target_info_dict['ab_update'] = 'true'
+ target_info_dict['ab_partitions'] = ['vendor']
+ target_info_dict["vendor.build.prop"] = copy.deepcopy(self.TEST_TARGET_VENDOR_INFO_DICT)
+ source_info_dict = copy.deepcopy(self.TEST_SOURCE_INFO_DICT)
+ source_info_dict['ab_update'] = 'true'
+ source_info_dict['ab_partitions'] = ['vendor']
+ source_info_dict["vendor.build.prop"] = copy.deepcopy(self.TEST_SOURCE_VENDOR_INFO_DICT)
+ self._test_GetPackageMetadata_swapVendorBuildTimestamps(
+ target_info_dict, source_info_dict)
+
+ target_info = common.BuildInfo(target_info_dict, None)
+ source_info = common.BuildInfo(source_info_dict, None)
+ common.OPTIONS.incremental_source = ''
+ self.assertRaises(RuntimeError, self.GetLegacyOtaMetadata, target_info,
+ source_info)
+
def test_GetPackageMetadata_downgrade(self):
target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT)
source_info_dict = copy.deepcopy(self.TEST_SOURCE_INFO_DICT)
@@ -397,6 +436,55 @@
},
metadata)
+ def test_GetPackageMetadata_vendorDowngrade(self):
+ target_info_dict = copy.deepcopy(self.TEST_TARGET_INFO_DICT)
+ target_info_dict['ab_update'] = 'true'
+ target_info_dict['ab_partitions'] = ['vendor']
+ target_info_dict["vendor.build.prop"] = copy.deepcopy(self.TEST_TARGET_VENDOR_INFO_DICT)
+ source_info_dict = copy.deepcopy(self.TEST_SOURCE_INFO_DICT)
+ source_info_dict['ab_update'] = 'true'
+ source_info_dict['ab_partitions'] = ['vendor']
+ source_info_dict["vendor.build.prop"] = copy.deepcopy(self.TEST_SOURCE_VENDOR_INFO_DICT)
+ self._test_GetPackageMetadata_swapVendorBuildTimestamps(
+ target_info_dict, source_info_dict)
+
+ target_info = common.BuildInfo(target_info_dict, None)
+ source_info = common.BuildInfo(source_info_dict, None)
+ common.OPTIONS.incremental_source = ''
+ common.OPTIONS.downgrade = True
+ common.OPTIONS.wipe_user_data = True
+ common.OPTIONS.spl_downgrade = True
+ metadata = self.GetLegacyOtaMetadata(target_info, source_info)
+ # Reset spl_downgrade so other tests are unaffected
+ common.OPTIONS.spl_downgrade = False
+
+ self.assertDictEqual(
+ {
+ 'ota-downgrade': 'yes',
+ 'ota-type': 'AB',
+ 'ota-required-cache': '0',
+ 'ota-wipe': 'yes',
+ 'post-build': 'build-fingerprint-target',
+ 'post-build-incremental': 'build-version-incremental-target',
+ 'post-sdk-level': '27',
+ 'post-security-patch-level': '2017-12-01',
+ 'post-timestamp': '1500000000',
+ 'pre-device': 'product-device',
+ 'pre-build': 'build-fingerprint-source',
+ 'pre-build-incremental': 'build-version-incremental-source',
+ 'spl-downgrade': 'yes',
+ },
+ metadata)
+
+ post_build = GetPackageMetadata(target_info, source_info).postcondition
+ self.assertEqual('vendor', post_build.partition_state[0].partition_name)
+ self.assertEqual('12345678', post_build.partition_state[0].version)
+
+ pre_build = GetPackageMetadata(target_info, source_info).precondition
+ self.assertEqual('vendor', pre_build.partition_state[0].partition_name)
+ self.assertEqual('87654321', pre_build.partition_state[0].version)
+
+
@test_utils.SkipIfExternalToolsUnavailable()
def test_GetTargetFilesZipForSecondaryImages(self):
input_file = construct_target_files(secondary=True)