Loading tools/aconfig/src/codegen_java.rs +146 −44 Original line number Diff line number Diff line Loading @@ -20,17 +20,23 @@ use std::path::PathBuf; use tinytemplate::TinyTemplate; use crate::codegen; use crate::commands::OutputFile; use crate::commands::{CodegenMode, OutputFile}; use crate::protos::{ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag}; pub fn generate_java_code<'a, I>(package: &str, parsed_flags_iter: I) -> Result<Vec<OutputFile>> pub fn generate_java_code<'a, I>( package: &str, parsed_flags_iter: I, codegen_mode: CodegenMode, ) -> Result<Vec<OutputFile>> where I: Iterator<Item = &'a ProtoParsedFlag>, { let class_elements: Vec<ClassElement> = parsed_flags_iter.map(|pf| create_class_element(package, pf)).collect(); let is_read_write = class_elements.iter().any(|elem| elem.is_read_write); let context = Context { package_name: package.to_string(), is_read_write, class_elements }; let is_test_mode = codegen_mode == CodegenMode::Test; let context = Context { class_elements, is_test_mode, is_read_write, package_name: package.to_string() }; let mut template = TinyTemplate::new(); template.add_template("Flags.java", include_str!("../templates/Flags.java.template"))?; template.add_template( Loading @@ -56,14 +62,15 @@ where #[derive(Serialize)] struct Context { pub package_name: String, pub is_read_write: bool, pub class_elements: Vec<ClassElement>, pub is_test_mode: bool, pub is_read_write: bool, pub package_name: String, } #[derive(Serialize)] struct ClassElement { pub default_value: String, pub default_value: bool, pub device_config_namespace: String, pub device_config_flag: String, pub flag_name_constant_suffix: String, Loading @@ -75,11 +82,7 @@ fn create_class_element(package: &str, pf: &ProtoParsedFlag) -> ClassElement { let device_config_flag = codegen::create_device_config_ident(package, pf.name()) .expect("values checked at flag parse time"); ClassElement { default_value: if pf.state() == ProtoFlagState::ENABLED { "true".to_string() } else { "false".to_string() }, default_value: pf.state() == ProtoFlagState::ENABLED, device_config_namespace: pf.namespace().to_string(), device_config_flag, flag_name_constant_suffix: pf.name().to_ascii_uppercase(), Loading Loading @@ -109,12 +112,16 @@ mod tests { use super::*; use std::collections::HashMap; #[test] fn test_generate_java_code() { let parsed_flags = crate::test::parse_test_flags(); let generated_files = generate_java_code(crate::test::TEST_PACKAGE, parsed_flags.parsed_flag.iter()).unwrap(); let expect_flags_content = r#" const EXPECTED_FEATUREFLAGS_CONTENT: &str = r#" package com.android.aconfig.test; public interface FeatureFlags { boolean disabledRo(); boolean disabledRw(); boolean enabledRo(); boolean enabledRw(); }"#; const EXPECTED_FLAG_COMMON_CONTENT: &str = r#" package com.android.aconfig.test; public final class Flags { public static final String FLAG_DISABLED_RO = "com.android.aconfig.test.disabled_ro"; Loading @@ -134,9 +141,21 @@ mod tests { public static boolean enabledRw() { return FEATURE_FLAGS.enabledRw(); } private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl(); } "#; #[test] fn test_generate_java_code_production() { let parsed_flags = crate::test::parse_test_flags(); let generated_files = generate_java_code( crate::test::TEST_PACKAGE, parsed_flags.parsed_flag.iter(), CodegenMode::Production, ) .unwrap(); let expect_flags_content = EXPECTED_FLAG_COMMON_CONTENT.to_string() + r#" private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl(); }"#; let expected_featureflagsimpl_content = r#" package com.android.aconfig.test; import android.provider.DeviceConfig; Loading Loading @@ -167,19 +186,102 @@ mod tests { } } "#; let expected_featureflags_content = r#" let mut file_set = HashMap::from([ ("com/android/aconfig/test/Flags.java", expect_flags_content.as_str()), ("com/android/aconfig/test/FeatureFlagsImpl.java", expected_featureflagsimpl_content), ("com/android/aconfig/test/FeatureFlags.java", EXPECTED_FEATUREFLAGS_CONTENT), ]); for file in generated_files { let file_path = file.path.to_str().unwrap(); assert!(file_set.contains_key(file_path), "Cannot find {}", file_path); assert_eq!( None, crate::test::first_significant_code_diff( file_set.get(file_path).unwrap(), &String::from_utf8(file.contents.clone()).unwrap() ), "File {} content is not correct", file_path ); file_set.remove(file_path); } assert!(file_set.is_empty()); } #[test] fn test_generate_java_code_test() { let parsed_flags = crate::test::parse_test_flags(); let generated_files = generate_java_code( crate::test::TEST_PACKAGE, parsed_flags.parsed_flag.iter(), CodegenMode::Test, ) .unwrap(); let expect_flags_content = EXPECTED_FLAG_COMMON_CONTENT.to_string() + r#" public static void setFeatureFlagsImpl(FeatureFlags featureFlags) { Flags.FEATURE_FLAGS = featureFlags; } public static void unsetFeatureFlagsImpl() { Flags.FEATURE_FLAGS = null; } private static FeatureFlags FEATURE_FLAGS; } "#; let expected_featureflagsimpl_content = r#" package com.android.aconfig.test; public interface FeatureFlags { boolean disabledRo(); boolean disabledRw(); boolean enabledRo(); boolean enabledRw(); import static java.util.stream.Collectors.toMap; import java.util.stream.Stream; import java.util.HashMap; public final class FeatureFlagsImpl implements FeatureFlags { @Override public boolean disabledRo() { return getFlag(Flags.FLAG_DISABLED_RO); } @Override public boolean disabledRw() { return getFlag(Flags.FLAG_DISABLED_RW); } @Override public boolean enabledRo() { return getFlag(Flags.FLAG_ENABLED_RO); } @Override public boolean enabledRw() { return getFlag(Flags.FLAG_ENABLED_RW); } public void setFlag(String flagName, boolean value) { if (!this.mFlagMap.containsKey(flagName)) { throw new IllegalArgumentException("no such flag" + flagName); } this.mFlagMap.put(flagName, value); } private boolean getFlag(String flagName) { Boolean value = this.mFlagMap.get(flagName); if (value == null) { throw new IllegalArgumentException(flagName + " is not set"); } return value; } private HashMap<String, Boolean> mFlagMap = Stream.of( Flags.FLAG_DISABLED_RO, Flags.FLAG_DISABLED_RW, Flags.FLAG_ENABLED_RO, Flags.FLAG_ENABLED_RW ) .collect( HashMap::new, (map, elem) -> map.put(elem, null), HashMap::putAll ); } "#; let mut file_set = HashMap::from([ ("com/android/aconfig/test/Flags.java", expect_flags_content), ("com/android/aconfig/test/Flags.java", expect_flags_content.as_str()), ("com/android/aconfig/test/FeatureFlagsImpl.java", expected_featureflagsimpl_content), ("com/android/aconfig/test/FeatureFlags.java", expected_featureflags_content), ("com/android/aconfig/test/FeatureFlags.java", EXPECTED_FEATUREFLAGS_CONTENT), ]); for file in generated_files { Loading tools/aconfig/src/commands.rs +8 −2 Original line number Diff line number Diff line Loading @@ -129,12 +129,18 @@ pub fn parse_flags(package: &str, declarations: Vec<Input>, values: Vec<Input>) Ok(output) } pub fn create_java_lib(mut input: Input) -> Result<Vec<OutputFile>> { #[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)] pub enum CodegenMode { Production, Test, } pub fn create_java_lib(mut input: Input, codegen_mode: CodegenMode) -> Result<Vec<OutputFile>> { let parsed_flags = input.try_parse_flags()?; let Some(package) = find_unique_package(&parsed_flags) else { bail!("no parsed flags, or the parsed flags use different packages"); }; generate_java_code(package, parsed_flags.parsed_flag.iter()) generate_java_code(package, parsed_flags.parsed_flag.iter(), codegen_mode) } pub fn create_cpp_lib(mut input: Input) -> Result<OutputFile> { Loading tools/aconfig/src/main.rs +10 −3 Original line number Diff line number Diff line Loading @@ -34,7 +34,7 @@ mod protos; #[cfg(test)] mod test; use commands::{DumpFormat, Input, OutputFile}; use commands::{CodegenMode, DumpFormat, Input, OutputFile}; fn cli() -> Command { Command::new("aconfig") Loading @@ -49,7 +49,13 @@ fn cli() -> Command { .subcommand( Command::new("create-java-lib") .arg(Arg::new("cache").long("cache").required(true)) .arg(Arg::new("out").long("out").required(true)), .arg(Arg::new("out").long("out").required(true)) .arg( Arg::new("mode") .long("mode") .value_parser(EnumValueParser::<commands::CodegenMode>::new()) .default_value("production"), ), ) .subcommand( Command::new("create-cpp-lib") Loading Loading @@ -148,7 +154,8 @@ fn main() -> Result<()> { } Some(("create-java-lib", sub_matches)) => { let cache = open_single_file(sub_matches, "cache")?; let generated_files = commands::create_java_lib(cache)?; let mode = get_required_arg::<CodegenMode>(sub_matches, "mode")?; let generated_files = commands::create_java_lib(cache, *mode)?; let dir = PathBuf::from(get_required_arg::<String>(sub_matches, "out")?); generated_files .iter() Loading tools/aconfig/templates/FeatureFlagsImpl.java.template +45 −4 Original line number Diff line number Diff line package {package_name}; {{ if is_read_write }} {{ -if is_test_mode }} import static java.util.stream.Collectors.toMap; import java.util.stream.Stream; import java.util.HashMap; {{ else}} {{ if is_read_write- }} import android.provider.DeviceConfig; {{ -endif- }} {{ endif }} public final class FeatureFlagsImpl implements FeatureFlags \{ {{ for item in class_elements}} @Override public boolean {item.method_name}() \{ {{ if item.is_read_write- }} {{ -if not is_test_mode- }} {{ if item.is_read_write }} return DeviceConfig.getBoolean( "{item.device_config_namespace}", "{item.device_config_flag}", {item.default_value} ); {{ -else- }} {{ else }} return {item.default_value}; {{ -endif- }} {{ else }} return getFlag(Flags.FLAG_{item.flag_name_constant_suffix}); {{ -endif }} } {{ endfor }} {{ if is_test_mode- }} public void setFlag(String flagName, boolean value) \{ if (!this.mFlagMap.containsKey(flagName)) \{ throw new IllegalArgumentException("no such flag" + flagName); } this.mFlagMap.put(flagName, value); } private boolean getFlag(String flagName) \{ Boolean value = this.mFlagMap.get(flagName); if (value == null) \{ throw new IllegalArgumentException(flagName + " is not set"); } return value; } private HashMap<String, Boolean> mFlagMap = Stream.of( {{-for item in class_elements}} Flags.FLAG_{item.flag_name_constant_suffix}{{ if not @last }},{{ endif }} {{ -endfor }} ) .collect( HashMap::new, (map, elem) -> map.put(elem, null), HashMap::putAll ); {{ -endif }} } tools/aconfig/templates/Flags.java.template +11 −1 Original line number Diff line number Diff line Loading @@ -9,6 +9,16 @@ public final class Flags \{ return FEATURE_FLAGS.{item.method_name}(); } {{ endfor }} private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl(); {{ if is_test_mode }} public static void setFeatureFlagsImpl(FeatureFlags featureFlags) \{ Flags.FEATURE_FLAGS = featureFlags; } public static void unsetFeatureFlagsImpl() \{ Flags.FEATURE_FLAGS = null; } {{ -endif}} private static FeatureFlags FEATURE_FLAGS{{ -if not is_test_mode }} = new FeatureFlagsImpl(){{ -endif- }}; } Loading
tools/aconfig/src/codegen_java.rs +146 −44 Original line number Diff line number Diff line Loading @@ -20,17 +20,23 @@ use std::path::PathBuf; use tinytemplate::TinyTemplate; use crate::codegen; use crate::commands::OutputFile; use crate::commands::{CodegenMode, OutputFile}; use crate::protos::{ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag}; pub fn generate_java_code<'a, I>(package: &str, parsed_flags_iter: I) -> Result<Vec<OutputFile>> pub fn generate_java_code<'a, I>( package: &str, parsed_flags_iter: I, codegen_mode: CodegenMode, ) -> Result<Vec<OutputFile>> where I: Iterator<Item = &'a ProtoParsedFlag>, { let class_elements: Vec<ClassElement> = parsed_flags_iter.map(|pf| create_class_element(package, pf)).collect(); let is_read_write = class_elements.iter().any(|elem| elem.is_read_write); let context = Context { package_name: package.to_string(), is_read_write, class_elements }; let is_test_mode = codegen_mode == CodegenMode::Test; let context = Context { class_elements, is_test_mode, is_read_write, package_name: package.to_string() }; let mut template = TinyTemplate::new(); template.add_template("Flags.java", include_str!("../templates/Flags.java.template"))?; template.add_template( Loading @@ -56,14 +62,15 @@ where #[derive(Serialize)] struct Context { pub package_name: String, pub is_read_write: bool, pub class_elements: Vec<ClassElement>, pub is_test_mode: bool, pub is_read_write: bool, pub package_name: String, } #[derive(Serialize)] struct ClassElement { pub default_value: String, pub default_value: bool, pub device_config_namespace: String, pub device_config_flag: String, pub flag_name_constant_suffix: String, Loading @@ -75,11 +82,7 @@ fn create_class_element(package: &str, pf: &ProtoParsedFlag) -> ClassElement { let device_config_flag = codegen::create_device_config_ident(package, pf.name()) .expect("values checked at flag parse time"); ClassElement { default_value: if pf.state() == ProtoFlagState::ENABLED { "true".to_string() } else { "false".to_string() }, default_value: pf.state() == ProtoFlagState::ENABLED, device_config_namespace: pf.namespace().to_string(), device_config_flag, flag_name_constant_suffix: pf.name().to_ascii_uppercase(), Loading Loading @@ -109,12 +112,16 @@ mod tests { use super::*; use std::collections::HashMap; #[test] fn test_generate_java_code() { let parsed_flags = crate::test::parse_test_flags(); let generated_files = generate_java_code(crate::test::TEST_PACKAGE, parsed_flags.parsed_flag.iter()).unwrap(); let expect_flags_content = r#" const EXPECTED_FEATUREFLAGS_CONTENT: &str = r#" package com.android.aconfig.test; public interface FeatureFlags { boolean disabledRo(); boolean disabledRw(); boolean enabledRo(); boolean enabledRw(); }"#; const EXPECTED_FLAG_COMMON_CONTENT: &str = r#" package com.android.aconfig.test; public final class Flags { public static final String FLAG_DISABLED_RO = "com.android.aconfig.test.disabled_ro"; Loading @@ -134,9 +141,21 @@ mod tests { public static boolean enabledRw() { return FEATURE_FLAGS.enabledRw(); } private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl(); } "#; #[test] fn test_generate_java_code_production() { let parsed_flags = crate::test::parse_test_flags(); let generated_files = generate_java_code( crate::test::TEST_PACKAGE, parsed_flags.parsed_flag.iter(), CodegenMode::Production, ) .unwrap(); let expect_flags_content = EXPECTED_FLAG_COMMON_CONTENT.to_string() + r#" private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl(); }"#; let expected_featureflagsimpl_content = r#" package com.android.aconfig.test; import android.provider.DeviceConfig; Loading Loading @@ -167,19 +186,102 @@ mod tests { } } "#; let expected_featureflags_content = r#" let mut file_set = HashMap::from([ ("com/android/aconfig/test/Flags.java", expect_flags_content.as_str()), ("com/android/aconfig/test/FeatureFlagsImpl.java", expected_featureflagsimpl_content), ("com/android/aconfig/test/FeatureFlags.java", EXPECTED_FEATUREFLAGS_CONTENT), ]); for file in generated_files { let file_path = file.path.to_str().unwrap(); assert!(file_set.contains_key(file_path), "Cannot find {}", file_path); assert_eq!( None, crate::test::first_significant_code_diff( file_set.get(file_path).unwrap(), &String::from_utf8(file.contents.clone()).unwrap() ), "File {} content is not correct", file_path ); file_set.remove(file_path); } assert!(file_set.is_empty()); } #[test] fn test_generate_java_code_test() { let parsed_flags = crate::test::parse_test_flags(); let generated_files = generate_java_code( crate::test::TEST_PACKAGE, parsed_flags.parsed_flag.iter(), CodegenMode::Test, ) .unwrap(); let expect_flags_content = EXPECTED_FLAG_COMMON_CONTENT.to_string() + r#" public static void setFeatureFlagsImpl(FeatureFlags featureFlags) { Flags.FEATURE_FLAGS = featureFlags; } public static void unsetFeatureFlagsImpl() { Flags.FEATURE_FLAGS = null; } private static FeatureFlags FEATURE_FLAGS; } "#; let expected_featureflagsimpl_content = r#" package com.android.aconfig.test; public interface FeatureFlags { boolean disabledRo(); boolean disabledRw(); boolean enabledRo(); boolean enabledRw(); import static java.util.stream.Collectors.toMap; import java.util.stream.Stream; import java.util.HashMap; public final class FeatureFlagsImpl implements FeatureFlags { @Override public boolean disabledRo() { return getFlag(Flags.FLAG_DISABLED_RO); } @Override public boolean disabledRw() { return getFlag(Flags.FLAG_DISABLED_RW); } @Override public boolean enabledRo() { return getFlag(Flags.FLAG_ENABLED_RO); } @Override public boolean enabledRw() { return getFlag(Flags.FLAG_ENABLED_RW); } public void setFlag(String flagName, boolean value) { if (!this.mFlagMap.containsKey(flagName)) { throw new IllegalArgumentException("no such flag" + flagName); } this.mFlagMap.put(flagName, value); } private boolean getFlag(String flagName) { Boolean value = this.mFlagMap.get(flagName); if (value == null) { throw new IllegalArgumentException(flagName + " is not set"); } return value; } private HashMap<String, Boolean> mFlagMap = Stream.of( Flags.FLAG_DISABLED_RO, Flags.FLAG_DISABLED_RW, Flags.FLAG_ENABLED_RO, Flags.FLAG_ENABLED_RW ) .collect( HashMap::new, (map, elem) -> map.put(elem, null), HashMap::putAll ); } "#; let mut file_set = HashMap::from([ ("com/android/aconfig/test/Flags.java", expect_flags_content), ("com/android/aconfig/test/Flags.java", expect_flags_content.as_str()), ("com/android/aconfig/test/FeatureFlagsImpl.java", expected_featureflagsimpl_content), ("com/android/aconfig/test/FeatureFlags.java", expected_featureflags_content), ("com/android/aconfig/test/FeatureFlags.java", EXPECTED_FEATUREFLAGS_CONTENT), ]); for file in generated_files { Loading
tools/aconfig/src/commands.rs +8 −2 Original line number Diff line number Diff line Loading @@ -129,12 +129,18 @@ pub fn parse_flags(package: &str, declarations: Vec<Input>, values: Vec<Input>) Ok(output) } pub fn create_java_lib(mut input: Input) -> Result<Vec<OutputFile>> { #[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)] pub enum CodegenMode { Production, Test, } pub fn create_java_lib(mut input: Input, codegen_mode: CodegenMode) -> Result<Vec<OutputFile>> { let parsed_flags = input.try_parse_flags()?; let Some(package) = find_unique_package(&parsed_flags) else { bail!("no parsed flags, or the parsed flags use different packages"); }; generate_java_code(package, parsed_flags.parsed_flag.iter()) generate_java_code(package, parsed_flags.parsed_flag.iter(), codegen_mode) } pub fn create_cpp_lib(mut input: Input) -> Result<OutputFile> { Loading
tools/aconfig/src/main.rs +10 −3 Original line number Diff line number Diff line Loading @@ -34,7 +34,7 @@ mod protos; #[cfg(test)] mod test; use commands::{DumpFormat, Input, OutputFile}; use commands::{CodegenMode, DumpFormat, Input, OutputFile}; fn cli() -> Command { Command::new("aconfig") Loading @@ -49,7 +49,13 @@ fn cli() -> Command { .subcommand( Command::new("create-java-lib") .arg(Arg::new("cache").long("cache").required(true)) .arg(Arg::new("out").long("out").required(true)), .arg(Arg::new("out").long("out").required(true)) .arg( Arg::new("mode") .long("mode") .value_parser(EnumValueParser::<commands::CodegenMode>::new()) .default_value("production"), ), ) .subcommand( Command::new("create-cpp-lib") Loading Loading @@ -148,7 +154,8 @@ fn main() -> Result<()> { } Some(("create-java-lib", sub_matches)) => { let cache = open_single_file(sub_matches, "cache")?; let generated_files = commands::create_java_lib(cache)?; let mode = get_required_arg::<CodegenMode>(sub_matches, "mode")?; let generated_files = commands::create_java_lib(cache, *mode)?; let dir = PathBuf::from(get_required_arg::<String>(sub_matches, "out")?); generated_files .iter() Loading
tools/aconfig/templates/FeatureFlagsImpl.java.template +45 −4 Original line number Diff line number Diff line package {package_name}; {{ if is_read_write }} {{ -if is_test_mode }} import static java.util.stream.Collectors.toMap; import java.util.stream.Stream; import java.util.HashMap; {{ else}} {{ if is_read_write- }} import android.provider.DeviceConfig; {{ -endif- }} {{ endif }} public final class FeatureFlagsImpl implements FeatureFlags \{ {{ for item in class_elements}} @Override public boolean {item.method_name}() \{ {{ if item.is_read_write- }} {{ -if not is_test_mode- }} {{ if item.is_read_write }} return DeviceConfig.getBoolean( "{item.device_config_namespace}", "{item.device_config_flag}", {item.default_value} ); {{ -else- }} {{ else }} return {item.default_value}; {{ -endif- }} {{ else }} return getFlag(Flags.FLAG_{item.flag_name_constant_suffix}); {{ -endif }} } {{ endfor }} {{ if is_test_mode- }} public void setFlag(String flagName, boolean value) \{ if (!this.mFlagMap.containsKey(flagName)) \{ throw new IllegalArgumentException("no such flag" + flagName); } this.mFlagMap.put(flagName, value); } private boolean getFlag(String flagName) \{ Boolean value = this.mFlagMap.get(flagName); if (value == null) \{ throw new IllegalArgumentException(flagName + " is not set"); } return value; } private HashMap<String, Boolean> mFlagMap = Stream.of( {{-for item in class_elements}} Flags.FLAG_{item.flag_name_constant_suffix}{{ if not @last }},{{ endif }} {{ -endfor }} ) .collect( HashMap::new, (map, elem) -> map.put(elem, null), HashMap::putAll ); {{ -endif }} }
tools/aconfig/templates/Flags.java.template +11 −1 Original line number Diff line number Diff line Loading @@ -9,6 +9,16 @@ public final class Flags \{ return FEATURE_FLAGS.{item.method_name}(); } {{ endfor }} private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl(); {{ if is_test_mode }} public static void setFeatureFlagsImpl(FeatureFlags featureFlags) \{ Flags.FEATURE_FLAGS = featureFlags; } public static void unsetFeatureFlagsImpl() \{ Flags.FEATURE_FLAGS = null; } {{ -endif}} private static FeatureFlags FEATURE_FLAGS{{ -if not is_test_mode }} = new FeatureFlagsImpl(){{ -endif- }}; }