diff --git a/src/cargo/core/profiles.rs b/src/cargo/core/profiles.rs index c9feafaed7f..24fa37d3b6a 100644 --- a/src/cargo/core/profiles.rs +++ b/src/cargo/core/profiles.rs @@ -1,13 +1,13 @@ -use std::collections::{HashMap, HashSet}; -use std::sync::atomic; +use std::collections::HashSet; use std::{cmp, fmt, hash}; use core::compiler::CompileMode; use core::interning::InternedString; use core::{Features, PackageId, PackageIdSpec, PackageSet, Shell}; +use util::errors::CargoResultExt; use util::lev_distance::lev_distance; use util::toml::{ProfilePackageSpec, StringOrBool, TomlProfile, TomlProfiles, U32OrBool}; -use util::{CargoResult, Config, ConfigValue}; +use util::{CargoResult, Config}; /// Collection of all user profiles. #[derive(Clone, Debug)] @@ -29,18 +29,20 @@ impl Profiles { if let Some(profiles) = profiles { profiles.validate(features, warnings)?; } - Profiles::validate_config(config, warnings)?; + + let config_profiles = config.profiles()?; + config_profiles.validate(features, warnings)?; Ok(Profiles { dev: ProfileMaker { default: Profile::default_dev(), toml: profiles.and_then(|p| p.dev.clone()), - config: TomlProfile::from_config(config, "dev", warnings)?, + config: config_profiles.dev.clone(), }, release: ProfileMaker { default: Profile::default_release(), toml: profiles.and_then(|p| p.release.clone()), - config: TomlProfile::from_config(config, "release", warnings)?, + config: config_profiles.release.clone(), }, test: ProfileMaker { default: Profile::default_test(), @@ -137,71 +139,6 @@ impl Profiles { self.doc.validate_packages(shell, packages)?; Ok(()) } - - fn validate_config(config: &Config, warnings: &mut Vec) -> CargoResult<()> { - static VALIDATE_ONCE: atomic::AtomicBool = atomic::ATOMIC_BOOL_INIT; - - if VALIDATE_ONCE.swap(true, atomic::Ordering::SeqCst) { - return Ok(()); - } - - // cv: Value> - if let Some(cv) = config.get_table("profile")? { - // Warn if config profiles without CLI option. - if !config.cli_unstable().config_profile { - warnings.push(format!( - "profile in config `{}` requires `-Z config-profile` command-line option", - cv.definition - )); - // Ignore the rest. - return Ok(()); - } - // Warn about unsupported profile names. - for (key, profile_cv) in cv.val.iter() { - if key != "dev" && key != "release" { - warnings.push(format!( - "profile `{}` in config `{}` is not supported", - key, - profile_cv.definition_path().display() - )); - } - } - // Warn about incorrect key names. - for profile_cv in cv.val.values() { - if let ConfigValue::Table(ref profile, _) = *profile_cv { - validate_profile_keys(profile, warnings); - if let Some(&ConfigValue::Table(ref bo_profile, _)) = - profile.get("build-override") - { - validate_profile_keys(bo_profile, warnings); - } - if let Some(&ConfigValue::Table(ref os, _)) = profile.get("overrides") { - for o_profile_cv in os.values() { - if let ConfigValue::Table(ref o_profile, _) = *o_profile_cv { - validate_profile_keys(o_profile, warnings); - } - } - } - } - } - } - return Ok(()); - - fn validate_profile_keys( - profile: &HashMap, - warnings: &mut Vec, - ) { - for (key, value_cv) in profile.iter() { - if !TOML_PROFILE_KEYS.iter().any(|k| k == key) { - warnings.push(format!( - "unused profile key `{}` in config `{}`", - key, - value_cv.definition_path().display() - )); - } - } - } - } } /// An object used for handling the profile override hierarchy. @@ -449,20 +386,6 @@ pub struct Profile { pub panic: Option, } -const TOML_PROFILE_KEYS: [&str; 11] = [ - "opt-level", - "lto", - "codegen-units", - "debug", - "debug-assertions", - "rpath", - "panic", - "overflow-checks", - "incremental", - "overrides", - "build-override", -]; - impl Default for Profile { fn default() -> Profile { Profile { @@ -605,3 +528,26 @@ impl ProfileFor { &ALL } } + +/// Profiles loaded from .cargo/config files. +#[derive(Clone, Debug, Deserialize, Default)] +pub struct ConfigProfiles { + dev: Option, + release: Option, +} + +impl ConfigProfiles { + pub fn validate(&self, features: &Features, warnings: &mut Vec) -> CargoResult<()> { + if let Some(ref profile) = self.dev { + profile + .validate("dev", features, warnings) + .chain_err(|| format_err!("config profile `profile.dev` is not valid"))?; + } + if let Some(ref profile) = self.release { + profile + .validate("release", features, warnings) + .chain_err(|| format_err!("config profile `profile.release` is not valid"))?; + } + Ok(()) + } +} diff --git a/src/cargo/util/config.rs b/src/cargo/util/config.rs index e4b93828f15..6af6dc91919 100644 --- a/src/cargo/util/config.rs +++ b/src/cargo/util/config.rs @@ -22,6 +22,7 @@ use lazycell::LazyCell; use serde::{de, de::IntoDeserializer, Serialize, Serializer}; use toml; +use core::profiles::ConfigProfiles; use core::shell::Verbosity; use core::{CliUnstable, Shell, SourceId, Workspace}; use ops; @@ -77,6 +78,8 @@ pub struct Config { target_dir: Option, /// Environment variables, separated to assist testing. env: HashMap, + /// Profiles loaded from config. + profiles: LazyCell, } impl Config { @@ -132,6 +135,7 @@ impl Config { creation_time: Instant::now(), target_dir: None, env, + profiles: LazyCell::new(), } } @@ -246,6 +250,25 @@ impl Config { .map(AsRef::as_ref) } + pub fn profiles(&self) -> CargoResult<&ConfigProfiles> { + self.profiles.try_borrow_with(|| { + let ocp = self.get::>("profile")?; + if let Some(config_profiles) = ocp { + // Warn if config profiles without CLI option. + if !self.cli_unstable().config_profile { + self.shell().warn( + "profiles in config files require `-Z config-profile` \ + command-line option", + )?; + return Ok(ConfigProfiles::default()); + } + Ok(config_profiles) + } else { + Ok(ConfigProfiles::default()) + } + }) + } + pub fn values(&self) -> CargoResult<&HashMap> { self.values.try_borrow_with(|| self.load_values()) } @@ -1380,7 +1403,7 @@ impl ConfigValue { } } - pub fn into_toml(self) -> toml::Value { + fn into_toml(self) -> toml::Value { match self { CV::Boolean(s, _) => toml::Value::Boolean(s), CV::String(s, _) => toml::Value::String(s), @@ -1425,6 +1448,7 @@ impl ConfigValue { }; } } + // Allow switching types except for tables or arrays. (expected @ &mut CV::List(_, _), found) | (expected @ &mut CV::Table(_, _), found) | (expected, found @ CV::List(_, _)) diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index ac2a452c194..0d4cd89d96a 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -5,7 +5,6 @@ use std::path::{Path, PathBuf}; use std::rc::Rc; use std::str; -use failure::Error; use semver::{self, VersionReq}; use serde::de::{self, Deserialize}; use serde::ser; @@ -22,7 +21,7 @@ use core::{GitReference, PackageIdSpec, SourceId, WorkspaceConfig, WorkspaceRoot use sources::CRATES_IO; use util::errors::{CargoError, CargoResult, CargoResultExt}; use util::paths; -use util::{self, Config, ConfigValue, ToUrl}; +use util::{self, Config, ToUrl}; mod targets; use self::targets::targets; @@ -248,19 +247,19 @@ pub struct TomlProfiles { impl TomlProfiles { pub fn validate(&self, features: &Features, warnings: &mut Vec) -> CargoResult<()> { if let Some(ref test) = self.test { - test.validate("test", Some(features), warnings)?; + test.validate("test", features, warnings)?; } if let Some(ref doc) = self.doc { - doc.validate("doc", Some(features), warnings)?; + doc.validate("doc", features, warnings)?; } if let Some(ref bench) = self.bench { - bench.validate("bench", Some(features), warnings)?; + bench.validate("bench", features, warnings)?; } if let Some(ref dev) = self.dev { - dev.validate("dev", Some(features), warnings)?; + dev.validate("dev", features, warnings)?; } if let Some(ref release) = self.release { - release.validate("release", Some(features), warnings)?; + release.validate("release", features, warnings)?; } Ok(()) } @@ -420,71 +419,18 @@ impl<'de> de::Deserialize<'de> for ProfilePackageSpec { } impl TomlProfile { - pub fn from_config( - config: &Config, - name: &str, - warnings: &mut Vec, - ) -> CargoResult> { - if !config.cli_unstable().config_profile { - return Ok(None); - } - if let Some(util::config::Value { val, .. }) = - config.get_table(&format!("profile.{}", name))? - { - let cv = ConfigValue::Table(val.clone(), PathBuf::new()); - let toml = cv.into_toml(); - let profile: TomlProfile = - Deserialize::deserialize(toml).chain_err(|| error_path(&val))?; - profile - .validate(name, None, warnings) - .chain_err(|| error_path(&val))?; - return Ok(Some(profile)); - } - return Ok(None); - - fn error_path(table: &HashMap) -> Error { - let mut paths = HashSet::new(); - error_path_rec(table, &mut paths); - if paths.len() == 1 { - format_err!( - "error in config profile `{}`", - paths.into_iter().next().unwrap() - ) - } else { - let mut ps = paths.into_iter().collect::>(); - ps.sort(); // to help with testing - format_err!( - "error in config profile, possible locations: {}", - ps.join(", ") - ) - } - } - fn error_path_rec(table: &HashMap, paths: &mut HashSet) { - for cv in table.values() { - paths.insert(cv.definition_path().display().to_string()); - if let &ConfigValue::Table(ref t, _) = cv { - error_path_rec(t, paths); - } - } - } - } - pub fn validate( &self, name: &str, - features: Option<&Features>, + features: &Features, warnings: &mut Vec, ) -> CargoResult<()> { if let Some(ref profile) = self.build_override { - if let Some(features) = features { - features.require(Feature::profile_overrides())?; - } + features.require(Feature::profile_overrides())?; profile.validate_override()?; } if let Some(ref override_map) = self.overrides { - if let Some(features) = features { - features.require(Feature::profile_overrides())?; - } + features.require(Feature::profile_overrides())?; for profile in override_map.values() { profile.validate_override()?; } diff --git a/tests/testsuite/profile_config.rs b/tests/testsuite/profile_config.rs index 2644d85d712..73613be9e57 100644 --- a/tests/testsuite/profile_config.rs +++ b/tests/testsuite/profile_config.rs @@ -18,18 +18,30 @@ fn profile_config_gated() { assert_that( p.cargo("build -v"), - execs().with_status(0).with_stderr_contains( - "\ -[WARNING] profile in config `[..]foo[/].cargo[/]config` requires `-Z config-profile` command-line option + execs() + .with_status(0) + .with_stderr_contains( + "\ +[WARNING] profiles in config files require `-Z config-profile` command-line option ", - ).with_stderr_contains("[..]-C debuginfo=2[..]"), + ) + .with_stderr_contains("[..]-C debuginfo=2[..]"), ); } #[test] fn profile_config_validate_warnings() { let p = project("foo") - .file("Cargo.toml", &basic_lib_manifest("foo")) + .file( + "Cargo.toml", + r#" + cargo-features = ["profile-overrides"] + + [package] + name = "foo" + version = "0.0.1" + "#, + ) .file("src/lib.rs", "") .file( ".cargo/config", @@ -53,34 +65,24 @@ fn profile_config_validate_warnings() { .build(); assert_that( - p.cargo("build -v -Z config-profile") + p.cargo("build -Z config-profile") .masquerade_as_nightly_cargo(), - execs() - .with_status(0) - .with_stderr_contains( - "\ -[WARNING] profile `test` in config `[..]foo[/].cargo[/]config` is not supported -", - ) - .with_stderr_contains( - "\ -[WARNING] profile `asdf` in config `[..]foo[/].cargo[/]config` is not supported -", - ) - .with_stderr_contains( - "\ -[WARNING] unused profile key `bad-key` in config `[..]foo[/].cargo[/]config` -[WARNING] unused profile key `bad-key-bo` in config `[..]foo[/].cargo[/]config` -[WARNING] unused profile key `bad-key-bar` in config `[..]foo[/].cargo[/]config` + execs().with_status(0).with_stderr_unordered( + "\ +[WARNING] unused key `profile.asdf` in config file `[..].cargo[/]config` +[WARNING] unused key `profile.test` in config file `[..].cargo[/]config` +[WARNING] unused key `profile.dev.bad-key` in config file `[..].cargo[/]config` +[WARNING] unused key `profile.dev.overrides.bar.bad-key-bar` in config file `[..].cargo[/]config` +[WARNING] unused key `profile.dev.build-override.bad-key-bo` in config file `[..].cargo[/]config` +[COMPILING] foo [..] +[FINISHED] [..] ", - ), + ), ); } #[test] fn profile_config_error_paths() { - // Due to how it's implemented, we are uncertain where a merged error - // comes from. let p = project("foo") .file("Cargo.toml", &basic_lib_manifest("foo")) .file("src/lib.rs", "") @@ -108,10 +110,7 @@ fn profile_config_error_paths() { [ERROR] failed to parse manifest at `[..]foo[/]Cargo.toml` Caused by: - error in config profile, possible locations: [..]foo[/].cargo[/]config, [..]home[/].cargo[/]config - -Caused by: - invalid type: string \"foo\", expected a boolean for key `rpath` + error in [..].cargo[/]config: `profile.dev.rpath` expected true/false, but found a string ", ), ); @@ -120,7 +119,16 @@ Caused by: #[test] fn profile_config_validate_errors() { let p = project("foo") - .file("Cargo.toml", &basic_lib_manifest("foo")) + .file( + "Cargo.toml", + r#" + cargo-features = ["profile-overrides"] + + [package] + name = "foo" + version = "0.0.1" + "#, + ) .file("src/lib.rs", "") .file( ".cargo/config", @@ -139,7 +147,7 @@ fn profile_config_validate_errors() { [ERROR] failed to parse manifest at `[..]foo[/]Cargo.toml` Caused by: - error in config profile `[..]foo[/].cargo[/]config` + config profile `profile.dev` is not valid Caused by: `panic` may not be specified in a profile override. @@ -170,10 +178,7 @@ fn profile_config_syntax_errors() { [ERROR] failed to parse manifest at [..] Caused by: - error in config profile `[..]foo[/].cargo[/]config` - -Caused by: - invalid type: string \"foo\", expected u32 for key `codegen-units` + error in [..].cargo[/]config: `profile.dev.codegen-units` expected an integer, but found a string ", ), ); @@ -206,7 +211,16 @@ fn profile_config_override_spec_multiple() { "#, ) .file("src/lib.rs", "") - .file("bar/Cargo.toml", &basic_lib_manifest("bar")) + .file( + "bar/Cargo.toml", + r#" + cargo-features = ["profile-overrides"] + + [package] + name = "bar" + version = "0.5.0" + "#, + ) .file("bar/src/lib.rs", "") .build(); @@ -290,7 +304,16 @@ fn profile_config_override_precedence() { "#, ) .file("src/lib.rs", "") - .file("bar/Cargo.toml", &basic_lib_manifest("bar")) + .file( + "bar/Cargo.toml", + r#" + cargo-features = ["profile-overrides"] + + [package] + name = "bar" + version = "0.0.1" + "#, + ) .file("bar/src/lib.rs", "") .file( ".cargo/config", @@ -318,7 +341,16 @@ fn profile_config_override_precedence() { #[test] fn profile_config_no_warn_unknown_override() { let p = project("foo") - .file("Cargo.toml", &basic_lib_manifest("foo")) + .file( + "Cargo.toml", + r#" + cargo-features = ["profile-overrides"] + + [package] + name = "foo" + version = "0.0.1" + "#, + ) .file("src/lib.rs", "") .file( ".cargo/config",