From 0e2c2bb26bacbe0ce339c928431525b22374681e Mon Sep 17 00:00:00 2001 From: Dan Aloni Date: Mon, 9 Apr 2018 23:09:11 +0300 Subject: [PATCH] Support for custom Cargo profiles This allows creating custom profiles that inherit from other profiles. For example, one can have a release-lto profile that looks like this: [profile.custom.release-lto] inherits = "release" lto = true The profile name will also carry itself into the output directory name so that the different build outputs can be cached independently from one another. So in effect, at the `target` directory, a name will be created for the new profile, in addition to the 'debug' and 'release' builds: ``` $ cargo build --profile release-lto $ ls -l target debug release release-lto ``` --- src/bin/command_prelude.rs | 5 + src/bin/commands/build.rs | 1 + src/bin/commands/run.rs | 1 + src/cargo/core/manifest.rs | 1 + src/cargo/core/workspace.rs | 1 + src/cargo/ops/cargo_clean.rs | 10 +- src/cargo/ops/cargo_compile.rs | 58 ++++++++-- src/cargo/ops/cargo_package.rs | 1 + src/cargo/ops/cargo_rustc/context/mod.rs | 22 +++- src/cargo/ops/cargo_rustc/mod.rs | 2 + src/cargo/util/toml/mod.rs | 137 ++++++++++++++++++++--- src/doc/src/reference/manifest.md | 22 +++- 12 files changed, 223 insertions(+), 38 deletions(-) diff --git a/src/bin/command_prelude.rs b/src/bin/command_prelude.rs index a057a1fc59b..4fb68ab70f0 100644 --- a/src/bin/command_prelude.rs +++ b/src/bin/command_prelude.rs @@ -109,6 +109,10 @@ pub trait AppExt: Sized { self._arg(opt("target", target).value_name("TRIPLE")) } + fn arg_profile(self, profile: &'static str) -> Self { + self._arg(opt("profile", profile).value_name("PROFILE-NAME")) + } + fn arg_manifest_path(self) -> Self { self._arg(opt("manifest-path", "Path to Cargo.toml").value_name("PATH")) } @@ -272,6 +276,7 @@ pub trait ArgMatchesExt { no_default_features: self._is_present("no-default-features"), spec, mode, + profile: self._value_of("profile").map(|s| s.to_string()), release: self._is_present("release"), filter: CompileFilter::new( self._is_present("lib"), diff --git a/src/bin/commands/build.rs b/src/bin/commands/build.rs index 70e9d322528..664758b5fa5 100644 --- a/src/bin/commands/build.rs +++ b/src/bin/commands/build.rs @@ -27,6 +27,7 @@ pub fn cli() -> App { .arg_release("Build artifacts in release mode, with optimizations") .arg_features() .arg_target_triple("Build for the target triple") + .arg_profile("Build artifacts with the specified custom profile") .arg(opt("out-dir", "Copy final artifacts to this directory").value_name("PATH")) .arg_manifest_path() .arg_message_format() diff --git a/src/bin/commands/run.rs b/src/bin/commands/run.rs index 77e5d8e13b9..9fabb91d4e3 100644 --- a/src/bin/commands/run.rs +++ b/src/bin/commands/run.rs @@ -18,6 +18,7 @@ pub fn cli() -> App { .arg_release("Build artifacts in release mode, with optimizations") .arg_features() .arg_target_triple("Build for the target triple") + .arg_profile("Build artifacts with the specified custom profile") .arg_manifest_path() .arg_message_format() .after_help( diff --git a/src/cargo/core/manifest.rs b/src/cargo/core/manifest.rs index 6784cee7f13..4c7cc91e4df 100644 --- a/src/cargo/core/manifest.rs +++ b/src/cargo/core/manifest.rs @@ -201,6 +201,7 @@ pub struct Profiles { pub check: Profile, pub check_test: Profile, pub doctest: Profile, + pub custom: HashMap, } /// Information about a binary, a library, an example, etc. that is part of the diff --git a/src/cargo/core/workspace.rs b/src/cargo/core/workspace.rs index 7bb9be948b0..b373ec44cef 100644 --- a/src/cargo/core/workspace.rs +++ b/src/cargo/core/workspace.rs @@ -656,6 +656,7 @@ impl<'cfg> Workspace<'cfg> { check: Profile::default_check(), check_test: Profile::default_check_test(), doctest: Profile::default_doctest(), + custom: HashMap::new(), }; for pkg in self.members() diff --git a/src/cargo/ops/cargo_clean.rs b/src/cargo/ops/cargo_clean.rs index 2319470b22c..e801b940121 100644 --- a/src/cargo/ops/cargo_clean.rs +++ b/src/cargo/ops/cargo_clean.rs @@ -59,6 +59,7 @@ pub fn clean(ws: &Workspace, opts: &CleanOptions) -> CargoResult<()> { ref check, ref check_test, ref doctest, + ref custom, } = *profiles; let profiles = [ release, @@ -73,13 +74,19 @@ pub fn clean(ws: &Workspace, opts: &CleanOptions) -> CargoResult<()> { check_test, doctest, ]; - for profile in profiles.iter() { + let mut add = |profile| { units.push(Unit { pkg, target, profile, kind: *kind, }); + }; + for profile in profiles.iter() { + add(profile); + } + for profile in custom.values() { + add(profile); } } } @@ -98,6 +105,7 @@ pub fn clean(ws: &Workspace, opts: &CleanOptions) -> CargoResult<()> { ..BuildConfig::default() }, profiles, + &None, None, &units, )?; diff --git a/src/cargo/ops/cargo_compile.rs b/src/cargo/ops/cargo_compile.rs index 27ba181aef1..7fbb0c29fee 100644 --- a/src/cargo/ops/cargo_compile.rs +++ b/src/cargo/ops/cargo_compile.rs @@ -55,6 +55,8 @@ pub struct CompileOptions<'a> { pub filter: CompileFilter, /// Whether this is a release build or not pub release: bool, + /// Custom profile + pub profile: Option, /// Mode for this compile. pub mode: CompileMode, /// `--error_format` flag for the compiler. @@ -87,6 +89,7 @@ impl<'a> CompileOptions<'a> { filter: CompileFilter::Default { required_features_filterable: false, }, + profile: None, message_format: MessageFormat::Human, target_rustdoc_args: None, target_rustc_args: None, @@ -241,8 +244,10 @@ pub fn compile_ws<'a>( ref target_rustdoc_args, ref target_rustc_args, ref export_dir, + ref profile, } = *options; + let profile_name = profile; let target = match target { &Some(ref target) if target.ends_with(".json") => { let path = Path::new(target) @@ -292,8 +297,15 @@ pub fn compile_ws<'a>( (&Some(ref args), _) => { let all_features = resolve_all_features(&resolve_with_overrides, to_builds[0].package_id()); - let targets = - generate_targets(to_builds[0], profiles, mode, filter, &all_features, release)?; + let targets = generate_targets( + to_builds[0], + profiles, + profile_name, + mode, + filter, + &all_features, + release, + )?; if targets.len() == 1 { let (target, profile) = targets[0]; let mut profile = profile.clone(); @@ -310,25 +322,37 @@ pub fn compile_ws<'a>( (&None, &Some(ref args)) => { let all_features = resolve_all_features(&resolve_with_overrides, to_builds[0].package_id()); - let targets = - generate_targets(to_builds[0], profiles, mode, filter, &all_features, release)?; + let targets = generate_targets( + to_builds[0], + profiles, + profile_name, + mode, + filter, + &all_features, + release, + )?; if targets.len() == 1 { let (target, profile) = targets[0]; let mut profile = profile.clone(); profile.rustdoc_args = Some(args.to_vec()); general_targets.push((target, profile)); } else { - bail!( - "extra arguments to `rustdoc` can only be passed to one \ - target, consider filtering\nthe package by passing e.g. \ - `--lib` or `--bin NAME` to specify a single target" - ) + bail!("extra arguments to `rustdoc` can only be passed to one \ + target, consider filtering\nthe package by passing e.g. \ + `--lib` or `--bin NAME` to specify a single target") } } (&None, &None) => for &to_build in to_builds.iter() { let all_features = resolve_all_features(&resolve_with_overrides, to_build.package_id()); - let targets = - generate_targets(to_build, profiles, mode, filter, &all_features, release)?; + let targets = generate_targets( + to_build, + profiles, + profile_name, + mode, + filter, + &all_features, + release, + )?; package_targets.push((to_build, targets)); }, }; @@ -357,6 +381,7 @@ pub fn compile_ws<'a>( build_config, profiles, export_dir.clone(), + &profile_name, &exec, )? }; @@ -689,6 +714,7 @@ fn filter_compatible_targets<'a>( fn generate_targets<'a>( pkg: &'a Package, profiles: &'a Profiles, + profile_name: &Option, mode: CompileMode, filter: &CompileFilter, features: &HashSet, @@ -704,7 +730,7 @@ fn generate_targets<'a>( } else { &profiles.test }; - let profile = match mode { + let predef_profile = match mode { CompileMode::Test => test, CompileMode::Bench => &profiles.bench, CompileMode::Build => build, @@ -714,6 +740,14 @@ fn generate_targets<'a>( CompileMode::Doctest => &profiles.doctest, }; + let profile = match profile_name { + &None => predef_profile, + &Some(ref name) => profiles + .custom + .get(name) + .expect(format!("Missing profile {}", name).as_ref()), + }; + let test_profile = if profile.check { &profiles.check_test } else if mode == CompileMode::Build { diff --git a/src/cargo/ops/cargo_package.rs b/src/cargo/ops/cargo_package.rs index 0e29b30b8d8..bf1174985bb 100644 --- a/src/cargo/ops/cargo_package.rs +++ b/src/cargo/ops/cargo_package.rs @@ -346,6 +346,7 @@ fn run_verify(ws: &Workspace, tar: &FileLock, opts: &PackageOpts) -> CargoResult required_features_filterable: true, }, release: false, + profile: None, message_format: ops::MessageFormat::Human, mode: ops::CompileMode::Build, target_rustdoc_args: None, diff --git a/src/cargo/ops/cargo_rustc/context/mod.rs b/src/cargo/ops/cargo_rustc/context/mod.rs index 389a0afd547..f43200be366 100644 --- a/src/cargo/ops/cargo_rustc/context/mod.rs +++ b/src/cargo/ops/cargo_rustc/context/mod.rs @@ -91,6 +91,7 @@ pub struct Context<'a, 'cfg: 'a> { target_info: TargetInfo, host_info: TargetInfo, profiles: &'a Profiles, + profile_name: Option, incremental_env: Option, unit_dependencies: HashMap, Vec>>, @@ -105,13 +106,17 @@ impl<'a, 'cfg> Context<'a, 'cfg> { config: &'cfg Config, build_config: BuildConfig, profiles: &'a Profiles, + profile_name: &Option, export_dir: Option, units: &[Unit<'a>], ) -> CargoResult> { - let dest = if build_config.release { - "release" - } else { - "debug" + let dest = match profile_name { + &None => if build_config.release { + "release" + } else { + "debug" + }, + &Some(ref s) => s.as_str(), }; let host_layout = Layout::new(ws, None, dest)?; let target_layout = match build_config.requested_target.as_ref() { @@ -148,6 +153,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> { build_state: Arc::new(BuildState::new(&build_config)), build_config, fingerprints: HashMap::new(), + profile_name: profile_name.clone(), profiles, compiled: HashSet::new(), build_scripts: HashMap::new(), @@ -363,7 +369,13 @@ impl<'a, 'cfg> Context<'a, 'cfg> { if self.build_config.test { test } else { - normal + match &self.profile_name { + &None => normal, + &Some(ref name) => self.profiles + .custom + .get(name) + .expect(format!("Missing profile {}", name).as_ref()), + } } } diff --git a/src/cargo/ops/cargo_rustc/mod.rs b/src/cargo/ops/cargo_rustc/mod.rs index c742f04f98d..4ffb862e4ef 100644 --- a/src/cargo/ops/cargo_rustc/mod.rs +++ b/src/cargo/ops/cargo_rustc/mod.rs @@ -142,6 +142,7 @@ pub fn compile_targets<'a, 'cfg: 'a>( build_config: BuildConfig, profiles: &'a Profiles, export_dir: Option, + profile_name: &Option, exec: &Arc, ) -> CargoResult> { let units = pkg_targets @@ -172,6 +173,7 @@ pub fn compile_targets<'a, 'cfg: 'a>( config, build_config, profiles, + profile_name, export_dir, &units, )?; diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index 651928efdb4..c8d00754d6d 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -241,6 +241,7 @@ pub struct TomlProfiles { bench: Option, dev: Option, release: Option, + custom: Option>, } #[derive(Clone, Debug)] @@ -361,6 +362,7 @@ pub struct TomlProfile { #[serde(rename = "overflow-checks")] overflow_checks: Option, incremental: Option, + inherits: Option, } #[derive(Clone, Debug, Serialize)] @@ -776,7 +778,7 @@ impl TomlManifest { `[workspace]`, only one can be specified" ), }; - let profiles = build_profiles(&me.profile); + let profiles = build_profiles(&me.profile)?; let publish = match project.publish { Some(VecStringOrBool::VecString(ref vecstring)) => { features @@ -896,7 +898,7 @@ impl TomlManifest { }; (me.replace(&mut cx)?, me.patch(&mut cx)?) }; - let profiles = build_profiles(&me.profile); + let profiles = build_profiles(&me.profile)?; let workspace_config = match me.workspace { Some(ref config) => WorkspaceConfig::Root(WorkspaceRootConfig::new( &root, @@ -1269,57 +1271,152 @@ impl fmt::Debug for PathValue { } } -fn build_profiles(profiles: &Option) -> Profiles { +fn build_profiles(profiles: &Option) -> CargoResult { let profiles = profiles.as_ref(); + let custom = profiles.and_then(|p| p.custom.clone().and_then(Some)); + let mut profiles = Profiles { release: merge( Profile::default_release(), profiles.and_then(|p| p.release.as_ref()), - ), + )?, dev: merge( Profile::default_dev(), profiles.and_then(|p| p.dev.as_ref()), - ), + )?, test: merge( Profile::default_test(), profiles.and_then(|p| p.test.as_ref()), - ), + )?, test_deps: merge( Profile::default_dev(), profiles.and_then(|p| p.dev.as_ref()), - ), + )?, bench: merge( Profile::default_bench(), profiles.and_then(|p| p.bench.as_ref()), - ), + )?, bench_deps: merge( Profile::default_release(), profiles.and_then(|p| p.release.as_ref()), - ), + )?, doc: merge( Profile::default_doc(), profiles.and_then(|p| p.doc.as_ref()), - ), + )?, custom_build: Profile::default_custom_build(), check: merge( Profile::default_check(), profiles.and_then(|p| p.dev.as_ref()), - ), + )?, check_test: merge( Profile::default_check_test(), profiles.and_then(|p| p.dev.as_ref()), - ), + )?, doctest: Profile::default_doctest(), + custom: HashMap::new(), }; + + if let Some(custom) = custom { + for (name, profile) in custom.iter() { + let mut stack = vec![name.clone()]; + + let merged_profile = + { get_parent_profile(&name, profile.clone(), &mut stack, &profiles, &custom)? }; + profiles.custom.insert(name.clone(), merged_profile); + } + } + // The test/bench targets cannot have panic=abort because they'll all get // compiled with --test which requires the unwind runtime currently profiles.test.panic = None; profiles.bench.panic = None; profiles.test_deps.panic = None; profiles.bench_deps.panic = None; - return profiles; + return Ok(profiles); + + fn get_parent_profile( + name: &str, + profile: TomlProfile, + stack: &mut Vec, + profiles: &Profiles, + customs: &BTreeMap, + ) -> CargoResult { + let custom_prefix = "custom."; + let orig_profile = match profile.inherits { + None => { + bail!( + "custom profile '{}' must inherit from another profile via \ + the 'inherit' field. It can either be 'dev', 'release', 'test' + 'doc', 'bench', or 'custom.'", + name + ); + } + Some(ref inherit_name) => match inherit_name.as_str() { + "release" => profiles.release.clone(), + "dev" => profiles.dev.clone(), + "test" => profiles.test.clone(), + "doc" => profiles.doc.clone(), + "bench" => profiles.bench.clone(), + other => { + if other.starts_with(custom_prefix) { + let other_name = &other[custom_prefix.len()..]; + match customs.get(other_name) { + None => { + bail!( + "custom profile '{}' not found, it is \ + refered from '{}' via 'inherit'", + other, + name + ); + } + Some(other_custom_profile) => { + if stack.iter().any(|x| x == other_name) { + bail!( + "detected loop in profile's 'inherit', \ + via profiles: {:?}", + stack + ); + } + stack.push(String::from(other_name)); + get_parent_profile( + other, + other_custom_profile.clone(), + stack, + profiles, + customs, + )? + } + } + } else { + bail!( + "inheriting for invalid profile '{}'. it can \ + either be 'dev', 'release', 'test', 'doc', 'bench' \ + or 'custom.'", + other + ); + } + } + }, + }; - fn merge(profile: Profile, toml: Option<&TomlProfile>) -> Profile { + merge_custom(orig_profile, Some(&profile)) + }; + + + fn merge(profile: Profile, toml: Option<&TomlProfile>) -> CargoResult { + merge_common(profile, toml, false) + } + + fn merge_custom(profile: Profile, toml: Option<&TomlProfile>) -> CargoResult { + merge_common(profile, toml, true) + } + + fn merge_common( + profile: Profile, + toml: Option<&TomlProfile>, + allow_inherit: bool, + ) -> CargoResult { let &TomlProfile { ref opt_level, ref lto, @@ -1330,17 +1427,23 @@ fn build_profiles(profiles: &Option) -> Profiles { ref panic, ref overflow_checks, ref incremental, + ref inherits, } = match toml { Some(toml) => toml, - None => return profile, + None => return Ok(profile), }; + if !allow_inherit { + if let &Some(_) = inherits { + bail!("'inherit' cannot be used on a predefined profile"); + } + } let debug = match *debug { Some(U32OrBool::U32(debug)) => Some(Some(debug)), Some(U32OrBool::Bool(true)) => Some(Some(2)), Some(U32OrBool::Bool(false)) => Some(None), None => None, }; - Profile { + Ok(Profile { opt_level: opt_level .clone() .unwrap_or(TomlOptLevel(profile.opt_level)) @@ -1363,6 +1466,6 @@ fn build_profiles(profiles: &Option) -> Profiles { check: profile.check, panic: panic.clone().or(profile.panic), incremental: incremental.unwrap_or(profile.incremental), - } + }) } } diff --git a/src/doc/src/reference/manifest.md b/src/doc/src/reference/manifest.md index ebc539dc359..08b7eb41cd2 100644 --- a/src/doc/src/reference/manifest.md +++ b/src/doc/src/reference/manifest.md @@ -279,9 +279,12 @@ project’s profiles are actually read. All dependencies’ profiles will be overridden. This is done so the top-level project has control over how its dependencies are compiled. -There are five currently supported profile names, all of which have the same -configuration available to them. Listed below is the configuration available, -along with the defaults for each profile. +There are five currently supported predefined profile names, all of which have +the same configuration scheme available to them. It is possible to override the +configuration for these profile, and it is also possible to define custom +profiles under new names that derive from the predefined profiles. + +Listed below are the predefined profiles with their default configuration. ```toml # The development profile, used for `cargo build`. @@ -356,6 +359,19 @@ incremental = true overflow-checks = true ``` +Defining custom profiles is done by specifing new sections prefixed with +`profile.custom.`. For example, we can define a new profile named `release-lto` +that inherits from the `release` profile above: + +```toml +[profile.custom.release-lto] +inherits = "release" +lto = true +``` + +To use the profile, pass it to `build` via `--profile`, e.g. `--profile release-lto`. + + ### The `[features]` section Cargo supports features to allow expression of: