Loading tools/aconfig/src/codegen_java.rs +107 −69 Original line number Diff line number Diff line Loading @@ -24,43 +24,59 @@ use crate::cache::{Cache, Item}; use crate::codegen; use crate::commands::OutputFile; pub fn generate_java_code(cache: &Cache) -> Result<OutputFile> { pub fn generate_java_code(cache: &Cache) -> Result<Vec<OutputFile>> { let package = cache.package(); let class_elements: Vec<ClassElement> = cache.iter().map(|item| create_class_element(package, item)).collect(); let readwrite = class_elements.iter().any(|item| item.readwrite); let context = Context { package: package.to_string(), readwrite, class_elements }; let is_read_write = class_elements.iter().any(|item| item.is_read_write); let context = Context { package_name: package.to_string(), is_read_write, class_elements }; let java_files = vec!["Flags.java", "FeatureFlagsImpl.java", "FeatureFlags.java"]; let mut template = TinyTemplate::new(); template.add_template("java_code_gen", include_str!("../templates/java.template"))?; let contents = template.render("java_code_gen", &context)?; let mut path: PathBuf = package.split('.').collect(); // TODO: Allow customization of the java class name path.push("Flags.java"); Ok(OutputFile { contents: contents.into(), path }) template.add_template("Flags.java", include_str!("../templates/Flags.java.template"))?; template.add_template( "FeatureFlagsImpl.java", include_str!("../templates/FeatureFlagsImpl.java.template"), )?; template.add_template( "FeatureFlags.java", include_str!("../templates/FeatureFlags.java.template"), )?; let path: PathBuf = package.split('.').collect(); java_files .iter() .map(|file| { Ok(OutputFile { contents: template.render(file, &context)?.into(), path: path.join(file), }) }) .collect::<Result<Vec<OutputFile>>>() } #[derive(Serialize)] struct Context { pub package: String, pub readwrite: bool, pub package_name: String, pub is_read_write: bool, pub class_elements: Vec<ClassElement>, } #[derive(Serialize)] struct ClassElement { pub method_name: String, pub readwrite: bool, pub default_value: String, pub device_config_namespace: String, pub device_config_flag: String, pub flag_name_constant_suffix: String, pub is_read_write: bool, pub method_name: String, } fn create_class_element(package: &str, item: &Item) -> ClassElement { let device_config_flag = codegen::create_device_config_ident(package, &item.name) .expect("values checked at cache creation time"); ClassElement { method_name: item.name.replace('-', "_"), readwrite: item.permission == Permission::ReadWrite, default_value: if item.state == FlagState::Enabled { "true".to_string() } else { Loading @@ -68,78 +84,100 @@ fn create_class_element(package: &str, item: &Item) -> ClassElement { }, device_config_namespace: item.namespace.clone(), device_config_flag, flag_name_constant_suffix: item.name.to_ascii_uppercase(), is_read_write: item.permission == Permission::ReadWrite, method_name: item.name.clone(), } } #[cfg(test)] mod tests { use super::*; use crate::aconfig::{FlagDeclaration, FlagValue}; use crate::cache::CacheBuilder; use crate::commands::Source; use std::collections::HashMap; #[test] fn test_generate_java_code() { let package = "com.example"; let mut builder = CacheBuilder::new(package.to_string()).unwrap(); builder .add_flag_declaration( Source::File("test.txt".to_string()), FlagDeclaration { name: "test".to_string(), namespace: "ns".to_string(), description: "buildtime enable".to_string(), }, ) .unwrap() .add_flag_declaration( Source::File("test2.txt".to_string()), FlagDeclaration { name: "test2".to_string(), namespace: "ns".to_string(), description: "runtime disable".to_string(), }, ) .unwrap() .add_flag_value( Source::Memory, FlagValue { package: package.to_string(), name: "test".to_string(), state: FlagState::Disabled, permission: Permission::ReadOnly, }, ) .unwrap(); let cache = builder.build(); let expect_content = r#"package com.example; import android.provider.DeviceConfig; let cache = crate::test::create_cache(); let generated_files = generate_java_code(&cache).unwrap(); let expect_flags_content = r#" package com.android.aconfig.test; public final class Flags { public static boolean disabled_ro() { return FEATURE_FLAGS.disabled_ro(); } public static boolean disabled_rw() { return FEATURE_FLAGS.disabled_rw(); } public static boolean enabled_ro() { return FEATURE_FLAGS.enabled_ro(); } public static boolean enabled_rw() { return FEATURE_FLAGS.enabled_rw(); } private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl(); public static boolean test() { } "#; let expected_featureflagsimpl_content = r#" package com.android.aconfig.test; import android.provider.DeviceConfig; public final class FeatureFlagsImpl implements FeatureFlags { @Override public boolean disabled_ro() { return false; } public static boolean test2() { @Override public boolean disabled_rw() { return DeviceConfig.getBoolean( "ns", "com.example.test2", "aconfig_test", "com.android.aconfig.test.disabled_rw", false ); } @Override public boolean enabled_ro() { return true; } @Override public boolean enabled_rw() { return DeviceConfig.getBoolean( "aconfig_test", "com.android.aconfig.test.enabled_rw", true ); } } "#; let expected_featureflags_content = r#" package com.android.aconfig.test; public interface FeatureFlags { boolean disabled_ro(); boolean disabled_rw(); boolean enabled_ro(); boolean enabled_rw(); } "#; let file = generate_java_code(&cache).unwrap(); assert_eq!("com/example/Flags.java", file.path.to_str().unwrap()); let mut file_set = HashMap::from([ ("com/android/aconfig/test/Flags.java", expect_flags_content), ("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( expect_content, &String::from_utf8(file.contents).unwrap() ) 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()); } } tools/aconfig/src/commands.rs +1 −1 Original line number Diff line number Diff line Loading @@ -89,7 +89,7 @@ pub fn create_cache(package: &str, declarations: Vec<Input>, values: Vec<Input>) Ok(builder.build()) } pub fn create_java_lib(cache: Cache) -> Result<OutputFile> { pub fn create_java_lib(cache: Cache) -> Result<Vec<OutputFile>> { generate_java_code(&cache) } Loading tools/aconfig/src/main.rs +4 −2 Original line number Diff line number Diff line Loading @@ -147,8 +147,10 @@ fn main() -> Result<()> { let file = fs::File::open(path)?; let cache = Cache::read_from_reader(file)?; let dir = PathBuf::from(get_required_arg::<String>(sub_matches, "out")?); let generated_file = commands::create_java_lib(cache)?; write_output_file_realtive_to_dir(&dir, &generated_file)?; let generated_files = commands::create_java_lib(cache)?; generated_files .iter() .try_for_each(|file| write_output_file_realtive_to_dir(&dir, file))?; } Some(("create-cpp-lib", sub_matches)) => { let path = get_required_arg::<String>(sub_matches, "cache")?; Loading tools/aconfig/templates/FeatureFlags.java.template 0 → 100644 +7 −0 Original line number Diff line number Diff line package {package_name}; public interface FeatureFlags \{ {{ for item in class_elements}} boolean {item.method_name}(); {{ endfor }} } No newline at end of file tools/aconfig/templates/java.template→tools/aconfig/templates/FeatureFlagsImpl.java.template +7 −6 Original line number Diff line number Diff line package {package}; {{ if readwrite }} package {package_name}; {{ if is_read_write }} import android.provider.DeviceConfig; {{ endif }} public final class Flags \{ public final class FeatureFlagsImpl implements FeatureFlags \{ {{ for item in class_elements}} public static boolean {item.method_name}() \{ {{ if item.readwrite- }} @Override public boolean {item.method_name}() \{ {{ if item.is_read_write- }} return DeviceConfig.getBoolean( "{item.device_config_namespace}", "{item.device_config_flag}", Loading Loading
tools/aconfig/src/codegen_java.rs +107 −69 Original line number Diff line number Diff line Loading @@ -24,43 +24,59 @@ use crate::cache::{Cache, Item}; use crate::codegen; use crate::commands::OutputFile; pub fn generate_java_code(cache: &Cache) -> Result<OutputFile> { pub fn generate_java_code(cache: &Cache) -> Result<Vec<OutputFile>> { let package = cache.package(); let class_elements: Vec<ClassElement> = cache.iter().map(|item| create_class_element(package, item)).collect(); let readwrite = class_elements.iter().any(|item| item.readwrite); let context = Context { package: package.to_string(), readwrite, class_elements }; let is_read_write = class_elements.iter().any(|item| item.is_read_write); let context = Context { package_name: package.to_string(), is_read_write, class_elements }; let java_files = vec!["Flags.java", "FeatureFlagsImpl.java", "FeatureFlags.java"]; let mut template = TinyTemplate::new(); template.add_template("java_code_gen", include_str!("../templates/java.template"))?; let contents = template.render("java_code_gen", &context)?; let mut path: PathBuf = package.split('.').collect(); // TODO: Allow customization of the java class name path.push("Flags.java"); Ok(OutputFile { contents: contents.into(), path }) template.add_template("Flags.java", include_str!("../templates/Flags.java.template"))?; template.add_template( "FeatureFlagsImpl.java", include_str!("../templates/FeatureFlagsImpl.java.template"), )?; template.add_template( "FeatureFlags.java", include_str!("../templates/FeatureFlags.java.template"), )?; let path: PathBuf = package.split('.').collect(); java_files .iter() .map(|file| { Ok(OutputFile { contents: template.render(file, &context)?.into(), path: path.join(file), }) }) .collect::<Result<Vec<OutputFile>>>() } #[derive(Serialize)] struct Context { pub package: String, pub readwrite: bool, pub package_name: String, pub is_read_write: bool, pub class_elements: Vec<ClassElement>, } #[derive(Serialize)] struct ClassElement { pub method_name: String, pub readwrite: bool, pub default_value: String, pub device_config_namespace: String, pub device_config_flag: String, pub flag_name_constant_suffix: String, pub is_read_write: bool, pub method_name: String, } fn create_class_element(package: &str, item: &Item) -> ClassElement { let device_config_flag = codegen::create_device_config_ident(package, &item.name) .expect("values checked at cache creation time"); ClassElement { method_name: item.name.replace('-', "_"), readwrite: item.permission == Permission::ReadWrite, default_value: if item.state == FlagState::Enabled { "true".to_string() } else { Loading @@ -68,78 +84,100 @@ fn create_class_element(package: &str, item: &Item) -> ClassElement { }, device_config_namespace: item.namespace.clone(), device_config_flag, flag_name_constant_suffix: item.name.to_ascii_uppercase(), is_read_write: item.permission == Permission::ReadWrite, method_name: item.name.clone(), } } #[cfg(test)] mod tests { use super::*; use crate::aconfig::{FlagDeclaration, FlagValue}; use crate::cache::CacheBuilder; use crate::commands::Source; use std::collections::HashMap; #[test] fn test_generate_java_code() { let package = "com.example"; let mut builder = CacheBuilder::new(package.to_string()).unwrap(); builder .add_flag_declaration( Source::File("test.txt".to_string()), FlagDeclaration { name: "test".to_string(), namespace: "ns".to_string(), description: "buildtime enable".to_string(), }, ) .unwrap() .add_flag_declaration( Source::File("test2.txt".to_string()), FlagDeclaration { name: "test2".to_string(), namespace: "ns".to_string(), description: "runtime disable".to_string(), }, ) .unwrap() .add_flag_value( Source::Memory, FlagValue { package: package.to_string(), name: "test".to_string(), state: FlagState::Disabled, permission: Permission::ReadOnly, }, ) .unwrap(); let cache = builder.build(); let expect_content = r#"package com.example; import android.provider.DeviceConfig; let cache = crate::test::create_cache(); let generated_files = generate_java_code(&cache).unwrap(); let expect_flags_content = r#" package com.android.aconfig.test; public final class Flags { public static boolean disabled_ro() { return FEATURE_FLAGS.disabled_ro(); } public static boolean disabled_rw() { return FEATURE_FLAGS.disabled_rw(); } public static boolean enabled_ro() { return FEATURE_FLAGS.enabled_ro(); } public static boolean enabled_rw() { return FEATURE_FLAGS.enabled_rw(); } private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl(); public static boolean test() { } "#; let expected_featureflagsimpl_content = r#" package com.android.aconfig.test; import android.provider.DeviceConfig; public final class FeatureFlagsImpl implements FeatureFlags { @Override public boolean disabled_ro() { return false; } public static boolean test2() { @Override public boolean disabled_rw() { return DeviceConfig.getBoolean( "ns", "com.example.test2", "aconfig_test", "com.android.aconfig.test.disabled_rw", false ); } @Override public boolean enabled_ro() { return true; } @Override public boolean enabled_rw() { return DeviceConfig.getBoolean( "aconfig_test", "com.android.aconfig.test.enabled_rw", true ); } } "#; let expected_featureflags_content = r#" package com.android.aconfig.test; public interface FeatureFlags { boolean disabled_ro(); boolean disabled_rw(); boolean enabled_ro(); boolean enabled_rw(); } "#; let file = generate_java_code(&cache).unwrap(); assert_eq!("com/example/Flags.java", file.path.to_str().unwrap()); let mut file_set = HashMap::from([ ("com/android/aconfig/test/Flags.java", expect_flags_content), ("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( expect_content, &String::from_utf8(file.contents).unwrap() ) 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()); } }
tools/aconfig/src/commands.rs +1 −1 Original line number Diff line number Diff line Loading @@ -89,7 +89,7 @@ pub fn create_cache(package: &str, declarations: Vec<Input>, values: Vec<Input>) Ok(builder.build()) } pub fn create_java_lib(cache: Cache) -> Result<OutputFile> { pub fn create_java_lib(cache: Cache) -> Result<Vec<OutputFile>> { generate_java_code(&cache) } Loading
tools/aconfig/src/main.rs +4 −2 Original line number Diff line number Diff line Loading @@ -147,8 +147,10 @@ fn main() -> Result<()> { let file = fs::File::open(path)?; let cache = Cache::read_from_reader(file)?; let dir = PathBuf::from(get_required_arg::<String>(sub_matches, "out")?); let generated_file = commands::create_java_lib(cache)?; write_output_file_realtive_to_dir(&dir, &generated_file)?; let generated_files = commands::create_java_lib(cache)?; generated_files .iter() .try_for_each(|file| write_output_file_realtive_to_dir(&dir, file))?; } Some(("create-cpp-lib", sub_matches)) => { let path = get_required_arg::<String>(sub_matches, "cache")?; Loading
tools/aconfig/templates/FeatureFlags.java.template 0 → 100644 +7 −0 Original line number Diff line number Diff line package {package_name}; public interface FeatureFlags \{ {{ for item in class_elements}} boolean {item.method_name}(); {{ endfor }} } No newline at end of file
tools/aconfig/templates/java.template→tools/aconfig/templates/FeatureFlagsImpl.java.template +7 −6 Original line number Diff line number Diff line package {package}; {{ if readwrite }} package {package_name}; {{ if is_read_write }} import android.provider.DeviceConfig; {{ endif }} public final class Flags \{ public final class FeatureFlagsImpl implements FeatureFlags \{ {{ for item in class_elements}} public static boolean {item.method_name}() \{ {{ if item.readwrite- }} @Override public boolean {item.method_name}() \{ {{ if item.is_read_write- }} return DeviceConfig.getBoolean( "{item.device_config_namespace}", "{item.device_config_flag}", Loading