blob: c20b5800a80fd39cf009eb8f6e78decd21333762 [file] [log] [blame]
use std::collections::HashSet;
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::util::errors::CargoResultExt;
use crate::util::toml::{ProfilePackageSpec, StringOrBool, TomlProfile, TomlProfiles, U32OrBool};
use crate::util::{closest_msg, CargoResult, Config};
/// Collection of all user profiles.
#[derive(Clone, Debug)]
pub struct Profiles {
dev: ProfileMaker,
release: ProfileMaker,
test: ProfileMaker,
bench: ProfileMaker,
doc: ProfileMaker,
/// Incremental compilation can be overridden globally via:
/// - `CARGO_INCREMENTAL` environment variable.
/// - `build.incremental` config value.
incremental: Option<bool>,
}
impl Profiles {
pub fn new(
profiles: Option<&TomlProfiles>,
config: &Config,
features: &Features,
warnings: &mut Vec<String>,
) -> CargoResult<Profiles> {
if let Some(profiles) = profiles {
profiles.validate(features, warnings)?;
}
let config_profiles = config.profiles()?;
let incremental = match env::var_os("CARGO_INCREMENTAL") {
Some(v) => Some(v == "1"),
None => config.get::<Option<bool>>("build.incremental")?,
};
Ok(Profiles {
dev: ProfileMaker {
default: Profile::default_dev(),
toml: profiles.and_then(|p| p.dev.clone()),
config: config_profiles.dev.clone(),
},
release: ProfileMaker {
default: Profile::default_release(),
toml: profiles.and_then(|p| p.release.clone()),
config: config_profiles.release.clone(),
},
test: ProfileMaker {
default: Profile::default_test(),
toml: profiles.and_then(|p| p.test.clone()),
config: None,
},
bench: ProfileMaker {
default: Profile::default_bench(),
toml: profiles.and_then(|p| p.bench.clone()),
config: None,
},
doc: ProfileMaker {
default: Profile::default_doc(),
toml: profiles.and_then(|p| p.doc.clone()),
config: None,
},
incremental,
})
}
/// Retrieves the profile for a target.
/// `is_member` is whether or not this package is a member of the
/// workspace.
pub fn get_profile(
&self,
pkg_id: PackageId,
is_member: bool,
unit_for: UnitFor,
mode: CompileMode,
release: bool,
) -> Profile {
let maker = match mode {
CompileMode::Test | CompileMode::Bench => {
if release {
&self.bench
} else {
&self.test
}
}
CompileMode::Build
| CompileMode::Check { .. }
| CompileMode::Doctest
| CompileMode::RunCustomBuild => {
// Note: `RunCustomBuild` doesn't normally use this code path.
// `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
}
}
CompileMode::Doc { .. } => &self.doc,
};
let mut profile = maker.get_profile(Some(pkg_id), is_member, unit_for);
// `panic` should not be set for tests/benches, or any of their
// dependencies.
if !unit_for.is_panic_abort_ok() || mode.is_any_test() {
profile.panic = PanicStrategy::Unwind;
}
// Incremental can be globally overridden.
if let Some(v) = self.incremental {
profile.incremental = v;
}
// Only enable incremental compilation for sources the user can
// modify (aka path sources). For things that change infrequently,
// non-incremental builds yield better performance in the compiler
// itself (aka crates.io / git dependencies)
//
// (see also https://github.com/rust-lang/cargo/issues/3972)
if !pkg_id.source_id().is_path() {
profile.incremental = false;
}
profile
}
/// The profile for *running* a `build.rs` script is only used for setting
/// a few environment variables. To ensure proper de-duplication of the
/// running `Unit`, this uses a stripped-down profile (so that unrelated
/// profile flags don't cause `build.rs` to needlessly run multiple
/// times).
pub fn get_profile_run_custom_build(&self, for_unit_profile: &Profile) -> Profile {
let mut result = Profile::default();
result.debuginfo = for_unit_profile.debuginfo;
result.opt_level = for_unit_profile.opt_level;
result
}
/// 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())
}
}
/// Used to check for overrides for non-existing packages.
pub fn validate_packages(
&self,
shell: &mut Shell,
packages: &PackageSet<'_>,
) -> CargoResult<()> {
self.dev.validate_packages(shell, packages)?;
self.release.validate_packages(shell, packages)?;
self.test.validate_packages(shell, packages)?;
self.bench.validate_packages(shell, packages)?;
self.doc.validate_packages(shell, packages)?;
Ok(())
}
}
/// An object used for handling the profile override hierarchy.
///
/// The precedence of profiles are (first one wins):
/// - Profiles in `.cargo/config` files (using same order as below).
/// - [profile.dev.overrides.name] -- a named package.
/// - [profile.dev.overrides."*"] -- this cannot apply to workspace members.
/// - [profile.dev.build-override] -- this can only apply to `build.rs` scripts
/// and their dependencies.
/// - [profile.dev]
/// - Default (hard-coded) values.
#[derive(Debug, Clone)]
struct ProfileMaker {
/// The starting, hard-coded defaults for the profile.
default: Profile,
/// The profile from the `Cargo.toml` manifest.
toml: Option<TomlProfile>,
/// Profile loaded from `.cargo/config` files.
config: Option<TomlProfile>,
}
impl ProfileMaker {
fn get_profile(
&self,
pkg_id: Option<PackageId>,
is_member: bool,
unit_for: UnitFor,
) -> Profile {
let mut profile = self.default;
if let Some(ref toml) = self.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);
}
profile
}
fn validate_packages(&self, shell: &mut Shell, packages: &PackageSet<'_>) -> CargoResult<()> {
self.validate_packages_toml(shell, packages, &self.toml, true)?;
self.validate_packages_toml(shell, packages, &self.config, false)?;
Ok(())
}
fn validate_packages_toml(
&self,
shell: &mut Shell,
packages: &PackageSet<'_>,
toml: &Option<TomlProfile>,
warn_unmatched: bool,
) -> CargoResult<()> {
let toml = match *toml {
Some(ref toml) => toml,
None => return Ok(()),
};
let overrides = match toml.overrides {
Some(ref overrides) => overrides,
None => return Ok(()),
};
// Verify that a package doesn't match multiple spec overrides.
let mut found = HashSet::new();
for pkg_id in packages.package_ids() {
let matches: Vec<&PackageIdSpec> = overrides
.keys()
.filter_map(|key| match *key {
ProfilePackageSpec::All => None,
ProfilePackageSpec::Spec(ref spec) => {
if spec.matches(pkg_id) {
Some(spec)
} else {
None
}
}
})
.collect();
match matches.len() {
0 => {}
1 => {
found.insert(matches[0].clone());
}
_ => {
let specs = matches
.iter()
.map(|spec| spec.to_string())
.collect::<Vec<_>>()
.join(", ");
failure::bail!(
"multiple profile overrides in profile `{}` match package `{}`\n\
found profile override specs: {}",
self.default.name,
pkg_id,
specs
);
}
}
}
if !warn_unmatched {
return Ok(());
}
// Verify every override matches at least one package.
let missing_specs = overrides.keys().filter_map(|key| {
if let ProfilePackageSpec::Spec(ref spec) = *key {
if !found.contains(spec) {
return Some(spec);
}
}
None
});
for spec in missing_specs {
// See if there is an exact name match.
let name_matches: Vec<String> = packages
.package_ids()
.filter_map(|pkg_id| {
if pkg_id.name() == spec.name() {
Some(pkg_id.to_string())
} else {
None
}
})
.collect();
if name_matches.is_empty() {
let suggestion =
closest_msg(&spec.name(), packages.package_ids(), |p| p.name().as_str());
shell.warn(format!(
"profile override spec `{}` did not match any packages{}",
spec, suggestion
))?;
} else {
shell.warn(format!(
"version or URL in profile override spec `{}` does not \
match any of the packages: {}",
spec,
name_matches.join(", ")
))?;
}
}
Ok(())
}
}
fn merge_toml(
pkg_id: Option<PackageId>,
is_member: bool,
unit_for: UnitFor,
profile: &mut Profile,
toml: &TomlProfile,
) {
merge_profile(profile, toml);
if unit_for.is_build() {
if let Some(ref build_override) = toml.build_override {
merge_profile(profile, build_override);
}
}
if let Some(ref overrides) = toml.overrides {
if !is_member {
if let Some(all) = overrides.get(&ProfilePackageSpec::All) {
merge_profile(profile, all);
}
}
if let Some(pkg_id) = pkg_id {
let mut matches = overrides
.iter()
.filter_map(|(key, spec_profile)| match *key {
ProfilePackageSpec::All => None,
ProfilePackageSpec::Spec(ref s) => {
if s.matches(pkg_id) {
Some(spec_profile)
} else {
None
}
}
});
if let Some(spec_profile) = matches.next() {
merge_profile(profile, spec_profile);
// `validate_packages` should ensure that there are
// no additional matches.
assert!(
matches.next().is_none(),
"package `{}` matched multiple profile overrides",
pkg_id
);
}
}
}
}
fn merge_profile(profile: &mut Profile, toml: &TomlProfile) {
if let Some(ref opt_level) = toml.opt_level {
profile.opt_level = InternedString::new(&opt_level.0);
}
match toml.lto {
Some(StringOrBool::Bool(b)) => profile.lto = Lto::Bool(b),
Some(StringOrBool::String(ref n)) => profile.lto = Lto::Named(InternedString::new(n)),
None => {}
}
if toml.codegen_units.is_some() {
profile.codegen_units = toml.codegen_units;
}
match toml.debug {
Some(U32OrBool::U32(debug)) => profile.debuginfo = Some(debug),
Some(U32OrBool::Bool(true)) => profile.debuginfo = Some(2),
Some(U32OrBool::Bool(false)) => profile.debuginfo = None,
None => {}
}
if let Some(debug_assertions) = toml.debug_assertions {
profile.debug_assertions = debug_assertions;
}
if let Some(rpath) = toml.rpath {
profile.rpath = rpath;
}
if let Some(panic) = &toml.panic {
profile.panic = match panic.as_str() {
"unwind" => PanicStrategy::Unwind,
"abort" => PanicStrategy::Abort,
// This should be validated in TomlProfile::validate
_ => panic!("Unexpected panic setting `{}`", panic),
};
}
if let Some(overflow_checks) = toml.overflow_checks {
profile.overflow_checks = overflow_checks;
}
if let Some(incremental) = toml.incremental {
profile.incremental = incremental;
}
}
/// Profile settings used to determine which compiler flags to use for a
/// target.
#[derive(Clone, Copy, Eq, PartialOrd, Ord)]
pub struct Profile {
pub name: &'static str,
pub opt_level: InternedString,
pub lto: Lto,
// `None` means use rustc default.
pub codegen_units: Option<u32>,
pub debuginfo: Option<u32>,
pub debug_assertions: bool,
pub overflow_checks: bool,
pub rpath: bool,
pub incremental: bool,
pub panic: PanicStrategy,
}
impl Default for Profile {
fn default() -> Profile {
Profile {
name: "",
opt_level: InternedString::new("0"),
lto: Lto::Bool(false),
codegen_units: None,
debuginfo: None,
debug_assertions: false,
overflow_checks: false,
rpath: false,
incremental: false,
panic: PanicStrategy::Unwind,
}
}
}
compact_debug! {
impl fmt::Debug for Profile {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let (default, default_name) = match self.name {
"dev" => (Profile::default_dev(), "default_dev()"),
"release" => (Profile::default_release(), "default_release()"),
"test" => (Profile::default_test(), "default_test()"),
"bench" => (Profile::default_bench(), "default_bench()"),
"doc" => (Profile::default_doc(), "default_doc()"),
_ => (Profile::default(), "default()"),
};
[debug_the_fields(
name
opt_level
lto
codegen_units
debuginfo
debug_assertions
overflow_checks
rpath
incremental
panic
)]
}
}
}
impl fmt::Display for Profile {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Profile({})", self.name)
}
}
impl hash::Hash for Profile {
fn hash<H>(&self, state: &mut H)
where
H: hash::Hasher,
{
self.comparable().hash(state);
}
}
impl cmp::PartialEq for Profile {
fn eq(&self, other: &Self) -> bool {
self.comparable() == other.comparable()
}
}
impl Profile {
fn default_dev() -> Profile {
Profile {
name: "dev",
debuginfo: Some(2),
debug_assertions: true,
overflow_checks: true,
incremental: true,
..Profile::default()
}
}
fn default_release() -> Profile {
Profile {
name: "release",
opt_level: InternedString::new("3"),
..Profile::default()
}
}
fn default_test() -> Profile {
Profile {
name: "test",
..Profile::default_dev()
}
}
fn default_bench() -> Profile {
Profile {
name: "bench",
..Profile::default_release()
}
}
fn default_doc() -> Profile {
Profile {
name: "doc",
..Profile::default_dev()
}
}
/// 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.
fn comparable(
&self,
) -> (
InternedString,
Lto,
Option<u32>,
Option<u32>,
bool,
bool,
bool,
bool,
PanicStrategy,
) {
(
self.opt_level,
self.lto,
self.codegen_units,
self.debuginfo,
self.debug_assertions,
self.overflow_checks,
self.rpath,
self.incremental,
self.panic,
)
}
}
/// The link-time-optimization setting.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)]
pub enum Lto {
/// False = no LTO
/// True = "Fat" LTO
Bool(bool),
/// Named LTO settings like "thin".
Named(InternedString),
}
/// The `panic` setting.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)]
pub enum PanicStrategy {
Unwind,
Abort,
}
impl fmt::Display for PanicStrategy {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
PanicStrategy::Unwind => "unwind",
PanicStrategy::Abort => "abort",
}
.fmt(f)
}
}
/// Flags used in creating `Unit`s to indicate the purpose for the target, and
/// to ensure the target's dependencies have the correct settings.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct UnitFor {
/// A target for `build.rs` or any of its dependencies, or a proc-macro or
/// any of its dependencies. This enables `build-override` profiles for
/// these targets.
build: bool,
/// This is true if it is *allowed* to set the `panic=abort` flag. Currently
/// this is false for test/bench targets and all their dependencies, and
/// "for_host" units such as proc macro and custom build scripts and their
/// dependencies.
panic_abort_ok: bool,
}
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 {
UnitFor {
build: false,
panic_abort_ok: true,
}
}
/// A unit for a custom build script or its dependencies.
pub fn new_build() -> UnitFor {
UnitFor {
build: true,
panic_abort_ok: false,
}
}
/// A unit for a proc macro or compiler plugin or their dependencies.
pub fn new_compiler() -> UnitFor {
UnitFor {
build: false,
panic_abort_ok: false,
}
}
/// A unit for a test/bench target or their dependencies.
pub fn new_test() -> UnitFor {
UnitFor {
build: false,
panic_abort_ok: false,
}
}
/// Creates a variant based on `for_host` setting.
///
/// When `for_host` is true, this clears `panic_abort_ok` in a sticky fashion so
/// that all its dependencies also have `panic_abort_ok=false`.
pub fn with_for_host(self, for_host: bool) -> UnitFor {
UnitFor {
build: self.build || for_host,
panic_abort_ok: self.panic_abort_ok && !for_host,
}
}
/// Returns `true` if this unit is for a custom build script or one of its
/// dependencies.
pub fn is_build(self) -> bool {
self.build
}
/// Returns `true` if this unit is allowed to set the `panic` compiler flag.
pub fn is_panic_abort_ok(self) -> bool {
self.panic_abort_ok
}
/// 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,
},
];
&ALL
}
}
/// Profiles loaded from `.cargo/config` files.
#[derive(Clone, Debug, Deserialize, Default)]
pub struct ConfigProfiles {
dev: Option<TomlProfile>,
release: Option<TomlProfile>,
}
impl ConfigProfiles {
pub fn validate(&self, features: &Features, warnings: &mut Vec<String>) -> 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")
})?;
}
Ok(())
}
}