• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 convert_finalized_flags::FinalizedFlagMap;
37 use dump::DumpFormat;
38 
39 #[cfg(test)]
40 mod test;
41 
42 use commands::{Input, OutputFile};
43 
44 const HELP_DUMP_CACHE: &str = r#"
45 An aconfig cache file, created via `aconfig create-cache`.
46 "#;
47 
48 const HELP_DUMP_FORMAT: &str = r#"
49 Change the output format for each flag.
50 
51 The argument to --format is a format string. Each flag will be a copy of this string, with certain
52 placeholders replaced by attributes of the flag. The placeholders are
53 
54   {package}
55   {name}
56   {namespace}
57   {description}
58   {bug}
59   {state}
60   {state:bool}
61   {permission}
62   {trace}
63   {trace:paths}
64   {is_fixed_read_only}
65   {is_exported}
66   {container}
67   {metadata}
68   {fully_qualified_name}
69 
70 Note: the format strings "textproto" and "protobuf" are handled in a special way: they output all
71 flag attributes in text or binary protobuf format.
72 
73 Examples:
74 
75   # See which files were read to determine the value of a flag; the files were read in the order
76   # listed.
77   --format='{fully_qualified_name} {trace}'
78 
79   # Trace the files read for a specific flag. Useful during debugging.
80   --filter=fully_qualified_name:com.foo.flag_name --format='{trace}'
81 
82   # Print a somewhat human readable description of each flag.
83   --format='The flag {name} in package {package} is {state} and has permission {permission}.'
84 "#;
85 
86 const HELP_DUMP_FILTER: &str = r#"
87 Limit which flags to output. If --filter is omitted, all flags will be printed. If multiple
88 --filter options are provided, the output will be limited to flags that match any of the filters.
89 
90 The argument to --filter is a search query. Multiple queries can be AND-ed together by
91 concatenating them with a plus sign.
92 
93 Valid queries are:
94 
95   package:<string>
96   name:<string>
97   namespace:<string>
98   bug:<string>
99   state:ENABLED|DISABLED
100   permission:READ_ONLY|READ_WRITE
101   is_fixed_read_only:true|false
102   is_exported:true|false
103   container:<string>
104   fully_qualified_name:<string>
105 
106 Note: there is currently no support for filtering based on these flag attributes: description,
107 trace, metadata.
108 
109 Examples:
110 
111   # Print a single flag:
112   --filter=fully_qualified_name:com.foo.flag_name
113 
114   # Print all known information about a single flag:
115   --filter=fully_qualified_name:com.foo.flag_name --format=textproto
116 
117   # Print all flags in the com.foo package, and all enabled flags in the com.bar package:
118   --filter=package:com.foo --filter=package.com.bar+state:ENABLED
119 "#;
120 
121 const HELP_DUMP_DEDUP: &str = r#"
122 Allow the same flag to be present in multiple cache files; if duplicates are found, collapse into
123 a single instance.
124 "#;
125 
cli() -> Command126 fn cli() -> Command {
127     Command::new("aconfig")
128         .subcommand_required(true)
129         .subcommand(
130             Command::new("create-cache")
131                 .arg(Arg::new("package").long("package").required(true))
132                 .arg(Arg::new("container").long("container").required(true))
133                 .arg(Arg::new("declarations").long("declarations").action(ArgAction::Append))
134                 .arg(Arg::new("values").long("values").action(ArgAction::Append))
135                 .arg(
136                     Arg::new("default-permission")
137                         .long("default-permission")
138                         .value_parser(aconfig_protos::flag_permission::parse_from_str)
139                         .default_value(aconfig_protos::flag_permission::to_string(
140                             &commands::DEFAULT_FLAG_PERMISSION,
141                         )),
142                 )
143                 .arg(
144                     Arg::new("allow-read-write")
145                         .long("allow-read-write")
146                         .value_parser(clap::value_parser!(bool))
147                         .default_value("true"),
148                 )
149                 .arg(Arg::new("cache").long("cache").required(true)),
150         )
151         .subcommand(
152             Command::new("create-java-lib")
153                 .arg(Arg::new("cache").long("cache").required(true))
154                 .arg(Arg::new("out").long("out").required(true))
155                 .arg(
156                     Arg::new("mode")
157                         .long("mode")
158                         .value_parser(EnumValueParser::<CodegenMode>::new())
159                         .default_value("production"),
160                 )
161                 .arg(
162                     Arg::new("single-exported-file")
163                         .long("single-exported-file")
164                         .value_parser(clap::value_parser!(bool))
165                         .default_value("false"),
166                 )
167                 // TODO: b/395899938 - clean up flags for switching to new storage
168                 .arg(
169                     Arg::new("allow-instrumentation")
170                         .long("allow-instrumentation")
171                         .value_parser(clap::value_parser!(bool))
172                         .default_value("false"),
173                 )
174                 // TODO: b/395899938 - clean up flags for switching to new storage
175                 .arg(
176                     Arg::new("new-exported")
177                         .long("new-exported")
178                         .value_parser(clap::value_parser!(bool))
179                         .default_value("false"),
180                 )
181                 // Allows build flag toggling of checking API level in exported
182                 // flag lib for finalized API flags.
183                 // TODO: b/378936061 - Remove once build flag for API level
184                 // check is fully enabled.
185                 .arg(
186                     Arg::new("check-api-level")
187                         .long("check-api-level")
188                         .value_parser(clap::value_parser!(bool))
189                         .default_value("false"),
190                 ),
191         )
192         .subcommand(
193             Command::new("create-cpp-lib")
194                 .arg(Arg::new("cache").long("cache").required(true))
195                 .arg(Arg::new("out").long("out").required(true))
196                 .arg(
197                     Arg::new("mode")
198                         .long("mode")
199                         .value_parser(EnumValueParser::<CodegenMode>::new())
200                         .default_value("production"),
201                 )
202                 .arg(
203                     Arg::new("allow-instrumentation")
204                         .long("allow-instrumentation")
205                         .value_parser(clap::value_parser!(bool))
206                         .default_value("false"),
207                 ),
208         )
209         .subcommand(
210             Command::new("create-rust-lib")
211                 .arg(Arg::new("cache").long("cache").required(true))
212                 .arg(Arg::new("out").long("out").required(true))
213                 .arg(
214                     Arg::new("allow-instrumentation")
215                         .long("allow-instrumentation")
216                         .value_parser(clap::value_parser!(bool))
217                         .default_value("false"),
218                 )
219                 .arg(
220                     Arg::new("mode")
221                         .long("mode")
222                         .value_parser(EnumValueParser::<CodegenMode>::new())
223                         .default_value("production"),
224                 ),
225         )
226         .subcommand(
227             Command::new("create-device-config-defaults")
228                 .arg(Arg::new("cache").long("cache").action(ArgAction::Append).required(true))
229                 .arg(Arg::new("out").long("out").default_value("-")),
230         )
231         .subcommand(
232             Command::new("create-device-config-sysprops")
233                 .arg(Arg::new("cache").long("cache").action(ArgAction::Append).required(true))
234                 .arg(Arg::new("out").long("out").default_value("-")),
235         )
236         .subcommand(
237             Command::new("dump-cache")
238                 .alias("dump")
239                 .arg(
240                     Arg::new("cache")
241                         .long("cache")
242                         .action(ArgAction::Append)
243                         .long_help(HELP_DUMP_CACHE.trim()),
244                 )
245                 .arg(
246                     Arg::new("format")
247                         .long("format")
248                         .value_parser(|s: &str| DumpFormat::try_from(s))
249                         .default_value(
250                             "{fully_qualified_name} [{container}]: {permission} + {state}",
251                         )
252                         .long_help(HELP_DUMP_FORMAT.trim()),
253                 )
254                 .arg(
255                     Arg::new("filter")
256                         .long("filter")
257                         .action(ArgAction::Append)
258                         .long_help(HELP_DUMP_FILTER.trim()),
259                 )
260                 .arg(
261                     Arg::new("dedup")
262                         .long("dedup")
263                         .num_args(0)
264                         .action(ArgAction::SetTrue)
265                         .long_help(HELP_DUMP_DEDUP.trim()),
266                 )
267                 .arg(Arg::new("out").long("out").default_value("-")),
268         )
269         .subcommand(
270             Command::new("create-storage")
271                 .arg(
272                     Arg::new("container")
273                         .long("container")
274                         .required(true)
275                         .help("The target container for the generated storage file."),
276                 )
277                 .arg(
278                     Arg::new("file")
279                         .long("file")
280                         .value_parser(|s: &str| StorageFileType::try_from(s)),
281                 )
282                 .arg(Arg::new("cache").long("cache").action(ArgAction::Append).required(true))
283                 .arg(Arg::new("out").long("out").required(true))
284                 .arg(
285                     Arg::new("version")
286                         .long("version")
287                         .required(false)
288                         .value_parser(|s: &str| s.parse::<u32>()),
289                 ),
290         )
291 }
292 
get_required_arg<'a, T>(matches: &'a ArgMatches, arg_name: &str) -> Result<&'a T> where T: Any + Clone + Send + Sync + 'static,293 fn get_required_arg<'a, T>(matches: &'a ArgMatches, arg_name: &str) -> Result<&'a T>
294 where
295     T: Any + Clone + Send + Sync + 'static,
296 {
297     matches
298         .get_one::<T>(arg_name)
299         .ok_or(anyhow!("internal error: required argument '{}' not found", arg_name))
300 }
301 
get_optional_arg<'a, T>(matches: &'a ArgMatches, arg_name: &str) -> Option<&'a T> where T: Any + Clone + Send + Sync + 'static,302 fn get_optional_arg<'a, T>(matches: &'a ArgMatches, arg_name: &str) -> Option<&'a T>
303 where
304     T: Any + Clone + Send + Sync + 'static,
305 {
306     matches.get_one::<T>(arg_name)
307 }
308 
open_zero_or_more_files(matches: &ArgMatches, arg_name: &str) -> Result<Vec<Input>>309 fn open_zero_or_more_files(matches: &ArgMatches, arg_name: &str) -> Result<Vec<Input>> {
310     let mut opened_files = vec![];
311     for path in matches.get_many::<String>(arg_name).unwrap_or_default() {
312         let file = Box::new(fs::File::open(path)?);
313         opened_files.push(Input { source: path.to_string(), reader: file });
314     }
315     Ok(opened_files)
316 }
317 
open_single_file(matches: &ArgMatches, arg_name: &str) -> Result<Input>318 fn open_single_file(matches: &ArgMatches, arg_name: &str) -> Result<Input> {
319     let Some(path) = matches.get_one::<String>(arg_name) else {
320         bail!("missing argument {}", arg_name);
321     };
322     let file = Box::new(fs::File::open(path)?);
323     Ok(Input { source: path.to_string(), reader: file })
324 }
325 
write_output_file_realtive_to_dir(root: &Path, output_file: &OutputFile) -> Result<()>326 fn write_output_file_realtive_to_dir(root: &Path, output_file: &OutputFile) -> Result<()> {
327     let path = root.join(&output_file.path);
328     let parent = path
329         .parent()
330         .ok_or(anyhow!("unable to locate parent of output file {}", path.display()))?;
331     fs::create_dir_all(parent)
332         .with_context(|| format!("failed to create directory {}", parent.display()))?;
333     let mut file =
334         fs::File::create(&path).with_context(|| format!("failed to open {}", path.display()))?;
335     file.write_all(&output_file.contents)
336         .with_context(|| format!("failed to write to {}", path.display()))?;
337     Ok(())
338 }
339 
write_output_to_file_or_stdout(path: &str, data: &[u8]) -> Result<()>340 fn write_output_to_file_or_stdout(path: &str, data: &[u8]) -> Result<()> {
341     if path == "-" {
342         io::stdout().write_all(data).context("failed to write to stdout")?;
343     } else {
344         fs::File::create(path)
345             .with_context(|| format!("failed to open {}", path))?
346             .write_all(data)
347             .with_context(|| format!("failed to write to {}", path))?;
348     }
349     Ok(())
350 }
351 
load_finalized_flags() -> Result<FinalizedFlagMap>352 fn load_finalized_flags() -> Result<FinalizedFlagMap> {
353     let json_str = include_str!(concat!(env!("OUT_DIR"), "/finalized_flags_record.json"));
354     let map = serde_json::from_str(json_str)?;
355     Ok(map)
356 }
357 
main() -> Result<()>358 fn main() -> Result<()> {
359     let matches = cli().get_matches();
360     match matches.subcommand() {
361         Some(("create-cache", sub_matches)) => {
362             let package = get_required_arg::<String>(sub_matches, "package")?;
363             let container =
364                 get_optional_arg::<String>(sub_matches, "container").map(|c| c.as_str());
365             let declarations = open_zero_or_more_files(sub_matches, "declarations")?;
366             let values = open_zero_or_more_files(sub_matches, "values")?;
367             let default_permission = get_required_arg::<aconfig_protos::ProtoFlagPermission>(
368                 sub_matches,
369                 "default-permission",
370             )?;
371             let allow_read_write = get_optional_arg::<bool>(sub_matches, "allow-read-write")
372                 .expect("failed to parse allow-read-write");
373             let output = commands::parse_flags(
374                 package,
375                 container,
376                 declarations,
377                 values,
378                 *default_permission,
379                 *allow_read_write,
380             )
381             .context("failed to create cache")?;
382             let path = get_required_arg::<String>(sub_matches, "cache")?;
383             write_output_to_file_or_stdout(path, &output)?;
384         }
385         Some(("create-java-lib", sub_matches)) => {
386             let cache = open_single_file(sub_matches, "cache")?;
387             let mode = get_required_arg::<CodegenMode>(sub_matches, "mode")?;
388             let allow_instrumentation =
389                 get_required_arg::<bool>(sub_matches, "allow-instrumentation")?;
390             let new_exported = get_required_arg::<bool>(sub_matches, "new-exported")?;
391             let single_exported_file =
392                 get_required_arg::<bool>(sub_matches, "single-exported-file")?;
393 
394             let check_api_level = get_required_arg::<bool>(sub_matches, "check-api-level")?;
395             let finalized_flags: FinalizedFlagMap =
396                 if *check_api_level { load_finalized_flags()? } else { FinalizedFlagMap::new() };
397 
398             let generated_files = commands::create_java_lib(
399                 cache,
400                 *mode,
401                 *allow_instrumentation,
402                 *new_exported,
403                 *single_exported_file,
404                 finalized_flags,
405             )
406             .context("failed to create java lib")?;
407             let dir = PathBuf::from(get_required_arg::<String>(sub_matches, "out")?);
408             generated_files
409                 .iter()
410                 .try_for_each(|file| write_output_file_realtive_to_dir(&dir, file))?;
411         }
412         Some(("create-cpp-lib", sub_matches)) => {
413             let cache = open_single_file(sub_matches, "cache")?;
414             let mode = get_required_arg::<CodegenMode>(sub_matches, "mode")?;
415             let generated_files =
416                 commands::create_cpp_lib(cache, *mode).context("failed to create cpp lib")?;
417             let dir = PathBuf::from(get_required_arg::<String>(sub_matches, "out")?);
418             generated_files
419                 .iter()
420                 .try_for_each(|file| write_output_file_realtive_to_dir(&dir, file))?;
421         }
422         Some(("create-rust-lib", sub_matches)) => {
423             let cache = open_single_file(sub_matches, "cache")?;
424             let mode = get_required_arg::<CodegenMode>(sub_matches, "mode")?;
425             let generated_file =
426                 commands::create_rust_lib(cache, *mode).context("failed to create rust lib")?;
427             let dir = PathBuf::from(get_required_arg::<String>(sub_matches, "out")?);
428             write_output_file_realtive_to_dir(&dir, &generated_file)?;
429         }
430         Some(("create-device-config-defaults", sub_matches)) => {
431             let cache = open_single_file(sub_matches, "cache")?;
432             let output = commands::create_device_config_defaults(cache)
433                 .context("failed to create device config defaults")?;
434             let path = get_required_arg::<String>(sub_matches, "out")?;
435             write_output_to_file_or_stdout(path, &output)?;
436         }
437         Some(("create-device-config-sysprops", sub_matches)) => {
438             let cache = open_single_file(sub_matches, "cache")?;
439             let output = commands::create_device_config_sysprops(cache)
440                 .context("failed to create device config sysprops")?;
441             let path = get_required_arg::<String>(sub_matches, "out")?;
442             write_output_to_file_or_stdout(path, &output)?;
443         }
444         Some(("dump-cache", sub_matches)) => {
445             let input = open_zero_or_more_files(sub_matches, "cache")?;
446             let format = get_required_arg::<DumpFormat>(sub_matches, "format")
447                 .context("failed to dump previously parsed flags")?;
448             let filters = sub_matches
449                 .get_many::<String>("filter")
450                 .unwrap_or_default()
451                 .map(String::as_ref)
452                 .collect::<Vec<_>>();
453             let dedup = get_required_arg::<bool>(sub_matches, "dedup")?;
454             let output = commands::dump_parsed_flags(input, format.clone(), &filters, *dedup)?;
455             let path = get_required_arg::<String>(sub_matches, "out")?;
456             write_output_to_file_or_stdout(path, &output)?;
457         }
458         Some(("create-storage", sub_matches)) => {
459             let version =
460                 get_optional_arg::<u32>(sub_matches, "version").unwrap_or(&DEFAULT_FILE_VERSION);
461             if *version > MAX_SUPPORTED_FILE_VERSION {
462                 bail!("Invalid version selected ({})", version);
463             }
464             let file = get_required_arg::<StorageFileType>(sub_matches, "file")
465                 .context("Invalid storage file selection")?;
466             let cache = open_zero_or_more_files(sub_matches, "cache")?;
467             let container = get_required_arg::<String>(sub_matches, "container")?;
468             let path = get_required_arg::<String>(sub_matches, "out")?;
469 
470             let output = commands::create_storage(cache, container, file, *version)
471                 .context("failed to create storage files")?;
472             write_output_to_file_or_stdout(path, &output)?;
473         }
474         _ => unreachable!(),
475     }
476     Ok(())
477 }
478