Loading tools/aconfig/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ rust_defaults { "libprotobuf", "libserde", "libserde_json", "libtinytemplate", ], } Loading tools/aconfig/Cargo.toml +1 −0 Original line number Diff line number Diff line Loading @@ -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" tools/aconfig/src/codegen_java.rs 0 → 100644 +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(' ', "")); } } tools/aconfig/src/commands.rs +5 −0 Original line number Diff line number Diff line Loading @@ -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)] Loading Loading @@ -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, Loading tools/aconfig/src/main.rs +17 −0 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ use std::io::Write; mod aconfig; mod cache; mod codegen_java; mod commands; mod protos; Loading @@ -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)) Loading Loading @@ -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 Loading
tools/aconfig/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ rust_defaults { "libprotobuf", "libserde", "libserde_json", "libtinytemplate", ], } Loading
tools/aconfig/Cargo.toml +1 −0 Original line number Diff line number Diff line Loading @@ -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"
tools/aconfig/src/codegen_java.rs 0 → 100644 +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(' ', "")); } }
tools/aconfig/src/commands.rs +5 −0 Original line number Diff line number Diff line Loading @@ -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)] Loading Loading @@ -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, Loading
tools/aconfig/src/main.rs +17 −0 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ use std::io::Write; mod aconfig; mod cache; mod codegen_java; mod commands; mod protos; Loading @@ -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)) Loading Loading @@ -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