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)]