Add cargo config subcommand.

This commit is contained in:
Eric Huss 2021-03-25 12:56:24 -07:00
parent 98e345674e
commit 96a5642217
16 changed files with 1187 additions and 143 deletions

View file

@ -54,6 +54,7 @@ semver = { version = "0.10", features = ["serde"] }
serde = { version = "1.0.123", features = ["derive"] }
serde_ignored = "0.1.0"
serde_json = { version = "1.0.30", features = ["raw_value"] }
shell-escape = "0.1.4"
strip-ansi-escapes = "0.1.0"
tar = { version = "0.4.26", default-features = false }
tempfile = "3.0"

View file

@ -1144,8 +1144,6 @@ impl Execs {
}
fn match_json(&self, expected: &str, line: &str) -> MatchResult {
let expected = self.normalize_matcher(expected);
let line = self.normalize_matcher(line);
let actual = match line.parse() {
Err(e) => return Err(format!("invalid json, {}:\n`{}`", e, line)),
Ok(actual) => actual,
@ -1155,7 +1153,8 @@ impl Execs {
Ok(expected) => expected,
};
find_json_mismatch(&expected, &actual)
let cwd = self.process_builder.as_ref().and_then(|p| p.get_cwd());
find_json_mismatch(&expected, &actual, cwd)
}
fn diff_lines<'a>(
@ -1333,8 +1332,12 @@ fn lines_match_works() {
/// as paths). You can use a `"{...}"` string literal as a wildcard for
/// arbitrary nested JSON (useful for parts of object emitted by other programs
/// (e.g., rustc) rather than Cargo itself).
pub fn find_json_mismatch(expected: &Value, actual: &Value) -> Result<(), String> {
match find_json_mismatch_r(expected, actual) {
pub fn find_json_mismatch(
expected: &Value,
actual: &Value,
cwd: Option<&Path>,
) -> Result<(), String> {
match find_json_mismatch_r(expected, actual, cwd) {
Some((expected_part, actual_part)) => Err(format!(
"JSON mismatch\nExpected:\n{}\nWas:\n{}\nExpected part:\n{}\nActual part:\n{}\n",
serde_json::to_string_pretty(expected).unwrap(),
@ -1349,12 +1352,21 @@ pub fn find_json_mismatch(expected: &Value, actual: &Value) -> Result<(), String
fn find_json_mismatch_r<'a>(
expected: &'a Value,
actual: &'a Value,
cwd: Option<&Path>,
) -> Option<(&'a Value, &'a Value)> {
use serde_json::Value::*;
match (expected, actual) {
(&Number(ref l), &Number(ref r)) if l == r => None,
(&Bool(l), &Bool(r)) if l == r => None,
(&String(ref l), &String(ref r)) if lines_match(l, r) => None,
(&String(ref l), _) if l == "{...}" => None,
(&String(ref l), &String(ref r)) => {
let normalized = normalize_matcher(r, cwd);
if lines_match(l, &normalized) {
None
} else {
Some((expected, actual))
}
}
(&Array(ref l), &Array(ref r)) => {
if l.len() != r.len() {
return Some((expected, actual));
@ -1362,7 +1374,7 @@ fn find_json_mismatch_r<'a>(
l.iter()
.zip(r.iter())
.filter_map(|(l, r)| find_json_mismatch_r(l, r))
.filter_map(|(l, r)| find_json_mismatch_r(l, r, cwd))
.next()
}
(&Object(ref l), &Object(ref r)) => {
@ -1373,12 +1385,11 @@ fn find_json_mismatch_r<'a>(
l.values()
.zip(r.values())
.filter_map(|(l, r)| find_json_mismatch_r(l, r))
.filter_map(|(l, r)| find_json_mismatch_r(l, r, cwd))
.next()
}
(&Null, &Null) => None,
// Magic string literal `"{...}"` acts as wildcard for any sub-JSON.
(&String(ref l), _) if l == "{...}" => None,
_ => Some((expected, actual)),
}
}

View file

@ -76,7 +76,7 @@ fn _validate_upload(
let actual_json = serde_json::from_slice(&json_bytes).expect("uploaded JSON should be valid");
let expected_json = serde_json::from_str(expected_json).expect("expected JSON does not parse");
if let Err(e) = find_json_mismatch(&expected_json, &actual_json) {
if let Err(e) = find_json_mismatch(&expected_json, &actual_json, None) {
panic!("{}", e);
}

View file

@ -0,0 +1,48 @@
use crate::command_prelude::*;
use cargo::ops::cargo_config;
pub fn cli() -> App {
subcommand("config")
.about("Inspect configuration values")
.after_help("Run `cargo help config` for more detailed information.\n")
.setting(clap::AppSettings::SubcommandRequiredElseHelp)
.subcommand(
subcommand("get")
.arg(Arg::with_name("key").help("The config key to display"))
.arg(
opt("format", "Display format")
.possible_values(cargo_config::ConfigFormat::POSSIBLE_VALUES)
.default_value("toml"),
)
.arg(opt(
"show-origin",
"Display where the config value is defined",
))
.arg(
opt("merged", "Whether or not to merge config values")
.possible_values(&["yes", "no"])
.default_value("yes"),
),
)
}
pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult {
config
.cli_unstable()
.fail_if_stable_command(config, "config", 9301)?;
match args.subcommand() {
("get", Some(args)) => {
let opts = cargo_config::GetOptions {
key: args.value_of("key"),
format: args.value_of("format").unwrap().parse()?,
show_origin: args.is_present("show-origin"),
merged: args.value_of("merged") == Some("yes"),
};
cargo_config::get(config, &opts)?;
}
(cmd, _) => {
panic!("unexpected command `{}`", cmd)
}
}
Ok(())
}

View file

@ -1,6 +1,4 @@
use crate::command_prelude::*;
use anyhow::format_err;
use cargo::core::features;
use cargo::ops;
pub fn cli() -> App {
@ -12,29 +10,10 @@ pub fn cli() -> App {
}
pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult {
let unstable = config.cli_unstable();
if !(unstable.credential_process || unstable.unstable_options) {
const SEE: &str = "See https://github.com/rust-lang/cargo/issues/8933 for more \
information about the `cargo logout` command.";
if config.nightly_features_allowed {
return Err(format_err!(
"the `cargo logout` command is unstable, pass `-Z unstable-options` to enable it\n\
{}",
SEE
)
.into());
} else {
return Err(format_err!(
"the `cargo logout` command is unstable, and only available on the \
nightly channel of Cargo, but this is the `{}` channel\n\
{}\n\
{}",
features::channel(),
features::SEE_CHANNELS,
SEE
)
.into());
}
if !config.cli_unstable().credential_process {
config
.cli_unstable()
.fail_if_stable_command(config, "logout", 8933)?;
}
config.load_credentials()?;
ops::registry_logout(config, args.value_of("registry").map(String::from))?;

View file

@ -6,6 +6,7 @@ pub fn builtin() -> Vec<App> {
build::cli(),
check::cli(),
clean::cli(),
config::cli(),
describe_future_incompatibilities::cli(),
doc::cli(),
fetch::cli(),
@ -45,6 +46,7 @@ pub fn builtin_exec(cmd: &str) -> Option<fn(&mut Config, &ArgMatches<'_>) -> Cli
"build" => build::exec,
"check" => check::exec,
"clean" => clean::exec,
"config" => config::exec,
"describe-future-incompatibilities" => describe_future_incompatibilities::exec,
"doc" => doc::exec,
"fetch" => fetch::exec,
@ -84,6 +86,7 @@ pub mod bench;
pub mod build;
pub mod check;
pub mod clean;
pub mod config;
pub mod describe_future_incompatibilities;
pub mod doc;
pub mod fetch;

View file

@ -765,9 +765,8 @@ impl CliUnstable {
Ok(())
}
/// Generates an error if `-Z unstable-options` was not used.
/// Intended to be used when a user passes a command-line flag that
/// requires `-Z unstable-options`.
/// Generates an error if `-Z unstable-options` was not used for a new,
/// unstable command-line flag.
pub fn fail_if_stable_opt(&self, flag: &str, issue: u32) -> CargoResult<()> {
if !self.unstable_options {
let see = format!(
@ -799,6 +798,43 @@ impl CliUnstable {
}
Ok(())
}
/// Generates an error if `-Z unstable-options` was not used for a new,
/// unstable subcommand.
pub fn fail_if_stable_command(
&self,
config: &Config,
command: &str,
issue: u32,
) -> CargoResult<()> {
if self.unstable_options {
return Ok(());
}
let see = format!(
"See https://github.com/rust-lang/cargo/issues/{} for more \
information about the `cargo {}` command.",
issue, command
);
if config.nightly_features_allowed {
bail!(
"the `cargo {}` command is unstable, pass `-Z unstable-options` to enable it\n\
{}",
command,
see
);
} else {
bail!(
"the `cargo {}` command is unstable, and only available on the \
nightly channel of Cargo, but this is the `{}` channel\n\
{}\n\
{}",
command,
channel(),
SEE_CHANNELS,
see
);
}
}
}
/// Returns the current release channel ("stable", "beta", "nightly", "dev").

View file

@ -0,0 +1,314 @@
//! Implementation of `cargo config` subcommand.
use crate::drop_println;
use crate::util::config::{Config, ConfigKey, ConfigValue as CV, Definition};
use crate::util::errors::CargoResult;
use anyhow::{bail, format_err, Error};
use serde_json::json;
use std::borrow::Cow;
use std::fmt;
use std::str::FromStr;
pub enum ConfigFormat {
Toml,
Json,
JsonValue,
}
impl ConfigFormat {
/// For clap.
pub const POSSIBLE_VALUES: &'static [&'static str] = &["toml", "json", "json-value"];
}
impl FromStr for ConfigFormat {
type Err = Error;
fn from_str(s: &str) -> CargoResult<Self> {
match s {
"toml" => Ok(ConfigFormat::Toml),
"json" => Ok(ConfigFormat::Json),
"json-value" => Ok(ConfigFormat::JsonValue),
f => bail!("unknown config format `{}`", f),
}
}
}
impl fmt::Display for ConfigFormat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
ConfigFormat::Toml => write!(f, "toml"),
ConfigFormat::Json => write!(f, "json"),
ConfigFormat::JsonValue => write!(f, "json-value"),
}
}
}
/// Options for `cargo config get`.
pub struct GetOptions<'a> {
pub key: Option<&'a str>,
pub format: ConfigFormat,
pub show_origin: bool,
pub merged: bool,
}
pub fn get(config: &Config, opts: &GetOptions<'_>) -> CargoResult<()> {
if opts.show_origin {
if !matches!(opts.format, ConfigFormat::Toml) {
bail!(
"the `{}` format does not support --show-origin, try the `toml` format instead",
opts.format
);
}
}
let key = match opts.key {
Some(key) => ConfigKey::from_str(key),
None => ConfigKey::new(),
};
if opts.merged {
let cv = config
.get_cv_with_env(&key)?
.ok_or_else(|| format_err!("config value `{}` is not set", key))?;
match opts.format {
ConfigFormat::Toml => {
print_toml(config, opts, &key, &cv);
if let Some(env) = maybe_env(config, &key, &cv) {
print_toml_env(config, &env);
}
}
ConfigFormat::Json => print_json(config, &key, &cv, true),
ConfigFormat::JsonValue => print_json(config, &key, &cv, false),
}
} else {
match &opts.format {
ConfigFormat::Toml => print_toml_unmerged(config, opts, &key)?,
format => bail!(
"the `{}` format does not support --merged=no, try the `toml` format instead",
format
),
}
}
Ok(())
}
/// Checks for environment variables that might be used.
fn maybe_env<'config>(
config: &'config Config,
key: &ConfigKey,
cv: &CV,
) -> Option<Vec<(&'config String, &'config String)>> {
// Only fetching a table is unable to load env values. Leaf entries should
// work properly.
match cv {
CV::Table(_map, _def) => {}
_ => return None,
}
let mut env: Vec<_> = config
.env()
.iter()
.filter(|(env_key, _val)| env_key.starts_with(&format!("{}_", key.as_env_key())))
.collect();
env.sort_by_key(|x| x.0);
if env.is_empty() {
None
} else {
Some(env)
}
}
fn print_toml(config: &Config, opts: &GetOptions<'_>, key: &ConfigKey, cv: &CV) {
let origin = |def: &Definition| -> String {
if !opts.show_origin {
return "".to_string();
}
format!(" # {}", def)
};
match cv {
CV::Boolean(val, def) => drop_println!(config, "{} = {}{}", key, val, origin(&def)),
CV::Integer(val, def) => drop_println!(config, "{} = {}{}", key, val, origin(&def)),
CV::String(val, def) => drop_println!(
config,
"{} = {}{}",
key,
toml::to_string(&val).unwrap(),
origin(&def)
),
CV::List(vals, _def) => {
if opts.show_origin {
drop_println!(config, "{} = [", key);
for (val, def) in vals {
drop_println!(config, " {}, # {}", toml::to_string(&val).unwrap(), def);
}
drop_println!(config, "]");
} else {
let vals: Vec<&String> = vals.iter().map(|x| &x.0).collect();
drop_println!(config, "{} = {}", key, toml::to_string(&vals).unwrap());
}
}
CV::Table(table, _def) => {
let mut key_vals: Vec<_> = table.into_iter().collect();
key_vals.sort_by(|a, b| a.0.cmp(&b.0));
for (table_key, val) in key_vals {
let mut subkey = key.clone();
// push or push_sensitive shouldn't matter here, since this is
// not dealing with environment variables.
subkey.push(&table_key);
print_toml(config, opts, &subkey, val);
}
}
}
}
fn print_toml_env(config: &Config, env: &[(&String, &String)]) {
drop_println!(
config,
"# The following environment variables may affect the loaded values."
);
for (env_key, env_value) in env {
let val = shell_escape::escape(Cow::Borrowed(env_value));
drop_println!(config, "# {}={}", env_key, val);
}
}
fn print_json(config: &Config, key: &ConfigKey, cv: &CV, include_key: bool) {
let json_value = if key.is_root() || !include_key {
match cv {
CV::Boolean(val, _def) => json!(val),
CV::Integer(val, _def) => json!(val),
CV::String(val, _def) => json!(val),
CV::List(vals, _def) => {
let jvals: Vec<_> = vals.into_iter().map(|(val, _def)| json!(val)).collect();
json!(jvals)
}
CV::Table(map, _def) => {
let mut root_table = json!({});
for (key, val) in map {
json_add(&mut root_table, key, val);
}
root_table
}
}
} else {
let mut parts: Vec<_> = key.parts().collect();
let last_part = parts.pop().unwrap();
let mut root_table = json!({});
// Create a JSON object with nested keys up to the value being displayed.
let mut table = &mut root_table;
for part in parts {
table[part] = json!({});
table = table.get_mut(part).unwrap();
}
json_add(table, last_part, cv);
root_table
};
drop_println!(config, "{}", serde_json::to_string(&json_value).unwrap());
// Helper for recursively converting a CV to JSON.
fn json_add(table: &mut serde_json::Value, key: &str, cv: &CV) {
match cv {
CV::Boolean(val, _def) => table[key] = json!(val),
CV::Integer(val, _def) => table[key] = json!(val),
CV::String(val, _def) => table[key] = json!(val),
CV::List(vals, _def) => {
let jvals: Vec<_> = vals.into_iter().map(|(val, _def)| json!(val)).collect();
table[key] = json!(jvals);
}
CV::Table(val, _def) => {
table
.as_object_mut()
.unwrap()
.insert(key.to_string(), json!({}));
let inner_table = &mut table[&key];
for (t_key, t_cv) in val {
json_add(inner_table, t_key, t_cv);
}
}
}
}
}
fn print_toml_unmerged(config: &Config, opts: &GetOptions<'_>, key: &ConfigKey) -> CargoResult<()> {
let print_table = |cv: &CV| {
drop_println!(config, "# {}", cv.definition());
print_toml(config, opts, &ConfigKey::new(), cv);
drop_println!(config, "");
};
// This removes entries from the given CV so that all that remains is the
// given key. Returns false if no entries were found.
fn trim_cv(mut cv: &mut CV, key: &ConfigKey) -> CargoResult<bool> {
for (i, part) in key.parts().enumerate() {
match cv {
CV::Table(map, _def) => {
map.retain(|key, _value| key == part);
match map.get_mut(part) {
Some(val) => cv = val,
None => return Ok(false),
}
}
_ => {
let mut key_so_far = ConfigKey::new();
for part in key.parts().take(i) {
key_so_far.push(part);
}
bail!(
"expected table for configuration key `{}`, \
but found {} in {}",
key_so_far,
cv.desc(),
cv.definition()
)
}
}
}
Ok(match cv {
CV::Table(map, _def) => !map.is_empty(),
_ => true,
})
}
let mut cli_args = config.cli_args_as_table()?;
if trim_cv(&mut cli_args, key)? {
print_table(&cli_args);
}
// This slurps up some extra env vars that aren't technically part of the
// "config" (or are special-cased). I'm personally fine with just keeping
// them here, though it might be confusing. The vars I'm aware of:
//
// * CARGO
// * CARGO_HOME
// * CARGO_NAME
// * CARGO_EMAIL
// * CARGO_INCREMENTAL
// * CARGO_TARGET_DIR
// * CARGO_CACHE_RUSTC_INFO
//
// All of these except CARGO, CARGO_HOME, and CARGO_CACHE_RUSTC_INFO are
// actually part of the config, but they are special-cased in the code.
//
// TODO: It might be a good idea to teach the Config loader to support
// environment variable aliases so that these special cases are less
// special, and will just naturally get loaded as part of the config.
let mut env: Vec<_> = config
.env()
.iter()
.filter(|(env_key, _val)| env_key.starts_with(key.as_env_key()))
.collect();
if !env.is_empty() {
env.sort_by_key(|x| x.0);
drop_println!(config, "# Environment variables");
for (key, value) in env {
// Displaying this in "shell" syntax instead of TOML, since that
// somehow makes more sense to me.
let val = shell_escape::escape(Cow::Borrowed(value));
drop_println!(config, "# {}={}", key, val);
}
drop_println!(config, "");
}
let unmerged = config.load_values_unmerged()?;
for mut cv in unmerged {
if trim_cv(&mut cv, key)? {
print_table(&cv);
}
}
Ok(())
}

View file

@ -31,6 +31,7 @@ pub use self::vendor::{vendor, VendorOptions};
mod cargo_clean;
mod cargo_compile;
pub mod cargo_config;
mod cargo_doc;
mod cargo_fetch;
mod cargo_generate_lockfile;

View file

@ -40,62 +40,6 @@ macro_rules! deserialize_method {
};
}
impl<'config> Deserializer<'config> {
/// This is a helper for getting a CV from a file or env var.
///
/// If this returns CV::List, then don't look at the value. Handling lists
/// is deferred to ConfigSeqAccess.
fn get_cv_with_env(&self) -> Result<Option<CV>, ConfigError> {
// Determine if value comes from env, cli, or file, and merge env if
// possible.
let cv = self.config.get_cv(&self.key)?;
let env = self.config.env.get(self.key.as_env_key());
let env_def = Definition::Environment(self.key.as_env_key().to_string());
let use_env = match (&cv, env) {
(Some(cv), Some(_)) => env_def.is_higher_priority(cv.definition()),
(None, Some(_)) => true,
_ => false,
};
if !use_env {
return Ok(cv);
}
// Future note: If you ever need to deserialize a non-self describing
// map type, this should implement a starts_with check (similar to how
// ConfigMapAccess does).
let env = env.unwrap();
if env == "true" {
Ok(Some(CV::Boolean(true, env_def)))
} else if env == "false" {
Ok(Some(CV::Boolean(false, env_def)))
} else if let Ok(i) = env.parse::<i64>() {
Ok(Some(CV::Integer(i, env_def)))
} else if self.config.cli_unstable().advanced_env
&& env.starts_with('[')
&& env.ends_with(']')
{
// Parsing is deferred to ConfigSeqAccess.
Ok(Some(CV::List(Vec::new(), env_def)))
} else {
// Try to merge if possible.
match cv {
Some(CV::List(cv_list, _cv_def)) => {
// Merging is deferred to ConfigSeqAccess.
Ok(Some(CV::List(cv_list, env_def)))
}
_ => {
// Note: CV::Table merging is not implemented, as env
// vars do not support table values. In the future, we
// could check for `{}`, and interpret it as TOML if
// that seems useful.
Ok(Some(CV::String(env.to_string(), env_def)))
}
}
}
}
}
impl<'de, 'config> de::Deserializer<'de> for Deserializer<'config> {
type Error = ConfigError;
@ -103,7 +47,7 @@ impl<'de, 'config> de::Deserializer<'de> for Deserializer<'config> {
where
V: de::Visitor<'de>,
{
let cv = self.get_cv_with_env()?;
let cv = self.config.get_cv_with_env(&self.key)?;
if let Some(cv) = cv {
let res: (Result<V::Value, ConfigError>, Definition) = match cv {
CV::Integer(i, def) => (visitor.visit_i64(i), def),

View file

@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::fmt;
/// Key for a configuration variable.
@ -84,16 +85,32 @@ impl ConfigKey {
}
/// Returns an iterator of the key parts as strings.
pub(super) fn parts(&self) -> impl Iterator<Item = &str> {
pub(crate) fn parts(&self) -> impl Iterator<Item = &str> {
self.parts.iter().map(|p| p.0.as_ref())
}
/// Returns whether or not this is a key for the root table.
pub fn is_root(&self) -> bool {
self.parts.is_empty()
}
}
impl fmt::Display for ConfigKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Note: This is not a perfect TOML representation. This really should
// check if the parts should be quoted.
let parts: Vec<&str> = self.parts().collect();
let parts: Vec<_> = self.parts().map(|part| escape_key_part(part)).collect();
parts.join(".").fmt(f)
}
}
fn escape_key_part<'a>(part: &'a str) -> Cow<'a, str> {
let ok = part.chars().all(|c| match c {
'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_' => true,
_ => false,
});
if ok {
Cow::Borrowed(part)
} else {
// This is a bit messy, but toml doesn't expose a function to do this.
Cow::Owned(toml::to_string(&toml::Value::String(part.to_string())).unwrap())
}
}

View file

@ -88,7 +88,7 @@ mod value;
pub use value::{Definition, OptValue, Value};
mod key;
use key::ConfigKey;
pub use key::ConfigKey;
mod path;
pub use path::{ConfigRelativePath, PathAndArgs};
@ -522,6 +522,14 @@ impl Config {
fn get_cv(&self, key: &ConfigKey) -> CargoResult<Option<ConfigValue>> {
log::trace!("get cv {:?}", key);
let vals = self.values()?;
if key.is_root() {
// Returning the entire root table (for example `cargo config get`
// with no key). The definition here shouldn't matter.
return Ok(Some(CV::Table(
vals.clone(),
Definition::Path(PathBuf::new()),
)));
}
let mut parts = key.parts().enumerate();
let mut val = match vals.get(parts.next().unwrap().1) {
Some(val) => val,
@ -539,12 +547,14 @@ impl Config {
| CV::String(_, def)
| CV::List(_, def)
| CV::Boolean(_, def) => {
let key_so_far: Vec<&str> = key.parts().take(i).collect();
let mut key_so_far = ConfigKey::new();
for part in key.parts().take(i) {
key_so_far.push(part);
}
bail!(
"expected table for configuration key `{}`, \
but found {} in {}",
// This join doesn't handle quoting properly.
key_so_far.join("."),
key_so_far,
val.desc(),
def
)
@ -554,11 +564,94 @@ impl Config {
Ok(Some(val.clone()))
}
/// This is a helper for getting a CV from a file or env var.
pub(crate) fn get_cv_with_env(&self, key: &ConfigKey) -> CargoResult<Option<CV>> {
// Determine if value comes from env, cli, or file, and merge env if
// possible.
let cv = self.get_cv(key)?;
if key.is_root() {
// Root table can't have env value.
return Ok(cv);
}
let env = self.env.get(key.as_env_key());
let env_def = Definition::Environment(key.as_env_key().to_string());
let use_env = match (&cv, env) {
// Lists are always merged.
(Some(CV::List(..)), Some(_)) => true,
(Some(cv), Some(_)) => env_def.is_higher_priority(cv.definition()),
(None, Some(_)) => true,
_ => false,
};
if !use_env {
return Ok(cv);
}
// Future note: If you ever need to deserialize a non-self describing
// map type, this should implement a starts_with check (similar to how
// ConfigMapAccess does).
let env = env.unwrap();
if env == "true" {
Ok(Some(CV::Boolean(true, env_def)))
} else if env == "false" {
Ok(Some(CV::Boolean(false, env_def)))
} else if let Ok(i) = env.parse::<i64>() {
Ok(Some(CV::Integer(i, env_def)))
} else if self.cli_unstable().advanced_env && env.starts_with('[') && env.ends_with(']') {
match cv {
Some(CV::List(mut cv_list, cv_def)) => {
// Merge with config file.
self.get_env_list(key, &mut cv_list)?;
Ok(Some(CV::List(cv_list, cv_def)))
}
Some(cv) => {
// This can't assume StringList or UnmergedStringList.
// Return an error, which is the behavior of merging
// multiple config.toml files with the same scenario.
bail!(
"unable to merge array env for config `{}`\n\
file: {:?}\n\
env: {}",
key,
cv,
env
);
}
None => {
let mut cv_list = Vec::new();
self.get_env_list(key, &mut cv_list)?;
Ok(Some(CV::List(cv_list, env_def)))
}
}
} else {
// Try to merge if possible.
match cv {
Some(CV::List(mut cv_list, cv_def)) => {
// Merge with config file.
self.get_env_list(key, &mut cv_list)?;
Ok(Some(CV::List(cv_list, cv_def)))
}
_ => {
// Note: CV::Table merging is not implemented, as env
// vars do not support table values. In the future, we
// could check for `{}`, and interpret it as TOML if
// that seems useful.
Ok(Some(CV::String(env.to_string(), env_def)))
}
}
}
}
/// Helper primarily for testing.
pub fn set_env(&mut self, env: HashMap<String, String>) {
self.env = env;
}
/// Returns all environment variables.
pub(crate) fn env(&self) -> &HashMap<String, String> {
&self.env
}
fn get_env<T>(&self, key: &ConfigKey) -> Result<OptValue<T>, ConfigError>
where
T: FromStr,
@ -912,6 +1005,39 @@ impl Config {
self.load_values_from(&self.cwd)
}
pub(crate) fn load_values_unmerged(&self) -> CargoResult<Vec<ConfigValue>> {
let mut result = Vec::new();
let mut seen = HashSet::new();
let home = self.home_path.clone().into_path_unlocked();
self.walk_tree(&self.cwd, &home, |path| {
let mut cv = self._load_file(path, &mut seen, false)?;
if self.cli_unstable().config_include {
self.load_unmerged_include(&mut cv, &mut seen, &mut result)?;
}
result.push(cv);
Ok(())
})
.chain_err(|| "could not load Cargo configuration")?;
Ok(result)
}
fn load_unmerged_include(
&self,
cv: &mut CV,
seen: &mut HashSet<PathBuf>,
output: &mut Vec<CV>,
) -> CargoResult<()> {
let includes = self.include_paths(cv, false)?;
for (path, abs_path, def) in includes {
let mut cv = self
._load_file(&abs_path, seen, false)
.chain_err(|| format!("failed to load config include `{}` from `{}`", path, def))?;
self.load_unmerged_include(&mut cv, seen, output)?;
output.push(cv);
}
Ok(())
}
fn load_values_from(&self, path: &Path) -> CargoResult<HashMap<String, ConfigValue>> {
// This definition path is ignored, this is just a temporary container
// representing the entire file.
@ -919,7 +1045,7 @@ impl Config {
let home = self.home_path.clone().into_path_unlocked();
self.walk_tree(path, &home, |path| {
let value = self.load_file(path)?;
let value = self.load_file(path, true)?;
cfg.merge(value, false)
.chain_err(|| format!("failed to merge configuration at `{}`", path.display()))?;
Ok(())
@ -932,12 +1058,17 @@ impl Config {
}
}
fn load_file(&self, path: &Path) -> CargoResult<ConfigValue> {
fn load_file(&self, path: &Path, includes: bool) -> CargoResult<ConfigValue> {
let mut seen = HashSet::new();
self._load_file(path, &mut seen)
self._load_file(path, &mut seen, includes)
}
fn _load_file(&self, path: &Path, seen: &mut HashSet<PathBuf>) -> CargoResult<ConfigValue> {
fn _load_file(
&self,
path: &Path,
seen: &mut HashSet<PathBuf>,
includes: bool,
) -> CargoResult<ConfigValue> {
if !seen.insert(path.to_path_buf()) {
bail!(
"config `include` cycle detected with path `{}`",
@ -954,8 +1085,11 @@ impl Config {
path.display()
)
})?;
let value = self.load_includes(value, seen)?;
Ok(value)
if includes {
self.load_includes(value, seen)
} else {
Ok(value)
}
}
/// Load any `include` files listed in the given `value`.
@ -965,33 +1099,15 @@ impl Config {
/// `seen` is used to check for cyclic includes.
fn load_includes(&self, mut value: CV, seen: &mut HashSet<PathBuf>) -> CargoResult<CV> {
// Get the list of files to load.
let includes = match &mut value {
CV::Table(table, _def) => match table.remove("include") {
Some(CV::String(s, def)) => vec![(s, def.clone())],
Some(CV::List(list, _def)) => list,
Some(other) => bail!(
"`include` expected a string or list, but found {} in `{}`",
other.desc(),
other.definition()
),
None => {
return Ok(value);
}
},
_ => unreachable!(),
};
let includes = self.include_paths(&mut value, true)?;
// Check unstable.
if !self.cli_unstable().config_include {
return Ok(value);
}
// Accumulate all values here.
let mut root = CV::Table(HashMap::new(), value.definition().clone());
for (path, def) in includes {
let abs_path = match &def {
Definition::Path(p) => p.parent().unwrap().join(&path),
Definition::Environment(_) | Definition::Cli => self.cwd().join(&path),
};
self._load_file(&abs_path, seen)
for (path, abs_path, def) in includes {
self._load_file(&abs_path, seen, true)
.and_then(|include| root.merge(include, true))
.chain_err(|| format!("failed to load config include `{}` from `{}`", path, def))?;
}
@ -999,13 +1115,54 @@ impl Config {
Ok(root)
}
/// Add config arguments passed on the command line.
fn merge_cli_args(&mut self) -> CargoResult<()> {
/// Converts the `include` config value to a list of absolute paths.
fn include_paths(
&self,
cv: &mut CV,
remove: bool,
) -> CargoResult<Vec<(String, PathBuf, Definition)>> {
let abs = |path: &String, def: &Definition| -> (String, PathBuf, Definition) {
let abs_path = match def {
Definition::Path(p) => p.parent().unwrap().join(&path),
Definition::Environment(_) | Definition::Cli => self.cwd().join(&path),
};
(path.to_string(), abs_path, def.clone())
};
let table = match cv {
CV::Table(table, _def) => table,
_ => unreachable!(),
};
let owned;
let include = if remove {
owned = table.remove("include");
owned.as_ref()
} else {
table.get("include")
};
let includes = match include {
Some(CV::String(s, def)) => {
vec![abs(s, def)]
}
Some(CV::List(list, _def)) => list.iter().map(|(s, def)| abs(s, def)).collect(),
Some(other) => bail!(
"`include` expected a string or list, but found {} in `{}`",
other.desc(),
other.definition()
),
None => {
return Ok(Vec::new());
}
};
Ok(includes)
}
/// Parses the CLI config args and returns them as a table.
pub(crate) fn cli_args_as_table(&self) -> CargoResult<ConfigValue> {
let mut loaded_args = CV::Table(HashMap::new(), Definition::Cli);
let cli_args = match &self.cli_config {
Some(cli_args) => cli_args,
None => return Ok(()),
None => return Ok(loaded_args),
};
let mut loaded_args = CV::Table(HashMap::new(), Definition::Cli);
for arg in cli_args {
let arg_as_path = self.cwd.join(arg);
let tmp_table = if !arg.is_empty() && arg_as_path.exists() {
@ -1044,13 +1201,18 @@ impl Config {
.merge(tmp_table, true)
.chain_err(|| format!("failed to merge --config argument `{}`", arg))?;
}
// Force values to be loaded.
let _ = self.values()?;
let values = self.values_mut()?;
let loaded_map = match loaded_args {
Ok(loaded_args)
}
/// Add config arguments passed on the command line.
fn merge_cli_args(&mut self) -> CargoResult<()> {
let loaded_map = match self.cli_args_as_table()? {
CV::Table(table, _def) => table,
_ => unreachable!(),
};
// Force values to be loaded.
let _ = self.values()?;
let values = self.values_mut()?;
for (key, value) in loaded_map.into_iter() {
match values.entry(key) {
Vacant(entry) => {
@ -1187,7 +1349,7 @@ impl Config {
None => return Ok(()),
};
let mut value = self.load_file(&credentials)?;
let mut value = self.load_file(&credentials, true)?;
// Backwards compatibility for old `.cargo/credentials` layout.
{
let (value_map, def) = match value {

View file

@ -1150,6 +1150,23 @@ lowest precedence.
Relative `path` dependencies in such a `[patch]` section are resolved
relative to the configuration file they appear in.
## `cargo config`
* Original Issue: [#2362](https://github.com/rust-lang/cargo/issues/2362)
* Tracking Issue: [#9301](https://github.com/rust-lang/cargo/issues/9301)
The `cargo config` subcommand provides a way to display the configuration
files that cargo loads. It currently includes the `get` subcommand which
can take an optional config value to display.
```console
cargo +nightly -Zunstable-options config get build.rustflags
```
If no config value is included, it will display all config values. See the
`--help` output for more options available.
<script>
(function() {
var fragments = {

View file

@ -1391,16 +1391,16 @@ fn bad_target_cfg() {
.with_stderr(
"\
[ERROR] error in [..]/foo/.cargo/config: \
could not load config key `target.cfg(not(target_os = \"none\")).runner`
could not load config key `target.\"cfg(not(target_os = /\"none/\"))\".runner`
Caused by:
error in [..]/foo/.cargo/config: \
could not load config key `target.cfg(not(target_os = \"none\")).runner`
could not load config key `target.\"cfg(not(target_os = /\"none/\"))\".runner`
Caused by:
invalid configuration for key `target.cfg(not(target_os = \"none\")).runner`
invalid configuration for key `target.\"cfg(not(target_os = /\"none/\"))\".runner`
expected a string or array of strings, but found a boolean for \
`target.cfg(not(target_os = \"none\")).runner` in [..]/foo/.cargo/config
`target.\"cfg(not(target_os = /\"none/\"))\".runner` in [..]/foo/.cargo/config
",
)
.run();

View file

@ -0,0 +1,510 @@
//! Tests for the `cargo config` command.
use super::config::write_config_at;
use cargo_test_support::paths;
use std::fs;
use std::path::PathBuf;
fn cargo_process(s: &str) -> cargo_test_support::Execs {
let mut p = cargo_test_support::cargo_process(s);
// Clear out some of the environment added by the default cargo_process so
// the tests don't need to deal with it.
p.env_remove("CARGO_PROFILE_DEV_SPLIT_DEBUGINFO")
.env_remove("CARGO_PROFILE_TEST_SPLIT_DEBUGINFO")
.env_remove("CARGO_PROFILE_RELEASE_SPLIT_DEBUGINFO")
.env_remove("CARGO_PROFILE_BENCH_SPLIT_DEBUGINFO")
.env_remove("CARGO_INCREMENTAL");
p
}
#[cargo_test]
fn gated() {
cargo_process("config get")
.masquerade_as_nightly_cargo()
.with_status(101)
.with_stderr("\
error: the `cargo config` command is unstable, pass `-Z unstable-options` to enable it
See https://github.com/rust-lang/cargo/issues/9301 for more information about the `cargo config` command.
")
.run();
}
fn common_setup() -> PathBuf {
write_config_at(
paths::home().join(".cargo/config.toml"),
"
[alias]
foo = \"abc --xyz\"
[build]
jobs = 99
rustflags = [\"--flag-global\"]
[profile.dev]
opt-level = 3
[profile.dev.package.foo]
opt-level = 1
[target.'cfg(target_os = \"linux\")']
runner = \"runme\"
# How unknown keys are handled.
[extra-table]
somekey = \"somevalue\"
",
);
let sub_folder = paths::root().join("foo/.cargo");
write_config_at(
sub_folder.join("config.toml"),
"
[alias]
sub-example = [\"sub\", \"example\"]
[build]
rustflags = [\"--flag-directory\"]
",
);
sub_folder
}
#[cargo_test]
fn get_toml() {
// Notes:
// - The "extra-table" is shown without a warning. I'm not sure how that
// should be handled, since displaying warnings could cause problems
// with ingesting the output.
// - Environment variables aren't loaded. :(
let sub_folder = common_setup();
cargo_process("config get -Zunstable-options")
.cwd(&sub_folder.parent().unwrap())
.masquerade_as_nightly_cargo()
.env("CARGO_ALIAS_BAR", "cat dog")
.env("CARGO_BUILD_JOBS", "100")
// The weird forward slash in the linux line is due to testsuite normalization.
.with_stdout(
"\
alias.foo = \"abc --xyz\"
alias.sub-example = [\"sub\", \"example\"]
build.jobs = 99
build.rustflags = [\"--flag-directory\", \"--flag-global\"]
extra-table.somekey = \"somevalue\"
profile.dev.opt-level = 3
profile.dev.package.foo.opt-level = 1
target.\"cfg(target_os = /\"linux/\")\".runner = \"runme\"
# The following environment variables may affect the loaded values.
# CARGO_ALIAS_BAR=[..]cat dog[..]
# CARGO_BUILD_JOBS=100
# CARGO_HOME=[ROOT]/home/.cargo
",
)
.with_stderr("")
.run();
// Env keys work if they are specific.
cargo_process("config get build.jobs -Zunstable-options")
.cwd(&sub_folder.parent().unwrap())
.masquerade_as_nightly_cargo()
.env("CARGO_BUILD_JOBS", "100")
.with_stdout("build.jobs = 100")
.with_stderr("")
.run();
// Array value.
cargo_process("config get build.rustflags -Zunstable-options")
.cwd(&sub_folder.parent().unwrap())
.masquerade_as_nightly_cargo()
.with_stdout("build.rustflags = [\"--flag-directory\", \"--flag-global\"]")
.with_stderr("")
.run();
// Sub-table
cargo_process("config get profile -Zunstable-options")
.cwd(&sub_folder.parent().unwrap())
.masquerade_as_nightly_cargo()
.with_stdout(
"\
profile.dev.opt-level = 3
profile.dev.package.foo.opt-level = 1
",
)
.with_stderr("")
.run();
// Specific profile entry.
cargo_process("config get profile.dev.opt-level -Zunstable-options")
.cwd(&sub_folder.parent().unwrap())
.masquerade_as_nightly_cargo()
.with_stdout("profile.dev.opt-level = 3")
.with_stderr("")
.run();
// A key that isn't set.
cargo_process("config get build.rustc -Zunstable-options")
.cwd(&sub_folder.parent().unwrap())
.masquerade_as_nightly_cargo()
.with_status(101)
.with_stdout("")
.with_stderr("error: config value `build.rustc` is not set")
.run();
// A key that is not part of Cargo's config schema.
cargo_process("config get not.set -Zunstable-options")
.cwd(&sub_folder.parent().unwrap())
.masquerade_as_nightly_cargo()
.with_status(101)
.with_stdout("")
.with_stderr("error: config value `not.set` is not set")
.run();
}
#[cargo_test]
fn get_json() {
// Notes:
// - This does not show env vars at all. :(
let all_json = r#"
{
"alias": {
"foo": "abc --xyz",
"sub-example": [
"sub",
"example"
]
},
"build": {
"jobs": 99,
"rustflags": [
"--flag-directory",
"--flag-global"
]
},
"extra-table": {
"somekey": "somevalue"
},
"profile": {
"dev": {
"opt-level": 3,
"package": {
"foo": {
"opt-level": 1
}
}
}
},
"target": {
"cfg(target_os = \"linux\")": {
"runner": "runme"
}
}
}
"#;
let sub_folder = common_setup();
cargo_process("config get --format=json -Zunstable-options")
.cwd(&sub_folder.parent().unwrap())
.masquerade_as_nightly_cargo()
.env("CARGO_ALIAS_BAR", "cat dog")
.env("CARGO_BUILD_JOBS", "100")
.with_json(all_json)
.with_stderr("")
.run();
// json-value is the same for the entire root table
cargo_process("config get --format=json-value -Zunstable-options")
.cwd(&sub_folder.parent().unwrap())
.masquerade_as_nightly_cargo()
.with_json(all_json)
.with_stderr("")
.run();
cargo_process("config get --format=json build.jobs -Zunstable-options")
.cwd(&sub_folder.parent().unwrap())
.masquerade_as_nightly_cargo()
.with_json(
r#"
{"build": {"jobs": 99}}
"#,
)
.with_stderr("")
.run();
cargo_process("config get --format=json-value build.jobs -Zunstable-options")
.cwd(&sub_folder.parent().unwrap())
.masquerade_as_nightly_cargo()
.with_stdout("99")
.with_stderr("")
.run();
}
#[cargo_test]
fn show_origin_toml() {
let sub_folder = common_setup();
cargo_process("config get --show-origin -Zunstable-options")
.cwd(&sub_folder.parent().unwrap())
.masquerade_as_nightly_cargo()
.with_stdout(
"\
alias.foo = \"abc --xyz\" # [ROOT]/home/.cargo/config.toml
alias.sub-example = [
\"sub\", # [ROOT]/foo/.cargo/config.toml
\"example\", # [ROOT]/foo/.cargo/config.toml
]
build.jobs = 99 # [ROOT]/home/.cargo/config.toml
build.rustflags = [
\"--flag-directory\", # [ROOT]/foo/.cargo/config.toml
\"--flag-global\", # [ROOT]/home/.cargo/config.toml
]
extra-table.somekey = \"somevalue\" # [ROOT]/home/.cargo/config.toml
profile.dev.opt-level = 3 # [ROOT]/home/.cargo/config.toml
profile.dev.package.foo.opt-level = 1 # [ROOT]/home/.cargo/config.toml
target.\"cfg(target_os = /\"linux/\")\".runner = \"runme\" # [ROOT]/home/.cargo/config.toml
# The following environment variables may affect the loaded values.
# CARGO_HOME=[ROOT]/home/.cargo
",
)
.with_stderr("")
.run();
cargo_process("config get --show-origin build.rustflags -Zunstable-options")
.cwd(&sub_folder.parent().unwrap())
.masquerade_as_nightly_cargo()
.env("CARGO_BUILD_RUSTFLAGS", "env1 env2")
.with_stdout(
"\
build.rustflags = [
\"--flag-directory\", # [ROOT]/foo/.cargo/config.toml
\"--flag-global\", # [ROOT]/home/.cargo/config.toml
\"env1\", # environment variable `CARGO_BUILD_RUSTFLAGS`
\"env2\", # environment variable `CARGO_BUILD_RUSTFLAGS`
]
",
)
.with_stderr("")
.run();
}
#[cargo_test]
fn show_origin_toml_cli() {
let sub_folder = common_setup();
cargo_process("config get --show-origin build.jobs -Zunstable-options --config build.jobs=123")
.cwd(&sub_folder.parent().unwrap())
.masquerade_as_nightly_cargo()
.env("CARGO_BUILD_JOBS", "1")
.with_stdout("build.jobs = 123 # --config cli option")
.with_stderr("")
.run();
cargo_process("config get --show-origin build.rustflags -Zunstable-options --config")
.arg("build.rustflags=[\"cli1\",\"cli2\"]")
.cwd(&sub_folder.parent().unwrap())
.masquerade_as_nightly_cargo()
.env("CARGO_BUILD_RUSTFLAGS", "env1 env2")
.with_stdout(
"\
build.rustflags = [
\"--flag-directory\", # [ROOT]/foo/.cargo/config.toml
\"--flag-global\", # [ROOT]/home/.cargo/config.toml
\"cli1\", # --config cli option
\"cli2\", # --config cli option
\"env1\", # environment variable `CARGO_BUILD_RUSTFLAGS`
\"env2\", # environment variable `CARGO_BUILD_RUSTFLAGS`
]
",
)
.with_stderr("")
.run();
}
#[cargo_test]
fn show_origin_json() {
let sub_folder = common_setup();
cargo_process("config get --show-origin --format=json -Zunstable-options")
.cwd(&sub_folder.parent().unwrap())
.masquerade_as_nightly_cargo()
.with_status(101)
.with_stderr("error: the `json` format does not support --show-origin, try the `toml` format instead")
.run();
}
#[cargo_test]
fn unmerged_toml() {
let sub_folder = common_setup();
cargo_process("config get --merged=no -Zunstable-options")
.cwd(&sub_folder.parent().unwrap())
.masquerade_as_nightly_cargo()
.env("CARGO_ALIAS_BAR", "cat dog")
.env("CARGO_BUILD_JOBS", "100")
.with_stdout(
"\
# Environment variables
# CARGO=[..]
# CARGO_ALIAS_BAR=[..]cat dog[..]
# CARGO_BUILD_JOBS=100
# CARGO_HOME=[ROOT]/home/.cargo
# [ROOT]/foo/.cargo/config.toml
alias.sub-example = [\"sub\", \"example\"]
build.rustflags = [\"--flag-directory\"]
# [ROOT]/home/.cargo/config.toml
alias.foo = \"abc --xyz\"
build.jobs = 99
build.rustflags = [\"--flag-global\"]
extra-table.somekey = \"somevalue\"
profile.dev.opt-level = 3
profile.dev.package.foo.opt-level = 1
target.\"cfg(target_os = /\"linux/\")\".runner = \"runme\"
",
)
.with_stderr("")
.run();
cargo_process("config get --merged=no build.rustflags -Zunstable-options")
.cwd(&sub_folder.parent().unwrap())
.masquerade_as_nightly_cargo()
.env("CARGO_BUILD_RUSTFLAGS", "env1 env2")
.with_stdout(
"\
# Environment variables
# CARGO_BUILD_RUSTFLAGS=[..]env1 env2[..]
# [ROOT]/foo/.cargo/config.toml
build.rustflags = [\"--flag-directory\"]
# [ROOT]/home/.cargo/config.toml
build.rustflags = [\"--flag-global\"]
",
)
.with_stderr("")
.run();
cargo_process("config get --merged=no does.not.exist -Zunstable-options")
.cwd(&sub_folder.parent().unwrap())
.masquerade_as_nightly_cargo()
.with_stderr("")
.with_stderr("")
.run();
cargo_process("config get --merged=no build.rustflags.extra -Zunstable-options")
.cwd(&sub_folder.parent().unwrap())
.masquerade_as_nightly_cargo()
.with_status(101)
.with_stderr(
"error: expected table for configuration key `build.rustflags`, \
but found array in [ROOT]/foo/.cargo/config.toml",
)
.run();
}
#[cargo_test]
fn unmerged_toml_cli() {
let sub_folder = common_setup();
cargo_process("config get --merged=no build.rustflags -Zunstable-options --config")
.arg("build.rustflags=[\"cli1\",\"cli2\"]")
.cwd(&sub_folder.parent().unwrap())
.masquerade_as_nightly_cargo()
.env("CARGO_BUILD_RUSTFLAGS", "env1 env2")
.with_stdout(
"\
# --config cli option
build.rustflags = [\"cli1\", \"cli2\"]
# Environment variables
# CARGO_BUILD_RUSTFLAGS=[..]env1 env2[..]
# [ROOT]/foo/.cargo/config.toml
build.rustflags = [\"--flag-directory\"]
# [ROOT]/home/.cargo/config.toml
build.rustflags = [\"--flag-global\"]
",
)
.with_stderr("")
.run();
}
#[cargo_test]
fn unmerged_json() {
let sub_folder = common_setup();
cargo_process("config get --merged=no --format=json -Zunstable-options")
.cwd(&sub_folder.parent().unwrap())
.masquerade_as_nightly_cargo()
.with_status(101)
.with_stderr(
"error: the `json` format does not support --merged=no, try the `toml` format instead",
)
.run();
}
#[cargo_test]
fn includes() {
let sub_folder = common_setup();
fs::write(
sub_folder.join("config.toml"),
"
include = 'other.toml'
[build]
rustflags = [\"--flag-directory\"]
",
)
.unwrap();
fs::write(
sub_folder.join("other.toml"),
"
[build]
rustflags = [\"--flag-other\"]
",
)
.unwrap();
cargo_process("config get build.rustflags -Zunstable-options -Zconfig-include")
.cwd(&sub_folder.parent().unwrap())
.masquerade_as_nightly_cargo()
.with_stdout(r#"build.rustflags = ["--flag-other", "--flag-directory", "--flag-global"]"#)
.with_stderr("")
.run();
cargo_process(
"config get build.rustflags --show-origin=yes -Zunstable-options -Zconfig-include",
)
.cwd(&sub_folder.parent().unwrap())
.masquerade_as_nightly_cargo()
.with_stdout(
"\
build.rustflags = [
\"--flag-other\", # [ROOT]/foo/.cargo/other.toml
\"--flag-directory\", # [ROOT]/foo/.cargo/config.toml
\"--flag-global\", # [ROOT]/home/.cargo/config.toml
]
",
)
.with_stderr("")
.run();
cargo_process("config get --merged=no -Zunstable-options -Zconfig-include")
.cwd(&sub_folder.parent().unwrap())
.masquerade_as_nightly_cargo()
.with_stdout(
"\
# Environment variables
# CARGO=[..]
# CARGO_HOME=[ROOT]/home/.cargo
# [ROOT]/foo/.cargo/other.toml
build.rustflags = [\"--flag-other\"]
# [ROOT]/foo/.cargo/config.toml
build.rustflags = [\"--flag-directory\"]
include = \"other.toml\"
# [ROOT]/home/.cargo/config.toml
alias.foo = \"abc --xyz\"
build.jobs = 99
build.rustflags = [\"--flag-global\"]
extra-table.somekey = \"somevalue\"
profile.dev.opt-level = 3
profile.dev.package.foo.opt-level = 1
target.\"cfg(target_os = /\"linux/\")\".runner = \"runme\"
",
)
.with_stderr("")
.run();
}

View file

@ -24,6 +24,7 @@ mod build_script_extra_link_arg;
mod cache_messages;
mod cargo_alias_config;
mod cargo_command;
mod cargo_config;
mod cargo_env_config;
mod cargo_features;
mod cargo_targets;