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:
Dennis Shen
2023-12-13 00:14:27 +00:00
committed by Automerger Merge Worker
6 changed files with 276 additions and 0 deletions

View File

@@ -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::*;

View File

@@ -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(())

View 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);
}
}

View 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
}

View 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
}

View 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
}