xref: /aosp_15_r20/build/make/tools/aconfig/aconfig/src/main.rs (revision 9e94795a3d4ef5c1d47486f9a02bb378756cea8a)
1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 //! `aconfig` is a build time tool to manage build time configurations, such as feature flags.
18 
19 use aconfig_storage_file::DEFAULT_FILE_VERSION;
20 use aconfig_storage_file::MAX_SUPPORTED_FILE_VERSION;
21 use anyhow::{anyhow, bail, Context, Result};
22 use clap::{builder::ArgAction, builder::EnumValueParser, Arg, ArgMatches, Command};
23 use core::any::Any;
24 use std::fs;
25 use std::io;
26 use std::io::Write;
27 use std::path::{Path, PathBuf};
28 
29 mod codegen;
30 mod commands;
31 mod dump;
32 mod storage;
33 
34 use aconfig_storage_file::StorageFileType;
35 use codegen::CodegenMode;
36 use dump::DumpFormat;
37 
38 #[cfg(test)]
39 mod test;
40 
41 use commands::{Input, OutputFile};
42 
43 const HELP_DUMP_FILTER: &str = r#"
44 Limit which flags to output. If multiple --filter arguments are provided, the output will be
45 limited to flags that match any of the filters.
46 "#;
47 
cli() -> Command48 fn cli() -> Command {
49     Command::new("aconfig")
50         .subcommand_required(true)
51         .subcommand(
52             Command::new("create-cache")
53                 .arg(Arg::new("package").long("package").required(true))
54                 .arg(Arg::new("container").long("container").required(true))
55                 .arg(Arg::new("declarations").long("declarations").action(ArgAction::Append))
56                 .arg(Arg::new("values").long("values").action(ArgAction::Append))
57                 .arg(
58                     Arg::new("default-permission")
59                         .long("default-permission")
60                         .value_parser(aconfig_protos::flag_permission::parse_from_str)
61                         .default_value(aconfig_protos::flag_permission::to_string(
62                             &commands::DEFAULT_FLAG_PERMISSION,
63                         )),
64                 )
65                 .arg(
66                     Arg::new("allow-read-write")
67                         .long("allow-read-write")
68                         .value_parser(clap::value_parser!(bool))
69                         .default_value("true"),
70                 )
71                 .arg(Arg::new("cache").long("cache").required(true)),
72         )
73         .subcommand(
74             Command::new("create-java-lib")
75                 .arg(Arg::new("cache").long("cache").required(true))
76                 .arg(Arg::new("out").long("out").required(true))
77                 .arg(
78                     Arg::new("mode")
79                         .long("mode")
80                         .value_parser(EnumValueParser::<CodegenMode>::new())
81                         .default_value("production"),
82                 )
83                 .arg(
84                     Arg::new("allow-instrumentation")
85                         .long("allow-instrumentation")
86                         .value_parser(clap::value_parser!(bool))
87                         .default_value("false"),
88                 ),
89         )
90         .subcommand(
91             Command::new("create-cpp-lib")
92                 .arg(Arg::new("cache").long("cache").required(true))
93                 .arg(Arg::new("out").long("out").required(true))
94                 .arg(
95                     Arg::new("mode")
96                         .long("mode")
97                         .value_parser(EnumValueParser::<CodegenMode>::new())
98                         .default_value("production"),
99                 )
100                 .arg(
101                     Arg::new("allow-instrumentation")
102                         .long("allow-instrumentation")
103                         .value_parser(clap::value_parser!(bool))
104                         .default_value("false"),
105                 ),
106         )
107         .subcommand(
108             Command::new("create-rust-lib")
109                 .arg(Arg::new("cache").long("cache").required(true))
110                 .arg(Arg::new("out").long("out").required(true))
111                 .arg(
112                     Arg::new("allow-instrumentation")
113                         .long("allow-instrumentation")
114                         .value_parser(clap::value_parser!(bool))
115                         .default_value("false"),
116                 )
117                 .arg(
118                     Arg::new("mode")
119                         .long("mode")
120                         .value_parser(EnumValueParser::<CodegenMode>::new())
121                         .default_value("production"),
122                 ),
123         )
124         .subcommand(
125             Command::new("create-device-config-defaults")
126                 .arg(Arg::new("cache").long("cache").action(ArgAction::Append).required(true))
127                 .arg(Arg::new("out").long("out").default_value("-")),
128         )
129         .subcommand(
130             Command::new("create-device-config-sysprops")
131                 .arg(Arg::new("cache").long("cache").action(ArgAction::Append).required(true))
132                 .arg(Arg::new("out").long("out").default_value("-")),
133         )
134         .subcommand(
135             Command::new("dump-cache")
136                 .alias("dump")
137                 .arg(Arg::new("cache").long("cache").action(ArgAction::Append))
138                 .arg(
139                     Arg::new("format")
140                         .long("format")
141                         .value_parser(|s: &str| DumpFormat::try_from(s))
142                         .default_value(
143                             "{fully_qualified_name} [{container}]: {permission} + {state}",
144                         ),
145                 )
146                 .arg(
147                     Arg::new("filter")
148                         .long("filter")
149                         .action(ArgAction::Append)
150                         .help(HELP_DUMP_FILTER.trim()),
151                 )
152                 .arg(Arg::new("dedup").long("dedup").num_args(0).action(ArgAction::SetTrue))
153                 .arg(Arg::new("out").long("out").default_value("-")),
154         )
155         .subcommand(
156             Command::new("create-storage")
157                 .arg(
158                     Arg::new("container")
159                         .long("container")
160                         .required(true)
161                         .help("The target container for the generated storage file."),
162                 )
163                 .arg(
164                     Arg::new("file")
165                         .long("file")
166                         .value_parser(|s: &str| StorageFileType::try_from(s)),
167                 )
168                 .arg(Arg::new("cache").long("cache").action(ArgAction::Append).required(true))
169                 .arg(Arg::new("out").long("out").required(true))
170                 .arg(
171                     Arg::new("version")
172                         .long("version")
173                         .required(false)
174                         .value_parser(|s: &str| s.parse::<u32>()),
175                 ),
176         )
177 }
178 
get_required_arg<'a, T>(matches: &'a ArgMatches, arg_name: &str) -> Result<&'a T> where T: Any + Clone + Send + Sync + 'static,179 fn get_required_arg<'a, T>(matches: &'a ArgMatches, arg_name: &str) -> Result<&'a T>
180 where
181     T: Any + Clone + Send + Sync + 'static,
182 {
183     matches
184         .get_one::<T>(arg_name)
185         .ok_or(anyhow!("internal error: required argument '{}' not found", arg_name))
186 }
187 
get_optional_arg<'a, T>(matches: &'a ArgMatches, arg_name: &str) -> Option<&'a T> where T: Any + Clone + Send + Sync + 'static,188 fn get_optional_arg<'a, T>(matches: &'a ArgMatches, arg_name: &str) -> Option<&'a T>
189 where
190     T: Any + Clone + Send + Sync + 'static,
191 {
192     matches.get_one::<T>(arg_name)
193 }
194 
open_zero_or_more_files(matches: &ArgMatches, arg_name: &str) -> Result<Vec<Input>>195 fn open_zero_or_more_files(matches: &ArgMatches, arg_name: &str) -> Result<Vec<Input>> {
196     let mut opened_files = vec![];
197     for path in matches.get_many::<String>(arg_name).unwrap_or_default() {
198         let file = Box::new(fs::File::open(path)?);
199         opened_files.push(Input { source: path.to_string(), reader: file });
200     }
201     Ok(opened_files)
202 }
203 
open_single_file(matches: &ArgMatches, arg_name: &str) -> Result<Input>204 fn open_single_file(matches: &ArgMatches, arg_name: &str) -> Result<Input> {
205     let Some(path) = matches.get_one::<String>(arg_name) else {
206         bail!("missing argument {}", arg_name);
207     };
208     let file = Box::new(fs::File::open(path)?);
209     Ok(Input { source: path.to_string(), reader: file })
210 }
211 
write_output_file_realtive_to_dir(root: &Path, output_file: &OutputFile) -> Result<()>212 fn write_output_file_realtive_to_dir(root: &Path, output_file: &OutputFile) -> Result<()> {
213     let path = root.join(&output_file.path);
214     let parent = path
215         .parent()
216         .ok_or(anyhow!("unable to locate parent of output file {}", path.display()))?;
217     fs::create_dir_all(parent)
218         .with_context(|| format!("failed to create directory {}", parent.display()))?;
219     let mut file =
220         fs::File::create(&path).with_context(|| format!("failed to open {}", path.display()))?;
221     file.write_all(&output_file.contents)
222         .with_context(|| format!("failed to write to {}", path.display()))?;
223     Ok(())
224 }
225 
write_output_to_file_or_stdout(path: &str, data: &[u8]) -> Result<()>226 fn write_output_to_file_or_stdout(path: &str, data: &[u8]) -> Result<()> {
227     if path == "-" {
228         io::stdout().write_all(data).context("failed to write to stdout")?;
229     } else {
230         fs::File::create(path)
231             .with_context(|| format!("failed to open {}", path))?
232             .write_all(data)
233             .with_context(|| format!("failed to write to {}", path))?;
234     }
235     Ok(())
236 }
237 
main() -> Result<()>238 fn main() -> Result<()> {
239     let matches = cli().get_matches();
240     match matches.subcommand() {
241         Some(("create-cache", sub_matches)) => {
242             let package = get_required_arg::<String>(sub_matches, "package")?;
243             let container =
244                 get_optional_arg::<String>(sub_matches, "container").map(|c| c.as_str());
245             let declarations = open_zero_or_more_files(sub_matches, "declarations")?;
246             let values = open_zero_or_more_files(sub_matches, "values")?;
247             let default_permission = get_required_arg::<aconfig_protos::ProtoFlagPermission>(
248                 sub_matches,
249                 "default-permission",
250             )?;
251             let allow_read_write = get_optional_arg::<bool>(sub_matches, "allow-read-write")
252                 .expect("failed to parse allow-read-write");
253             let output = commands::parse_flags(
254                 package,
255                 container,
256                 declarations,
257                 values,
258                 *default_permission,
259                 *allow_read_write,
260             )
261             .context("failed to create cache")?;
262             let path = get_required_arg::<String>(sub_matches, "cache")?;
263             write_output_to_file_or_stdout(path, &output)?;
264         }
265         Some(("create-java-lib", sub_matches)) => {
266             let cache = open_single_file(sub_matches, "cache")?;
267             let mode = get_required_arg::<CodegenMode>(sub_matches, "mode")?;
268             let allow_instrumentation =
269                 get_required_arg::<bool>(sub_matches, "allow-instrumentation")?;
270             let generated_files = commands::create_java_lib(cache, *mode, *allow_instrumentation)
271                 .context("failed to create java lib")?;
272             let dir = PathBuf::from(get_required_arg::<String>(sub_matches, "out")?);
273             generated_files
274                 .iter()
275                 .try_for_each(|file| write_output_file_realtive_to_dir(&dir, file))?;
276         }
277         Some(("create-cpp-lib", sub_matches)) => {
278             let cache = open_single_file(sub_matches, "cache")?;
279             let mode = get_required_arg::<CodegenMode>(sub_matches, "mode")?;
280             let allow_instrumentation =
281                 get_required_arg::<bool>(sub_matches, "allow-instrumentation")?;
282             let generated_files = commands::create_cpp_lib(cache, *mode, *allow_instrumentation)
283                 .context("failed to create cpp lib")?;
284             let dir = PathBuf::from(get_required_arg::<String>(sub_matches, "out")?);
285             generated_files
286                 .iter()
287                 .try_for_each(|file| write_output_file_realtive_to_dir(&dir, file))?;
288         }
289         Some(("create-rust-lib", sub_matches)) => {
290             let cache = open_single_file(sub_matches, "cache")?;
291             let mode = get_required_arg::<CodegenMode>(sub_matches, "mode")?;
292             let allow_instrumentation =
293                 get_required_arg::<bool>(sub_matches, "allow-instrumentation")?;
294             let generated_file = commands::create_rust_lib(cache, *mode, *allow_instrumentation)
295                 .context("failed to create rust lib")?;
296             let dir = PathBuf::from(get_required_arg::<String>(sub_matches, "out")?);
297             write_output_file_realtive_to_dir(&dir, &generated_file)?;
298         }
299         Some(("create-device-config-defaults", sub_matches)) => {
300             let cache = open_single_file(sub_matches, "cache")?;
301             let output = commands::create_device_config_defaults(cache)
302                 .context("failed to create device config defaults")?;
303             let path = get_required_arg::<String>(sub_matches, "out")?;
304             write_output_to_file_or_stdout(path, &output)?;
305         }
306         Some(("create-device-config-sysprops", sub_matches)) => {
307             let cache = open_single_file(sub_matches, "cache")?;
308             let output = commands::create_device_config_sysprops(cache)
309                 .context("failed to create device config sysprops")?;
310             let path = get_required_arg::<String>(sub_matches, "out")?;
311             write_output_to_file_or_stdout(path, &output)?;
312         }
313         Some(("dump-cache", sub_matches)) => {
314             let input = open_zero_or_more_files(sub_matches, "cache")?;
315             let format = get_required_arg::<DumpFormat>(sub_matches, "format")
316                 .context("failed to dump previously parsed flags")?;
317             let filters = sub_matches
318                 .get_many::<String>("filter")
319                 .unwrap_or_default()
320                 .map(String::as_ref)
321                 .collect::<Vec<_>>();
322             let dedup = get_required_arg::<bool>(sub_matches, "dedup")?;
323             let output = commands::dump_parsed_flags(input, format.clone(), &filters, *dedup)?;
324             let path = get_required_arg::<String>(sub_matches, "out")?;
325             write_output_to_file_or_stdout(path, &output)?;
326         }
327         Some(("create-storage", sub_matches)) => {
328             let version =
329                 get_optional_arg::<u32>(sub_matches, "version").unwrap_or(&DEFAULT_FILE_VERSION);
330             if *version > MAX_SUPPORTED_FILE_VERSION {
331                 bail!("Invalid version selected ({})", version);
332             }
333             let file = get_required_arg::<StorageFileType>(sub_matches, "file")
334                 .context("Invalid storage file selection")?;
335             let cache = open_zero_or_more_files(sub_matches, "cache")?;
336             let container = get_required_arg::<String>(sub_matches, "container")?;
337             let path = get_required_arg::<String>(sub_matches, "out")?;
338 
339             let output = commands::create_storage(cache, container, file, *version)
340                 .context("failed to create storage files")?;
341             write_output_to_file_or_stdout(path, &output)?;
342         }
343         _ => unreachable!(),
344     }
345     Ok(())
346 }
347