aconfig: add support for changing flag value based on build

Teach aconfig about build IDs (continuously increasing integers). Extend
the aconfig file format to allow flags to say "by default, my value is
X, but starting from build ID A, it's Y, and from build ID B, it's Z".

Bug: 279485059
Test: atest aconfig.test
Change-Id: Idde03dee06f6cb9041c0dd4ca917c8b2f2faafdd
diff --git a/tools/aconfig/protos/aconfig.proto b/tools/aconfig/protos/aconfig.proto
index 7d0e8ad..65817ca 100644
--- a/tools/aconfig/protos/aconfig.proto
+++ b/tools/aconfig/protos/aconfig.proto
@@ -20,10 +20,15 @@
 
 package android.aconfig;
 
+message value {
+  required bool value = 1;
+  optional uint32 since = 2;
+}
+
 message flag {
   required string id = 1;
   required string description = 2;
-  required bool value = 3;
+  repeated value value = 3;
 };
 
 message android_config {
diff --git a/tools/aconfig/src/aconfig.rs b/tools/aconfig/src/aconfig.rs
index 41815b7..22fcb88 100644
--- a/tools/aconfig/src/aconfig.rs
+++ b/tools/aconfig/src/aconfig.rs
@@ -16,13 +16,46 @@
 
 use anyhow::{anyhow, Context, Error, Result};
 
-use crate::protos::{ProtoAndroidConfig, ProtoFlag, ProtoOverride, ProtoOverrideConfig};
+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,
-    pub value: bool,
+
+    // ordered by Value.since; guaranteed to contain at least one item (the default value, with
+    // since == None)
+    pub values: Vec<Value>,
 }
 
 impl Flag {
@@ -38,6 +71,17 @@
             .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 {
@@ -50,10 +94,25 @@
         let Some(description) = proto.description else {
             return Err(anyhow!("missing 'description' field"));
         };
-        let Some(value) = proto.value else {
+        if proto.value.is_empty() {
             return Err(anyhow!("missing 'value' field"));
-        };
-        Ok(Flag { id, description, value })
+        }
+
+        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 })
     }
 }
 
@@ -99,13 +158,19 @@
         let expected = Flag {
             id: "1234".to_owned(),
             description: "Description of the flag".to_owned(),
-            value: true,
+            values: vec![Value::default(false), Value::new(true, 8)],
         };
 
         let s = r#"
         id: "1234"
         description: "Description of the flag"
-        value: true
+        value {
+            value: false
+        }
+        value {
+            value: true
+            since: 8
+        }
         "#;
         let actual = Flag::try_from_text_proto(s).unwrap();
 
@@ -113,32 +178,66 @@
     }
 
     #[test]
-    fn test_flag_try_from_text_proto_missing_field() {
+    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: true
+        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(), value: true },
-            Flag { id: "b".to_owned(), description: "B".to_owned(), value: false },
+            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: true
+            value {
+                value: true
+            }
         }
         flag {
             id: "b"
             description: "B"
-            value: false
+            value {
+                value: false
+            }
         }
         "#;
         let actual = Flag::try_from_text_proto_list(s).unwrap();
@@ -158,4 +257,28 @@
 
         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));
+    }
 }
diff --git a/tools/aconfig/src/cache.rs b/tools/aconfig/src/cache.rs
index d60cc20..d27459d 100644
--- a/tools/aconfig/src/cache.rs
+++ b/tools/aconfig/src/cache.rs
@@ -22,26 +22,22 @@
 use crate::commands::Source;
 
 #[derive(Serialize, Deserialize)]
-pub struct Value {
-    pub value: bool,
-    pub source: Source,
-}
-
-#[derive(Serialize, Deserialize)]
 pub struct Item {
     pub id: String,
     pub description: String,
-    pub values: Vec<Value>,
+    pub value: bool,
+    pub debug: Vec<String>,
 }
 
 #[derive(Serialize, Deserialize)]
 pub struct Cache {
+    build_id: u32,
     items: Vec<Item>,
 }
 
 impl Cache {
-    pub fn new() -> Cache {
-        Cache { items: vec![] }
+    pub fn new(build_id: u32) -> Cache {
+        Cache { build_id, items: vec![] }
     }
 
     pub fn read_from_reader(reader: impl Read) -> Result<Cache> {
@@ -53,18 +49,19 @@
     }
 
     pub fn add_flag(&mut self, source: Source, flag: Flag) -> Result<()> {
-        if let Some(existing_item) = self.items.iter().find(|&item| item.id == flag.id) {
+        if self.items.iter().any(|item| item.id == flag.id) {
             return Err(anyhow!(
-                "failed to add flag {} from {}: already added from {}",
+                "failed to add flag {} from {}: flag already defined",
                 flag.id,
                 source,
-                existing_item.values.first().unwrap().source
             ));
         }
+        let value = flag.resolve_value(self.build_id);
         self.items.push(Item {
             id: flag.id.clone(),
-            description: flag.description.clone(),
-            values: vec![Value { value: flag.value, source }],
+            description: flag.description,
+            value,
+            debug: vec![format!("{}:{}", source, value)],
         });
         Ok(())
     }
@@ -73,7 +70,8 @@
         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.values.push(Value { value: override_.value, source });
+        existing_item.value = override_.value;
+        existing_item.debug.push(format!("{}:{}", source, override_.value));
         Ok(())
     }
 
@@ -82,65 +80,74 @@
     }
 }
 
