Loading tools/aconfig/src/commands.rs +23 −0 Original line number Diff line number Diff line Loading @@ -23,6 +23,8 @@ 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::storage::generate_storage_files; use crate::protos::{ ParsedFlagExt, ProtoFlagMetadata, ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag, ProtoParsedFlags, ProtoTracepoint, Loading Loading @@ -217,6 +219,17 @@ pub fn create_rust_lib(mut input: Input, codegen_mode: CodegenMode) -> Result<Ou 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(); Loading Loading @@ -339,6 +352,16 @@ fn find_unique_package(parsed_flags: &ProtoParsedFlags) -> Option<&str> { 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::*; Loading tools/aconfig/src/main.rs +22 −0 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ use std::path::{Path, PathBuf}; mod codegen; mod commands; mod protos; mod storage; #[cfg(test)] mod test; Loading Loading @@ -108,6 +109,17 @@ fn cli() -> Command { .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> Loading Loading @@ -242,6 +254,16 @@ fn main() -> Result<()> { 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(()) Loading tools/aconfig/src/storage/mod.rs 0 → 100644 +166 −0 Original line number Diff line number Diff line /* * 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); } } tools/aconfig/tests/storage_test_1_part_1.aconfig 0 → 100644 +17 −0 Original line number Diff line number Diff line package: "com.android.aconfig.storage.test_1" container: "system" flag { name: "enabled_rw" namespace: "aconfig_test" description: "This flag is ENABLED + READ_WRITE" bug: "" } flag { name: "disabled_rw" namespace: "aconfig_test" description: "This flag is DISABLED + READ_WRITE" bug: "456" is_exported: true } tools/aconfig/tests/storage_test_1_part_2.aconfig 0 → 100644 +24 −0 Original line number Diff line number Diff line package: "com.android.aconfig.storage.test_1" container: "system" flag { name: "enabled_ro" namespace: "aconfig_test" description: "This flag is ENABLED + READ_ONLY" bug: "abc" } flag { name: "disabled_ro" namespace: "aconfig_test" description: "This flag is DISABLED + READ_ONLY" bug: "123" } flag { name: "enabled_fixed_ro" namespace: "aconfig_test" description: "This flag is fixed READ_ONLY + ENABLED" bug: "" is_fixed_read_only: true } Loading
tools/aconfig/src/commands.rs +23 −0 Original line number Diff line number Diff line Loading @@ -23,6 +23,8 @@ 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::storage::generate_storage_files; use crate::protos::{ ParsedFlagExt, ProtoFlagMetadata, ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag, ProtoParsedFlags, ProtoTracepoint, Loading Loading @@ -217,6 +219,17 @@ pub fn create_rust_lib(mut input: Input, codegen_mode: CodegenMode) -> Result<Ou 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(); Loading Loading @@ -339,6 +352,16 @@ fn find_unique_package(parsed_flags: &ProtoParsedFlags) -> Option<&str> { 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::*; Loading
tools/aconfig/src/main.rs +22 −0 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ use std::path::{Path, PathBuf}; mod codegen; mod commands; mod protos; mod storage; #[cfg(test)] mod test; Loading Loading @@ -108,6 +109,17 @@ fn cli() -> Command { .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> Loading Loading @@ -242,6 +254,16 @@ fn main() -> Result<()> { 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(()) Loading
tools/aconfig/src/storage/mod.rs 0 → 100644 +166 −0 Original line number Diff line number Diff line /* * 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); } }
tools/aconfig/tests/storage_test_1_part_1.aconfig 0 → 100644 +17 −0 Original line number Diff line number Diff line package: "com.android.aconfig.storage.test_1" container: "system" flag { name: "enabled_rw" namespace: "aconfig_test" description: "This flag is ENABLED + READ_WRITE" bug: "" } flag { name: "disabled_rw" namespace: "aconfig_test" description: "This flag is DISABLED + READ_WRITE" bug: "456" is_exported: true }
tools/aconfig/tests/storage_test_1_part_2.aconfig 0 → 100644 +24 −0 Original line number Diff line number Diff line package: "com.android.aconfig.storage.test_1" container: "system" flag { name: "enabled_ro" namespace: "aconfig_test" description: "This flag is ENABLED + READ_ONLY" bug: "abc" } flag { name: "disabled_ro" namespace: "aconfig_test" description: "This flag is DISABLED + READ_ONLY" bug: "123" } flag { name: "enabled_fixed_ro" namespace: "aconfig_test" description: "This flag is fixed READ_ONLY + ENABLED" bug: "" is_fixed_read_only: true }