Skip to content

Commit 94bf478

Browse files
committedOct 9, 2019
Auto merge of #7456 - alexcrichton:config-only-serde, r=ehuss
Migrate towards exclusively using serde for `Config` This series of commits was spawned off a thought I had while reading #7253 (comment), although it ended up not really touching on that at all. I was a little unsettled about how unstructured the config accesses are throughout Cargo and we had sort of two systems (one serde which is nice, one which is more manual) for reading config values. This PR converts everything to run through serde for deserializing values, except for `get_list` which is funky. There's only one usage of that with the `paths` key though and we can probably fix this soon-ish. In any case, the highlights of this PR are: * This PR is surprisingly large. I did a lot of movement in `config.rs` to try to make the file smaller and more understandable. * The `Value` type which retains information about where it was deserialized from is very special, and has special treatment with serde's data model. That's what allows us to use that and serde at the same time. * The `ConfigRelativePath` and `ConfigKey` structures have been revamped internally, but morally serve the same purposes as before. * Cargo now has structured `struct` access for a bunch of its configuration (`net`, `http`, `build`, etc). I would ideally like to move toward a world where this is the *only* way to read configuration, or at least everything conventionally runs through those paths. * Functionally, this PR should have no difference other than tweaks to error messages here and there, and perhaps more strict validation on commands where we validate more configuration on each run than we previously did. * This isn't a 100% transition for Cargo yet, but I figured it would be a good idea to post this and get some feedback first. * In the long run I want to remove `get_env`, `get_cv`, and `get_*_priv` from `Config` as internal details. I'd like to move this all to `de.rs` and have it walk down the tree of configuration as we deserialize a value. For now though these all remain in place and that refactoring is left to a future PR.
2 parents 515a6df + 9a12e48 commit 94bf478

File tree

20 files changed

+1083
-660
lines changed

20 files changed

+1083
-660
lines changed
 

