cargo/ops/
cargo_config.rs

1//! Implementation of `cargo config` subcommand.
2
3use crate::util::context::{ConfigKey, ConfigValue as CV, Definition, GlobalContext};
4use crate::util::errors::CargoResult;
5use crate::{drop_eprintln, drop_println};
6use anyhow::{Error, bail, format_err};
7use serde_json::json;
8use std::borrow::Cow;
9use std::fmt;
10use std::str::FromStr;
11
12pub enum ConfigFormat {
13    Toml,
14    Json,
15    JsonValue,
16}
17
18impl ConfigFormat {
19    /// For clap.
20    pub const POSSIBLE_VALUES: [&'static str; 3] = ["toml", "json", "json-value"];
21}
22
23impl FromStr for ConfigFormat {
24    type Err = Error;
25    fn from_str(s: &str) -> CargoResult<Self> {
26        match s {
27            "toml" => Ok(ConfigFormat::Toml),
28            "json" => Ok(ConfigFormat::Json),
29            "json-value" => Ok(ConfigFormat::JsonValue),
30            f => bail!("unknown config format `{}`", f),
31        }
32    }
33}
34
35impl fmt::Display for ConfigFormat {
36    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37        match *self {
38            ConfigFormat::Toml => write!(f, "toml"),
39            ConfigFormat::Json => write!(f, "json"),
40            ConfigFormat::JsonValue => write!(f, "json-value"),
41        }
42    }
43}
44
45/// Options for `cargo config get`.
46pub struct GetOptions<'a> {
47    pub key: Option<&'a str>,
48    pub format: ConfigFormat,
49    pub show_origin: bool,
50    pub merged: bool,
51}
52
53pub fn get(gctx: &GlobalContext, opts: &GetOptions<'_>) -> CargoResult<()> {
54    if opts.show_origin && !matches!(opts.format, ConfigFormat::Toml) {
55        bail!(
56            "the `{}` format does not support --show-origin, try the `toml` format instead",
57            opts.format
58        );
59    }
60    let key = match opts.key {
61        Some(key) => ConfigKey::from_str(key),
62        None => ConfigKey::new(),
63    };
64    if opts.merged {
65        let cv = gctx
66            .get_cv_with_env(&key)?
67            .ok_or_else(|| format_err!("config value `{}` is not set", key))?;
68        match opts.format {
69            ConfigFormat::Toml => print_toml(gctx, opts, &key, &cv),
70            ConfigFormat::Json => print_json(gctx, &key, &cv, true),
71            ConfigFormat::JsonValue => print_json(gctx, &key, &cv, false),
72        }
73        if let Some(env) = maybe_env(gctx, &key, &cv) {
74            match opts.format {
75                ConfigFormat::Toml => print_toml_env(gctx, &env),
76                ConfigFormat::Json | ConfigFormat::JsonValue => print_json_env(gctx, &env),
77            }
78        }
79    } else {
80        match &opts.format {
81            ConfigFormat::Toml => print_toml_unmerged(gctx, opts, &key)?,
82            format => bail!(
83                "the `{}` format does not support --merged=no, try the `toml` format instead",
84                format
85            ),
86        }
87    }
88    Ok(())
89}
90
91/// Checks for environment variables that might be used.
92fn maybe_env<'gctx>(
93    gctx: &'gctx GlobalContext,
94    key: &ConfigKey,
95    cv: &CV,
96) -> Option<Vec<(&'gctx str, &'gctx str)>> {
97    // Only fetching a table is unable to load env values. Leaf entries should
98    // work properly.
99    match cv {
100        CV::Table(_map, _def) => {}
101        _ => return None,
102    }
103    let mut env: Vec<_> = gctx
104        .env()
105        .filter(|(env_key, _val)| env_key.starts_with(&format!("{}_", key.as_env_key())))
106        .collect();
107    env.sort_by_key(|x| x.0);
108    if env.is_empty() { None } else { Some(env) }
109}
110
111fn print_toml(gctx: &GlobalContext, opts: &GetOptions<'_>, key: &ConfigKey, cv: &CV) {
112    let origin = |def: &Definition| -> String {
113        if !opts.show_origin {
114            return "".to_string();
115        }
116        format!(" # {}", def)
117    };
118
119    fn cv_to_toml(cv: &CV) -> toml_edit::Value {
120        match cv {
121            CV::String(s, _) => toml_edit::Value::from(s.as_str()),
122            CV::Integer(i, _) => toml_edit::Value::from(*i),
123            CV::Boolean(b, _) => toml_edit::Value::from(*b),
124            CV::List(l, _) => toml_edit::Value::from_iter(l.iter().map(cv_to_toml)),
125            CV::Table(t, _) => toml_edit::Value::from_iter({
126                let mut t: Vec<_> = t.iter().collect();
127                t.sort_by_key(|t| t.0);
128                t.into_iter().map(|(k, v)| (k, cv_to_toml(v)))
129            }),
130        }
131    }
132
133    match cv {
134        CV::Boolean(val, def) => drop_println!(gctx, "{} = {}{}", key, val, origin(def)),
135        CV::Integer(val, def) => drop_println!(gctx, "{} = {}{}", key, val, origin(def)),
136        CV::String(val, def) => drop_println!(
137            gctx,
138            "{} = {}{}",
139            key,
140            toml_edit::Value::from(val),
141            origin(def)
142        ),
143        CV::List(vals, _def) => {
144            if opts.show_origin {
145                drop_println!(gctx, "{} = [", key);
146                for cv in vals {
147                    let val = cv_to_toml(cv);
148                    let def = cv.definition();
149                    drop_println!(gctx, "    {val}, # {def}");
150                }
151                drop_println!(gctx, "]");
152            } else {
153                let vals: toml_edit::Array = vals.iter().map(cv_to_toml).collect();
154                drop_println!(gctx, "{} = {}", key, vals);
155            }
156        }
157        CV::Table(table, _def) => {
158            let mut key_vals: Vec<_> = table.iter().collect();
159            key_vals.sort_by(|a, b| a.0.cmp(b.0));
160            for (table_key, val) in key_vals {
161                let mut subkey = key.clone();
162                // push or push_sensitive shouldn't matter here, since this is
163                // not dealing with environment variables.
164                subkey.push(table_key);
165                print_toml(gctx, opts, &subkey, val);
166            }
167        }
168    }
169}
170
171fn print_toml_env(gctx: &GlobalContext, env: &[(&str, &str)]) {
172    drop_println!(
173        gctx,
174        "# The following environment variables may affect the loaded values."
175    );
176    for (env_key, env_value) in env {
177        let val = shell_escape::escape(Cow::Borrowed(env_value));
178        drop_println!(gctx, "# {}={}", env_key, val);
179    }
180}
181
182fn print_json_env(gctx: &GlobalContext, env: &[(&str, &str)]) {
183    drop_eprintln!(
184        gctx,
185        "note: The following environment variables may affect the loaded values."
186    );
187    for (env_key, env_value) in env {
188        let val = shell_escape::escape(Cow::Borrowed(env_value));
189        drop_eprintln!(gctx, "{}={}", env_key, val);
190    }
191}
192
193fn print_json(gctx: &GlobalContext, key: &ConfigKey, cv: &CV, include_key: bool) {
194    let json_value = if key.is_root() || !include_key {
195        cv_to_json(cv)
196    } else {
197        let mut parts: Vec<_> = key.parts().collect();
198        let last_part = parts.pop().unwrap();
199        let mut root_table = json!({});
200        // Create a JSON object with nested keys up to the value being displayed.
201        let mut table = &mut root_table;
202        for part in parts {
203            table[part] = json!({});
204            table = table.get_mut(part).unwrap();
205        }
206        table[last_part] = cv_to_json(cv);
207        root_table
208    };
209    drop_println!(gctx, "{}", serde_json::to_string(&json_value).unwrap());
210
211    // Helper for recursively converting a CV to JSON.
212    fn cv_to_json(cv: &CV) -> serde_json::Value {
213        match cv {
214            CV::Boolean(val, _def) => json!(val),
215            CV::Integer(val, _def) => json!(val),
216            CV::String(val, _def) => json!(val),
217            CV::List(vals, _def) => {
218                let jvals: Vec<_> = vals.iter().map(cv_to_json).collect();
219                json!(jvals)
220            }
221            CV::Table(map, _def) => {
222                let mut table = json!({});
223                for (key, val) in map {
224                    table[key] = cv_to_json(val);
225                }
226                table
227            }
228        }
229    }
230}
231
232fn print_toml_unmerged(
233    gctx: &GlobalContext,
234    opts: &GetOptions<'_>,
235    key: &ConfigKey,
236) -> CargoResult<()> {
237    let print_table = |cv: &CV| {
238        drop_println!(gctx, "# {}", cv.definition());
239        print_toml(gctx, opts, &ConfigKey::new(), cv);
240        drop_println!(gctx, "");
241    };
242    // This removes entries from the given CV so that all that remains is the
243    // given key. Returns false if no entries were found.
244    fn trim_cv(mut cv: &mut CV, key: &ConfigKey) -> CargoResult<bool> {
245        for (i, part) in key.parts().enumerate() {
246            match cv {
247                CV::Table(map, _def) => {
248                    map.retain(|key, _value| key == part);
249                    match map.get_mut(part) {
250                        Some(val) => cv = val,
251                        None => return Ok(false),
252                    }
253                }
254                _ => {
255                    let mut key_so_far = ConfigKey::new();
256                    for part in key.parts().take(i) {
257                        key_so_far.push(part);
258                    }
259                    bail!(
260                        "expected table for configuration key `{}`, \
261                         but found {} in {}",
262                        key_so_far,
263                        cv.desc(),
264                        cv.definition()
265                    )
266                }
267            }
268        }
269        Ok(match cv {
270            CV::Table(map, _def) => !map.is_empty(),
271            _ => true,
272        })
273    }
274
275    let mut cli_args = gctx.cli_args_as_table()?;
276    if trim_cv(&mut cli_args, key)? {
277        print_table(&cli_args);
278    }
279
280    // This slurps up some extra env vars that aren't technically part of the
281    // "config" (or are special-cased). I'm personally fine with just keeping
282    // them here, though it might be confusing. The vars I'm aware of:
283    //
284    // * CARGO
285    // * CARGO_HOME
286    // * CARGO_NAME
287    // * CARGO_EMAIL
288    // * CARGO_INCREMENTAL
289    // * CARGO_TARGET_DIR
290    // * CARGO_CACHE_RUSTC_INFO
291    //
292    // All of these except CARGO, CARGO_HOME, and CARGO_CACHE_RUSTC_INFO are
293    // actually part of the config, but they are special-cased in the code.
294    //
295    // TODO: It might be a good idea to teach the Config loader to support
296    // environment variable aliases so that these special cases are less
297    // special, and will just naturally get loaded as part of the config.
298    let mut env: Vec<_> = gctx
299        .env()
300        .filter(|(env_key, _val)| env_key.starts_with(key.as_env_key()))
301        .collect();
302    if !env.is_empty() {
303        env.sort_by_key(|x| x.0);
304        drop_println!(gctx, "# Environment variables");
305        for (key, value) in env {
306            // Displaying this in "shell" syntax instead of TOML, since that
307            // somehow makes more sense to me.
308            let val = shell_escape::escape(Cow::Borrowed(value));
309            drop_println!(gctx, "# {}={}", key, val);
310        }
311        drop_println!(gctx, "");
312    }
313
314    let unmerged = gctx.load_values_unmerged()?;
315    for mut cv in unmerged {
316        if trim_cv(&mut cv, key)? {
317            print_table(&cv);
318        }
319    }
320    Ok(())
321}