Skip to content

Commit e37f62f

Browse files
committed
Auto merge of #7649 - ehuss:config2, r=alexcrichton
Config enhancements. This is a collection of changes to config handling. I intended to split this into separate PRs, but they all built on one another so I decided to do it as one. However, I can still split this up if desired. High level overview: - Refactorings, mainly to remove `pub` from `Config::get_table` and use serde API instead. - Add `--config` CLI option. - Add config `include` to include other files. This makes some progress on #5416. Closes #6699. This makes a minor user-visible change in regards to `StringList` types. If an array is specified in a config as a list, and also as an env var, they will now be merged. Previously the environment variable overrode the file value. But if it is a string, then it won't join (env var takes precedence). I can probably change this, but I'm not sure if the old behavior is desired, or if it should merge all the time? **Future plans** This lays the groundwork for some more changes: - Work on #7253 (`debug-assertions` and `debug` fails in environment vars). I have some ideas to try. - Consider removing use of `get_list` for `paths`, and use a `Vec<ConfigRelativePath>`. This will require some non-trivial changes to how `ConfigSeqAccess` works. This is one of the last parts that does not use the serde API. - Possibly change `[source]` to load config values in a lazy fashion. This will unlock the ability to use environment variables with source definitions (like CARGO_SOURCE_CRATES_IO_REPLACE_WITH). - Possibly change `[profile]` to load config profiles in a lazy fashion. This will make it easier to use environment variables with profiles, particularly with arbitrarily named profiles. - Possibly remove the case-sensitive environment variables in `-Zadvanced-env`. I think they are just too awkward, and prone to problems. Instead, drive people towards using `--config` instead of env vars. - Add support for TOML tables in env vars (like `CARGO_PROFILES={my-profile={opt-level=1}})`). I started implementing it, but then looking at the use cases, it didn't seem as useful as I initially thought. However, it's still an option to try. **Refactoring overview** - `[source]` table now uses the serde API. - `[target]` table now uses the serde API. This is complicated since the 'cfg()' entries are different from the triple entries. The 'cfg()' tables are loaded separately, and are accessed from `Config::target_cfgs`. Otherwise, it just uses `config.get` of the specific target.TRIPLE. - Moved the target config stuff into `config/target.rs`. - Various changes to make this work: - Added `PathAndArgs` type which replaces `config.get_path_and_args`. - Changed `ConfigKey` to track the key parts as a list (instead of a string). This fixes an issue where quoted keys weren't handled properly (like `[foo.'a.b'.bar]`). This also seems to make a little more sense (it was joining parts into a string only to immediately call `split` on it). Changed various APIs to take a `ConfigKey` object instead of a string to avoid that splitting behavior. - `ValueDeserializer` now pre-computes the `Definition` so that it can provide a better error message when a value fails to deserialize. Overall, there shouldn't be significant user-visible changes. Some error messages have changed and warnings have been added for some ignored keys. `-Zadvanced-env` now works for source and target tables, though I'm not really happy with that feature.
2 parents 4647b1d + ce7fd68 commit e37f62f

File tree

30 files changed

+2237
-862
lines changed

30 files changed

+2237
-862
lines changed

crates/cargo-test-support/src/git.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ let git_project = git::new("dep1", |project| {
1212
project
1313
.file("Cargo.toml", &basic_manifest("dep1"))
1414
.file("src/lib.rs", r#"pub fn f() { println!("hi!"); } "#)
15-
}).unwrap();
15+
});
1616
1717
// Use the `url()` method to get the file url to the new repository.
1818
let p = project()

crates/cargo-test-support/src/lib.rs

+59-39
Original file line numberDiff line numberDiff line change
@@ -1123,45 +1123,10 @@ impl Execs {
11231123
}
11241124

