xref: /aosp_15_r20/development/tools/cargo_embargo/src/config.rs (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
1 // Copyright (C) 2023 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! Code for reading configuration json files, usually called `cargo_embargo.json`.
16 //!
17 //! A single configuration file may cover several Rust packages under its directory tree, and may
18 //! have multiple "variants". A variant is a particular configuration for building a set of
19 //! packages, such as what feature flags to enable. Multiple variants are most often used to build
20 //! both a std variant of a library and a no_std variant. Each variant generates one or more modules
21 //! for each package, usually distinguished by a suffix.
22 //!
23 //! The [`Config`] struct has a map of `PackageConfig`s keyed by package name (for options that
24 //! apply across all variants of a package), and a vector of `VariantConfig`s. There must be at
25 //! least one variant for the configuration to generate any output. Each `VariantConfig` has the
26 //! options that apply to that variant across all packages, and then a map of
27 //! `PackageVariantConfig`s for options specific to a particular package of the variant.
28 
29 use anyhow::{bail, Context, Result};
30 use serde::{Deserialize, Serialize};
31 use serde_json::{Map, Value};
32 use std::collections::BTreeMap;
33 use std::path::{Path, PathBuf};
34 
default_apex_available() -> Vec<String>35 fn default_apex_available() -> Vec<String> {
36     vec!["//apex_available:platform".to_string(), "//apex_available:anyapex".to_string()]
37 }
38 
is_default_apex_available(apex_available: &[String]) -> bool39 fn is_default_apex_available(apex_available: &[String]) -> bool {
40     apex_available == default_apex_available()
41 }
42 
default_true() -> bool43 fn default_true() -> bool {
44     true
45 }
46 
is_true(value: &bool) -> bool47 fn is_true(value: &bool) -> bool {
48     *value
49 }
50 
is_false(value: &bool) -> bool51 fn is_false(value: &bool) -> bool {
52     !*value
53 }
54 
55 /// Options that apply to everything.
56 #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
57 #[serde(deny_unknown_fields)]
58 pub struct Config {
59     pub variants: Vec<VariantConfig>,
60     /// Package specific config options across all variants.
61     #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
62     pub package: BTreeMap<String, PackageConfig>,
63 }
64 
65 /// Inserts entries from `defaults` into `variant` if neither it nor `ignored_fields` contain
66 /// matching keys.
add_defaults_to_variant( variant: &mut Map<String, Value>, defaults: &Map<String, Value>, ignored_fields: &[&str], )67 fn add_defaults_to_variant(
68     variant: &mut Map<String, Value>,
69     defaults: &Map<String, Value>,
70     ignored_fields: &[&str],
71 ) {
72     for (key, value) in defaults {
73         if !ignored_fields.contains(&key.as_str()) && !variant.contains_key(key) {
74             variant.insert(key.to_owned(), value.to_owned());
75         }
76     }
77 }
78 
79 impl Config {
80     /// Names of all fields in [`Config`] other than `variants` (which is treated specially).
81     const FIELD_NAMES: [&'static str; 1] = ["package"];
82 
83     /// Parses an instance of this config from the given JSON file.
from_file(filename: &Path) -> Result<Self>84     pub fn from_file(filename: &Path) -> Result<Self> {
85         let json_string = std::fs::read_to_string(filename)
86             .with_context(|| format!("failed to read file: {:?}", filename))?;
87         Self::from_json_str(&json_string)
88     }
89 
90     /// Parses an instance of this config from a string of JSON.
from_json_str(json_str: &str) -> Result<Self>91     pub fn from_json_str(json_str: &str) -> Result<Self> {
92         // Ignore comments.
93         let json_str: String =
94             json_str.lines().filter(|l| !l.trim_start().starts_with("//")).collect();
95         // First parse into untyped map.
96         let mut config: Map<String, Value> =
97             serde_json::from_str(&json_str).context("failed to parse config")?;
98 
99         // Flatten variants. First, get the variants from the config file.
100         let mut variants = match config.remove("variants") {
101             Some(Value::Array(v)) => v,
102             Some(_) => bail!("Failed to parse config: variants is not an array"),
103             None => {
104                 // There are no variants, so just put everything into a single variant.
105                 vec![Value::Object(Map::new())]
106             }
107         };
108         // Set default values in variants from top-level config.
109         for variant in &mut variants {
110             let variant = variant
111                 .as_object_mut()
112                 .context("Failed to parse config: variant is not an object")?;
113             add_defaults_to_variant(variant, &config, &Config::FIELD_NAMES);
114 
115             if let Some(packages) = config.get("package") {
116                 // Copy package entries across.
117                 let variant_packages = variant
118                     .entry("package")
119                     .or_insert_with(|| Map::new().into())
120                     .as_object_mut()
121                     .context("Failed to parse config: variant package is not an object")?;
122                 for (package_name, package_config) in packages
123                     .as_object()
124                     .context("Failed to parse config: package is not an object")?
125                 {
126                     let variant_package = variant_packages
127                         .entry(package_name)
128                         .or_insert_with(|| Map::new().into())
129                         .as_object_mut()
130                         .context(
131                             "Failed to parse config: variant package config is not an object",
132                         )?;
133                     add_defaults_to_variant(
134                         variant_package,
135                         package_config
136                             .as_object()
137                             .context("Failed to parse config: package is not an object")?,
138                         &PackageConfig::FIELD_NAMES,
139                     );
140                 }
141             }
142         }
143         // Remove other entries from the top-level config, and put variants back.
144         config.retain(|key, _| Self::FIELD_NAMES.contains(&key.as_str()));
145         if let Some(package) = config.get_mut("package") {
146             for value in package
147                 .as_object_mut()
148                 .context("Failed to parse config: package is not an object")?
149                 .values_mut()
150             {
151                 let package_config = value
152                     .as_object_mut()
153                     .context("Failed to parse config: package is not an object")?;
154                 package_config.retain(|key, _| PackageConfig::FIELD_NAMES.contains(&key.as_str()))
155             }
156         }
157         config.insert("variants".to_string(), Value::Array(variants));
158 
159         // Parse into `Config` struct.
160         serde_json::from_value(Value::Object(config)).context("failed to parse config")
161     }
162 
163     /// Serializes an instance of this config to a string of pretty-printed JSON.
to_json_string(&self) -> Result<String>164     pub fn to_json_string(&self) -> Result<String> {
165         // First convert to an untyped map.
166         let Value::Object(mut config) = serde_json::to_value(self)? else {
167             panic!("Config wasn't a map.");
168         };
169 
170         // Factor out common options which are set for all variants.
171         let Value::Array(mut variants) = config.remove("variants").unwrap() else {
172             panic!("variants wasn't an array.")
173         };
174         let mut packages = if let Some(Value::Object(packages)) = config.remove("package") {
175             packages
176         } else {
177             Map::new()
178         };
179         for (key, value) in variants[0].as_object().unwrap() {
180             if key == "package" {
181                 for (package_name, package_config) in value.as_object().unwrap() {
182                     for (package_key, package_value) in package_config.as_object().unwrap() {
183                         // Check whether all other variants have the same entry for the same package.
184                         if variants[1..variants.len()].iter().all(|variant| {
185                             if let Some(Value::Object(variant_packages)) =
186                                 variant.as_object().unwrap().get("package")
187                             {
188                                 if let Some(Value::Object(variant_package_config)) =
189                                     variant_packages.get(package_name)
190                                 {
191                                     return variant_package_config.get(package_key)
192                                         == Some(package_value);
193                                 }
194                             }
195                             false
196                         }) {
197                             packages
198                                 .entry(package_name)
199                                 .or_insert_with(|| Map::new().into())
200                                 .as_object_mut()
201                                 .unwrap()
202                                 .insert(package_key.to_owned(), package_value.to_owned());
203                         }
204                     }
205                 }
206             } else {
207                 // Check whether all the other variants have the same entry.
208                 if variants[1..variants.len()]
209                     .iter()
210                     .all(|variant| variant.as_object().unwrap().get(key) == Some(value))
211                 {
212                     // Add it to the top-level config.
213                     config.insert(key.to_owned(), value.to_owned());
214                 }
215             }
216         }
217         // Remove factored out common options from all variants.
218         for key in config.keys() {
219             for variant in &mut variants {
220                 variant.as_object_mut().unwrap().remove(key);
221             }
222         }
223         // Likewise, remove package options factored out from variants.
224         for (package_name, package_config) in &packages {
225             for package_key in package_config.as_object().unwrap().keys() {
226                 for variant in &mut variants {
227                     if let Some(Value::Object(variant_packages)) = variant.get_mut("package") {
228                         if let Some(Value::Object(variant_package_config)) =
229                             variant_packages.get_mut(package_name)
230                         {
231                             variant_package_config.remove(package_key);
232                         }
233                     }
234                 }
235             }
236         }
237         // Remove any variant packages which are now empty.
238         for variant in &mut variants {
239             if let Some(Value::Object(variant_packages)) = variant.get_mut("package") {
240                 variant_packages
241                     .retain(|_, package_config| !package_config.as_object().unwrap().is_empty());
242                 if variant_packages.is_empty() {
243                     variant.as_object_mut().unwrap().remove("package");
244                 }
245             }
246         }
247         // Put packages and variants back into the top-level config.
248         if variants.len() > 1 || !variants[0].as_object().unwrap().is_empty() {
249             config.insert("variants".to_string(), Value::Array(variants));
250         }
251         if !packages.is_empty() {
252             config.insert("package".to_string(), Value::Object(packages));
253         }
254 
255         // Serialise the map into a JSON string.
256         serde_json::to_string_pretty(&config).context("failed to serialize config")
257     }
258 }
259 
260 #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
261 #[serde(deny_unknown_fields)]
262 pub struct VariantConfig {
263     /// Whether to output `rust_test` modules.
264     #[serde(default, skip_serializing_if = "is_false")]
265     pub tests: bool,
266     /// Set of features to enable. If not set, uses the default crate features.
267     #[serde(skip_serializing_if = "Option::is_none")]
268     pub features: Option<Vec<String>>,
269     /// Whether to build with `--workspace`.
270     #[serde(default, skip_serializing_if = "is_false")]
271     pub workspace: bool,
272     /// When workspace is enabled, list of `--exclude` crates.
273     #[serde(default, skip_serializing_if = "Vec::is_empty")]
274     pub workspace_excludes: Vec<String>,
275     /// Value to use for every generated module's `defaults` field.
276     #[serde(skip_serializing_if = "Option::is_none")]
277     pub global_defaults: Option<String>,
278     /// Value to use for every generated library module's `apex_available` field.
279     #[serde(default = "default_apex_available", skip_serializing_if = "is_default_apex_available")]
280     pub apex_available: Vec<String>,
281     /// Value to use for every generated library module's `native_bridge_supported` field.
282     #[serde(default, skip_serializing_if = "is_false")]
283     pub native_bridge_supported: bool,
284     /// Value to use for every generated library module's `product_available` field.
285     #[serde(default = "default_true", skip_serializing_if = "is_true")]
286     pub product_available: bool,
287     /// Value to use for every generated library module's `ramdisk_available` field.
288     #[serde(default, skip_serializing_if = "is_false")]
289     pub ramdisk_available: bool,
290     /// Value to use for every generated library module's `recovery_available` field.
291     #[serde(default, skip_serializing_if = "is_false")]
292     pub recovery_available: bool,
293     /// Value to use for every generated library module's `vendor_available` field.
294     #[serde(default = "default_true", skip_serializing_if = "is_true")]
295     pub vendor_available: bool,
296     /// Value to use for every generated library module's `vendor_ramdisk_available` field.
297     #[serde(default, skip_serializing_if = "is_false")]
298     pub vendor_ramdisk_available: bool,
299     /// Minimum SDK version.
300     #[serde(skip_serializing_if = "Option::is_none")]
301     pub min_sdk_version: Option<String>,
302     /// Map of renames for modules. For example, if a "libfoo" would be generated and there is an
303     /// entry ("libfoo", "libbar"), the generated module will be called "libbar" instead.
304     ///
305     /// Also, affects references to dependencies (e.g. in a "static_libs" list), even those outside
306     /// the project being processed.
307     #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
308     pub module_name_overrides: BTreeMap<String, String>,
309     /// Package specific config options.
310     #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
311     pub package: BTreeMap<String, PackageVariantConfig>,
312     /// `cfg` flags in this list will not be included.
313     #[serde(default, skip_serializing_if = "Vec::is_empty")]
314     pub cfg_blocklist: Vec<String>,
315     /// Extra `cfg` flags to enable in output modules.
316     #[serde(default, skip_serializing_if = "Vec::is_empty")]
317     pub extra_cfg: Vec<String>,
318     /// Modules in this list will not be generated.
319     #[serde(default, skip_serializing_if = "Vec::is_empty")]
320     pub module_blocklist: Vec<String>,
321     /// Modules name => Soong "visibility" property.
322     #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
323     pub module_visibility: BTreeMap<String, Vec<String>>,
324     /// Whether to run the cargo build and parse its output, rather than just figuring things out
325     /// from the cargo metadata.
326     #[serde(default = "default_true", skip_serializing_if = "is_true")]
327     pub run_cargo: bool,
328     /// Generate Android build rules at Android.bp for this variant if true.
329     #[serde(default = "default_true", skip_serializing_if = "is_true")]
330     pub generate_androidbp: bool,
331     /// Generate Trusty build rules at rules.mk and Android.bp if true.
332     #[serde(default, skip_serializing_if = "is_false")]
333     pub generate_rulesmk: bool,
334 }
335 
336 impl Default for VariantConfig {
default() -> Self337     fn default() -> Self {
338         Self {
339             tests: false,
340             features: Default::default(),
341             workspace: false,
342             workspace_excludes: Default::default(),
343             global_defaults: None,
344             apex_available: default_apex_available(),
345             native_bridge_supported: false,
346             product_available: true,
347             ramdisk_available: false,
348             recovery_available: false,
349             vendor_available: true,
350             vendor_ramdisk_available: false,
351             min_sdk_version: None,
352             module_name_overrides: Default::default(),
353             package: Default::default(),
354             cfg_blocklist: Default::default(),
355             extra_cfg: Default::default(),
356             module_blocklist: Default::default(),
357             module_visibility: Default::default(),
358             run_cargo: true,
359             generate_androidbp: true,
360             generate_rulesmk: false,
361         }
362     }
363 }
364 
365 /// Options that apply to everything in a package (i.e. everything associated with a particular
366 /// Cargo.toml file), for all variants.
367 #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
368 #[serde(deny_unknown_fields)]
369 pub struct PackageConfig {
370     /// File with content to append to the end of the generated Android.bp.
371     #[serde(skip_serializing_if = "Option::is_none")]
372     pub add_toplevel_block: Option<PathBuf>,
373     /// Patch file to apply after Android.bp is generated.
374     #[serde(skip_serializing_if = "Option::is_none")]
375     pub patch: Option<PathBuf>,
376     /// Patch file to apply after rules.mk is generated.
377     #[serde(skip_serializing_if = "Option::is_none")]
378     pub rulesmk_patch: Option<PathBuf>,
379     /// `license_text` to use for `license` module, overriding the `license_file` given by the
380     /// package or the default "LICENSE".
381     #[serde(skip_serializing_if = "Option::is_none")]
382     pub license_text: Option<Vec<String>>,
383 }
384 
385 impl PackageConfig {
386     /// Names of all the fields on `PackageConfig`.
387     const FIELD_NAMES: [&'static str; 4] =
388         ["add_toplevel_block", "license_text", "patch", "rulesmk_patch"];
389 }
390 
391 /// Options that apply to everything in a package (i.e. everything associated with a particular
392 /// Cargo.toml file), for a particular variant.
393 #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
394 #[serde(deny_unknown_fields)]
395 pub struct PackageVariantConfig {
396     /// Link against `alloc`. Only valid if `no_std` is also true.
397     #[serde(default, skip_serializing_if = "is_false")]
398     pub alloc: bool,
399     /// Whether to compile for device. Defaults to true.
400     #[serde(default = "default_true", skip_serializing_if = "is_true")]
401     pub device_supported: bool,
402     /// Whether to compile for host. Defaults to true.
403     #[serde(default = "default_true", skip_serializing_if = "is_true")]
404     pub host_supported: bool,
405     /// Whether to compile for non-build host targets. Defaults to true.
406     #[serde(default = "default_true", skip_serializing_if = "is_true")]
407     pub host_cross_supported: bool,
408     /// Add a `compile_multilib: "first"` property to host modules.
409     #[serde(default, skip_serializing_if = "is_false")]
410     pub host_first_multilib: bool,
411     /// Generate "rust_library_rlib" instead of "rust_library".
412     #[serde(default, skip_serializing_if = "is_false")]
413     pub force_rlib: bool,
414     /// Whether to disable "unit_test" for "rust_test" modules.
415     // TODO: Should probably be a list of modules or crates. A package might have a mix of unit and
416     // integration tests.
417     #[serde(default, skip_serializing_if = "is_false")]
418     pub no_presubmit: bool,
419     /// File with content to append to the end of each generated module.
420     #[serde(skip_serializing_if = "Option::is_none")]
421     pub add_module_block: Option<PathBuf>,
422     /// Modules in this list will not be added as dependencies of generated modules.
423     #[serde(default, skip_serializing_if = "Vec::is_empty")]
424     pub dep_blocklist: Vec<String>,
425     /// Don't link against `std`, only `core`.
426     #[serde(default, skip_serializing_if = "is_false")]
427     pub no_std: bool,
428     /// Copy build.rs output to ./out/* and add a genrule to copy ./out/* to genrule output.
429     /// For crates with code pattern:
430     ///     include!(concat!(env!("OUT_DIR"), "/<some_file>.rs"))
431     #[serde(default, skip_serializing_if = "is_false")]
432     pub copy_out: bool,
433     /// Add the given files to the given tests' `data` property. The key is the test source filename
434     /// relative to the crate root.
435     #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
436     pub test_data: BTreeMap<String, Vec<String>>,
437     /// Static libraries in this list will instead be added as whole_static_libs.
438     #[serde(default, skip_serializing_if = "Vec::is_empty")]
439     pub whole_static_libs: Vec<String>,
440     /// Directories with headers to export for C usage.
441     #[serde(default, skip_serializing_if = "Vec::is_empty")]
442     pub exported_c_header_dir: Vec<PathBuf>,
443 }
444 
445 impl Default for PackageVariantConfig {
default() -> Self446     fn default() -> Self {
447         Self {
448             alloc: false,
449             device_supported: true,
450             host_supported: true,
451             host_cross_supported: true,
452             host_first_multilib: false,
453             force_rlib: false,
454             no_presubmit: false,
455             add_module_block: None,
456             dep_blocklist: Default::default(),
457             no_std: false,
458             copy_out: false,
459             test_data: Default::default(),
460             whole_static_libs: Default::default(),
461             exported_c_header_dir: Default::default(),
462         }
463     }
464 }
465 
466 #[cfg(test)]
467 mod tests {
468     use super::*;
469 
470     #[test]
variant_config()471     fn variant_config() {
472         let config = Config::from_json_str(
473             r#"{
474             "tests": true,
475             "package": {
476                 "argh": {
477                     "patch": "patches/Android.bp.patch"
478                 },
479                 "another": {
480                     "add_toplevel_block": "block.bp",
481                     "device_supported": false,
482                     "force_rlib": true
483                 },
484                 "rulesmk": {
485                     "rulesmk_patch": "patches/rules.mk.patch"
486                 }
487             },
488             "variants": [
489                 {},
490                 {
491                     "generate_androidbp": false,
492                     "generate_rulesmk": true,
493                     "tests": false,
494                     "features": ["feature"],
495                     "vendor_available": false,
496                     "package": {
497                         "another": {
498                             "alloc": false,
499                             "force_rlib": false
500                         },
501                         "variant_package": {
502                             "add_module_block": "variant_module_block.bp"
503                         }
504                     }
505                 }
506             ]
507         }"#,
508         )
509         .unwrap();
510 
511         assert_eq!(
512             config,
513             Config {
514                 variants: vec![
515                     VariantConfig {
516                         generate_androidbp: true,
517                         generate_rulesmk: false,
518                         tests: true,
519                         features: None,
520                         vendor_available: true,
521                         package: [
522                             ("argh".to_string(), PackageVariantConfig { ..Default::default() }),
523                             (
524                                 "another".to_string(),
525                                 PackageVariantConfig {
526                                     device_supported: false,
527                                     force_rlib: true,
528                                     ..Default::default()
529                                 },
530                             ),
531                             ("rulesmk".to_string(), PackageVariantConfig { ..Default::default() }),
532                         ]
533                         .into_iter()
534                         .collect(),
535                         ..Default::default()
536                     },
537                     VariantConfig {
538                         generate_androidbp: false,
539                         generate_rulesmk: true,
540                         tests: false,
541                         features: Some(vec!["feature".to_string()]),
542                         vendor_available: false,
543                         package: [
544                             ("argh".to_string(), PackageVariantConfig { ..Default::default() }),
545                             (
546                                 "another".to_string(),
547                                 PackageVariantConfig {
548                                     alloc: false,
549                                     device_supported: false,
550                                     force_rlib: false,
551                                     ..Default::default()
552                                 },
553                             ),
554                             ("rulesmk".to_string(), PackageVariantConfig { ..Default::default() }),
555                             (
556                                 "variant_package".to_string(),
557                                 PackageVariantConfig {
558                                     add_module_block: Some("variant_module_block.bp".into()),
559                                     ..Default::default()
560                                 },
561                             ),
562                         ]
563                         .into_iter()
564                         .collect(),
565                         ..Default::default()
566                     },
567                 ],
568                 package: [
569                     (
570                         "argh".to_string(),
571                         PackageConfig {
572                             patch: Some("patches/Android.bp.patch".into()),
573                             ..Default::default()
574                         },
575                     ),
576                     (
577                         "another".to_string(),
578                         PackageConfig {
579                             add_toplevel_block: Some("block.bp".into()),
580                             ..Default::default()
581                         },
582                     ),
583                     (
584                         "rulesmk".to_string(),
585                         PackageConfig {
586                             rulesmk_patch: Some("patches/rules.mk.patch".into()),
587                             ..Default::default()
588                         },
589                     ),
590                 ]
591                 .into_iter()
592                 .collect(),
593             }
594         );
595     }
596 
597     /// Tests that variant configuration options are factored out to the top level where possible.
598     #[test]
factor_variants()599     fn factor_variants() {
600         let config = Config {
601             variants: vec![
602                 VariantConfig {
603                     features: Some(vec![]),
604                     tests: true,
605                     vendor_available: false,
606                     package: [(
607                         "argh".to_string(),
608                         PackageVariantConfig {
609                             dep_blocklist: vec!["bad_dep".to_string()],
610                             ..Default::default()
611                         },
612                     )]
613                     .into_iter()
614                     .collect(),
615                     ..Default::default()
616                 },
617                 VariantConfig {
618                     features: Some(vec![]),
619                     tests: true,
620                     product_available: false,
621                     module_name_overrides: [("argh".to_string(), "argh_nostd".to_string())]
622                         .into_iter()
623                         .collect(),
624                     vendor_available: false,
625                     package: [(
626                         "argh".to_string(),
627                         PackageVariantConfig {
628                             dep_blocklist: vec!["bad_dep".to_string()],
629                             no_std: true,
630                             ..Default::default()
631                         },
632                     )]
633                     .into_iter()
634                     .collect(),
635                     ..Default::default()
636                 },
637             ],
638             package: [(
639                 "argh".to_string(),
640                 PackageConfig { add_toplevel_block: Some("block.bp".into()), ..Default::default() },
641             )]
642             .into_iter()
643             .collect(),
644         };
645 
646         assert_eq!(
647             config.to_json_string().unwrap(),
648             r#"{
649   "features": [],
650   "package": {
651     "argh": {
652       "add_toplevel_block": "block.bp",
653       "dep_blocklist": [
654         "bad_dep"
655       ]
656     }
657   },
658   "tests": true,
659   "variants": [
660     {},
661     {
662       "module_name_overrides": {
663         "argh": "argh_nostd"
664       },
665       "package": {
666         "argh": {
667           "no_std": true
668         }
669       },
670       "product_available": false
671     }
672   ],
673   "vendor_available": false
674 }"#
675         );
676     }
677 
678     #[test]
factor_trivial_variant()679     fn factor_trivial_variant() {
680         let config = Config {
681             variants: vec![VariantConfig {
682                 tests: true,
683                 package: [("argh".to_string(), Default::default())].into_iter().collect(),
684                 ..Default::default()
685             }],
686             package: Default::default(),
687         };
688 
689         assert_eq!(
690             config.to_json_string().unwrap(),
691             r#"{
692   "tests": true
693 }"#
694         );
695     }
696 }
697