Merge changes from topic "aconfig-part-5"
* changes: aconfig: follow Java conventions for Java file paths aconfig: separate flag declarations and flag values
This commit is contained in:
commit
71f9dabe47
8 changed files with 219 additions and 359 deletions
|
@ -12,8 +12,8 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License
|
// limitations under the License
|
||||||
|
|
||||||
// This is the schema definition for of Aconfig files. Modifications need to be
|
// This is the schema definition for aconfig files. Modifications need to be
|
||||||
// either backwards compatible, or include updates to all Aconfig files in the
|
// either backwards compatible, or include updates to all aconfig files in the
|
||||||
// Android tree.
|
// Android tree.
|
||||||
|
|
||||||
syntax = "proto2";
|
syntax = "proto2";
|
||||||
|
@ -32,40 +32,33 @@ enum flag_permission {
|
||||||
READ_WRITE = 2;
|
READ_WRITE = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// aconfig input messages: configuration and override data
|
// aconfig input messages: flag declarations and values
|
||||||
|
|
||||||
message flag_value {
|
message flag_declaration {
|
||||||
required flag_state state = 1;
|
|
||||||
required flag_permission permission = 2;
|
|
||||||
optional uint32 since = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message flag_definition {
|
|
||||||
required string name = 1;
|
required string name = 1;
|
||||||
required string description = 2;
|
required string description = 2;
|
||||||
repeated flag_value value = 3;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
message namespace {
|
message flag_declarations {
|
||||||
required string namespace = 1;
|
required string namespace = 1;
|
||||||
repeated flag_definition flag = 2;
|
repeated flag_declaration flag = 2;
|
||||||
};
|
};
|
||||||
|
|
||||||
message flag_override {
|
message flag_value {
|
||||||
required string namespace = 1;
|
required string namespace = 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;
|
||||||
};
|
};
|
||||||
|
|
||||||
message flag_overrides {
|
message flag_values {
|
||||||
repeated flag_override flag_override = 1;
|
repeated flag_value flag_value = 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
// aconfig output messages: parsed and verified configuration and override data
|
// aconfig output messages: parsed and verified flag declarations and values
|
||||||
|
|
||||||
message tracepoint {
|
message tracepoint {
|
||||||
// path to config or override file releative to $TOP
|
// path to declaration or value file relative to $TOP
|
||||||
required string source = 1;
|
required string source = 1;
|
||||||
required flag_state state = 2;
|
required flag_state state = 2;
|
||||||
required flag_permission permission = 3;
|
required flag_permission permission = 3;
|
||||||
|
|
|
@ -20,9 +20,8 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::cache::{Cache, Item, Tracepoint};
|
use crate::cache::{Cache, Item, Tracepoint};
|
||||||
use crate::protos::{
|
use crate::protos::{
|
||||||
ProtoFlagDefinition, ProtoFlagDefinitionValue, ProtoFlagOverride, ProtoFlagOverrides,
|
ProtoFlagDeclaration, ProtoFlagDeclarations, ProtoFlagPermission, ProtoFlagState,
|
||||||
ProtoFlagPermission, ProtoFlagState, ProtoNamespace, ProtoParsedFlag, ProtoParsedFlags,
|
ProtoFlagValue, ProtoFlagValues, ProtoParsedFlag, ProtoParsedFlags, ProtoTracepoint,
|
||||||
ProtoTracepoint,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)]
|
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)]
|
||||||
|
@ -80,112 +79,43 @@ impl From<Permission> for ProtoFlagPermission {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct Value {
|
pub struct FlagDeclaration {
|
||||||
state: FlagState,
|
|
||||||
permission: Permission,
|
|
||||||
since: Option<u32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)] // only used in unit tests
|
|
||||||
impl Value {
|
|
||||||
pub fn new(state: FlagState, permission: Permission, since: u32) -> Value {
|
|
||||||
Value { state, permission, since: Some(since) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn default(state: FlagState, permission: Permission) -> Value {
|
|
||||||
Value { state, permission, since: None }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<ProtoFlagDefinitionValue> for Value {
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn try_from(proto: ProtoFlagDefinitionValue) -> Result<Self, Self::Error> {
|
|
||||||
let Some(proto_state) = proto.state else {
|
|
||||||
return Err(anyhow!("missing 'state' field"));
|
|
||||||
};
|
|
||||||
let state = proto_state.try_into()?;
|
|
||||||
let Some(proto_permission) = proto.permission else {
|
|
||||||
return Err(anyhow!("missing 'permission' field"));
|
|
||||||
};
|
|
||||||
let permission = proto_permission.try_into()?;
|
|
||||||
Ok(Value { state, permission, since: proto.since })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
pub struct Flag {
|
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
|
|
||||||
// ordered by Value.since; guaranteed to contain at least one item (the default value, with
|
|
||||||
// since == None)
|
|
||||||
pub values: Vec<Value>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Flag {
|
impl FlagDeclaration {
|
||||||
#[allow(dead_code)] // only used in unit tests
|
#[allow(dead_code)] // only used in unit tests
|
||||||
pub fn try_from_text_proto(text_proto: &str) -> Result<Flag> {
|
pub fn try_from_text_proto(text_proto: &str) -> Result<FlagDeclaration> {
|
||||||
let proto: ProtoFlagDefinition = crate::protos::try_from_text_proto(text_proto)
|
let proto: ProtoFlagDeclaration = crate::protos::try_from_text_proto(text_proto)
|
||||||
.with_context(|| text_proto.to_owned())?;
|
.with_context(|| text_proto.to_owned())?;
|
||||||
proto.try_into()
|
proto.try_into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resolve(&self, build_id: u32) -> (FlagState, Permission) {
|
|
||||||
let mut state = self.values[0].state;
|
|
||||||
let mut permission = self.values[0].permission;
|
|
||||||
for candidate in self.values.iter().skip(1) {
|
|
||||||
let since = candidate.since.expect("invariant: non-defaults values have Some(since)");
|
|
||||||
if since <= build_id {
|
|
||||||
state = candidate.state;
|
|
||||||
permission = candidate.permission;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(state, permission)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<ProtoFlagDefinition> for Flag {
|
impl TryFrom<ProtoFlagDeclaration> for FlagDeclaration {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
fn try_from(proto: ProtoFlagDefinition) -> Result<Self, Self::Error> {
|
fn try_from(proto: ProtoFlagDeclaration) -> Result<Self, Self::Error> {
|
||||||
let Some(name) = proto.name else {
|
let Some(name) = proto.name else {
|
||||||
return Err(anyhow!("missing 'name' field"));
|
return Err(anyhow!("missing 'name' field"));
|
||||||
};
|
};
|
||||||
let Some(description) = proto.description else {
|
let Some(description) = proto.description else {
|
||||||
return Err(anyhow!("missing 'description' field"));
|
return Err(anyhow!("missing 'description' field"));
|
||||||
};
|
};
|
||||||
if proto.value.is_empty() {
|
Ok(FlagDeclaration { name, description })
|
||||||
return Err(anyhow!("missing 'value' field"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut values: Vec<Value> = vec![];
|
|
||||||
for proto_value in proto.value.into_iter() {
|
|
||||||
let v: Value = proto_value.try_into()?;
|
|
||||||
if values.iter().any(|w| v.since == w.since) {
|
|
||||||
let msg = match v.since {
|
|
||||||
None => format!("flag {}: multiple default values", name),
|
|
||||||
Some(x) => format!("flag {}: multiple values for since={}", name, x),
|
|
||||||
};
|
|
||||||
return Err(anyhow!(msg));
|
|
||||||
}
|
|
||||||
values.push(v);
|
|
||||||
}
|
|
||||||
values.sort_by_key(|v| v.since);
|
|
||||||
|
|
||||||
Ok(Flag { name, description, values })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct Namespace {
|
pub struct FlagDeclarations {
|
||||||
pub namespace: String,
|
pub namespace: String,
|
||||||
pub flags: Vec<Flag>,
|
pub flags: Vec<FlagDeclaration>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Namespace {
|
impl FlagDeclarations {
|
||||||
pub fn try_from_text_proto(text_proto: &str) -> Result<Namespace> {
|
pub fn try_from_text_proto(text_proto: &str) -> Result<FlagDeclarations> {
|
||||||
let proto: ProtoNamespace = 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(namespace) = proto.namespace else {
|
||||||
return Err(anyhow!("missing 'namespace' field"));
|
return Err(anyhow!("missing 'namespace' field"));
|
||||||
|
@ -194,35 +124,35 @@ impl Namespace {
|
||||||
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(Namespace { namespace, flags })
|
Ok(FlagDeclarations { namespace, flags })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct Override {
|
pub struct FlagValue {
|
||||||
pub namespace: String,
|
pub namespace: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub state: FlagState,
|
pub state: FlagState,
|
||||||
pub permission: Permission,
|
pub permission: Permission,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Override {
|
impl FlagValue {
|
||||||
#[allow(dead_code)] // only used in unit tests
|
#[allow(dead_code)] // only used in unit tests
|
||||||
pub fn try_from_text_proto(text_proto: &str) -> Result<Override> {
|
pub fn try_from_text_proto(text_proto: &str) -> Result<FlagValue> {
|
||||||
let proto: ProtoFlagOverride = crate::protos::try_from_text_proto(text_proto)?;
|
let proto: ProtoFlagValue = crate::protos::try_from_text_proto(text_proto)?;
|
||||||
proto.try_into()
|
proto.try_into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_from_text_proto_list(text_proto: &str) -> Result<Vec<Override>> {
|
pub fn try_from_text_proto_list(text_proto: &str) -> Result<Vec<FlagValue>> {
|
||||||
let proto: ProtoFlagOverrides = crate::protos::try_from_text_proto(text_proto)?;
|
let proto: ProtoFlagValues = crate::protos::try_from_text_proto(text_proto)?;
|
||||||
proto.flag_override.into_iter().map(|proto_flag| proto_flag.try_into()).collect()
|
proto.flag_value.into_iter().map(|proto_flag| proto_flag.try_into()).collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<ProtoFlagOverride> for Override {
|
impl TryFrom<ProtoFlagValue> for FlagValue {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
fn try_from(proto: ProtoFlagOverride) -> Result<Self, Self::Error> {
|
fn try_from(proto: ProtoFlagValue) -> Result<Self, Self::Error> {
|
||||||
let Some(namespace) = proto.namespace else {
|
let Some(namespace) = proto.namespace else {
|
||||||
return Err(anyhow!("missing 'namespace' field"));
|
return Err(anyhow!("missing 'namespace' field"));
|
||||||
};
|
};
|
||||||
|
@ -237,7 +167,7 @@ impl TryFrom<ProtoFlagOverride> for Override {
|
||||||
return Err(anyhow!("missing 'permission' field"));
|
return Err(anyhow!("missing 'permission' field"));
|
||||||
};
|
};
|
||||||
let permission = proto_permission.try_into()?;
|
let permission = proto_permission.try_into()?;
|
||||||
Ok(Override { namespace, name, state, permission })
|
Ok(FlagValue { namespace, name, state, permission })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -282,29 +212,16 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_flag_try_from_text_proto() {
|
fn test_flag_try_from_text_proto() {
|
||||||
let expected = Flag {
|
let expected = FlagDeclaration {
|
||||||
name: "1234".to_owned(),
|
name: "1234".to_owned(),
|
||||||
description: "Description of the flag".to_owned(),
|
description: "Description of the flag".to_owned(),
|
||||||
values: vec![
|
|
||||||
Value::default(FlagState::Disabled, Permission::ReadOnly),
|
|
||||||
Value::new(FlagState::Enabled, Permission::ReadWrite, 8),
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let s = r#"
|
let s = r#"
|
||||||
name: "1234"
|
name: "1234"
|
||||||
description: "Description of the flag"
|
description: "Description of the flag"
|
||||||
value {
|
|
||||||
state: DISABLED
|
|
||||||
permission: READ_ONLY
|
|
||||||
}
|
|
||||||
value {
|
|
||||||
state: ENABLED
|
|
||||||
permission: READ_WRITE
|
|
||||||
since: 8
|
|
||||||
}
|
|
||||||
"#;
|
"#;
|
||||||
let actual = Flag::try_from_text_proto(s).unwrap();
|
let actual = FlagDeclaration::try_from_text_proto(s).unwrap();
|
||||||
|
|
||||||
assert_eq!(expected, actual);
|
assert_eq!(expected, actual);
|
||||||
}
|
}
|
||||||
|
@ -313,52 +230,24 @@ mod tests {
|
||||||
fn test_flag_try_from_text_proto_bad_input() {
|
fn test_flag_try_from_text_proto_bad_input() {
|
||||||
let s = r#"
|
let s = r#"
|
||||||
name: "a"
|
name: "a"
|
||||||
description: "Description of the flag"
|
|
||||||
"#;
|
"#;
|
||||||
let error = Flag::try_from_text_proto(s).unwrap_err();
|
let error = FlagDeclaration::try_from_text_proto(s).unwrap_err();
|
||||||
assert_eq!(format!("{:?}", error), "missing 'value' field");
|
|
||||||
|
|
||||||
let s = r#"
|
|
||||||
description: "Description of the flag"
|
|
||||||
value {
|
|
||||||
state: ENABLED
|
|
||||||
permission: READ_ONLY
|
|
||||||
}
|
|
||||||
"#;
|
|
||||||
let error = Flag::try_from_text_proto(s).unwrap_err();
|
|
||||||
assert!(format!("{:?}", error).contains("Message not initialized"));
|
assert!(format!("{:?}", error).contains("Message not initialized"));
|
||||||
|
|
||||||
let s = r#"
|
let s = r#"
|
||||||
name: "a"
|
|
||||||
description: "Description of the flag"
|
description: "Description of the flag"
|
||||||
value {
|
|
||||||
state: ENABLED
|
|
||||||
permission: READ_ONLY
|
|
||||||
}
|
|
||||||
value {
|
|
||||||
state: ENABLED
|
|
||||||
permission: READ_ONLY
|
|
||||||
}
|
|
||||||
"#;
|
"#;
|
||||||
let error = Flag::try_from_text_proto(s).unwrap_err();
|
let error = FlagDeclaration::try_from_text_proto(s).unwrap_err();
|
||||||
assert_eq!(format!("{:?}", error), "flag a: multiple default values");
|
assert!(format!("{:?}", error).contains("Message not initialized"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_namespace_try_from_text_proto() {
|
fn test_namespace_try_from_text_proto() {
|
||||||
let expected = Namespace {
|
let expected = FlagDeclarations {
|
||||||
namespace: "ns".to_owned(),
|
namespace: "ns".to_owned(),
|
||||||
flags: vec![
|
flags: vec![
|
||||||
Flag {
|
FlagDeclaration { name: "a".to_owned(), description: "A".to_owned() },
|
||||||
name: "a".to_owned(),
|
FlagDeclaration { name: "b".to_owned(), description: "B".to_owned() },
|
||||||
description: "A".to_owned(),
|
|
||||||
values: vec![Value::default(FlagState::Enabled, Permission::ReadOnly)],
|
|
||||||
},
|
|
||||||
Flag {
|
|
||||||
name: "b".to_owned(),
|
|
||||||
description: "B".to_owned(),
|
|
||||||
values: vec![Value::default(FlagState::Disabled, Permission::ReadWrite)],
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -367,28 +256,20 @@ mod tests {
|
||||||
flag {
|
flag {
|
||||||
name: "a"
|
name: "a"
|
||||||
description: "A"
|
description: "A"
|
||||||
value {
|
|
||||||
state: ENABLED
|
|
||||||
permission: READ_ONLY
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
flag {
|
flag {
|
||||||
name: "b"
|
name: "b"
|
||||||
description: "B"
|
description: "B"
|
||||||
value {
|
|
||||||
state: DISABLED
|
|
||||||
permission: READ_WRITE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
"#;
|
"#;
|
||||||
let actual = Namespace::try_from_text_proto(s).unwrap();
|
let actual = FlagDeclarations::try_from_text_proto(s).unwrap();
|
||||||
|
|
||||||
assert_eq!(expected, actual);
|
assert_eq!(expected, actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_override_try_from_text_proto_list() {
|
fn test_flag_declaration_try_from_text_proto_list() {
|
||||||
let expected = Override {
|
let expected = FlagValue {
|
||||||
namespace: "ns".to_owned(),
|
namespace: "ns".to_owned(),
|
||||||
name: "1234".to_owned(),
|
name: "1234".to_owned(),
|
||||||
state: FlagState::Enabled,
|
state: FlagState::Enabled,
|
||||||
|
@ -401,32 +282,8 @@ mod tests {
|
||||||
state: ENABLED
|
state: ENABLED
|
||||||
permission: READ_ONLY
|
permission: READ_ONLY
|
||||||
"#;
|
"#;
|
||||||
let actual = Override::try_from_text_proto(s).unwrap();
|
let actual = FlagValue::try_from_text_proto(s).unwrap();
|
||||||
|
|
||||||
assert_eq!(expected, actual);
|
assert_eq!(expected, actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_flag_resolve() {
|
|
||||||
let flag = Flag {
|
|
||||||
name: "a".to_owned(),
|
|
||||||
description: "A".to_owned(),
|
|
||||||
values: vec![
|
|
||||||
Value::default(FlagState::Disabled, Permission::ReadOnly),
|
|
||||||
Value::new(FlagState::Disabled, Permission::ReadWrite, 10),
|
|
||||||
Value::new(FlagState::Enabled, Permission::ReadOnly, 20),
|
|
||||||
Value::new(FlagState::Enabled, Permission::ReadWrite, 30),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
assert_eq!((FlagState::Disabled, Permission::ReadOnly), flag.resolve(0));
|
|
||||||
assert_eq!((FlagState::Disabled, Permission::ReadOnly), flag.resolve(9));
|
|
||||||
assert_eq!((FlagState::Disabled, Permission::ReadWrite), flag.resolve(10));
|
|
||||||
assert_eq!((FlagState::Disabled, Permission::ReadWrite), flag.resolve(11));
|
|
||||||
assert_eq!((FlagState::Disabled, Permission::ReadWrite), flag.resolve(19));
|
|
||||||
assert_eq!((FlagState::Enabled, Permission::ReadOnly), flag.resolve(20));
|
|
||||||
assert_eq!((FlagState::Enabled, Permission::ReadOnly), flag.resolve(21));
|
|
||||||
assert_eq!((FlagState::Enabled, Permission::ReadOnly), flag.resolve(29));
|
|
||||||
assert_eq!((FlagState::Enabled, Permission::ReadWrite), flag.resolve(30));
|
|
||||||
assert_eq!((FlagState::Enabled, Permission::ReadWrite), flag.resolve(10_000));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,13 +14,16 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, bail, ensure, Result};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
use crate::aconfig::{Flag, FlagState, Override, Permission};
|
use crate::aconfig::{FlagDeclaration, FlagState, FlagValue, Permission};
|
||||||
use crate::commands::Source;
|
use crate::commands::Source;
|
||||||
|
|
||||||
|
const DEFAULT_FLAG_STATE: FlagState = FlagState::Disabled;
|
||||||
|
const DEFAULT_FLAG_PERMISSION: Permission = Permission::ReadWrite;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub struct Tracepoint {
|
pub struct Tracepoint {
|
||||||
pub source: Source,
|
pub source: Source,
|
||||||
|
@ -44,14 +47,13 @@ pub struct Item {
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub struct Cache {
|
pub struct Cache {
|
||||||
build_id: u32,
|
|
||||||
namespace: String,
|
namespace: String,
|
||||||
items: Vec<Item>,
|
items: Vec<Item>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cache {
|
impl Cache {
|
||||||
pub fn new(build_id: u32, namespace: String) -> Cache {
|
pub fn new(namespace: String) -> Cache {
|
||||||
Cache { build_id, namespace, items: vec![] }
|
Cache { namespace, items: vec![] }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_from_reader(reader: impl Read) -> Result<Cache> {
|
pub fn read_from_reader(reader: impl Read) -> Result<Cache> {
|
||||||
|
@ -62,40 +64,51 @@ impl Cache {
|
||||||
serde_json::to_writer(writer, self).map_err(|e| e.into())
|
serde_json::to_writer(writer, self).map_err(|e| e.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_flag(&mut self, source: Source, flag: Flag) -> Result<()> {
|
pub fn add_flag_declaration(
|
||||||
if self.items.iter().any(|item| item.name == flag.name) {
|
&mut self,
|
||||||
|
source: Source,
|
||||||
|
declaration: FlagDeclaration,
|
||||||
|
) -> Result<()> {
|
||||||
|
if self.items.iter().any(|item| item.name == declaration.name) {
|
||||||
return Err(anyhow!(
|
return Err(anyhow!(
|
||||||
"failed to add flag {} from {}: flag already defined",
|
"failed to declare flag {} from {}: flag already declared",
|
||||||
flag.name,
|
declaration.name,
|
||||||
source,
|
source,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let (state, permission) = flag.resolve(self.build_id);
|
|
||||||
self.items.push(Item {
|
self.items.push(Item {
|
||||||
namespace: self.namespace.clone(),
|
namespace: self.namespace.clone(),
|
||||||
name: flag.name.clone(),
|
name: declaration.name.clone(),
|
||||||
description: flag.description,
|
description: declaration.description,
|
||||||
state,
|
state: DEFAULT_FLAG_STATE,
|
||||||
permission,
|
permission: DEFAULT_FLAG_PERMISSION,
|
||||||
trace: vec![Tracepoint { source, state, permission }],
|
trace: vec![Tracepoint {
|
||||||
|
source,
|
||||||
|
state: DEFAULT_FLAG_STATE,
|
||||||
|
permission: DEFAULT_FLAG_PERMISSION,
|
||||||
|
}],
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_override(&mut self, source: Source, override_: Override) -> Result<()> {
|
pub fn add_flag_value(&mut self, source: Source, value: FlagValue) -> Result<()> {
|
||||||
if override_.namespace != self.namespace {
|
ensure!(
|
||||||
// TODO: print warning?
|
value.namespace == self.namespace,
|
||||||
return Ok(());
|
"failed to set values for flag {}/{} from {}: expected namespace {}",
|
||||||
}
|
value.namespace,
|
||||||
let Some(existing_item) = self.items.iter_mut().find(|item| item.name == override_.name) else {
|
value.name,
|
||||||
return Err(anyhow!("failed to override flag {}: unknown flag", override_.name));
|
source,
|
||||||
|
self.namespace
|
||||||
|
);
|
||||||
|
let Some(existing_item) = self.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);
|
||||||
};
|
};
|
||||||
existing_item.state = override_.state;
|
existing_item.state = value.state;
|
||||||
existing_item.permission = override_.permission;
|
existing_item.permission = value.permission;
|
||||||
existing_item.trace.push(Tracepoint {
|
existing_item.trace.push(Tracepoint {
|
||||||
source,
|
source,
|
||||||
state: override_.state,
|
state: value.state,
|
||||||
permission: override_.permission,
|
permission: value.permission,
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -112,49 +125,41 @@ impl Cache {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::aconfig::{FlagState, Permission, Value};
|
use crate::aconfig::{FlagState, Permission};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_add_flag() {
|
fn test_add_flag_declaration() {
|
||||||
let mut cache = Cache::new(1, "ns".to_string());
|
let mut cache = Cache::new("ns".to_string());
|
||||||
cache
|
cache
|
||||||
.add_flag(
|
.add_flag_declaration(
|
||||||
Source::File("first.txt".to_string()),
|
Source::File("first.txt".to_string()),
|
||||||
Flag {
|
FlagDeclaration { name: "foo".to_string(), description: "desc".to_string() },
|
||||||
name: "foo".to_string(),
|
|
||||||
description: "desc".to_string(),
|
|
||||||
values: vec![Value::default(FlagState::Enabled, Permission::ReadOnly)],
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let error = cache
|
let error = cache
|
||||||
.add_flag(
|
.add_flag_declaration(
|
||||||
Source::File("second.txt".to_string()),
|
Source::File("second.txt".to_string()),
|
||||||
Flag {
|
FlagDeclaration { name: "foo".to_string(), description: "desc".to_string() },
|
||||||
name: "foo".to_string(),
|
|
||||||
description: "desc".to_string(),
|
|
||||||
values: vec![Value::default(FlagState::Disabled, Permission::ReadOnly)],
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&format!("{:?}", error),
|
&format!("{:?}", error),
|
||||||
"failed to add flag foo from second.txt: flag already defined"
|
"failed to declare flag foo from second.txt: flag already declared"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_add_override() {
|
fn test_add_flag_value() {
|
||||||
fn check(cache: &Cache, name: &str, expected: (FlagState, Permission)) -> bool {
|
fn check(cache: &Cache, name: &str, expected: (FlagState, Permission)) -> bool {
|
||||||
let item = cache.iter().find(|&item| item.name == name).unwrap();
|
let item = cache.iter().find(|&item| item.name == name).unwrap();
|
||||||
item.state == expected.0 && item.permission == expected.1
|
item.state == expected.0 && item.permission == expected.1
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut cache = Cache::new(1, "ns".to_string());
|
let mut cache = Cache::new("ns".to_string());
|
||||||
let error = cache
|
let error = cache
|
||||||
.add_override(
|
.add_flag_value(
|
||||||
Source::Memory,
|
Source::Memory,
|
||||||
Override {
|
FlagValue {
|
||||||
namespace: "ns".to_string(),
|
namespace: "ns".to_string(),
|
||||||
name: "foo".to_string(),
|
name: "foo".to_string(),
|
||||||
state: FlagState::Enabled,
|
state: FlagState::Enabled,
|
||||||
|
@ -162,39 +167,36 @@ mod tests {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
assert_eq!(&format!("{:?}", error), "failed to override flag foo: unknown flag");
|
assert_eq!(
|
||||||
|
&format!("{:?}", error),
|
||||||
|
"failed to set values for flag ns/foo from <memory>: flag not declared"
|
||||||
|
);
|
||||||
|
|
||||||
cache
|
cache
|
||||||
.add_flag(
|
.add_flag_declaration(
|
||||||
Source::File("first.txt".to_string()),
|
Source::File("first.txt".to_string()),
|
||||||
Flag {
|
FlagDeclaration { name: "foo".to_string(), description: "desc".to_string() },
|
||||||
name: "foo".to_string(),
|
|
||||||
description: "desc".to_string(),
|
|
||||||
values: vec![Value::default(FlagState::Enabled, Permission::ReadOnly)],
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
dbg!(&cache);
|
assert!(check(&cache, "foo", (DEFAULT_FLAG_STATE, DEFAULT_FLAG_PERMISSION)));
|
||||||
assert!(check(&cache, "foo", (FlagState::Enabled, Permission::ReadOnly)));
|
|
||||||
|
|
||||||
cache
|
cache
|
||||||
.add_override(
|
.add_flag_value(
|
||||||
Source::Memory,
|
Source::Memory,
|
||||||
Override {
|
FlagValue {
|
||||||
namespace: "ns".to_string(),
|
namespace: "ns".to_string(),
|
||||||
name: "foo".to_string(),
|
name: "foo".to_string(),
|
||||||
state: FlagState::Disabled,
|
state: FlagState::Disabled,
|
||||||
permission: Permission::ReadWrite,
|
permission: Permission::ReadOnly,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
dbg!(&cache);
|
assert!(check(&cache, "foo", (FlagState::Disabled, Permission::ReadOnly)));
|
||||||
assert!(check(&cache, "foo", (FlagState::Disabled, Permission::ReadWrite)));
|
|
||||||
|
|
||||||
cache
|
cache
|
||||||
.add_override(
|
.add_flag_value(
|
||||||
Source::Memory,
|
Source::Memory,
|
||||||
Override {
|
FlagValue {
|
||||||
namespace: "ns".to_string(),
|
namespace: "ns".to_string(),
|
||||||
name: "foo".to_string(),
|
name: "foo".to_string(),
|
||||||
state: FlagState::Enabled,
|
state: FlagState::Enabled,
|
||||||
|
@ -205,17 +207,18 @@ mod tests {
|
||||||
assert!(check(&cache, "foo", (FlagState::Enabled, Permission::ReadWrite)));
|
assert!(check(&cache, "foo", (FlagState::Enabled, Permission::ReadWrite)));
|
||||||
|
|
||||||
// different namespace -> no-op
|
// different namespace -> no-op
|
||||||
cache
|
let error = cache
|
||||||
.add_override(
|
.add_flag_value(
|
||||||
Source::Memory,
|
Source::Memory,
|
||||||
Override {
|
FlagValue {
|
||||||
namespace: "some-other-namespace".to_string(),
|
namespace: "some-other-namespace".to_string(),
|
||||||
name: "foo".to_string(),
|
name: "foo".to_string(),
|
||||||
state: FlagState::Enabled,
|
state: FlagState::Enabled,
|
||||||
permission: Permission::ReadOnly,
|
permission: Permission::ReadOnly,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap_err();
|
||||||
|
assert_eq!(&format!("{:?}", error), "failed to set values for flag some-other-namespace/foo from <memory>: expected namespace ns");
|
||||||
assert!(check(&cache, "foo", (FlagState::Enabled, Permission::ReadWrite)));
|
assert!(check(&cache, "foo", (FlagState::Enabled, Permission::ReadWrite)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,13 +20,9 @@ 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::commands::OutputFile;
|
||||||
|
|
||||||
pub struct GeneratedFile {
|
pub fn generate_java_code(cache: &Cache) -> Result<OutputFile> {
|
||||||
pub file_content: String,
|
|
||||||
pub file_name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn generate_java_code(cache: &Cache) -> Result<GeneratedFile> {
|
|
||||||
let class_elements: Vec<ClassElement> = cache.iter().map(create_class_element).collect();
|
let class_elements: Vec<ClassElement> = cache.iter().map(create_class_element).collect();
|
||||||
let readwrite = class_elements.iter().any(|item| item.readwrite);
|
let readwrite = class_elements.iter().any(|item| item.readwrite);
|
||||||
let namespace = uppercase_first_letter(
|
let namespace = uppercase_first_letter(
|
||||||
|
@ -35,8 +31,9 @@ pub fn generate_java_code(cache: &Cache) -> Result<GeneratedFile> {
|
||||||
let context = Context { namespace: namespace.clone(), readwrite, class_elements };
|
let context = Context { namespace: namespace.clone(), 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 file_content = template.render("java_code_gen", &context)?;
|
let contents = template.render("java_code_gen", &context)?;
|
||||||
Ok(GeneratedFile { file_content, file_name: format!("{}.java", namespace) })
|
let path = ["com", "android", "internal", "aconfig", &(namespace + ".java")].iter().collect();
|
||||||
|
Ok(OutputFile { contents: contents.into(), path })
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
|
@ -87,56 +84,67 @@ fn uppercase_first_letter(s: &str) -> String {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::aconfig::{Flag, Value};
|
use crate::aconfig::{FlagDeclaration, FlagValue};
|
||||||
use crate::commands::Source;
|
use crate::commands::Source;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_generate_java_code() {
|
fn test_generate_java_code() {
|
||||||
let namespace = "TeSTFlaG";
|
let namespace = "TeSTFlaG";
|
||||||
let mut cache = Cache::new(1, namespace.to_string());
|
let mut cache = Cache::new(namespace.to_string());
|
||||||
cache
|
cache
|
||||||
.add_flag(
|
.add_flag_declaration(
|
||||||
Source::File("test.txt".to_string()),
|
Source::File("test.txt".to_string()),
|
||||||
Flag {
|
FlagDeclaration {
|
||||||
name: "test".to_string(),
|
name: "test".to_string(),
|
||||||
description: "buildtime enable".to_string(),
|
description: "buildtime enable".to_string(),
|
||||||
values: vec![Value::default(FlagState::Enabled, Permission::ReadOnly)],
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
cache
|
cache
|
||||||
.add_flag(
|
.add_flag_declaration(
|
||||||
Source::File("test2.txt".to_string()),
|
Source::File("test2.txt".to_string()),
|
||||||
Flag {
|
FlagDeclaration {
|
||||||
name: "test2".to_string(),
|
name: "test2".to_string(),
|
||||||
description: "runtime disable".to_string(),
|
description: "runtime disable".to_string(),
|
||||||
values: vec![Value::default(FlagState::Disabled, Permission::ReadWrite)],
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let expect_content = "package com.android.aconfig;
|
cache
|
||||||
|
.add_flag_value(
|
||||||
|
Source::Memory,
|
||||||
|
FlagValue {
|
||||||
|
namespace: namespace.to_string(),
|
||||||
|
name: "test".to_string(),
|
||||||
|
state: FlagState::Disabled,
|
||||||
|
permission: Permission::ReadOnly,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let expect_content = r#"package com.android.internal.aconfig;
|
||||||
|
|
||||||
import android.provider.DeviceConfig;
|
import android.provider.DeviceConfig;
|
||||||
|
|
||||||
public final class Testflag {
|
public final class Testflag {
|
||||||
|
|
||||||
public static boolean test() {
|
public static boolean test() {
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean test2() {
|
public static boolean test2() {
|
||||||
return DeviceConfig.getBoolean(
|
return DeviceConfig.getBoolean(
|
||||||
\"Testflag\",
|
"Testflag",
|
||||||
\"test2__test2\",
|
"test2__test2",
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
";
|
"#;
|
||||||
let expected_file_name = format!("{}.java", uppercase_first_letter(namespace));
|
let file = generate_java_code(&cache).unwrap();
|
||||||
let generated_file = generate_java_code(&cache).unwrap();
|
assert_eq!("com/android/internal/aconfig/Testflag.java", file.path.to_str().unwrap());
|
||||||
assert_eq!(expected_file_name, generated_file.file_name);
|
assert_eq!(
|
||||||
assert_eq!(expect_content.replace(' ', ""), generated_file.file_content.replace(' ', ""));
|
expect_content.replace(' ', ""),
|
||||||
|
String::from_utf8(file.contents).unwrap().replace(' ', "")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,10 +20,11 @@ use protobuf::Message;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::aconfig::{Namespace, Override};
|
use crate::aconfig::{FlagDeclarations, FlagValue};
|
||||||
use crate::cache::Cache;
|
use crate::cache::Cache;
|
||||||
use crate::codegen_java::{generate_java_code, GeneratedFile};
|
use crate::codegen_java::generate_java_code;
|
||||||
use crate::protos::ProtoParsedFlags;
|
use crate::protos::ProtoParsedFlags;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
@ -47,45 +48,50 @@ pub struct Input {
|
||||||
pub reader: Box<dyn Read>,
|
pub reader: Box<dyn Read>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_cache(
|
pub struct OutputFile {
|
||||||
build_id: u32,
|
pub path: PathBuf, // relative to some root directory only main knows about
|
||||||
namespace: &str,
|
pub contents: Vec<u8>,
|
||||||
aconfigs: Vec<Input>,
|
}
|
||||||
overrides: Vec<Input>,
|
|
||||||
) -> Result<Cache> {
|
|
||||||
let mut cache = Cache::new(build_id, namespace.to_owned());
|
|
||||||
|
|
||||||
for mut input in aconfigs {
|
pub fn create_cache(
|
||||||
|
namespace: &str,
|
||||||
|
declarations: Vec<Input>,
|
||||||
|
values: Vec<Input>,
|
||||||
|
) -> Result<Cache> {
|
||||||
|
let mut cache = Cache::new(namespace.to_owned());
|
||||||
|
|
||||||
|
for mut input in declarations {
|
||||||
let mut contents = String::new();
|
let mut contents = String::new();
|
||||||
input.reader.read_to_string(&mut contents)?;
|
input.reader.read_to_string(&mut contents)?;
|
||||||
let ns = Namespace::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 == ns.namespace,
|
namespace == dec_list.namespace,
|
||||||
"Failed to parse {}: expected namespace {}, got {}",
|
"Failed to parse {}: expected namespace {}, got {}",
|
||||||
input.source,
|
input.source,
|
||||||
namespace,
|
namespace,
|
||||||
ns.namespace
|
dec_list.namespace
|
||||||
);
|
);
|
||||||
for flag in ns.flags.into_iter() {
|
for d in dec_list.flags.into_iter() {
|
||||||
cache.add_flag(input.source.clone(), flag)?;
|
cache.add_flag_declaration(input.source.clone(), d)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for mut input in overrides {
|
for mut input in values {
|
||||||
let mut contents = String::new();
|
let mut contents = String::new();
|
||||||
input.reader.read_to_string(&mut contents)?;
|
input.reader.read_to_string(&mut contents)?;
|
||||||
let overrides = Override::try_from_text_proto_list(&contents)
|
let values_list = FlagValue::try_from_text_proto_list(&contents)
|
||||||
.with_context(|| format!("Failed to parse {}", input.source))?;
|
.with_context(|| format!("Failed to parse {}", input.source))?;
|
||||||
for override_ in overrides {
|
for v in values_list {
|
||||||
cache.add_override(input.source.clone(), override_)?;
|
// TODO: warn about flag values that do not take effect?
|
||||||
|
let _ = cache.add_flag_value(input.source.clone(), v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(cache)
|
Ok(cache)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_code(cache: &Cache) -> Result<GeneratedFile> {
|
pub fn generate_code(cache: &Cache) -> Result<OutputFile> {
|
||||||
generate_java_code(cache)
|
generate_java_code(cache)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,31 +138,23 @@ mod tests {
|
||||||
flag {
|
flag {
|
||||||
name: "a"
|
name: "a"
|
||||||
description: "Description of a"
|
description: "Description of a"
|
||||||
value {
|
|
||||||
state: ENABLED
|
|
||||||
permission: READ_WRITE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
flag {
|
flag {
|
||||||
name: "b"
|
name: "b"
|
||||||
description: "Description of b"
|
description: "Description of b"
|
||||||
value {
|
|
||||||
state: ENABLED
|
|
||||||
permission: READ_ONLY
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
"#;
|
"#;
|
||||||
let aconfigs = 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_override {
|
flag_value {
|
||||||
namespace: "ns"
|
namespace: "ns"
|
||||||
name: "a"
|
name: "a"
|
||||||
state: DISABLED
|
state: DISABLED
|
||||||
permission: READ_ONLY
|
permission: READ_ONLY
|
||||||
}
|
}
|
||||||
"#;
|
"#;
|
||||||
let overrides = 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(1, "ns", aconfigs, overrides).unwrap()
|
create_cache("ns", declarations, values).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -194,12 +192,12 @@ mod tests {
|
||||||
assert_eq!(item.namespace(), "ns");
|
assert_eq!(item.namespace(), "ns");
|
||||||
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::ENABLED);
|
assert_eq!(item.state(), ProtoFlagState::DISABLED);
|
||||||
assert_eq!(item.permission(), ProtoFlagPermission::READ_ONLY);
|
assert_eq!(item.permission(), ProtoFlagPermission::READ_WRITE);
|
||||||
let mut tp = ProtoTracepoint::new();
|
let mut tp = ProtoTracepoint::new();
|
||||||
tp.set_source("<memory>".to_string());
|
tp.set_source("<memory>".to_string());
|
||||||
tp.set_state(ProtoFlagState::ENABLED);
|
tp.set_state(ProtoFlagState::DISABLED);
|
||||||
tp.set_permission(ProtoFlagPermission::READ_ONLY);
|
tp.set_permission(ProtoFlagPermission::READ_WRITE);
|
||||||
assert_eq!(item.trace, vec![tp]);
|
assert_eq!(item.trace, vec![tp]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,11 +16,12 @@
|
||||||
|
|
||||||
//! `aconfig` is a build time tool to manage build time configurations, such as feature flags.
|
//! `aconfig` is a build time tool to manage build time configurations, such as feature flags.
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{anyhow, ensure, Result};
|
||||||
use clap::{builder::ArgAction, builder::EnumValueParser, Arg, ArgMatches, Command};
|
use clap::{builder::ArgAction, builder::EnumValueParser, Arg, ArgMatches, Command};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
mod aconfig;
|
mod aconfig;
|
||||||
mod cache;
|
mod cache;
|
||||||
|
@ -29,22 +30,16 @@ mod commands;
|
||||||
mod protos;
|
mod protos;
|
||||||
|
|
||||||
use crate::cache::Cache;
|
use crate::cache::Cache;
|
||||||
use commands::{Input, Source};
|
use commands::{Input, OutputFile, Source};
|
||||||
|
|
||||||
fn cli() -> Command {
|
fn cli() -> Command {
|
||||||
Command::new("aconfig")
|
Command::new("aconfig")
|
||||||
.subcommand_required(true)
|
.subcommand_required(true)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
Command::new("create-cache")
|
Command::new("create-cache")
|
||||||
.arg(
|
|
||||||
Arg::new("build-id")
|
|
||||||
.long("build-id")
|
|
||||||
.value_parser(clap::value_parser!(u32))
|
|
||||||
.required(true),
|
|
||||||
)
|
|
||||||
.arg(Arg::new("namespace").long("namespace").required(true))
|
.arg(Arg::new("namespace").long("namespace").required(true))
|
||||||
.arg(Arg::new("aconfig").long("aconfig").action(ArgAction::Append))
|
.arg(Arg::new("declarations").long("declarations").action(ArgAction::Append))
|
||||||
.arg(Arg::new("override").long("override").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)),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
|
@ -74,15 +69,30 @@ fn open_zero_or_more_files(matches: &ArgMatches, arg_name: &str) -> Result<Vec<I
|
||||||
Ok(opened_files)
|
Ok(opened_files)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_output_file_realtive_to_dir(root: &Path, output_file: &OutputFile) -> Result<()> {
|
||||||
|
ensure!(
|
||||||
|
root.is_dir(),
|
||||||
|
"output directory {} does not exist or is not a directory",
|
||||||
|
root.display()
|
||||||
|
);
|
||||||
|
let path = root.join(output_file.path.clone());
|
||||||
|
let parent = path
|
||||||
|
.parent()
|
||||||
|
.ok_or(anyhow!("unable to locate parent of output file {}", path.display()))?;
|
||||||
|
fs::create_dir_all(parent)?;
|
||||||
|
let mut file = fs::File::create(path)?;
|
||||||
|
file.write_all(&output_file.contents)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
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 build_id = *sub_matches.get_one::<u32>("build-id").unwrap();
|
|
||||||
let namespace = sub_matches.get_one::<String>("namespace").unwrap();
|
let namespace = sub_matches.get_one::<String>("namespace").unwrap();
|
||||||
let aconfigs = open_zero_or_more_files(sub_matches, "aconfig")?;
|
let declarations = open_zero_or_more_files(sub_matches, "declarations")?;
|
||||||
let overrides = open_zero_or_more_files(sub_matches, "override")?;
|
let values = open_zero_or_more_files(sub_matches, "values")?;
|
||||||
let cache = commands::create_cache(build_id, namespace, aconfigs, overrides)?;
|
let cache = commands::create_cache(namespace, declarations, values)?;
|
||||||
let path = sub_matches.get_one::<String>("cache").unwrap();
|
let path = sub_matches.get_one::<String>("cache").unwrap();
|
||||||
let file = fs::File::create(path)?;
|
let file = fs::File::create(path)?;
|
||||||
cache.write_to_writer(file)?;
|
cache.write_to_writer(file)?;
|
||||||
|
@ -91,12 +101,9 @@ fn main() -> Result<()> {
|
||||||
let path = sub_matches.get_one::<String>("cache").unwrap();
|
let path = sub_matches.get_one::<String>("cache").unwrap();
|
||||||
let file = fs::File::open(path)?;
|
let file = fs::File::open(path)?;
|
||||||
let cache = Cache::read_from_reader(file)?;
|
let cache = Cache::read_from_reader(file)?;
|
||||||
let out = sub_matches.get_one::<String>("out").unwrap();
|
let dir = PathBuf::from(sub_matches.get_one::<String>("out").unwrap());
|
||||||
let generated_file = commands::generate_code(&cache).unwrap();
|
let generated_file = commands::generate_code(&cache).unwrap();
|
||||||
fs::write(
|
write_output_file_realtive_to_dir(&dir, &generated_file)?;
|
||||||
format!("{}/{}", out, generated_file.file_name),
|
|
||||||
generated_file.file_content,
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
Some(("dump", sub_matches)) => {
|
Some(("dump", sub_matches)) => {
|
||||||
let path = sub_matches.get_one::<String>("cache").unwrap();
|
let path = sub_matches.get_one::<String>("cache").unwrap();
|
||||||
|
|
|
@ -28,19 +28,16 @@
|
||||||
|
|
||||||
// ---- When building with the Android tool-chain ----
|
// ---- When building with the Android tool-chain ----
|
||||||
#[cfg(not(feature = "cargo"))]
|
#[cfg(not(feature = "cargo"))]
|
||||||
pub use aconfig_protos::aconfig::Namespace as ProtoNamespace;
|
pub use aconfig_protos::aconfig::Flag_declaration as ProtoFlagDeclaration;
|
||||||
|
|
||||||
#[cfg(not(feature = "cargo"))]
|
#[cfg(not(feature = "cargo"))]
|
||||||
pub use aconfig_protos::aconfig::Flag_value as ProtoFlagDefinitionValue;
|
pub use aconfig_protos::aconfig::Flag_declarations as ProtoFlagDeclarations;
|
||||||
|
|
||||||
#[cfg(not(feature = "cargo"))]
|
#[cfg(not(feature = "cargo"))]
|
||||||
pub use aconfig_protos::aconfig::Flag_definition as ProtoFlagDefinition;
|
pub use aconfig_protos::aconfig::Flag_value as ProtoFlagValue;
|
||||||
|
|
||||||
#[cfg(not(feature = "cargo"))]
|
#[cfg(not(feature = "cargo"))]
|
||||||
pub use aconfig_protos::aconfig::Flag_overrides as ProtoFlagOverrides;
|
pub use aconfig_protos::aconfig::Flag_values as ProtoFlagValues;
|
||||||
|
|
||||||
#[cfg(not(feature = "cargo"))]
|
|
||||||
pub use aconfig_protos::aconfig::Flag_override as ProtoFlagOverride;
|
|
||||||
|
|
||||||
#[cfg(not(feature = "cargo"))]
|
#[cfg(not(feature = "cargo"))]
|
||||||
pub use aconfig_protos::aconfig::Flag_permission as ProtoFlagPermission;
|
pub use aconfig_protos::aconfig::Flag_permission as ProtoFlagPermission;
|
||||||
|
@ -62,19 +59,16 @@ pub use aconfig_protos::aconfig::Tracepoint as ProtoTracepoint;
|
||||||
include!(concat!(env!("OUT_DIR"), "/aconfig_proto/mod.rs"));
|
include!(concat!(env!("OUT_DIR"), "/aconfig_proto/mod.rs"));
|
||||||
|
|
||||||
#[cfg(feature = "cargo")]
|
#[cfg(feature = "cargo")]
|
||||||
pub use aconfig::Namespace as ProtoNamespace;
|
pub use aconfig::Flag_declaration as ProtoFlagDeclaration;
|
||||||
|
|
||||||
#[cfg(feature = "cargo")]
|
#[cfg(feature = "cargo")]
|
||||||
pub use aconfig::Flag_value as ProtoFlagDefinitionValue;
|
pub use aconfig::Flag_declarations as ProtoFlagDeclarations;
|
||||||
|
|
||||||
#[cfg(feature = "cargo")]
|
#[cfg(feature = "cargo")]
|
||||||
pub use aconfig::Flag_definition as ProtoFlagDefinition;
|
pub use aconfig::Flag_value as ProtoFlagValue;
|
||||||
|
|
||||||
#[cfg(feature = "cargo")]
|
#[cfg(feature = "cargo")]
|
||||||
pub use aconfig::Flag_overrides as ProtoFlagOverrides;
|
pub use aconfig::Flag_values as ProtoFlagValues;
|
||||||
|
|
||||||
#[cfg(feature = "cargo")]
|
|
||||||
pub use aconfig::Flag_override as ProtoFlagOverride;
|
|
||||||
|
|
||||||
#[cfg(feature = "cargo")]
|
#[cfg(feature = "cargo")]
|
||||||
pub use aconfig::Flag_permission as ProtoFlagPermission;
|
pub use aconfig::Flag_permission as ProtoFlagPermission;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package com.android.aconfig;
|
package com.android.internal.aconfig;
|
||||||
{{ if readwrite }}
|
{{ if readwrite }}
|
||||||
import android.provider.DeviceConfig;
|
import android.provider.DeviceConfig;
|
||||||
{{ endif }}
|
{{ endif }}
|
||||||
|
|
Loading…
Reference in a new issue