-impl Item {
-    pub fn value(&self) -> bool {
-        self.values.last().unwrap().value
-    }
-}
+impl Item {}
 
 #[cfg(test)]
 mod tests {
     use super::*;
+    use crate::aconfig::Value;
 
     #[test]
     fn test_add_flag() {
-        let mut cache = Cache::new();
+        let mut cache = Cache::new(1);
         cache
             .add_flag(
                 Source::File("first.txt".to_string()),
-                Flag { id: "foo".to_string(), description: "desc".to_string(), value: true },
+                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(), value: false },
+                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: already added from first.txt"
+            "failed to add flag foo from second.txt: flag already defined"
         );
     }
 
     #[test]
     fn test_add_override() {
-        fn get_value(cache: &Cache, id: &str) -> bool {
-            cache.iter().find(|&item| item.id == id).unwrap().value()
+        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();
+        let mut cache = Cache::new(1);
         let error = cache
-            .add_override(Source::Memory, Override { id: "foo".to_string(), value: false })
+            .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(), value: true },
+                Flag {
+                    id: "foo".to_string(),
+                    description: "desc".to_string(),
+                    values: vec![Value::default(true)],
+                },
             )
             .unwrap();
-        assert!(get_value(&cache, "foo"));
+        assert!(check_value(&cache, "foo", true));
 
         cache
             .add_override(Source::Memory, Override { id: "foo".to_string(), value: false })
             .unwrap();
-        assert!(!get_value(&cache, "foo"));
+        assert!(check_value(&cache, "foo", false));
 
         cache
             .add_override(Source::Memory, Override { id: "foo".to_string(), value: true })
             .unwrap();
-        assert!(get_value(&cache, "foo"));
+        assert!(check_value(&cache, "foo", true));
     }
 }
diff --git a/tools/aconfig/src/commands.rs b/tools/aconfig/src/commands.rs
index 0e377aa..76b853b 100644
--- a/tools/aconfig/src/commands.rs
+++ b/tools/aconfig/src/commands.rs
@@ -44,8 +44,8 @@
     pub reader: Box<dyn Read>,
 }
 
-pub fn create_cache(aconfigs: Vec<Input>, overrides: Vec<Input>) -> Result<Cache> {
-    let mut cache = Cache::new();
+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();
@@ -80,15 +80,12 @@
     match format {
         Format::Text => {
             for item in cache.iter() {
-                println!("{}: {}", item.id, item.value());
+                println!("{}: {}", item.id, item.value);
             }
         }
         Format::Debug => {
             for item in cache.iter() {
-                println!("{}: {}", item.id, item.value());
-                for value in &item.values {
-                    println!("    {}: {}", value.source, value.value);
-                }
+                println!("{}: {} ({:?})", item.id, item.value, item.debug);
             }
         }
     }
@@ -105,7 +102,9 @@
         flag {
             id: "a"
             description: "Description of a"
-            value: true
+            value {
+                value: true
+            }
         }
         "#;
         let aconfigs = vec![Input { source: Source::Memory, reader: Box::new(s.as_bytes()) }];
@@ -116,8 +115,8 @@
         }
         "#;
         let overrides = vec![Input { source: Source::Memory, reader: Box::new(o.as_bytes()) }];
-        let cache = create_cache(aconfigs, overrides).unwrap();
-        let value = cache.iter().find(|&item| item.id == "a").unwrap().value();
+        let cache = create_cache(1, aconfigs, overrides).unwrap();
+        let value = cache.iter().find(|&item| item.id == "a").unwrap().value;
         assert!(!value);
     }
 }
diff --git a/tools/aconfig/src/main.rs b/tools/aconfig/src/main.rs
index 79ac920..3ce9747 100644
--- a/tools/aconfig/src/main.rs
+++ b/tools/aconfig/src/main.rs
@@ -33,6 +33,12 @@
         .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)),
@@ -52,6 +58,7 @@
     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<_>>()
             {
@@ -65,7 +72,7 @@
                 let file = Box::new(fs::File::open(path)?);
                 overrides.push(Input { source: Source::File(path.to_string()), reader: file });
             }
-            let cache = commands::create_cache(aconfigs, overrides)?;
+            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)?;
diff --git a/tools/aconfig/src/protos.rs b/tools/aconfig/src/protos.rs
index 42a7c1a..3c156b3 100644
--- a/tools/aconfig/src/protos.rs
+++ b/tools/aconfig/src/protos.rs
@@ -31,6 +31,9 @@
 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"))]
@@ -47,6 +50,9 @@
 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")]