Merge changes from topic "aconfig-rename-namespace-to-package" am: ec5da68dbd

Original change: https://android-review.googlesource.com/c/platform/build/+/2617393

Change-Id: Ia24fee81a09ad3dd8bbecabfab38359b1d6cceb5
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Mårten Kongstad 2023-06-08 16:13:24 +00:00 committed by Automerger Merge Worker
commit 5c4398ba52
18 changed files with 425 additions and 211 deletions

View file

@ -35,4 +35,7 @@ rust_binary_host {
rust_test_host { rust_test_host {
name: "aconfig.test", name: "aconfig.test",
defaults: ["aconfig.defaults"], defaults: ["aconfig.defaults"],
rustlibs: [
"libitertools",
],
} }

View file

@ -18,3 +18,6 @@ tinytemplate = "1.2.1"
[build-dependencies] [build-dependencies]
protobuf-codegen = "3.2.0" protobuf-codegen = "3.2.0"
[dev-dependencies]
itertools = "0.10.5"

View file

@ -36,16 +36,17 @@ enum flag_permission {
message flag_declaration { message flag_declaration {
required string name = 1; required string name = 1;
required string description = 2; required string namespace = 2;
required string description = 3;
}; };
message flag_declarations { message flag_declarations {
required string namespace = 1; required string package = 1;
repeated flag_declaration flag = 2; repeated flag_declaration flag = 2;
}; };
message flag_value { message flag_value {
required string namespace = 1; required string package = 1;
required string name = 2; required string name = 2;
required flag_state state = 3; required flag_state state = 3;
required flag_permission permission = 4; required flag_permission permission = 4;
@ -65,12 +66,13 @@ message tracepoint {
} }
message parsed_flag { message parsed_flag {
required string namespace = 1; required string package = 1;
required string name = 2; required string name = 2;
required string description = 3; required string namespace = 3;
required flag_state state = 4; required string description = 4;
required flag_permission permission = 5; required flag_state state = 5;
repeated tracepoint trace = 6; required flag_permission permission = 6;
repeated tracepoint trace = 7;
} }
message parsed_flags { message parsed_flags {

View file

@ -81,6 +81,7 @@ impl From<Permission> for ProtoFlagPermission {
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct FlagDeclaration { pub struct FlagDeclaration {
pub name: String, pub name: String,
pub namespace: String,
pub description: String, pub description: String,
} }
@ -100,16 +101,19 @@ impl TryFrom<ProtoFlagDeclaration> for FlagDeclaration {
let Some(name) = proto.name else { let Some(name) = proto.name else {
bail!("missing 'name' field"); bail!("missing 'name' field");
}; };
let Some(namespace) = proto.namespace else {
bail!("missing 'namespace' field");
};
let Some(description) = proto.description else { let Some(description) = proto.description else {
bail!("missing 'description' field"); bail!("missing 'description' field");
}; };
Ok(FlagDeclaration { name, description }) Ok(FlagDeclaration { name, namespace, description })
} }
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct FlagDeclarations { pub struct FlagDeclarations {
pub namespace: String, pub package: String,
pub flags: Vec<FlagDeclaration>, pub flags: Vec<FlagDeclaration>,
} }
@ -117,20 +121,20 @@ impl FlagDeclarations {
pub fn try_from_text_proto(text_proto: &str) -> Result<FlagDeclarations> { pub fn try_from_text_proto(text_proto: &str) -> Result<FlagDeclarations> {
let proto: ProtoFlagDeclarations = crate::protos::try_from_text_proto(text_proto) let proto: ProtoFlagDeclarations = crate::protos::try_from_text_proto(text_proto)
.with_context(|| text_proto.to_owned())?; .with_context(|| text_proto.to_owned())?;
let Some(namespace) = proto.namespace else { let Some(package) = proto.package else {
bail!("missing 'namespace' field"); bail!("missing 'package' field");
}; };
let mut flags = vec![]; let mut flags = vec![];
for proto_flag in proto.flag.into_iter() { for proto_flag in proto.flag.into_iter() {
flags.push(proto_flag.try_into()?); flags.push(proto_flag.try_into()?);
} }
Ok(FlagDeclarations { namespace, flags }) Ok(FlagDeclarations { package, flags })
} }
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct FlagValue { pub struct FlagValue {
pub namespace: String, pub package: String,
pub name: String, pub name: String,
pub state: FlagState, pub state: FlagState,
pub permission: Permission, pub permission: Permission,
@ -153,8 +157,8 @@ impl TryFrom<ProtoFlagValue> for FlagValue {
type Error = Error; type Error = Error;
fn try_from(proto: ProtoFlagValue) -> Result<Self, Self::Error> { fn try_from(proto: ProtoFlagValue) -> Result<Self, Self::Error> {
let Some(namespace) = proto.namespace else { let Some(package) = proto.package else {
bail!("missing 'namespace' field"); bail!("missing 'package' field");
}; };
let Some(name) = proto.name else { let Some(name) = proto.name else {
bail!("missing 'name' field"); bail!("missing 'name' field");
@ -167,7 +171,7 @@ impl TryFrom<ProtoFlagValue> for FlagValue {
bail!("missing 'permission' field"); bail!("missing 'permission' field");
}; };
let permission = proto_permission.try_into()?; let permission = proto_permission.try_into()?;
Ok(FlagValue { namespace, name, state, permission }) Ok(FlagValue { package, name, state, permission })
} }
} }
@ -184,8 +188,9 @@ impl From<Cache> for ProtoParsedFlags {
impl From<Item> for ProtoParsedFlag { impl From<Item> for ProtoParsedFlag {
fn from(item: Item) -> Self { fn from(item: Item) -> Self {
let mut proto = crate::protos::ProtoParsedFlag::new(); let mut proto = crate::protos::ProtoParsedFlag::new();
proto.set_namespace(item.namespace.to_owned()); proto.set_package(item.package.to_owned());
proto.set_name(item.name.clone()); proto.set_name(item.name.clone());
proto.set_namespace(item.namespace.clone());
proto.set_description(item.description.clone()); proto.set_description(item.description.clone());
proto.set_state(item.state.into()); proto.set_state(item.state.into());
proto.set_permission(item.permission.into()); proto.set_permission(item.permission.into());
@ -214,11 +219,13 @@ mod tests {
fn test_flag_try_from_text_proto() { fn test_flag_try_from_text_proto() {
let expected = FlagDeclaration { let expected = FlagDeclaration {
name: "1234".to_owned(), name: "1234".to_owned(),
namespace: "ns".to_owned(),
description: "Description of the flag".to_owned(), description: "Description of the flag".to_owned(),
}; };
let s = r#" let s = r#"
name: "1234" name: "1234"
namespace: "ns"
description: "Description of the flag" description: "Description of the flag"
"#; "#;
let actual = FlagDeclaration::try_from_text_proto(s).unwrap(); let actual = FlagDeclaration::try_from_text_proto(s).unwrap();
@ -242,23 +249,33 @@ mod tests {
} }
#[test] #[test]
fn test_namespace_try_from_text_proto() { fn test_package_try_from_text_proto() {
let expected = FlagDeclarations { let expected = FlagDeclarations {
namespace: "ns".to_owned(), package: "com.example".to_owned(),
flags: vec![ flags: vec![
FlagDeclaration { name: "a".to_owned(), description: "A".to_owned() }, FlagDeclaration {
FlagDeclaration { name: "b".to_owned(), description: "B".to_owned() }, name: "a".to_owned(),
namespace: "ns".to_owned(),
description: "A".to_owned(),
},
FlagDeclaration {
name: "b".to_owned(),
namespace: "ns".to_owned(),
description: "B".to_owned(),
},
], ],
}; };
let s = r#" let s = r#"
namespace: "ns" package: "com.example"
flag { flag {
name: "a" name: "a"
namespace: "ns"
description: "A" description: "A"
} }
flag { flag {
name: "b" name: "b"
namespace: "ns"
description: "B" description: "B"
} }
"#; "#;
@ -270,14 +287,14 @@ mod tests {
#[test] #[test]
fn test_flag_declaration_try_from_text_proto_list() { fn test_flag_declaration_try_from_text_proto_list() {
let expected = FlagValue { let expected = FlagValue {
namespace: "ns".to_owned(), package: "com.example".to_owned(),
name: "1234".to_owned(), name: "1234".to_owned(),
state: FlagState::Enabled, state: FlagState::Enabled,
permission: Permission::ReadOnly, permission: Permission::ReadOnly,
}; };
let s = r#" let s = r#"
namespace: "ns" package: "com.example"
name: "1234" name: "1234"
state: ENABLED state: ENABLED
permission: READ_ONLY permission: READ_ONLY

View file

@ -34,12 +34,13 @@ pub struct Tracepoint {
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct Item { pub struct Item {
// TODO: duplicating the Cache.namespace as Item.namespace makes the internal representation // TODO: duplicating the Cache.package as Item.package makes the internal representation
// closer to the proto message `parsed_flag`; hopefully this will enable us to replace the Item // closer to the proto message `parsed_flag`; hopefully this will enable us to replace the Item
// struct and use a newtype instead once aconfig has matured. Until then, namespace should // struct and use a newtype instead once aconfig has matured. Until then, package should
// really be a Cow<String>. // really be a Cow<String>.
pub namespace: String, pub package: String,
pub name: String, pub name: String,
pub namespace: String,
pub description: String, pub description: String,
pub state: FlagState, pub state: FlagState,
pub permission: Permission, pub permission: Permission,
@ -48,7 +49,7 @@ pub struct Item {
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct Cache { pub struct Cache {
namespace: String, package: String,
items: Vec<Item>, items: Vec<Item>,
} }
@ -96,9 +97,9 @@ impl Cache {
self.items.into_iter() self.items.into_iter()
} }
pub fn namespace(&self) -> &str { pub fn package(&self) -> &str {
debug_assert!(!self.namespace.is_empty()); debug_assert!(!self.package.is_empty());
&self.namespace &self.package
} }
} }
@ -108,9 +109,9 @@ pub struct CacheBuilder {
} }
impl CacheBuilder { impl CacheBuilder {
pub fn new(namespace: String) -> Result<CacheBuilder> { pub fn new(package: String) -> Result<CacheBuilder> {
ensure!(codegen::is_valid_identifier(&namespace), "bad namespace"); ensure!(codegen::is_valid_package_ident(&package), "bad package");
let cache = Cache { namespace, items: vec![] }; let cache = Cache { package, items: vec![] };
Ok(CacheBuilder { cache }) Ok(CacheBuilder { cache })
} }
@ -119,7 +120,8 @@ impl CacheBuilder {
source: Source, source: Source,
declaration: FlagDeclaration, declaration: FlagDeclaration,
) -> Result<&mut CacheBuilder> { ) -> Result<&mut CacheBuilder> {
ensure!(codegen::is_valid_identifier(&declaration.name), "bad flag name"); ensure!(codegen::is_valid_name_ident(&declaration.name), "bad flag name");
ensure!(codegen::is_valid_name_ident(&declaration.namespace), "bad namespace");
ensure!(!declaration.description.is_empty(), "empty flag description"); ensure!(!declaration.description.is_empty(), "empty flag description");
ensure!( ensure!(
self.cache.items.iter().all(|item| item.name != declaration.name), self.cache.items.iter().all(|item| item.name != declaration.name),
@ -128,8 +130,9 @@ impl CacheBuilder {
source source
); );
self.cache.items.push(Item { self.cache.items.push(Item {
namespace: self.cache.namespace.clone(), package: self.cache.package.clone(),
name: declaration.name.clone(), name: declaration.name.clone(),
namespace: declaration.namespace.clone(),
description: declaration.description, description: declaration.description,
state: DEFAULT_FLAG_STATE, state: DEFAULT_FLAG_STATE,
permission: DEFAULT_FLAG_PERMISSION, permission: DEFAULT_FLAG_PERMISSION,
@ -147,18 +150,18 @@ impl CacheBuilder {
source: Source, source: Source,
value: FlagValue, value: FlagValue,
) -> Result<&mut CacheBuilder> { ) -> Result<&mut CacheBuilder> {
ensure!(codegen::is_valid_identifier(&value.namespace), "bad flag namespace"); ensure!(codegen::is_valid_package_ident(&value.package), "bad flag package");
ensure!(codegen::is_valid_identifier(&value.name), "bad flag name"); ensure!(codegen::is_valid_name_ident(&value.name), "bad flag name");
ensure!( ensure!(
value.namespace == self.cache.namespace, value.package == self.cache.package,
"failed to set values for flag {}/{} from {}: expected namespace {}", "failed to set values for flag {}/{} from {}: expected package {}",
value.namespace, value.package,
value.name, value.name,
source, source,
self.cache.namespace self.cache.package
); );
let Some(existing_item) = self.cache.items.iter_mut().find(|item| item.name == value.name) else { let Some(existing_item) = self.cache.items.iter_mut().find(|item| item.name == value.name) else {
bail!("failed to set values for flag {}/{} from {}: flag not declared", value.namespace, value.name, source); bail!("failed to set values for flag {}/{} from {}: flag not declared", value.package, value.name, source);
}; };
existing_item.state = value.state; existing_item.state = value.state;
existing_item.permission = value.permission; existing_item.permission = value.permission;
@ -182,17 +185,25 @@ mod tests {
#[test] #[test]
fn test_add_flag_declaration() { fn test_add_flag_declaration() {
let mut builder = CacheBuilder::new("ns".to_string()).unwrap(); let mut builder = CacheBuilder::new("com.example".to_string()).unwrap();
builder builder
.add_flag_declaration( .add_flag_declaration(
Source::File("first.txt".to_string()), Source::File("first.txt".to_string()),
FlagDeclaration { name: "foo".to_string(), description: "desc".to_string() }, FlagDeclaration {
name: "foo".to_string(),
namespace: "ns".to_string(),
description: "desc".to_string(),
},
) )
.unwrap(); .unwrap();
let error = builder let error = builder
.add_flag_declaration( .add_flag_declaration(
Source::File("second.txt".to_string()), Source::File("second.txt".to_string()),
FlagDeclaration { name: "foo".to_string(), description: "desc".to_string() }, FlagDeclaration {
name: "foo".to_string(),
namespace: "ns".to_string(),
description: "desc".to_string(),
},
) )
.unwrap_err(); .unwrap_err();
assert_eq!( assert_eq!(
@ -202,7 +213,11 @@ mod tests {
builder builder
.add_flag_declaration( .add_flag_declaration(
Source::File("first.txt".to_string()), Source::File("first.txt".to_string()),
FlagDeclaration { name: "bar".to_string(), description: "desc".to_string() }, FlagDeclaration {
name: "bar".to_string(),
namespace: "ns".to_string(),
description: "desc".to_string(),
},
) )
.unwrap(); .unwrap();
@ -217,12 +232,12 @@ mod tests {
#[test] #[test]
fn test_add_flag_value() { fn test_add_flag_value() {
let mut builder = CacheBuilder::new("ns".to_string()).unwrap(); let mut builder = CacheBuilder::new("com.example".to_string()).unwrap();
let error = builder let error = builder
.add_flag_value( .add_flag_value(
Source::Memory, Source::Memory,
FlagValue { FlagValue {
namespace: "ns".to_string(), package: "com.example".to_string(),
name: "foo".to_string(), name: "foo".to_string(),
state: FlagState::Enabled, state: FlagState::Enabled,
permission: Permission::ReadOnly, permission: Permission::ReadOnly,
@ -231,13 +246,17 @@ mod tests {
.unwrap_err(); .unwrap_err();
assert_eq!( assert_eq!(
&format!("{:?}", error), &format!("{:?}", error),
"failed to set values for flag ns/foo from <memory>: flag not declared" "failed to set values for flag com.example/foo from <memory>: flag not declared"
); );
builder builder
.add_flag_declaration( .add_flag_declaration(
Source::File("first.txt".to_string()), Source::File("first.txt".to_string()),
FlagDeclaration { name: "foo".to_string(), description: "desc".to_string() }, FlagDeclaration {
name: "foo".to_string(),
namespace: "ns".to_string(),
description: "desc".to_string(),
},
) )
.unwrap(); .unwrap();
@ -245,7 +264,7 @@ mod tests {
.add_flag_value( .add_flag_value(
Source::Memory, Source::Memory,
FlagValue { FlagValue {
namespace: "ns".to_string(), package: "com.example".to_string(),
name: "foo".to_string(), name: "foo".to_string(),
state: FlagState::Disabled, state: FlagState::Disabled,
permission: Permission::ReadOnly, permission: Permission::ReadOnly,
@ -257,7 +276,7 @@ mod tests {
.add_flag_value( .add_flag_value(
Source::Memory, Source::Memory,
FlagValue { FlagValue {
namespace: "ns".to_string(), package: "com.example".to_string(),
name: "foo".to_string(), name: "foo".to_string(),
state: FlagState::Enabled, state: FlagState::Enabled,
permission: Permission::ReadWrite, permission: Permission::ReadWrite,
@ -265,19 +284,19 @@ mod tests {
) )
.unwrap(); .unwrap();
// different namespace -> no-op // different package -> no-op
let error = builder let error = builder
.add_flag_value( .add_flag_value(
Source::Memory, Source::Memory,
FlagValue { FlagValue {
namespace: "some_other_namespace".to_string(), package: "some_other_package".to_string(),
name: "foo".to_string(), name: "foo".to_string(),
state: FlagState::Enabled, state: FlagState::Enabled,
permission: Permission::ReadOnly, permission: Permission::ReadOnly,
}, },
) )
.unwrap_err(); .unwrap_err();
assert_eq!(&format!("{:?}", error), "failed to set values for flag some_other_namespace/foo from <memory>: expected namespace ns"); assert_eq!(&format!("{:?}", error), "failed to set values for flag some_other_package/foo from <memory>: expected package com.example");
let cache = builder.build(); let cache = builder.build();
let item = cache.iter().find(|&item| item.name == "foo").unwrap(); let item = cache.iter().find(|&item| item.name == "foo").unwrap();
@ -286,18 +305,22 @@ mod tests {
} }
#[test] #[test]
fn test_reject_empty_cache_namespace() { fn test_reject_empty_cache_package() {
CacheBuilder::new("".to_string()).unwrap_err(); CacheBuilder::new("".to_string()).unwrap_err();
} }
#[test] #[test]
fn test_reject_empty_flag_declaration_fields() { fn test_reject_empty_flag_declaration_fields() {
let mut builder = CacheBuilder::new("ns".to_string()).unwrap(); let mut builder = CacheBuilder::new("com.example".to_string()).unwrap();
let error = builder let error = builder
.add_flag_declaration( .add_flag_declaration(
Source::Memory, Source::Memory,
FlagDeclaration { name: "".to_string(), description: "Description".to_string() }, FlagDeclaration {
name: "".to_string(),
namespace: "ns".to_string(),
description: "Description".to_string(),
},
) )
.unwrap_err(); .unwrap_err();
assert_eq!(&format!("{:?}", error), "bad flag name"); assert_eq!(&format!("{:?}", error), "bad flag name");
@ -305,7 +328,11 @@ mod tests {
let error = builder let error = builder
.add_flag_declaration( .add_flag_declaration(
Source::Memory, Source::Memory,
FlagDeclaration { name: "foo".to_string(), description: "".to_string() }, FlagDeclaration {
name: "foo".to_string(),
namespace: "ns".to_string(),
description: "".to_string(),
},
) )
.unwrap_err(); .unwrap_err();
assert_eq!(&format!("{:?}", error), "empty flag description"); assert_eq!(&format!("{:?}", error), "empty flag description");
@ -313,11 +340,15 @@ mod tests {
#[test] #[test]
fn test_reject_empty_flag_value_files() { fn test_reject_empty_flag_value_files() {
let mut builder = CacheBuilder::new("ns".to_string()).unwrap(); let mut builder = CacheBuilder::new("com.example".to_string()).unwrap();
builder builder
.add_flag_declaration( .add_flag_declaration(
Source::Memory, Source::Memory,
FlagDeclaration { name: "foo".to_string(), description: "desc".to_string() }, FlagDeclaration {
name: "foo".to_string(),
namespace: "ns".to_string(),
description: "desc".to_string(),
},
) )
.unwrap(); .unwrap();
@ -325,20 +356,20 @@ mod tests {
.add_flag_value( .add_flag_value(
Source::Memory, Source::Memory,
FlagValue { FlagValue {
namespace: "".to_string(), package: "".to_string(),
name: "foo".to_string(), name: "foo".to_string(),
state: FlagState::Enabled, state: FlagState::Enabled,
permission: Permission::ReadOnly, permission: Permission::ReadOnly,
}, },
) )
.unwrap_err(); .unwrap_err();
assert_eq!(&format!("{:?}", error), "bad flag namespace"); assert_eq!(&format!("{:?}", error), "bad flag package");
let error = builder let error = builder
.add_flag_value( .add_flag_value(
Source::Memory, Source::Memory,
FlagValue { FlagValue {
namespace: "ns".to_string(), package: "com.example".to_string(),
name: "".to_string(), name: "".to_string(),
state: FlagState::Enabled, state: FlagState::Enabled,
permission: Permission::ReadOnly, permission: Permission::ReadOnly,

View file

@ -14,7 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
pub fn is_valid_identifier(s: &str) -> bool { use anyhow::{ensure, Result};
pub fn is_valid_name_ident(s: &str) -> bool {
// Identifiers must match [a-z][a-z0-9_]* // Identifiers must match [a-z][a-z0-9_]*
let mut chars = s.chars(); let mut chars = s.chars();
let Some(first) = chars.next() else { let Some(first) = chars.next() else {
@ -26,18 +28,54 @@ pub fn is_valid_identifier(s: &str) -> bool {
chars.all(|ch| ch.is_ascii_lowercase() || ch.is_ascii_digit() || ch == '_') chars.all(|ch| ch.is_ascii_lowercase() || ch.is_ascii_digit() || ch == '_')
} }
pub fn is_valid_package_ident(s: &str) -> bool {
s.split('.').all(is_valid_name_ident)
}
pub fn create_device_config_ident(package: &str, flag_name: &str) -> Result<String> {
ensure!(is_valid_package_ident(package), "bad package");
ensure!(is_valid_package_ident(flag_name), "bad flag name");
Ok(format!("{}.{}", package, flag_name))
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
#[test] #[test]
fn test_is_valid_identifier() { fn test_is_valid_name_ident() {
assert!(is_valid_identifier("foo")); assert!(is_valid_name_ident("foo"));
assert!(is_valid_identifier("foo_bar_123")); assert!(is_valid_name_ident("foo_bar_123"));
assert!(!is_valid_identifier("")); assert!(!is_valid_name_ident(""));
assert!(!is_valid_identifier("123_foo")); assert!(!is_valid_name_ident("123_foo"));
assert!(!is_valid_identifier("foo-bar")); assert!(!is_valid_name_ident("foo-bar"));
assert!(!is_valid_identifier("foo-b\u{00e5}r")); assert!(!is_valid_name_ident("foo-b\u{00e5}r"));
}
#[test]
fn test_is_valid_package_ident() {
assert!(is_valid_package_ident("foo"));
assert!(is_valid_package_ident("foo_bar_123"));
assert!(is_valid_package_ident("foo.bar"));
assert!(is_valid_package_ident("foo.bar.a123"));
assert!(!is_valid_package_ident(""));
assert!(!is_valid_package_ident("123_foo"));
assert!(!is_valid_package_ident("foo-bar"));
assert!(!is_valid_package_ident("foo-b\u{00e5}r"));
assert!(!is_valid_package_ident("foo.bar.123"));
assert!(!is_valid_package_ident(".foo.bar"));
assert!(!is_valid_package_ident("foo.bar."));
assert!(!is_valid_package_ident("."));
assert!(!is_valid_package_ident("foo..bar"));
}
#[test]
fn test_create_device_config_ident() {
assert_eq!(
"com.foo.bar.some_flag",
create_device_config_ident("com.foo.bar", "some_flag").unwrap()
);
} }
} }

View file

@ -14,29 +14,42 @@
* limitations under the License. * limitations under the License.
*/ */
use anyhow::Result; use anyhow::{ensure, Result};
use serde::Serialize; use serde::Serialize;
use tinytemplate::TinyTemplate; use tinytemplate::TinyTemplate;
use crate::aconfig::{FlagState, Permission}; use crate::aconfig::{FlagState, Permission};
use crate::cache::{Cache, Item}; use crate::cache::{Cache, Item};
use crate::codegen;
use crate::commands::OutputFile; use crate::commands::OutputFile;
pub fn generate_cpp_code(cache: &Cache) -> Result<OutputFile> { pub fn generate_cpp_code(cache: &Cache) -> Result<OutputFile> {
let class_elements: Vec<ClassElement> = cache.iter().map(create_class_element).collect(); let package = cache.package();
let class_elements: Vec<ClassElement> =
cache.iter().map(|item| create_class_element(package, item)).collect();
let readwrite = class_elements.iter().any(|item| item.readwrite); let readwrite = class_elements.iter().any(|item| item.readwrite);
let namespace = cache.namespace().to_lowercase(); let header = package.replace('.', "_");
let context = Context { namespace: namespace.clone(), readwrite, class_elements }; let cpp_namespace = package.replace('.', "::");
ensure!(codegen::is_valid_name_ident(&header));
let context = Context {
header: header.clone(),
cpp_namespace,
package: package.to_string(),
readwrite,
class_elements,
};
let mut template = TinyTemplate::new(); let mut template = TinyTemplate::new();
template.add_template("cpp_code_gen", include_str!("../templates/cpp.template"))?; template.add_template("cpp_code_gen", include_str!("../templates/cpp.template"))?;
let contents = template.render("cpp_code_gen", &context)?; let contents = template.render("cpp_code_gen", &context)?;
let path = ["aconfig", &(namespace + ".h")].iter().collect(); let path = ["aconfig", &(header + ".h")].iter().collect();
Ok(OutputFile { contents: contents.into(), path }) Ok(OutputFile { contents: contents.into(), path })
} }
#[derive(Serialize)] #[derive(Serialize)]
struct Context { struct Context {
pub namespace: String, pub header: String,
pub cpp_namespace: String,
pub package: String,
pub readwrite: bool, pub readwrite: bool,
pub class_elements: Vec<ClassElement>, pub class_elements: Vec<ClassElement>,
} }
@ -46,9 +59,11 @@ struct ClassElement {
pub readwrite: bool, pub readwrite: bool,
pub default_value: String, pub default_value: String,
pub flag_name: String, pub flag_name: String,
pub device_config_namespace: String,
pub device_config_flag: String,
} }
fn create_class_element(item: &Item) -> ClassElement { fn create_class_element(package: &str, item: &Item) -> ClassElement {
ClassElement { ClassElement {
readwrite: item.permission == Permission::ReadWrite, readwrite: item.permission == Permission::ReadWrite,
default_value: if item.state == FlagState::Enabled { default_value: if item.state == FlagState::Enabled {
@ -57,6 +72,9 @@ fn create_class_element(item: &Item) -> ClassElement {
"false".to_string() "false".to_string()
}, },
flag_name: item.name.clone(), flag_name: item.name.clone(),
device_config_namespace: item.namespace.to_string(),
device_config_flag: codegen::create_device_config_ident(package, &item.name)
.expect("values checked at cache creation time"),
} }
} }
@ -69,13 +87,14 @@ mod tests {
#[test] #[test]
fn test_cpp_codegen_build_time_flag_only() { fn test_cpp_codegen_build_time_flag_only() {
let namespace = "my_namespace"; let package = "com.example";
let mut builder = CacheBuilder::new(namespace.to_string()).unwrap(); let mut builder = CacheBuilder::new(package.to_string()).unwrap();
builder builder
.add_flag_declaration( .add_flag_declaration(
Source::File("aconfig_one.txt".to_string()), Source::File("aconfig_one.txt".to_string()),
FlagDeclaration { FlagDeclaration {
name: "my_flag_one".to_string(), name: "my_flag_one".to_string(),
namespace: "ns".to_string(),
description: "buildtime disable".to_string(), description: "buildtime disable".to_string(),
}, },
) )
@ -83,7 +102,7 @@ mod tests {
.add_flag_value( .add_flag_value(
Source::Memory, Source::Memory,
FlagValue { FlagValue {
namespace: namespace.to_string(), package: package.to_string(),
name: "my_flag_one".to_string(), name: "my_flag_one".to_string(),
state: FlagState::Disabled, state: FlagState::Disabled,
permission: Permission::ReadOnly, permission: Permission::ReadOnly,
@ -94,6 +113,7 @@ mod tests {
Source::File("aconfig_two.txt".to_string()), Source::File("aconfig_two.txt".to_string()),
FlagDeclaration { FlagDeclaration {
name: "my_flag_two".to_string(), name: "my_flag_two".to_string(),
namespace: "ns".to_string(),
description: "buildtime enable".to_string(), description: "buildtime enable".to_string(),
}, },
) )
@ -101,7 +121,7 @@ mod tests {
.add_flag_value( .add_flag_value(
Source::Memory, Source::Memory,
FlagValue { FlagValue {
namespace: namespace.to_string(), package: package.to_string(),
name: "my_flag_two".to_string(), name: "my_flag_two".to_string(),
state: FlagState::Enabled, state: FlagState::Enabled,
permission: Permission::ReadOnly, permission: Permission::ReadOnly,
@ -109,11 +129,10 @@ mod tests {
) )
.unwrap(); .unwrap();
let cache = builder.build(); let cache = builder.build();
let expect_content = r#"#ifndef my_namespace_HEADER_H let expect_content = r#"#ifndef com_example_HEADER_H
#define my_namespace_HEADER_H #define com_example_HEADER_H
#include "my_namespace.h"
namespace my_namespace { namespace com::example {
class my_flag_one { class my_flag_one {
public: public:
@ -133,7 +152,7 @@ mod tests {
#endif #endif
"#; "#;
let file = generate_cpp_code(&cache).unwrap(); let file = generate_cpp_code(&cache).unwrap();
assert_eq!("aconfig/my_namespace.h", file.path.to_str().unwrap()); assert_eq!("aconfig/com_example.h", file.path.to_str().unwrap());
assert_eq!( assert_eq!(
expect_content.replace(' ', ""), expect_content.replace(' ', ""),
String::from_utf8(file.contents).unwrap().replace(' ', "") String::from_utf8(file.contents).unwrap().replace(' ', "")
@ -142,13 +161,14 @@ mod tests {
#[test] #[test]
fn test_cpp_codegen_runtime_flag() { fn test_cpp_codegen_runtime_flag() {
let namespace = "my_namespace"; let package = "com.example";
let mut builder = CacheBuilder::new(namespace.to_string()).unwrap(); let mut builder = CacheBuilder::new(package.to_string()).unwrap();
builder builder
.add_flag_declaration( .add_flag_declaration(
Source::File("aconfig_one.txt".to_string()), Source::File("aconfig_one.txt".to_string()),
FlagDeclaration { FlagDeclaration {
name: "my_flag_one".to_string(), name: "my_flag_one".to_string(),
namespace: "ns".to_string(),
description: "buildtime disable".to_string(), description: "buildtime disable".to_string(),
}, },
) )
@ -157,6 +177,7 @@ mod tests {
Source::File("aconfig_two.txt".to_string()), Source::File("aconfig_two.txt".to_string()),
FlagDeclaration { FlagDeclaration {
name: "my_flag_two".to_string(), name: "my_flag_two".to_string(),
namespace: "ns".to_string(),
description: "runtime enable".to_string(), description: "runtime enable".to_string(),
}, },
) )
@ -164,7 +185,7 @@ mod tests {
.add_flag_value( .add_flag_value(
Source::Memory, Source::Memory,
FlagValue { FlagValue {
namespace: namespace.to_string(), package: package.to_string(),
name: "my_flag_two".to_string(), name: "my_flag_two".to_string(),
state: FlagState::Enabled, state: FlagState::Enabled,
permission: Permission::ReadWrite, permission: Permission::ReadWrite,
@ -172,21 +193,20 @@ mod tests {
) )
.unwrap(); .unwrap();
let cache = builder.build(); let cache = builder.build();
let expect_content = r#"#ifndef my_namespace_HEADER_H let expect_content = r#"#ifndef com_example_HEADER_H
#define my_namespace_HEADER_H #define com_example_HEADER_H
#include "my_namespace.h"
#include <server_configurable_flags/get_flags.h> #include <server_configurable_flags/get_flags.h>
using namespace server_configurable_flags; using namespace server_configurable_flags;
namespace my_namespace { namespace com::example {
class my_flag_one { class my_flag_one {
public: public:
virtual const bool value() { virtual const bool value() {
return GetServerConfigurableFlag( return GetServerConfigurableFlag(
"my_namespace", "ns",
"my_flag_one", "com.example.my_flag_one",
"false") == "true"; "false") == "true";
} }
} }
@ -195,8 +215,8 @@ mod tests {
public: public:
virtual const bool value() { virtual const bool value() {
return GetServerConfigurableFlag( return GetServerConfigurableFlag(
"my_namespace", "ns",
"my_flag_two", "com.example.my_flag_two",
"true") == "true"; "true") == "true";
} }
} }
@ -205,10 +225,13 @@ mod tests {
#endif #endif
"#; "#;
let file = generate_cpp_code(&cache).unwrap(); let file = generate_cpp_code(&cache).unwrap();
assert_eq!("aconfig/my_namespace.h", file.path.to_str().unwrap()); assert_eq!("aconfig/com_example.h", file.path.to_str().unwrap());
assert_eq!( assert_eq!(
expect_content.replace(' ', ""), None,
String::from_utf8(file.contents).unwrap().replace(' ', "") crate::test::first_significant_code_diff(
expect_content,
&String::from_utf8(file.contents).unwrap()
)
); );
} }
} }

View file

@ -21,17 +21,19 @@ use tinytemplate::TinyTemplate;
use crate::aconfig::{FlagState, Permission}; use crate::aconfig::{FlagState, Permission};
use crate::cache::{Cache, Item}; use crate::cache::{Cache, Item};
use crate::codegen;
use crate::commands::OutputFile; use crate::commands::OutputFile;
pub fn generate_java_code(cache: &Cache) -> Result<OutputFile> { pub fn generate_java_code(cache: &Cache) -> Result<OutputFile> {
let class_elements: Vec<ClassElement> = cache.iter().map(create_class_element).collect(); let package = cache.package();
let class_elements: Vec<ClassElement> =
cache.iter().map(|item| create_class_element(package, item)).collect();
let readwrite = class_elements.iter().any(|item| item.readwrite); let readwrite = class_elements.iter().any(|item| item.readwrite);
let namespace = cache.namespace(); let context = Context { package: package.to_string(), readwrite, class_elements };
let context = Context { namespace: namespace.to_string(), readwrite, class_elements };
let mut template = TinyTemplate::new(); let mut template = TinyTemplate::new();
template.add_template("java_code_gen", include_str!("../templates/java.template"))?; template.add_template("java_code_gen", include_str!("../templates/java.template"))?;
let contents = template.render("java_code_gen", &context)?; let contents = template.render("java_code_gen", &context)?;
let mut path: PathBuf = ["aconfig", namespace].iter().collect(); let mut path: PathBuf = package.split('.').collect();
// TODO: Allow customization of the java class name // TODO: Allow customization of the java class name
path.push("Flags.java"); path.push("Flags.java");
Ok(OutputFile { contents: contents.into(), path }) Ok(OutputFile { contents: contents.into(), path })
@ -39,7 +41,7 @@ pub fn generate_java_code(cache: &Cache) -> Result<OutputFile> {
#[derive(Serialize)] #[derive(Serialize)]
struct Context { struct Context {
pub namespace: String, pub package: String,
pub readwrite: bool, pub readwrite: bool,
pub class_elements: Vec<ClassElement>, pub class_elements: Vec<ClassElement>,
} }
@ -49,11 +51,13 @@ struct ClassElement {
pub method_name: String, pub method_name: String,
pub readwrite: bool, pub readwrite: bool,
pub default_value: String, pub default_value: String,
pub feature_name: String, pub device_config_namespace: String,
pub flag_name: String, pub device_config_flag: String,
} }
fn create_class_element(item: &Item) -> ClassElement { fn create_class_element(package: &str, item: &Item) -> ClassElement {
let device_config_flag = codegen::create_device_config_ident(package, &item.name)
.expect("values checked at cache creation time");
ClassElement { ClassElement {
method_name: item.name.clone(), method_name: item.name.clone(),
readwrite: item.permission == Permission::ReadWrite, readwrite: item.permission == Permission::ReadWrite,
@ -62,8 +66,8 @@ fn create_class_element(item: &Item) -> ClassElement {
} else { } else {
"false".to_string() "false".to_string()
}, },
feature_name: item.name.clone(), device_config_namespace: item.namespace.clone(),
flag_name: item.name.clone(), device_config_flag,
} }
} }
@ -76,13 +80,14 @@ mod tests {
#[test] #[test]
fn test_generate_java_code() { fn test_generate_java_code() {
let namespace = "example"; let package = "com.example";
let mut builder = CacheBuilder::new(namespace.to_string()).unwrap(); let mut builder = CacheBuilder::new(package.to_string()).unwrap();
builder builder
.add_flag_declaration( .add_flag_declaration(
Source::File("test.txt".to_string()), Source::File("test.txt".to_string()),
FlagDeclaration { FlagDeclaration {
name: "test".to_string(), name: "test".to_string(),
namespace: "ns".to_string(),
description: "buildtime enable".to_string(), description: "buildtime enable".to_string(),
}, },
) )
@ -91,6 +96,7 @@ mod tests {
Source::File("test2.txt".to_string()), Source::File("test2.txt".to_string()),
FlagDeclaration { FlagDeclaration {
name: "test2".to_string(), name: "test2".to_string(),
namespace: "ns".to_string(),
description: "runtime disable".to_string(), description: "runtime disable".to_string(),
}, },
) )
@ -98,7 +104,7 @@ mod tests {
.add_flag_value( .add_flag_value(
Source::Memory, Source::Memory,
FlagValue { FlagValue {
namespace: namespace.to_string(), package: package.to_string(),
name: "test".to_string(), name: "test".to_string(),
state: FlagState::Disabled, state: FlagState::Disabled,
permission: Permission::ReadOnly, permission: Permission::ReadOnly,
@ -106,7 +112,7 @@ mod tests {
) )
.unwrap(); .unwrap();
let cache = builder.build(); let cache = builder.build();
let expect_content = r#"package aconfig.example; let expect_content = r#"package com.example;
import android.provider.DeviceConfig; import android.provider.DeviceConfig;
@ -118,8 +124,8 @@ mod tests {
public static boolean test2() { public static boolean test2() {
return DeviceConfig.getBoolean( return DeviceConfig.getBoolean(
"example", "ns",
"test2__test2", "com.example.test2",
false false
); );
} }
@ -127,10 +133,13 @@ mod tests {
} }
"#; "#;
let file = generate_java_code(&cache).unwrap(); let file = generate_java_code(&cache).unwrap();
assert_eq!("aconfig/example/Flags.java", file.path.to_str().unwrap()); assert_eq!("com/example/Flags.java", file.path.to_str().unwrap());
assert_eq!( assert_eq!(
expect_content.replace(' ', ""), None,
String::from_utf8(file.contents).unwrap().replace(' ', "") crate::test::first_significant_code_diff(
expect_content,
&String::from_utf8(file.contents).unwrap()
)
); );
} }
} }

View file

@ -20,13 +20,18 @@ use tinytemplate::TinyTemplate;
use crate::aconfig::{FlagState, Permission}; use crate::aconfig::{FlagState, Permission};
use crate::cache::{Cache, Item}; use crate::cache::{Cache, Item};
use crate::codegen;
use crate::commands::OutputFile; use crate::commands::OutputFile;
pub fn generate_rust_code(cache: &Cache) -> Result<OutputFile> { pub fn generate_rust_code(cache: &Cache) -> Result<OutputFile> {
let namespace = cache.namespace(); let package = cache.package();
let parsed_flags: Vec<TemplateParsedFlag> = let parsed_flags: Vec<TemplateParsedFlag> =
cache.iter().map(|item| create_template_parsed_flag(namespace, item)).collect(); cache.iter().map(|item| TemplateParsedFlag::new(package, item)).collect();
let context = TemplateContext { namespace: namespace.to_string(), parsed_flags }; let context = TemplateContext {
package: package.to_string(),
parsed_flags,
modules: package.split('.').map(|s| s.to_string()).collect::<Vec<_>>(),
};
let mut template = TinyTemplate::new(); let mut template = TinyTemplate::new();
template.add_template("rust_code_gen", include_str!("../templates/rust.template"))?; template.add_template("rust_code_gen", include_str!("../templates/rust.template"))?;
let contents = template.render("rust_code_gen", &context)?; let contents = template.render("rust_code_gen", &context)?;
@ -36,14 +41,16 @@ pub fn generate_rust_code(cache: &Cache) -> Result<OutputFile> {
#[derive(Serialize)] #[derive(Serialize)]
struct TemplateContext { struct TemplateContext {
pub namespace: String, pub package: String,
pub parsed_flags: Vec<TemplateParsedFlag>, pub parsed_flags: Vec<TemplateParsedFlag>,
pub modules: Vec<String>,
} }
#[derive(Serialize)] #[derive(Serialize)]
struct TemplateParsedFlag { struct TemplateParsedFlag {
pub name: String, pub name: String,
pub fn_name: String, pub device_config_namespace: String,
pub device_config_flag: String,
// TinyTemplate's conditionals are limited to single <bool> expressions; list all options here // TinyTemplate's conditionals are limited to single <bool> expressions; list all options here
// Invariant: exactly one of these fields will be true // Invariant: exactly one of these fields will be true
@ -52,28 +59,32 @@ struct TemplateParsedFlag {
pub is_read_write: bool, pub is_read_write: bool,
} }
#[allow(clippy::nonminimal_bool)] impl TemplateParsedFlag {
fn create_template_parsed_flag(namespace: &str, item: &Item) -> TemplateParsedFlag { #[allow(clippy::nonminimal_bool)]
let template = TemplateParsedFlag { fn new(package: &str, item: &Item) -> Self {
name: item.name.clone(), let template = TemplateParsedFlag {
fn_name: format!("{}_{}", namespace, &item.name), name: item.name.clone(),
is_read_only_enabled: item.permission == Permission::ReadOnly device_config_namespace: item.namespace.to_string(),
&& item.state == FlagState::Enabled, device_config_flag: codegen::create_device_config_ident(package, &item.name)
is_read_only_disabled: item.permission == Permission::ReadOnly .expect("values checked at cache creation time"),
&& item.state == FlagState::Disabled, is_read_only_enabled: item.permission == Permission::ReadOnly
is_read_write: item.permission == Permission::ReadWrite, && item.state == FlagState::Enabled,
}; is_read_only_disabled: item.permission == Permission::ReadOnly
#[rustfmt::skip] && item.state == FlagState::Disabled,
debug_assert!( is_read_write: item.permission == Permission::ReadWrite,
(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) || #[rustfmt::skip]
(!template.is_read_only_enabled && !template.is_read_only_disabled && template.is_read_write), debug_assert!(
"TemplateParsedFlag invariant failed: {} {} {}", (template.is_read_only_enabled && !template.is_read_only_disabled && !template.is_read_write) ||
template.is_read_only_enabled, (!template.is_read_only_enabled && template.is_read_only_disabled && !template.is_read_write) ||
template.is_read_only_disabled, (!template.is_read_only_enabled && !template.is_read_only_disabled && template.is_read_write),
template.is_read_write, "TemplateParsedFlag invariant failed: {} {} {}",
); template.is_read_only_enabled,
template template.is_read_only_disabled,
template.is_read_write,
);
template
}
} }
#[cfg(test)] #[cfg(test)]
@ -86,26 +97,41 @@ mod tests {
let generated = generate_rust_code(&cache).unwrap(); let generated = generate_rust_code(&cache).unwrap();
assert_eq!("src/lib.rs", format!("{}", generated.path.display())); assert_eq!("src/lib.rs", format!("{}", generated.path.display()));
let expected = r#" let expected = r#"
pub mod com {
pub mod android {
pub mod aconfig {
pub mod test {
#[inline(always)] #[inline(always)]
pub const fn r#test_disabled_ro() -> bool { pub const fn r#disabled_ro() -> bool {
false false
} }
#[inline(always)] #[inline(always)]
pub fn r#test_disabled_rw() -> bool { pub fn r#disabled_rw() -> bool {
flags_rust::GetServerConfigurableFlag("test", "disabled_rw", "false") == "true" flags_rust::GetServerConfigurableFlag("aconfig_test", "com.android.aconfig.test.disabled_rw", "false") == "true"
} }
#[inline(always)] #[inline(always)]
pub const fn r#test_enabled_ro() -> bool { pub const fn r#enabled_ro() -> bool {
true true
} }
#[inline(always)] #[inline(always)]
pub fn r#test_enabled_rw() -> bool { pub fn r#enabled_rw() -> bool {
flags_rust::GetServerConfigurableFlag("test", "enabled_rw", "false") == "true" flags_rust::GetServerConfigurableFlag("aconfig_test", "com.android.aconfig.test.enabled_rw", "false") == "true"
}
}
}
}
} }
"#; "#;
assert_eq!(expected.trim(), String::from_utf8(generated.contents).unwrap().trim()); assert_eq!(
None,
crate::test::first_significant_code_diff(
expected,
&String::from_utf8(generated.contents).unwrap()
)
);
} }
} }

View file

@ -55,12 +55,8 @@ pub struct OutputFile {
pub contents: Vec<u8>, pub contents: Vec<u8>,
} }
pub fn create_cache( pub fn create_cache(package: &str, declarations: Vec<Input>, values: Vec<Input>) -> Result<Cache> {
namespace: &str, let mut builder = CacheBuilder::new(package.to_owned())?;
declarations: Vec<Input>,
values: Vec<Input>,
) -> Result<Cache> {
let mut builder = CacheBuilder::new(namespace.to_owned())?;
for mut input in declarations { for mut input in declarations {
let mut contents = String::new(); let mut contents = String::new();
@ -68,11 +64,11 @@ pub fn create_cache(
let dec_list = FlagDeclarations::try_from_text_proto(&contents) let dec_list = FlagDeclarations::try_from_text_proto(&contents)
.with_context(|| format!("Failed to parse {}", input.source))?; .with_context(|| format!("Failed to parse {}", input.source))?;
ensure!( ensure!(
namespace == dec_list.namespace, package == dec_list.package,
"Failed to parse {}: expected namespace {}, got {}", "Failed to parse {}: expected package {}, got {}",
input.source, input.source,
namespace, package,
dec_list.namespace dec_list.package
); );
for d in dec_list.flags.into_iter() { for d in dec_list.flags.into_iter() {
builder.add_flag_declaration(input.source.clone(), d)?; builder.add_flag_declaration(input.source.clone(), d)?;
@ -110,8 +106,9 @@ pub fn create_device_config_defaults(caches: Vec<Cache>) -> Result<Vec<u8>> {
for item in sort_and_iter_items(caches).filter(|item| item.permission == Permission::ReadWrite) for item in sort_and_iter_items(caches).filter(|item| item.permission == Permission::ReadWrite)
{ {
let line = format!( let line = format!(
"{}/{}:{}\n", "{}:{}.{}={}\n",
item.namespace, item.namespace,
item.package,
item.name, item.name,
match item.state { match item.state {
FlagState::Enabled => "enabled", FlagState::Enabled => "enabled",
@ -129,7 +126,7 @@ pub fn create_device_config_sysprops(caches: Vec<Cache>) -> Result<Vec<u8>> {
{ {
let line = format!( let line = format!(
"persist.device_config.{}.{}={}\n", "persist.device_config.{}.{}={}\n",
item.namespace, item.package,
item.name, item.name,
match item.state { match item.state {
FlagState::Enabled => "true", FlagState::Enabled => "true",
@ -155,7 +152,7 @@ pub fn dump_cache(caches: Vec<Cache>, format: DumpFormat) -> Result<Vec<u8>> {
for item in sort_and_iter_items(caches) { for item in sort_and_iter_items(caches) {
let line = format!( let line = format!(
"{}/{}: {:?} {:?}\n", "{}/{}: {:?} {:?}\n",
item.namespace, item.name, item.state, item.permission item.package, item.name, item.state, item.permission
); );
output.extend_from_slice(line.as_bytes()); output.extend_from_slice(line.as_bytes());
} }
@ -181,7 +178,7 @@ fn sort_and_iter_items(caches: Vec<Cache>) -> impl Iterator<Item = Item> {
} }
fn sort_and_iter_caches(mut caches: Vec<Cache>) -> impl Iterator<Item = Cache> { fn sort_and_iter_caches(mut caches: Vec<Cache>) -> impl Iterator<Item = Cache> {
caches.sort_by_cached_key(|cache| cache.namespace().to_string()); caches.sort_by_cached_key(|cache| cache.package().to_string());
caches.into_iter() caches.into_iter()
} }
@ -190,55 +187,58 @@ mod tests {
use super::*; use super::*;
use crate::aconfig::{FlagState, Permission}; use crate::aconfig::{FlagState, Permission};
fn create_test_cache_ns1() -> Cache { fn create_test_cache_com_example() -> Cache {
let s = r#" let s = r#"
namespace: "ns1" package: "com.example"
flag { flag {
name: "a" name: "a"
namespace: "ns"
description: "Description of a" description: "Description of a"
} }
flag { flag {
name: "b" name: "b"
namespace: "ns"
description: "Description of b" description: "Description of b"
} }
"#; "#;
let declarations = vec![Input { source: Source::Memory, reader: Box::new(s.as_bytes()) }]; let declarations = vec![Input { source: Source::Memory, reader: Box::new(s.as_bytes()) }];
let o = r#" let o = r#"
flag_value { flag_value {
namespace: "ns1" package: "com.example"
name: "a" name: "a"
state: DISABLED state: DISABLED
permission: READ_ONLY permission: READ_ONLY
} }
"#; "#;
let values = vec![Input { source: Source::Memory, reader: Box::new(o.as_bytes()) }]; let values = vec![Input { source: Source::Memory, reader: Box::new(o.as_bytes()) }];
create_cache("ns1", declarations, values).unwrap() create_cache("com.example", declarations, values).unwrap()
} }
fn create_test_cache_ns2() -> Cache { fn create_test_cache_com_other() -> Cache {
let s = r#" let s = r#"
namespace: "ns2" package: "com.other"
flag { flag {
name: "c" name: "c"
namespace: "ns"
description: "Description of c" description: "Description of c"
} }
"#; "#;
let declarations = vec![Input { source: Source::Memory, reader: Box::new(s.as_bytes()) }]; let declarations = vec![Input { source: Source::Memory, reader: Box::new(s.as_bytes()) }];
let o = r#" let o = r#"
flag_value { flag_value {
namespace: "ns2" package: "com.other"
name: "c" name: "c"
state: DISABLED state: DISABLED
permission: READ_ONLY permission: READ_ONLY
} }
"#; "#;
let values = vec![Input { source: Source::Memory, reader: Box::new(o.as_bytes()) }]; let values = vec![Input { source: Source::Memory, reader: Box::new(o.as_bytes()) }];
create_cache("ns2", declarations, values).unwrap() create_cache("com.other", declarations, values).unwrap()
} }
#[test] #[test]
fn test_create_cache() { fn test_create_cache() {
let caches = create_test_cache_ns1(); // calls create_cache let caches = create_test_cache_com_example(); // calls create_cache
let item = caches.iter().find(|&item| item.name == "a").unwrap(); let item = caches.iter().find(|&item| item.name == "a").unwrap();
assert_eq!(FlagState::Disabled, item.state); assert_eq!(FlagState::Disabled, item.state);
assert_eq!(Permission::ReadOnly, item.permission); assert_eq!(Permission::ReadOnly, item.permission);
@ -249,7 +249,7 @@ mod tests {
let caches = vec![crate::test::create_cache()]; let caches = vec![crate::test::create_cache()];
let bytes = create_device_config_defaults(caches).unwrap(); let bytes = create_device_config_defaults(caches).unwrap();
let text = std::str::from_utf8(&bytes).unwrap(); let text = std::str::from_utf8(&bytes).unwrap();
assert_eq!("test/disabled_rw:disabled\ntest/enabled_rw:enabled\n", text); assert_eq!("aconfig_test:com.android.aconfig.test.disabled_rw=disabled\naconfig_test:com.android.aconfig.test.enabled_rw=enabled\n", text);
} }
#[test] #[test]
@ -257,12 +257,12 @@ mod tests {
let caches = vec![crate::test::create_cache()]; let caches = vec![crate::test::create_cache()];
let bytes = create_device_config_sysprops(caches).unwrap(); let bytes = create_device_config_sysprops(caches).unwrap();
let text = std::str::from_utf8(&bytes).unwrap(); let text = std::str::from_utf8(&bytes).unwrap();
assert_eq!("persist.device_config.test.disabled_rw=false\npersist.device_config.test.enabled_rw=true\n", text); assert_eq!("persist.device_config.com.android.aconfig.test.disabled_rw=false\npersist.device_config.com.android.aconfig.test.enabled_rw=true\n", text);
} }
#[test] #[test]
fn test_dump_text_format() { fn test_dump_text_format() {
let caches = vec![create_test_cache_ns1()]; let caches = vec![create_test_cache_com_example()];
let bytes = dump_cache(caches, DumpFormat::Text).unwrap(); let bytes = dump_cache(caches, DumpFormat::Text).unwrap();
let text = std::str::from_utf8(&bytes).unwrap(); let text = std::str::from_utf8(&bytes).unwrap();
assert!(text.contains("a: Disabled")); assert!(text.contains("a: Disabled"));
@ -273,7 +273,7 @@ mod tests {
use crate::protos::{ProtoFlagPermission, ProtoFlagState, ProtoTracepoint}; use crate::protos::{ProtoFlagPermission, ProtoFlagState, ProtoTracepoint};
use protobuf::Message; use protobuf::Message;
let caches = vec![create_test_cache_ns1()]; let caches = vec![create_test_cache_com_example()];
let bytes = dump_cache(caches, DumpFormat::Protobuf).unwrap(); let bytes = dump_cache(caches, DumpFormat::Protobuf).unwrap();
let actual = ProtoParsedFlags::parse_from_bytes(&bytes).unwrap(); let actual = ProtoParsedFlags::parse_from_bytes(&bytes).unwrap();
@ -284,7 +284,7 @@ mod tests {
let item = let item =
actual.parsed_flag.iter().find(|item| item.name == Some("b".to_string())).unwrap(); actual.parsed_flag.iter().find(|item| item.name == Some("b".to_string())).unwrap();
assert_eq!(item.namespace(), "ns1"); assert_eq!(item.package(), "com.example");
assert_eq!(item.name(), "b"); assert_eq!(item.name(), "b");
assert_eq!(item.description(), "Description of b"); assert_eq!(item.description(), "Description of b");
assert_eq!(item.state(), ProtoFlagState::DISABLED); assert_eq!(item.state(), ProtoFlagState::DISABLED);
@ -298,18 +298,22 @@ mod tests {
#[test] #[test]
fn test_dump_multiple_caches() { fn test_dump_multiple_caches() {
let caches = vec![create_test_cache_ns1(), create_test_cache_ns2()]; let caches = vec![create_test_cache_com_example(), create_test_cache_com_other()];
let bytes = dump_cache(caches, DumpFormat::Protobuf).unwrap(); let bytes = dump_cache(caches, DumpFormat::Protobuf).unwrap();
let dump = ProtoParsedFlags::parse_from_bytes(&bytes).unwrap(); let dump = ProtoParsedFlags::parse_from_bytes(&bytes).unwrap();
assert_eq!( assert_eq!(
dump.parsed_flag dump.parsed_flag
.iter() .iter()
.map(|parsed_flag| format!("{}/{}", parsed_flag.namespace(), parsed_flag.name())) .map(|parsed_flag| format!("{}/{}", parsed_flag.package(), parsed_flag.name()))
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
vec!["ns1/a".to_string(), "ns1/b".to_string(), "ns2/c".to_string()] vec![
"com.example/a".to_string(),
"com.example/b".to_string(),
"com.other/c".to_string()
]
); );
let caches = vec![create_test_cache_ns2(), create_test_cache_ns1()]; let caches = vec![create_test_cache_com_other(), create_test_cache_com_example()];
let bytes = dump_cache(caches, DumpFormat::Protobuf).unwrap(); let bytes = dump_cache(caches, DumpFormat::Protobuf).unwrap();
let dump_reversed_input = ProtoParsedFlags::parse_from_bytes(&bytes).unwrap(); let dump_reversed_input = ProtoParsedFlags::parse_from_bytes(&bytes).unwrap();
assert_eq!(dump, dump_reversed_input); assert_eq!(dump, dump_reversed_input);

View file

@ -44,7 +44,7 @@ fn cli() -> Command {
.subcommand_required(true) .subcommand_required(true)
.subcommand( .subcommand(
Command::new("create-cache") Command::new("create-cache")
.arg(Arg::new("namespace").long("namespace").required(true)) .arg(Arg::new("package").long("package").required(true))
.arg(Arg::new("declarations").long("declarations").action(ArgAction::Append)) .arg(Arg::new("declarations").long("declarations").action(ArgAction::Append))
.arg(Arg::new("values").long("values").action(ArgAction::Append)) .arg(Arg::new("values").long("values").action(ArgAction::Append))
.arg(Arg::new("cache").long("cache").required(true)), .arg(Arg::new("cache").long("cache").required(true)),
@ -134,10 +134,10 @@ fn main() -> Result<()> {
let matches = cli().get_matches(); let matches = cli().get_matches();
match matches.subcommand() { match matches.subcommand() {
Some(("create-cache", sub_matches)) => { Some(("create-cache", sub_matches)) => {
let namespace = get_required_arg::<String>(sub_matches, "namespace")?; let package = get_required_arg::<String>(sub_matches, "package")?;
let declarations = open_zero_or_more_files(sub_matches, "declarations")?; let declarations = open_zero_or_more_files(sub_matches, "declarations")?;
let values = open_zero_or_more_files(sub_matches, "values")?; let values = open_zero_or_more_files(sub_matches, "values")?;
let cache = commands::create_cache(namespace, declarations, values)?; let cache = commands::create_cache(package, declarations, values)?;
let path = get_required_arg::<String>(sub_matches, "cache")?; let path = get_required_arg::<String>(sub_matches, "cache")?;
let file = fs::File::create(path)?; let file = fs::File::create(path)?;
cache.write_to_writer(file)?; cache.write_to_writer(file)?;

View file

@ -18,10 +18,11 @@
pub mod test_utils { pub mod test_utils {
use crate::cache::Cache; use crate::cache::Cache;
use crate::commands::{Input, Source}; use crate::commands::{Input, Source};
use itertools;
pub fn create_cache() -> Cache { pub fn create_cache() -> Cache {
crate::commands::create_cache( crate::commands::create_cache(
"test", "com.android.aconfig.test",
vec![Input { vec![Input {
source: Source::File("testdata/test.aconfig".to_string()), source: Source::File("testdata/test.aconfig".to_string()),
reader: Box::new(include_bytes!("../testdata/test.aconfig").as_slice()), reader: Box::new(include_bytes!("../testdata/test.aconfig").as_slice()),
@ -39,6 +40,54 @@ pub mod test_utils {
) )
.unwrap() .unwrap()
} }
pub fn first_significant_code_diff(a: &str, b: &str) -> Option<String> {
let a = a.lines().map(|line| line.trim_start()).filter(|line| !line.is_empty());
let b = b.lines().map(|line| line.trim_start()).filter(|line| !line.is_empty());
match itertools::diff_with(a, b, |left, right| left == right) {
Some(itertools::Diff::FirstMismatch(_, mut left, mut right)) => {
Some(format!("'{}' vs '{}'", left.next().unwrap(), right.next().unwrap()))
}
Some(itertools::Diff::Shorter(_, mut left)) => {
Some(format!("LHS trailing data: '{}'", left.next().unwrap()))
}
Some(itertools::Diff::Longer(_, mut right)) => {
Some(format!("RHS trailing data: '{}'", right.next().unwrap()))
}
None => None,
}
}
#[test]
fn test_first_significant_code_diff() {
assert!(first_significant_code_diff("", "").is_none());
assert!(first_significant_code_diff(" a", "\n\na\n").is_none());
let a = r#"
public class A {
private static final String FOO = "FOO";
public static void main(String[] args) {
System.out.println("FOO=" + FOO);
}
}
"#;
let b = r#"
public class A {
private static final String FOO = "BAR";
public static void main(String[] args) {
System.out.println("foo=" + FOO);
}
}
"#;
assert_eq!(Some(r#"'private static final String FOO = "FOO";' vs 'private static final String FOO = "BAR";'"#.to_string()), first_significant_code_diff(a, b));
assert_eq!(
Some("LHS trailing data: 'b'".to_string()),
first_significant_code_diff("a\nb", "a")
);
assert_eq!(
Some("RHS trailing data: 'b'".to_string()),
first_significant_code_diff("a", "a\nb")
);
}
} }
#[cfg(test)] #[cfg(test)]

View file

@ -1,19 +1,18 @@
#ifndef {namespace}_HEADER_H #ifndef {header}_HEADER_H
#define {namespace}_HEADER_H #define {header}_HEADER_H
#include "{namespace}.h"
{{ if readwrite }} {{ if readwrite }}
#include <server_configurable_flags/get_flags.h> #include <server_configurable_flags/get_flags.h>
using namespace server_configurable_flags; using namespace server_configurable_flags;
{{ endif }} {{ endif }}
namespace {namespace} \{ namespace {cpp_namespace} \{
{{ for item in class_elements}} {{ for item in class_elements}}
class {item.flag_name} \{ class {item.flag_name} \{
public: public:
virtual const bool value() \{ virtual const bool value() \{
{{ if item.readwrite- }} {{ if item.readwrite- }}
return GetServerConfigurableFlag( return GetServerConfigurableFlag(
"{namespace}", "{item.device_config_namespace}",
"{item.flag_name}", "{item.device_config_flag}",
"{item.default_value}") == "true"; "{item.default_value}") == "true";
{{ -else- }} {{ -else- }}
return {item.default_value}; return {item.default_value};

View file

@ -1,4 +1,4 @@
package aconfig.{namespace}; package {package};
{{ if readwrite }} {{ if readwrite }}
import android.provider.DeviceConfig; import android.provider.DeviceConfig;
{{ endif }} {{ endif }}
@ -7,8 +7,8 @@ public final class Flags \{
public static boolean {item.method_name}() \{ public static boolean {item.method_name}() \{
{{ if item.readwrite- }} {{ if item.readwrite- }}
return DeviceConfig.getBoolean( return DeviceConfig.getBoolean(
"{namespace}", "{item.device_config_namespace}",
"{item.feature_name}__{item.flag_name}", "{item.device_config_flag}",
{item.default_value} {item.default_value}
); );
{{ -else- }} {{ -else- }}

View file

@ -1,23 +1,29 @@
{{- for mod in modules -}}
pub mod {mod} \{
{{ endfor -}}
{{- for parsed_flag in parsed_flags -}} {{- for parsed_flag in parsed_flags -}}
{{- if parsed_flag.is_read_only_disabled -}} {{- if parsed_flag.is_read_only_disabled -}}
#[inline(always)] #[inline(always)]
pub const fn r#{parsed_flag.fn_name}() -> bool \{ pub const fn r#{parsed_flag.name}() -> bool \{
false false
} }
{{ endif -}} {{ endif -}}
{{- if parsed_flag.is_read_only_enabled -}} {{- if parsed_flag.is_read_only_enabled -}}
#[inline(always)] #[inline(always)]
pub const fn r#{parsed_flag.fn_name}() -> bool \{ pub const fn r#{parsed_flag.name}() -> bool \{
true true
} }
{{ endif -}} {{ endif -}}
{{- if parsed_flag.is_read_write -}} {{- if parsed_flag.is_read_write -}}
#[inline(always)] #[inline(always)]
pub fn r#{parsed_flag.fn_name}() -> bool \{ pub fn r#{parsed_flag.name}() -> bool \{
flags_rust::GetServerConfigurableFlag("{namespace}", "{parsed_flag.name}", "false") == "true" flags_rust::GetServerConfigurableFlag("{parsed_flag.device_config_namespace}", "{parsed_flag.device_config_flag}", "false") == "true"
} }
{{ endif -}} {{ endif -}}
{{- endfor -}} {{- endfor -}}
{{- for mod in modules -}}
}
{{ endfor -}}

View file

@ -1,17 +1,17 @@
flag_value { flag_value {
namespace: "test" package: "com.android.aconfig.test"
name: "disabled_ro" name: "disabled_ro"
state: DISABLED state: DISABLED
permission: READ_ONLY permission: READ_ONLY
} }
flag_value { flag_value {
namespace: "test" package: "com.android.aconfig.test"
name: "enabled_ro" name: "enabled_ro"
state: DISABLED state: DISABLED
permission: READ_WRITE permission: READ_WRITE
} }
flag_value { flag_value {
namespace: "test" package: "com.android.aconfig.test"
name: "enabled_rw" name: "enabled_rw"
state: ENABLED state: ENABLED
permission: READ_WRITE permission: READ_WRITE

View file

@ -1,5 +1,5 @@
flag_value { flag_value {
namespace: "test" package: "com.android.aconfig.test"
name: "enabled_ro" name: "enabled_ro"
state: ENABLED state: ENABLED
permission: READ_ONLY permission: READ_ONLY

View file

@ -1,10 +1,11 @@
namespace: "test" package: "com.android.aconfig.test"
# This flag's final value is calculated from: # This flag's final value is calculated from:
# - test.aconfig: DISABLED + READ_WRITE (default) # - test.aconfig: DISABLED + READ_WRITE (default)
# - first.values: DISABLED + READ_ONLY # - first.values: DISABLED + READ_ONLY
flag { flag {
name: "disabled_ro" name: "disabled_ro"
namespace: "aconfig_test"
description: "This flag is DISABLED + READ_ONLY" description: "This flag is DISABLED + READ_ONLY"
} }
@ -12,6 +13,7 @@ flag {
# - test.aconfig: DISABLED + READ_WRITE (default) # - test.aconfig: DISABLED + READ_WRITE (default)
flag { flag {
name: "disabled_rw" name: "disabled_rw"
namespace: "aconfig_test"
description: "This flag is DISABLED + READ_WRITE" description: "This flag is DISABLED + READ_WRITE"
} }
@ -21,6 +23,7 @@ flag {
# - second.values: ENABLED + READ_ONLY # - second.values: ENABLED + READ_ONLY
flag { flag {
name: "enabled_ro" name: "enabled_ro"
namespace: "aconfig_test"
description: "This flag is ENABLED + READ_ONLY" description: "This flag is ENABLED + READ_ONLY"
} }
@ -29,5 +32,6 @@ flag {
# - first.values: ENABLED + READ_WRITE # - first.values: ENABLED + READ_WRITE
flag { flag {
name: "enabled_rw" name: "enabled_rw"
namespace: "aconfig_test"
description: "This flag is ENABLED + READ_WRITE" description: "This flag is ENABLED + READ_WRITE"
} }