#
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}