aconfig: add create storage command
Add a new aconfig command called create-storage which takes a number
of aconfig cache files that belong to a specific container and produces
storage files.
Add a new module called storage (src/storage/mod.rs) as the entry point
of storage files generation. FlagPackage struct is defined as an
intermediate data structure that will be used to drive all storage files creation.
Add a unit test to lock down FlagPackage creation behaviors.
Bug: b/312243587
Test: atest aconfig.test
Change-Id: Ia7e9f68237ea903f295ac7891c923f6a39f3422d
diff --git a/tools/aconfig/src/commands.rs b/tools/aconfig/src/commands.rs
index be32bde..2a3fe27 100644
--- a/tools/aconfig/src/commands.rs
+++ b/tools/aconfig/src/commands.rs
@@ -23,6 +23,8 @@
use crate::codegen::cpp::generate_cpp_code;
use crate::codegen::java::generate_java_code;
use crate::codegen::rust::generate_rust_code;
+use crate::storage::generate_storage_files;
+
use crate::protos::{
ParsedFlagExt, ProtoFlagMetadata, ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag,
ProtoParsedFlags, ProtoTracepoint,
@@ -217,6 +219,17 @@
generate_rust_code(package, parsed_flags.parsed_flag.iter(), codegen_mode)
}
+pub fn create_storage(caches: Vec<Input>, container: &str) -> Result<Vec<OutputFile>> {
+ let parsed_flags_vec: Vec<ProtoParsedFlags> = caches
+ .into_iter()
+ .map(|mut input| input.try_parse_flags())
+ .collect::<Result<Vec<_>>>()?
+ .into_iter()
+ .filter(|pfs| find_unique_container(pfs) == Some(container))
+ .collect();
+ generate_storage_files(container, parsed_flags_vec.iter())
+}
+
pub fn create_device_config_defaults(mut input: Input) -> Result<Vec<u8>> {
let parsed_flags = input.try_parse_flags()?;
let mut output = Vec::new();
@@ -339,6 +352,16 @@
Some(package)
}
+fn find_unique_container(parsed_flags: &ProtoParsedFlags) -> Option<&str> {
+ let Some(container) = parsed_flags.parsed_flag.first().map(|pf| pf.container()) else {
+ return None;
+ };
+ if parsed_flags.parsed_flag.iter().any(|pf| pf.container() != container) {
+ return None;
+ }
+ Some(container)
+}
+
#[cfg(test)]
mod tests {
use super::*;
diff --git a/tools/aconfig/src/main.rs b/tools/aconfig/src/main.rs
index 6872809..63a50c8 100644
--- a/tools/aconfig/src/main.rs
+++ b/tools/aconfig/src/main.rs
@@ -27,6 +27,7 @@
mod codegen;
mod commands;
mod protos;
+mod storage;
#[cfg(test)]
mod test;
@@ -108,6 +109,17 @@
.arg(Arg::new("dedup").long("dedup").num_args(0).action(ArgAction::SetTrue))
.arg(Arg::new("out").long("out").default_value("-")),
)
+ .subcommand(
+ Command::new("create-storage")
+ .arg(
+ Arg::new("container")
+ .long("container")
+ .required(true)
+ .help("The target container for the generated storage file."),
+ )
+ .arg(Arg::new("cache").long("cache").required(true))
+ .arg(Arg::new("out").long("out").required(true)),
+ )
}
fn get_required_arg<'a, T>(matches: &'a ArgMatches, arg_name: &str) -> Result<&'a T>
@@ -242,6 +254,16 @@
let path = get_required_arg::<String>(sub_matches, "out")?;
write_output_to_file_or_stdout(path, &output)?;
}
+ Some(("create-storage", sub_matches)) => {
+ let cache = open_zero_or_more_files(sub_matches, "cache")?;
+ let container = get_required_arg::<String>(sub_matches, "container")?;
+ let dir = PathBuf::from(get_required_arg::<String>(sub_matches, "out")?);
+ let generated_files = commands::create_storage(cache, container)
+ .context("failed to create storage files")?;
+ generated_files
+ .iter()
+ .try_for_each(|file| write_output_file_realtive_to_dir(&dir, file))?;
+ }
_ => unreachable!(),
}
Ok(())
diff --git a/tools/aconfig/src/storage/mod.rs b/tools/aconfig/src/storage/mod.rs
new file mode 100644
index 0000000..90e05f5
--- /dev/null
+++ b/tools/aconfig/src/storage/mod.rs
@@ -0,0 +1,166 @@
+/*
+ * 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 anyhow::Result;
+use std::collections::{HashMap, HashSet};
+
+use crate::commands::OutputFile;
+use crate::protos::{ProtoParsedFlag, ProtoParsedFlags};
+
+pub struct FlagPackage<'a> {
+ pub package_name: &'a str,
+ pub package_id: u32,
+ pub flag_names: HashSet<&'a str>,
+ pub boolean_flags: Vec<&'a ProtoParsedFlag>,
+ pub boolean_offset: u32,
+}
+
+impl<'a> FlagPackage<'a> {
+ fn new(package_name: &'a str, package_id: u32) -> Self {
+ FlagPackage {
+ package_name,
+ package_id,
+ flag_names: HashSet::new(),
+ boolean_flags: vec![],
+ boolean_offset: 0,
+ }
+ }
+
+ fn insert(&mut self, pf: &'a ProtoParsedFlag) {
+ if self.flag_names.insert(pf.name()) {
+ self.boolean_flags.push(pf);
+ }
+ }
+}
+
+pub fn group_flags_by_package<'a, I>(parsed_flags_vec_iter: I) -> Vec<FlagPackage<'a>>
+where
+ I: Iterator<Item = &'a ProtoParsedFlags>,
+{
+ // group flags by package
+ let mut packages: Vec<FlagPackage<'a>> = Vec::new();
+ let mut package_index: HashMap<&'a 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()));
+ if index == packages.len() {
+ packages.push(FlagPackage::new(parsed_flag.package(), index as u32));
+ }
+ packages[index].insert(parsed_flag);
+ }
+ }
+
+ // calculate package flag value start offset, in flag value file, each boolean
+ // is stored as two bytes, the first byte will be the flag value. the second
+ // byte is flag info byte, which is a bitmask to indicate the status of a flag
+ let mut boolean_offset = 0;
+ for p in packages.iter_mut() {
+ p.boolean_offset = boolean_offset;
+ boolean_offset += 2 * p.boolean_flags.len() as u32;
+ }
+
+ packages
+}
+
+pub fn generate_storage_files<'a, I>(
+ _containser: &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![])
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::Input;
+
+ pub fn parse_all_test_flags() -> Vec<ProtoParsedFlags> {
+ let aconfig_files = [
+ (
+ "com.android.aconfig.storage.test_1",
+ "storage_test_1_part_1.aconfig",
+ include_bytes!("../../tests/storage_test_1_part_1.aconfig").as_slice(),
+ ),
+ (
+ "com.android.aconfig.storage.test_1",
+ "storage_test_1_part_2.aconfig",
+ include_bytes!("../../tests/storage_test_1_part_2.aconfig").as_slice(),
+ ),
+ (
+ "com.android.aconfig.storage.test_2",
+ "storage_test_2.aconfig",
+ include_bytes!("../../tests/storage_test_2.aconfig").as_slice(),
+ ),
+ ];
+
+ aconfig_files
+ .into_iter()
+ .map(|(pkg, file, content)| {
+ let bytes = crate::commands::parse_flags(
+ pkg,
+ Some("system"),
+ vec![Input {
+ source: format!("tests/{}", file).to_string(),
+ reader: Box::new(content),
+ }],
+ vec![],
+ crate::commands::DEFAULT_FLAG_PERMISSION,
+ )
+ .unwrap();
+ crate::protos::parsed_flags::try_from_binary_proto(&bytes).unwrap()
+ })
+ .collect()
+ }
+
+ #[test]
+ fn test_flag_package() {
+ let caches = parse_all_test_flags();
+ let packages = group_flags_by_package(caches.iter());
+
+ for pkg in packages.iter() {
+ let pkg_name = pkg.package_name;
+ assert_eq!(pkg.flag_names.len(), pkg.boolean_flags.len());
+ for pf in pkg.boolean_flags.iter() {
+ assert!(pkg.flag_names.contains(pf.name()));
+ assert_eq!(pf.package(), pkg_name);
+ }
+ }
+
+ assert_eq!(packages.len(), 2);
+
+ assert_eq!(packages[0].package_name, "com.android.aconfig.storage.test_1");
+ assert_eq!(packages[0].package_id, 0);
+ assert_eq!(packages[0].flag_names.len(), 5);
+ assert!(packages[0].flag_names.contains("enabled_rw"));
+ assert!(packages[0].flag_names.contains("disabled_rw"));
+ assert!(packages[0].flag_names.contains("enabled_ro"));
+ assert!(packages[0].flag_names.contains("disabled_ro"));
+ assert!(packages[0].flag_names.contains("enabled_fixed_ro"));
+ assert_eq!(packages[0].boolean_offset, 0);
+
+ assert_eq!(packages[1].package_name, "com.android.aconfig.storage.test_2");
+ assert_eq!(packages[1].package_id, 1);
+ assert_eq!(packages[1].flag_names.len(), 3);
+ assert!(packages[1].flag_names.contains("enabled_ro"));
+ assert!(packages[1].flag_names.contains("disabled_ro"));
+ assert!(packages[1].flag_names.contains("enabled_fixed_ro"));
+ assert_eq!(packages[1].boolean_offset, 10);
+ }
+}