From f683f42fd66dc0588cc19a9558ea7123ad374d4c 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/cargo/commands/bench.rs | 2 +- src/bin/cargo/commands/build.rs | 1 + src/bin/cargo/commands/install.rs | 6 +- src/cargo/core/compiler/build_config.rs | 23 ++- src/cargo/core/compiler/context/mod.rs | 6 +- .../compiler/context/unit_dependencies.rs | 2 +- src/cargo/core/compiler/custom_build.rs | 9 +- src/cargo/core/compiler/job_queue.rs | 9 +- src/cargo/core/compiler/mod.rs | 2 +- src/cargo/core/profiles.rs | 138 +++++++++++++++--- src/cargo/ops/cargo_clean.rs | 13 +- src/cargo/ops/cargo_compile.rs | 2 +- src/cargo/util/command_prelude.rs | 17 ++- src/cargo/util/toml/mod.rs | 2 + 14 files changed, 184 insertions(+), 48 deletions(-) diff --git a/src/bin/cargo/commands/bench.rs b/src/bin/cargo/commands/bench.rs index aa44eb0d817..9afb221c23d 100644 --- a/src/bin/cargo/commands/bench.rs +++ b/src/bin/cargo/commands/bench.rs @@ -74,7 +74,7 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult { let ws = args.workspace(config)?; let mut compile_opts = args.compile_options(config, CompileMode::Bench, Some(&ws))?; - compile_opts.build_config.release = true; + compile_opts.build_config.build_profile = BuildProfile::Release; let ops = TestOptions { no_run: args.is_present("no-run"), diff --git a/src/bin/cargo/commands/build.rs b/src/bin/cargo/commands/build.rs index 3938a8e5630..87f058b6dec 100644 --- a/src/bin/cargo/commands/build.rs +++ b/src/bin/cargo/commands/build.rs @@ -26,6 +26,7 @@ pub fn cli() -> App { "Build all targets", ) .arg_release("Build artifacts in release mode, with optimizations") + .arg_profile("Build artifacts with the specified custom profile") .arg_features() .arg_target_triple("Build for the target triple") .arg_target_dir() diff --git a/src/bin/cargo/commands/install.rs b/src/bin/cargo/commands/install.rs index 8702fd99a39..c344a6cc373 100644 --- a/src/bin/cargo/commands/install.rs +++ b/src/bin/cargo/commands/install.rs @@ -82,7 +82,11 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult { let workspace = args.workspace(config).ok(); let mut compile_opts = args.compile_options(config, CompileMode::Build, workspace.as_ref())?; - compile_opts.build_config.release = !args.is_present("debug"); + compile_opts.build_config.build_profile = if !args._is_present("debug") { + BuildProfile::Release + } else { + BuildProfile::Dev + }; let krates = args .values_of("crate") diff --git a/src/cargo/core/compiler/build_config.rs b/src/cargo/core/compiler/build_config.rs index 530890469be..9bfbaaaaabc 100644 --- a/src/cargo/core/compiler/build_config.rs +++ b/src/cargo/core/compiler/build_config.rs @@ -5,6 +5,23 @@ use serde::ser; use crate::util::{CargoResult, CargoResultExt, Config, RustfixDiagnosticServer}; +#[derive(Debug, Clone)] +pub enum BuildProfile { + Dev, + Release, + Custom(String), +} + +impl BuildProfile { + pub fn dest(&self) -> &str { + match self { + BuildProfile::Dev => "debug", + BuildProfile::Release => "release", + BuildProfile::Custom(name) => &name, + } + } +} + /// Configuration information for a rustc build. #[derive(Debug)] pub struct BuildConfig { @@ -12,8 +29,8 @@ pub struct BuildConfig { pub requested_target: Option, /// How many rustc jobs to run in parallel pub jobs: u32, - /// Whether we are building for release - pub release: bool, + /// Custom profile + pub build_profile: BuildProfile, /// In what mode we are compiling pub mode: CompileMode, /// Whether to print std output in json format (for machine reading) @@ -82,7 +99,7 @@ impl BuildConfig { Ok(BuildConfig { requested_target: target, jobs, - release: false, + build_profile: BuildProfile::Dev, mode, message_format: MessageFormat::Human, force_rebuild: false, diff --git a/src/cargo/core/compiler/context/mod.rs b/src/cargo/core/compiler/context/mod.rs index 6285fa19673..3b0719bb1cc 100644 --- a/src/cargo/core/compiler/context/mod.rs +++ b/src/cargo/core/compiler/context/mod.rs @@ -293,11 +293,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> { export_dir: Option, units: &[Unit<'a>], ) -> CargoResult<()> { - let dest = if self.bcx.build_config.release { - "release" - } else { - "debug" - }; + let dest = self.bcx.build_config.build_profile.dest(); let host_layout = Layout::new(self.bcx.ws, None, dest)?; let target_layout = match self.bcx.build_config.requested_target.as_ref() { Some(target) => Some(Layout::new(self.bcx.ws, Some(target), dest)?), diff --git a/src/cargo/core/compiler/context/unit_dependencies.rs b/src/cargo/core/compiler/context/unit_dependencies.rs index b101e944360..0930ca8ae14 100644 --- a/src/cargo/core/compiler/context/unit_dependencies.rs +++ b/src/cargo/core/compiler/context/unit_dependencies.rs @@ -402,7 +402,7 @@ fn new_unit<'a>( bcx.ws.is_member(pkg), unit_for, mode, - bcx.build_config.release, + bcx.build_config.build_profile.clone(), ); Unit { pkg, diff --git a/src/cargo/core/compiler/custom_build.rs b/src/cargo/core/compiler/custom_build.rs index c413ddcb66a..934a87c3562 100644 --- a/src/cargo/core/compiler/custom_build.rs +++ b/src/cargo/core/compiler/custom_build.rs @@ -164,14 +164,7 @@ fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoRes ) .env("DEBUG", debug.to_string()) .env("OPT_LEVEL", &unit.profile.opt_level.to_string()) - .env( - "PROFILE", - if bcx.build_config.release { - "release" - } else { - "debug" - }, - ) + .env("PROFILE", bcx.build_config.build_profile.dest()) .env("HOST", &bcx.host_triple()) .env("RUSTC", &bcx.rustc.path) .env("RUSTDOC", &*bcx.config.rustdoc()?) diff --git a/src/cargo/core/compiler/job_queue.rs b/src/cargo/core/compiler/job_queue.rs index 87278d20f2c..720062db477 100644 --- a/src/cargo/core/compiler/job_queue.rs +++ b/src/cargo/core/compiler/job_queue.rs @@ -11,6 +11,7 @@ use crossbeam_utils::thread::Scope; use jobserver::{Acquired, HelperThread}; use log::{debug, info, trace}; +use crate::core::compiler::{BuildProfile}; use crate::core::profiles::Profile; use crate::core::{PackageId, Target, TargetKind}; use crate::handle_error; @@ -38,7 +39,7 @@ pub struct JobQueue<'a, 'cfg> { compiled: HashSet, documented: HashSet, counts: HashMap, - is_release: bool, + build_profile: BuildProfile, progress: Progress<'cfg>, } @@ -145,8 +146,8 @@ impl<'a, 'cfg> JobQueue<'a, 'cfg> { compiled: HashSet::new(), documented: HashSet::new(), counts: HashMap::new(), - is_release: bcx.build_config.release, progress, + build_profile: bcx.build_config.build_profile.clone(), } } @@ -354,7 +355,7 @@ impl<'a, 'cfg> JobQueue<'a, 'cfg> { } self.progress.clear(); - let build_type = if self.is_release { "release" } else { "dev" }; + let build_type = self.build_profile.dest(); // NOTE: This may be a bit inaccurate, since this may not display the // profile for what was actually built. Profile overrides can change // these settings, and in some cases different targets are built with @@ -362,7 +363,7 @@ impl<'a, 'cfg> JobQueue<'a, 'cfg> { // list of Units built, and maybe display a list of the different // profiles used. However, to keep it simple and compatible with old // behavior, we just display what the base profile is. - let profile = cx.bcx.profiles.base_profile(self.is_release); + let profile = cx.bcx.profiles.base_profile(&self.build_profile); let mut opt_type = String::from(if profile.opt_level.as_str() == "0" { "unoptimized" } else { diff --git a/src/cargo/core/compiler/mod.rs b/src/cargo/core/compiler/mod.rs index a3bf609ad2a..5a926d6ff49 100644 --- a/src/cargo/core/compiler/mod.rs +++ b/src/cargo/core/compiler/mod.rs @@ -24,7 +24,7 @@ use self::job_queue::JobQueue; use self::output_depinfo::output_depinfo; -pub use self::build_config::{BuildConfig, CompileMode, MessageFormat}; +pub use self::build_config::{BuildConfig, CompileMode, MessageFormat, BuildProfile}; pub use self::build_context::{BuildContext, FileFlavor, TargetConfig, TargetInfo}; pub use self::compilation::{Compilation, Doctest}; pub use self::context::{Context, Unit}; diff --git a/src/cargo/core/profiles.rs b/src/cargo/core/profiles.rs index 6a4214c90ae..ea4896685da 100644 --- a/src/cargo/core/profiles.rs +++ b/src/cargo/core/profiles.rs @@ -1,9 +1,10 @@ use std::collections::HashSet; +use std::collections::BTreeMap; use std::{cmp, fmt, hash}; use serde::Deserialize; -use crate::core::compiler::CompileMode; +use crate::core::compiler::{BuildProfile, CompileMode}; use crate::core::interning::InternedString; use crate::core::{Features, PackageId, PackageIdSpec, PackageSet, Shell}; use crate::util::errors::CargoResultExt; @@ -19,6 +20,7 @@ pub struct Profiles { test: ProfileMaker, bench: ProfileMaker, doc: ProfileMaker, + custom: BTreeMap, } impl Profiles { @@ -35,35 +37,110 @@ impl Profiles { let config_profiles = config.profiles()?; config_profiles.validate(features, warnings)?; - Ok(Profiles { + let mut profile_makers = Profiles { dev: ProfileMaker { default: Profile::default_dev(), toml: profiles.and_then(|p| p.dev.clone()), config: config_profiles.dev.clone(), + inherits: vec![], }, release: ProfileMaker { default: Profile::default_release(), toml: profiles.and_then(|p| p.release.clone()), + inherits: vec![], config: config_profiles.release.clone(), }, test: ProfileMaker { default: Profile::default_test(), toml: profiles.and_then(|p| p.test.clone()), config: None, + inherits: vec![], }, bench: ProfileMaker { default: Profile::default_bench(), toml: profiles.and_then(|p| p.bench.clone()), config: None, + inherits: vec![], }, doc: ProfileMaker { default: Profile::default_doc(), toml: profiles.and_then(|p| p.doc.clone()), config: None, + inherits: vec![], }, - }) + custom: BTreeMap::new(), + }; + + if let Some(profiles) = profiles { + match &profiles.custom { + None => {}, + Some(customs) => { + profile_makers.process_customs(customs)?; + } + } + } + + Ok(profile_makers) + } + + pub fn process_customs(&mut self, profiles: &BTreeMap) + -> CargoResult<()> + { + for (name, profile) in profiles { + let mut set = HashSet::new(); + let mut result = Vec::new(); + + set.insert(name.as_str().to_owned()); + + let mut maker = self.process_chain_custom(&profile, &mut set, + &mut result, profiles)?; + result.reverse(); + maker.inherits = result; + + self.custom.insert(name.as_str().to_owned(), maker); + } + + Ok(()) } + fn process_chain_custom(&mut self, + profile: &TomlProfile, + set: &mut HashSet, + result: &mut Vec, + profiles: &BTreeMap) + -> CargoResult + { + result.push(profile.clone()); + match profile.inherits.as_ref().map(|x| x.as_str()) { + Some("release") => { + return Ok(self.release.clone()); + } + Some("dev") => { + return Ok(self.dev.clone()); + } + Some(custom_name) => { + let custom_name = custom_name.to_owned(); + if set.get(&custom_name).is_some() { + return Err(failure::format_err!("Inheritance loop of custom profiles cycles with {}", custom_name)); + } + + set.insert(custom_name.clone()); + match profiles.get(&custom_name) { + None => { + return Err(failure::format_err!("Custom profile {} not found in Cargo.toml", custom_name)); + } + Some(parent) => { + self.process_chain_custom(parent, set, result, profiles) + } + } + } + None => { + Err(failure::format_err!("An 'inherits' directive is needed for all custom profiles")) + } + } + } + + /// Retrieve the profile for a target. /// `is_member` is whether or not this package is a member of the /// workspace. @@ -73,14 +150,20 @@ impl Profiles { is_member: bool, unit_for: UnitFor, mode: CompileMode, - release: bool, + build_profile: BuildProfile, ) -> Profile { let maker = match mode { CompileMode::Test | CompileMode::Bench => { - if release { - &self.bench - } else { - &self.test + match &build_profile { + BuildProfile::Release => { + &self.bench + } + BuildProfile::Dev => { + &self.test + } + BuildProfile::Custom(name) => { + self.custom.get(name.as_str()).unwrap() + } } } CompileMode::Build @@ -91,10 +174,16 @@ 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 - } else { - &self.dev + match &build_profile { + BuildProfile::Release => { + &self.release + } + BuildProfile::Dev => { + &self.dev + } + BuildProfile::Custom(name) => { + self.custom.get(name.as_str()).unwrap() + } } } CompileMode::Doc { .. } => &self.doc, @@ -123,11 +212,18 @@ impl Profiles { /// This returns a generic base profile. This is currently used for the /// `[Finished]` line. It is not entirely accurate, since it doesn't /// 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()) - } else { - self.dev.get_profile(None, true, UnitFor::new_normal()) + pub fn base_profile(&self, build_profile: &BuildProfile) -> Profile { + match &build_profile { + BuildProfile::Release => { + self.release.get_profile(None, true, UnitFor::new_normal()) + } + BuildProfile::Dev => { + self.dev.get_profile(None, true, UnitFor::new_normal()) + } + BuildProfile::Custom(name) => { + let r = self.custom.get(name.as_str()).unwrap(); + r.get_profile(None, true, UnitFor::new_normal()) + } } } @@ -162,6 +258,11 @@ struct ProfileMaker { default: Profile, /// The profile from the `Cargo.toml` manifest. toml: Option, + + /// Profiles from which we inherit, in the order from which + /// we inherit. + inherits: Vec, + /// Profile loaded from `.cargo/config` files. config: Option, } @@ -177,6 +278,9 @@ impl ProfileMaker { if let Some(ref toml) = self.toml { merge_toml(pkg_id, is_member, unit_for, &mut profile, toml); } + for toml in &self.inherits { + 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); } diff --git a/src/cargo/ops/cargo_clean.rs b/src/cargo/ops/cargo_clean.rs index 8dae82603bd..9473f0d53a0 100644 --- a/src/cargo/ops/cargo_clean.rs +++ b/src/cargo/ops/cargo_clean.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use std::fs; use std::path::Path; -use crate::core::compiler::{BuildConfig, BuildContext, CompileMode, Context, Kind, Unit}; +use crate::core::compiler::{BuildConfig, BuildContext, BuildProfile, CompileMode, Context, Kind, Unit}; use crate::core::profiles::UnitFor; use crate::core::Workspace; use crate::ops; @@ -51,6 +51,11 @@ pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> { let profiles = ws.profiles(); let mut units = Vec::new(); + let build_profile = if opts.release { + BuildProfile::Release + } else { + BuildProfile::Dev + }; for spec in opts.spec.iter() { // Translate the spec to a Package @@ -68,7 +73,7 @@ pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> { ws.is_member(pkg), *unit_for, CompileMode::Build, - opts.release, + build_profile.clone(), )) } else { profiles.get_profile( @@ -76,7 +81,7 @@ pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> { ws.is_member(pkg), *unit_for, *mode, - opts.release, + build_profile.clone(), ) }; units.push(Unit { @@ -93,7 +98,7 @@ pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> { } let mut build_config = BuildConfig::new(config, Some(1), &opts.target, CompileMode::Build)?; - build_config.release = opts.release; + build_config.build_profile = build_profile; let bcx = BuildContext::new( ws, &resolve, diff --git a/src/cargo/ops/cargo_compile.rs b/src/cargo/ops/cargo_compile.rs index 1f8df9626aa..9886de1512d 100644 --- a/src/cargo/ops/cargo_compile.rs +++ b/src/cargo/ops/cargo_compile.rs @@ -567,7 +567,7 @@ fn generate_targets<'a>( ws.is_member(pkg), unit_for, target_mode, - build_config.release, + build_config.build_profile.clone(), ); Unit { pkg, diff --git a/src/cargo/util/command_prelude.rs b/src/cargo/util/command_prelude.rs index a8ddfdb94dd..5b8af5a5bd8 100644 --- a/src/cargo/util/command_prelude.rs +++ b/src/cargo/util/command_prelude.rs @@ -14,7 +14,7 @@ use crate::util::{ use crate::CargoResult; use clap::{self, SubCommand}; -pub use crate::core::compiler::CompileMode; +pub use crate::core::compiler::{BuildProfile, CompileMode}; pub use crate::{CliError, CliResult, Config}; pub use clap::{AppSettings, Arg, ArgMatches}; @@ -112,6 +112,10 @@ pub trait AppExt: Sized { self._arg(opt("release", release)) } + fn arg_profile(self, profile: &'static str) -> Self { + self._arg(opt("profile", profile).value_name("PROFILE-NAME")) + } + fn arg_doc(self, doc: &'static str) -> Self { self._arg(opt("doc", doc)) } @@ -313,7 +317,16 @@ pub trait ArgMatchesExt { let mut build_config = BuildConfig::new(config, self.jobs()?, &self.target(), mode)?; build_config.message_format = message_format; - build_config.release = self._is_present("release"); + build_config.build_profile = if self._is_present("release") { + BuildProfile::Release + } else { + match self._value_of("profile").map(|s| s.to_string()) { + None => BuildProfile::Dev, + Some(name) => { + BuildProfile::Custom(name) + } + } + }; build_config.build_plan = self._is_present("build-plan"); if build_config.build_plan && !config.cli_unstable().unstable_options { Err(failure::format_err!( diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index d3e6fd9aa95..70e33f37d3e 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -255,6 +255,7 @@ pub struct TomlProfiles { pub bench: Option, pub dev: Option, pub release: Option, + pub custom: Option>, } impl TomlProfiles { @@ -395,6 +396,7 @@ pub struct TomlProfile { pub incremental: Option, pub overrides: Option>, pub build_override: Option>, + pub inherits: Option, } #[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]