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