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

Commit 7b6aacb0 authored by Mårten Kongstad's avatar Mårten Kongstad Committed by Gerrit Code Review
Browse files

Merge "aconfig: Add codegen for java"

parents 6cc53f12 eb74489b
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ rust_defaults {
        "libprotobuf",
        "libserde",
        "libserde_json",
        "libtinytemplate",
    ],
}

+1 −0
Original line number Diff line number Diff line
@@ -14,6 +14,7 @@ clap = { version = "4.1.8", features = ["derive"] }
protobuf = "3.2.0"
serde = { version = "1.0.152", features = ["derive"] }
serde_json = "1.0.93"
tinytemplate = "1.2.1"

[build-dependencies]
protobuf-codegen = "3.2.0"
+142 −0
Original line number Diff line number Diff line
/*
 * 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 serde::Serialize;
use tinytemplate::TinyTemplate;

use crate::aconfig::{FlagState, Permission};
use crate::cache::{Cache, Item};

pub struct GeneratedFile {
    pub file_content: String,
    pub file_name: String,
}

pub fn generate_java_code(cache: &Cache) -> Result<GeneratedFile> {
    let class_elements: Vec<ClassElement> = cache.iter().map(create_class_element).collect();
    let readwrite = class_elements.iter().any(|item| item.readwrite);
    let namespace = uppercase_first_letter(
        cache.iter().find(|item| !item.namespace.is_empty()).unwrap().namespace.as_str(),
    );
    let context = Context { namespace: namespace.clone(), readwrite, class_elements };
    let mut template = TinyTemplate::new();
    template.add_template("java_code_gen", include_str!("../templates/java.template"))?;
    let file_content = template.render("java_code_gen", &context)?;
    Ok(GeneratedFile { file_content, file_name: format!("{}.java", namespace) })
}

#[derive(Serialize)]
struct Context {
    pub namespace: String,
    pub readwrite: bool,
    pub class_elements: Vec<ClassElement>,
}

#[derive(Serialize)]
struct ClassElement {
    pub method_name: String,
    pub readwrite: bool,
    pub default_value: String,
    pub feature_name: String,
    pub flag_name: String,
}

fn create_class_element(item: &Item) -> ClassElement {
    ClassElement {
        method_name: item.name.clone(),
        readwrite: item.permission == Permission::ReadWrite,
        default_value: if item.state == FlagState::Enabled {
            "true".to_string()
        } else {
            "false".to_string()
        },
        feature_name: item.name.clone(),
        flag_name: item.name.clone(),
    }
}

fn uppercase_first_letter(s: &str) -> String {
    s.chars()
        .enumerate()
        .map(
            |(index, ch)| {
                if index == 0 {
                    ch.to_ascii_uppercase()
                } else {
                    ch.to_ascii_lowercase()
                }
            },
        )
        .collect()
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::aconfig::{Flag, Value};
    use crate::commands::Source;

    #[test]
    fn test_generate_java_code() {
        let namespace = "TeSTFlaG";
        let mut cache = Cache::new(1, namespace.to_string());
        cache
            .add_flag(
                Source::File("test.txt".to_string()),
                Flag {
                    name: "test".to_string(),
                    description: "buildtime enable".to_string(),
                    values: vec![Value::default(FlagState::Enabled, Permission::ReadOnly)],
                },
            )
            .unwrap();
        cache
            .add_flag(
                Source::File("test2.txt".to_string()),
                Flag {
                    name: "test2".to_string(),
                    description: "runtime disable".to_string(),
                    values: vec![Value::default(FlagState::Disabled, Permission::ReadWrite)],
                },
            )
            .unwrap();
        let expect_content = "package com.android.aconfig;

        import android.provider.DeviceConfig;

        public final class Testflag {

            public static boolean test() {
                return true;
            }

            public static boolean test2() {
                return DeviceConfig.getBoolean(
                    \"Testflag\",
                    \"test2__test2\",
                    false
                );
            }

        }
        ";
        let expected_file_name = format!("{}.java", uppercase_first_letter(namespace));
        let generated_file = generate_java_code(&cache).unwrap();
        assert_eq!(expected_file_name, generated_file.file_name);
        assert_eq!(expect_content.replace(' ', ""), generated_file.file_content.replace(' ', ""));
    }
}
+5 −0
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ use std::io::Read;

use crate::aconfig::{Namespace, Override};
use crate::cache::Cache;
use crate::codegen_java::{generate_java_code, GeneratedFile};
use crate::protos::ProtoParsedFlags;

#[derive(Serialize, Deserialize, Clone, Debug)]
@@ -84,6 +85,10 @@ pub fn create_cache(
    Ok(cache)
}

pub fn generate_code(cache: &Cache) -> Result<GeneratedFile> {
    generate_java_code(cache)
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)]
pub enum Format {
    Text,
+17 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ use std::io::Write;

mod aconfig;
mod cache;
mod codegen_java;
mod commands;
mod protos;

@@ -46,6 +47,11 @@ fn cli() -> Command {
                .arg(Arg::new("override").long("override").action(ArgAction::Append))
                .arg(Arg::new("cache").long("cache").required(true)),
        )
        .subcommand(
            Command::new("create-java-lib")
                .arg(Arg::new("cache").long("cache").required(true))
                .arg(Arg::new("out").long("out").required(true)),
        )
        .subcommand(
            Command::new("dump")
                .arg(Arg::new("cache").long("cache").required(true))
@@ -81,6 +87,17 @@ fn main() -> Result<()> {
            let file = fs::File::create(path)?;
            cache.write_to_writer(file)?;
        }
        Some(("create-java-lib", sub_matches)) => {
            let path = sub_matches.get_one::<String>("cache").unwrap();
            let file = fs::File::open(path)?;
            let cache = Cache::read_from_reader(file)?;
            let out = sub_matches.get_one::<String>("out").unwrap();
            let generated_file = commands::generate_code(&cache).unwrap();
            fs::write(
                format!("{}/{}", out, generated_file.file_name),
                generated_file.file_content,
            )?;
        }
        Some(("dump", sub_matches)) => {
            let path = sub_matches.get_one::<String>("cache").unwrap();
            let file = fs::File::open(path)?;
Loading