summaryrefslogtreecommitdiffstats
path: root/src/cargo/core/profiles.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/cargo/core/profiles.rs')
-rw-r--r--src/cargo/core/profiles.rs1358
1 files changed, 1358 insertions, 0 deletions
diff --git a/src/cargo/core/profiles.rs b/src/cargo/core/profiles.rs
new file mode 100644
index 0000000..51d19e3
--- /dev/null
+++ b/src/cargo/core/profiles.rs
@@ -0,0 +1,1358 @@
+//! # Profiles: built-in and customizable compiler flag presets
+//!
+//! [`Profiles`] is a collections of built-in profiles, and profiles defined
+//! in the root manifest and configurations.
+//!
+//! To start using a profile, most of the time you start from [`Profiles::new`],
+//! which does the followings:
+//!
+//! - Create a `Profiles` by merging profiles from configs onto the profile
+//! from root manifest (see [`merge_config_profiles`]).
+//! - Add built-in profiles onto it (see [`Profiles::add_root_profiles`]).
+//! - Process profile inheritance for each profiles. (see [`Profiles::add_maker`]).
+//!
+//! Then you can query a [`Profile`] via [`Profiles::get_profile`], which respects
+//! the profile overridden hierarchy described in below. The [`Profile`] you get
+//! is basically an immutable struct containing the compiler flag presets.
+//!
+//! ## Profile overridden hierarchy
+//!
+//! Profile settings can be overridden for specific packages and build-time crates.
+//! The precedence is explained in [`ProfileMaker`].
+//! The algorithm happens within [`ProfileMaker::get_profile`].
+
+use crate::core::compiler::{CompileKind, CompileTarget, Unit};
+use crate::core::dependency::Artifact;
+use crate::core::resolver::features::FeaturesFor;
+use crate::core::{PackageId, PackageIdSpec, Resolve, Shell, Target, Workspace};
+use crate::util::interning::InternedString;
+use crate::util::toml::{ProfilePackageSpec, StringOrBool, TomlProfile, TomlProfiles, U32OrBool};
+use crate::util::{closest_msg, config, CargoResult, Config};
+use anyhow::{bail, Context as _};
+use std::collections::{BTreeMap, HashMap, HashSet};
+use std::hash::Hash;
+use std::{cmp, fmt, hash};
+
+/// Collection of all profiles.
+///
+/// To get a specific [`Profile`], you usually create this and call [`get_profile`] then.
+///
+/// [`get_profile`]: Profiles::get_profile
+#[derive(Clone, Debug)]
+pub struct Profiles {
+ /// Incremental compilation can be overridden globally via:
+ /// - `CARGO_INCREMENTAL` environment variable.
+ /// - `build.incremental` config value.
+ incremental: Option<bool>,
+ /// Map of profile name to directory name for that profile.
+ dir_names: HashMap<InternedString, InternedString>,
+ /// The profile makers. Key is the profile name.
+ by_name: HashMap<InternedString, ProfileMaker>,
+ /// The original profiles written by the user in the manifest and config.
+ ///
+ /// This is here to assist with error reporting, as the `ProfileMaker`
+ /// values have the inherits chains all merged together.
+ original_profiles: BTreeMap<InternedString, TomlProfile>,
+ /// The profile the user requested to use.
+ requested_profile: InternedString,
+ /// The host target for rustc being used by this `Profiles`.
+ rustc_host: InternedString,
+}
+
+impl Profiles {
+ pub fn new(ws: &Workspace<'_>, requested_profile: InternedString) -> CargoResult<Profiles> {
+ let config = ws.config();
+ let incremental = match config.get_env_os("CARGO_INCREMENTAL") {
+ Some(v) => Some(v == "1"),
+ None => config.build_config()?.incremental,
+ };
+ let mut profiles = merge_config_profiles(ws, requested_profile)?;
+ let rustc_host = ws.config().load_global_rustc(Some(ws))?.host;
+
+ let mut profile_makers = Profiles {
+ incremental,
+ dir_names: Self::predefined_dir_names(),
+ by_name: HashMap::new(),
+ original_profiles: profiles.clone(),
+ requested_profile,
+ rustc_host,
+ };
+
+ Self::add_root_profiles(&mut profile_makers, &profiles);
+
+ // Merge with predefined profiles.
+ use std::collections::btree_map::Entry;
+ for (predef_name, mut predef_prof) in Self::predefined_profiles().into_iter() {
+ match profiles.entry(InternedString::new(predef_name)) {
+ Entry::Vacant(vac) => {
+ vac.insert(predef_prof);
+ }
+ Entry::Occupied(mut oc) => {
+ // Override predefined with the user-provided Toml.
+ let r = oc.get_mut();
+ predef_prof.merge(r);
+ *r = predef_prof;
+ }
+ }
+ }
+
+ for (name, profile) in &profiles {
+ profile_makers.add_maker(*name, profile, &profiles)?;
+ }
+ // Verify that the requested profile is defined *somewhere*.
+ // This simplifies the API (no need for CargoResult), and enforces
+ // assumptions about how config profiles are loaded.
+ profile_makers.get_profile_maker(requested_profile)?;
+ Ok(profile_makers)
+ }
+
+ /// Returns the hard-coded directory names for built-in profiles.
+ fn predefined_dir_names() -> HashMap<InternedString, InternedString> {
+ [
+ (InternedString::new("dev"), InternedString::new("debug")),
+ (InternedString::new("test"), InternedString::new("debug")),
+ (InternedString::new("bench"), InternedString::new("release")),
+ ]
+ .into()
+ }
+
+ /// Initialize `by_name` with the two "root" profiles, `dev`, and
+ /// `release` given the user's definition.
+ fn add_root_profiles(
+ profile_makers: &mut Profiles,
+ profiles: &BTreeMap<InternedString, TomlProfile>,
+ ) {
+ profile_makers.by_name.insert(
+ InternedString::new("dev"),
+ ProfileMaker::new(Profile::default_dev(), profiles.get("dev").cloned()),
+ );
+
+ profile_makers.by_name.insert(
+ InternedString::new("release"),
+ ProfileMaker::new(Profile::default_release(), profiles.get("release").cloned()),
+ );
+ }
+
+ /// Returns the built-in profiles (not including dev/release, which are
+ /// "root" profiles).
+ fn predefined_profiles() -> Vec<(&'static str, TomlProfile)> {
+ vec![
+ (
+ "bench",
+ TomlProfile {
+ inherits: Some(InternedString::new("release")),
+ ..TomlProfile::default()
+ },
+ ),
+ (
+ "test",
+ TomlProfile {
+ inherits: Some(InternedString::new("dev")),
+ ..TomlProfile::default()
+ },
+ ),
+ (
+ "doc",
+ TomlProfile {
+ inherits: Some(InternedString::new("dev")),
+ ..TomlProfile::default()
+ },
+ ),
+ ]
+ }
+
+ /// Creates a `ProfileMaker`, and inserts it into `self.by_name`.
+ fn add_maker(
+ &mut self,
+ name: InternedString,
+ profile: &TomlProfile,
+ profiles: &BTreeMap<InternedString, TomlProfile>,
+ ) -> CargoResult<()> {
+ match &profile.dir_name {
+ None => {}
+ Some(dir_name) => {
+ self.dir_names.insert(name, dir_name.to_owned());
+ }
+ }
+
+ // dev/release are "roots" and don't inherit.
+ if name == "dev" || name == "release" {
+ if profile.inherits.is_some() {
+ bail!(
+ "`inherits` must not be specified in root profile `{}`",
+ name
+ );
+ }
+ // Already inserted from `add_root_profiles`, no need to do anything.
+ return Ok(());
+ }
+
+ // Keep track for inherits cycles.
+ let mut set = HashSet::new();
+ set.insert(name);
+ let maker = self.process_chain(name, profile, &mut set, profiles)?;
+ self.by_name.insert(name, maker);
+ Ok(())
+ }
+
+ /// Build a `ProfileMaker` by recursively following the `inherits` setting.
+ ///
+ /// * `name`: The name of the profile being processed.
+ /// * `profile`: The TOML profile being processed.
+ /// * `set`: Set of profiles that have been visited, used to detect cycles.
+ /// * `profiles`: Map of all TOML profiles.
+ ///
+ /// Returns a `ProfileMaker` to be used for the given named profile.
+ fn process_chain(
+ &mut self,
+ name: InternedString,
+ profile: &TomlProfile,
+ set: &mut HashSet<InternedString>,
+ profiles: &BTreeMap<InternedString, TomlProfile>,
+ ) -> CargoResult<ProfileMaker> {
+ let mut maker = match profile.inherits {
+ Some(inherits_name) if inherits_name == "dev" || inherits_name == "release" => {
+ // These are the root profiles added in `add_root_profiles`.
+ self.get_profile_maker(inherits_name).unwrap().clone()
+ }
+ Some(inherits_name) => {
+ if !set.insert(inherits_name) {
+ bail!(
+ "profile inheritance loop detected with profile `{}` inheriting `{}`",
+ name,
+ inherits_name
+ );
+ }
+
+ match profiles.get(&inherits_name) {
+ None => {
+ bail!(
+ "profile `{}` inherits from `{}`, but that profile is not defined",
+ name,
+ inherits_name
+ );
+ }
+ Some(parent) => self.process_chain(inherits_name, parent, set, profiles)?,
+ }
+ }
+ None => {
+ bail!(
+ "profile `{}` is missing an `inherits` directive \
+ (`inherits` is required for all profiles except `dev` or `release`)",
+ name
+ );
+ }
+ };
+ match &mut maker.toml {
+ Some(toml) => toml.merge(profile),
+ None => maker.toml = Some(profile.clone()),
+ };
+ Ok(maker)
+ }
+
+ /// 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,
+ is_local: bool,
+ unit_for: UnitFor,
+ kind: CompileKind,
+ ) -> Profile {
+ let maker = self.get_profile_maker(self.requested_profile).unwrap();
+ let mut profile = maker.get_profile(Some(pkg_id), is_member, unit_for.is_for_host());
+
+ // Dealing with `panic=abort` and `panic=unwind` requires some special
+ // treatment. Be sure to process all the various options here.
+ match unit_for.panic_setting() {
+ PanicSetting::AlwaysUnwind => profile.panic = PanicStrategy::Unwind,
+ PanicSetting::ReadProfile => {}
+ }
+
+ // Default macOS debug information to being stored in the "unpacked"
+ // split-debuginfo format. At the time of this writing that's the only
+ // platform which has a stable `-Csplit-debuginfo` option for rustc,
+ // and it's typically much faster than running `dsymutil` on all builds
+ // in incremental cases.
+ if let Some(debug) = profile.debuginfo.to_option() {
+ if profile.split_debuginfo.is_none() && debug > 0 {
+ let target = match &kind {
+ CompileKind::Host => self.rustc_host.as_str(),
+ CompileKind::Target(target) => target.short_name(),
+ };
+ if target.contains("-apple-") {
+ profile.split_debuginfo = Some(InternedString::new("unpacked"));
+ }
+ }
+ }
+
+ // 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 !is_local {
+ profile.incremental = false;
+ }
+ profile.name = self.requested_profile;
+ 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.name = for_unit_profile.name;
+ result.root = for_unit_profile.root;
+ result.debuginfo = for_unit_profile.debuginfo;
+ result.opt_level = for_unit_profile.opt_level;
+ result
+ }
+
+ /// This returns the 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) -> Profile {
+ let profile_name = self.requested_profile;
+ let maker = self.get_profile_maker(profile_name).unwrap();
+ maker.get_profile(None, /*is_member*/ true, /*is_for_host*/ false)
+ }
+
+ /// Gets the directory name for a profile, like `debug` or `release`.
+ pub fn get_dir_name(&self) -> InternedString {
+ *self
+ .dir_names
+ .get(&self.requested_profile)
+ .unwrap_or(&self.requested_profile)
+ }
+
+ /// Used to check for overrides for non-existing packages.
+ pub fn validate_packages(
+ &self,
+ profiles: Option<&TomlProfiles>,
+ shell: &mut Shell,
+ resolve: &Resolve,
+ ) -> CargoResult<()> {
+ for (name, profile) in &self.by_name {
+ // If the user did not specify an override, skip this. This is here
+ // to avoid generating errors for inherited profiles which don't
+ // specify package overrides. The `by_name` profile has had the inherits
+ // chain merged, so we need to look at the original source to check
+ // if an override was specified.
+ if self
+ .original_profiles
+ .get(name)
+ .and_then(|orig| orig.package.as_ref())
+ .is_none()
+ {
+ continue;
+ }
+ let found = validate_packages_unique(resolve, name, &profile.toml)?;
+ // We intentionally do not validate unmatched packages for config
+ // profiles, in case they are defined in a central location. This
+ // iterates over the manifest profiles only.
+ if let Some(profiles) = profiles {
+ if let Some(toml_profile) = profiles.get(name) {
+ validate_packages_unmatched(shell, resolve, name, toml_profile, &found)?;
+ }
+ }
+ }
+ Ok(())
+ }
+
+ /// Returns the profile maker for the given profile name.
+ fn get_profile_maker(&self, name: InternedString) -> CargoResult<&ProfileMaker> {
+ self.by_name
+ .get(&name)
+ .ok_or_else(|| anyhow::format_err!("profile `{}` is not defined", name))
+ }
+}
+
+/// An object used for handling the profile hierarchy.
+///
+/// The precedence of profiles are (first one wins):
+///
+/// - Profiles in `.cargo/config` files (using same order as below).
+/// - `[profile.dev.package.name]` -- a named package.
+/// - `[profile.dev.package."*"]` -- 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 TOML profile defined in `Cargo.toml` or config.
+ ///
+ /// This is None if the user did not specify one, in which case the
+ /// `default` is used. Note that the built-in defaults for test/bench/doc
+ /// always set this since they need to declare the `inherits` value.
+ toml: Option<TomlProfile>,
+}
+
+impl ProfileMaker {
+ /// Creates a new `ProfileMaker`.
+ ///
+ /// Note that this does not process `inherits`, the caller is responsible for that.
+ fn new(default: Profile, toml: Option<TomlProfile>) -> ProfileMaker {
+ ProfileMaker { default, toml }
+ }
+
+ /// Generates a new `Profile`.
+ fn get_profile(
+ &self,
+ pkg_id: Option<PackageId>,
+ is_member: bool,
+ is_for_host: bool,
+ ) -> Profile {
+ let mut profile = self.default.clone();
+
+ // First apply profile-specific settings, things like
+ // `[profile.release]`
+ if let Some(toml) = &self.toml {
+ merge_profile(&mut profile, toml);
+ }
+
+ // Next start overriding those settings. First comes build dependencies
+ // which default to opt-level 0...
+ if is_for_host {
+ // For-host units are things like procedural macros, build scripts, and
+ // their dependencies. For these units most projects simply want them
+ // to compile quickly and the runtime doesn't matter too much since
+ // they tend to process very little data. For this reason we default
+ // them to a "compile as quickly as possible" mode which for now means
+ // basically turning down the optimization level and avoid limiting
+ // codegen units. This ensures that we spend little time optimizing as
+ // well as enabling parallelism by not constraining codegen units.
+ profile.opt_level = InternedString::new("0");
+ profile.codegen_units = None;
+
+ // For build dependencies, we usually don't need debuginfo, and
+ // removing it will compile faster. However, that can conflict with
+ // a unit graph optimization, reusing units that are shared between
+ // build dependencies and runtime dependencies: when the runtime
+ // target is the same as the build host, we only need to build a
+ // dependency once and reuse the results, instead of building twice.
+ // We defer the choice of the debuginfo level until we can check if
+ // a unit is shared. If that's the case, we'll use the deferred value
+ // below so the unit can be reused, otherwise we can avoid emitting
+ // the unit's debuginfo.
+ if let Some(debuginfo) = profile.debuginfo.to_option() {
+ profile.debuginfo = DebugInfo::Deferred(debuginfo);
+ }
+ }
+ // ... and next comes any other sorts of overrides specified in
+ // profiles, such as `[profile.release.build-override]` or
+ // `[profile.release.package.foo]`
+ if let Some(toml) = &self.toml {
+ merge_toml_overrides(pkg_id, is_member, is_for_host, &mut profile, toml);
+ }
+ profile
+ }
+}
+
+/// Merge package and build overrides from the given TOML profile into the given `Profile`.
+fn merge_toml_overrides(
+ pkg_id: Option<PackageId>,
+ is_member: bool,
+ is_for_host: bool,
+ profile: &mut Profile,
+ toml: &TomlProfile,
+) {
+ if is_for_host {
+ if let Some(build_override) = &toml.build_override {
+ merge_profile(profile, build_override);
+ }
+ }
+ if let Some(overrides) = toml.package.as_ref() {
+ 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 package profile overrides",
+ pkg_id
+ );
+ }
+ }
+ }
+}
+
+/// Merge the given TOML profile into the given `Profile`.
+///
+/// Does not merge overrides (see `merge_toml_overrides`).
+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)) if is_off(n.as_str()) => profile.lto = Lto::Off,
+ Some(StringOrBool::String(ref n)) => profile.lto = Lto::Named(InternedString::new(n)),
+ None => {}
+ }
+ if toml.codegen_backend.is_some() {
+ profile.codegen_backend = toml.codegen_backend;
+ }
+ if toml.codegen_units.is_some() {
+ profile.codegen_units = toml.codegen_units;
+ }
+ match toml.debug {
+ Some(U32OrBool::U32(debug)) => profile.debuginfo = DebugInfo::Explicit(debug),
+ Some(U32OrBool::Bool(true)) => profile.debuginfo = DebugInfo::Explicit(2),
+ Some(U32OrBool::Bool(false)) => profile.debuginfo = DebugInfo::None,
+ None => {}
+ }
+ if let Some(debug_assertions) = toml.debug_assertions {
+ profile.debug_assertions = debug_assertions;
+ }
+ if let Some(split_debuginfo) = &toml.split_debuginfo {
+ profile.split_debuginfo = Some(InternedString::new(split_debuginfo));
+ }
+ 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;
+ }
+ if let Some(flags) = &toml.rustflags {
+ profile.rustflags = flags.clone();
+ }
+ profile.strip = match toml.strip {
+ Some(StringOrBool::Bool(true)) => Strip::Named(InternedString::new("symbols")),
+ None | Some(StringOrBool::Bool(false)) => Strip::None,
+ Some(StringOrBool::String(ref n)) if n.as_str() == "none" => Strip::None,
+ Some(StringOrBool::String(ref n)) => Strip::Named(InternedString::new(n)),
+ };
+}
+
+/// The root profile (dev/release).
+///
+/// This is currently only used for the `PROFILE` env var for build scripts
+/// for backwards compatibility. We should probably deprecate `PROFILE` and
+/// encourage using things like `DEBUG` and `OPT_LEVEL` instead.
+#[derive(Clone, Copy, Eq, PartialOrd, Ord, PartialEq, Debug)]
+pub enum ProfileRoot {
+ Release,
+ Debug,
+}
+
+/// Profile settings used to determine which compiler flags to use for a
+/// target.
+#[derive(Clone, Eq, PartialOrd, Ord, serde::Serialize)]
+pub struct Profile {
+ pub name: InternedString,
+ pub opt_level: InternedString,
+ #[serde(skip)] // named profiles are unstable
+ pub root: ProfileRoot,
+ pub lto: Lto,
+ // `None` means use rustc default.
+ pub codegen_backend: Option<InternedString>,
+ // `None` means use rustc default.
+ pub codegen_units: Option<u32>,
+ pub debuginfo: DebugInfo,
+ pub split_debuginfo: Option<InternedString>,
+ pub debug_assertions: bool,
+ pub overflow_checks: bool,
+ pub rpath: bool,
+ pub incremental: bool,
+ pub panic: PanicStrategy,
+ pub strip: Strip,
+ #[serde(skip_serializing_if = "Vec::is_empty")] // remove when `rustflags` is stablized
+ // Note that `rustflags` is used for the cargo-feature `profile_rustflags`
+ pub rustflags: Vec<InternedString>,
+}
+
+impl Default for Profile {
+ fn default() -> Profile {
+ Profile {
+ name: InternedString::new(""),
+ opt_level: InternedString::new("0"),
+ root: ProfileRoot::Debug,
+ lto: Lto::Bool(false),
+ codegen_backend: None,
+ codegen_units: None,
+ debuginfo: DebugInfo::None,
+ debug_assertions: false,
+ split_debuginfo: None,
+ overflow_checks: false,
+ rpath: false,
+ incremental: false,
+ panic: PanicStrategy::Unwind,
+ strip: Strip::None,
+ rustflags: vec![],
+ }
+ }
+}
+
+compact_debug! {
+ impl fmt::Debug for Profile {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let (default, default_name) = match self.name.as_str() {
+ "dev" => (Profile::default_dev(), "default_dev()"),
+ "release" => (Profile::default_release(), "default_release()"),
+ _ => (Profile::default(), "default()"),
+ };
+ [debug_the_fields(
+ name
+ opt_level
+ lto
+ root
+ codegen_backend
+ codegen_units
+ debuginfo
+ split_debuginfo
+ debug_assertions
+ overflow_checks
+ rpath
+ incremental
+ panic
+ strip
+ rustflags
+ )]
+ }
+ }
+}
+
+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 {
+ /// Returns a built-in `dev` profile.
+ fn default_dev() -> Profile {
+ Profile {
+ name: InternedString::new("dev"),
+ root: ProfileRoot::Debug,
+ debuginfo: DebugInfo::Explicit(2),
+ debug_assertions: true,
+ overflow_checks: true,
+ incremental: true,
+ ..Profile::default()
+ }
+ }
+
+ /// Returns a built-in `release` profile.
+ fn default_release() -> Profile {
+ Profile {
+ name: InternedString::new("release"),
+ root: ProfileRoot::Release,
+ opt_level: InternedString::new("3"),
+ ..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.
+ fn comparable(&self) -> impl Hash + Eq {
+ (
+ self.opt_level,
+ self.lto,
+ self.codegen_backend,
+ self.codegen_units,
+ self.debuginfo,
+ self.split_debuginfo,
+ self.debug_assertions,
+ self.overflow_checks,
+ self.rpath,
+ self.incremental,
+ self.panic,
+ self.strip,
+ )
+ }
+}
+
+/// The debuginfo level setting.
+///
+/// This is semantically an `Option<u32>`, and should be used as so via the
+/// [DebugInfo::to_option] method for all intents and purposes:
+/// - `DebugInfo::None` corresponds to `None`
+/// - `DebugInfo::Explicit(u32)` and `DebugInfo::Deferred` correspond to
+/// `Option<u32>::Some`
+///
+/// Internally, it's used to model a debuginfo level whose value can be deferred
+/// for optimization purposes: host dependencies usually don't need the same
+/// level as target dependencies. For dependencies that are shared between the
+/// two however, that value also affects reuse: different debuginfo levels would
+/// cause to build a unit twice. By deferring the choice until we know
+/// whether to choose the optimized value or the default value, we can make sure
+/// the unit is only built once and the unit graph is still optimized.
+#[derive(Debug, Copy, Clone, serde::Serialize)]
+#[serde(untagged)]
+pub enum DebugInfo {
+ /// No debuginfo level was set.
+ None,
+ /// A debuginfo level that is explicitly set, by a profile or a user.
+ Explicit(u32),
+ /// For internal purposes: a deferred debuginfo level that can be optimized
+ /// away, but has this value otherwise.
+ ///
+ /// Behaves like `Explicit` in all situations except for the default build
+ /// dependencies profile: whenever a build dependency is not shared with
+ /// runtime dependencies, this level is weakened to a lower level that is
+ /// faster to build (see [DebugInfo::weaken]).
+ ///
+ /// In all other situations, this level value will be the one to use.
+ Deferred(u32),
+}
+
+impl DebugInfo {
+ /// The main way to interact with this debuginfo level, turning it into an Option.
+ pub fn to_option(&self) -> Option<u32> {
+ match self {
+ DebugInfo::None => None,
+ DebugInfo::Explicit(v) | DebugInfo::Deferred(v) => Some(*v),
+ }
+ }
+
+ /// Returns true if the debuginfo level is high enough (at least 1). Helper
+ /// for a common operation on the usual `Option` representation.
+ pub(crate) fn is_turned_on(&self) -> bool {
+ self.to_option().unwrap_or(0) != 0
+ }
+
+ pub(crate) fn is_deferred(&self) -> bool {
+ matches!(self, DebugInfo::Deferred(_))
+ }
+
+ /// Force the deferred, preferred, debuginfo level to a finalized explicit value.
+ pub(crate) fn finalize(self) -> Self {
+ match self {
+ DebugInfo::Deferred(v) => DebugInfo::Explicit(v),
+ _ => self,
+ }
+ }
+
+ /// Reset to the lowest level: no debuginfo.
+ pub(crate) fn weaken(self) -> Self {
+ DebugInfo::None
+ }
+}
+
+impl PartialEq for DebugInfo {
+ fn eq(&self, other: &DebugInfo) -> bool {
+ self.to_option().eq(&other.to_option())
+ }
+}
+
+impl Eq for DebugInfo {}
+
+impl Hash for DebugInfo {
+ fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+ self.to_option().hash(state);
+ }
+}
+
+impl PartialOrd for DebugInfo {
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+ self.to_option().partial_cmp(&other.to_option())
+ }
+}
+
+impl Ord for DebugInfo {
+ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+ self.to_option().cmp(&other.to_option())
+ }
+}
+
+/// The link-time-optimization setting.
+#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)]
+pub enum Lto {
+ /// Explicitly no LTO, disables thin-LTO.
+ Off,
+ /// True = "Fat" LTO
+ /// False = rustc default (no args), currently "thin LTO"
+ Bool(bool),
+ /// Named LTO settings like "thin".
+ Named(InternedString),
+}
+
+impl serde::ser::Serialize for Lto {
+ fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
+ where
+ S: serde::ser::Serializer,
+ {
+ match self {
+ Lto::Off => "off".serialize(s),
+ Lto::Bool(b) => b.to_string().serialize(s),
+ Lto::Named(n) => n.serialize(s),
+ }
+ }
+}
+
+/// The `panic` setting.
+#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, PartialOrd, Ord, serde::Serialize)]
+#[serde(rename_all = "lowercase")]
+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)
+ }
+}
+
+/// The setting for choosing which symbols to strip
+#[derive(
+ Clone, Copy, PartialEq, Eq, Debug, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
+)]
+#[serde(rename_all = "lowercase")]
+pub enum Strip {
+ /// Don't remove any symbols
+ None,
+ /// Named Strip settings
+ Named(InternedString),
+}
+
+impl fmt::Display for Strip {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match *self {
+ Strip::None => "none",
+ Strip::Named(s) => s.as_str(),
+ }
+ .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.
+///
+/// This means these are passed down from the root of the dependency tree to apply
+/// to most child dependencies.
+#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
+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.
+ ///
+ /// An invariant is that if `host_features` is true, `host` must be true.
+ ///
+ /// Note that this is `true` for `RunCustomBuild` units, even though that
+ /// unit should *not* use build-override profiles. This is a bit of a
+ /// special case. When computing the `RunCustomBuild` unit, it manually
+ /// uses the `get_profile_run_custom_build` method to get the correct
+ /// profile information for the unit. `host` needs to be true so that all
+ /// of the dependencies of that `RunCustomBuild` unit have this flag be
+ /// sticky (and forced to `true` for all further dependencies) — which is
+ /// the whole point of `UnitFor`.
+ host: bool,
+ /// A target for a build dependency or proc-macro (or any of its
+ /// dependencies). This is used for computing features of build
+ /// dependencies and proc-macros independently of other dependency kinds.
+ ///
+ /// The subtle difference between this and `host` is that the build script
+ /// for a non-host package sets this to `false` because it wants the
+ /// features of the non-host package (whereas `host` is true because the
+ /// build script is being built for the host). `host_features` becomes
+ /// `true` for build-dependencies or proc-macros, or any of their
+ /// dependencies. For example, with this dependency tree:
+ ///
+ /// ```text
+ /// foo
+ /// ├── foo build.rs
+ /// │ └── shared_dep (BUILD dependency)
+ /// │ └── shared_dep build.rs
+ /// └── shared_dep (Normal dependency)
+ /// └── shared_dep build.rs
+ /// ```
+ ///
+ /// In this example, `foo build.rs` is HOST=true, HOST_FEATURES=false.
+ /// This is so that `foo build.rs` gets the profile settings for build
+ /// scripts (HOST=true) and features of foo (HOST_FEATURES=false) because
+ /// build scripts need to know which features their package is being built
+ /// with.
+ ///
+ /// But in the case of `shared_dep`, when built as a build dependency,
+ /// both flags are true (it only wants the build-dependency features).
+ /// When `shared_dep` is built as a normal dependency, then `shared_dep
+ /// build.rs` is HOST=true, HOST_FEATURES=false for the same reasons that
+ /// foo's build script is set that way.
+ host_features: bool,
+ /// How Cargo processes the `panic` setting or profiles.
+ panic_setting: PanicSetting,
+
+ /// The compile kind of the root unit for which artifact dependencies are built.
+ /// This is required particularly for the `target = "target"` setting of artifact
+ /// dependencies which mean to inherit the `--target` specified on the command-line.
+ /// However, that is a multi-value argument and root units are already created to
+ /// reflect one unit per --target. Thus we have to build one artifact with the
+ /// correct target for each of these trees.
+ /// Note that this will always be set as we don't initially know if there are
+ /// artifacts that make use of it.
+ root_compile_kind: CompileKind,
+
+ /// This is only set for artifact dependencies which have their
+ /// `<target-triple>|target` set.
+ /// If so, this information is used as part of the key for resolving their features,
+ /// allowing for target-dependent feature resolution within the entire dependency tree.
+ /// Note that this target corresponds to the target used to build the units in that
+ /// dependency tree, too, but this copy of it is specifically used for feature lookup.
+ artifact_target_for_features: Option<CompileTarget>,
+}
+
+/// How Cargo processes the `panic` setting or profiles.
+///
+/// This is done to handle test/benches inheriting from dev/release,
+/// as well as forcing `for_host` units to always unwind.
+/// It also interacts with [`-Z panic-abort-tests`].
+///
+/// [`-Z panic-abort-tests`]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#panic-abort-tests
+#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
+enum PanicSetting {
+ /// Used to force a unit to always be compiled with the `panic=unwind`
+ /// strategy, notably for build scripts, proc macros, etc.
+ AlwaysUnwind,
+
+ /// Indicates that this unit will read its `profile` setting and use
+ /// whatever is configured there.
+ ReadProfile,
+}
+
+impl UnitFor {
+ /// A unit for a normal target/dependency (i.e., not custom build,
+ /// proc macro/plugin, or test/bench).
+ pub fn new_normal(root_compile_kind: CompileKind) -> UnitFor {
+ UnitFor {
+ host: false,
+ host_features: false,
+ panic_setting: PanicSetting::ReadProfile,
+ root_compile_kind,
+ artifact_target_for_features: None,
+ }
+ }
+
+ /// A unit for a custom build script or proc-macro or its dependencies.
+ ///
+ /// The `host_features` parameter is whether or not this is for a build
+ /// dependency or proc-macro (something that requires being built "on the
+ /// host"). Build scripts for non-host units should use `false` because
+ /// they want to use the features of the package they are running for.
+ pub fn new_host(host_features: bool, root_compile_kind: CompileKind) -> UnitFor {
+ UnitFor {
+ host: true,
+ host_features,
+ // Force build scripts to always use `panic=unwind` for now to
+ // maximally share dependencies with procedural macros.
+ panic_setting: PanicSetting::AlwaysUnwind,
+ root_compile_kind,
+ artifact_target_for_features: None,
+ }
+ }
+
+ /// A unit for a compiler plugin or their dependencies.
+ pub fn new_compiler(root_compile_kind: CompileKind) -> UnitFor {
+ UnitFor {
+ host: false,
+ // The feature resolver doesn't know which dependencies are
+ // plugins, so for now plugins don't split features. Since plugins
+ // are mostly deprecated, just leave this as false.
+ host_features: false,
+ // Force plugins to use `panic=abort` so panics in the compiler do
+ // not abort the process but instead end with a reasonable error
+ // message that involves catching the panic in the compiler.
+ panic_setting: PanicSetting::AlwaysUnwind,
+ root_compile_kind,
+ artifact_target_for_features: None,
+ }
+ }
+
+ /// A unit for a test/bench target or their dependencies.
+ ///
+ /// Note that `config` is taken here for unstable CLI features to detect
+ /// whether `panic=abort` is supported for tests. Historical versions of
+ /// rustc did not support this, but newer versions do with an unstable
+ /// compiler flag.
+ pub fn new_test(config: &Config, root_compile_kind: CompileKind) -> UnitFor {
+ UnitFor {
+ host: false,
+ host_features: false,
+ // We're testing out an unstable feature (`-Zpanic-abort-tests`)
+ // which inherits the panic setting from the dev/release profile
+ // (basically avoid recompiles) but historical defaults required
+ // that we always unwound.
+ panic_setting: if config.cli_unstable().panic_abort_tests {
+ PanicSetting::ReadProfile
+ } else {
+ PanicSetting::AlwaysUnwind
+ },
+ root_compile_kind,
+ artifact_target_for_features: None,
+ }
+ }
+
+ /// This is a special case for unit tests of a proc-macro.
+ ///
+ /// Proc-macro unit tests are forced to be run on the host.
+ pub fn new_host_test(config: &Config, root_compile_kind: CompileKind) -> UnitFor {
+ let mut unit_for = UnitFor::new_test(config, root_compile_kind);
+ unit_for.host = true;
+ unit_for.host_features = true;
+ unit_for
+ }
+
+ /// Returns a new copy updated based on the target dependency.
+ ///
+ /// This is where the magic happens that the host/host_features settings
+ /// transition in a sticky fashion. As the dependency graph is being
+ /// built, once those flags are set, they stay set for the duration of
+ /// that portion of tree.
+ pub fn with_dependency(
+ self,
+ parent: &Unit,
+ dep_target: &Target,
+ root_compile_kind: CompileKind,
+ ) -> UnitFor {
+ // A build script or proc-macro transitions this to being built for the host.
+ let dep_for_host = dep_target.for_host();
+ // This is where feature decoupling of host versus target happens.
+ //
+ // Once host features are desired, they are always desired.
+ //
+ // A proc-macro should always use host features.
+ //
+ // Dependencies of a build script should use host features (subtle
+ // point: the build script itself does *not* use host features, that's
+ // why the parent is checked here, and not the dependency).
+ let host_features =
+ self.host_features || parent.target.is_custom_build() || dep_target.proc_macro();
+ // Build scripts and proc macros, and all of their dependencies are
+ // AlwaysUnwind.
+ let panic_setting = if dep_for_host {
+ PanicSetting::AlwaysUnwind
+ } else {
+ self.panic_setting
+ };
+ UnitFor {
+ host: self.host || dep_for_host,
+ host_features,
+ panic_setting,
+ root_compile_kind,
+ artifact_target_for_features: self.artifact_target_for_features,
+ }
+ }
+
+ pub fn for_custom_build(self) -> UnitFor {
+ UnitFor {
+ host: true,
+ host_features: self.host_features,
+ // Force build scripts to always use `panic=unwind` for now to
+ // maximally share dependencies with procedural macros.
+ panic_setting: PanicSetting::AlwaysUnwind,
+ root_compile_kind: self.root_compile_kind,
+ artifact_target_for_features: self.artifact_target_for_features,
+ }
+ }
+
+ /// Set the artifact compile target for use in features using the given `artifact`.
+ pub(crate) fn with_artifact_features(mut self, artifact: &Artifact) -> UnitFor {
+ self.artifact_target_for_features = artifact.target().and_then(|t| t.to_compile_target());
+ self
+ }
+
+ /// Set the artifact compile target as determined by a resolved compile target. This is used if `target = "target"`.
+ pub(crate) fn with_artifact_features_from_resolved_compile_kind(
+ mut self,
+ kind: Option<CompileKind>,
+ ) -> UnitFor {
+ self.artifact_target_for_features = kind.and_then(|kind| match kind {
+ CompileKind::Host => None,
+ CompileKind::Target(triple) => Some(triple),
+ });
+ self
+ }
+
+ /// Returns `true` if this unit is for a build script or any of its
+ /// dependencies, or a proc macro or any of its dependencies.
+ pub fn is_for_host(&self) -> bool {
+ self.host
+ }
+
+ pub fn is_for_host_features(&self) -> bool {
+ self.host_features
+ }
+
+ /// Returns how `panic` settings should be handled for this profile
+ fn panic_setting(&self) -> PanicSetting {
+ self.panic_setting
+ }
+
+ /// We might contain a parent artifact compile kind for features already, but will
+ /// gladly accept the one of this dependency as an override as it defines how
+ /// the artifact is built.
+ /// If we are an artifact but don't specify a `target`, we assume the default
+ /// compile kind that is suitable in this situation.
+ pub(crate) fn map_to_features_for(&self, dep_artifact: Option<&Artifact>) -> FeaturesFor {
+ FeaturesFor::from_for_host_or_artifact_target(
+ self.is_for_host_features(),
+ match dep_artifact {
+ Some(artifact) => artifact
+ .target()
+ .and_then(|t| t.to_resolved_compile_target(self.root_compile_kind)),
+ None => self.artifact_target_for_features,
+ },
+ )
+ }
+
+ pub(crate) fn root_compile_kind(&self) -> CompileKind {
+ self.root_compile_kind
+ }
+}
+
+/// Takes the manifest profiles, and overlays the config profiles on-top.
+///
+/// Returns a new copy of the profile map with all the mergers complete.
+fn merge_config_profiles(
+ ws: &Workspace<'_>,
+ requested_profile: InternedString,
+) -> CargoResult<BTreeMap<InternedString, TomlProfile>> {
+ let mut profiles = match ws.profiles() {
+ Some(profiles) => profiles.get_all().clone(),
+ None => BTreeMap::new(),
+ };
+ // Set of profile names to check if defined in config only.
+ let mut check_to_add = HashSet::new();
+ check_to_add.insert(requested_profile);
+ // Merge config onto manifest profiles.
+ for (name, profile) in &mut profiles {
+ if let Some(config_profile) = get_config_profile(ws, name)? {
+ profile.merge(&config_profile);
+ }
+ if let Some(inherits) = &profile.inherits {
+ check_to_add.insert(*inherits);
+ }
+ }
+ // Add the built-in profiles. This is important for things like `cargo
+ // test` which implicitly use the "dev" profile for dependencies.
+ for name in &["dev", "release", "test", "bench"] {
+ check_to_add.insert(InternedString::new(name));
+ }
+ // Add config-only profiles.
+ // Need to iterate repeatedly to get all the inherits values.
+ let mut current = HashSet::new();
+ while !check_to_add.is_empty() {
+ std::mem::swap(&mut current, &mut check_to_add);
+ for name in current.drain() {
+ if !profiles.contains_key(&name) {
+ if let Some(config_profile) = get_config_profile(ws, &name)? {
+ if let Some(inherits) = &config_profile.inherits {
+ check_to_add.insert(*inherits);
+ }
+ profiles.insert(name, config_profile);
+ }
+ }
+ }
+ }
+ Ok(profiles)
+}
+
+/// Helper for fetching a profile from config.
+fn get_config_profile(ws: &Workspace<'_>, name: &str) -> CargoResult<Option<TomlProfile>> {
+ let profile: Option<config::Value<TomlProfile>> =
+ ws.config().get(&format!("profile.{}", name))?;
+ let profile = match profile {
+ Some(profile) => profile,
+ None => return Ok(None),
+ };
+ let mut warnings = Vec::new();
+ profile
+ .val
+ .validate(
+ name,
+ ws.config().cli_unstable(),
+ ws.unstable_features(),
+ &mut warnings,
+ )
+ .with_context(|| {
+ format!(
+ "config profile `{}` is not valid (defined in `{}`)",
+ name, profile.definition
+ )
+ })?;
+ for warning in warnings {
+ ws.config().shell().warn(warning)?;
+ }
+ Ok(Some(profile.val))
+}
+
+/// Validate that a package does not match multiple package override specs.
+///
+/// For example `[profile.dev.package.bar]` and `[profile.dev.package."bar:0.5.0"]`
+/// would both match `bar:0.5.0` which would be ambiguous.
+fn validate_packages_unique(
+ resolve: &Resolve,
+ name: &str,
+ toml: &Option<TomlProfile>,
+) -> CargoResult<HashSet<PackageIdSpec>> {
+ let toml = match toml {
+ Some(ref toml) => toml,
+ None => return Ok(HashSet::new()),
+ };
+ let overrides = match toml.package.as_ref() {
+ Some(overrides) => overrides,
+ None => return Ok(HashSet::new()),
+ };
+ // Verify that a package doesn't match multiple spec overrides.
+ let mut found = HashSet::new();
+ for pkg_id in resolve.iter() {
+ 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(", ");
+ bail!(
+ "multiple package overrides in profile `{}` match package `{}`\n\
+ found package specs: {}",
+ name,
+ pkg_id,
+ specs
+ );
+ }
+ }
+ }
+ Ok(found)
+}
+
+/// Check for any profile override specs that do not match any known packages.
+///
+/// This helps check for typos and mistakes.
+fn validate_packages_unmatched(
+ shell: &mut Shell,
+ resolve: &Resolve,
+ name: &str,
+ toml: &TomlProfile,
+ found: &HashSet<PackageIdSpec>,
+) -> CargoResult<()> {
+ let overrides = match toml.package.as_ref() {
+ Some(overrides) => overrides,
+ None => 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> = resolve
+ .iter()
+ .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(), resolve.iter(), |p| p.name().as_str());
+ shell.warn(format!(
+ "profile package spec `{}` in profile `{}` did not match any packages{}",
+ spec, name, suggestion
+ ))?;
+ } else {
+ shell.warn(format!(
+ "profile package spec `{}` in profile `{}` \
+ has a version or URL that does not match any of the packages: {}",
+ spec,
+ name,
+ name_matches.join(", ")
+ ))?;
+ }
+ }
+ Ok(())
+}
+
+/// Returns `true` if a string is a toggle that turns an option off.
+fn is_off(s: &str) -> bool {
+ matches!(s, "off" | "n" | "no" | "none")
+}