Skip to content

Commit 94bf478

Browse files
committed
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

Lines changed: 1 addition & 3 deletions
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

Lines changed: 7 additions & 7 deletions
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

Lines changed: 13 additions & 10 deletions
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

Lines changed: 1 addition & 4 deletions
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

Lines changed: 2 additions & 2 deletions
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

Lines changed: 1 addition & 3 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 37 additions & 36 deletions
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

Lines changed: 25 additions & 35 deletions
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

Lines changed: 2 additions & 4 deletions
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);

0 commit comments

Comments
 (0)