From d6b26a2dc296912ec167f615421697351f991bb8 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Sun, 20 Jan 2019 13:50:46 -0800 Subject: [PATCH] Add build profile. --- src/cargo/core/features.rs | 3 + src/cargo/core/profiles.rs | 132 +++++--- src/cargo/util/toml/mod.rs | 27 +- src/doc/src/reference/unstable.md | 30 ++ tests/testsuite/build_profile.rs | 481 ++++++++++++++++++++++++++++++ tests/testsuite/main.rs | 1 + 6 files changed, 635 insertions(+), 39 deletions(-) create mode 100644 tests/testsuite/build_profile.rs diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index b62dc50704b..4ed1ac03c0d 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -191,6 +191,9 @@ features! { // Overriding profiles for dependencies. [unstable] profile_overrides: bool, + // Build profile. + [unstable] build_profile: bool, + // Separating the namespaces for features and dependencies [unstable] namespaced_features: bool, diff --git a/src/cargo/core/profiles.rs b/src/cargo/core/profiles.rs index 8cf142981c8..202ca107445 100644 --- a/src/cargo/core/profiles.rs +++ b/src/cargo/core/profiles.rs @@ -1,11 +1,12 @@ use std::collections::HashSet; +use std::path::Path; use std::{cmp, env, fmt, hash}; use serde::Deserialize; use crate::core::compiler::CompileMode; use crate::core::interning::InternedString; -use crate::core::{Features, PackageId, PackageIdSpec, PackageSet, Shell}; +use crate::core::{Feature, Features, PackageId, PackageIdSpec, PackageSet, Shell}; use crate::util::errors::CargoResultExt; use crate::util::lev_distance::lev_distance; use crate::util::toml::{ProfilePackageSpec, StringOrBool, TomlProfile, TomlProfiles, U32OrBool}; @@ -19,10 +20,13 @@ pub struct Profiles { test: ProfileMaker, bench: ProfileMaker, doc: ProfileMaker, + build: ProfileMaker, /// Incremental compilation can be overridden globally via: /// - `CARGO_INCREMENTAL` environment variable. /// - `build.incremental` config value. incremental: Option, + // Temporary flag to detect if unstable feature is enabled. + build_enabled: bool, } impl Profiles { @@ -31,9 +35,13 @@ impl Profiles { config: &Config, features: &Features, warnings: &mut Vec, + manifest_path: &Path, ) -> CargoResult { if let Some(profiles) = profiles { profiles.validate(features, warnings)?; + if profiles.build.is_some() { + features.require(Feature::build_profile())?; + } } let config_profiles = config.profiles()?; @@ -43,6 +51,18 @@ impl Profiles { None => config.get::>("build.incremental")?, }; + let config_build_profile = + if !features.is_enabled(Feature::build_profile()) && config_profiles.build.is_some() { + warnings.push(format!( + "profile `build` in config file will be ignored for \ + manifest `{}` because \"build-profile\" feature is not enabled", + manifest_path.display() + )); + None + } else { + config_profiles.build.clone() + }; + Ok(Profiles { dev: ProfileMaker { default: Profile::default_dev(), @@ -69,7 +89,13 @@ impl Profiles { toml: profiles.and_then(|p| p.doc.clone()), config: None, }, + build: ProfileMaker { + default: Profile::default_build(), + toml: profiles.and_then(|p| p.build.clone()), + config: config_build_profile, + }, incremental, + build_enabled: features.is_enabled(Feature::build_profile()), }) } @@ -100,10 +126,14 @@ impl Profiles { // `build_unit_profiles` normally ensures that it selects the // ancestor's profile. However, `cargo clean -p` can hit this // path. - if release { - &self.release + let maker = if release { &self.release } else { &self.dev }; + if (unit_for.build) + && self.build_enabled + && !maker.has_build_override() + { + &self.build } else { - &self.dev + maker } } CompileMode::Doc { .. } => &self.doc, @@ -148,9 +178,11 @@ impl Profiles { /// select for the package that was actually built. pub fn base_profile(&self, release: bool) -> Profile { if release { - self.release.get_profile(None, true, UnitFor::new_normal()) + self.release + .get_profile(None, true, UnitFor::new_normal()) } else { - self.dev.get_profile(None, true, UnitFor::new_normal()) + self.dev + .get_profile(None, true, UnitFor::new_normal()) } } @@ -165,6 +197,7 @@ impl Profiles { self.test.validate_packages(shell, packages)?; self.bench.validate_packages(shell, packages)?; self.doc.validate_packages(shell, packages)?; + self.build.validate_packages(shell, packages)?; Ok(()) } } @@ -198,10 +231,22 @@ impl ProfileMaker { ) -> Profile { let mut profile = self.default; if let Some(ref toml) = self.toml { - merge_toml(pkg_id, is_member, unit_for, &mut profile, toml); + merge_toml( + pkg_id, + is_member, + unit_for, + &mut profile, + toml, + ); } if let Some(ref toml) = self.config { - merge_toml(pkg_id, is_member, unit_for, &mut profile, toml); + merge_toml( + pkg_id, + is_member, + unit_for, + &mut profile, + toml, + ); } profile } @@ -318,6 +363,13 @@ impl ProfileMaker { } Ok(()) } + + fn has_build_override(&self) -> bool { + self.toml + .as_ref() + .map(|tp| tp.build_override.is_some()) + .unwrap_or(false) + } } fn merge_toml( @@ -449,6 +501,7 @@ compact_debug! { "test" => (Profile::default_test(), "default_test()"), "bench" => (Profile::default_bench(), "default_bench()"), "doc" => (Profile::default_doc(), "default_doc()"), + "build" => (Profile::default_build(), "default_build()"), _ => (Profile::default(), "default()"), }; [debug_the_fields( @@ -529,6 +582,13 @@ impl Profile { } } + fn default_build() -> Profile { + Profile { + name: "build", + ..Profile::default() + } + } + /// Compares all fields except `name`, which doesn't affect compilation. /// This is necessary for `Unit` deduplication for things like "test" and /// "dev" which are essentially the same. @@ -604,7 +664,7 @@ pub struct UnitFor { impl UnitFor { /// A unit for a normal target/dependency (i.e., not custom build, /// proc macro/plugin, or test/bench). - pub fn new_normal() -> UnitFor { + pub const fn new_normal() -> UnitFor { UnitFor { build: false, panic_abort_ok: true, @@ -612,7 +672,7 @@ impl UnitFor { } /// A unit for a custom build script or its dependencies. - pub fn new_build() -> UnitFor { + pub const fn new_build() -> UnitFor { UnitFor { build: true, panic_abort_ok: false, @@ -620,15 +680,17 @@ impl UnitFor { } /// A unit for a proc macro or compiler plugin or their dependencies. - pub fn new_compiler() -> UnitFor { + pub const fn new_compiler() -> UnitFor { + // Note: This is currently the same as `new_build`, but keeping it + // separate for now in case it is useful in the future. UnitFor { - build: false, + build: true, panic_abort_ok: false, } } /// A unit for a test/bench target or their dependencies. - pub fn new_test() -> UnitFor { + pub const fn new_test() -> UnitFor { UnitFor { build: false, panic_abort_ok: false, @@ -660,18 +722,9 @@ impl UnitFor { /// All possible values, used by `clean`. pub fn all_values() -> &'static [UnitFor] { static ALL: [UnitFor; 3] = [ - UnitFor { - build: false, - panic_abort_ok: true, - }, - UnitFor { - build: true, - panic_abort_ok: false, - }, - UnitFor { - build: false, - panic_abort_ok: false, - }, + UnitFor::new_normal(), + UnitFor::new_build(), + UnitFor::new_test(), ]; &ALL } @@ -682,22 +735,29 @@ impl UnitFor { pub struct ConfigProfiles { dev: Option, release: Option, + build: 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(|| failure::format_err!("config profile `profile.dev` is not valid"))?; - } - if let Some(ref profile) = self.release { - profile - .validate("release", features, warnings) - .chain_err(|| { - failure::format_err!("config profile `profile.release` is not valid") - })?; + macro_rules! check_profile { + ($name:ident) => { + if let Some(ref profile) = self.$name { + profile + .validate(stringify!($name), features, warnings) + .chain_err(|| { + failure::format_err!( + "config profile `profile.{}` is not valid", + stringify!($name) + ) + })?; + } + }; } + + check_profile!(dev); + check_profile!(release); + check_profile!(build); Ok(()) } } diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index 676e882e15c..713cc20f8f1 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -275,6 +275,7 @@ pub struct TomlProfiles { pub bench: Option, pub dev: Option, pub release: Option, + pub build: Option, } impl TomlProfiles { @@ -294,6 +295,9 @@ impl TomlProfiles { if let Some(ref release) = self.release { release.validate("release", features, warnings)?; } + if let Some(ref build) = self.build { + build.validate("build", features, warnings)?; + } Ok(()) } } @@ -470,7 +474,7 @@ impl TomlProfile { } match name { - "dev" | "release" => {} + "dev" | "release" | "build" => {} _ => { if self.overrides.is_some() || self.build_override.is_some() { bail!( @@ -491,6 +495,11 @@ impl TomlProfile { warnings.push(format!("`panic` setting is ignored for `{}` profile", name)) } } + "build" => { + if self.build_override.is_some() { + failure::bail!("`build` profile cannot specify build overrides.") + } + } _ => {} } @@ -1023,7 +1032,13 @@ impl TomlManifest { `[workspace]`, only one can be specified" ), }; - let profiles = Profiles::new(me.profile.as_ref(), config, &features, &mut warnings)?; + let profiles = Profiles::new( + me.profile.as_ref(), + config, + &features, + &mut warnings, + &package_root.join("Cargo.toml"), + )?; let publish = match project.publish { Some(VecStringOrBool::VecString(ref vecstring)) => Some(vecstring.clone()), Some(VecStringOrBool::Bool(false)) => Some(vec![]), @@ -1154,7 +1169,13 @@ impl TomlManifest { }; (me.replace(&mut cx)?, me.patch(&mut cx)?) }; - let profiles = Profiles::new(me.profile.as_ref(), config, &features, &mut warnings)?; + let profiles = Profiles::new( + me.profile.as_ref(), + config, + &features, + &mut warnings, + &root.join("Cargo.toml"), + )?; let workspace_config = match me.workspace { Some(ref config) => WorkspaceConfig::Root(WorkspaceRootConfig::new( root, diff --git a/src/doc/src/reference/unstable.md b/src/doc/src/reference/unstable.md index 8322ab8aa51..9047e8b4c88 100644 --- a/src/doc/src/reference/unstable.md +++ b/src/doc/src/reference/unstable.md @@ -148,6 +148,36 @@ cargo +nightly build -Z config-profile ``` +### Build Profile +* Tracking Issue: [rust-lang/rust#48683](https://github.com/rust-lang/rust/issues/48683) + +The `build` profile controls the build settings for build scripts, +proc-macros, compiler plugins, and all of their dependencies. It requires the +`build-profile` feature to be enabled. + +```toml +cargo-features = ["build-profile"] + +[profile.build] +# The following illustrates the defaults. +opt-level = 0 +debug = false +rpath = false +lto = false +debug-assertions = false +codegen-units = 16 +panic = 'unwind' +incremental = false +overflow-checks = false +``` + +It is compatible with [Profile Overrides](#profile-overrides) and [Config +Profiles](#config-profiles). If `build-override` is specified in a dev or +release profile, that takes precedence over the `build` profile. Enabling +`build-profile` will cause `build-override` to also affect proc-macros and +plugins (normally it only affects build scripts). + + ### Namespaced features * Original issue: [#1286](https://github.com/rust-lang/cargo/issues/1286) * Tracking Issue: [#5565](https://github.com/rust-lang/cargo/issues/5565) diff --git a/tests/testsuite/build_profile.rs b/tests/testsuite/build_profile.rs new file mode 100644 index 00000000000..602e69cfefe --- /dev/null +++ b/tests/testsuite/build_profile.rs @@ -0,0 +1,481 @@ +use crate::support::{basic_lib_manifest, project}; + +#[test] +fn build_profile_gated() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + + [profile.build] + opt-level = 3 + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("build") + .masquerade_as_nightly_cargo() + .with_status(101) + .with_stderr( + "\ +error: failed to parse manifest at `[..]` + +Caused by: + feature `build-profile` is required + +consider adding `cargo-features = [\"build-profile\"]` to the manifest +", + ) + .run(); +} + +#[test] +fn build_profile_basic() { + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["build-profile"] + + [package] + name = "foo" + version = "0.0.1" + + [profile.build] + opt-level = 3 + "#, + ) + .file("src/lib.rs", "") + .file("build.rs", "fn main() {}") + .build(); + + p.cargo("build -v") + .masquerade_as_nightly_cargo() + .with_stderr_line_without( + &[ + "[RUNNING] `rustc --crate-name build_script_build", + "-C opt-level=3", + ], + &["-C debuginfo", "-C incremental"], + ) + .run(); +} + +#[test] +fn build_profile_default_disabled() { + // Verify the defaults are not affected if build-profile is not enabled. + let p = project() + .file("src/lib.rs", "") + .file("build.rs", "fn main() {}") + .build(); + + p.cargo("build -v") + .masquerade_as_nightly_cargo() + .with_stderr_line_without( + &[ + "[RUNNING] `rustc --crate-name build_script_build", + "-C debuginfo=2", + ], + &["-C opt-level"], + ) + .run(); + + p.cargo("build -v --release") + .masquerade_as_nightly_cargo() + .with_stderr_line_without( + &[ + "[RUNNING] `rustc --crate-name build_script_build", + "-C opt-level=3", + ], + &["-C debuginfo"], + ) + .run(); +} + +#[test] +fn build_profile_default_enabled() { + // Check the change in defaults when the feature is enabled. + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["build-profile"] + + [package] + name = "foo" + version = "0.0.1" + "#, + ) + .file("src/lib.rs", "") + .file("build.rs", "fn main() {}") + .build(); + + p.cargo("build -v") + .masquerade_as_nightly_cargo() + .with_stderr_line_without( + &["[RUNNING] `rustc --crate-name build_script_build"], + &["-C debuginfo", "-C opt-level"], + ) + .run(); + + p.cargo("build -v --release") + .masquerade_as_nightly_cargo() + .with_stderr_line_without( + &["[RUNNING] `rustc --crate-name build_script_build"], + &["-C debuginfo", "-C opt-level"], + ) + .run(); +} + +#[test] +fn build_profile_with_override() { + // Check with `overrides` in `profile.build`. + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["build-profile", "profile-overrides"] + + [package] + name = "foo" + version = "0.0.1" + + [profile.build.overrides.bar] + opt-level = 3 + + [dependencies] + bar = { path = "bar" } + + [build-dependencies] + bar = { path = "bar" } + "#, + ) + .file("src/lib.rs", "extern crate bar;") + .file("build.rs", "extern crate bar; fn main() {}") + .file("bar/Cargo.toml", &basic_lib_manifest("bar")) + .file("bar/src/lib.rs", "") + .build(); + + p.cargo("build -v") + .masquerade_as_nightly_cargo() + // bar as a build-dependency + .with_stderr_line_without( + &["[RUNNING] `rustc --crate-name bar", "-C opt-level=3"], + &["-C debuginfo"], + ) + // bar as a regular dependency + .with_stderr_line_without( + &["[RUNNING] `rustc --crate-name bar", "-C debuginfo=2"], + &["-C opt-level"], + ) + .with_stderr_line_without( + &["[RUNNING] `rustc --crate-name build_script_build"], + &["-C opt-level", "-C debuginfo"], + ) + .run(); +} + +#[test] +fn build_profile_rejects_build_override() { + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["build-profile", "profile-overrides"] + + [package] + name = "foo" + version = "0.0.1" + + [profile.build.build-override] + opt-level = 3 + "#, + ) + .file("src/lib.rs", "") + .file("build.rs", "fn main() {}") + .build(); + + p.cargo("build -v") + .masquerade_as_nightly_cargo() + .with_status(101) + .with_stderr( + "\ +error: failed to parse manifest at `[..]` + +Caused by: + `build` profile cannot specify build overrides. +", + ) + .run(); +} + +#[test] +fn build_profile_with_other_build_override() { + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["build-profile", "profile-overrides"] + + [package] + name = "foo" + version = "0.0.1" + + [profile.build] + codegen-units = 1 + + [profile.dev] + codegen-units = 2 + + [profile.dev.build-override] + codegen-units = 3 + + [profile.release] + codegen-units = 4 + "#, + ) + .file("src/lib.rs", "") + .file("build.rs", "fn main() {}") + .build(); + + // build script = 3 + // lib = 2 + p.cargo("build -v") + .masquerade_as_nightly_cargo() + // Note: it inherits all settings from profile.dev (including debuginfo) + .with_stderr_line_without( + &[ + "[RUNNING] `rustc --crate-name build_script_build", + "-C codegen-units=3", + "-C debuginfo=2", + ], + &["-C opt-level"], + ) + .with_stderr_line_without( + &[ + "[RUNNING] `rustc --crate-name foo", + "-C codegen-units=2", + "-C debuginfo=2", + ], + &[], + ) + .run(); + + // build script = 1 + // lib = 4 + p.cargo("build -v --release") + .masquerade_as_nightly_cargo() + // This is not overridden. + .with_stderr_line_without( + &[ + "[RUNNING] `rustc --crate-name build_script_build", + "-C codegen-units=1", + ], + &["-C opt-level", "-C debuginfo"], + ) + .with_stderr_line_without( + &["[RUNNING] `rustc --crate-name foo", "-C codegen-units=4"], + &["-C debuginfo"], + ) + .run(); +} + +#[test] +fn build_profile_default_with_pm_build_override() { + // Check that enabling build-profile engages the ability for + // build-overrides to affect proc-macros. + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["build-profile", "profile-overrides"] + + [package] + name = "foo" + version = "0.0.1" + + [profile.dev.build-override] + codegen-units = 3 + + [dependencies] + pm = { path = "pm" } + "#, + ) + .file("src/lib.rs", "extern crate pm;") + .file( + "pm/Cargo.toml", + r#" + [package] + name = "pm" + version = "0.1.0" + [lib] + proc-macro = true + "#, + ) + .file("pm/src/lib.rs", "") + .build(); + + p.cargo("build -v") + .masquerade_as_nightly_cargo() + .with_stderr_line_without( + &[ + "[RUNNING] `rustc --crate-name pm", + "-C codegen-units=3", + "-C debuginfo=2", + ], + &["-C opt-level"], + ) + .run(); +} + +#[test] +fn build_profile_config() { + // Check `[profile.build]` inside .cargo/config. + let p = project() + .file("src/lib.rs", "") + .file("build.rs", "fn main() {}") + .file( + ".cargo/config", + r#" + [profile.build] + codegen-units = 3 + "#, + ) + .build(); + + p.cargo("build -v -Z config-profile") + .masquerade_as_nightly_cargo() + .with_stderr_contains("\ +[WARNING] profile `build` in config file will be ignored for manifest `[CWD]/Cargo.toml` \ +because \"build-profile\" feature is not enabled") + .with_stderr_line_without( + &["[RUNNING] `rustc --crate-name build_script_build", "-C debuginfo=2"], + &["-C codegen-units", "-C opt-level"]) + .run(); + + p.change_file( + "Cargo.toml", + r#" + cargo-features = ["build-profile"] + + [package] + name = "foo" + version = "0.0.1" + "#, + ); + + p.cargo("build -v -Z config-profile") + .masquerade_as_nightly_cargo() + .with_stderr_line_without( + &[ + "[RUNNING] `rustc --crate-name build_script_build", + "-C codegen-units=3", + ], + &["-C debuginfo=2", "-C opt-level"], + ) + .run(); +} + +#[test] +fn proc_macro_default_disabled() { + // Make sure proc-macros are not affected if feature is not enabled. + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + [dependencies] + pm = { path = "pm" } + "#, + ) + .file("src/lib.rs", "extern crate pm;") + .file( + "pm/Cargo.toml", + r#" + [package] + name = "pm" + version = "0.1.0" + [lib] + proc-macro = true + "#, + ) + .file("pm/src/lib.rs", "") + .build(); + + p.cargo("build -v --release") + .with_stderr_line_without( + &["[RUNNING] `rustc --crate-name pm", "-C opt-level=3"], + &["-C debuginfo=2"], + ) + .run(); +} + +#[test] +fn proc_macro_default_enabled() { + // Check that proc-macros *are* affected when build-profile is enabled, + // but no profile settings are set (checking the default settings). + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["build-profile"] + [package] + name = "foo" + version = "0.1.0" + [dependencies] + bar = { path = "bar" } + pm = { path = "pm" } + "#, + ) + .file( + "src/lib.rs", + "\ + extern crate pm; + extern crate bar; + ", + ) + .file( + "pm/Cargo.toml", + r#" + [package] + name = "pm" + version = "0.1.0" + [lib] + proc-macro = true + [dependencies] + bar = { path = "../bar" } + "#, + ) + .file("pm/src/lib.rs", "") + .file("bar/Cargo.toml", &basic_lib_manifest("bar")) + .file("bar/src/lib.rs", "") + .build(); + + p.cargo("build -v --release") + .masquerade_as_nightly_cargo() + // bar for the proc-macro + .with_stderr_line_without( + &["[RUNNING] `rustc --crate-name bar"], + &["-C opt-level", "-C debuginfo"], + ) + // bar as a normal dependency + .with_stderr_line_without( + &["[RUNNING] `rustc --crate-name bar", "-C opt-level=3"], + &["-C debuginfo"], + ) + .with_stderr_line_without( + &["[RUNNING] `rustc --crate-name pm"], + &["-C debuginfo", "-C opt-level"], + ) + .with_stderr_line_without( + &["[RUNNING] `rustc --crate-name foo", "-C opt-level=3"], + &["-C debuginfo"], + ) + .run(); +} diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs index b8b86a93820..d4461870a84 100644 --- a/tests/testsuite/main.rs +++ b/tests/testsuite/main.rs @@ -16,6 +16,7 @@ mod build; mod build_auth; mod build_lib; mod build_plan; +mod build_profile; mod build_script; mod build_script_env; mod cargo_alias_config;