‎src/bin/cargo/main.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,7 @@ fn aliased_command(config: &Config, command: &str) -> CargoResult<Option<Vec<Str
6060
.collect(),
6161
),
6262
Ok(None) => None,
63-
Err(_) => config
64-
.get_list(&alias_name)?
65-
.map(|record| record.val.iter().map(|s| s.0.to_string()).collect()),
63+
Err(_) => config.get::<Option<Vec<String>>>(&alias_name)?,
6664
};
6765
let result = user_alias.or_else(|| match command {
6866
"b" => Some(vec!["build".to_string()]),

‎src/cargo/core/compiler/build_config.rs

+7-7
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,16 @@ impl BuildConfig {
6262
requested_target: &Option<String>,
6363
mode: CompileMode,
6464
) -> CargoResult<BuildConfig> {
65+
let cfg = config.build_config()?;
6566
let requested_kind = match requested_target {
6667
Some(s) => CompileKind::Target(CompileTarget::new(s)?),
67-
None => match config.get_string("build.target")? {
68-
Some(cfg) => {
69-
let value = if cfg.val.ends_with(".json") {
70-
let path = cfg.definition.root(config).join(&cfg.val);
68+
None => match &cfg.target {
69+
Some(val) => {
70+
let value = if val.raw_value().ends_with(".json") {
71+
let path = val.clone().resolve_path(config);
7172
path.to_str().expect("must be utf-8 in toml").to_string()
7273
} else {
73-
cfg.val
74+
val.raw_value().to_string()
7475
};
7576
CompileKind::Target(CompileTarget::new(&value)?)
7677
}
@@ -88,8 +89,7 @@ impl BuildConfig {
8889
its environment, ignoring the `-j` parameter",
8990
)?;
9091
}
91-
let cfg_jobs: Option<u32> = config.get("build.jobs")?;
92-
let jobs = jobs.or(cfg_jobs).unwrap_or(::num_cpus::get() as u32);
92+
let jobs = jobs.or(cfg.jobs).unwrap_or(::num_cpus::get() as u32);
9393

9494
Ok(BuildConfig {
9595
requested_kind,

‎src/cargo/core/compiler/build_context/target_info.rs

+13-10
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use std::str::{self, FromStr};
66

77
use crate::core::compiler::CompileKind;
88
use crate::core::TargetKind;
9+
use crate::util::config::StringList;
910
use crate::util::{CargoResult, CargoResultExt, Config, ProcessBuilder, Rustc};
1011
use cargo_platform::{Cfg, CfgExpr};
1112

@@ -427,9 +428,8 @@ fn env_args(
427428
CompileKind::Target(target) => target.short_name(),
428429
};
429430
let key = format!("target.{}.{}", target, name);
430-
if let Some(args) = config.get_list_or_split_string(&key)? {
431-
let args = args.val.into_iter();
432-
rustflags.extend(args);
431+
if let Some(args) = config.get::<Option<StringList>>(&key)? {
432+
rustflags.extend(args.as_slice().iter().cloned());
433433
}
434434
// ...including target.'cfg(...)'.rustflags
435435
if let Some(target_cfg) = target_cfg {
@@ -450,9 +450,8 @@ fn env_args(
450450

451451
for n in cfgs {
452452
let key = format!("target.{}.{}", n, name);
453-
if let Some(args) = config.get_list_or_split_string(&key)? {
454-
let args = args.val.into_iter();
455-
rustflags.extend(args);
453+
if let Some(args) = config.get::<Option<StringList>>(&key)? {
454+
rustflags.extend(args.as_slice().iter().cloned());
456455
}
457456
}
458457
}
@@ -463,10 +462,14 @@ fn env_args(
463462
}
464463

465464
// Then the `build.rustflags` value.
466-
let key = format!("build.{}", name);
467-
if let Some(args) = config.get_list_or_split_string(&key)? {
468-
let args = args.val.into_iter();
469-
return Ok(args.collect());
465+
let build = config.build_config()?;
466+
let list = if name == "rustflags" {
467+
&build.rustflags
468+
} else {
469+
&build.rustdocflags
470+
};
471+
if let Some(list) = list {
472+
return Ok(list.as_slice().to_vec());
470473
}
471474

472475
Ok(Vec::new())

‎src/cargo/core/compiler/context/mod.rs

+1-4
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
9999
}
100100
};
101101

102-
let pipelining = bcx
103-
.config
104-
.get::<Option<bool>>("build.pipelining")?
105-
.unwrap_or(true);
102+
let pipelining = bcx.config.build_config()?.pipelining.unwrap_or(true);
106103

107104
Ok(Self {
108105
bcx,

‎src/cargo/core/compiler/output_depinfo.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,10 @@ pub fn output_depinfo<'a, 'b>(cx: &mut Context<'a, 'b>, unit: &Unit<'a>) -> Carg
8181
let mut visited = HashSet::new();
8282
let success = add_deps_for_unit(&mut deps, cx, unit, &mut visited).is_ok();
8383
let basedir_string;
84-
let basedir = match bcx.config.get_path("build.dep-info-basedir")? {
84+
let basedir = match bcx.config.build_config()?.dep_info_basedir.clone() {
8585
Some(value) => {
8686
basedir_string = value
87-
.val
87+
.resolve_path(bcx.config)
8888
.as_os_str()
8989
.to_str()
9090
.ok_or_else(|| internal("build.dep-info-basedir path not utf-8"))?

‎src/cargo/core/package.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -376,9 +376,7 @@ impl<'cfg> PackageSet<'cfg> {
376376
// that it's buggy, and we've empirically seen that it's buggy with HTTP
377377
// proxies.
378378
let mut multi = Multi::new();
379-
let multiplexing = config
380-
.get::<Option<bool>>("http.multiplexing")?
381-
.unwrap_or(true);
379+
let multiplexing = config.http_config()?.multiplexing.unwrap_or(true);
382380
multi
383381
.pipelining(false, multiplexing)
384382
.chain_err(|| "failed to enable multiplexing/pipelining in curl")?;

‎src/cargo/core/profiles.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ impl Profiles {
3838

3939
let incremental = match env::var_os("CARGO_INCREMENTAL") {
4040
Some(v) => Some(v == "1"),
41-
None => config.get::<Option<bool>>("build.incremental")?,
41+
None => config.build_config()?.incremental,
4242
};
4343

4444
if !features.is_enabled(Feature::named_profiles()) {

‎src/cargo/ops/cargo_new.rs

+37-36
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
1+
use crate::core::{compiler, Workspace};
2+
use crate::util::errors::{self, CargoResult, CargoResultExt};
3+
use crate::util::{existing_vcs_repo, FossilRepo, GitRepo, HgRepo, PijulRepo};
4+
use crate::util::{paths, validate_package_name, Config};
5+
use git2::Config as GitConfig;
6+
use git2::Repository as GitRepository;
7+
use serde::de;
8+
use serde::Deserialize;
19
use std::collections::BTreeMap;
210
use std::env;
311
use std::fmt;
412
use std::fs;
513
use std::io::{BufRead, BufReader, ErrorKind};
614
use std::path::{Path, PathBuf};
7-
8-
use git2::Config as GitConfig;
9-
use git2::Repository as GitRepository;
10-
11-
use crate::core::{compiler, Workspace};
12-
use crate::util::errors::{self, CargoResult, CargoResultExt};
13-
use crate::util::{existing_vcs_repo, internal, FossilRepo, GitRepo, HgRepo, PijulRepo};
14-
use crate::util::{paths, validate_package_name, Config};
15+
use std::str::FromStr;
1516

1617
use toml;
1718

@@ -24,6 +25,31 @@ pub enum VersionControl {
2425
NoVcs,
2526
}
2627

28+
impl FromStr for VersionControl {
29+
type Err = failure::Error;
30+
31+
fn from_str(s: &str) -> Result<Self, failure::Error> {
32+
match s {
33+
"git" => Ok(VersionControl::Git),
34+
"hg" => Ok(VersionControl::Hg),
35+
"pijul" => Ok(VersionControl::Pijul),
36+
"fossil" => Ok(VersionControl::Fossil),
37+
"none" => Ok(VersionControl::NoVcs),
38+
other => failure::bail!("unknown vcs specification: `{}`", other),
39+
}
40+
}
41+
}
42+
43+
impl<'de> de::Deserialize<'de> for VersionControl {
44+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
45+
where
46+
D: de::Deserializer<'de>,
47+
{
48+
let s = String::deserialize(deserializer)?;
49+
FromStr::from_str(&s).map_err(de::Error::custom)
50+
}
51+
}
52+
2753
#[derive(Debug)]
2854
pub struct NewOptions {
2955
pub version_control: Option<VersionControl>,
@@ -102,9 +128,11 @@ impl NewOptions {
102128
}
103129
}
104130

131+
#[derive(Deserialize)]
105132
struct CargoNewConfig {
106133
name: Option<String>,
107134
email: Option<String>,
135+
#[serde(rename = "vcs")]
108136
version_control: Option<VersionControl>,
109137
}
110138

@@ -543,7 +571,7 @@ fn init_vcs(path: &Path, vcs: VersionControl, config: &Config) -> CargoResult<()
543571
fn mk(config: &Config, opts: &MkOptions<'_>) -> CargoResult<()> {
544572
let path = opts.path;
545573
let name = opts.name;
546-
let cfg = global_config(config)?;
574+
let cfg = config.get::<CargoNewConfig>("cargo-new")?;
547575

548576
// Using the push method with two arguments ensures that the entries for
549577
// both `ignore` and `hgignore` are in sync.
@@ -752,30 +780,3 @@ fn discover_author() -> CargoResult<(String, Option<String>)> {
752780

753781
Ok((name, email))
754782
}
755-
756-
fn global_config(config: &Config) -> CargoResult<CargoNewConfig> {
757-
let name = config.get_string("cargo-new.name")?.map(|s| s.val);
758-
let email = config.get_string("cargo-new.email")?.map(|s| s.val);
759-
let vcs = config.get_string("cargo-new.vcs")?;
760-
761-
let vcs = match vcs.as_ref().map(|p| (&p.val[..], &p.definition)) {
762-
Some(("git", _)) => Some(VersionControl::Git),
763-
Some(("hg", _)) => Some(VersionControl::Hg),
764-
Some(("pijul", _)) => Some(VersionControl::Pijul),
765-
Some(("none", _)) => Some(VersionControl::NoVcs),
766-
Some((s, p)) => {
767-
return Err(internal(format!(
768-
"invalid configuration for key \
769-
`cargo-new.vcs`, unknown vcs `{}` \
770-
(found in {})",
771-
s, p
772-
)));
773-
}
774-
None => None,
775-
};
776-
Ok(CargoNewConfig {
777-
name,
778-
email,
779-
version_control: vcs,
780-
})
781-
}

‎src/cargo/ops/registry.rs

+25-35
Original file line numberDiff line numberDiff line change
@@ -408,34 +408,26 @@ pub fn http_handle_and_timeout(config: &Config) -> CargoResult<(Easy, HttpTimeou
408408
}
409409

410410
pub fn needs_custom_http_transport(config: &Config) -> CargoResult<bool> {
411-
let proxy_exists = http_proxy_exists(config)?;
412-
let timeout = HttpTimeout::new(config)?.is_non_default();
413-
let cainfo = config.get_path("http.cainfo")?;
414-
let check_revoke = config.get_bool("http.check-revoke")?;
415-
let user_agent = config.get_string("http.user-agent")?;
416-
let ssl_version = config.get::<Option<SslVersionConfig>>("http.ssl-version")?;
417-
418-
Ok(proxy_exists
419-
|| timeout
420-
|| cainfo.is_some()
421-
|| check_revoke.is_some()
422-
|| user_agent.is_some()
423-
|| ssl_version.is_some())
411+
Ok(http_proxy_exists(config)?
412+
|| *config.http_config()? != Default::default()
413+
|| env::var_os("HTTP_TIMEOUT").is_some())
424414
}
425415

426416
/// Configure a libcurl http handle with the defaults options for Cargo
427417
pub fn configure_http_handle(config: &Config, handle: &mut Easy) -> CargoResult<HttpTimeout> {
418+
let http = config.http_config()?;
428419
if let Some(proxy) = http_proxy(config)? {
429420
handle.proxy(&proxy)?;
430421
}
431-
if let Some(cainfo) = config.get_path("http.cainfo")? {
432-
handle.cainfo(&cainfo.val)?;
422+
if let Some(cainfo) = &http.cainfo {
423+
let cainfo = cainfo.resolve_path(config);
424+
handle.cainfo(&cainfo)?;
433425
}
434-
if let Some(check) = config.get_bool("http.check-revoke")? {
435-
handle.ssl_options(SslOpt::new().no_revoke(!check.val))?;
426+
if let Some(check) = http.check_revoke {
427+
handle.ssl_options(SslOpt::new().no_revoke(!check))?;
436428
}
437-
if let Some(user_agent) = config.get_string("http.user-agent")? {
438-
handle.useragent(&user_agent.val)?;
429+
if let Some(user_agent) = &http.user_agent {
430+
handle.useragent(user_agent)?;
439431
} else {
440432
handle.useragent(&version().to_string())?;
441433
}
@@ -456,23 +448,25 @@ pub fn configure_http_handle(config: &Config, handle: &mut Easy) -> CargoResult<
456448
};
457449
Ok(version)
458450
}
459-
if let Some(ssl_version) = config.get::<Option<SslVersionConfig>>("http.ssl-version")? {
451+
if let Some(ssl_version) = &http.ssl_version {
460452
match ssl_version {
461453
SslVersionConfig::Single(s) => {
462454
let version = to_ssl_version(s.as_str())?;
463455
handle.ssl_version(version)?;
464456
}
465457
SslVersionConfig::Range(SslVersionConfigRange { min, max }) => {
466-
let min_version =
467-
min.map_or(Ok(SslVersion::Default), |s| to_ssl_version(s.as_str()))?;
468-
let max_version =
469-
max.map_or(Ok(SslVersion::Default), |s| to_ssl_version(s.as_str()))?;
458+
let min_version = min
459+
.as_ref()
460+
.map_or(Ok(SslVersion::Default), |s| to_ssl_version(s))?;
461+
let max_version = max
462+
.as_ref()
463+
.map_or(Ok(SslVersion::Default), |s| to_ssl_version(s))?;
470464
handle.ssl_min_max_version(min_version, max_version)?;
471465
}
472466
}
473467
}
474468

475-
if let Some(true) = config.get::<Option<bool>>("http.debug")? {
469+
if let Some(true) = http.debug {
476470
handle.verbose(true)?;
477471
handle.debug_function(|kind, data| {
478472
let (prefix, level) = match kind {
@@ -513,11 +507,10 @@ pub struct HttpTimeout {
513507

514508
impl HttpTimeout {
515509
pub fn new(config: &Config) -> CargoResult<HttpTimeout> {
516-
let low_speed_limit = config
517-
.get::<Option<u32>>("http.low-speed-limit")?
518-
.unwrap_or(10);
510+
let config = config.http_config()?;
511+
let low_speed_limit = config.low_speed_limit.unwrap_or(10);
519512
let seconds = config
520-
.get::<Option<u64>>("http.timeout")?
513+
.timeout
521514
.or_else(|| env::var("HTTP_TIMEOUT").ok().and_then(|s| s.parse().ok()))
522515
.unwrap_or(30);
523516
Ok(HttpTimeout {
@@ -526,10 +519,6 @@ impl HttpTimeout {
526519
})
527520
}
528521

529-
fn is_non_default(&self) -> bool {
530-
self.dur != Duration::new(30, 0) || self.low_speed_limit != 10
531-
}
532-
533522
pub fn configure(&self, handle: &mut Easy) -> CargoResult<()> {
534523
// The timeout option for libcurl by default times out the entire
535524
// transfer, but we probably don't want this. Instead we only set
@@ -548,8 +537,9 @@ impl HttpTimeout {
548537
/// Favor cargo's `http.proxy`, then git's `http.proxy`. Proxies specified
549538
/// via environment variables are picked up by libcurl.
550539
fn http_proxy(config: &Config) -> CargoResult<Option<String>> {
551-
if let Some(s) = config.get_string("http.proxy")? {
552-
return Ok(Some(s.val));
540+
let http = config.http_config()?;
541+
if let Some(s) = &http.proxy {
542+
return Ok(Some(s.clone()));
553543
}
554544
if let Ok(cfg) = git2::Config::open_default() {
555545
if let Ok(s) = cfg.get_str("http.proxy") {

‎src/cargo/sources/git/utils.rs

+2-4
Original file line numberDiff line numberDiff line change
@@ -707,10 +707,8 @@ pub fn fetch(
707707
// repositories instead of `libgit2`-the-library. This should make more
708708
// flavors of authentication possible while also still giving us all the
709709
// speed and portability of using `libgit2`.
710-
if let Some(val) = config.get_bool("net.git-fetch-with-cli")? {
711-
if val.val {
712-
return fetch_with_cli(repo, url, refspec, config);
713-
}
710+
if let Some(true) = config.net_config()?.git_fetch_with_cli {
711+
return fetch_with_cli(repo, url, refspec, config);
714712
}
715713

716714
debug!("doing a fetch for {}", url);

‎src/cargo/util/config/de.rs

+461
Large diffs are not rendered by default.

‎src/cargo/util/config/key.rs

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
use std::fmt;
2+
3+
/// Key for a configuration variable.
4+
///
5+
/// This type represents a configuration variable that we're looking up in
6+
/// Cargo's configuration. This structure simultaneously keeps track of a
7+
/// corresponding environment variable name as well as a TOML config name. The
8+
/// intention here is that this is built up and torn down over time efficiently,
9+
/// avoiding clones and such as possible.
10+
#[derive(Debug, Clone)]
11+
pub struct ConfigKey {
12+
// The current environment variable this configuration key maps to. This is
13+
// updated with `push` methods and looks like `CARGO_FOO_BAR` for pushing
14+
// `foo` and then `bar`.
15+
env: String,
16+
// The current toml key this configuration key maps to. This is
17+
// updated with `push` methods and looks like `foo.bar` for pushing
18+
// `foo` and then `bar`.
19+
config: String,
20+
// This is used to keep track of how many sub-keys have been pushed on this
21+
// `ConfigKey`. Each element of this vector is a new sub-key pushed onto
22+
// this `ConfigKey`. Each element is a pair of `usize` where the first item
23+
// is an index into `env` and the second item is an index into `config`.
24+
// These indices are used on `pop` to truncate `env` and `config` to rewind
25+
// back to the previous `ConfigKey` state before a `push`.
26+
parts: Vec<(usize, usize)>,
27+
}
28+
29+
impl ConfigKey {
30+
/// Creates a new blank configuration key which is ready to get built up by
31+
/// using `push` and `push_sensitive`.
32+
pub fn new() -> ConfigKey {
33+
ConfigKey {
34+
env: "CARGO".to_string(),
35+
config: String::new(),
36+
parts: Vec::new(),
37+
}
38+
}
39+
40+
/// Creates a `ConfigKey` from the `key` specified.
41+
///
42+
/// The `key` specified is expected to be a period-separated toml
43+
/// configuration key.
44+
pub fn from_str(key: &str) -> ConfigKey {
45+
let mut cfg = ConfigKey::new();
46+
for part in key.split('.') {
47+
cfg.push(part);
48+
}
49+
return cfg;
50+
}
51+
52+
/// Pushes a new sub-key on this `ConfigKey`. This sub-key should be
53+
/// equivalent to accessing a sub-table in TOML.
54+
///
55+
/// Note that this considers `name` to be case-insensitive, meaning that the
56+
/// corrseponding toml key is appended with this `name` as-is and the
57+
/// corresponding env key is appended with `name` after transforming it to
58+
/// uppercase characters.
59+
pub fn push(&mut self, name: &str) {
60+
let env = name.replace("-", "_").to_uppercase();
61+
self._push(&env, name);
62+
}
63+
64+
/// Performs the same function as `push` except that the corresponding
65+
/// environment variable does not get the uppercase letters of `name` but
66+
/// instead `name` is pushed raw onto the corresponding environment
67+
/// variable.
68+
pub fn push_sensitive(&mut self, name: &str) {
69+
self._push(name, name);
70+
}
71+
72+
fn _push(&mut self, env: &str, config: &str) {
73+
self.parts.push((self.env.len(), self.config.len()));
74+
75+
self.env.push_str("_");
76+
self.env.push_str(env);
77+
78+
if !self.config.is_empty() {
79+
self.config.push_str(".");
80+
}
81+
self.config.push_str(config);
82+
}
83+
84+
/// Rewinds this `ConfigKey` back to the state it was at before the last
85+
/// `push` method being called.
86+
pub fn pop(&mut self) {
87+
let (env, config) = self.parts.pop().unwrap();
88+
self.env.truncate(env);
89+
self.config.truncate(config);
90+
}
91+
92+
/// Returns the corresponding environment variable key for this
93+
/// configuration value.
94+
pub fn as_env_key(&self) -> &str {
95+
&self.env
96+
}
97+
98+
/// Returns the corresponding TOML (period-separated) key for this
99+
/// configuration value.
100+
pub fn as_config_key(&self) -> &str {
101+
&self.config
102+
}
103+
}
104+
105+
impl fmt::Display for ConfigKey {
106+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107+
self.as_config_key().fmt(f)
108+
}
109+
}

‎src/cargo/util/config.rs ‎src/cargo/util/config/mod.rs

+170-545
Large diffs are not rendered by default.

‎src/cargo/util/config/path.rs

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use crate::util::config::{Config, Value};
2+
use serde::Deserialize;
3+
use std::path::PathBuf;
4+
5+
/// Use with the `get` API to fetch a string that will be converted to a
6+
/// `PathBuf`. Relative paths are converted to absolute paths based on the
7+
/// location of the config file.
8+
#[derive(Debug, Deserialize, PartialEq, Clone)]
9+
#[serde(transparent)]
10+
pub struct ConfigRelativePath(Value<String>);
11+
12+
impl ConfigRelativePath {
13+
/// Returns the raw underlying configuration value for this key.
14+
pub fn raw_value(&self) -> &str {
15+
&self.0.val
16+
}
17+
18+
/// Resolves this configuration-relative path to an absolute path.
19+
///
20+
/// This will always return an absolute path where it's relative to the
21+
/// location for configuration for this value.
22+
pub fn resolve_path(&self, config: &Config) -> PathBuf {
23+
self.0.definition.root(config).join(&self.0.val)
24+
}
25+
26+
/// Resolves this configuration-relative path to either an absolute path or
27+
/// something appropriate to execute from `PATH`.
28+
///
29+
/// Values which don't look like a filesystem path (don't contain `/` or
30+
/// `\`) will be returned as-is, and everything else will fall through to an
31+
/// absolute path.
32+
pub fn resolve_program(self, config: &Config) -> PathBuf {
33+
config.string_to_path(self.0.val, &self.0.definition)
34+
}
35+
}

‎src/cargo/util/config/value.rs

+202
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
//! Deserialization of a `Value<T>` type which tracks where it was deserialized
2+
//! from.
3+
//!
4+
//! Often Cargo wants to report semantic error information or other sorts of
5+
//! error information about configuration keys but it also may wish to indicate
6+
//! as an error context where the key was defined as well (to help user
7+
//! debugging). The `Value<T>` type here can be used to deserialize a `T` value
8+
//! from configuration, but also record where it was deserialized from when it
9+
//! was read.
10+
11+
use crate::util::config::Config;
12+
use serde::de;
13+
use std::fmt;
14+
use std::marker;
15+
use std::mem;
16+
use std::path::{Path, PathBuf};
17+
18+
/// A type which can be deserialized as a configuration value which records
19+
/// where it was deserialized from.
20+
#[derive(Debug, PartialEq, Clone)]
21+
pub struct Value<T> {
22+
/// The inner value that was deserialized.
23+
pub val: T,
24+
/// The location where `val` was defined in configuration (e.g. file it was
25+
/// defined in, env var etc).
26+
pub definition: Definition,
27+
}
28+
29+
pub type OptValue<T> = Option<Value<T>>;
30+
31+
// Deserializing `Value<T>` is pretty special, and serde doesn't have built-in
32+
// support for this operation. To implement this we extend serde's "data model"
33+
// a bit. We configure deserialization of `Value<T>` to basically only work with
34+
// our one deserializer using configuration.
35+
//
36+
// We define that `Value<T>` deserialization asks the deserializer for a very
37+
// special struct name and struct field names. In doing so the deserializer will
38+
// recognize this and synthesize a magical value for the `definition` field when
39+
// we deserialize it. This protocol is how we're able to have a channel of
40+
// information flowing from the configuration deserializer into the
41+
// deserialization implementation here.
42+
//
43+
// You'll want to also check out the implementation of `ValueDeserializer` in
44+
// `de.rs`. Also note that the names below are intended to be invalid Rust
45+
// identifiers to avoid how they might conflict with other valid structures.
46+
// Finally the `definition` field is transmitted as a tuple of i32/string, which
47+
// is effectively a tagged union of `Definition` itself.
48+
49+
pub(crate) const VALUE_FIELD: &str = "$__cargo_private_value";
50+
pub(crate) const DEFINITION_FIELD: &str = "$__cargo_private_definition";
51+
pub(crate) const NAME: &str = "$__cargo_private_Value";
52+
pub(crate) static FIELDS: [&str; 2] = [VALUE_FIELD, DEFINITION_FIELD];
53+
54+
#[derive(Clone, Debug)]
55+
pub enum Definition {
56+
Path(PathBuf),
57+
Environment(String),
58+
}
59+
60+
impl Definition {
61+
pub fn root<'a>(&'a self, config: &'a Config) -> &'a Path {
62+
match self {
63+
Definition::Path(p) => p.parent().unwrap().parent().unwrap(),
64+
Definition::Environment(_) => config.cwd(),
65+
}
66+
}
67+
}
68+
69+
impl PartialEq for Definition {
70+
fn eq(&self, other: &Definition) -> bool {
71+
// configuration values are equivalent no matter where they're defined,
72+
// but they need to be defined in the same location. For example if
73+
// they're defined in the environment that's different than being
74+
// defined in a file due to path interepretations.
75+
mem::discriminant(self) == mem::discriminant(other)
76+
}
77+
}
78+
79+
impl fmt::Display for Definition {
80+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81+
match self {
82+
Definition::Path(p) => p.display().fmt(f),
83+
Definition::Environment(key) => write!(f, "environment variable `{}`", key),
84+
}
85+
}
86+
}
87+
88+
impl<'de, T> de::Deserialize<'de> for Value<T>
89+
where
90+
T: de::Deserialize<'de>,
91+
{
92+
fn deserialize<D>(deserializer: D) -> Result<Value<T>, D::Error>
93+
where
94+
D: de::Deserializer<'de>,
95+
{
96+
struct ValueVisitor<T> {
97+
_marker: marker::PhantomData<T>,
98+
}
99+
100+
impl<'de, T> de::Visitor<'de> for ValueVisitor<T>
101+
where
102+
T: de::Deserialize<'de>,
103+
{
104+
type Value = Value<T>;
105+
106+
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
107+
formatter.write_str("a value")
108+
}
109+
110+
fn visit_map<V>(self, mut visitor: V) -> Result<Value<T>, V::Error>
111+
where
112+
V: de::MapAccess<'de>,
113+
{
114+
let value = visitor.next_key::<ValueKey>()?;
115+
if value.is_none() {
116+
return Err(de::Error::custom("value not found"));
117+
}
118+
let val: T = visitor.next_value()?;
119+
120+
let definition = visitor.next_key::<DefinitionKey>()?;
121+
if definition.is_none() {
122+
return Err(de::Error::custom("definition not found"));
123+
}
124+
let definition: Definition = visitor.next_value()?;
125+
Ok(Value { val, definition })
126+
}
127+
}
128+
129+
deserializer.deserialize_struct(
130+
NAME,
131+
&FIELDS,
132+
ValueVisitor {
133+
_marker: marker::PhantomData,
134+
},
135+
)
136+
}
137+
}
138+
139+
struct FieldVisitor {
140+
expected: &'static str,
141+
}
142+
143+
impl<'de> de::Visitor<'de> for FieldVisitor {
144+
type Value = ();
145+
146+
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
147+
formatter.write_str("a valid value field")
148+
}
149+
150+
fn visit_str<E>(self, s: &str) -> Result<(), E>
151+
where
152+
E: de::Error,
153+
{
154+
if s == self.expected {
155+
Ok(())
156+
} else {
157+
Err(de::Error::custom("expected field with custom name"))
158+
}
159+
}
160+
}
161+
162+
struct ValueKey;
163+
164+
impl<'de> de::Deserialize<'de> for ValueKey {
165+
fn deserialize<D>(deserializer: D) -> Result<ValueKey, D::Error>
166+
where
167+
D: de::Deserializer<'de>,
168+
{
169+
deserializer.deserialize_identifier(FieldVisitor {
170+
expected: VALUE_FIELD,
171+
})?;
172+
Ok(ValueKey)
173+
}
174+
}
175+
176+
struct DefinitionKey;
177+
178+
impl<'de> de::Deserialize<'de> for DefinitionKey {
179+
fn deserialize<D>(deserializer: D) -> Result<DefinitionKey, D::Error>
180+
where
181+
D: de::Deserializer<'de>,
182+
{
183+
deserializer.deserialize_identifier(FieldVisitor {
184+
expected: DEFINITION_FIELD,
185+
})?;
186+
Ok(DefinitionKey)
187+
}
188+
}
189+
190+
impl<'de> de::Deserialize<'de> for Definition {
191+
fn deserialize<D>(deserializer: D) -> Result<Definition, D::Error>
192+
where
193+
D: de::Deserializer<'de>,
194+
{
195+
let (discr, value) = <(u32, String)>::deserialize(deserializer)?;
196+
if discr == 0 {
197+
Ok(Definition::Path(value.into()))
198+
} else {
199+
Ok(Definition::Environment(value))
200+
}
201+
}
202+
}

‎src/cargo/util/errors.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -394,5 +394,5 @@ pub fn display_causes(error: &Error) -> String {
394394
.iter_chain()
395395
.map(|e| e.to_string())
396396
.collect::<Vec<_>>()
397-
.join("\nCaused by:\n ")
397+
.join("\n\nCaused by:\n ")
398398
}

‎src/cargo/util/network.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ impl<'a> Retry<'a> {
1515
pub fn new(config: &'a Config) -> CargoResult<Retry<'a>> {
1616
Ok(Retry {
1717
config,
18-
remaining: config.get::<Option<u32>>("net.retry")?.unwrap_or(2),
18+
remaining: config.net_config()?.retry.unwrap_or(2),
1919
})
2020
}
2121

‎tests/testsuite/bad_config.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ fn bad1() {
1717
.with_status(101)
1818
.with_stderr(
1919
"\
20-
[ERROR] expected table for configuration key `target.nonexistent-target`, \
21-
but found string in [..]config
20+
[ERROR] invalid configuration for key `target.nonexistent-target`
21+
expected a table, but found a string for `[..]` in [..]config
2222
",
2323
)
2424
.run();

‎tests/testsuite/config.rs

+10-6
Original file line numberDiff line numberDiff line change
@@ -560,10 +560,13 @@ fn config_bad_toml() {
560560
config.get::<i32>("foo").unwrap_err(),
561561
"\
562562
could not load Cargo configuration
563+
563564
Caused by:
564565
could not parse TOML configuration in `[..]/.cargo/config`
566+
565567
Caused by:
566568
could not parse input as TOML
569+
567570
Caused by:
568571
expected an equals, found eof at line 1 column 5",
569572
);
@@ -643,7 +646,7 @@ expected a list, but found a integer for `l3` in [..]/.cargo/config",
643646
assert_error(
644647
config.get::<L>("bad-env").unwrap_err(),
645648
"error in environment variable `CARGO_BAD_ENV`: \
646-
could not parse TOML list: invalid number at line 1 column 10",
649+
could not parse TOML list: invalid number at line 1 column 8",
647650
);
648651

649652
// Try some other sequence-like types.
@@ -706,6 +709,7 @@ ns2 = 456
706709
let config = new_config(&[("CARGO_NSE", "987"), ("CARGO_NS2", "654")]);
707710

708711
#[derive(Debug, Deserialize, Eq, PartialEq)]
712+
#[serde(transparent)]
709713
struct NewS(i32);
710714
assert_eq!(config.get::<NewS>("ns").unwrap(), NewS(123));
711715
assert_eq!(config.get::<NewS>("ns2").unwrap(), NewS(654));
@@ -734,35 +738,35 @@ abs = '{}'
734738
config
735739
.get::<config::ConfigRelativePath>("p1")
736740
.unwrap()
737-
.path(),
741+
.resolve_path(&config),
738742
paths::root().join("foo/bar")
739743
);
740744
assert_eq!(
741745
config
742746
.get::<config::ConfigRelativePath>("p2")
743747
.unwrap()
744-
.path(),
748+
.resolve_path(&config),
745749
paths::root().join("../abc")
746750
);
747751
assert_eq!(
748752
config
749753
.get::<config::ConfigRelativePath>("p3")
750754
.unwrap()
751-
.path(),
755+
.resolve_path(&config),
752756
paths::root().join("d/e")
753757
);
754758
assert_eq!(
755759
config
756760
.get::<config::ConfigRelativePath>("abs")
757761
.unwrap()
758-
.path(),
762+
.resolve_path(&config),
759763
paths::home()
760764
);
761765
assert_eq!(
762766
config
763767
.get::<config::ConfigRelativePath>("epath")
764768
.unwrap()
765-
.path(),
769+
.resolve_path(&config),
766770
paths::root().join("a/b")
767771
);
768772
}

‎tests/testsuite/workspaces.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1061,8 +1061,10 @@ fn new_warning_with_corrupt_ws() {
10611061
[WARNING] compiling this new crate may not work due to invalid workspace configuration
10621062
10631063
failed to parse manifest at `[..]foo/Cargo.toml`
1064+
10641065
Caused by:
10651066
could not parse input as TOML
1067+
10661068
Caused by:
10671069
expected an equals, found eof at line 1 column 5
10681070
Created binary (application) `bar` package

0 commit comments

Comments
 (0)
Please sign in to comment.