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 anyhow::{anyhow, bail, Context, Result};
20 use clap::{builder::ArgAction, builder::EnumValueParser, Arg, ArgMatches, Command};
21 use core::any::Any;
22 use std::fs;
23 use std::io;
24 use std::io::Write;
25 use std::path::{Path, PathBuf};
26
27 mod codegen;
28 mod commands;
29 mod dump;
30 mod storage;
31
32 use aconfig_storage_file::StorageFileType;
33 use codegen::CodegenMode;
34 use dump::DumpFormat;
35
36 #[cfg(test)]
37 mod test;
38
39 use commands::{Input, OutputFile};
40
41 const HELP_DUMP_FILTER: &str = r#"
42 Limit which flags to output. If multiple --filter arguments are provided, the output will be
43 limited to flags that match any of the filters.
44 "#;
45
cli() -> Command46 fn cli() -> Command {
47 Command::new("aconfig")
48 .subcommand_required(true)
49 .subcommand(
50 Command::new("create-cache")
51 .arg(Arg::new("package").long("package").required(true))
52 // TODO(b/312769710): Make this argument required.
53 .arg(Arg::new("container").long("container"))
54 .arg(Arg::new("declarations").long("declarations").action(ArgAction::Append))
55 .arg(Arg::new("values").long("values").action(ArgAction::Append))
56 .arg(
57 Arg::new("default-permission")
58 .long("default-permission")
59 .value_parser(aconfig_protos::flag_permission::parse_from_str)
60 .default_value(aconfig_protos::flag_permission::to_string(
61 &commands::DEFAULT_FLAG_PERMISSION,
62 )),
63 )
64 .arg(Arg::new("cache").long("cache").required(true)),
65 )
66 .subcommand(
67 Command::new("create-java-lib")
68 .arg(Arg::new("cache").long("cache").required(true))
69 .arg(Arg::new("out").long("out").required(true))
70 .arg(
71 Arg::new("mode")
72 .long("mode")
73 .value_parser(EnumValueParser::<CodegenMode>::new())
74 .default_value("production"),
75 ),
76 )
77 .subcommand(
78 Command::new("create-cpp-lib")
79 .arg(Arg::new("cache").long("cache").required(true))
80 .arg(Arg::new("out").long("out").required(true))
81 .arg(
82 Arg::new("mode")
83 .long("mode")
84 .value_parser(EnumValueParser::<CodegenMode>::new())
85 .default_value("production"),
86 )
87 .arg(
88 Arg::new("allow-instrumentation")
89 .long("allow-instrumentation")
90 .value_parser(clap::value_parser!(bool))
91 .default_value("false"),
92 ),
93 )
94 .subcommand(
95 Command::new("create-rust-lib")
96 .arg(Arg::new("cache").long("cache").required(true))
97 .arg(Arg::new("out").long("out").required(true))
98 .arg(
99 Arg::new("allow-instrumentation")
100 .long("allow-instrumentation")
101 .value_parser(clap::value_parser!(bool))
102 .default_value("false"),
103 )
104 .arg(
105 Arg::new("mode")
106 .long("mode")
107 .value_parser(EnumValueParser::<CodegenMode>::new())
108 .default_value("production"),
109 ),
110 )
111 .subcommand(
112 Command::new("create-device-config-defaults")
113 .arg(Arg::new("cache").long("cache").action(ArgAction::Append).required(true))
114 .arg(Arg::new("out").long("out").default_value("-")),
115 )
116 .subcommand(
117 Command::new("create-device-config-sysprops")
118 .arg(Arg::new("cache").long("cache").action(ArgAction::Append).required(true))
119 .arg(Arg::new("out").long("out").default_value("-")),
120 )
121 .subcommand(
122 Command::new("dump-cache")
123 .alias("dump")
124 .arg(Arg::new("cache").long("cache").action(ArgAction::Append))
125 .arg(
126 Arg::new("format")
127 .long("format")
128 .value_parser(|s: &str| DumpFormat::try_from(s))
129 .default_value(
130 "{fully_qualified_name} [{container}]: {permission} + {state}",
131 ),
132 )
133 .arg(
134 Arg::new("filter")
135 .long("filter")
136 .action(ArgAction::Append)
137 .help(HELP_DUMP_FILTER.trim()),
138 )
139 .arg(Arg::new("dedup").long("dedup").num_args(0).action(ArgAction::SetTrue))
140 .arg(Arg::new("out").long("out").default_value("-")),
141 )
142 .subcommand(
143 Command::new("create-storage")
144 .arg(
145 Arg::new("container")
146 .long("container")
147 .required(true)
148 .help("The target container for the generated storage file."),
149 )
150 .arg(
151 Arg::new("file")
152 .long("file")
153 .value_parser(|s: &str| StorageFileType::try_from(s)),
154 )
155 .arg(Arg::new("cache").long("cache").action(ArgAction::Append).required(true))
156 .arg(Arg::new("out").long("out").required(true)),
157 )
158 }
159
get_required_arg<'a, T>(matches: &'a ArgMatches, arg_name: &str) -> Result<&'a T> where T: Any + Clone + Send + Sync + 'static,160 fn get_required_arg<'a, T>(matches: &'a ArgMatches, arg_name: &str) -> Result<&'a T>
161 where
162 T: Any + Clone + Send + Sync + 'static,
163 {
164 matches
165 .get_one::<T>(arg_name)
166 .ok_or(anyhow!("internal error: required argument '{}' not found", arg_name))
167 }
168
get_optional_arg<'a, T>(matches: &'a ArgMatches, arg_name: &str) -> Option<&'a T> where T: Any + Clone + Send + Sync + 'static,169 fn get_optional_arg<'a, T>(matches: &'a ArgMatches, arg_name: &str) -> Option<&'a T>
170 where
171 T: Any + Clone + Send + Sync + 'static,
172 {
173 matches.get_one::<T>(arg_name)
174 }
175
open_zero_or_more_files(matches: &ArgMatches, arg_name: &str) -> Result<Vec<Input>>176 fn open_zero_or_more_files(matches: &ArgMatches, arg_name: &str) -> Result<Vec<Input>> {
177 let mut opened_files = vec![];
178 for path in matches.get_many::<String>(arg_name).unwrap_or_default() {
179 let file = Box::new(fs::File::open(path)?);
180 opened_files.push(Input { source: path.to_string(), reader: file });
181 }
182 Ok(opened_files)
183 }
184
open_single_file(matches: &ArgMatches, arg_name: &str) -> Result<Input>185 fn open_single_file(matches: &ArgMatches, arg_name: &str) -> Result<Input> {
186 let Some(path) = matches.get_one::<String>(arg_name) else {
187 bail!("missing argument {}", arg_name);
188 };
189 let file = Box::new(fs::File::open(path)?);
190 Ok(Input { source: path.to_string(), reader: file })
191 }
192
write_output_file_realtive_to_dir(root: &Path, output_file: &OutputFile) -> Result<()>193 fn write_output_file_realtive_to_dir(root: &Path, output_file: &OutputFile) -> Result<()> {
194 let path = root.join(&output_file.path);
195 let parent = path
196 .parent()
197 .ok_or(anyhow!("unable to locate parent of output file {}", path.display()))?;
198 fs::create_dir_all(parent)
199 .with_context(|| format!("failed to create directory {}", parent.display()))?;
200 let mut file =
201 fs::File::create(&path).with_context(|| format!("failed to open {}", path.display()))?;
202 file.write_all(&output_file.contents)
203 .with_context(|| format!("failed to write to {}", path.display()))?;
204 Ok(())
205 }
206
write_output_to_file_or_stdout(path: &str, data: &[u8]) -> Result<()>207 fn write_output_to_file_or_stdout(path: &str, data: &[u8]) -> Result<()> {
208 if path == "-" {
209 io::stdout().write_all(data).context("failed to write to stdout")?;
210 } else {
211 fs::File::create(path)
212 .with_context(|| format!("failed to open {}", path))?
213 .write_all(data)
214 .with_context(|| format!("failed to write to {}", path))?;
215 }
216 Ok(())
217 }
218
main() -> Result<()>219 fn main() -> Result<()> {
220 let matches = cli().get_matches();
221 match matches.subcommand() {
222 Some(("create-cache", sub_matches)) => {
223 let package = get_required_arg::<String>(sub_matches, "package")?;
224 let container =
225 get_optional_arg::<String>(sub_matches, "container").map(|c| c.as_str());
226 let declarations = open_zero_or_more_files(sub_matches, "declarations")?;
227 let values = open_zero_or_more_files(sub_matches, "values")?;
228 let default_permission = get_required_arg::<aconfig_protos::ProtoFlagPermission>(
229 sub_matches,
230 "default-permission",
231 )?;
232 let output = commands::parse_flags(
233 package,
234 container,
235 declarations,
236 values,
237 *default_permission,
238 )
239 .context("failed to create cache")?;
240 let path = get_required_arg::<String>(sub_matches, "cache")?;
241 write_output_to_file_or_stdout(path, &output)?;
242 }
243 Some(("create-java-lib", sub_matches)) => {
244 let cache = open_single_file(sub_matches, "cache")?;
245 let mode = get_required_arg::<CodegenMode>(sub_matches, "mode")?;
246 let generated_files =
247 commands::create_java_lib(cache, *mode).context("failed to create java lib")?;
248 let dir = PathBuf::from(get_required_arg::<String>(sub_matches, "out")?);
249 generated_files
250 .iter()
251 .try_for_each(|file| write_output_file_realtive_to_dir(&dir, file))?;
252 }
253 Some(("create-cpp-lib", sub_matches)) => {
254 let cache = open_single_file(sub_matches, "cache")?;
255 let mode = get_required_arg::<CodegenMode>(sub_matches, "mode")?;
256 let allow_instrumentation =
257 get_required_arg::<bool>(sub_matches, "allow-instrumentation")?;
258 let generated_files = commands::create_cpp_lib(cache, *mode, *allow_instrumentation)
259 .context("failed to create cpp lib")?;
260 let dir = PathBuf::from(get_required_arg::<String>(sub_matches, "out")?);
261 generated_files
262 .iter()
263 .try_for_each(|file| write_output_file_realtive_to_dir(&dir, file))?;
264 }
265 Some(("create-rust-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_file = commands::create_rust_lib(cache, *mode, *allow_instrumentation)
271 .context("failed to create rust lib")?;
272 let dir = PathBuf::from(get_required_arg::<String>(sub_matches, "out")?);
273 write_output_file_realtive_to_dir(&dir, &generated_file)?;
274 }
275 Some(("create-device-config-defaults", sub_matches)) => {
276 let cache = open_single_file(sub_matches, "cache")?;
277 let output = commands::create_device_config_defaults(cache)
278 .context("failed to create device config defaults")?;
279 let path = get_required_arg::<String>(sub_matches, "out")?;
280 write_output_to_file_or_stdout(path, &output)?;
281 }
282 Some(("create-device-config-sysprops", sub_matches)) => {
283 let cache = open_single_file(sub_matches, "cache")?;
284 let output = commands::create_device_config_sysprops(cache)
285 .context("failed to create device config sysprops")?;
286 let path = get_required_arg::<String>(sub_matches, "out")?;
287 write_output_to_file_or_stdout(path, &output)?;
288 }
289 Some(("dump-cache", sub_matches)) => {
290 let input = open_zero_or_more_files(sub_matches, "cache")?;
291 let format = get_required_arg::<DumpFormat>(sub_matches, "format")
292 .context("failed to dump previously parsed flags")?;
293 let filters = sub_matches
294 .get_many::<String>("filter")
295 .unwrap_or_default()
296 .map(String::as_ref)
297 .collect::<Vec<_>>();
298 let dedup = get_required_arg::<bool>(sub_matches, "dedup")?;
299 let output = commands::dump_parsed_flags(input, format.clone(), &filters, *dedup)?;
300 let path = get_required_arg::<String>(sub_matches, "out")?;
301 write_output_to_file_or_stdout(path, &output)?;
302 }
303 Some(("create-storage", sub_matches)) => {
304 let file = get_required_arg::<StorageFileType>(sub_matches, "file")
305 .context("Invalid storage file selection")?;
306 let cache = open_zero_or_more_files(sub_matches, "cache")?;
307 let container = get_required_arg::<String>(sub_matches, "container")?;
308 let path = get_required_arg::<String>(sub_matches, "out")?;
309 let output = commands::create_storage(cache, container, file)
310 .context("failed to create storage files")?;
311 write_output_to_file_or_stdout(path, &output)?;
312 }
313 _ => unreachable!(),
314 }
315 Ok(())
316 }
317