Merge changes from topic "aconfig-part-1" am: 0b6b92e5a5
Original change: https://android-review.googlesource.com/c/platform/build/+/2578416 Change-Id: Icdeb54a877d6e0e7df55321e4ed61d04a9011e91 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
commit
787a0d1ffd
10 changed files with 767 additions and 34 deletions
2
tools/aconfig/.gitignore
vendored
Normal file
2
tools/aconfig/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/Cargo.lock
|
||||
/target
|
|
@ -18,7 +18,11 @@ rust_defaults {
|
|||
srcs: ["src/main.rs"],
|
||||
rustlibs: [
|
||||
"libaconfig_protos",
|
||||
"libanyhow",
|
||||
"libclap",
|
||||
"libprotobuf",
|
||||
"libserde",
|
||||
"libserde_json",
|
||||
],
|
||||
}
|
||||
|
||||
|
|
19
tools/aconfig/Cargo.toml
Normal file
19
tools/aconfig/Cargo.toml
Normal file
|
@ -0,0 +1,19 @@
|
|||
[package]
|
||||
name = "aconfig"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
build = "build.rs"
|
||||
|
||||
[features]
|
||||
default = ["cargo"]
|
||||
cargo = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.69"
|
||||
clap = { version = "4.1.8", features = ["derive"] }
|
||||
protobuf = "3.2.0"
|
||||
serde = { version = "1.0.152", features = ["derive"] }
|
||||
serde_json = "1.0.93"
|
||||
|
||||
[build-dependencies]
|
||||
protobuf-codegen = "3.2.0"
|
17
tools/aconfig/build.rs
Normal file
17
tools/aconfig/build.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
use protobuf_codegen::Codegen;
|
||||
|
||||
fn main() {
|
||||
let proto_files = vec!["protos/aconfig.proto"];
|
||||
|
||||
// tell cargo to only re-run the build script if any of the proto files has changed
|
||||
for path in &proto_files {
|
||||
println!("cargo:rerun-if-changed={}", path);
|
||||
}
|
||||
|
||||
Codegen::new()
|
||||
.pure()
|
||||
.include("protos")
|
||||
.inputs(proto_files)
|
||||
.cargo_out_dir("aconfig_proto")
|
||||
.run_from_script();
|
||||
}
|
|
@ -12,12 +12,34 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License
|
||||
|
||||
// Placeholder proto file. Will be replaced by actual contents.
|
||||
// This is the schema definition for of Aconfig files. Modifications need to be
|
||||
// either backwards compatible, or include updates to all Aconfig files in the
|
||||
// Android tree.
|
||||
|
||||
syntax = "proto3";
|
||||
syntax = "proto2";
|
||||
|
||||
package android.aconfig;
|
||||
|
||||
message Placeholder {
|
||||
string name = 1;
|
||||
message value {
|
||||
required bool value = 1;
|
||||
optional uint32 since = 2;
|
||||
}
|
||||
|
||||
message flag {
|
||||
required string id = 1;
|
||||
required string description = 2;
|
||||
repeated value value = 3;
|
||||
};
|
||||
|
||||
message android_config {
|
||||
repeated flag flag = 1;
|
||||
};
|
||||
|
||||
message override {
|
||||
required string id = 1;
|
||||
required bool value = 2;
|
||||
};
|
||||
|
||||
message override_config {
|
||||
repeated override override = 1;
|
||||
};
|
||||
|
|
284
tools/aconfig/src/aconfig.rs
Normal file
284
tools/aconfig/src/aconfig.rs
Normal file
|
@ -0,0 +1,284 @@
|
|||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use anyhow::{anyhow, Context, Error, Result};
|
||||
|
||||
use crate::protos::{
|
||||
ProtoAndroidConfig, ProtoFlag, ProtoOverride, ProtoOverrideConfig, ProtoValue,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Value {
|
||||
value: bool,
|
||||
since: Option<u32>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // only used in unit tests
|
||||
impl Value {
|
||||
pub fn new(value: bool, since: u32) -> Value {
|
||||
Value { value, since: Some(since) }
|
||||
}
|
||||
|
||||
pub fn default(value: bool) -> Value {
|
||||
Value { value, since: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ProtoValue> for Value {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(proto: ProtoValue) -> Result<Self, Self::Error> {
|
||||
let Some(value) = proto.value else {
|
||||
return Err(anyhow!("missing 'value' field"));
|
||||
};
|
||||
Ok(Value { value, since: proto.since })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Flag {
|
||||
pub id: 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 {
|
||||
#[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)
|
||||
.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_value(&self, build_id: u32) -> bool {
|
||||
let mut value = self.values[0].value;
|
||||
for candidate in self.values.iter().skip(1) {
|
||||
let since = candidate.since.expect("invariant: non-defaults values have Some(since)");
|
||||
if since <= build_id {
|
||||
value = candidate.value;
|
||||
}
|
||||
}
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ProtoFlag> 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"));
|
||||
};
|
||||
let Some(description) = proto.description else {
|
||||
return Err(anyhow!("missing 'description' field"));
|
||||
};
|
||||
if proto.value.is_empty() {
|
||||
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", id),
|
||||
Some(x) => format!("flag {}: multiple values for since={}", id, x),
|
||||
};
|
||||
return Err(anyhow!(msg));
|
||||
}
|
||||
values.push(v);
|
||||
}
|
||||
values.sort_by_key(|v| v.since);
|
||||
|
||||
Ok(Flag { id, description, values })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Override {
|
||||
pub id: String,
|
||||
pub value: bool,
|
||||
}
|
||||
|
||||
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)?;
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ProtoOverride> 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"));
|
||||
};
|
||||
let Some(value) = proto.value else {
|
||||
return Err(anyhow!("missing 'value' field"));
|
||||
};
|
||||
Ok(Override { id, value })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_flag_try_from_text_proto() {
|
||||
let expected = Flag {
|
||||
id: "1234".to_owned(),
|
||||
description: "Description of the flag".to_owned(),
|
||||
values: vec![Value::default(false), Value::new(true, 8)],
|
||||
};
|
||||
|
||||
let s = r#"
|
||||
id: "1234"
|
||||
description: "Description of the flag"
|
||||
value {
|
||||
value: false
|
||||
}
|
||||
value {
|
||||
value: true
|
||||
since: 8
|
||||
}
|
||||
"#;
|
||||
let actual = Flag::try_from_text_proto(s).unwrap();
|
||||
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_flag_try_from_text_proto_bad_input() {
|
||||
let s = r#"
|
||||
id: "a"
|
||||
description: "Description of the flag"
|
||||
"#;
|
||||
let error = Flag::try_from_text_proto(s).unwrap_err();
|
||||
assert_eq!(format!("{:?}", error), "missing 'value' field");
|
||||
|
||||
let s = r#"
|
||||
description: "Description of the flag"
|
||||
value {
|
||||
value: true
|
||||
}
|
||||
"#;
|
||||
let error = Flag::try_from_text_proto(s).unwrap_err();
|
||||
assert!(format!("{:?}", error).contains("Message not initialized"));
|
||||
|
||||
let s = r#"
|
||||
id: "a"
|
||||
description: "Description of the flag"
|
||||
value {
|
||||
value: true
|
||||
}
|
||||
value {
|
||||
value: true
|
||||
}
|
||||
"#;
|
||||
let error = Flag::try_from_text_proto(s).unwrap_err();
|
||||
assert_eq!(format!("{:?}", error), "flag a: multiple default values");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_flag_try_from_text_proto_list() {
|
||||
let expected = vec![
|
||||
Flag {
|
||||
id: "a".to_owned(),
|
||||
description: "A".to_owned(),
|
||||
values: vec![Value::default(true)],
|
||||
},
|
||||
Flag {
|
||||
id: "b".to_owned(),
|
||||
description: "B".to_owned(),
|
||||
values: vec![Value::default(false)],
|
||||
},
|
||||
];
|
||||
|
||||
let s = r#"
|
||||
flag {
|
||||
id: "a"
|
||||
description: "A"
|
||||
value {
|
||||
value: true
|
||||
}
|
||||
}
|
||||
flag {
|
||||
id: "b"
|
||||
description: "B"
|
||||
value {
|
||||
value: false
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let actual = Flag::try_from_text_proto_list(s).unwrap();
|
||||
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_override_try_from_text_proto_list() {
|
||||
let expected = Override { id: "1234".to_owned(), value: true };
|
||||
|
||||
let s = r#"
|
||||
id: "1234"
|
||||
value: true
|
||||
"#;
|
||||
let actual = Override::try_from_text_proto(s).unwrap();
|
||||
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_value() {
|
||||
let flag = Flag {
|
||||
id: "a".to_owned(),
|
||||
description: "A".to_owned(),
|
||||
values: vec![
|
||||
Value::default(true),
|
||||
Value::new(false, 10),
|
||||
Value::new(true, 20),
|
||||
Value::new(false, 30),
|
||||
],
|
||||
};
|
||||
assert!(flag.resolve_value(0));
|
||||
assert!(flag.resolve_value(9));
|
||||
assert!(!flag.resolve_value(10));
|
||||
assert!(!flag.resolve_value(11));
|
||||
assert!(!flag.resolve_value(19));
|
||||
assert!(flag.resolve_value(20));
|
||||
assert!(flag.resolve_value(21));
|
||||
assert!(flag.resolve_value(29));
|
||||
assert!(!flag.resolve_value(30));
|
||||
assert!(!flag.resolve_value(10_000));
|
||||
}
|
||||
}
|
153
tools/aconfig/src/cache.rs
Normal file
153
tools/aconfig/src/cache.rs
Normal file
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use crate::aconfig::{Flag, Override};
|
||||
use crate::commands::Source;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Item {
|
||||
pub id: String,
|
||||
pub description: String,
|
||||
pub value: bool,
|
||||
pub debug: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Cache {
|
||||
build_id: u32,
|
||||
items: Vec<Item>,
|
||||
}
|
||||
|
||||
impl Cache {
|
||||
pub fn new(build_id: u32) -> Cache {
|
||||
Cache { build_id, items: vec![] }
|
||||
}
|
||||
|
||||
pub fn read_from_reader(reader: impl Read) -> Result<Cache> {
|
||||
serde_json::from_reader(reader).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
pub fn write_to_writer(&self, writer: impl Write) -> Result<()> {
|
||||
serde_json::to_writer(writer, self).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
pub fn add_flag(&mut self, source: Source, flag: Flag) -> Result<()> {
|
||||
if self.items.iter().any(|item| item.id == flag.id) {
|
||||
return Err(anyhow!(
|
||||
"failed to add flag {} from {}: flag already defined",
|
||||
flag.id,
|
||||
source,
|
||||
));
|
||||
}
|
||||
let value = flag.resolve_value(self.build_id);
|
||||
self.items.push(Item {
|
||||
id: flag.id.clone(),
|
||||
description: flag.description,
|
||||
value,
|
||||
debug: vec![format!("{}:{}", source, value)],
|
||||
});
|
||||
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));
|
||||
};
|
||||
existing_item.value = override_.value;
|
||||
existing_item.debug.push(format!("{}:{}", source, override_.value));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &Item> {
|
||||
self.items.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl Item {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::aconfig::Value;
|
||||
|
||||
#[test]
|
||||
fn test_add_flag() {
|
||||
let mut cache = Cache::new(1);
|
||||
cache
|
||||
.add_flag(
|
||||
Source::File("first.txt".to_string()),
|
||||
Flag {
|
||||
id: "foo".to_string(),
|
||||
description: "desc".to_string(),
|
||||
values: vec![Value::default(true)],
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let error = cache
|
||||
.add_flag(
|
||||
Source::File("second.txt".to_string()),
|
||||
Flag {
|
||||
id: "foo".to_string(),
|
||||
description: "desc".to_string(),
|
||||
values: vec![Value::default(false)],
|
||||
},
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
&format!("{:?}", error),
|
||||
"failed to add flag foo from second.txt: flag already defined"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_override() {
|
||||
fn check_value(cache: &Cache, id: &str, expected: bool) -> bool {
|
||||
cache.iter().find(|&item| item.id == id).unwrap().value == expected
|
||||
}
|
||||
|
||||
let mut cache = Cache::new(1);
|
||||
let error = cache
|
||||
.add_override(Source::Memory, Override { id: "foo".to_string(), value: true })
|
||||
.unwrap_err();
|
||||
assert_eq!(&format!("{:?}", error), "failed to override flag foo: unknown flag");
|
||||
|
||||
cache
|
||||
.add_flag(
|
||||
Source::File("first.txt".to_string()),
|
||||
Flag {
|
||||
id: "foo".to_string(),
|
||||
description: "desc".to_string(),
|
||||
values: vec![Value::default(true)],
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
assert!(check_value(&cache, "foo", true));
|
||||
|
||||
cache
|
||||
.add_override(Source::Memory, Override { id: "foo".to_string(), value: false })
|
||||
.unwrap();
|
||||
assert!(check_value(&cache, "foo", false));
|
||||
|
||||
cache
|
||||
.add_override(Source::Memory, Override { id: "foo".to_string(), value: true })
|
||||
.unwrap();
|
||||
assert!(check_value(&cache, "foo", true));
|
||||
}
|
||||
}
|
122
tools/aconfig/src/commands.rs
Normal file
122
tools/aconfig/src/commands.rs
Normal file
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use clap::ValueEnum;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
use std::io::Read;
|
||||
|
||||
use crate::aconfig::{Flag, Override};
|
||||
use crate::cache::Cache;
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub enum Source {
|
||||
#[allow(dead_code)] // only used in unit tests
|
||||
Memory,
|
||||
File(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for Source {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Memory => write!(f, "<memory>"),
|
||||
Self::File(path) => write!(f, "{}", path),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Input {
|
||||
pub source: Source,
|
||||
pub reader: Box<dyn Read>,
|
||||
}
|
||||
|
||||
pub fn create_cache(build_id: u32, aconfigs: Vec<Input>, overrides: Vec<Input>) -> Result<Cache> {
|
||||
let mut cache = Cache::new(build_id);
|
||||
|
||||
for mut input in aconfigs {
|
||||
let mut contents = String::new();
|
||||
input.reader.read_to_string(&mut contents)?;
|
||||
let flags = Flag::try_from_text_proto_list(&contents)
|
||||
.with_context(|| format!("Failed to parse {}", input.source))?;
|
||||
for flag in flags {
|
||||
cache.add_flag(input.source.clone(), flag)?;
|
||||
}
|
||||
}
|
||||
|
||||
for mut input in overrides {
|
||||
let mut contents = String::new();
|
||||
input.reader.read_to_string(&mut contents)?;
|
||||
let overrides = Override::try_from_text_proto_list(&contents)
|
||||
.with_context(|| format!("Failed to parse {}", input.source))?;
|
||||
for override_ in overrides {
|
||||
cache.add_override(input.source.clone(), override_)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(cache)
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)]
|
||||
pub enum Format {
|
||||
Text,
|
||||
Debug,
|
||||
}
|
||||
|
||||
pub fn dump_cache(cache: Cache, format: Format) -> Result<()> {
|
||||
match format {
|
||||
Format::Text => {
|
||||
for item in cache.iter() {
|
||||
println!("{}: {}", item.id, item.value);
|
||||
}
|
||||
}
|
||||
Format::Debug => {
|
||||
for item in cache.iter() {
|
||||
println!("{}: {} ({:?})", item.id, item.value, item.debug);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_create_cache() {
|
||||
let s = r#"
|
||||
flag {
|
||||
id: "a"
|
||||
description: "Description of a"
|
||||
value {
|
||||
value: true
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let aconfigs = vec![Input { source: Source::Memory, reader: Box::new(s.as_bytes()) }];
|
||||
let o = r#"
|
||||
override {
|
||||
id: "a"
|
||||
value: false
|
||||
}
|
||||
"#;
|
||||
let overrides = vec![Input { source: Source::Memory, reader: Box::new(o.as_bytes()) }];
|
||||
let cache = create_cache(1, aconfigs, overrides).unwrap();
|
||||
let value = cache.iter().find(|&item| item.id == "a").unwrap().value;
|
||||
assert!(!value);
|
||||
}
|
||||
}
|
|
@ -16,38 +16,75 @@
|
|||
|
||||
//! `aconfig` is a build time tool to manage build time configurations, such as feature flags.
|
||||
|
||||
use aconfig_protos::aconfig::Placeholder;
|
||||
use protobuf::text_format::{parse_from_str, ParseError};
|
||||
use anyhow::Result;
|
||||
use clap::{builder::ArgAction, builder::EnumValueParser, Arg, Command};
|
||||
use std::fs;
|
||||
|
||||
fn foo() -> Result<String, ParseError> {
|
||||
let placeholder = parse_from_str::<Placeholder>(r#"name: "aconfig""#)?;
|
||||
Ok(placeholder.name)
|
||||
mod aconfig;
|
||||
mod cache;
|
||||
mod commands;
|
||||
mod protos;
|
||||
|
||||
use crate::cache::Cache;
|
||||
use commands::{Input, Source};
|
||||
|
||||
fn cli() -> Command {
|
||||
Command::new("aconfig")
|
||||
.subcommand_required(true)
|
||||
.subcommand(
|
||||
Command::new("create-cache")
|
||||
.arg(
|
||||
Arg::new("build-id")
|
||||
.long("build-id")
|
||||
.value_parser(clap::value_parser!(u32))
|
||||
.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(
|
||||
Arg::new("format")
|
||||
.long("format")
|
||||
.value_parser(EnumValueParser::<commands::Format>::new())
|
||||
.default_value("text"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("{:?}", foo());
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_foo() {
|
||||
assert_eq!("aconfig", foo().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_binary_protobuf() {
|
||||
use protobuf::Message;
|
||||
let mut buffer = Vec::new();
|
||||
|
||||
let mut original = Placeholder::new();
|
||||
original.name = "test".to_owned();
|
||||
original.write_to_writer(&mut buffer).unwrap();
|
||||
|
||||
let copy = Placeholder::parse_from_reader(&mut buffer.as_slice()).unwrap();
|
||||
|
||||
assert_eq!(original, copy);
|
||||
fn main() -> Result<()> {
|
||||
let matches = cli().get_matches();
|
||||
match matches.subcommand() {
|
||||
Some(("create-cache", sub_matches)) => {
|
||||
let mut aconfigs = vec![];
|
||||
let build_id = *sub_matches.get_one::<u32>("build-id").unwrap();
|
||||
for path in
|
||||
sub_matches.get_many::<String>("aconfig").unwrap_or_default().collect::<Vec<_>>()
|
||||
{
|
||||
let file = Box::new(fs::File::open(path)?);
|
||||
aconfigs.push(Input { source: Source::File(path.to_string()), reader: file });
|
||||
}
|
||||
let mut overrides = vec![];
|
||||
for path in
|
||||
sub_matches.get_many::<String>("override").unwrap_or_default().collect::<Vec<_>>()
|
||||
{
|
||||
let file = Box::new(fs::File::open(path)?);
|
||||
overrides.push(Input { source: Source::File(path.to_string()), reader: file });
|
||||
}
|
||||
let cache = commands::create_cache(build_id, aconfigs, overrides)?;
|
||||
let path = sub_matches.get_one::<String>("cache").unwrap();
|
||||
let file = fs::File::create(path)?;
|
||||
cache.write_to_writer(file)?;
|
||||
}
|
||||
Some(("dump", sub_matches)) => {
|
||||
let path = sub_matches.get_one::<String>("cache").unwrap();
|
||||
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)?;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
73
tools/aconfig/src/protos.rs
Normal file
73
tools/aconfig/src/protos.rs
Normal file
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// When building with the Android tool-chain
|
||||
//
|
||||
// - an external crate `aconfig_protos` will be generated
|
||||
// - the feature "cargo" will be disabled
|
||||
//
|
||||
// When building with cargo
|
||||
//
|
||||
// - a local sub-module will be generated in OUT_DIR and included in this file
|
||||
// - the feature "cargo" will be enabled
|
||||
//
|
||||
// This module hides these differences from the rest of aconfig.
|
||||
|
||||
// ---- When building with the Android tool-chain ----
|
||||
#[cfg(not(feature = "cargo"))]
|
||||
pub use aconfig_protos::aconfig::Android_config as ProtoAndroidConfig;
|
||||
|
||||
#[cfg(not(feature = "cargo"))]
|
||||
pub use aconfig_protos::aconfig::Value as ProtoValue;
|
||||
|
||||
#[cfg(not(feature = "cargo"))]
|
||||
pub use aconfig_protos::aconfig::Flag as ProtoFlag;
|
||||
|
||||
#[cfg(not(feature = "cargo"))]
|
||||
pub use aconfig_protos::aconfig::Override_config as ProtoOverrideConfig;
|
||||
|
||||
#[cfg(not(feature = "cargo"))]
|
||||
pub use aconfig_protos::aconfig::Override as ProtoOverride;
|
||||
|
||||
// ---- When building with cargo ----
|
||||
#[cfg(feature = "cargo")]
|
||||
include!(concat!(env!("OUT_DIR"), "/aconfig_proto/mod.rs"));
|
||||
|
||||
#[cfg(feature = "cargo")]
|
||||
pub use aconfig::Android_config as ProtoAndroidConfig;
|
||||
|
||||
#[cfg(feature = "cargo")]
|
||||
pub use aconfig::Value as ProtoValue;
|
||||
|
||||
#[cfg(feature = "cargo")]
|
||||
pub use aconfig::Flag as ProtoFlag;
|
||||
|
||||
#[cfg(feature = "cargo")]
|
||||
pub use aconfig::Override_config as ProtoOverrideConfig;
|
||||
|
||||
#[cfg(feature = "cargo")]
|
||||
pub use aconfig::Override as ProtoOverride;
|
||||
|
||||
// ---- Common for both the Android tool-chain and cargo ----
|
||||
use anyhow::Result;
|
||||
|
||||
pub fn try_from_text_proto<T>(s: &str) -> Result<T>
|
||||
where
|
||||
T: protobuf::MessageFull,
|
||||
{
|
||||
// warning: parse_from_str does not check if required fields are set
|
||||
protobuf::text_format::parse_from_str(s).map_err(|e| e.into())
|
||||
}
|
Loading…
Reference in a new issue