From c77c6612e7e24712d5a2a19d6e6189d39c5abe3a Mon Sep 17 00:00:00 2001 From: Dennis Shen Date: Tue, 23 Jan 2024 18:01:52 +0000 Subject: [PATCH] Restructure aconfig repo to be a cargo workspace with many crates This is cherry pick of aosp/2924191 to avoid merge conflict in git main Previously, aconfig repo is the root directory of aconfig binary crate, but it also hosts printflags crate inside, and there is no cargo support for printflags binary crate. In addition, with more aconfig development, more crates are being added to this repo. Thus this repo should be configured as a Cargo workspace with multiple crates rather than a single crate. Note the top level Cargo.toml file specifies the crates this workspace carries: (1) aconfig_protos: the proto library crate that will be used by many other crates such as aconfig binary crate and printflags binary crate (2) aconfig: the aconfig binary crate (3) printflags: the printflags binary crate (1) aconfig_protos crate setup: Inside aconfig_protos dir we set up the aconfig_protos crate, the previously src/proto.rs is now aconfig_protos/src/lib.rs, the build.rs is carried over to this crate. (2) aconfig binary crate setup: Notice its Cargo.toml file claims package dependency on aconfig_protos crate. It no longer carries proto related module and build.rs file. (3) printflags binary crate setup: Similary, notice that in its Cargo.toml file, it claims package dependency on aconfig_protos crate. With this setup, we can Cargo build/test each crate individually when inside a specific crate dir. But we can also run Cargo build/test at repo root level, which will build/test all the crates in this workplace. This is the structuring cl. The next cl is to move storage modules into its own library crate. This storage file library crate will be used by both aconfig binary crate as well as flag read library crate (to be created as another new crate here). Bug: b/321984352 Test: top and individual crate dir level Cargo build/test, m each individual targets Ignore-AOSP-First: cherrypick to git main to resolve merge conflict Change-Id: I75833f4997f7ee554ff6c1557df9ac87f62b2732 --- tools/aconfig/Cargo.toml | 27 ++----- tools/aconfig/{ => aconfig}/Android.bp | 48 ------------ tools/aconfig/aconfig/Cargo.toml | 18 +++++ .../aconfig/{ => aconfig}/src/codegen/cpp.rs | 5 +- .../aconfig/{ => aconfig}/src/codegen/java.rs | 3 +- .../aconfig/{ => aconfig}/src/codegen/mod.rs | 28 +------ .../aconfig/{ => aconfig}/src/codegen/rust.rs | 3 +- tools/aconfig/{ => aconfig}/src/commands.rs | 30 ++++---- tools/aconfig/{ => aconfig}/src/dump.rs | 6 +- tools/aconfig/{ => aconfig}/src/main.rs | 7 +- .../{ => aconfig}/src/storage/flag_table.rs | 0 .../{ => aconfig}/src/storage/flag_value.rs | 2 +- .../aconfig/{ => aconfig}/src/storage/mod.rs | 4 +- .../src/storage/package_table.rs | 0 tools/aconfig/{ => aconfig}/src/test.rs | 6 +- .../FakeFeatureFlagsImpl.java.template | 0 .../templates/FeatureFlags.java.template | 0 .../templates/FeatureFlagsImpl.java.template | 0 .../templates/Flags.java.template | 0 .../templates/cpp_exported_header.template | 0 .../templates/cpp_source_file.template | 0 .../{ => aconfig}/templates/rust.template | 0 .../templates/rust_test.template | 0 .../{ => aconfig}/tests/AconfigHostTest.java | 0 .../{ => aconfig}/tests/AconfigTest.java | 0 .../{ => aconfig}/tests/AndroidManifest.xml | 0 .../tests/aconfig_exported_mode_test.cpp | 0 .../tests/aconfig_exported_mode_test.rs | 0 .../aconfig_force_read_only_mode_test.cpp | 0 .../aconfig_force_read_only_mode_test.rs | 0 .../tests/aconfig_prod_mode_test.rs | 0 .../{ => aconfig}/tests/aconfig_test.cpp | 0 .../tests/aconfig_test_mode_test.rs | 0 .../tests/aconfig_test_test_variant.cpp | 0 .../aconfig/{ => aconfig}/tests/first.values | 0 .../tests/read_only_test.aconfig | 0 .../{ => aconfig}/tests/read_only_test.values | 0 .../aconfig/{ => aconfig}/tests/second.values | 0 .../tests/storage_test_1.aconfig | 0 .../tests/storage_test_2.aconfig | 0 .../tests/storage_test_4.aconfig | 0 .../aconfig/{ => aconfig}/tests/test.aconfig | 0 .../{ => aconfig}/tests/test_exported.aconfig | 0 .../tests/test_force_read_only.aconfig | 0 tools/aconfig/aconfig_protos/Android.bp | 62 +++++++++++++++ tools/aconfig/aconfig_protos/Cargo.toml | 17 +++++ tools/aconfig/{ => aconfig_protos}/build.rs | 0 .../{ => aconfig_protos}/protos/aconfig.proto | 0 .../protos.rs => aconfig_protos/src/lib.rs} | 76 ++++++++++++------- tools/aconfig/printflags/Cargo.toml | 15 ++++ tools/aconfig/printflags/src/main.rs | 4 +- 51 files changed, 205 insertions(+), 156 deletions(-) rename tools/aconfig/{ => aconfig}/Android.bp (85%) create mode 100644 tools/aconfig/aconfig/Cargo.toml rename tools/aconfig/{ => aconfig}/src/codegen/cpp.rs (99%) rename tools/aconfig/{ => aconfig}/src/codegen/java.rs (99%) rename tools/aconfig/{ => aconfig}/src/codegen/mod.rs (86%) rename tools/aconfig/{ => aconfig}/src/codegen/rust.rs (99%) rename tools/aconfig/{ => aconfig}/src/commands.rs (96%) rename tools/aconfig/{ => aconfig}/src/dump.rs (99%) rename tools/aconfig/{ => aconfig}/src/main.rs (97%) rename tools/aconfig/{ => aconfig}/src/storage/flag_table.rs (100%) rename tools/aconfig/{ => aconfig}/src/storage/flag_value.rs (99%) rename tools/aconfig/{ => aconfig}/src/storage/mod.rs (98%) rename tools/aconfig/{ => aconfig}/src/storage/package_table.rs (100%) rename tools/aconfig/{ => aconfig}/src/test.rs (97%) rename tools/aconfig/{ => aconfig}/templates/FakeFeatureFlagsImpl.java.template (100%) rename tools/aconfig/{ => aconfig}/templates/FeatureFlags.java.template (100%) rename tools/aconfig/{ => aconfig}/templates/FeatureFlagsImpl.java.template (100%) rename tools/aconfig/{ => aconfig}/templates/Flags.java.template (100%) rename tools/aconfig/{ => aconfig}/templates/cpp_exported_header.template (100%) rename tools/aconfig/{ => aconfig}/templates/cpp_source_file.template (100%) rename tools/aconfig/{ => aconfig}/templates/rust.template (100%) rename tools/aconfig/{ => aconfig}/templates/rust_test.template (100%) rename tools/aconfig/{ => aconfig}/tests/AconfigHostTest.java (100%) rename tools/aconfig/{ => aconfig}/tests/AconfigTest.java (100%) rename tools/aconfig/{ => aconfig}/tests/AndroidManifest.xml (100%) rename tools/aconfig/{ => aconfig}/tests/aconfig_exported_mode_test.cpp (100%) rename tools/aconfig/{ => aconfig}/tests/aconfig_exported_mode_test.rs (100%) rename tools/aconfig/{ => aconfig}/tests/aconfig_force_read_only_mode_test.cpp (100%) rename tools/aconfig/{ => aconfig}/tests/aconfig_force_read_only_mode_test.rs (100%) rename tools/aconfig/{ => aconfig}/tests/aconfig_prod_mode_test.rs (100%) rename tools/aconfig/{ => aconfig}/tests/aconfig_test.cpp (100%) rename tools/aconfig/{ => aconfig}/tests/aconfig_test_mode_test.rs (100%) rename tools/aconfig/{ => aconfig}/tests/aconfig_test_test_variant.cpp (100%) rename tools/aconfig/{ => aconfig}/tests/first.values (100%) rename tools/aconfig/{ => aconfig}/tests/read_only_test.aconfig (100%) rename tools/aconfig/{ => aconfig}/tests/read_only_test.values (100%) rename tools/aconfig/{ => aconfig}/tests/second.values (100%) rename tools/aconfig/{ => aconfig}/tests/storage_test_1.aconfig (100%) rename tools/aconfig/{ => aconfig}/tests/storage_test_2.aconfig (100%) rename tools/aconfig/{ => aconfig}/tests/storage_test_4.aconfig (100%) rename tools/aconfig/{ => aconfig}/tests/test.aconfig (100%) rename tools/aconfig/{ => aconfig}/tests/test_exported.aconfig (100%) rename tools/aconfig/{ => aconfig}/tests/test_force_read_only.aconfig (100%) create mode 100644 tools/aconfig/aconfig_protos/Android.bp create mode 100644 tools/aconfig/aconfig_protos/Cargo.toml rename tools/aconfig/{ => aconfig_protos}/build.rs (100%) rename tools/aconfig/{ => aconfig_protos}/protos/aconfig.proto (100%) rename tools/aconfig/{src/protos.rs => aconfig_protos/src/lib.rs} (92%) create mode 100644 tools/aconfig/printflags/Cargo.toml diff --git a/tools/aconfig/Cargo.toml b/tools/aconfig/Cargo.toml index 7b58e94631..8d93261934 100644 --- a/tools/aconfig/Cargo.toml +++ b/tools/aconfig/Cargo.toml @@ -1,22 +1,7 @@ -[package] -name = "aconfig" -version = "0.1.0" -edition = "2021" -build = "build.rs" +[workspace] -[features] -default = ["cargo"] -cargo = [] - -[dependencies] -anyhow = "1.0.69" -clap = { version = "4.1.8", features = ["derive"] } -itertools = "0.10.5" -paste = "1.0.11" -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" +members = [ + "aconfig", + "aconfig_protos", + "printflags" +] diff --git a/tools/aconfig/Android.bp b/tools/aconfig/aconfig/Android.bp similarity index 85% rename from tools/aconfig/Android.bp rename to tools/aconfig/aconfig/Android.bp index 0f9561de3d..3be456c97f 100644 --- a/tools/aconfig/Android.bp +++ b/tools/aconfig/aconfig/Android.bp @@ -2,51 +2,6 @@ package { default_applicable_licenses: ["Android-Apache-2.0"], } -// proto libraries for consumers of `aconfig dump --format=protobuf` output - -java_library { - name: "libaconfig_java_proto_lite", - host_supported: true, - srcs: ["protos/aconfig.proto"], - static_libs: ["libprotobuf-java-lite"], - proto: { - type: "lite", - }, - sdk_version: "current", - min_sdk_version: "34", - apex_available: [ - "com.android.configinfrastructure", - "//apex_available:platform", - ] -} - -java_library_host { - name: "libaconfig_java_proto_full", - srcs: ["protos/aconfig.proto"], - static_libs: ["libprotobuf-java-full"], - proto: { - type: "full", - }, -} - -python_library_host { - name: "libaconfig_python_proto", - srcs: ["protos/aconfig.proto"], - proto: { - canonical_path_from_root: false, - }, -} - -// host binary: aconfig - -rust_protobuf { - name: "libaconfig_protos", - protos: ["protos/aconfig.proto"], - crate_name: "aconfig_protos", - source_stem: "aconfig_protos", - host_supported: true, -} - rust_defaults { name: "aconfig.defaults", edition: "2021", @@ -63,9 +18,6 @@ rust_defaults { "libserde_json", "libtinytemplate", ], - proc_macros: [ - "libpaste", - ] } rust_binary_host { diff --git a/tools/aconfig/aconfig/Cargo.toml b/tools/aconfig/aconfig/Cargo.toml new file mode 100644 index 0000000000..01ad8c6760 --- /dev/null +++ b/tools/aconfig/aconfig/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "aconfig" +version = "0.1.0" +edition = "2021" + +[features] +default = ["cargo"] +cargo = [] + +[dependencies] +anyhow = "1.0.69" +clap = { version = "4.1.8", features = ["derive"] } +itertools = "0.10.5" +protobuf = "3.2.0" +serde = { version = "1.0.152", features = ["derive"] } +serde_json = "1.0.93" +tinytemplate = "1.2.1" +aconfig_protos = { path = "../aconfig_protos" } diff --git a/tools/aconfig/src/codegen/cpp.rs b/tools/aconfig/aconfig/src/codegen/cpp.rs similarity index 99% rename from tools/aconfig/src/codegen/cpp.rs rename to tools/aconfig/aconfig/src/codegen/cpp.rs index 1279d8e7fa..cd71b10d52 100644 --- a/tools/aconfig/src/codegen/cpp.rs +++ b/tools/aconfig/aconfig/src/codegen/cpp.rs @@ -19,10 +19,11 @@ use serde::Serialize; use std::path::PathBuf; use tinytemplate::TinyTemplate; +use aconfig_protos::{ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag}; + use crate::codegen; use crate::codegen::CodegenMode; use crate::commands::OutputFile; -use crate::protos::{ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag}; pub fn generate_cpp_code( package: &str, @@ -136,7 +137,7 @@ fn create_class_element(package: &str, pf: &ProtoParsedFlag, rw_count: &mut i32) #[cfg(test)] mod tests { use super::*; - use crate::protos::ProtoParsedFlags; + use aconfig_protos::ProtoParsedFlags; use std::collections::HashMap; const EXPORTED_PROD_HEADER_EXPECTED: &str = r#" diff --git a/tools/aconfig/src/codegen/java.rs b/tools/aconfig/aconfig/src/codegen/java.rs similarity index 99% rename from tools/aconfig/src/codegen/java.rs rename to tools/aconfig/aconfig/src/codegen/java.rs index 78e892b479..7ce1d51a4a 100644 --- a/tools/aconfig/src/codegen/java.rs +++ b/tools/aconfig/aconfig/src/codegen/java.rs @@ -20,10 +20,11 @@ use std::collections::{BTreeMap, BTreeSet}; use std::path::PathBuf; use tinytemplate::TinyTemplate; +use aconfig_protos::{ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag}; + use crate::codegen; use crate::codegen::CodegenMode; use crate::commands::OutputFile; -use crate::protos::{ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag}; pub fn generate_java_code( package: &str, diff --git a/tools/aconfig/src/codegen/mod.rs b/tools/aconfig/aconfig/src/codegen/mod.rs similarity index 86% rename from tools/aconfig/src/codegen/mod.rs rename to tools/aconfig/aconfig/src/codegen/mod.rs index 64ffa8b379..7b2336f987 100644 --- a/tools/aconfig/src/codegen/mod.rs +++ b/tools/aconfig/aconfig/src/codegen/mod.rs @@ -20,32 +20,7 @@ pub mod rust; use anyhow::{ensure, Result}; use clap::ValueEnum; - -pub fn is_valid_name_ident(s: &str) -> bool { - // Identifiers must match [a-z][a-z0-9_]*, except consecutive underscores are not allowed - if s.contains("__") { - return false; - } - let mut chars = s.chars(); - let Some(first) = chars.next() else { - return false; - }; - if !first.is_ascii_lowercase() { - return false; - } - chars.all(|ch| ch.is_ascii_lowercase() || ch.is_ascii_digit() || ch == '_') -} - -pub fn is_valid_package_ident(s: &str) -> bool { - if !s.contains('.') { - return false; - } - s.split('.').all(is_valid_name_ident) -} - -pub fn is_valid_container_ident(s: &str) -> bool { - s.split('.').all(is_valid_name_ident) -} +use aconfig_protos::{is_valid_name_ident, is_valid_package_ident}; pub fn create_device_config_ident(package: &str, flag_name: &str) -> Result { ensure!(is_valid_package_ident(package), "bad package"); @@ -75,6 +50,7 @@ impl std::fmt::Display for CodegenMode { #[cfg(test)] mod tests { use super::*; + use aconfig_protos::is_valid_container_ident; #[test] fn test_is_valid_name_ident() { diff --git a/tools/aconfig/src/codegen/rust.rs b/tools/aconfig/aconfig/src/codegen/rust.rs similarity index 99% rename from tools/aconfig/src/codegen/rust.rs rename to tools/aconfig/aconfig/src/codegen/rust.rs index 8a88ffecc7..33c3d37633 100644 --- a/tools/aconfig/src/codegen/rust.rs +++ b/tools/aconfig/aconfig/src/codegen/rust.rs @@ -18,10 +18,11 @@ use anyhow::Result; use serde::Serialize; use tinytemplate::TinyTemplate; +use aconfig_protos::{ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag}; + use crate::codegen; use crate::codegen::CodegenMode; use crate::commands::OutputFile; -use crate::protos::{ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag}; pub fn generate_rust_code( package: &str, diff --git a/tools/aconfig/src/commands.rs b/tools/aconfig/aconfig/src/commands.rs similarity index 96% rename from tools/aconfig/src/commands.rs rename to tools/aconfig/aconfig/src/commands.rs index a35ad08730..93bc436fd8 100644 --- a/tools/aconfig/src/commands.rs +++ b/tools/aconfig/aconfig/src/commands.rs @@ -26,7 +26,7 @@ use crate::codegen::java::generate_java_code; use crate::codegen::rust::generate_rust_code; use crate::codegen::CodegenMode; use crate::dump::{DumpFormat, DumpPredicate}; -use crate::protos::{ +use aconfig_protos::{ ParsedFlagExt, ProtoFlagMetadata, ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag, ProtoParsedFlags, ProtoTracepoint, }; @@ -44,7 +44,7 @@ impl Input { self.reader .read_to_end(&mut buffer) .with_context(|| format!("failed to read {}", self.source))?; - crate::protos::parsed_flags::try_from_binary_proto(&buffer) + aconfig_protos::parsed_flags::try_from_binary_proto(&buffer) .with_context(|| self.error_context()) } @@ -77,7 +77,7 @@ pub fn parse_flags( .read_to_string(&mut contents) .with_context(|| format!("failed to read {}", input.source))?; - let flag_declarations = crate::protos::flag_declarations::try_from_text_proto(&contents) + let flag_declarations = aconfig_protos::flag_declarations::try_from_text_proto(&contents) .with_context(|| input.error_context())?; ensure!( package == flag_declarations.package(), @@ -96,7 +96,7 @@ pub fn parse_flags( ); } for mut flag_declaration in flag_declarations.flag.into_iter() { - crate::protos::flag_declaration::verify_fields(&flag_declaration) + aconfig_protos::flag_declaration::verify_fields(&flag_declaration) .with_context(|| input.error_context())?; // create ParsedFlag using FlagDeclaration and default values @@ -130,7 +130,7 @@ pub fn parse_flags( parsed_flag.metadata = Some(metadata).into(); // verify ParsedFlag looks reasonable - crate::protos::parsed_flag::verify_fields(&parsed_flag)?; + aconfig_protos::parsed_flag::verify_fields(&parsed_flag)?; // verify ParsedFlag can be added ensure!( @@ -151,10 +151,10 @@ pub fn parse_flags( .reader .read_to_string(&mut contents) .with_context(|| format!("failed to read {}", input.source))?; - let flag_values = crate::protos::flag_values::try_from_text_proto(&contents) + let flag_values = aconfig_protos::flag_values::try_from_text_proto(&contents) .with_context(|| input.error_context())?; for flag_value in flag_values.flag_value.into_iter() { - crate::protos::flag_value::verify_fields(&flag_value) + aconfig_protos::flag_value::verify_fields(&flag_value) .with_context(|| input.error_context())?; let Some(parsed_flag) = parsed_flags @@ -184,8 +184,8 @@ pub fn parse_flags( } // Create a sorted parsed_flags - crate::protos::parsed_flags::sort_parsed_flags(&mut parsed_flags); - crate::protos::parsed_flags::verify_fields(&parsed_flags)?; + aconfig_protos::parsed_flags::sort_parsed_flags(&mut parsed_flags); + aconfig_protos::parsed_flags::verify_fields(&parsed_flags)?; let mut output = Vec::new(); parsed_flags.write_to_vec(&mut output)?; Ok(output) @@ -287,7 +287,7 @@ pub fn dump_parsed_flags( let individually_parsed_flags: Result> = input.iter_mut().map(|i| i.try_parse_flags()).collect(); let parsed_flags: ProtoParsedFlags = - crate::protos::parsed_flags::merge(individually_parsed_flags?, dedup)?; + aconfig_protos::parsed_flags::merge(individually_parsed_flags?, dedup)?; let filters: Vec> = if filters.is_empty() { vec![Box::new(|_| true)] } else { @@ -386,16 +386,16 @@ where #[cfg(test)] mod tests { use super::*; - use crate::protos::ProtoFlagPurpose; + use aconfig_protos::ProtoFlagPurpose; #[test] fn test_parse_flags() { let parsed_flags = crate::test::parse_test_flags(); // calls parse_flags - crate::protos::parsed_flags::verify_fields(&parsed_flags).unwrap(); + aconfig_protos::parsed_flags::verify_fields(&parsed_flags).unwrap(); let enabled_ro = parsed_flags.parsed_flag.iter().find(|pf| pf.name() == "enabled_ro").unwrap(); - assert!(crate::protos::parsed_flag::verify_fields(enabled_ro).is_ok()); + assert!(aconfig_protos::parsed_flag::verify_fields(enabled_ro).is_ok()); assert_eq!("com.android.aconfig.test", enabled_ro.package()); assert_eq!("enabled_ro", enabled_ro.name()); assert_eq!("This flag is ENABLED + READ_ONLY", enabled_ro.description()); @@ -462,7 +462,7 @@ mod tests { ) .unwrap(); let parsed_flags = - crate::protos::parsed_flags::try_from_binary_proto(&flags_bytes).unwrap(); + aconfig_protos::parsed_flags::try_from_binary_proto(&flags_bytes).unwrap(); assert_eq!(1, parsed_flags.parsed_flag.len()); let parsed_flag = parsed_flags.parsed_flag.first().unwrap(); assert_eq!(ProtoFlagState::DISABLED, parsed_flag.state()); @@ -602,7 +602,7 @@ mod tests { ) .unwrap(); let parsed_flags = - crate::protos::parsed_flags::try_from_binary_proto(&flags_bytes).unwrap(); + aconfig_protos::parsed_flags::try_from_binary_proto(&flags_bytes).unwrap(); assert_eq!(1, parsed_flags.parsed_flag.len()); let parsed_flag = parsed_flags.parsed_flag.first().unwrap(); assert_eq!(ProtoFlagPurpose::PURPOSE_FEATURE, parsed_flag.metadata.purpose()); diff --git a/tools/aconfig/src/dump.rs b/tools/aconfig/aconfig/src/dump.rs similarity index 99% rename from tools/aconfig/src/dump.rs rename to tools/aconfig/aconfig/src/dump.rs index 37368eec1a..12352f99f8 100644 --- a/tools/aconfig/src/dump.rs +++ b/tools/aconfig/aconfig/src/dump.rs @@ -14,10 +14,10 @@ * limitations under the License. */ -use crate::protos::{ +use aconfig_protos::{ ParsedFlagExt, ProtoFlagMetadata, ProtoFlagPermission, ProtoFlagState, ProtoTracepoint, }; -use crate::protos::{ProtoParsedFlag, ProtoParsedFlags}; +use aconfig_protos::{ProtoParsedFlag, ProtoParsedFlags}; use anyhow::{anyhow, bail, Context, Result}; use protobuf::Message; @@ -197,7 +197,7 @@ fn create_filter_predicate_single(filter: &str) -> Result> { #[cfg(test)] mod tests { use super::*; - use crate::protos::ProtoParsedFlags; + use aconfig_protos::ProtoParsedFlags; use crate::test::parse_test_flags; use protobuf::Message; diff --git a/tools/aconfig/src/main.rs b/tools/aconfig/aconfig/src/main.rs similarity index 97% rename from tools/aconfig/src/main.rs rename to tools/aconfig/aconfig/src/main.rs index 120e98caa7..30a7e9db92 100644 --- a/tools/aconfig/src/main.rs +++ b/tools/aconfig/aconfig/src/main.rs @@ -27,7 +27,6 @@ use std::path::{Path, PathBuf}; mod codegen; mod commands; mod dump; -mod protos; mod storage; use codegen::CodegenMode; @@ -57,8 +56,8 @@ fn cli() -> Command { .arg( Arg::new("default-permission") .long("default-permission") - .value_parser(protos::flag_permission::parse_from_str) - .default_value(protos::flag_permission::to_string( + .value_parser(aconfig_protos::flag_permission::parse_from_str) + .default_value(aconfig_protos::flag_permission::to_string( &commands::DEFAULT_FLAG_PERMISSION, )), ) @@ -215,7 +214,7 @@ fn main() -> Result<()> { let declarations = open_zero_or_more_files(sub_matches, "declarations")?; let values = open_zero_or_more_files(sub_matches, "values")?; let default_permission = - get_required_arg::(sub_matches, "default-permission")?; + get_required_arg::(sub_matches, "default-permission")?; let output = commands::parse_flags( package, container, diff --git a/tools/aconfig/src/storage/flag_table.rs b/tools/aconfig/aconfig/src/storage/flag_table.rs similarity index 100% rename from tools/aconfig/src/storage/flag_table.rs rename to tools/aconfig/aconfig/src/storage/flag_table.rs diff --git a/tools/aconfig/src/storage/flag_value.rs b/tools/aconfig/aconfig/src/storage/flag_value.rs similarity index 99% rename from tools/aconfig/src/storage/flag_value.rs rename to tools/aconfig/aconfig/src/storage/flag_value.rs index 45f5ec0c08..3c5bb17384 100644 --- a/tools/aconfig/src/storage/flag_value.rs +++ b/tools/aconfig/aconfig/src/storage/flag_value.rs @@ -15,7 +15,7 @@ */ use crate::commands::assign_flag_ids; -use crate::protos::ProtoFlagState; +use aconfig_protos::ProtoFlagState; use crate::storage::{self, FlagPackage}; use anyhow::{anyhow, Result}; diff --git a/tools/aconfig/src/storage/mod.rs b/tools/aconfig/aconfig/src/storage/mod.rs similarity index 98% rename from tools/aconfig/src/storage/mod.rs rename to tools/aconfig/aconfig/src/storage/mod.rs index b4a8b5e5aa..4f2dc81a03 100644 --- a/tools/aconfig/src/storage/mod.rs +++ b/tools/aconfig/aconfig/src/storage/mod.rs @@ -22,7 +22,7 @@ use anyhow::{anyhow, Result}; use std::collections::{hash_map::DefaultHasher, HashMap, HashSet}; use std::hash::{Hash, Hasher}; -use crate::protos::{ProtoParsedFlag, ProtoParsedFlags}; +use aconfig_protos::{ProtoParsedFlag, ProtoParsedFlags}; use crate::storage::{ flag_table::FlagTable, flag_value::FlagValueList, package_table::PackageTable, }; @@ -221,7 +221,7 @@ mod tests { crate::commands::DEFAULT_FLAG_PERMISSION, ) .unwrap(); - crate::protos::parsed_flags::try_from_binary_proto(&bytes).unwrap() + aconfig_protos::parsed_flags::try_from_binary_proto(&bytes).unwrap() }) .collect() } diff --git a/tools/aconfig/src/storage/package_table.rs b/tools/aconfig/aconfig/src/storage/package_table.rs similarity index 100% rename from tools/aconfig/src/storage/package_table.rs rename to tools/aconfig/aconfig/src/storage/package_table.rs diff --git a/tools/aconfig/src/test.rs b/tools/aconfig/aconfig/src/test.rs similarity index 97% rename from tools/aconfig/src/test.rs rename to tools/aconfig/aconfig/src/test.rs index cbb95b8ef0..7b5318d2e0 100644 --- a/tools/aconfig/src/test.rs +++ b/tools/aconfig/aconfig/src/test.rs @@ -17,7 +17,7 @@ #[cfg(test)] pub mod test_utils { use crate::commands::Input; - use crate::protos::ProtoParsedFlags; + use aconfig_protos::ProtoParsedFlags; use itertools; pub const TEST_PACKAGE: &str = "com.android.aconfig.test"; @@ -265,7 +265,7 @@ parsed_flag { crate::commands::DEFAULT_FLAG_PERMISSION, ) .unwrap(); - crate::protos::parsed_flags::try_from_binary_proto(&bytes).unwrap() + aconfig_protos::parsed_flags::try_from_binary_proto(&bytes).unwrap() } pub fn parse_test_flags() -> ProtoParsedFlags { @@ -289,7 +289,7 @@ parsed_flag { crate::commands::DEFAULT_FLAG_PERMISSION, ) .unwrap(); - crate::protos::parsed_flags::try_from_binary_proto(&bytes).unwrap() + aconfig_protos::parsed_flags::try_from_binary_proto(&bytes).unwrap() } pub fn first_significant_code_diff(a: &str, b: &str) -> Option { diff --git a/tools/aconfig/templates/FakeFeatureFlagsImpl.java.template b/tools/aconfig/aconfig/templates/FakeFeatureFlagsImpl.java.template similarity index 100% rename from tools/aconfig/templates/FakeFeatureFlagsImpl.java.template rename to tools/aconfig/aconfig/templates/FakeFeatureFlagsImpl.java.template diff --git a/tools/aconfig/templates/FeatureFlags.java.template b/tools/aconfig/aconfig/templates/FeatureFlags.java.template similarity index 100% rename from tools/aconfig/templates/FeatureFlags.java.template rename to tools/aconfig/aconfig/templates/FeatureFlags.java.template diff --git a/tools/aconfig/templates/FeatureFlagsImpl.java.template b/tools/aconfig/aconfig/templates/FeatureFlagsImpl.java.template similarity index 100% rename from tools/aconfig/templates/FeatureFlagsImpl.java.template rename to tools/aconfig/aconfig/templates/FeatureFlagsImpl.java.template diff --git a/tools/aconfig/templates/Flags.java.template b/tools/aconfig/aconfig/templates/Flags.java.template similarity index 100% rename from tools/aconfig/templates/Flags.java.template rename to tools/aconfig/aconfig/templates/Flags.java.template diff --git a/tools/aconfig/templates/cpp_exported_header.template b/tools/aconfig/aconfig/templates/cpp_exported_header.template similarity index 100% rename from tools/aconfig/templates/cpp_exported_header.template rename to tools/aconfig/aconfig/templates/cpp_exported_header.template diff --git a/tools/aconfig/templates/cpp_source_file.template b/tools/aconfig/aconfig/templates/cpp_source_file.template similarity index 100% rename from tools/aconfig/templates/cpp_source_file.template rename to tools/aconfig/aconfig/templates/cpp_source_file.template diff --git a/tools/aconfig/templates/rust.template b/tools/aconfig/aconfig/templates/rust.template similarity index 100% rename from tools/aconfig/templates/rust.template rename to tools/aconfig/aconfig/templates/rust.template diff --git a/tools/aconfig/templates/rust_test.template b/tools/aconfig/aconfig/templates/rust_test.template similarity index 100% rename from tools/aconfig/templates/rust_test.template rename to tools/aconfig/aconfig/templates/rust_test.template diff --git a/tools/aconfig/tests/AconfigHostTest.java b/tools/aconfig/aconfig/tests/AconfigHostTest.java similarity index 100% rename from tools/aconfig/tests/AconfigHostTest.java rename to tools/aconfig/aconfig/tests/AconfigHostTest.java diff --git a/tools/aconfig/tests/AconfigTest.java b/tools/aconfig/aconfig/tests/AconfigTest.java similarity index 100% rename from tools/aconfig/tests/AconfigTest.java rename to tools/aconfig/aconfig/tests/AconfigTest.java diff --git a/tools/aconfig/tests/AndroidManifest.xml b/tools/aconfig/aconfig/tests/AndroidManifest.xml similarity index 100% rename from tools/aconfig/tests/AndroidManifest.xml rename to tools/aconfig/aconfig/tests/AndroidManifest.xml diff --git a/tools/aconfig/tests/aconfig_exported_mode_test.cpp b/tools/aconfig/aconfig/tests/aconfig_exported_mode_test.cpp similarity index 100% rename from tools/aconfig/tests/aconfig_exported_mode_test.cpp rename to tools/aconfig/aconfig/tests/aconfig_exported_mode_test.cpp diff --git a/tools/aconfig/tests/aconfig_exported_mode_test.rs b/tools/aconfig/aconfig/tests/aconfig_exported_mode_test.rs similarity index 100% rename from tools/aconfig/tests/aconfig_exported_mode_test.rs rename to tools/aconfig/aconfig/tests/aconfig_exported_mode_test.rs diff --git a/tools/aconfig/tests/aconfig_force_read_only_mode_test.cpp b/tools/aconfig/aconfig/tests/aconfig_force_read_only_mode_test.cpp similarity index 100% rename from tools/aconfig/tests/aconfig_force_read_only_mode_test.cpp rename to tools/aconfig/aconfig/tests/aconfig_force_read_only_mode_test.cpp diff --git a/tools/aconfig/tests/aconfig_force_read_only_mode_test.rs b/tools/aconfig/aconfig/tests/aconfig_force_read_only_mode_test.rs similarity index 100% rename from tools/aconfig/tests/aconfig_force_read_only_mode_test.rs rename to tools/aconfig/aconfig/tests/aconfig_force_read_only_mode_test.rs diff --git a/tools/aconfig/tests/aconfig_prod_mode_test.rs b/tools/aconfig/aconfig/tests/aconfig_prod_mode_test.rs similarity index 100% rename from tools/aconfig/tests/aconfig_prod_mode_test.rs rename to tools/aconfig/aconfig/tests/aconfig_prod_mode_test.rs diff --git a/tools/aconfig/tests/aconfig_test.cpp b/tools/aconfig/aconfig/tests/aconfig_test.cpp similarity index 100% rename from tools/aconfig/tests/aconfig_test.cpp rename to tools/aconfig/aconfig/tests/aconfig_test.cpp diff --git a/tools/aconfig/tests/aconfig_test_mode_test.rs b/tools/aconfig/aconfig/tests/aconfig_test_mode_test.rs similarity index 100% rename from tools/aconfig/tests/aconfig_test_mode_test.rs rename to tools/aconfig/aconfig/tests/aconfig_test_mode_test.rs diff --git a/tools/aconfig/tests/aconfig_test_test_variant.cpp b/tools/aconfig/aconfig/tests/aconfig_test_test_variant.cpp similarity index 100% rename from tools/aconfig/tests/aconfig_test_test_variant.cpp rename to tools/aconfig/aconfig/tests/aconfig_test_test_variant.cpp diff --git a/tools/aconfig/tests/first.values b/tools/aconfig/aconfig/tests/first.values similarity index 100% rename from tools/aconfig/tests/first.values rename to tools/aconfig/aconfig/tests/first.values diff --git a/tools/aconfig/tests/read_only_test.aconfig b/tools/aconfig/aconfig/tests/read_only_test.aconfig similarity index 100% rename from tools/aconfig/tests/read_only_test.aconfig rename to tools/aconfig/aconfig/tests/read_only_test.aconfig diff --git a/tools/aconfig/tests/read_only_test.values b/tools/aconfig/aconfig/tests/read_only_test.values similarity index 100% rename from tools/aconfig/tests/read_only_test.values rename to tools/aconfig/aconfig/tests/read_only_test.values diff --git a/tools/aconfig/tests/second.values b/tools/aconfig/aconfig/tests/second.values similarity index 100% rename from tools/aconfig/tests/second.values rename to tools/aconfig/aconfig/tests/second.values diff --git a/tools/aconfig/tests/storage_test_1.aconfig b/tools/aconfig/aconfig/tests/storage_test_1.aconfig similarity index 100% rename from tools/aconfig/tests/storage_test_1.aconfig rename to tools/aconfig/aconfig/tests/storage_test_1.aconfig diff --git a/tools/aconfig/tests/storage_test_2.aconfig b/tools/aconfig/aconfig/tests/storage_test_2.aconfig similarity index 100% rename from tools/aconfig/tests/storage_test_2.aconfig rename to tools/aconfig/aconfig/tests/storage_test_2.aconfig diff --git a/tools/aconfig/tests/storage_test_4.aconfig b/tools/aconfig/aconfig/tests/storage_test_4.aconfig similarity index 100% rename from tools/aconfig/tests/storage_test_4.aconfig rename to tools/aconfig/aconfig/tests/storage_test_4.aconfig diff --git a/tools/aconfig/tests/test.aconfig b/tools/aconfig/aconfig/tests/test.aconfig similarity index 100% rename from tools/aconfig/tests/test.aconfig rename to tools/aconfig/aconfig/tests/test.aconfig diff --git a/tools/aconfig/tests/test_exported.aconfig b/tools/aconfig/aconfig/tests/test_exported.aconfig similarity index 100% rename from tools/aconfig/tests/test_exported.aconfig rename to tools/aconfig/aconfig/tests/test_exported.aconfig diff --git a/tools/aconfig/tests/test_force_read_only.aconfig b/tools/aconfig/aconfig/tests/test_force_read_only.aconfig similarity index 100% rename from tools/aconfig/tests/test_force_read_only.aconfig rename to tools/aconfig/aconfig/tests/test_force_read_only.aconfig diff --git a/tools/aconfig/aconfig_protos/Android.bp b/tools/aconfig/aconfig_protos/Android.bp new file mode 100644 index 0000000000..1cc4e419e2 --- /dev/null +++ b/tools/aconfig/aconfig_protos/Android.bp @@ -0,0 +1,62 @@ +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +// proto libraries for consumers of `aconfig dump --format=protobuf` output + +java_library { + name: "libaconfig_java_proto_lite", + host_supported: true, + srcs: ["protos/aconfig.proto"], + static_libs: ["libprotobuf-java-lite"], + proto: { + type: "lite", + }, + sdk_version: "current", + min_sdk_version: "UpsideDownCake", + apex_available: [ + "com.android.configinfrastructure", + "//apex_available:platform", + ] +} + +java_library_host { + name: "libaconfig_java_proto_full", + srcs: ["protos/aconfig.proto"], + static_libs: ["libprotobuf-java-full"], + proto: { + type: "full", + }, +} + +python_library_host { + name: "libaconfig_python_proto", + srcs: ["protos/aconfig.proto"], + proto: { + canonical_path_from_root: false, + }, +} + +rust_protobuf { + name: "libaconfig_rust_proto", + protos: ["protos/aconfig.proto"], + crate_name: "aconfig_rust_proto", + source_stem: "aconfig_rust_proto", + host_supported: true, +} + +rust_library { + name: "libaconfig_protos", + srcs: ["src/lib.rs"], + crate_name: "aconfig_protos", + host_supported: true, + lints: "none", + rustlibs: [ + "libaconfig_rust_proto", + "libanyhow", + "libprotobuf", + ], + proc_macros: [ + "libpaste", + ] +} diff --git a/tools/aconfig/aconfig_protos/Cargo.toml b/tools/aconfig/aconfig_protos/Cargo.toml new file mode 100644 index 0000000000..114cf80612 --- /dev/null +++ b/tools/aconfig/aconfig_protos/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "aconfig_protos" +version = "0.1.0" +edition = "2021" +build = "build.rs" + +[features] +default = ["cargo"] +cargo = [] + +[dependencies] +anyhow = "1.0.69" +paste = "1.0.11" +protobuf = "3.2.0" + +[build-dependencies] +protobuf-codegen = "3.2.0" diff --git a/tools/aconfig/build.rs b/tools/aconfig/aconfig_protos/build.rs similarity index 100% rename from tools/aconfig/build.rs rename to tools/aconfig/aconfig_protos/build.rs diff --git a/tools/aconfig/protos/aconfig.proto b/tools/aconfig/aconfig_protos/protos/aconfig.proto similarity index 100% rename from tools/aconfig/protos/aconfig.proto rename to tools/aconfig/aconfig_protos/protos/aconfig.proto diff --git a/tools/aconfig/src/protos.rs b/tools/aconfig/aconfig_protos/src/lib.rs similarity index 92% rename from tools/aconfig/src/protos.rs rename to tools/aconfig/aconfig_protos/src/lib.rs index 2684d20278..f0d27d6d47 100644 --- a/tools/aconfig/src/protos.rs +++ b/tools/aconfig/aconfig_protos/src/lib.rs @@ -29,17 +29,17 @@ // ---- When building with the Android tool-chain ---- #[cfg(not(feature = "cargo"))] mod auto_generated { - pub use aconfig_protos::aconfig::flag_metadata::Flag_purpose as ProtoFlagPurpose; - pub use aconfig_protos::aconfig::Flag_declaration as ProtoFlagDeclaration; - pub use aconfig_protos::aconfig::Flag_declarations as ProtoFlagDeclarations; - pub use aconfig_protos::aconfig::Flag_metadata as ProtoFlagMetadata; - pub use aconfig_protos::aconfig::Flag_permission as ProtoFlagPermission; - pub use aconfig_protos::aconfig::Flag_state as ProtoFlagState; - pub use aconfig_protos::aconfig::Flag_value as ProtoFlagValue; - pub use aconfig_protos::aconfig::Flag_values as ProtoFlagValues; - pub use aconfig_protos::aconfig::Parsed_flag as ProtoParsedFlag; - pub use aconfig_protos::aconfig::Parsed_flags as ProtoParsedFlags; - pub use aconfig_protos::aconfig::Tracepoint as ProtoTracepoint; + pub use aconfig_rust_proto::aconfig::flag_metadata::Flag_purpose as ProtoFlagPurpose; + pub use aconfig_rust_proto::aconfig::Flag_declaration as ProtoFlagDeclaration; + pub use aconfig_rust_proto::aconfig::Flag_declarations as ProtoFlagDeclarations; + pub use aconfig_rust_proto::aconfig::Flag_metadata as ProtoFlagMetadata; + pub use aconfig_rust_proto::aconfig::Flag_permission as ProtoFlagPermission; + pub use aconfig_rust_proto::aconfig::Flag_state as ProtoFlagState; + pub use aconfig_rust_proto::aconfig::Flag_value as ProtoFlagValue; + pub use aconfig_rust_proto::aconfig::Flag_values as ProtoFlagValues; + pub use aconfig_rust_proto::aconfig::Parsed_flag as ProtoParsedFlag; + pub use aconfig_rust_proto::aconfig::Parsed_flags as ProtoParsedFlags; + pub use aconfig_rust_proto::aconfig::Tracepoint as ProtoTracepoint; } // ---- When building with cargo ---- @@ -68,6 +68,32 @@ pub use auto_generated::*; use anyhow::Result; use paste::paste; +pub fn is_valid_name_ident(s: &str) -> bool { + // Identifiers must match [a-z][a-z0-9_]*, except consecutive underscores are not allowed + if s.contains("__") { + return false; + } + let mut chars = s.chars(); + let Some(first) = chars.next() else { + return false; + }; + if !first.is_ascii_lowercase() { + return false; + } + chars.all(|ch| ch.is_ascii_lowercase() || ch.is_ascii_digit() || ch == '_') +} + +pub fn is_valid_package_ident(s: &str) -> bool { + if !s.contains('.') { + return false; + } + s.split('.').all(is_valid_name_ident) +} + +pub fn is_valid_container_ident(s: &str) -> bool { + s.split('.').all(is_valid_name_ident) +} + fn try_from_text_proto(s: &str) -> Result where T: protobuf::MessageFull, @@ -87,14 +113,13 @@ macro_rules! ensure_required_fields { pub mod flag_declaration { use super::*; - use crate::codegen; use anyhow::ensure; pub fn verify_fields(pdf: &ProtoFlagDeclaration) -> Result<()> { ensure_required_fields!("flag declaration", pdf, "name", "namespace", "description"); - ensure!(codegen::is_valid_name_ident(pdf.name()), "bad flag declaration: bad name"); - ensure!(codegen::is_valid_name_ident(pdf.namespace()), "bad flag declaration: bad name"); + ensure!(is_valid_name_ident(pdf.name()), "bad flag declaration: bad name"); + ensure!(is_valid_name_ident(pdf.namespace()), "bad flag declaration: bad name"); ensure!(!pdf.description().is_empty(), "bad flag declaration: empty description"); ensure!(pdf.bug.len() == 1, "bad flag declaration: exactly one bug required"); @@ -104,7 +129,6 @@ pub mod flag_declaration { pub mod flag_declarations { use super::*; - use crate::codegen; use anyhow::ensure; pub fn try_from_text_proto(s: &str) -> Result { @@ -118,11 +142,11 @@ pub mod flag_declarations { // TODO(b/312769710): Make the container field required. ensure!( - codegen::is_valid_package_ident(pdf.package()), + is_valid_package_ident(pdf.package()), "bad flag declarations: bad package" ); ensure!( - !pdf.has_container() || codegen::is_valid_container_ident(pdf.container()), + !pdf.has_container() || is_valid_container_ident(pdf.container()), "bad flag declarations: bad container" ); for flag_declaration in pdf.flag.iter() { @@ -135,14 +159,13 @@ pub mod flag_declarations { pub mod flag_value { use super::*; - use crate::codegen; use anyhow::ensure; pub fn verify_fields(fv: &ProtoFlagValue) -> Result<()> { ensure_required_fields!("flag value", fv, "package", "name", "state", "permission"); - ensure!(codegen::is_valid_package_ident(fv.package()), "bad flag value: bad package"); - ensure!(codegen::is_valid_name_ident(fv.name()), "bad flag value: bad name"); + ensure!(is_valid_package_ident(fv.package()), "bad flag value: bad package"); + ensure!(is_valid_name_ident(fv.name()), "bad flag value: bad name"); Ok(()) } @@ -200,7 +223,6 @@ pub mod tracepoint { pub mod parsed_flag { use super::*; - use crate::codegen; use anyhow::ensure; pub fn verify_fields(pf: &ProtoParsedFlag) -> Result<()> { @@ -215,13 +237,13 @@ pub mod parsed_flag { "permission" ); - ensure!(codegen::is_valid_package_ident(pf.package()), "bad parsed flag: bad package"); + ensure!(is_valid_package_ident(pf.package()), "bad parsed flag: bad package"); ensure!( - !pf.has_container() || codegen::is_valid_container_ident(pf.container()), + !pf.has_container() || is_valid_container_ident(pf.container()), "bad parsed flag: bad container" ); - ensure!(codegen::is_valid_name_ident(pf.name()), "bad parsed flag: bad name"); - ensure!(codegen::is_valid_name_ident(pf.namespace()), "bad parsed flag: bad namespace"); + ensure!(is_valid_name_ident(pf.name()), "bad parsed flag: bad name"); + ensure!(is_valid_name_ident(pf.namespace()), "bad parsed flag: bad namespace"); ensure!(!pf.description().is_empty(), "bad parsed flag: empty description"); ensure!(!pf.trace.is_empty(), "bad parsed flag: empty trace"); for tp in pf.trace.iter() { @@ -261,7 +283,7 @@ pub mod parsed_flags { } pub fn verify_fields(pf: &ProtoParsedFlags) -> Result<()> { - use crate::protos::parsed_flag::path_to_declaration; + use crate::parsed_flag::path_to_declaration; let mut previous: Option<&ProtoParsedFlag> = None; for parsed_flag in pf.parsed_flag.iter() { @@ -848,7 +870,7 @@ parsed_flag { let parsed_flags = try_from_binary_proto_from_text_proto(text_proto).unwrap(); let parsed_flag = &parsed_flags.parsed_flag[0]; assert_eq!( - crate::protos::parsed_flag::path_to_declaration(parsed_flag), + crate::parsed_flag::path_to_declaration(parsed_flag), "flags.declarations" ); } diff --git a/tools/aconfig/printflags/Cargo.toml b/tools/aconfig/printflags/Cargo.toml new file mode 100644 index 0000000000..7313f5d044 --- /dev/null +++ b/tools/aconfig/printflags/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "printflags" +version = "0.1.0" +edition = "2021" + +[features] +default = ["cargo"] +cargo = [] + +[dependencies] +anyhow = "1.0.69" +paste = "1.0.11" +protobuf = "3.2.0" +regex = "1.10.3" +aconfig_protos = { path = "../aconfig_protos" } diff --git a/tools/aconfig/printflags/src/main.rs b/tools/aconfig/printflags/src/main.rs index ae9b83aae6..7fcde61273 100644 --- a/tools/aconfig/printflags/src/main.rs +++ b/tools/aconfig/printflags/src/main.rs @@ -16,8 +16,8 @@ //! `printflags` is a device binary to print feature flags. -use aconfig_protos::aconfig::Flag_state as State; -use aconfig_protos::aconfig::Parsed_flags as ProtoParsedFlags; +use aconfig_protos::ProtoFlagState as State; +use aconfig_protos::ProtoParsedFlags as ProtoParsedFlags; use anyhow::{bail, Context, Result}; use regex::Regex; use std::collections::BTreeMap;