11251125
fn normalize_matcher(&self, matcher: &str) -> String {
1126-
// Let's not deal with / vs \ (windows...)
1127-
let matcher = matcher.replace("\\\\", "/").replace("\\", "/");
1128-
1129-
// Weirdness for paths on Windows extends beyond `/` vs `\` apparently.
1130-
// Namely paths like `c:\` and `C:\` are equivalent and that can cause
1131-
// issues. The return value of `env::current_dir()` may return a
1132-
// lowercase drive name, but we round-trip a lot of values through `Url`
1133-
// which will auto-uppercase the drive name. To just ignore this
1134-
// distinction we try to canonicalize as much as possible, taking all
1135-
// forms of a path and canonicalizing them to one.
1136-
let replace_path = |s: &str, path: &Path, with: &str| {
1137-
let path_through_url = Url::from_file_path(path).unwrap().to_file_path().unwrap();
1138-
let path1 = path.display().to_string().replace("\\", "/");
1139-
let path2 = path_through_url.display().to_string().replace("\\", "/");
1140-
s.replace(&path1, with)
1141-
.replace(&path2, with)
1142-
.replace(with, &path1)
1143-
};
1144-
1145-
// Do the template replacements on the expected string.
1146-
let matcher = match &self.process_builder {
1147-
None => matcher,
1148-
Some(p) => match p.get_cwd() {
1149-
None => matcher,
1150-
Some(cwd) => replace_path(&matcher, cwd, "[CWD]"),
1151-
},
1152-
};
1153-
1154-
// Similar to cwd above, perform similar treatment to the root path
1155-
// which in theory all of our paths should otherwise get rooted at.
1156-
let root = paths::root();
1157-
let matcher = replace_path(&matcher, &root, "[ROOT]");
1158-
1159-
// Let's not deal with \r\n vs \n on windows...
1160-
let matcher = matcher.replace("\r", "");
1161-
1162-
// It's easier to read tabs in outputs if they don't show up as literal
1163-
// hidden characters
1164-
matcher.replace("\t", "<tab>")
1126+
normalize_matcher(
1127+
matcher,
1128+
self.process_builder.as_ref().and_then(|p| p.get_cwd()),
1129+
)
11651130
}
11661131

