Loading tools/aconfig/aconfig/Android.bp +3 −0 Original line number Diff line number Diff line Loading @@ -45,6 +45,9 @@ rust_test_host { "libitertools", ], test_suites: ["general-tests"], data: [ "tests/mainline_beta_namespaces.json" ] } // integration tests: general Loading tools/aconfig/aconfig/config/mainline_beta_namespaces_apr_25.json 0 → 100644 +24 −0 Original line number Diff line number Diff line { "namespaces": { "com_android_tethering": { "container": "com.android.tethering", "allow_exported": true }, "com_android_networkstack": { "container": "com.android.networkstack", "allow_exported": false }, "com_android_captiveportallogin": { "container": "com.android.captiveportallogin", "allow_exported": false }, "com_android_healthfitness": { "container": "com.android.healthfitness", "allow_exported": true }, "com_android_mediaprovider": { "container": "com.android.mediaprovider", "allow_exported": true } } } tools/aconfig/aconfig/src/cli_parser.rs +47 −3 Original line number Diff line number Diff line Loading @@ -91,6 +91,24 @@ Allow the same flag to be present in multiple cache files; if duplicates are fou a single instance. "#; const MAINLINE_BETA_NAMESPACE_CONFIG: &str = r#" A json file to configure mainline beta namespaces. This option is internal to Google. The json configuration should assume the following format: { "namespaces": { "com_android_tethering": { "container": "com.android.tethering", "allow_exported": true }, "com_android_mediaprovider": { "container": "com.android.mediaprovider", "allow_exported": true } } } "#; /// Conventional prefix to mark response file pub const RESPONSE_FILE_PREFIX: char = '@'; Loading @@ -111,6 +129,7 @@ pub enum ParsedCommand { default_permission: aconfig_protos::ProtoFlagPermission, allow_read_write: bool, cache_out_path: String, mainline_beta_namespace_config: Option<PathBuf>, }, CreateJavaLib { cache_path: String, Loading Loading @@ -168,8 +187,11 @@ fn build_cli() -> Command { .value_parser(clap::value_parser!(bool)) .default_value("true"), ) .arg(Arg::new("cache").long("cache").required(true).help("Output cache file path.")) .arg( Arg::new("cache").long("cache").required(true).help("Output cache file path."), Arg::new("mainline-beta-namespace-config") .long("mainline-beta-namespace-config") .long_help(MAINLINE_BETA_NAMESPACE_CONFIG.trim()), ), ) .subcommand( Loading Loading @@ -339,6 +361,17 @@ pub fn parse_args( Some(("create-cache", sub_matches)) => { let declarations = get_zero_or_more_string_paths_from_arg(sub_matches, "declarations"); let values = get_zero_or_more_string_paths_from_arg(sub_matches, "values"); let mainline_beta_namespace_config = match sub_matches.get_one::<String>("mainline-beta-namespace-config") { Some(config) => { if config.is_empty() { None } else { Some(PathBuf::from(config)) } } None => None, }; Ok(ParsedCommand::CreateCache { package: get_required_arg::<String>(sub_matches, "package")?.clone(), container: get_required_arg::<String>(sub_matches, "container")?.clone(), Loading @@ -350,6 +383,7 @@ pub fn parse_args( )?, allow_read_write: *get_required_arg::<bool>(sub_matches, "allow-read-write")?, cache_out_path: get_required_arg::<String>(sub_matches, "cache")?.clone(), mainline_beta_namespace_config, }) } Some(("create-java-lib", sub_matches)) => Ok(ParsedCommand::CreateJavaLib { Loading Loading @@ -452,7 +486,8 @@ mod tests { --values flag.val \ --cache /output/cache.pb \ --default-permission READ_WRITE \ --allow-read-write true"; --allow-read-write true \ --mainline-beta-namespace-config /path/to/some/file.json"; let input_args = create_os_command(command_string); let parsed = parse_args(input_args)?; Loading @@ -465,6 +500,7 @@ mod tests { default_permission, allow_read_write, cache_out_path, mainline_beta_namespace_config, } = parsed { assert_eq!(package, "com.test.cache"); Loading @@ -476,6 +512,10 @@ mod tests { assert_eq!(default_permission, aconfig_protos::ProtoFlagPermission::READ_WRITE); assert!(allow_read_write); assert_eq!(cache_out_path, "/output/cache.pb"); assert_eq!( mainline_beta_namespace_config, Some(PathBuf::from("/path/to/some/file.json")) ); } Ok(()) } Loading Loading @@ -661,6 +701,7 @@ mod tests { cache_out_path, default_permission, allow_read_write, mainline_beta_namespace_config, } = parsed { assert_eq!(package, "com.via.respfile"); Loading @@ -672,6 +713,7 @@ mod tests { assert_eq!(default_permission, aconfig_protos::ProtoFlagPermission::READ_WRITE); assert!(allow_read_write); assert_eq!(cache_out_path, "cache.pb"); assert_eq!(mainline_beta_namespace_config, None); } Ok(()) Loading Loading @@ -705,6 +747,7 @@ mod tests { cache_out_path, default_permission, allow_read_write, mainline_beta_namespace_config, } = parsed { assert_eq!(package, "com.via.respfile"); Loading @@ -716,6 +759,7 @@ mod tests { assert_eq!(default_permission, aconfig_protos::ProtoFlagPermission::READ_WRITE); assert!(allow_read_write); assert_eq!(cache_out_path, "cache.pb"); assert_eq!(mainline_beta_namespace_config, None); } Ok(()) Loading tools/aconfig/aconfig/src/commands.rs +182 −72 Original line number Diff line number Diff line Loading @@ -17,8 +17,9 @@ use anyhow::{anyhow, bail, ensure, Context, Result}; use convert_finalized_flags::FinalizedFlagMap; use itertools::Itertools; use lazy_static::lazy_static; use protobuf::Message; use serde::Deserialize; use serde::Serialize; use std::collections::HashMap; use std::fmt; use std::hash::Hasher; Loading Loading @@ -73,18 +74,50 @@ pub struct OutputFile { pub const DEFAULT_FLAG_STATE: ProtoFlagState = ProtoFlagState::DISABLED; pub const DEFAULT_FLAG_PERMISSION: ProtoFlagPermission = ProtoFlagPermission::READ_WRITE; // A hash map from beta namespace name to the corresponding containers. This is used to // determine if an RW flag is a mainline beta flag which is defined as: // 1, defined inside a beta namespace // 2, has the corresponding mainline module as container // TODO(b/328657418): Populate the map with mainline beta namespaces lazy_static! { static ref MAINLINE_BETA_NAMESPACES: HashMap<&'static str, &'static str> = vec![].into_iter().collect(); #[derive(Serialize, Deserialize, Debug)] pub struct NamespaceSetting { pub container: String, pub allow_exported: bool, } fn assign_storage_backend(pf: &mut ProtoParsedFlag) -> Result<()> { let is_mainline_beta = MAINLINE_BETA_NAMESPACES.get(pf.namespace()) == Some(&pf.container()); #[derive(Serialize, Deserialize, Debug)] pub struct MainlineBetaNamespaces { pub namespaces: HashMap<String, NamespaceSetting>, } #[allow(dead_code)] impl MainlineBetaNamespaces { fn is_mainline_beta_flag(&self, pf: &ProtoParsedFlag) -> bool { match self.namespaces.get(pf.namespace()) { Some(setting) => setting.container == pf.container(), None => false, } } fn supports_exported_mode(&self, pf: &ProtoParsedFlag) -> bool { match self.namespaces.get(pf.namespace()) { Some(setting) => { if setting.container == pf.container() { setting.allow_exported } else { panic!("Should not be called on none mainline beta flag") } } None => { panic!("Should not be called on none mainline beta flag") } } } } fn assign_storage_backend( pf: &mut ProtoParsedFlag, beta_namespaces: &Option<MainlineBetaNamespaces>, ) -> Result<()> { let is_mainline_beta = match beta_namespaces { Some(namespaces) => namespaces.is_mainline_beta_flag(pf), None => false, }; let is_read_only = pf.permission() == ProtoFlagPermission::READ_ONLY; let storage = if is_read_only { ProtoFlagStorageBackend::NONE Loading @@ -105,9 +138,18 @@ pub fn parse_flags( values: Vec<Input>, default_permission: ProtoFlagPermission, allow_read_write: bool, mainline_beta_namespace_config: Option<PathBuf>, ) -> Result<Vec<u8>> { let mut parsed_flags = ProtoParsedFlags::new(); let beta_namespaces: Option<MainlineBetaNamespaces> = match mainline_beta_namespace_config { Some(file) => { let contents = std::fs::read_to_string(file)?; Some(serde_json::from_str(&contents)?) } None => None, }; for mut input in declarations { let mut contents = String::new(); input Loading @@ -131,6 +173,7 @@ pub fn parse_flags( container, flag_declarations.container() ); for mut flag_declaration in flag_declarations.flag.into_iter() { aconfig_protos::flag_declaration::verify_fields(&flag_declaration) .with_context(|| input.error_context())?; Loading Loading @@ -162,7 +205,7 @@ pub fn parse_flags( let purpose = flag_declaration.metadata.purpose(); metadata.set_purpose(purpose); parsed_flag.metadata = Some(metadata).into(); assign_storage_backend(&mut parsed_flag)?; assign_storage_backend(&mut parsed_flag, &beta_namespaces)?; // verify ParsedFlag looks reasonable aconfig_protos::parsed_flag::verify_fields(&parsed_flag)?; Loading Loading @@ -211,7 +254,7 @@ pub fn parse_flags( parsed_flag.set_state(flag_value.state()); if parsed_flag.permission() != flag_value.permission() { parsed_flag.set_permission(flag_value.permission()); assign_storage_backend(parsed_flag)?; assign_storage_backend(parsed_flag, &beta_namespaces)?; } let mut tracepoint = ProtoTracepoint::new(); tracepoint.set_source(input.source.clone()); Loading Loading @@ -590,6 +633,7 @@ mod tests { value, ProtoFlagPermission::READ_ONLY, true, None, ) .unwrap(); let parsed_flags = Loading Loading @@ -624,6 +668,7 @@ mod tests { value, ProtoFlagPermission::READ_WRITE, true, None, ) .unwrap_err(); assert_eq!( Loading Loading @@ -656,6 +701,7 @@ mod tests { value, ProtoFlagPermission::READ_WRITE, true, None, ) .unwrap_err(); assert_eq!( Loading Loading @@ -685,6 +731,7 @@ mod tests { vec![], ProtoFlagPermission::READ_WRITE, false, None, ) .unwrap_err(); assert_eq!( Loading Loading @@ -727,6 +774,7 @@ mod tests { value, ProtoFlagPermission::READ_ONLY, false, None, ) .unwrap_err(); assert_eq!( Loading Loading @@ -769,6 +817,7 @@ mod tests { value, ProtoFlagPermission::READ_ONLY, false, None, ) .unwrap(); let parsed_flags = Loading Loading @@ -814,6 +863,7 @@ mod tests { value, ProtoFlagPermission::READ_WRITE, true, None, ) .unwrap_err(); assert_eq!( Loading Loading @@ -850,6 +900,7 @@ mod tests { value, ProtoFlagPermission::READ_ONLY, true, None, ) .unwrap(); let parsed_flags = Loading @@ -859,6 +910,43 @@ mod tests { assert_eq!(ProtoFlagPurpose::PURPOSE_FEATURE, parsed_flag.metadata.purpose()); } fn get_parsed_flag_proto( container: &'static str, package: &'static str, decl: &'static str, val: Option<&'static str>, config: Option<PathBuf>, ) -> ProtoParsedFlag { let declaration = vec![Input { source: "memory".to_string(), reader: Box::new(decl.as_bytes()) }]; let value: Vec<Input> = match val { Some(val_str) => { vec![Input { source: "memory".to_string(), reader: Box::new(val_str.as_bytes()) }] } None => { vec![] } }; let flags_bytes = crate::commands::parse_flags( package, container, declaration, value, ProtoFlagPermission::READ_WRITE, true, config, ) .unwrap(); let parsed_flags = aconfig_protos::parsed_flags::try_from_binary_proto(&flags_bytes).unwrap(); assert_eq!(1, parsed_flags.parsed_flag.len()); parsed_flags.parsed_flag.first().unwrap().clone() } #[test] fn test_parse_flags_metadata_storage() { let metadata_flag = r#" Loading @@ -872,33 +960,14 @@ mod tests { } "#; // Case 1, regular RW flag without value file override let declaration = vec![Input { source: "memory".to_string(), reader: Box::new(metadata_flag.as_bytes()), }]; let value: Vec<Input> = vec![]; let config = Some(PathBuf::from("tests/mainline_beta_namespaces.json")); let flags_bytes = crate::commands::parse_flags( "com.first", "test", declaration, value, ProtoFlagPermission::READ_WRITE, true, ) .unwrap(); let parsed_flags = aconfig_protos::parsed_flags::try_from_binary_proto(&flags_bytes).unwrap(); assert_eq!(1, parsed_flags.parsed_flag.len()); let parsed_flag = parsed_flags.parsed_flag.first().unwrap(); // Case 1, regular RW flag without value file override let parsed_flag = get_parsed_flag_proto("test", "com.first", metadata_flag, None, config.clone()); assert_eq!(ProtoFlagStorageBackend::ACONFIGD, parsed_flag.metadata.storage()); // Case 2, regular RW flag with value file override to RO let declaration = vec![Input { source: "memory".to_string(), reader: Box::new(metadata_flag.as_bytes()), }]; let first_flag_value = r#" flag_value { package: "com.first" Loading @@ -907,23 +976,13 @@ mod tests { permission: READ_ONLY } "#; let value = vec![Input { source: "memory".to_string(), reader: Box::new(first_flag_value.as_bytes()), }]; let flags_bytes = crate::commands::parse_flags( "com.first", let parsed_flag = get_parsed_flag_proto( "test", declaration, value, ProtoFlagPermission::READ_WRITE, true, ) .unwrap(); let parsed_flags = aconfig_protos::parsed_flags::try_from_binary_proto(&flags_bytes).unwrap(); assert_eq!(1, parsed_flags.parsed_flag.len()); let parsed_flag = parsed_flags.parsed_flag.first().unwrap(); "com.first", metadata_flag, Some(first_flag_value), config.clone(), ); assert_eq!(ProtoFlagStorageBackend::NONE, parsed_flag.metadata.storage()); // Case 3, fixed read only flag Loading @@ -938,30 +997,81 @@ mod tests { is_fixed_read_only: true } "#; let declaration = vec![Input { source: "memory".to_string(), reader: Box::new(metadata_flag.as_bytes()), }]; let value: Vec<Input> = vec![]; let flags_bytes = crate::commands::parse_flags( let parsed_flag = get_parsed_flag_proto("test", "com.first", metadata_flag, None, config.clone()); assert_eq!(ProtoFlagStorageBackend::NONE, parsed_flag.metadata.storage()); // Case 4, mainline beta namespace fixed read only flag let metadata_flag = r#" package: "com.first" container: "com.android.tethering" flag { name: "first" namespace: "com_android_tethering" description: "This is the description of this feature flag." bug: "123" is_fixed_read_only: true } "#; let parsed_flag = get_parsed_flag_proto( "com.android.tethering", "com.first", "test", declaration, value, ProtoFlagPermission::READ_WRITE, true, ) .unwrap(); let parsed_flags = aconfig_protos::parsed_flags::try_from_binary_proto(&flags_bytes).unwrap(); assert_eq!(1, parsed_flags.parsed_flag.len()); let parsed_flag = parsed_flags.parsed_flag.first().unwrap(); metadata_flag, None, config.clone(), ); assert_eq!(ProtoFlagStorageBackend::NONE, parsed_flag.metadata.storage()); // TODO case 4, mainline beta namespace fixed read only flag // TODO case 5, mainline beta namespace platform flag // TODO case 6, mainline beta namespace mainline flag // Case 5, mainline beta namespace platform flag let metadata_flag = r#" package: "com.first" container: "system" flag { name: "first" namespace: "com_android_tethering" description: "This is the description of this feature flag." bug: "123" } "#; let parsed_flag = get_parsed_flag_proto("system", "com.first", metadata_flag, None, config.clone()); assert_eq!(ProtoFlagStorageBackend::ACONFIGD, parsed_flag.metadata.storage()); // Case 6, mainline beta namespace mainline flag let metadata_flag = r#" package: "com.first" container: "com.android.tethering" flag { name: "first" namespace: "com_android_tethering" description: "This is the description of this feature flag." bug: "123" } "#; let parsed_flag = get_parsed_flag_proto( "com.android.tethering", "com.first", metadata_flag, None, config.clone(), ); assert_eq!(ProtoFlagStorageBackend::DEVICE_CONFIG, parsed_flag.metadata.storage()); // Case 7, mainline beta namespace mainline flag but without config let metadata_flag = r#" package: "com.first" container: "com.android.tethering" flag { name: "first" namespace: "com_android_tethering" description: "This is the description of this feature flag." bug: "123" } "#; let parsed_flag = get_parsed_flag_proto("com.android.tethering", "com.first", metadata_flag, None, None); assert_eq!(ProtoFlagStorageBackend::ACONFIGD, parsed_flag.metadata.storage()); } #[test] Loading tools/aconfig/aconfig/src/main.rs +2 −0 Original line number Diff line number Diff line Loading @@ -113,6 +113,7 @@ fn main() -> Result<()> { default_permission, allow_read_write, cache_out_path, mainline_beta_namespace_config, } => { let output = commands::parse_flags( &package, Loading @@ -121,6 +122,7 @@ fn main() -> Result<()> { open_zero_or_more_files(&values)?, // values default_permission, allow_read_write, mainline_beta_namespace_config, ) .context("failed to create cache")?; write_output_to_file_or_stdout(&cache_out_path, &output)?; Loading Loading
tools/aconfig/aconfig/Android.bp +3 −0 Original line number Diff line number Diff line Loading @@ -45,6 +45,9 @@ rust_test_host { "libitertools", ], test_suites: ["general-tests"], data: [ "tests/mainline_beta_namespaces.json" ] } // integration tests: general Loading
tools/aconfig/aconfig/config/mainline_beta_namespaces_apr_25.json 0 → 100644 +24 −0 Original line number Diff line number Diff line { "namespaces": { "com_android_tethering": { "container": "com.android.tethering", "allow_exported": true }, "com_android_networkstack": { "container": "com.android.networkstack", "allow_exported": false }, "com_android_captiveportallogin": { "container": "com.android.captiveportallogin", "allow_exported": false }, "com_android_healthfitness": { "container": "com.android.healthfitness", "allow_exported": true }, "com_android_mediaprovider": { "container": "com.android.mediaprovider", "allow_exported": true } } }
tools/aconfig/aconfig/src/cli_parser.rs +47 −3 Original line number Diff line number Diff line Loading @@ -91,6 +91,24 @@ Allow the same flag to be present in multiple cache files; if duplicates are fou a single instance. "#; const MAINLINE_BETA_NAMESPACE_CONFIG: &str = r#" A json file to configure mainline beta namespaces. This option is internal to Google. The json configuration should assume the following format: { "namespaces": { "com_android_tethering": { "container": "com.android.tethering", "allow_exported": true }, "com_android_mediaprovider": { "container": "com.android.mediaprovider", "allow_exported": true } } } "#; /// Conventional prefix to mark response file pub const RESPONSE_FILE_PREFIX: char = '@'; Loading @@ -111,6 +129,7 @@ pub enum ParsedCommand { default_permission: aconfig_protos::ProtoFlagPermission, allow_read_write: bool, cache_out_path: String, mainline_beta_namespace_config: Option<PathBuf>, }, CreateJavaLib { cache_path: String, Loading Loading @@ -168,8 +187,11 @@ fn build_cli() -> Command { .value_parser(clap::value_parser!(bool)) .default_value("true"), ) .arg(Arg::new("cache").long("cache").required(true).help("Output cache file path.")) .arg( Arg::new("cache").long("cache").required(true).help("Output cache file path."), Arg::new("mainline-beta-namespace-config") .long("mainline-beta-namespace-config") .long_help(MAINLINE_BETA_NAMESPACE_CONFIG.trim()), ), ) .subcommand( Loading Loading @@ -339,6 +361,17 @@ pub fn parse_args( Some(("create-cache", sub_matches)) => { let declarations = get_zero_or_more_string_paths_from_arg(sub_matches, "declarations"); let values = get_zero_or_more_string_paths_from_arg(sub_matches, "values"); let mainline_beta_namespace_config = match sub_matches.get_one::<String>("mainline-beta-namespace-config") { Some(config) => { if config.is_empty() { None } else { Some(PathBuf::from(config)) } } None => None, }; Ok(ParsedCommand::CreateCache { package: get_required_arg::<String>(sub_matches, "package")?.clone(), container: get_required_arg::<String>(sub_matches, "container")?.clone(), Loading @@ -350,6 +383,7 @@ pub fn parse_args( )?, allow_read_write: *get_required_arg::<bool>(sub_matches, "allow-read-write")?, cache_out_path: get_required_arg::<String>(sub_matches, "cache")?.clone(), mainline_beta_namespace_config, }) } Some(("create-java-lib", sub_matches)) => Ok(ParsedCommand::CreateJavaLib { Loading Loading @@ -452,7 +486,8 @@ mod tests { --values flag.val \ --cache /output/cache.pb \ --default-permission READ_WRITE \ --allow-read-write true"; --allow-read-write true \ --mainline-beta-namespace-config /path/to/some/file.json"; let input_args = create_os_command(command_string); let parsed = parse_args(input_args)?; Loading @@ -465,6 +500,7 @@ mod tests { default_permission, allow_read_write, cache_out_path, mainline_beta_namespace_config, } = parsed { assert_eq!(package, "com.test.cache"); Loading @@ -476,6 +512,10 @@ mod tests { assert_eq!(default_permission, aconfig_protos::ProtoFlagPermission::READ_WRITE); assert!(allow_read_write); assert_eq!(cache_out_path, "/output/cache.pb"); assert_eq!( mainline_beta_namespace_config, Some(PathBuf::from("/path/to/some/file.json")) ); } Ok(()) } Loading Loading @@ -661,6 +701,7 @@ mod tests { cache_out_path, default_permission, allow_read_write, mainline_beta_namespace_config, } = parsed { assert_eq!(package, "com.via.respfile"); Loading @@ -672,6 +713,7 @@ mod tests { assert_eq!(default_permission, aconfig_protos::ProtoFlagPermission::READ_WRITE); assert!(allow_read_write); assert_eq!(cache_out_path, "cache.pb"); assert_eq!(mainline_beta_namespace_config, None); } Ok(()) Loading Loading @@ -705,6 +747,7 @@ mod tests { cache_out_path, default_permission, allow_read_write, mainline_beta_namespace_config, } = parsed { assert_eq!(package, "com.via.respfile"); Loading @@ -716,6 +759,7 @@ mod tests { assert_eq!(default_permission, aconfig_protos::ProtoFlagPermission::READ_WRITE); assert!(allow_read_write); assert_eq!(cache_out_path, "cache.pb"); assert_eq!(mainline_beta_namespace_config, None); } Ok(()) Loading
tools/aconfig/aconfig/src/commands.rs +182 −72 Original line number Diff line number Diff line Loading @@ -17,8 +17,9 @@ use anyhow::{anyhow, bail, ensure, Context, Result}; use convert_finalized_flags::FinalizedFlagMap; use itertools::Itertools; use lazy_static::lazy_static; use protobuf::Message; use serde::Deserialize; use serde::Serialize; use std::collections::HashMap; use std::fmt; use std::hash::Hasher; Loading Loading @@ -73,18 +74,50 @@ pub struct OutputFile { pub const DEFAULT_FLAG_STATE: ProtoFlagState = ProtoFlagState::DISABLED; pub const DEFAULT_FLAG_PERMISSION: ProtoFlagPermission = ProtoFlagPermission::READ_WRITE; // A hash map from beta namespace name to the corresponding containers. This is used to // determine if an RW flag is a mainline beta flag which is defined as: // 1, defined inside a beta namespace // 2, has the corresponding mainline module as container // TODO(b/328657418): Populate the map with mainline beta namespaces lazy_static! { static ref MAINLINE_BETA_NAMESPACES: HashMap<&'static str, &'static str> = vec![].into_iter().collect(); #[derive(Serialize, Deserialize, Debug)] pub struct NamespaceSetting { pub container: String, pub allow_exported: bool, } fn assign_storage_backend(pf: &mut ProtoParsedFlag) -> Result<()> { let is_mainline_beta = MAINLINE_BETA_NAMESPACES.get(pf.namespace()) == Some(&pf.container()); #[derive(Serialize, Deserialize, Debug)] pub struct MainlineBetaNamespaces { pub namespaces: HashMap<String, NamespaceSetting>, } #[allow(dead_code)] impl MainlineBetaNamespaces { fn is_mainline_beta_flag(&self, pf: &ProtoParsedFlag) -> bool { match self.namespaces.get(pf.namespace()) { Some(setting) => setting.container == pf.container(), None => false, } } fn supports_exported_mode(&self, pf: &ProtoParsedFlag) -> bool { match self.namespaces.get(pf.namespace()) { Some(setting) => { if setting.container == pf.container() { setting.allow_exported } else { panic!("Should not be called on none mainline beta flag") } } None => { panic!("Should not be called on none mainline beta flag") } } } } fn assign_storage_backend( pf: &mut ProtoParsedFlag, beta_namespaces: &Option<MainlineBetaNamespaces>, ) -> Result<()> { let is_mainline_beta = match beta_namespaces { Some(namespaces) => namespaces.is_mainline_beta_flag(pf), None => false, }; let is_read_only = pf.permission() == ProtoFlagPermission::READ_ONLY; let storage = if is_read_only { ProtoFlagStorageBackend::NONE Loading @@ -105,9 +138,18 @@ pub fn parse_flags( values: Vec<Input>, default_permission: ProtoFlagPermission, allow_read_write: bool, mainline_beta_namespace_config: Option<PathBuf>, ) -> Result<Vec<u8>> { let mut parsed_flags = ProtoParsedFlags::new(); let beta_namespaces: Option<MainlineBetaNamespaces> = match mainline_beta_namespace_config { Some(file) => { let contents = std::fs::read_to_string(file)?; Some(serde_json::from_str(&contents)?) } None => None, }; for mut input in declarations { let mut contents = String::new(); input Loading @@ -131,6 +173,7 @@ pub fn parse_flags( container, flag_declarations.container() ); for mut flag_declaration in flag_declarations.flag.into_iter() { aconfig_protos::flag_declaration::verify_fields(&flag_declaration) .with_context(|| input.error_context())?; Loading Loading @@ -162,7 +205,7 @@ pub fn parse_flags( let purpose = flag_declaration.metadata.purpose(); metadata.set_purpose(purpose); parsed_flag.metadata = Some(metadata).into(); assign_storage_backend(&mut parsed_flag)?; assign_storage_backend(&mut parsed_flag, &beta_namespaces)?; // verify ParsedFlag looks reasonable aconfig_protos::parsed_flag::verify_fields(&parsed_flag)?; Loading Loading @@ -211,7 +254,7 @@ pub fn parse_flags( parsed_flag.set_state(flag_value.state()); if parsed_flag.permission() != flag_value.permission() { parsed_flag.set_permission(flag_value.permission()); assign_storage_backend(parsed_flag)?; assign_storage_backend(parsed_flag, &beta_namespaces)?; } let mut tracepoint = ProtoTracepoint::new(); tracepoint.set_source(input.source.clone()); Loading Loading @@ -590,6 +633,7 @@ mod tests { value, ProtoFlagPermission::READ_ONLY, true, None, ) .unwrap(); let parsed_flags = Loading Loading @@ -624,6 +668,7 @@ mod tests { value, ProtoFlagPermission::READ_WRITE, true, None, ) .unwrap_err(); assert_eq!( Loading Loading @@ -656,6 +701,7 @@ mod tests { value, ProtoFlagPermission::READ_WRITE, true, None, ) .unwrap_err(); assert_eq!( Loading Loading @@ -685,6 +731,7 @@ mod tests { vec![], ProtoFlagPermission::READ_WRITE, false, None, ) .unwrap_err(); assert_eq!( Loading Loading @@ -727,6 +774,7 @@ mod tests { value, ProtoFlagPermission::READ_ONLY, false, None, ) .unwrap_err(); assert_eq!( Loading Loading @@ -769,6 +817,7 @@ mod tests { value, ProtoFlagPermission::READ_ONLY, false, None, ) .unwrap(); let parsed_flags = Loading Loading @@ -814,6 +863,7 @@ mod tests { value, ProtoFlagPermission::READ_WRITE, true, None, ) .unwrap_err(); assert_eq!( Loading Loading @@ -850,6 +900,7 @@ mod tests { value, ProtoFlagPermission::READ_ONLY, true, None, ) .unwrap(); let parsed_flags = Loading @@ -859,6 +910,43 @@ mod tests { assert_eq!(ProtoFlagPurpose::PURPOSE_FEATURE, parsed_flag.metadata.purpose()); } fn get_parsed_flag_proto( container: &'static str, package: &'static str, decl: &'static str, val: Option<&'static str>, config: Option<PathBuf>, ) -> ProtoParsedFlag { let declaration = vec![Input { source: "memory".to_string(), reader: Box::new(decl.as_bytes()) }]; let value: Vec<Input> = match val { Some(val_str) => { vec![Input { source: "memory".to_string(), reader: Box::new(val_str.as_bytes()) }] } None => { vec![] } }; let flags_bytes = crate::commands::parse_flags( package, container, declaration, value, ProtoFlagPermission::READ_WRITE, true, config, ) .unwrap(); let parsed_flags = aconfig_protos::parsed_flags::try_from_binary_proto(&flags_bytes).unwrap(); assert_eq!(1, parsed_flags.parsed_flag.len()); parsed_flags.parsed_flag.first().unwrap().clone() } #[test] fn test_parse_flags_metadata_storage() { let metadata_flag = r#" Loading @@ -872,33 +960,14 @@ mod tests { } "#; // Case 1, regular RW flag without value file override let declaration = vec![Input { source: "memory".to_string(), reader: Box::new(metadata_flag.as_bytes()), }]; let value: Vec<Input> = vec![]; let config = Some(PathBuf::from("tests/mainline_beta_namespaces.json")); let flags_bytes = crate::commands::parse_flags( "com.first", "test", declaration, value, ProtoFlagPermission::READ_WRITE, true, ) .unwrap(); let parsed_flags = aconfig_protos::parsed_flags::try_from_binary_proto(&flags_bytes).unwrap(); assert_eq!(1, parsed_flags.parsed_flag.len()); let parsed_flag = parsed_flags.parsed_flag.first().unwrap(); // Case 1, regular RW flag without value file override let parsed_flag = get_parsed_flag_proto("test", "com.first", metadata_flag, None, config.clone()); assert_eq!(ProtoFlagStorageBackend::ACONFIGD, parsed_flag.metadata.storage()); // Case 2, regular RW flag with value file override to RO let declaration = vec![Input { source: "memory".to_string(), reader: Box::new(metadata_flag.as_bytes()), }]; let first_flag_value = r#" flag_value { package: "com.first" Loading @@ -907,23 +976,13 @@ mod tests { permission: READ_ONLY } "#; let value = vec![Input { source: "memory".to_string(), reader: Box::new(first_flag_value.as_bytes()), }]; let flags_bytes = crate::commands::parse_flags( "com.first", let parsed_flag = get_parsed_flag_proto( "test", declaration, value, ProtoFlagPermission::READ_WRITE, true, ) .unwrap(); let parsed_flags = aconfig_protos::parsed_flags::try_from_binary_proto(&flags_bytes).unwrap(); assert_eq!(1, parsed_flags.parsed_flag.len()); let parsed_flag = parsed_flags.parsed_flag.first().unwrap(); "com.first", metadata_flag, Some(first_flag_value), config.clone(), ); assert_eq!(ProtoFlagStorageBackend::NONE, parsed_flag.metadata.storage()); // Case 3, fixed read only flag Loading @@ -938,30 +997,81 @@ mod tests { is_fixed_read_only: true } "#; let declaration = vec![Input { source: "memory".to_string(), reader: Box::new(metadata_flag.as_bytes()), }]; let value: Vec<Input> = vec![]; let flags_bytes = crate::commands::parse_flags( let parsed_flag = get_parsed_flag_proto("test", "com.first", metadata_flag, None, config.clone()); assert_eq!(ProtoFlagStorageBackend::NONE, parsed_flag.metadata.storage()); // Case 4, mainline beta namespace fixed read only flag let metadata_flag = r#" package: "com.first" container: "com.android.tethering" flag { name: "first" namespace: "com_android_tethering" description: "This is the description of this feature flag." bug: "123" is_fixed_read_only: true } "#; let parsed_flag = get_parsed_flag_proto( "com.android.tethering", "com.first", "test", declaration, value, ProtoFlagPermission::READ_WRITE, true, ) .unwrap(); let parsed_flags = aconfig_protos::parsed_flags::try_from_binary_proto(&flags_bytes).unwrap(); assert_eq!(1, parsed_flags.parsed_flag.len()); let parsed_flag = parsed_flags.parsed_flag.first().unwrap(); metadata_flag, None, config.clone(), ); assert_eq!(ProtoFlagStorageBackend::NONE, parsed_flag.metadata.storage()); // TODO case 4, mainline beta namespace fixed read only flag // TODO case 5, mainline beta namespace platform flag // TODO case 6, mainline beta namespace mainline flag // Case 5, mainline beta namespace platform flag let metadata_flag = r#" package: "com.first" container: "system" flag { name: "first" namespace: "com_android_tethering" description: "This is the description of this feature flag." bug: "123" } "#; let parsed_flag = get_parsed_flag_proto("system", "com.first", metadata_flag, None, config.clone()); assert_eq!(ProtoFlagStorageBackend::ACONFIGD, parsed_flag.metadata.storage()); // Case 6, mainline beta namespace mainline flag let metadata_flag = r#" package: "com.first" container: "com.android.tethering" flag { name: "first" namespace: "com_android_tethering" description: "This is the description of this feature flag." bug: "123" } "#; let parsed_flag = get_parsed_flag_proto( "com.android.tethering", "com.first", metadata_flag, None, config.clone(), ); assert_eq!(ProtoFlagStorageBackend::DEVICE_CONFIG, parsed_flag.metadata.storage()); // Case 7, mainline beta namespace mainline flag but without config let metadata_flag = r#" package: "com.first" container: "com.android.tethering" flag { name: "first" namespace: "com_android_tethering" description: "This is the description of this feature flag." bug: "123" } "#; let parsed_flag = get_parsed_flag_proto("com.android.tethering", "com.first", metadata_flag, None, None); assert_eq!(ProtoFlagStorageBackend::ACONFIGD, parsed_flag.metadata.storage()); } #[test] Loading
tools/aconfig/aconfig/src/main.rs +2 −0 Original line number Diff line number Diff line Loading @@ -113,6 +113,7 @@ fn main() -> Result<()> { default_permission, allow_read_write, cache_out_path, mainline_beta_namespace_config, } => { let output = commands::parse_flags( &package, Loading @@ -121,6 +122,7 @@ fn main() -> Result<()> { open_zero_or_more_files(&values)?, // values default_permission, allow_read_write, mainline_beta_namespace_config, ) .context("failed to create cache")?; write_output_to_file_or_stdout(&cache_out_path, &output)?; Loading