Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ef0a2c21 authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge changes from topic "aconfig-part-4"

* changes:
  aconfig: introduce namespace, rename proto messages
  aconfig: add dump protobuf format
parents 081bad48 30950789
Loading
Loading
Loading
Loading
+42 −14
Original line number Diff line number Diff line
@@ -20,38 +20,66 @@ syntax = "proto2";

package android.aconfig;

// messages used in both aconfig input and output

enum flag_state {
  ENABLED = 1;
  DISABLED = 2;
}

enum permission {
enum flag_permission {
  READ_ONLY = 1;
  READ_WRITE = 2;
}

message value {
// aconfig input messages: configuration and override data

message flag_value {
  required flag_state state = 1;
  required permission permission = 2;
  required flag_permission permission = 2;
  optional uint32 since = 3;
}

message flag {
  required string id = 1;
message flag_definition {
  required string name = 1;
  required string description = 2;
  repeated value value = 3;
  repeated flag_value value = 3;
};

message android_config {
  repeated flag flag = 1;
message namespace {
  required string namespace = 1;
  repeated flag_definition flag = 2;
};

message override {
  required string id = 1;
  required flag_state state = 2;
  required permission permission = 3;
message flag_override {
  required string namespace = 1;
  required string name = 2;
  required flag_state state = 3;
  required flag_permission permission = 4;
};

message override_config {
  repeated override override = 1;
message flag_overrides {
  repeated flag_override flag_override = 1;
};

// aconfig output messages: parsed and verified configuration and override data

message tracepoint {
  // path to config or override file releative to $TOP
  required string source = 1;
  required flag_state state = 2;
  required flag_permission permission = 3;
}

message parsed_flag {
  required string namespace = 1;
  required string name = 2;
  required string description = 3;
  required flag_state state = 4;
  required flag_permission permission = 5;
  repeated tracepoint trace = 6;
}

message parsed_flags {
  repeated parsed_flag parsed_flag = 1;
}
+136 −56
Original line number Diff line number Diff line
@@ -18,9 +18,11 @@ use anyhow::{anyhow, Context, Error, Result};
use protobuf::{Enum, EnumOrUnknown};
use serde::{Deserialize, Serialize};

use crate::cache::{Cache, Item, Tracepoint};
use crate::protos::{
    ProtoAndroidConfig, ProtoFlag, ProtoFlagState, ProtoOverride, ProtoOverrideConfig,
    ProtoPermission, ProtoValue,
    ProtoFlagDefinition, ProtoFlagDefinitionValue, ProtoFlagOverride, ProtoFlagOverrides,
    ProtoFlagPermission, ProtoFlagState, ProtoNamespace, ProtoParsedFlag, ProtoParsedFlags,
    ProtoTracepoint,
};

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)]
@@ -41,24 +43,42 @@ impl TryFrom<EnumOrUnknown<ProtoFlagState>> for FlagState {
    }
}

impl From<FlagState> for ProtoFlagState {
    fn from(state: FlagState) -> Self {
        match state {
            FlagState::Enabled => ProtoFlagState::ENABLED,
            FlagState::Disabled => ProtoFlagState::DISABLED,
        }
    }
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)]
pub enum Permission {
    ReadOnly,
    ReadWrite,
}

impl TryFrom<EnumOrUnknown<ProtoPermission>> for Permission {
impl TryFrom<EnumOrUnknown<ProtoFlagPermission>> for Permission {
    type Error = Error;

    fn try_from(proto: EnumOrUnknown<ProtoPermission>) -> Result<Self, Self::Error> {
        match ProtoPermission::from_i32(proto.value()) {
            Some(ProtoPermission::READ_ONLY) => Ok(Permission::ReadOnly),
            Some(ProtoPermission::READ_WRITE) => Ok(Permission::ReadWrite),
    fn try_from(proto: EnumOrUnknown<ProtoFlagPermission>) -> Result<Self, Self::Error> {
        match ProtoFlagPermission::from_i32(proto.value()) {
            Some(ProtoFlagPermission::READ_ONLY) => Ok(Permission::ReadOnly),
            Some(ProtoFlagPermission::READ_WRITE) => Ok(Permission::ReadWrite),
            None => Err(anyhow!("unknown permission enum value {}", proto.value())),
        }
    }
}

impl From<Permission> for ProtoFlagPermission {
    fn from(permission: Permission) -> Self {
        match permission {
            Permission::ReadOnly => ProtoFlagPermission::READ_ONLY,
            Permission::ReadWrite => ProtoFlagPermission::READ_WRITE,
        }
    }
}

#[derive(Debug, PartialEq, Eq)]
pub struct Value {
    state: FlagState,
@@ -77,10 +97,10 @@ impl Value {
    }
}

impl TryFrom<ProtoValue> for Value {
impl TryFrom<ProtoFlagDefinitionValue> for Value {
    type Error = Error;

    fn try_from(proto: ProtoValue) -> Result<Self, Self::Error> {
    fn try_from(proto: ProtoFlagDefinitionValue) -> Result<Self, Self::Error> {
        let Some(proto_state) = proto.state else {
            return Err(anyhow!("missing 'state' field"));
        };
@@ -95,7 +115,7 @@ impl TryFrom<ProtoValue> for Value {

#[derive(Debug, PartialEq, Eq)]
pub struct Flag {
    pub id: String,
    pub name: String,
    pub description: String,

    // ordered by Value.since; guaranteed to contain at least one item (the default value, with
@@ -106,17 +126,11 @@ pub struct Flag {
impl Flag {
    #[allow(dead_code)] // only used in unit tests
    pub fn try_from_text_proto(text_proto: &str) -> Result<Flag> {
        let proto: ProtoFlag = crate::protos::try_from_text_proto(text_proto)
        let proto: ProtoFlagDefinition = crate::protos::try_from_text_proto(text_proto)
            .with_context(|| text_proto.to_owned())?;
        proto.try_into()
    }

    pub fn try_from_text_proto_list(text_proto: &str) -> Result<Vec<Flag>> {
        let proto: ProtoAndroidConfig = crate::protos::try_from_text_proto(text_proto)
            .with_context(|| text_proto.to_owned())?;
        proto.flag.into_iter().map(|proto_flag| proto_flag.try_into()).collect()
    }

    pub fn resolve(&self, build_id: u32) -> (FlagState, Permission) {
        let mut state = self.values[0].state;
        let mut permission = self.values[0].permission;
@@ -131,12 +145,12 @@ impl Flag {
    }
}

impl TryFrom<ProtoFlag> for Flag {
impl TryFrom<ProtoFlagDefinition> for Flag {
    type Error = Error;

    fn try_from(proto: ProtoFlag) -> Result<Self, Self::Error> {
        let Some(id) = proto.id else {
            return Err(anyhow!("missing 'id' field"));
    fn try_from(proto: ProtoFlagDefinition) -> Result<Self, Self::Error> {
        let Some(name) = proto.name else {
            return Err(anyhow!("missing 'name' field"));
        };
        let Some(description) = proto.description else {
            return Err(anyhow!("missing 'description' field"));
@@ -150,8 +164,8 @@ impl TryFrom<ProtoFlag> for Flag {
            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", id),
                    Some(x) => format!("flag {}: multiple values for since={}", id, x),
                    None => format!("flag {}: multiple default values", name),
                    Some(x) => format!("flag {}: multiple values for since={}", name, x),
                };
                return Err(anyhow!(msg));
            }
@@ -159,13 +173,35 @@ impl TryFrom<ProtoFlag> for Flag {
        }
        values.sort_by_key(|v| v.since);

        Ok(Flag { id, description, values })
        Ok(Flag { name, description, values })
    }
}

#[derive(Debug, PartialEq, Eq)]
pub struct Namespace {
    pub namespace: String,
    pub flags: Vec<Flag>,
}

impl Namespace {
    pub fn try_from_text_proto(text_proto: &str) -> Result<Namespace> {
        let proto: ProtoNamespace = crate::protos::try_from_text_proto(text_proto)
            .with_context(|| text_proto.to_owned())?;
        let Some(namespace) = proto.namespace else {
            return Err(anyhow!("missing 'namespace' field"));
        };
        let mut flags = vec![];
        for proto_flag in proto.flag.into_iter() {
            flags.push(proto_flag.try_into()?);
        }
        Ok(Namespace { namespace, flags })
    }
}

#[derive(Debug, PartialEq, Eq)]
pub struct Override {
    pub id: String,
    pub namespace: String,
    pub name: String,
    pub state: FlagState,
    pub permission: Permission,
}
@@ -173,22 +209,25 @@ pub struct Override {
impl Override {
    #[allow(dead_code)] // only used in unit tests
    pub fn try_from_text_proto(text_proto: &str) -> Result<Override> {
        let proto: ProtoOverride = crate::protos::try_from_text_proto(text_proto)?;
        let proto: ProtoFlagOverride = crate::protos::try_from_text_proto(text_proto)?;
        proto.try_into()
    }

    pub fn try_from_text_proto_list(text_proto: &str) -> Result<Vec<Override>> {
        let proto: ProtoOverrideConfig = crate::protos::try_from_text_proto(text_proto)?;
        proto.override_.into_iter().map(|proto_flag| proto_flag.try_into()).collect()
        let proto: ProtoFlagOverrides = crate::protos::try_from_text_proto(text_proto)?;
        proto.flag_override.into_iter().map(|proto_flag| proto_flag.try_into()).collect()
    }
}

impl TryFrom<ProtoOverride> for Override {
impl TryFrom<ProtoFlagOverride> for Override {
    type Error = Error;

    fn try_from(proto: ProtoOverride) -> Result<Self, Self::Error> {
        let Some(id) = proto.id else {
            return Err(anyhow!("missing 'id' field"));
    fn try_from(proto: ProtoFlagOverride) -> Result<Self, Self::Error> {
        let Some(namespace) = proto.namespace else {
            return Err(anyhow!("missing 'namespace' field"));
        };
        let Some(name) = proto.name else {
            return Err(anyhow!("missing 'name' field"));
        };
        let Some(proto_state) = proto.state else {
            return Err(anyhow!("missing 'state' field"));
@@ -198,7 +237,42 @@ impl TryFrom<ProtoOverride> for Override {
            return Err(anyhow!("missing 'permission' field"));
        };
        let permission = proto_permission.try_into()?;
        Ok(Override { id, state, permission })
        Ok(Override { namespace, name, state, permission })
    }
}

impl From<Cache> for ProtoParsedFlags {
    fn from(cache: Cache) -> Self {
        let mut proto = ProtoParsedFlags::new();
        for item in cache.into_iter() {
            proto.parsed_flag.push(item.into());
        }
        proto
    }
}

impl From<Item> for ProtoParsedFlag {
    fn from(item: Item) -> Self {
        let mut proto = crate::protos::ProtoParsedFlag::new();
        proto.set_namespace(item.namespace.to_owned());
        proto.set_name(item.name.clone());
        proto.set_description(item.description.clone());
        proto.set_state(item.state.into());
        proto.set_permission(item.permission.into());
        for trace in item.trace.into_iter() {
            proto.trace.push(trace.into());
        }
        proto
    }
}

impl From<Tracepoint> for ProtoTracepoint {
    fn from(tracepoint: Tracepoint) -> Self {
        let mut proto = ProtoTracepoint::new();
        proto.set_source(format!("{}", tracepoint.source));
        proto.set_state(tracepoint.state.into());
        proto.set_permission(tracepoint.permission.into());
        proto
    }
}

@@ -209,7 +283,7 @@ mod tests {
    #[test]
    fn test_flag_try_from_text_proto() {
        let expected = Flag {
            id: "1234".to_owned(),
            name: "1234".to_owned(),
            description: "Description of the flag".to_owned(),
            values: vec![
                Value::default(FlagState::Disabled, Permission::ReadOnly),
@@ -218,7 +292,7 @@ mod tests {
        };

        let s = r#"
        id: "1234"
        name: "1234"
        description: "Description of the flag"
        value {
            state: DISABLED
@@ -238,7 +312,7 @@ mod tests {
    #[test]
    fn test_flag_try_from_text_proto_bad_input() {
        let s = r#"
        id: "a"
        name: "a"
        description: "Description of the flag"
        "#;
        let error = Flag::try_from_text_proto(s).unwrap_err();
@@ -255,7 +329,7 @@ mod tests {
        assert!(format!("{:?}", error).contains("Message not initialized"));

        let s = r#"
        id: "a"
        name: "a"
        description: "Description of the flag"
        value {
            state: ENABLED
@@ -271,23 +345,27 @@ mod tests {
    }

    #[test]
    fn test_flag_try_from_text_proto_list() {
        let expected = vec![
    fn test_namespace_try_from_text_proto() {
        let expected = Namespace {
            namespace: "ns".to_owned(),
            flags: vec![
                Flag {
                id: "a".to_owned(),
                    name: "a".to_owned(),
                    description: "A".to_owned(),
                    values: vec![Value::default(FlagState::Enabled, Permission::ReadOnly)],
                },
                Flag {
                id: "b".to_owned(),
                    name: "b".to_owned(),
                    description: "B".to_owned(),
                    values: vec![Value::default(FlagState::Disabled, Permission::ReadWrite)],
                },
        ];
            ],
        };

        let s = r#"
        namespace: "ns"
        flag {
            id: "a"
            name: "a"
            description: "A"
            value {
                state: ENABLED
@@ -295,7 +373,7 @@ mod tests {
            }
        }
        flag {
            id: "b"
            name: "b"
            description: "B"
            value {
                state: DISABLED
@@ -303,7 +381,7 @@ mod tests {
            }
        }
        "#;
        let actual = Flag::try_from_text_proto_list(s).unwrap();
        let actual = Namespace::try_from_text_proto(s).unwrap();

        assert_eq!(expected, actual);
    }
@@ -311,13 +389,15 @@ mod tests {
    #[test]
    fn test_override_try_from_text_proto_list() {
        let expected = Override {
            id: "1234".to_owned(),
            namespace: "ns".to_owned(),
            name: "1234".to_owned(),
            state: FlagState::Enabled,
            permission: Permission::ReadOnly,
        };

        let s = r#"
        id: "1234"
        namespace: "ns"
        name: "1234"
        state: ENABLED
        permission: READ_ONLY
        "#;
@@ -329,7 +409,7 @@ mod tests {
    #[test]
    fn test_flag_resolve() {
        let flag = Flag {
            id: "a".to_owned(),
            name: "a".to_owned(),
            description: "A".to_owned(),
            values: vec![
                Value::default(FlagState::Disabled, Permission::ReadOnly),
+54 −24
Original line number Diff line number Diff line
@@ -22,7 +22,7 @@ use crate::aconfig::{Flag, FlagState, Override, Permission};
use crate::commands::Source;

#[derive(Serialize, Deserialize, Debug)]
pub struct TracePoint {
pub struct Tracepoint {
    pub source: Source,
    pub state: FlagState,
    pub permission: Permission,
@@ -30,22 +30,28 @@ pub struct TracePoint {

#[derive(Serialize, Deserialize, Debug)]
pub struct Item {
    pub id: String,
    // TODO: duplicating the Cache.namespace as Item.namespace makes the internal representation
    // 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
    // really be a Cow<String>.
    pub namespace: String,
    pub name: String,
    pub description: String,
    pub state: FlagState,
    pub permission: Permission,
    pub trace: Vec<TracePoint>,
    pub trace: Vec<Tracepoint>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct Cache {
    build_id: u32,
    namespace: String,
    items: Vec<Item>,
}

impl Cache {
    pub fn new(build_id: u32) -> Cache {
        Cache { build_id, items: vec![] }
    pub fn new(build_id: u32, namespace: String) -> Cache {
        Cache { build_id, namespace, items: vec![] }
    }

    pub fn read_from_reader(reader: impl Read) -> Result<Cache> {
@@ -57,31 +63,36 @@ impl Cache {
    }

    pub fn add_flag(&mut self, source: Source, flag: Flag) -> Result<()> {
        if self.items.iter().any(|item| item.id == flag.id) {
        if self.items.iter().any(|item| item.name == flag.name) {
            return Err(anyhow!(
                "failed to add flag {} from {}: flag already defined",
                flag.id,
                flag.name,
                source,
            ));
        }
        let (state, permission) = flag.resolve(self.build_id);
        self.items.push(Item {
            id: flag.id.clone(),
            namespace: self.namespace.clone(),
            name: flag.name.clone(),
            description: flag.description,
            state,
            permission,
            trace: vec![TracePoint { source, state, permission }],
            trace: vec![Tracepoint { source, state, permission }],
        });
        Ok(())
    }

    pub fn add_override(&mut self, source: Source, override_: Override) -> Result<()> {
        let Some(existing_item) = self.items.iter_mut().find(|item| item.id == override_.id) else {
            return Err(anyhow!("failed to override flag {}: unknown flag", override_.id));
        if override_.namespace != self.namespace {
            // TODO: print warning?
            return Ok(());
        }
        let Some(existing_item) = self.items.iter_mut().find(|item| item.name == override_.name) else {
            return Err(anyhow!("failed to override flag {}: unknown flag", override_.name));
        };
        existing_item.state = override_.state;
        existing_item.permission = override_.permission;
        existing_item.trace.push(TracePoint {
        existing_item.trace.push(Tracepoint {
            source,
            state: override_.state,
            permission: override_.permission,
@@ -92,9 +103,11 @@ impl Cache {
    pub fn iter(&self) -> impl Iterator<Item = &Item> {
        self.items.iter()
    }
}

impl Item {}
    pub fn into_iter(self) -> impl Iterator<Item = Item> {
        self.items.into_iter()
    }
}

#[cfg(test)]
mod tests {
@@ -103,12 +116,12 @@ mod tests {

    #[test]
    fn test_add_flag() {
        let mut cache = Cache::new(1);
        let mut cache = Cache::new(1, "ns".to_string());
        cache
            .add_flag(
                Source::File("first.txt".to_string()),
                Flag {
                    id: "foo".to_string(),
                    name: "foo".to_string(),
                    description: "desc".to_string(),
                    values: vec![Value::default(FlagState::Enabled, Permission::ReadOnly)],
                },
@@ -118,7 +131,7 @@ mod tests {
            .add_flag(
                Source::File("second.txt".to_string()),
                Flag {
                    id: "foo".to_string(),
                    name: "foo".to_string(),
                    description: "desc".to_string(),
                    values: vec![Value::default(FlagState::Disabled, Permission::ReadOnly)],
                },
@@ -132,17 +145,18 @@ mod tests {

    #[test]
    fn test_add_override() {
        fn check(cache: &Cache, id: &str, expected: (FlagState, Permission)) -> bool {
            let item = cache.iter().find(|&item| item.id == id).unwrap();
        fn check(cache: &Cache, name: &str, expected: (FlagState, Permission)) -> bool {
            let item = cache.iter().find(|&item| item.name == name).unwrap();
            item.state == expected.0 && item.permission == expected.1
        }

        let mut cache = Cache::new(1);
        let mut cache = Cache::new(1, "ns".to_string());
        let error = cache
            .add_override(
                Source::Memory,
                Override {
                    id: "foo".to_string(),
                    namespace: "ns".to_string(),
                    name: "foo".to_string(),
                    state: FlagState::Enabled,
                    permission: Permission::ReadOnly,
                },
@@ -154,7 +168,7 @@ mod tests {
            .add_flag(
                Source::File("first.txt".to_string()),
                Flag {
                    id: "foo".to_string(),
                    name: "foo".to_string(),
                    description: "desc".to_string(),
                    values: vec![Value::default(FlagState::Enabled, Permission::ReadOnly)],
                },
@@ -167,7 +181,8 @@ mod tests {
            .add_override(
                Source::Memory,
                Override {
                    id: "foo".to_string(),
                    namespace: "ns".to_string(),
                    name: "foo".to_string(),
                    state: FlagState::Disabled,
                    permission: Permission::ReadWrite,
                },
@@ -180,12 +195,27 @@ mod tests {
            .add_override(
                Source::Memory,
                Override {
                    id: "foo".to_string(),
                    namespace: "ns".to_string(),
                    name: "foo".to_string(),
                    state: FlagState::Enabled,
                    permission: Permission::ReadWrite,
                },
            )
            .unwrap();
        assert!(check(&cache, "foo", (FlagState::Enabled, Permission::ReadWrite)));

        // different namespace -> no-op
        cache
            .add_override(
                Source::Memory,
                Override {
                    namespace: "some-other-namespace".to_string(),
                    name: "foo".to_string(),
                    state: FlagState::Enabled,
                    permission: Permission::ReadOnly,
                },
            )
            .unwrap();
        assert!(check(&cache, "foo", (FlagState::Enabled, Permission::ReadWrite)));
    }
}
+91 −17

File changed.

Preview size limit exceeded, changes collapsed.

+22 −8
Original line number Diff line number Diff line
@@ -19,6 +19,8 @@
use anyhow::Result;
use clap::{builder::ArgAction, builder::EnumValueParser, Arg, ArgMatches, Command};
use std::fs;
use std::io;
use std::io::Write;

mod aconfig;
mod cache;
@@ -39,17 +41,21 @@ fn cli() -> Command {
                        .value_parser(clap::value_parser!(u32))
                        .required(true),
                )
                .arg(Arg::new("namespace").long("namespace").required(true))
                .arg(Arg::new("aconfig").long("aconfig").action(ArgAction::Append))
                .arg(Arg::new("override").long("override").action(ArgAction::Append))
                .arg(Arg::new("cache").long("cache").required(true)),
        )
        .subcommand(
            Command::new("dump").arg(Arg::new("cache").long("cache").required(true)).arg(
            Command::new("dump")
                .arg(Arg::new("cache").long("cache").required(true))
                .arg(
                    Arg::new("format")
                        .long("format")
                        .value_parser(EnumValueParser::<commands::Format>::new())
                        .default_value("text"),
            ),
                )
                .arg(Arg::new("out").long("out").default_value("-")),
        )
}

@@ -67,9 +73,10 @@ fn main() -> Result<()> {
    match matches.subcommand() {
        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 aconfigs = open_zero_or_more_files(sub_matches, "aconfig")?;
            let overrides = open_zero_or_more_files(sub_matches, "override")?;
            let cache = commands::create_cache(build_id, aconfigs, overrides)?;
            let cache = commands::create_cache(build_id, namespace, aconfigs, overrides)?;
            let path = sub_matches.get_one::<String>("cache").unwrap();
            let file = fs::File::create(path)?;
            cache.write_to_writer(file)?;
@@ -79,7 +86,14 @@ fn main() -> Result<()> {
            let file = fs::File::open(path)?;
            let cache = Cache::read_from_reader(file)?;
            let format = sub_matches.get_one("format").unwrap();
            commands::dump_cache(cache, *format)?;
            let output = commands::dump_cache(cache, *format)?;
            let path = sub_matches.get_one::<String>("out").unwrap();
            let mut file: Box<dyn Write> = if path == "-" {
                Box::new(io::stdout())
            } else {
                Box::new(fs::File::create(path)?)
            };
            file.write_all(&output)?;
        }
        _ => unreachable!(),
    }
Loading