aconfig: introduce cache

Introduce the Cache struct to represent parsed and verified aconfig and
override content. Most commands in aconfig will work of an existing
cache file, eliminating the need to re-read the input every time.

Restructure main.rs to use clap to create a proper command line
interface with support for sub-commands. main.rs is responsible for
parsing the command line, performing disk I/O and calling the correct
subcommand implementation (in commands.rs).

To simplify unit tests, subcommands never perform explicit I/O; instead
they only work with Read and Write traits.

Also add dependencies on clap, serde and serde_json.

Bug: 279485059
Test: atest aconfig.test
Change-Id: Ib6abf2eabd264009804f253874b6fba924fc391b
diff --git a/tools/aconfig/src/main.rs b/tools/aconfig/src/main.rs
index 9d87ca4..79ac920 100644
--- a/tools/aconfig/src/main.rs
+++ b/tools/aconfig/src/main.rs
@@ -17,22 +17,67 @@
 //! `aconfig` is a build time tool to manage build time configurations, such as feature flags.
 
 use anyhow::Result;
+use clap::{builder::ArgAction, builder::EnumValueParser, Arg, Command};
+use std::fs;
 
 mod aconfig;
+mod cache;
+mod commands;
 mod protos;
 
-use aconfig::{Flag, Override};
+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("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() -> Result<()> {
-    let flag = Flag::try_from_text_proto(r#"id: "a" description: "description of a" value: true"#)?;
-    println!("{:?}", flag);
-    let flags = Flag::try_from_text_proto_list(
-        r#"flag { id: "a" description: "description of a" value: true }"#,
-    )?;
-    println!("{:?}", flags);
-    let override_ = Override::try_from_text_proto(r#"id: "foo" value: true"#)?;
-    println!("{:?}", override_);
-    let overrides = Override::try_from_text_proto_list(r#"override { id: "foo" value: true }"#)?;
-    println!("{:?}", overrides);
+    let matches = cli().get_matches();
+    match matches.subcommand() {
+        Some(("create-cache", sub_matches)) => {
+            let mut aconfigs = vec![];
+            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(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(())
 }