From 993111f91c7cb12392666e6694006710924ccfde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A5rten=20Kongstad?= Date: Wed, 24 May 2023 14:55:11 +0200 Subject: [PATCH 1/2] aconfig: improve dump --format=debug output Use Rust's {:#?} formatter for more readable output. Bug: 279485059 Test: atest aconfig.test Change-Id: I127f413e3d7aebfba96cad1dd58d9e261dd613a4 --- tools/aconfig/src/commands.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/aconfig/src/commands.rs b/tools/aconfig/src/commands.rs index 22de33175a..c854b62be0 100644 --- a/tools/aconfig/src/commands.rs +++ b/tools/aconfig/src/commands.rs @@ -125,7 +125,7 @@ pub fn dump_cache(mut caches: Vec, format: DumpFormat) -> Result> DumpFormat::Debug => { let mut lines = vec![]; for item in cache.iter() { - lines.push(format!("{:?}\n", item)); + lines.push(format!("{:#?}\n", item)); } output.append(&mut lines.concat().into()); } From f73b9632839caf94e12a046d0aeaac7e64375296 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A5rten=20Kongstad?= Date: Wed, 24 May 2023 15:43:47 +0200 Subject: [PATCH 2/2] aconfig: first iteration of Rust codegen Add a new `create-rust-lib` command to generate Rust code. The output is a src/lib.rs file; the build system is assumed to set the generated crate's name. For READ_ONLY flags, the generated code returns a hard-coded true or false. For READ_WRITE flags, the generated code reaches out to DeviceConfig via the cc_library server_configurable_flags via the libprofcollect_libflags_rust Rust bindings. The build system is assumed to add this to the generated crate's dependencies. Note: libprofcollect_libflags_rust seems generic enough that it should be moved to an official Rust wrapper for server_configurable_flags. This is tracked in b/284096062. Summary of module the built system is assumed to wrap the auto-generated code in: rust_library { name: "lib_rs", crate_name: "_rs", edition: "2021", clippy_lints: "none", no_stdlibs: true, lints: "none", srcs: ["src/lib.rs"], rustlibs: [ "libprofcollect_libflags_rust", ], } Also add a set of test input to be used in the unit tests for a more coherent test strategy. A follow-up CL will migrate the code in commands.rs, codegen_java.rs and codegen_cpp.rs. Bug: 279483360 Bug: 283907905 Test: atest aconfig.test Test: manual: create cache from files in testdata, create rust lib, add to module template above, verify the module builds Change-Id: I02606aa3686eda921116e33f7e2df8fd1156a7aa --- tools/aconfig/src/codegen_rust.rs | 129 ++++++++++++++++++++++++++ tools/aconfig/src/commands.rs | 5 + tools/aconfig/src/main.rs | 14 +++ tools/aconfig/templates/rust.template | 23 +++++ tools/aconfig/testdata/first.values | 18 ++++ tools/aconfig/testdata/second.values | 6 ++ tools/aconfig/testdata/test.aconfig | 33 +++++++ 7 files changed, 228 insertions(+) create mode 100644 tools/aconfig/src/codegen_rust.rs create mode 100644 tools/aconfig/templates/rust.template create mode 100644 tools/aconfig/testdata/first.values create mode 100644 tools/aconfig/testdata/second.values create mode 100644 tools/aconfig/testdata/test.aconfig diff --git a/tools/aconfig/src/codegen_rust.rs b/tools/aconfig/src/codegen_rust.rs new file mode 100644 index 0000000000..d75e315f61 --- /dev/null +++ b/tools/aconfig/src/codegen_rust.rs @@ -0,0 +1,129 @@ +/* + * 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}; +use crate::commands::OutputFile; + +pub fn generate_rust_code(cache: &Cache) -> Result { + let namespace = cache.namespace().to_lowercase(); + let parsed_flags: Vec = + cache.iter().map(|item| create_template_parsed_flag(&namespace, item)).collect(); + let context = TemplateContext { namespace, parsed_flags }; + let mut template = TinyTemplate::new(); + template.add_template("rust_code_gen", include_str!("../templates/rust.template"))?; + let contents = template.render("rust_code_gen", &context)?; + let path = ["src", "lib.rs"].iter().collect(); + Ok(OutputFile { contents: contents.into(), path }) +} + +#[derive(Serialize)] +struct TemplateContext { + pub namespace: String, + pub parsed_flags: Vec, +} + +#[derive(Serialize)] +struct TemplateParsedFlag { + pub name: String, + pub fn_name: String, + + // TinyTemplate's conditionals are limited to single expressions; list all options here + // Invariant: exactly one of these fields will be true + pub is_read_only_enabled: bool, + pub is_read_only_disabled: bool, + pub is_read_write: bool, +} + +#[allow(clippy::nonminimal_bool)] +fn create_template_parsed_flag(namespace: &str, item: &Item) -> TemplateParsedFlag { + let template = TemplateParsedFlag { + name: item.name.clone(), + fn_name: format!("{}_{}", namespace, item.name.replace('-', "_").to_lowercase()), + is_read_only_enabled: item.permission == Permission::ReadOnly + && item.state == FlagState::Enabled, + is_read_only_disabled: item.permission == Permission::ReadOnly + && item.state == FlagState::Disabled, + is_read_write: item.permission == Permission::ReadWrite, + }; + #[rustfmt::skip] + debug_assert!( + (template.is_read_only_enabled && !template.is_read_only_disabled && !template.is_read_write) || + (!template.is_read_only_enabled && template.is_read_only_disabled && !template.is_read_write) || + (!template.is_read_only_enabled && !template.is_read_only_disabled && template.is_read_write), + "TemplateParsedFlag invariant failed: {} {} {}", + template.is_read_only_enabled, + template.is_read_only_disabled, + template.is_read_write, + ); + template +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::commands::{create_cache, Input, Source}; + + #[test] + fn test_generate_rust_code() { + let cache = create_cache( + "test", + vec![Input { + source: Source::File("testdata/test.aconfig".to_string()), + reader: Box::new(include_bytes!("../testdata/test.aconfig").as_slice()), + }], + vec![ + Input { + source: Source::File("testdata/first.values".to_string()), + reader: Box::new(include_bytes!("../testdata/first.values").as_slice()), + }, + Input { + source: Source::File("testdata/test.aconfig".to_string()), + reader: Box::new(include_bytes!("../testdata/second.values").as_slice()), + }, + ], + ) + .unwrap(); + let generated = generate_rust_code(&cache).unwrap(); + assert_eq!("src/lib.rs", format!("{}", generated.path.display())); + let expected = r#" +#[inline(always)] +pub const fn r#test_disabled_ro() -> bool { + false +} + +#[inline(always)] +pub fn r#test_disabled_rw() -> bool { + profcollect_libflags_rust::GetServerConfigurableFlag("test", "disabled-rw", "false") == "true" +} + +#[inline(always)] +pub const fn r#test_enabled_ro() -> bool { + true +} + +#[inline(always)] +pub fn r#test_enabled_rw() -> bool { + profcollect_libflags_rust::GetServerConfigurableFlag("test", "enabled-rw", "false") == "true" +} +"#; + assert_eq!(expected.trim(), String::from_utf8(generated.contents).unwrap().trim()); + } +} diff --git a/tools/aconfig/src/commands.rs b/tools/aconfig/src/commands.rs index c854b62be0..cce1d7f43c 100644 --- a/tools/aconfig/src/commands.rs +++ b/tools/aconfig/src/commands.rs @@ -26,6 +26,7 @@ use crate::aconfig::{FlagDeclarations, FlagValue}; use crate::cache::{Cache, CacheBuilder}; use crate::codegen_cpp::generate_cpp_code; use crate::codegen_java::generate_java_code; +use crate::codegen_rust::generate_rust_code; use crate::protos::ProtoParsedFlags; #[derive(Serialize, Deserialize, Clone, Debug)] @@ -100,6 +101,10 @@ pub fn create_cpp_lib(cache: &Cache) -> Result { generate_cpp_code(cache) } +pub fn create_rust_lib(cache: &Cache) -> Result { + generate_rust_code(cache) +} + #[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)] pub enum DumpFormat { Text, diff --git a/tools/aconfig/src/main.rs b/tools/aconfig/src/main.rs index d02307d702..b60909b377 100644 --- a/tools/aconfig/src/main.rs +++ b/tools/aconfig/src/main.rs @@ -28,6 +28,7 @@ mod aconfig; mod cache; mod codegen_cpp; mod codegen_java; +mod codegen_rust; mod commands; mod protos; @@ -54,6 +55,11 @@ fn cli() -> Command { .arg(Arg::new("cache").long("cache").required(true)) .arg(Arg::new("out").long("out").required(true)), ) + .subcommand( + Command::new("create-rust-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").action(ArgAction::Append).required(true)) @@ -129,6 +135,14 @@ fn main() -> Result<()> { let generated_file = commands::create_cpp_lib(&cache)?; write_output_file_realtive_to_dir(&dir, &generated_file)?; } + Some(("create-rust-lib", sub_matches)) => { + let path = get_required_arg::(sub_matches, "cache")?; + let file = fs::File::open(path)?; + let cache = Cache::read_from_reader(file)?; + let dir = PathBuf::from(get_required_arg::(sub_matches, "out")?); + let generated_file = commands::create_rust_lib(&cache)?; + write_output_file_realtive_to_dir(&dir, &generated_file)?; + } Some(("dump", sub_matches)) => { let mut caches = Vec::new(); for path in sub_matches.get_many::("cache").unwrap_or_default() { diff --git a/tools/aconfig/templates/rust.template b/tools/aconfig/templates/rust.template new file mode 100644 index 0000000000..391c594eb0 --- /dev/null +++ b/tools/aconfig/templates/rust.template @@ -0,0 +1,23 @@ +{{- for parsed_flag in parsed_flags -}} +{{- if parsed_flag.is_read_only_disabled -}} +#[inline(always)] +pub const fn r#{parsed_flag.fn_name}() -> bool \{ + false +} + +{{ endif -}} +{{- if parsed_flag.is_read_only_enabled -}} +#[inline(always)] +pub const fn r#{parsed_flag.fn_name}() -> bool \{ + true +} + +{{ endif -}} +{{- if parsed_flag.is_read_write -}} +#[inline(always)] +pub fn r#{parsed_flag.fn_name}() -> bool \{ + profcollect_libflags_rust::GetServerConfigurableFlag("{namespace}", "{parsed_flag.name}", "false") == "true" +} + +{{ endif -}} +{{- endfor -}} diff --git a/tools/aconfig/testdata/first.values b/tools/aconfig/testdata/first.values new file mode 100644 index 0000000000..e6017fe841 --- /dev/null +++ b/tools/aconfig/testdata/first.values @@ -0,0 +1,18 @@ +flag_value { + namespace: "test" + name: "disabled-ro" + state: DISABLED + permission: READ_ONLY +} +flag_value { + namespace: "test" + name: "enabled-ro" + state: DISABLED + permission: READ_WRITE +} +flag_value { + namespace: "test" + name: "enabled-rw" + state: ENABLED + permission: READ_WRITE +} diff --git a/tools/aconfig/testdata/second.values b/tools/aconfig/testdata/second.values new file mode 100644 index 0000000000..44b6b3e445 --- /dev/null +++ b/tools/aconfig/testdata/second.values @@ -0,0 +1,6 @@ +flag_value { + namespace: "test" + name: "enabled-ro" + state: ENABLED + permission: READ_ONLY +} diff --git a/tools/aconfig/testdata/test.aconfig b/tools/aconfig/testdata/test.aconfig new file mode 100644 index 0000000000..16be425839 --- /dev/null +++ b/tools/aconfig/testdata/test.aconfig @@ -0,0 +1,33 @@ +namespace: "test" + +# This flag's final value is calculated from: +# - test.aconfig: DISABLED + READ_WRITE (default) +# - first.values: DISABLED + READ_ONLY +flag { + name: "disabled-ro" + description: "This flag is DISABLED + READ_ONLY" +} + +# This flag's final value is calculated from: +# - test.aconfig: DISABLED + READ_WRITE (default) +flag { + name: "disabled-rw" + description: "This flag is DISABLED + READ_WRITE" +} + +# This flag's final value is calculated from: +# - test.aconfig: DISABLED + READ_WRITE (default) +# - first.values: DISABLED + READ_WRITE +# - second.values: ENABLED + READ_ONLY +flag { + name: "enabled-ro" + description: "This flag is ENABLED + READ_ONLY" +} + +# This flag's final value is calculated from: +# - test.aconfig: DISABLED + READ_WRITE (default) +# - first.values: ENABLED + READ_WRITE +flag { + name: "enabled-rw" + description: "This flag is ENABLED + READ_WRITE" +}