Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 06377d79 authored by Zhi Dou's avatar Zhi Dou Committed by Gerrit Code Review
Browse files

Merge "aconfig: Java codegen iteration 1"

parents c37e824f 4655c967
Loading
Loading
Loading
Loading
+107 −69
Original line number Diff line number Diff line
@@ -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 {
@@ -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());
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -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)
}

+4 −2
Original line number Diff line number Diff line
@@ -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")?;
+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
+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