Merge changes from topic "aconfig-create-rust-lib"

* changes:
  aconfig: first iteration of Rust codegen
  aconfig: improve dump --format=debug output
diff --git a/tools/aconfig/src/codegen_rust.rs b/tools/aconfig/src/codegen_rust.rs
new file mode 100644
index 0000000..d75e315
--- /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<OutputFile> {
+    let namespace = cache.namespace().to_lowercase();
+    let parsed_flags: Vec<TemplateParsedFlag> =
+        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<TemplateParsedFlag>,
+}
+
+#[derive(Serialize)]
+struct TemplateParsedFlag {
+    pub name: String,
+    pub fn_name: String,
+
+    // TinyTemplate's conditionals are limited to single <bool> 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 22de331..cce1d7f 100644
--- a/tools/aconfig/src/commands.rs
+++ b/tools/aconfig/src/commands.rs
@@ -26,6 +26,7 @@
 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 @@
     generate_cpp_code(cache)
 }
 
+pub fn create_rust_lib(cache: &Cache) -> Result<OutputFile> {
+    generate_rust_code(cache)
+}
+
 #[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)]
 pub enum DumpFormat {
     Text,
@@ -125,7 +130,7 @@
             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());
             }
diff --git a/tools/aconfig/src/main.rs b/tools/aconfig/src/main.rs
index d02307d..b60909b 100644
--- a/tools/aconfig/src/main.rs
+++ b/tools/aconfig/src/main.rs
@@ -28,6 +28,7 @@
 mod cache;
 mod codegen_cpp;
 mod codegen_java;
+mod codegen_rust;
 mod commands;
 mod protos;
 
@@ -55,6 +56,11 @@
                 .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))
                 .arg(
@@ -129,6 +135,14 @@
             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::<String>(sub_matches, "cache")?;
+            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_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::<String>("cache").unwrap_or_default() {
diff --git a/tools/aconfig/templates/rust.template b/tools/aconfig/templates/rust.template
new file mode 100644
index 0000000..391c594
--- /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 0000000..e6017fe
--- /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 0000000..44b6b3e
--- /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 0000000..16be425
--- /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"
+}