11671132
fn match_std(
@@ -1429,6 +1394,52 @@ pub fn lines_match(expected: &str, mut actual: &str) -> bool {
14291394
actual.is_empty() || expected.ends_with("[..]")
14301395
}
14311396

1397+
/// Variant of `lines_match` that applies normalization to the strings.
1398+
pub fn normalized_lines_match(expected: &str, actual: &str, cwd: Option<&Path>) -> bool {
1399+
let expected = normalize_matcher(expected, cwd);
1400+
let actual = normalize_matcher(actual, cwd);
1401+
lines_match(&expected, &actual)
1402+
}
1403+
1404+
fn normalize_matcher(matcher: &str, cwd: Option<&Path>) -> String {
1405+
// Let's not deal with / vs \ (windows...)
1406+
let matcher = matcher.replace("\\\\", "/").replace("\\", "/");
1407+
1408+
// Weirdness for paths on Windows extends beyond `/` vs `\` apparently.
1409+
// Namely paths like `c:\` and `C:\` are equivalent and that can cause
1410+
// issues. The return value of `env::current_dir()` may return a
1411+
// lowercase drive name, but we round-trip a lot of values through `Url`
1412+
// which will auto-uppercase the drive name. To just ignore this
1413+
// distinction we try to canonicalize as much as possible, taking all
1414+
// forms of a path and canonicalizing them to one.
1415+
let replace_path = |s: &str, path: &Path, with: &str| {
1416+
let path_through_url = Url::from_file_path(path).unwrap().to_file_path().unwrap();
1417+
let path1 = path.display().to_string().replace("\\", "/");
1418+
let path2 = path_through_url.display().to_string().replace("\\", "/");
1419+
s.replace(&path1, with)
1420+
.replace(&path2, with)
1421+
.replace(with, &path1)
1422+
};
1423+
1424+
// Do the template replacements on the expected string.
1425+
let matcher = match cwd {
1426+
None => matcher,
1427+
Some(p) => replace_path(&matcher, p, "[CWD]"),
1428+
};
1429+
1430+
// Similar to cwd above, perform similar treatment to the root path
1431+
// which in theory all of our paths should otherwise get rooted at.
1432+
let root = paths::root();
1433+
let matcher = replace_path(&matcher, &root, "[ROOT]");
1434+
1435+
// Let's not deal with \r\n vs \n on windows...
1436+
let matcher = matcher.replace("\r", "");
1437+
1438+
// It's easier to read tabs in outputs if they don't show up as literal
1439+
// hidden characters
1440+
matcher.replace("\t", "<tab>")
1441+
}
1442+
14321443
#[test]
14331444
fn lines_match_works() {
14341445
assert!(lines_match("a b", "a b"));
@@ -1835,3 +1846,12 @@ pub fn symlink_supported() -> bool {
18351846
pub fn symlink_supported() -> bool {
18361847
true
18371848
}
1849+
1850+
/// The error message for ENOENT.
1851+
///
1852+
/// It's generally not good to match against OS error messages, but I think
1853+
/// this one is relatively stable.
1854+
#[cfg(windows)]
1855+
pub const NO_SUCH_FILE_ERR_MSG: &str = "The system cannot find the file specified. (os error 2)";
1856+
#[cfg(not(windows))]
1857+
pub const NO_SUCH_FILE_ERR_MSG: &str = "No such file or directory (os error 2)";

crates/resolver-tests/tests/resolve.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,13 @@ proptest! {
6262
.configure(
6363
1,
6464
None,
65-
&None,
65+
None,
6666
false,
6767
false,
6868
false,
6969
&None,
7070
&["minimal-versions".to_string()],
71+
&[],
7172
)
7273
.unwrap();
7374

@@ -565,12 +566,13 @@ fn test_resolving_minimum_version_with_transitive_deps() {
565566
.configure(
566567
1,
567568
None,
568-
&None,
569+
None,
569570
false,
570571
false,
571572
false,
572573
&None,
573574
&["minimal-versions".to_string()],
575+
&[],
574576
)
575577
.unwrap();
576578

src/bin/cargo/cli.rs

+40-17
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ use super::list_commands;
1010
use crate::command_prelude::*;
1111

1212
pub fn main(config: &mut Config) -> CliResult {
13+
// CAUTION: Be careful with using `config` until it is configured below.
14+
// In general, try to avoid loading config values unless necessary (like
15+
// the [alias] table).
1316
let args = match cli().get_matches_safe() {
1417
Ok(args) => args,
1518
Err(e) => {
@@ -90,8 +93,18 @@ Run with 'cargo -Z [FLAG] [SUBCOMMAND]'"
9093
}
9194

9295
let args = expand_aliases(config, args)?;
96+
let (cmd, subcommand_args) = match args.subcommand() {
97+
(cmd, Some(args)) => (cmd, args),
98+
_ => {
99+
// No subcommand provided.
100+
cli().print_help()?;
101+
return Ok(());
102+
}
103+
};
104+
config_configure(config, &args, subcommand_args)?;
105+
super::init_git_transports(&config);
93106

94-
execute_subcommand(config, &args)
107+
execute_subcommand(config, cmd, subcommand_args)
95108
}
96109

97110
pub fn get_version_string(is_verbose: bool) -> String {
@@ -147,34 +160,39 @@ fn expand_aliases(
147160
Ok(args)
148161
}
149162

150-
fn execute_subcommand(config: &mut Config, args: &ArgMatches<'_>) -> CliResult {
151-
let (cmd, subcommand_args) = match args.subcommand() {
152-
(cmd, Some(args)) => (cmd, args),
153-
_ => {
154-
cli().print_help()?;
155-
return Ok(());
156-
}
157-
};
158-
163+
fn config_configure(
164+
config: &mut Config,
165+
args: &ArgMatches<'_>,
166+
subcommand_args: &ArgMatches<'_>,
167+
) -> CliResult {
159168
let arg_target_dir = &subcommand_args.value_of_path("target-dir", config);
160-
169+
let config_args: Vec<&str> = args.values_of("config").unwrap_or_default().collect();
170+
let quiet = if args.is_present("quiet") || subcommand_args.is_present("quiet") {
171+
Some(true)
172+
} else {
173+
None
174+
};
161175
config.configure(
162176
args.occurrences_of("verbose") as u32,
163-
if args.is_present("quiet") || subcommand_args.is_present("quiet") {
164-
Some(true)
165-
} else {
166-
None
167-
},
168-
&args.value_of("color").map(|s| s.to_string()),
177+
quiet,
178+
args.value_of("color"),
169179
args.is_present("frozen"),
170180
args.is_present("locked"),
171181
args.is_present("offline"),
172182
arg_target_dir,
173183
&args
174184
.values_of_lossy("unstable-features")
175185
.unwrap_or_default(),
186+
&config_args,
176187
)?;
188+
Ok(())
189+
}
177190

191+
fn execute_subcommand(
192+
config: &mut Config,
193+
cmd: &str,
194+
subcommand_args: &ArgMatches<'_>,
195+
) -> CliResult {
178196
if let Some(exec) = commands::builtin_exec(cmd) {
179197
return exec(config, subcommand_args);
180198
}
@@ -241,6 +259,11 @@ See 'cargo help <command>' for more information on a specific command.\n",
241259
.arg(opt("frozen", "Require Cargo.lock and cache are up to date").global(true))
242260
.arg(opt("locked", "Require Cargo.lock is up to date").global(true))
243261
.arg(opt("offline", "Run without accessing the network").global(true))
262+
.arg(
263+
multi_opt("config", "KEY=VALUE", "Override a configuration value")
264+
.global(true)
265+
.hidden(true),
266+
)
244267
.arg(
245268
Arg::with_name("unstable-features")
246269
.help("Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details")

src/bin/cargo/main.rs

-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ fn main() {
3535
let result = match cargo::ops::fix_maybe_exec_rustc() {
3636
Ok(true) => Ok(()),
3737
Ok(false) => {
38-
init_git_transports(&config);
3938
let _token = cargo::util::job::setup();
4039
cli::main(&mut config)
4140
}

0 commit comments

Comments
 (0)