Merge "Use sha256 when zipping target_files" into main
diff --git a/tools/aconfig/src/codegen/cpp.rs b/tools/aconfig/src/codegen/cpp.rs
index 000581b..568302d 100644
--- a/tools/aconfig/src/codegen/cpp.rs
+++ b/tools/aconfig/src/codegen/cpp.rs
@@ -23,17 +23,17 @@
 use crate::commands::{CodegenMode, OutputFile};
 use crate::protos::{ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag};
 
-pub fn generate_cpp_code<'a, I>(
+pub fn generate_cpp_code<I>(
     package: &str,
     parsed_flags_iter: I,
     codegen_mode: CodegenMode,
 ) -> Result<Vec<OutputFile>>
 where
-    I: Iterator<Item = &'a ProtoParsedFlag>,
+    I: Iterator<Item = ProtoParsedFlag>,
 {
     let mut readwrite_count = 0;
     let class_elements: Vec<ClassElement> = parsed_flags_iter
-        .map(|pf| create_class_element(package, pf, &mut readwrite_count))
+        .map(|pf| create_class_element(package, &pf, &mut readwrite_count))
         .collect();
     let readwrite = readwrite_count > 0;
     let has_fixed_read_only = class_elements.iter().any(|item| item.is_fixed_read_only);
@@ -135,6 +135,7 @@
 #[cfg(test)]
 mod tests {
     use super::*;
+    use crate::protos::ProtoParsedFlags;
     use std::collections::HashMap;
 
     const EXPORTED_PROD_HEADER_EXPECTED: &str = r#"
@@ -732,11 +733,133 @@
 
 "#;
 
-    fn test_generate_cpp_code(mode: CodegenMode) {
-        let parsed_flags = crate::test::parse_test_flags();
-        let generated =
-            generate_cpp_code(crate::test::TEST_PACKAGE, parsed_flags.parsed_flag.iter(), mode)
-                .unwrap();
+    const READ_ONLY_EXPORTED_PROD_HEADER_EXPECTED: &str = r#"
+#pragma once
+
+#ifndef COM_ANDROID_ACONFIG_TEST
+#define COM_ANDROID_ACONFIG_TEST(FLAG) COM_ANDROID_ACONFIG_TEST_##FLAG
+#endif
+
+#ifndef COM_ANDROID_ACONFIG_TEST_DISABLED_FIXED_RO
+#define COM_ANDROID_ACONFIG_TEST_DISABLED_FIXED_RO false
+#endif
+
+#ifndef COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO
+#define COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO true
+#endif
+
+#ifdef __cplusplus
+
+#include <memory>
+
+namespace com::android::aconfig::test {
+
+class flag_provider_interface {
+public:
+    virtual ~flag_provider_interface() = default;
+
+    virtual bool disabled_fixed_ro() = 0;
+
+    virtual bool disabled_ro() = 0;
+
+    virtual bool enabled_fixed_ro() = 0;
+
+    virtual bool enabled_ro() = 0;
+};
+
+extern std::unique_ptr<flag_provider_interface> provider_;
+
+inline bool disabled_fixed_ro() {
+    return COM_ANDROID_ACONFIG_TEST_DISABLED_FIXED_RO;
+}
+
+inline bool disabled_ro() {
+    return false;
+}
+
+inline bool enabled_fixed_ro() {
+    return COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO;
+}
+
+inline bool enabled_ro() {
+    return true;
+}
+}
+
+extern "C" {
+#endif // __cplusplus
+
+bool com_android_aconfig_test_disabled_fixed_ro();
+
+bool com_android_aconfig_test_disabled_ro();
+
+bool com_android_aconfig_test_enabled_fixed_ro();
+
+bool com_android_aconfig_test_enabled_ro();
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+"#;
+
+    const READ_ONLY_PROD_SOURCE_FILE_EXPECTED: &str = r#"
+#include "com_android_aconfig_test.h"
+
+namespace com::android::aconfig::test {
+
+    class flag_provider : public flag_provider_interface {
+        public:
+
+            virtual bool disabled_fixed_ro() override {
+                return COM_ANDROID_ACONFIG_TEST_DISABLED_FIXED_RO;
+            }
+
+            virtual bool disabled_ro() override {
+                return false;
+            }
+
+            virtual bool enabled_fixed_ro() override {
+                return COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO;
+            }
+
+            virtual bool enabled_ro() override {
+                return true;
+            }
+    };
+
+    std::unique_ptr<flag_provider_interface> provider_ =
+        std::make_unique<flag_provider>();
+}
+
+bool com_android_aconfig_test_disabled_fixed_ro() {
+    return COM_ANDROID_ACONFIG_TEST_DISABLED_FIXED_RO;
+}
+
+bool com_android_aconfig_test_disabled_ro() {
+    return false;
+}
+
+bool com_android_aconfig_test_enabled_fixed_ro() {
+    return COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO;
+}
+
+bool com_android_aconfig_test_enabled_ro() {
+    return true;
+}
+"#;
+
+    fn test_generate_cpp_code(
+        parsed_flags: ProtoParsedFlags,
+        mode: CodegenMode,
+        expected_header: &str,
+        expected_src: &str,
+    ) {
+        let generated = generate_cpp_code(
+            crate::test::TEST_PACKAGE,
+            parsed_flags.parsed_flag.into_iter(),
+            mode,
+        )
+        .unwrap();
         let mut generated_files_map = HashMap::new();
         for file in generated {
             generated_files_map.insert(
@@ -750,12 +873,7 @@
         assert_eq!(
             None,
             crate::test::first_significant_code_diff(
-                match mode {
-                    CodegenMode::Production => EXPORTED_PROD_HEADER_EXPECTED,
-                    CodegenMode::Test => EXPORTED_TEST_HEADER_EXPECTED,
-                    CodegenMode::Exported =>
-                        todo!("exported mode not yet supported for cpp, see b/313894653."),
-                },
+                expected_header,
                 generated_files_map.get(&target_file_path).unwrap()
             )
         );
@@ -765,12 +883,7 @@
         assert_eq!(
             None,
             crate::test::first_significant_code_diff(
-                match mode {
-                    CodegenMode::Production => PROD_SOURCE_FILE_EXPECTED,
-                    CodegenMode::Test => TEST_SOURCE_FILE_EXPECTED,
-                    CodegenMode::Exported =>
-                        todo!("exported mode not yet supported for cpp, see b/313894653."),
-                },
+                expected_src,
                 generated_files_map.get(&target_file_path).unwrap()
             )
         );
@@ -778,11 +891,34 @@
 
     #[test]
     fn test_generate_cpp_code_for_prod() {
-        test_generate_cpp_code(CodegenMode::Production);
+        let parsed_flags = crate::test::parse_test_flags();
+        test_generate_cpp_code(
+            parsed_flags,
+            CodegenMode::Production,
+            EXPORTED_PROD_HEADER_EXPECTED,
+            PROD_SOURCE_FILE_EXPECTED,
+        );
     }
 
     #[test]
     fn test_generate_cpp_code_for_test() {
-        test_generate_cpp_code(CodegenMode::Test);
+        let parsed_flags = crate::test::parse_test_flags();
+        test_generate_cpp_code(
+            parsed_flags,
+            CodegenMode::Test,
+            EXPORTED_TEST_HEADER_EXPECTED,
+            TEST_SOURCE_FILE_EXPECTED,
+        );
+    }
+
+    #[test]
+    fn test_generate_cpp_code_for_read_only_prod() {
+        let parsed_flags = crate::test::parse_read_only_test_flags();
+        test_generate_cpp_code(
+            parsed_flags,
+            CodegenMode::Production,
+            READ_ONLY_EXPORTED_PROD_HEADER_EXPECTED,
+            READ_ONLY_PROD_SOURCE_FILE_EXPECTED,
+        );
     }
 }
diff --git a/tools/aconfig/src/codegen/java.rs b/tools/aconfig/src/codegen/java.rs
index ae3f274..1d5dabf 100644
--- a/tools/aconfig/src/codegen/java.rs
+++ b/tools/aconfig/src/codegen/java.rs
@@ -24,16 +24,16 @@
 use crate::commands::{CodegenMode, OutputFile};
 use crate::protos::{ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag};
 
-pub fn generate_java_code<'a, I>(
+pub fn generate_java_code<I>(
     package: &str,
     parsed_flags_iter: I,
     codegen_mode: CodegenMode,
 ) -> Result<Vec<OutputFile>>
 where
-    I: Iterator<Item = &'a ProtoParsedFlag>,
+    I: Iterator<Item = ProtoParsedFlag>,
 {
     let flag_elements: Vec<FlagElement> =
-        parsed_flags_iter.map(|pf| create_flag_element(package, pf)).collect();
+        parsed_flags_iter.map(|pf| create_flag_element(package, &pf)).collect();
     let exported_flag_elements: Vec<FlagElement> =
         flag_elements.iter().filter(|elem| elem.exported).cloned().collect();
     let namespace_flags = gen_flags_by_namespace(&flag_elements);
@@ -361,7 +361,7 @@
         let parsed_flags = crate::test::parse_test_flags();
         let generated_files = generate_java_code(
             crate::test::TEST_PACKAGE,
-            parsed_flags.parsed_flag.iter(),
+            parsed_flags.parsed_flag.into_iter(),
             CodegenMode::Production,
         )
         .unwrap();
@@ -514,7 +514,7 @@
         let parsed_flags = crate::test::parse_test_flags();
         let generated_files = generate_java_code(
             crate::test::TEST_PACKAGE,
-            parsed_flags.parsed_flag.iter(),
+            parsed_flags.parsed_flag.into_iter(),
             CodegenMode::Exported,
         )
         .unwrap();
@@ -705,7 +705,7 @@
         let parsed_flags = crate::test::parse_test_flags();
         let generated_files = generate_java_code(
             crate::test::TEST_PACKAGE,
-            parsed_flags.parsed_flag.iter(),
+            parsed_flags.parsed_flag.into_iter(),
             CodegenMode::Test,
         )
         .unwrap();
diff --git a/tools/aconfig/src/codegen/rust.rs b/tools/aconfig/src/codegen/rust.rs
index 04be93b..7aafddb 100644
--- a/tools/aconfig/src/codegen/rust.rs
+++ b/tools/aconfig/src/codegen/rust.rs
@@ -22,16 +22,16 @@
 use crate::commands::{CodegenMode, OutputFile};
 use crate::protos::{ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag};
 
-pub fn generate_rust_code<'a, I>(
+pub fn generate_rust_code<I>(
     package: &str,
     parsed_flags_iter: I,
     codegen_mode: CodegenMode,
 ) -> Result<OutputFile>
 where
-    I: Iterator<Item = &'a ProtoParsedFlag>,
+    I: Iterator<Item = ProtoParsedFlag>,
 {
     let template_flags: Vec<TemplateParsedFlag> =
-        parsed_flags_iter.map(|pf| TemplateParsedFlag::new(package, pf)).collect();
+        parsed_flags_iter.map(|pf| TemplateParsedFlag::new(package, &pf)).collect();
     let has_readwrite = template_flags.iter().any(|item| item.readwrite);
     let context = TemplateContext {
         package: package.to_string(),
@@ -456,9 +456,12 @@
 
     fn test_generate_rust_code(mode: CodegenMode) {
         let parsed_flags = crate::test::parse_test_flags();
-        let generated =
-            generate_rust_code(crate::test::TEST_PACKAGE, parsed_flags.parsed_flag.iter(), mode)
-                .unwrap();
+        let generated = generate_rust_code(
+            crate::test::TEST_PACKAGE,
+            parsed_flags.parsed_flag.into_iter(),
+            mode,
+        )
+        .unwrap();
         assert_eq!("src/lib.rs", format!("{}", generated.path.display()));
         assert_eq!(
             None,
diff --git a/tools/aconfig/src/commands.rs b/tools/aconfig/src/commands.rs
index e437c02..87905fd 100644
--- a/tools/aconfig/src/commands.rs
+++ b/tools/aconfig/src/commands.rs
@@ -23,12 +23,12 @@
 use crate::codegen::cpp::generate_cpp_code;
 use crate::codegen::java::generate_java_code;
 use crate::codegen::rust::generate_rust_code;
-use crate::storage::generate_storage_files;
-
+use crate::dump::{DumpFormat, DumpPredicate};
 use crate::protos::{
     ParsedFlagExt, ProtoFlagMetadata, ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag,
     ProtoParsedFlags, ProtoTracepoint,
 };
+use crate::storage::generate_storage_files;
 
 pub struct Input {
     pub source: String,
@@ -201,7 +201,8 @@
     let Some(package) = find_unique_package(&filtered_parsed_flags) else {
         bail!("no parsed flags, or the parsed flags use different packages");
     };
-    generate_java_code(package, filtered_parsed_flags.iter(), codegen_mode)
+    let package = package.to_string();
+    generate_java_code(&package, filtered_parsed_flags.into_iter(), codegen_mode)
 }
 
 pub fn create_cpp_lib(mut input: Input, codegen_mode: CodegenMode) -> Result<Vec<OutputFile>> {
@@ -210,7 +211,8 @@
     let Some(package) = find_unique_package(&filtered_parsed_flags) else {
         bail!("no parsed flags, or the parsed flags use different packages");
     };
-    generate_cpp_code(package, filtered_parsed_flags.iter(), codegen_mode)
+    let package = package.to_string();
+    generate_cpp_code(&package, filtered_parsed_flags.into_iter(), codegen_mode)
 }
 
 pub fn create_rust_lib(mut input: Input, codegen_mode: CodegenMode) -> Result<OutputFile> {
@@ -219,7 +221,8 @@
     let Some(package) = find_unique_package(&filtered_parsed_flags) else {
         bail!("no parsed flags, or the parsed flags use different packages");
     };
-    generate_rust_code(package, filtered_parsed_flags.iter(), codegen_mode)
+    let package = package.to_string();
+    generate_rust_code(&package, filtered_parsed_flags.into_iter(), codegen_mode)
 }
 
 pub fn create_storage(caches: Vec<Input>, container: &str) -> Result<Vec<OutputFile>> {
@@ -276,73 +279,28 @@
     Ok(output)
 }
 
-#[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)]
-pub enum DumpFormat {
-    Text,
-    Verbose,
-    Protobuf,
-    Textproto,
-    Bool,
-}
-
 pub fn dump_parsed_flags(
     mut input: Vec<Input>,
     format: DumpFormat,
+    filters: &[&str],
     dedup: bool,
 ) -> Result<Vec<u8>> {
     let individually_parsed_flags: Result<Vec<ProtoParsedFlags>> =
         input.iter_mut().map(|i| i.try_parse_flags()).collect();
     let parsed_flags: ProtoParsedFlags =
         crate::protos::parsed_flags::merge(individually_parsed_flags?, dedup)?;
-
-    let mut output = Vec::new();
-    match format {
-        DumpFormat::Text => {
-            for parsed_flag in parsed_flags.parsed_flag.into_iter() {
-                let line = format!(
-                    "{} [{}]: {:?} + {:?}\n",
-                    parsed_flag.fully_qualified_name(),
-                    parsed_flag.container(),
-                    parsed_flag.permission(),
-                    parsed_flag.state()
-                );
-                output.extend_from_slice(line.as_bytes());
-            }
-        }
-        DumpFormat::Verbose => {
-            for parsed_flag in parsed_flags.parsed_flag.into_iter() {
-                let sources: Vec<_> =
-                    parsed_flag.trace.iter().map(|tracepoint| tracepoint.source()).collect();
-                let line = format!(
-                    "{} [{}]: {:?} + {:?} ({})\n",
-                    parsed_flag.fully_qualified_name(),
-                    parsed_flag.container(),
-                    parsed_flag.permission(),
-                    parsed_flag.state(),
-                    sources.join(", ")
-                );
-                output.extend_from_slice(line.as_bytes());
-            }
-        }
-        DumpFormat::Protobuf => {
-            parsed_flags.write_to_vec(&mut output)?;
-        }
-        DumpFormat::Textproto => {
-            let s = protobuf::text_format::print_to_string_pretty(&parsed_flags);
-            output.extend_from_slice(s.as_bytes());
-        }
-        DumpFormat::Bool => {
-            for parsed_flag in parsed_flags.parsed_flag.into_iter() {
-                let line = format!(
-                    "{}={:?}\n",
-                    parsed_flag.fully_qualified_name(),
-                    parsed_flag.state() == ProtoFlagState::ENABLED
-                );
-                output.extend_from_slice(line.as_bytes());
-            }
-        }
-    }
-    Ok(output)
+    let filters: Vec<Box<DumpPredicate>> = if filters.is_empty() {
+        vec![Box::new(|_| true)]
+    } else {
+        filters
+            .iter()
+            .map(|f| crate::dump::create_filter_predicate(f))
+            .collect::<Result<Vec<_>>>()?
+    };
+    crate::dump::dump_parsed_flags(
+        parsed_flags.parsed_flag.into_iter().filter(|flag| filters.iter().any(|p| p(flag))),
+        format,
+    )
 }
 
 fn find_unique_package(parsed_flags: &[ProtoParsedFlag]) -> Option<&str> {
@@ -619,43 +577,25 @@
     }
 
     #[test]
-    fn test_dump_text_format() {
+    fn test_dump() {
         let input = parse_test_flags_as_input();
-        let bytes = dump_parsed_flags(vec![input], DumpFormat::Text, false).unwrap();
-        let text = std::str::from_utf8(&bytes).unwrap();
-        assert!(
-            text.contains("com.android.aconfig.test.disabled_ro [system]: READ_ONLY + DISABLED")
-        );
-    }
-
-    #[test]
-    fn test_dump_protobuf_format() {
-        let expected = protobuf::text_format::parse_from_str::<ProtoParsedFlags>(
-            crate::test::TEST_FLAGS_TEXTPROTO,
+        let bytes = dump_parsed_flags(
+            vec![input],
+            DumpFormat::Custom("{fully_qualified_name}".to_string()),
+            &[],
+            false,
         )
-        .unwrap()
-        .write_to_bytes()
         .unwrap();
-
-        let input = parse_test_flags_as_input();
-        let actual = dump_parsed_flags(vec![input], DumpFormat::Protobuf, false).unwrap();
-
-        assert_eq!(expected, actual);
-    }
-
-    #[test]
-    fn test_dump_textproto_format() {
-        let input = parse_test_flags_as_input();
-        let bytes = dump_parsed_flags(vec![input], DumpFormat::Textproto, false).unwrap();
         let text = std::str::from_utf8(&bytes).unwrap();
-        assert_eq!(crate::test::TEST_FLAGS_TEXTPROTO.trim(), text.trim());
+        assert!(text.contains("com.android.aconfig.test.disabled_ro"));
     }
 
     #[test]
     fn test_dump_textproto_format_dedup() {
         let input = parse_test_flags_as_input();
         let input2 = parse_test_flags_as_input();
-        let bytes = dump_parsed_flags(vec![input, input2], DumpFormat::Textproto, true).unwrap();
+        let bytes =
+            dump_parsed_flags(vec![input, input2], DumpFormat::Textproto, &[], true).unwrap();
         let text = std::str::from_utf8(&bytes).unwrap();
         assert_eq!(crate::test::TEST_FLAGS_TEXTPROTO.trim(), text.trim());
     }
diff --git a/tools/aconfig/src/dump.rs b/tools/aconfig/src/dump.rs
new file mode 100644
index 0000000..f2b3687
--- /dev/null
+++ b/tools/aconfig/src/dump.rs
@@ -0,0 +1,414 @@
+/*
+ * 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 crate::protos::{
+    ParsedFlagExt, ProtoFlagMetadata, ProtoFlagPermission, ProtoFlagState, ProtoTracepoint,
+};
+use crate::protos::{ProtoParsedFlag, ProtoParsedFlags};
+use anyhow::{anyhow, bail, Context, Result};
+use protobuf::Message;
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum DumpFormat {
+    Protobuf,
+    Textproto,
+    Custom(String),
+}
+
+impl TryFrom<&str> for DumpFormat {
+    type Error = anyhow::Error;
+
+    fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
+        match value {
+            // protobuf formats
+            "protobuf" => Ok(Self::Protobuf),
+            "textproto" => Ok(Self::Textproto),
+
+            // old formats now implemented as aliases to custom format
+            "text" => Ok(Self::Custom(
+                "{fully_qualified_name} [{container}]: {permission} + {state}".to_owned(),
+            )),
+            "verbose" => Ok(Self::Custom(
+                "{fully_qualified_name} [{container}]: {permission} + {state} ({trace:paths})"
+                    .to_owned(),
+            )),
+            "bool" => Ok(Self::Custom("{fully_qualified_name}={state:bool}".to_owned())),
+
+            // custom format
+            _ => Ok(Self::Custom(value.to_owned())),
+        }
+    }
+}
+
+pub fn dump_parsed_flags<I>(parsed_flags_iter: I, format: DumpFormat) -> Result<Vec<u8>>
+where
+    I: Iterator<Item = ProtoParsedFlag>,
+{
+    let mut output = Vec::new();
+    match format {
+        DumpFormat::Protobuf => {
+            let parsed_flags =
+                ProtoParsedFlags { parsed_flag: parsed_flags_iter.collect(), ..Default::default() };
+            parsed_flags.write_to_vec(&mut output)?;
+        }
+        DumpFormat::Textproto => {
+            let parsed_flags =
+                ProtoParsedFlags { parsed_flag: parsed_flags_iter.collect(), ..Default::default() };
+            let s = protobuf::text_format::print_to_string_pretty(&parsed_flags);
+            output.extend_from_slice(s.as_bytes());
+        }
+        DumpFormat::Custom(format) => {
+            for flag in parsed_flags_iter {
+                dump_custom_format(&flag, &format, &mut output);
+            }
+        }
+    }
+    Ok(output)
+}
+
+fn dump_custom_format(flag: &ProtoParsedFlag, format: &str, output: &mut Vec<u8>) {
+    fn format_trace(trace: &[ProtoTracepoint]) -> String {
+        trace
+            .iter()
+            .map(|tracepoint| {
+                format!(
+                    "{}: {:?} + {:?}",
+                    tracepoint.source(),
+                    tracepoint.permission(),
+                    tracepoint.state()
+                )
+            })
+            .collect::<Vec<_>>()
+            .join(", ")
+    }
+
+    fn format_trace_paths(trace: &[ProtoTracepoint]) -> String {
+        trace.iter().map(|tracepoint| tracepoint.source()).collect::<Vec<_>>().join(", ")
+    }
+
+    fn format_metadata(metadata: &ProtoFlagMetadata) -> String {
+        format!("{:?}", metadata.purpose())
+    }
+
+    let mut str = format
+        // ProtoParsedFlag fields
+        .replace("{package}", flag.package())
+        .replace("{name}", flag.name())
+        .replace("{namespace}", flag.namespace())
+        .replace("{description}", flag.description())
+        .replace("{bug}", &flag.bug.join(", "))
+        .replace("{state}", &format!("{:?}", flag.state()))
+        .replace("{state:bool}", &format!("{}", flag.state() == ProtoFlagState::ENABLED))
+        .replace("{permission}", &format!("{:?}", flag.permission()))
+        .replace("{trace}", &format_trace(&flag.trace))
+        .replace("{trace:paths}", &format_trace_paths(&flag.trace))
+        .replace("{is_fixed_read_only}", &format!("{}", flag.is_fixed_read_only()))
+        .replace("{is_exported}", &format!("{}", flag.is_exported()))
+        .replace("{container}", flag.container())
+        .replace("{metadata}", &format_metadata(&flag.metadata))
+        // ParsedFlagExt functions
+        .replace("{fully_qualified_name}", &flag.fully_qualified_name());
+    str.push('\n');
+    output.extend_from_slice(str.as_bytes());
+}
+
+pub type DumpPredicate = dyn Fn(&ProtoParsedFlag) -> bool;
+
+pub fn create_filter_predicate(filter: &str) -> Result<Box<DumpPredicate>> {
+    let predicates = filter
+        .split('+')
+        .map(|sub_filter| create_filter_predicate_single(sub_filter))
+        .collect::<Result<Vec<_>>>()?;
+    Ok(Box::new(move |flag| predicates.iter().all(|p| p(flag))))
+}
+
+fn create_filter_predicate_single(filter: &str) -> Result<Box<DumpPredicate>> {
+    fn enum_from_str<T>(expected: &[T], s: &str) -> Result<T>
+    where
+        T: std::fmt::Debug + Copy,
+    {
+        for candidate in expected.iter() {
+            if s == format!("{:?}", candidate) {
+                return Ok(*candidate);
+            }
+        }
+        let expected =
+            expected.iter().map(|state| format!("{:?}", state)).collect::<Vec<_>>().join(", ");
+        bail!("\"{s}\": not a valid flag state, expected one of {expected}");
+    }
+
+    let error_msg = format!("\"{filter}\": filter syntax error");
+    let (what, arg) = filter.split_once(':').ok_or_else(|| anyhow!(error_msg.clone()))?;
+    match what {
+        "package" => {
+            let expected = arg.to_owned();
+            Ok(Box::new(move |flag: &ProtoParsedFlag| flag.package() == expected))
+        }
+        "name" => {
+            let expected = arg.to_owned();
+            Ok(Box::new(move |flag: &ProtoParsedFlag| flag.name() == expected))
+        }
+        "namespace" => {
+            let expected = arg.to_owned();
+            Ok(Box::new(move |flag: &ProtoParsedFlag| flag.namespace() == expected))
+        }
+        // description: not supported yet
+        "bug" => {
+            let expected = arg.to_owned();
+            Ok(Box::new(move |flag: &ProtoParsedFlag| flag.bug.join(", ") == expected))
+        }
+        "state" => {
+            let expected = enum_from_str(&[ProtoFlagState::ENABLED, ProtoFlagState::DISABLED], arg)
+                .context(error_msg)?;
+            Ok(Box::new(move |flag: &ProtoParsedFlag| flag.state() == expected))
+        }
+        "permission" => {
+            let expected = enum_from_str(
+                &[ProtoFlagPermission::READ_ONLY, ProtoFlagPermission::READ_WRITE],
+                arg,
+            )
+            .context(error_msg)?;
+            Ok(Box::new(move |flag: &ProtoParsedFlag| flag.permission() == expected))
+        }
+        // trace: not supported yet
+        "is_fixed_read_only" => {
+            let expected: bool = arg.parse().context(error_msg)?;
+            Ok(Box::new(move |flag: &ProtoParsedFlag| flag.is_fixed_read_only() == expected))
+        }
+        "is_exported" => {
+            let expected: bool = arg.parse().context(error_msg)?;
+            Ok(Box::new(move |flag: &ProtoParsedFlag| flag.is_exported() == expected))
+        }
+        "container" => {
+            let expected = arg.to_owned();
+            Ok(Box::new(move |flag: &ProtoParsedFlag| flag.container() == expected))
+        }
+        // metadata: not supported yet
+        _ => Err(anyhow!(error_msg)),
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::protos::ProtoParsedFlags;
+    use crate::test::parse_test_flags;
+    use protobuf::Message;
+
+    fn parse_enabled_ro_flag() -> ProtoParsedFlag {
+        parse_test_flags().parsed_flag.into_iter().find(|pf| pf.name() == "enabled_ro").unwrap()
+    }
+
+    #[test]
+    fn test_dumpformat_from_str() {
+        // supported format types
+        assert_eq!(DumpFormat::try_from("protobuf").unwrap(), DumpFormat::Protobuf);
+        assert_eq!(DumpFormat::try_from("textproto").unwrap(), DumpFormat::Textproto);
+        assert_eq!(
+            DumpFormat::try_from("foobar").unwrap(),
+            DumpFormat::Custom("foobar".to_owned())
+        );
+    }
+
+    #[test]
+    fn test_dump_parsed_flags_protobuf_format() {
+        let expected = protobuf::text_format::parse_from_str::<ProtoParsedFlags>(
+            crate::test::TEST_FLAGS_TEXTPROTO,
+        )
+        .unwrap()
+        .write_to_bytes()
+        .unwrap();
+        let parsed_flags = parse_test_flags();
+        let actual =
+            dump_parsed_flags(parsed_flags.parsed_flag.into_iter(), DumpFormat::Protobuf).unwrap();
+        assert_eq!(expected, actual);
+    }
+
+    #[test]
+    fn test_dump_parsed_flags_textproto_format() {
+        let parsed_flags = parse_test_flags();
+        let bytes =
+            dump_parsed_flags(parsed_flags.parsed_flag.into_iter(), DumpFormat::Textproto).unwrap();
+        let text = std::str::from_utf8(&bytes).unwrap();
+        assert_eq!(crate::test::TEST_FLAGS_TEXTPROTO.trim(), text.trim());
+    }
+
+    #[test]
+    fn test_dump_parsed_flags_custom_format() {
+        macro_rules! assert_dump_parsed_flags_custom_format_contains {
+            ($format:expr, $expected:expr) => {
+                let parsed_flags = parse_test_flags();
+                let bytes = dump_parsed_flags(
+                    parsed_flags.parsed_flag.into_iter(),
+                    $format.try_into().unwrap(),
+                )
+                .unwrap();
+                let text = std::str::from_utf8(&bytes).unwrap();
+                assert!(text.contains($expected));
+            };
+        }
+
+        // custom format
+        assert_dump_parsed_flags_custom_format_contains!(
+            "{fully_qualified_name}={permission} + {state}",
+            "com.android.aconfig.test.enabled_ro=READ_ONLY + ENABLED"
+        );
+
+        // aliases
+        assert_dump_parsed_flags_custom_format_contains!(
+            "text",
+            "com.android.aconfig.test.enabled_ro [system]: READ_ONLY + ENABLED"
+        );
+        assert_dump_parsed_flags_custom_format_contains!(
+            "verbose",
+            "com.android.aconfig.test.enabled_ro [system]: READ_ONLY + ENABLED (tests/test.aconfig, tests/first.values, tests/second.values)"
+        );
+        assert_dump_parsed_flags_custom_format_contains!(
+            "bool",
+            "com.android.aconfig.test.enabled_ro=true"
+        );
+    }
+
+    #[test]
+    fn test_dump_custom_format() {
+        macro_rules! assert_custom_format {
+            ($format:expr, $expected:expr) => {
+                let flag = parse_enabled_ro_flag();
+                let mut bytes = vec![];
+                dump_custom_format(&flag, $format, &mut bytes);
+                let text = std::str::from_utf8(&bytes).unwrap();
+                assert_eq!(text, $expected);
+            };
+        }
+
+        assert_custom_format!("{package}", "com.android.aconfig.test\n");
+        assert_custom_format!("{name}", "enabled_ro\n");
+        assert_custom_format!("{namespace}", "aconfig_test\n");
+        assert_custom_format!("{description}", "This flag is ENABLED + READ_ONLY\n");
+        assert_custom_format!("{bug}", "abc\n");
+        assert_custom_format!("{state}", "ENABLED\n");
+        assert_custom_format!("{state:bool}", "true\n");
+        assert_custom_format!("{permission}", "READ_ONLY\n");
+        assert_custom_format!("{trace}", "tests/test.aconfig: READ_WRITE + DISABLED, tests/first.values: READ_WRITE + DISABLED, tests/second.values: READ_ONLY + ENABLED\n");
+        assert_custom_format!(
+            "{trace:paths}",
+            "tests/test.aconfig, tests/first.values, tests/second.values\n"
+        );
+        assert_custom_format!("{is_fixed_read_only}", "false\n");
+        assert_custom_format!("{is_exported}", "false\n");
+        assert_custom_format!("{container}", "system\n");
+        assert_custom_format!("{metadata}", "PURPOSE_BUGFIX\n");
+
+        assert_custom_format!("name={name}|state={state}", "name=enabled_ro|state=ENABLED\n");
+        assert_custom_format!("{state}{state}{state}", "ENABLEDENABLEDENABLED\n");
+    }
+
+    #[test]
+    fn test_create_filter_predicate() {
+        macro_rules! assert_create_filter_predicate {
+            ($filter:expr, $expected:expr) => {
+                let parsed_flags = parse_test_flags();
+                let predicate = create_filter_predicate($filter).unwrap();
+                let mut filtered_flags: Vec<String> = parsed_flags
+                    .parsed_flag
+                    .into_iter()
+                    .filter(predicate)
+                    .map(|flag| flag.fully_qualified_name())
+                    .collect();
+                filtered_flags.sort();
+                assert_eq!(&filtered_flags, $expected);
+            };
+        }
+
+        assert_create_filter_predicate!(
+            "package:com.android.aconfig.test",
+            &[
+                "com.android.aconfig.test.disabled_ro",
+                "com.android.aconfig.test.disabled_rw",
+                "com.android.aconfig.test.disabled_rw_exported",
+                "com.android.aconfig.test.disabled_rw_in_other_namespace",
+                "com.android.aconfig.test.enabled_fixed_ro",
+                "com.android.aconfig.test.enabled_ro",
+                "com.android.aconfig.test.enabled_ro_exported",
+                "com.android.aconfig.test.enabled_rw",
+            ]
+        );
+        assert_create_filter_predicate!(
+            "name:disabled_rw",
+            &["com.android.aconfig.test.disabled_rw"]
+        );
+        assert_create_filter_predicate!(
+            "namespace:other_namespace",
+            &["com.android.aconfig.test.disabled_rw_in_other_namespace"]
+        );
+        // description: not supported yet
+        assert_create_filter_predicate!("bug:123", &["com.android.aconfig.test.disabled_ro",]);
+        assert_create_filter_predicate!(
+            "state:ENABLED",
+            &[
+                "com.android.aconfig.test.enabled_fixed_ro",
+                "com.android.aconfig.test.enabled_ro",
+                "com.android.aconfig.test.enabled_ro_exported",
+                "com.android.aconfig.test.enabled_rw",
+            ]
+        );
+        assert_create_filter_predicate!(
+            "permission:READ_ONLY",
+            &[
+                "com.android.aconfig.test.disabled_ro",
+                "com.android.aconfig.test.enabled_fixed_ro",
+                "com.android.aconfig.test.enabled_ro",
+                "com.android.aconfig.test.enabled_ro_exported",
+            ]
+        );
+        // trace: not supported yet
+        assert_create_filter_predicate!(
+            "is_fixed_read_only:true",
+            &["com.android.aconfig.test.enabled_fixed_ro"]
+        );
+        assert_create_filter_predicate!(
+            "is_exported:true",
+            &[
+                "com.android.aconfig.test.disabled_rw_exported",
+                "com.android.aconfig.test.enabled_ro_exported",
+            ]
+        );
+        assert_create_filter_predicate!(
+            "container:system",
+            &[
+                "com.android.aconfig.test.disabled_ro",
+                "com.android.aconfig.test.disabled_rw",
+                "com.android.aconfig.test.disabled_rw_exported",
+                "com.android.aconfig.test.disabled_rw_in_other_namespace",
+                "com.android.aconfig.test.enabled_fixed_ro",
+                "com.android.aconfig.test.enabled_ro",
+                "com.android.aconfig.test.enabled_ro_exported",
+                "com.android.aconfig.test.enabled_rw",
+            ]
+        );
+        // metadata: not supported yet
+
+        // multiple sub filters
+        assert_create_filter_predicate!(
+            "permission:READ_ONLY+state:ENABLED",
+            &[
+                "com.android.aconfig.test.enabled_fixed_ro",
+                "com.android.aconfig.test.enabled_ro",
+                "com.android.aconfig.test.enabled_ro_exported",
+            ]
+        );
+    }
+}
diff --git a/tools/aconfig/src/main.rs b/tools/aconfig/src/main.rs
index 63a50c8..fcc5ea5 100644
--- a/tools/aconfig/src/main.rs
+++ b/tools/aconfig/src/main.rs
@@ -26,13 +26,21 @@
 
 mod codegen;
 mod commands;
+mod dump;
 mod protos;
 mod storage;
 
+use dump::DumpFormat;
+
 #[cfg(test)]
 mod test;
 
-use commands::{CodegenMode, DumpFormat, Input, OutputFile};
+use commands::{CodegenMode, Input, OutputFile};
+
+const HELP_DUMP_FILTER: &str = r#"
+Limit which flags to output. If multiple --filter arguments are provided, the output will be
+limited to flags that match any of the filters.
+"#;
 
 fn cli() -> Command {
     Command::new("aconfig")
@@ -103,9 +111,10 @@
                 .arg(
                     Arg::new("format")
                         .long("format")
-                        .value_parser(EnumValueParser::<commands::DumpFormat>::new())
+                        .value_parser(|s: &str| DumpFormat::try_from(s))
                         .default_value("text"),
                 )
+                .arg(Arg::new("filter").long("filter").action(ArgAction::Append).help(HELP_DUMP_FILTER.trim()))
                 .arg(Arg::new("dedup").long("dedup").num_args(0).action(ArgAction::SetTrue))
                 .arg(Arg::new("out").long("out").default_value("-")),
         )
@@ -249,8 +258,13 @@
             let input = open_zero_or_more_files(sub_matches, "cache")?;
             let format = get_required_arg::<DumpFormat>(sub_matches, "format")
                 .context("failed to dump previously parsed flags")?;
+            let filters = sub_matches
+                .get_many::<String>("filter")
+                .unwrap_or_default()
+                .map(String::as_ref)
+                .collect::<Vec<_>>();
             let dedup = get_required_arg::<bool>(sub_matches, "dedup")?;
-            let output = commands::dump_parsed_flags(input, *format, *dedup)?;
+            let output = commands::dump_parsed_flags(input, format.clone(), &filters, *dedup)?;
             let path = get_required_arg::<String>(sub_matches, "out")?;
             write_output_to_file_or_stdout(path, &output)?;
         }
diff --git a/tools/aconfig/src/test.rs b/tools/aconfig/src/test.rs
index 71de57e..309cb28 100644
--- a/tools/aconfig/src/test.rs
+++ b/tools/aconfig/src/test.rs
@@ -225,6 +225,24 @@
 }
 "#;
 
+    pub fn parse_read_only_test_flags() -> ProtoParsedFlags {
+        let bytes = crate::commands::parse_flags(
+            "com.android.aconfig.test",
+            Some("system"),
+            vec![Input {
+                source: "tests/read_only_test.aconfig".to_string(),
+                reader: Box::new(include_bytes!("../tests/read_only_test.aconfig").as_slice()),
+            }],
+            vec![Input {
+                source: "tests/read_only_test.values".to_string(),
+                reader: Box::new(include_bytes!("../tests/read_only_test.values").as_slice()),
+            }],
+            crate::commands::DEFAULT_FLAG_PERMISSION,
+        )
+        .unwrap();
+        crate::protos::parsed_flags::try_from_binary_proto(&bytes).unwrap()
+    }
+
     pub fn parse_test_flags() -> ProtoParsedFlags {
         let bytes = crate::commands::parse_flags(
             "com.android.aconfig.test",
diff --git a/tools/aconfig/templates/cpp_exported_header.template b/tools/aconfig/templates/cpp_exported_header.template
index cc1b18d..377295d 100644
--- a/tools/aconfig/templates/cpp_exported_header.template
+++ b/tools/aconfig/templates/cpp_exported_header.template
@@ -5,12 +5,14 @@
 #ifndef {package_macro}
 #define {package_macro}(FLAG) {package_macro}_##FLAG
 #endif
-{{ for item in class_elements- }}
+{{ for item in class_elements }}
+
 {{ if item.is_fixed_read_only- }}
 #ifndef {package_macro}_{item.flag_macro}
 #define {package_macro}_{item.flag_macro} {item.default_value}
 #endif
-{{ endif }}
+{{ -endif }}
+
 {{ -endfor }}
 {{ -endif }}
 {{ -endif }}
@@ -27,7 +29,7 @@
     {{ for item in class_elements}}
     virtual bool {item.flag_name}() = 0;
 
-    {{ if for_test }}
+    {{ if for_test- }}
     virtual void {item.flag_name}(bool val) = 0;
     {{ -endif }}
     {{ -endfor }}
@@ -41,13 +43,13 @@
 
 {{ for item in class_elements}}
 inline bool {item.flag_name}() \{
-    {{ if for_test }}
+    {{ if for_test- }}
     return provider_->{item.flag_name}();
     {{ -else- }}
     {{ if item.readwrite- }}
     return provider_->{item.flag_name}();
     {{ -else- }}
-    {{ if item.is_fixed_read_only }}
+    {{ if item.is_fixed_read_only- }}
     return {package_macro}_{item.flag_macro};
     {{ -else- }}
     return {item.default_value};
@@ -56,14 +58,14 @@
     {{ -endif }}
 }
 
-{{ if for_test }}
+{{ if for_test- }}
 inline void {item.flag_name}(bool val) \{
     provider_->{item.flag_name}(val);
 }
 {{ -endif }}
 {{ -endfor }}
 
-{{ if for_test }}
+{{ if for_test- }}
 inline void reset_flags() \{
     return provider_->reset_flags();
 }
@@ -77,12 +79,12 @@
 {{ for item in class_elements }}
 bool {header}_{item.flag_name}();
 
-{{ if for_test }}
+{{ if for_test- }}
 void set_{header}_{item.flag_name}(bool val);
 {{ -endif }}
 {{ -endfor }}
 
-{{ if for_test }}
+{{ if for_test- }}
 void {header}_reset_flags();
 {{ -endif }}
 
diff --git a/tools/aconfig/templates/cpp_source_file.template b/tools/aconfig/templates/cpp_source_file.template
index 1bfa4b6..fbbfedc 100644
--- a/tools/aconfig/templates/cpp_source_file.template
+++ b/tools/aconfig/templates/cpp_source_file.template
@@ -1,17 +1,21 @@
 #include "{header}.h"
-{{ if readwrite }}
+
+{{ if readwrite- }}
 #include <server_configurable_flags/get_flags.h>
-{{ endif }}
-{{ if for_test }}
+{{ -endif }}
+
+{{ if for_test- }}
 #include <unordered_map>
 #include <string>
 {{ -else- }}
+{{ if readwrite- }}
 #include <vector>
-{{ endif }}
+{{ -endif }}
+{{ -endif }}
 
 namespace {cpp_namespace} \{
 
-{{ if for_test }}
+{{ if for_test- }}
     class flag_provider : public flag_provider_interface \{
     private:
         std::unordered_map<std::string, bool> overrides_;
@@ -21,7 +25,7 @@
             : overrides_()
         \{}
 
-        {{ for item in class_elements}}
+        {{ for item in class_elements }}
         virtual bool {item.flag_name}() override \{
             auto it = overrides_.find("{item.flag_name}");
               if (it != overrides_.end()) \{
@@ -41,7 +45,7 @@
         virtual void {item.flag_name}(bool val) override \{
             overrides_["{item.flag_name}"] = val;
         }
-        {{ endfor }}
+        {{ -endfor }}
 
         virtual void reset_flags() override \{
             overrides_.clear();
@@ -52,7 +56,8 @@
 
     class flag_provider : public flag_provider_interface \{
     public:
-        {{ for item in class_elements}}
+
+        {{ for item in class_elements }}
         virtual bool {item.flag_name}() override \{
             {{ if item.readwrite- }}
             if (cache_[{item.readwrite_idx}] == -1) \{
@@ -71,8 +76,10 @@
             {{ -endif }}
         }
         {{ endfor }}
+    {{ if readwrite- }}
     private:
         std::vector<int8_t> cache_ = std::vector<int8_t>({readwrite_count}, -1);
+    {{ -endif }}
     };
 
 
@@ -82,10 +89,9 @@
     std::make_unique<flag_provider>();
 }
 
-
-{{ for item in class_elements}}
+{{ for item in class_elements }}
 bool {header}_{item.flag_name}() \{
-    {{ if for_test }}
+    {{ if for_test- }}
     return {cpp_namespace}::{item.flag_name}();
     {{ -else- }}
     {{ if item.readwrite- }}
@@ -100,12 +106,12 @@
     {{ -endif }}
 }
 
-{{ if for_test }}
+{{ if for_test- }}
 void set_{header}_{item.flag_name}(bool val) \{
     {cpp_namespace}::{item.flag_name}(val);
 }
 {{ -endif }}
-{{ endfor -}}
+{{ endfor-}}
 
 {{ if for_test }}
 void {header}_reset_flags() \{
diff --git a/tools/aconfig/tests/read_only_test.aconfig b/tools/aconfig/tests/read_only_test.aconfig
new file mode 100644
index 0000000..5eb5056
--- /dev/null
+++ b/tools/aconfig/tests/read_only_test.aconfig
@@ -0,0 +1,32 @@
+package: "com.android.aconfig.test"
+container: "system"
+
+flag {
+    name: "enabled_ro"
+    namespace: "aconfig_test"
+    description: "This flag is ENABLED + READ_ONLY"
+    bug: "abc"
+}
+
+flag {
+    name: "disabled_ro"
+    namespace: "aconfig_test"
+    description: "This flag is DISABLED + READ_ONLY"
+    bug: "123"
+}
+
+flag {
+    name: "enabled_fixed_ro"
+    namespace: "aconfig_test"
+    description: "This flag is fixed READ_ONLY + ENABLED"
+    bug: ""
+    is_fixed_read_only: true
+}
+
+flag {
+    name: "disabled_fixed_ro"
+    namespace: "aconfig_test"
+    description: "This flag is fixed READ_ONLY + DISABLED"
+    bug: ""
+    is_fixed_read_only: true
+}
diff --git a/tools/aconfig/tests/read_only_test.values b/tools/aconfig/tests/read_only_test.values
new file mode 100644
index 0000000..349c7aa
--- /dev/null
+++ b/tools/aconfig/tests/read_only_test.values
@@ -0,0 +1,18 @@
+flag_value {
+    package: "com.android.aconfig.test"
+    name: "disabled_ro"
+    state: DISABLED
+    permission: READ_ONLY
+}
+flag_value {
+    package: "com.android.aconfig.test"
+    name: "enabled_ro"
+    state: ENABLED
+    permission: READ_ONLY
+}
+flag_value {
+    package: "com.android.aconfig.test"
+    name: "enabled_fixed_ro"
+    state: ENABLED
+    permission: READ_ONLY
+}
diff --git a/tools/rbcrun/go.mod b/tools/rbcrun/go.mod
index 5ae2972..6e99ce9 100644
--- a/tools/rbcrun/go.mod
+++ b/tools/rbcrun/go.mod
@@ -4,4 +4,4 @@
 
 replace go.starlark.net => ../../../../external/starlark-go
 
-go 1.15
+go 1.21