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 an Android.bp build file for this variant if true.
329     #[serde(default = "default_true", skip_serializing_if = "is_true")]
330     pub generate_androidbp: bool,
331     /// Generate a rules.mk build file for this variant 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 }
380 
381 impl PackageConfig {
382     /// Names of all the fields on `PackageConfig`.
383     const FIELD_NAMES: [&'static str; 3] = ["add_toplevel_block", "patch", "rulesmk_patch"];
384 }
385 
386 /// Options that apply to everything in a package (i.e. everything associated with a particular
387 /// Cargo.toml file), for a particular variant.
388 #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
389 #[serde(deny_unknown_fields)]
390 pub struct PackageVariantConfig {
391     /// Link against `alloc`. Only valid if `no_std` is also true.
392     #[serde(default, skip_serializing_if = "is_false")]
393     pub alloc: bool,
394     /// Whether to compile for device. Defaults to true.
395     #[serde(default = "default_true", skip_serializing_if = "is_true")]
396     pub device_supported: bool,
397     /// Whether to compile for host. Defaults to true.
398     #[serde(default = "default_true", skip_serializing_if = "is_true")]
399     pub host_supported: bool,
400     /// Add a `compile_multilib: "first"` property to host modules.
401     #[serde(default, skip_serializing_if = "is_false")]
402     pub host_first_multilib: bool,
403     /// Generate "rust_library_rlib" instead of "rust_library".
404     #[serde(default, skip_serializing_if = "is_false")]
405     pub force_rlib: bool,
406     /// Whether to disable "unit_test" for "rust_test" modules.
407     // TODO: Should probably be a list of modules or crates. A package might have a mix of unit and
408     // integration tests.
409     #[serde(default, skip_serializing_if = "is_false")]
410     pub no_presubmit: bool,
411     /// File with content to append to the end of each generated module.
412     #[serde(skip_serializing_if = "Option::is_none")]
413     pub add_module_block: Option<PathBuf>,
414     /// Modules in this list will not be added as dependencies of generated modules.
415     #[serde(default, skip_serializing_if = "Vec::is_empty")]
416     pub dep_blocklist: Vec<String>,
417     /// Don't link against `std`, only `core`.
418     #[serde(default, skip_serializing_if = "is_false")]
419     pub no_std: bool,
420     /// Copy build.rs output to ./out/* and add a genrule to copy ./out/* to genrule output.
421     /// For crates with code pattern:
422     ///     include!(concat!(env!("OUT_DIR"), "/<some_file>.rs"))
423     #[serde(default, skip_serializing_if = "is_false")]
424     pub copy_out: bool,
425     /// Add the given files to the given tests' `data` property. The key is the test source filename
426     /// relative to the crate root.
427     #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
428     pub test_data: BTreeMap<String, Vec<String>>,
429     /// Static libraries in this list will instead be added as whole_static_libs.
430     #[serde(default, skip_serializing_if = "Vec::is_empty")]
431     pub whole_static_libs: Vec<String>,
432     /// Directories with headers to export for C usage.
433     #[serde(default, skip_serializing_if = "Vec::is_empty")]
434     pub exported_c_header_dir: Vec<PathBuf>,
435 }
436 
437 impl Default for PackageVariantConfig {
default() -> Self438     fn default() -> Self {
439         Self {
440             alloc: false,
441             device_supported: true,
442             host_supported: true,
443             host_first_multilib: false,
444             force_rlib: false,
445             no_presubmit: false,
446             add_module_block: None,
447             dep_blocklist: Default::default(),
448             no_std: false,
449             copy_out: false,
450             test_data: Default::default(),
451             whole_static_libs: Default::default(),
452             exported_c_header_dir: Default::default(),
453         }
454     }
455 }
456 
457 #[cfg(test)]
458 mod tests {
459     use super::*;
460 
461     #[test]
variant_config()462     fn variant_config() {
463         let config = Config::from_json_str(
464             r#"{
465             "tests": true,
466             "package": {
467                 "argh": {
468                     "patch": "patches/Android.bp.patch"
469                 },
470                 "another": {
471                     "add_toplevel_block": "block.bp",
472                     "device_supported": false,
473                     "force_rlib": true
474                 },
475                 "rulesmk": {
476                     "rulesmk_patch": "patches/rules.mk.patch"
477                 }
478             },
479             "variants": [
480                 {},
481                 {
482                     "generate_androidbp": false,
483                     "generate_rulesmk": true,
484                     "tests": false,
485                     "features": ["feature"],
486                     "vendor_available": false,
487                     "package": {
488                         "another": {
489                             "alloc": false,
490                             "force_rlib": false
491                         },
492                         "variant_package": {
493                             "add_module_block": "variant_module_block.bp"
494                         }
495                     }
496                 }
497             ]
498         }"#,
499         )
500         .unwrap();
501 
502         assert_eq!(
503             config,
504             Config {
505                 variants: vec![
506                     VariantConfig {
507                         generate_androidbp: true,
508                         generate_rulesmk: false,
509                         tests: true,
510                         features: None,
511                         vendor_available: true,
512                         package: [
513                             ("argh".to_string(), PackageVariantConfig { ..Default::default() }),
514                             (
515                                 "another".to_string(),
516                                 PackageVariantConfig {
517                                     device_supported: false,
518                                     force_rlib: true,
519                                     ..Default::default()
520                                 },
521                             ),
522                             ("rulesmk".to_string(), PackageVariantConfig { ..Default::default() }),
523                         ]
524                         .into_iter()
525                         .collect(),
526                         ..Default::default()
527                     },
528                     VariantConfig {
529                         generate_androidbp: false,
530                         generate_rulesmk: true,
531                         tests: false,
532                         features: Some(vec!["feature".to_string()]),
533                         vendor_available: false,
534                         package: [
535                             ("argh".to_string(), PackageVariantConfig { ..Default::default() }),
536                             (
537                                 "another".to_string(),
538                                 PackageVariantConfig {
539                                     alloc: false,
540                                     device_supported: false,
541                                     force_rlib: false,
542                                     ..Default::default()
543                                 },
544                             ),
545                             ("rulesmk".to_string(), PackageVariantConfig { ..Default::default() }),
546                             (
547                                 "variant_package".to_string(),
548                                 PackageVariantConfig {
549                                     add_module_block: Some("variant_module_block.bp".into()),
550                                     ..Default::default()
551                                 },
552                             ),
553                         ]
554                         .into_iter()
555                         .collect(),
556                         ..Default::default()
557                     },
558                 ],
559                 package: [
560                     (
561                         "argh".to_string(),
562                         PackageConfig {
563                             patch: Some("patches/Android.bp.patch".into()),
564                             ..Default::default()
565                         },
566                     ),
567                     (
568                         "another".to_string(),
569                         PackageConfig {
570                             add_toplevel_block: Some("block.bp".into()),
571                             ..Default::default()
572                         },
573                     ),
574                     (
575                         "rulesmk".to_string(),
576                         PackageConfig {
577                             rulesmk_patch: Some("patches/rules.mk.patch".into()),
578                             ..Default::default()
579                         },
580                     ),
581                 ]
582                 .into_iter()
583                 .collect(),
584             }
585         );
586     }
587 
588     /// Tests that variant configuration options are factored out to the top level where possible.
589     #[test]
factor_variants()590     fn factor_variants() {
591         let config = Config {
592             variants: vec![
593                 VariantConfig {
594                     features: Some(vec![]),
595                     tests: true,
596                     vendor_available: false,
597                     package: [(
598                         "argh".to_string(),
599                         PackageVariantConfig {
600                             dep_blocklist: vec!["bad_dep".to_string()],
601                             ..Default::default()
602                         },
603                     )]
604                     .into_iter()
605                     .collect(),
606                     ..Default::default()
607                 },
608                 VariantConfig {
609                     features: Some(vec![]),
610                     tests: true,
611                     product_available: false,
612                     module_name_overrides: [("argh".to_string(), "argh_nostd".to_string())]
613                         .into_iter()
614                         .collect(),
615                     vendor_available: false,
616                     package: [(
617                         "argh".to_string(),
618                         PackageVariantConfig {
619                             dep_blocklist: vec!["bad_dep".to_string()],
620                             no_std: true,
621                             ..Default::default()
622                         },
623                     )]
624                     .into_iter()
625                     .collect(),
626                     ..Default::default()
627                 },
628             ],
629             package: [(
630                 "argh".to_string(),
631                 PackageConfig { add_toplevel_block: Some("block.bp".into()), ..Default::default() },
632             )]
633             .into_iter()
634             .collect(),
635         };
636 
637         assert_eq!(
638             config.to_json_string().unwrap(),
639             r#"{
640   "features": [],
641   "package": {
642     "argh": {
643       "add_toplevel_block": "block.bp",
644       "dep_blocklist": [
645         "bad_dep"
646       ]
647     }
648   },
649   "tests": true,
650   "variants": [
651     {},
652     {
653       "module_name_overrides": {
654         "argh": "argh_nostd"
655       },
656       "package": {
657         "argh": {
658           "no_std": true
659         }
660       },
661       "product_available": false
662     }
663   ],
664   "vendor_available": false
665 }"#
666         );
667     }
668 
669     #[test]
factor_trivial_variant()670     fn factor_trivial_variant() {
671         let config = Config {
672             variants: vec![VariantConfig {
673                 tests: true,
674                 package: [("argh".to_string(), Default::default())].into_iter().collect(),
675                 ..Default::default()
676             }],
677             package: Default::default(),
678         };
679 
680         assert_eq!(
681             config.to_json_string().unwrap(),
682             r#"{
683   "tests": true
684 }"#
685         );
686     }
687 }
688