Merge "aconfig: add create storage command" into main am: 728fe988d8
am: 0d4c6cafca
Original change: https://android-review.googlesource.com/c/platform/build/+/2855807 Change-Id: I61d224f38c10718e40402ab96815508c8da62de8 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -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,
|
||||
@@ -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();
|
||||
@@ -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::*;
|
||||
|
@@ -27,6 +27,7 @@ use std::path::{Path, PathBuf};
|
||||
mod codegen;
|
||||
mod commands;
|
||||
mod protos;
|
||||
mod storage;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
@@ -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>
|
||||
@@ -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(())
|
||||
|
166
tools/aconfig/src/storage/mod.rs
Normal file
166
tools/aconfig/src/storage/mod.rs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
17
tools/aconfig/tests/storage_test_1_part_1.aconfig
Normal file
17
tools/aconfig/tests/storage_test_1_part_1.aconfig
Normal file
@@ -0,0 +1,17 @@
|
||||
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
|
||||
}
|
24
tools/aconfig/tests/storage_test_1_part_2.aconfig
Normal file
24
tools/aconfig/tests/storage_test_1_part_2.aconfig
Normal file
@@ -0,0 +1,24 @@
|
||||
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
|
||||
}
|
24
tools/aconfig/tests/storage_test_2.aconfig
Normal file
24
tools/aconfig/tests/storage_test_2.aconfig
Normal file
@@ -0,0 +1,24 @@
|
||||
package: "com.android.aconfig.storage.test_2"
|
||||
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
|
||||
}
|
Reference in New Issue
Block a user