mirror of
https://github.com/rust-lang/cargo.git
synced 2025-09-25 11:14:46 +00:00
Auto merge of #9302 - ehuss:cargo-config, r=alexcrichton
Add `cargo config` subcommand. This adds an initial version of the `cargo config` command as discussed in #2362. Closes #2362
This commit is contained in:
commit
7204d3989d
@ -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"
|
||||
|
@ -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)),
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
48
src/bin/cargo/commands/config.rs
Normal file
48
src/bin/cargo/commands/config.rs
Normal 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(())
|
||||
}
|
@ -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))?;
|
||||
|
@ -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;
|
||||
|
@ -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").
|
||||
|
308
src/cargo/ops/cargo_config.rs
Normal file
308
src/cargo/ops/cargo_config.rs
Normal file
@ -0,0 +1,308 @@
|
||||
//! Implementation of `cargo config` subcommand.
|
||||
|
||||
use crate::util::config::{Config, ConfigKey, ConfigValue as CV, Definition};
|
||||
use crate::util::errors::CargoResult;
|
||||
use crate::{drop_eprintln, drop_println};
|
||||
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),
|
||||
ConfigFormat::Json => print_json(config, &key, &cv, true),
|
||||
ConfigFormat::JsonValue => print_json(config, &key, &cv, false),
|
||||
}
|
||||
if let Some(env) = maybe_env(config, &key, &cv) {
|
||||
match opts.format {
|
||||
ConfigFormat::Toml => print_toml_env(config, &env),
|
||||
ConfigFormat::Json | ConfigFormat::JsonValue => print_json_env(config, &env),
|
||||
}
|
||||
}
|
||||
} 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_env(config: &Config, env: &[(&String, &String)]) {
|
||||
drop_eprintln!(
|
||||
config,
|
||||
"note: 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_eprintln!(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 {
|
||||
cv_to_json(cv)
|
||||
} 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();
|
||||
}
|
||||
table[last_part] = cv_to_json(cv);
|
||||
root_table
|
||||
};
|
||||
drop_println!(config, "{}", serde_json::to_string(&json_value).unwrap());
|
||||
|
||||
// Helper for recursively converting a CV to JSON.
|
||||
fn cv_to_json(cv: &CV) -> serde_json::Value {
|
||||
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 table = json!({});
|
||||
for (key, val) in map {
|
||||
table[key] = cv_to_json(val);
|
||||
}
|
||||
table
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
@ -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;
|
||||
|
@ -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),
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -1091,38 +1091,6 @@ The 2021 edition will set the default [resolver version] to "2".
|
||||
[edition]: ../../edition-guide/index.html
|
||||
[resolver version]: resolver.md#resolver-versions
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var fragments = {
|
||||
"#edition": "manifest.html#the-edition-field",
|
||||
"#compile-progress": "config.html#termprogresswhen",
|
||||
"#rename-dependency": "specifying-dependencies.html#renaming-dependencies-in-cargotoml",
|
||||
"#alternate-registries": "registries.html",
|
||||
"#offline-mode": "../commands/cargo.html",
|
||||
"#publish-lockfile": "../commands/cargo-package.html",
|
||||
"#default-run": "manifest.html#the-default-run-field",
|
||||
"#cache-messages": "https://github.com/rust-lang/cargo/pull/7450",
|
||||
"#install-upgrade": "../commands/cargo-install.html",
|
||||
"#profile-overrides": "profiles.html#overrides",
|
||||
"#config-profiles": "config.html#profile",
|
||||
"#crate-versions": "https://github.com/rust-lang/cargo/pull/8509",
|
||||
"#features": "features.html#feature-resolver-version-2",
|
||||
"#package-features": "features.html#resolver-version-2-command-line-flags",
|
||||
"#resolver": "resolver.html#resolver-versions",
|
||||
};
|
||||
var target = fragments[window.location.hash];
|
||||
if (target) {
|
||||
if (target.startsWith('https')) {
|
||||
window.location.replace(target);
|
||||
} else {
|
||||
var url = window.location.toString();
|
||||
var base = url.substring(0, url.lastIndexOf('/'));
|
||||
window.location.replace(base + "/" + target);
|
||||
}
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
### future incompat report
|
||||
* RFC: [#2834](https://github.com/rust-lang/rfcs/blob/master/text/2834-cargo-report-future-incompat.md)
|
||||
* rustc Tracking Issue: [#71249](https://github.com/rust-lang/rust/issues/71249)
|
||||
@ -1181,3 +1149,52 @@ 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 = {
|
||||
"#edition": "manifest.html#the-edition-field",
|
||||
"#compile-progress": "config.html#termprogresswhen",
|
||||
"#rename-dependency": "specifying-dependencies.html#renaming-dependencies-in-cargotoml",
|
||||
"#alternate-registries": "registries.html",
|
||||
"#offline-mode": "../commands/cargo.html",
|
||||
"#publish-lockfile": "../commands/cargo-package.html",
|
||||
"#default-run": "manifest.html#the-default-run-field",
|
||||
"#cache-messages": "https://github.com/rust-lang/cargo/pull/7450",
|
||||
"#install-upgrade": "../commands/cargo-install.html",
|
||||
"#profile-overrides": "profiles.html#overrides",
|
||||
"#config-profiles": "config.html#profile",
|
||||
"#crate-versions": "https://github.com/rust-lang/cargo/pull/8509",
|
||||
"#features": "features.html#feature-resolver-version-2",
|
||||
"#package-features": "features.html#resolver-version-2-command-line-flags",
|
||||
"#resolver": "resolver.html#resolver-versions",
|
||||
};
|
||||
var target = fragments[window.location.hash];
|
||||
if (target) {
|
||||
if (target.startsWith('https')) {
|
||||
window.location.replace(target);
|
||||
} else {
|
||||
var url = window.location.toString();
|
||||
var base = url.substring(0, url.lastIndexOf('/'));
|
||||
window.location.replace(base + "/" + target);
|
||||
}
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
@ -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();
|
||||
|
522
tests/testsuite/cargo_config.rs
Normal file
522
tests/testsuite/cargo_config.rs
Normal file
@ -0,0 +1,522 @@
|
||||
//! 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(
|
||||
"\
|
||||
note: The following environment variables may affect the loaded values.
|
||||
CARGO_ALIAS_BAR=[..]cat dog[..]
|
||||
CARGO_BUILD_JOBS=100
|
||||
CARGO_HOME=[ROOT]/home/.cargo
|
||||
",
|
||||
)
|
||||
.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(
|
||||
"\
|
||||
note: The following environment variables may affect the loaded values.
|
||||
CARGO_HOME=[ROOT]/home/.cargo
|
||||
",
|
||||
)
|
||||
.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();
|
||||
}
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user