| //! Handles 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::compiler::{CompileKind, CompileTarget, Unit}; | |
| use crate::context; | |
| use crate::resolver::Resolve; | |
| use crate::resolver::features::FeaturesFor; | |
| use crate::util::interning::InternedString; | |
| use crate::util::{CargoResult, GlobalContext, closest_msg}; | |
| use crate::workspace::dependency::Artifact; | |
| use crate::workspace::parser::validate_profile; | |
| use crate::workspace::{PackageId, PackageIdSpec, PackageIdSpecQuery, Target, Workspace}; | |
| use anyhow::{Context as _, bail}; | |
| use cargo_util::collections::{HashMap, HashSet}; | |
| use cargo_util::is_ci; | |
| use cargo_util_schemas::manifest::{Hints, TomlTrimPaths}; | |
| use cargo_util_schemas::manifest::{ | |
| ProfilePackageSpec, StringOrBool, TomlDebugInfo, TomlProfile, TomlProfiles, | |
| }; | |
| use cargo_util_terminal::Shell; | |
| use std::collections::BTreeMap; | |
| use std::hash::Hash; | |
| use std::ops::RangeInclusive; | |
| 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, | |
| /// Whether `-Zhint-min-opt-level` was passed, enabling the | |
| /// `hints.min-opt-level` manifest key. | |
| hint_min_opt_level: bool, | |
| } | |
| impl Profiles { | |
| pub fn new(ws: &Workspace<'_>, requested_profile: InternedString) -> CargoResult<Profiles> { | |
| let gctx = ws.gctx(); | |
| let incremental = match gctx.get_env_os("CARGO_INCREMENTAL") { | |
| Some(v) => Some(v == "1"), | |
| None => gctx | |
| .build_config()? | |
| .incremental | |
| .or_else(|| is_ci().then_some(false)), | |
| }; | |
| let mut profiles = merge_config_profiles(ws, requested_profile)?; | |
| let rustc_host = ws.gctx().load_global_rustc(Some(ws))?.host; | |
| let mut profile_makers = Profiles { | |
| incremental, | |
| dir_names: Self::predefined_dir_names(), | |
| by_name: HashMap::default(), | |
| original_profiles: profiles.clone(), | |
| requested_profile, | |
| rustc_host, | |
| hint_min_opt_level: gctx.cli_unstable().hint_min_opt_level, | |
| }; | |
| 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(predef_name.into()) { | |
| 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> { | |
| HashMap::from_iter([ | |
| ("dev".into(), "debug".into()), | |
| ("test".into(), "debug".into()), | |
| ("bench".into(), "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( | |
| "dev".into(), | |
| ProfileMaker::new(Profile::default_dev(), profiles.get("dev").cloned()), | |
| ); | |
| profile_makers.by_name.insert( | |
| "release".into(), | |
| 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![ | |
| ( | |
| "debug", | |
| TomlProfile { | |
| inherits: Some(String::from("dev")), | |
| debug: Some(TomlDebugInfo::Full), | |
| ..TomlProfile::default() | |
| }, | |
| ), | |
| ( | |
| "bench", | |
| TomlProfile { | |
| inherits: Some(String::from("release")), | |
| ..TomlProfile::default() | |
| }, | |
| ), | |
| ( | |
| "test", | |
| TomlProfile { | |
| inherits: Some(String::from("dev")), | |
| ..TomlProfile::default() | |
| }, | |
| ), | |
| ( | |
| "doc", | |
| TomlProfile { | |
| inherits: Some(String::from("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.into()); | |
| } | |
| } | |
| // 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::default(); | |
| 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) => { | |
| let inherits_name = inherits_name.into(); | |
| 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, | |
| pkg_hints: Option<&Hints>, | |
| is_member: bool, | |
| is_local: bool, | |
| unit_for: UnitFor, | |
| kind: CompileKind, | |
| ) -> Profile { | |
| let maker = self.get_profile_maker(&self.requested_profile).unwrap(); | |
| let min_opt_level = if self.hint_min_opt_level { | |
| parse_min_opt_level_hint(pkg_hints.and_then(|hints| hints.min_opt_level.as_ref())) | |
| .ok() | |
| .flatten() | |
| } else { | |
| // The `min_opt_level_hint` parse pass rule warns that the hint is being ignored. | |
| None | |
| }; | |
| let mut profile = maker.get_profile( | |
| Some(pkg_id), | |
| is_member, | |
| unit_for.is_for_host(), | |
| min_opt_level, | |
| ); | |
| // 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 profile.debuginfo.is_turned_on() && profile.split_debuginfo.is_none() { | |
| 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("unpacked".into()); | |
| } | |
| } | |
| // 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.debug_assertions = for_unit_profile.debug_assertions; | |
| result.trim_paths = for_unit_profile.trim_paths.clone(); | |
| 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, None, | |
| ) | |
| } | |
| /// 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: &str) -> CargoResult<&ProfileMaker> { | |
| self.by_name | |
| .get(name) | |
| .ok_or_else(|| anyhow::format_err!("profile `{}` is not defined", name)) | |
| } | |
| /// Returns an iterator over all profile names known to Cargo. | |
| pub fn profile_names(&self) -> impl Iterator<Item = InternedString> + '_ { | |
| self.by_name.keys().copied() | |
| } | |
| } | |
| pub(crate) enum MinOptLevelHintError { | |
| OutOfRange { | |
| level: i64, | |
| expected: RangeInclusive<i64>, | |
| }, | |
| WrongType(&'static str), | |
| } | |
| pub(crate) fn parse_min_opt_level_hint( | |
| value: Option<&toml::Value>, | |
| ) -> Result<Option<u32>, MinOptLevelHintError> { | |
| let Some(value) = value else { | |
| return Ok(None); | |
| }; | |
| let Some(level) = value.as_integer() else { | |
| return Err(MinOptLevelHintError::WrongType(value.type_str())); | |
| }; | |
| let expected = 0..=3; | |
| if expected.contains(&level) { | |
| Ok(Some(level as u32)) | |
| } else { | |
| Err(MinOptLevelHintError::OutOfRange { level, expected }) | |
| } | |
| } | |
| /// 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, | |
| min_opt_level: Option<u32>, | |
| ) -> 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 = "0".into(); | |
| 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. | |
| profile.debuginfo = DebugInfo::Deferred(profile.debuginfo.into_inner()); | |
| } | |
| if let (Some(min_opt_level), Ok(opt_level)) = | |
| (min_opt_level, profile.opt_level.as_str().parse::<u32>()) | |
| { | |
| if opt_level < min_opt_level { | |
| profile.opt_level = min_opt_level.to_string().into(); | |
| } | |
| } | |
| // ... 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 = opt_level.0.as_str().into(); | |
| } | |
| 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(n.into()), | |
| None => {} | |
| } | |
| if toml.codegen_backend.is_some() { | |
| profile.codegen_backend = toml.codegen_backend.as_ref().map(InternedString::from); | |
| } | |
| if toml.codegen_units.is_some() { | |
| profile.codegen_units = toml.codegen_units; | |
| } | |
| if let Some(debuginfo) = toml.debug { | |
| profile.debuginfo = DebugInfo::Resolved(debuginfo); | |
| } | |
| 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(split_debuginfo.into()); | |
| } | |
| 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, | |
| "immediate-abort" => PanicStrategy::ImmediateAbort, | |
| // 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.iter().map(InternedString::from).collect(); | |
| } | |
| if let Some(trim_paths) = &toml.trim_paths { | |
| profile.trim_paths = trim_paths.clone(); | |
| } | |
| if let Some(hint_mostly_unused) = toml.hint_mostly_unused { | |
| profile.hint_mostly_unused = Some(hint_mostly_unused); | |
| } | |
| profile.strip = match toml.strip { | |
| Some(StringOrBool::Bool(true)) => Strip::Resolved(StripInner::Named("symbols".into())), | |
| Some(StringOrBool::Bool(false)) => Strip::Resolved(StripInner::None), | |
| Some(StringOrBool::String(ref n)) if n.as_str() == "none" => { | |
| Strip::Resolved(StripInner::None) | |
| } | |
| Some(StringOrBool::String(ref n)) => Strip::Resolved(StripInner::Named(n.into())), | |
| None => profile.strip, | |
| }; | |
| } | |
| /// 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 stabilized | |
| // Note that `rustflags` is used for the cargo-feature `profile_rustflags` | |
| pub rustflags: Vec<InternedString>, | |
| pub trim_paths: TomlTrimPaths, | |
| #[serde(skip_serializing_if = "Option::is_none")] | |
| pub hint_mostly_unused: Option<bool>, | |
| } | |
| impl Default for Profile { | |
| fn default() -> Profile { | |
| Profile { | |
| name: "".into(), | |
| opt_level: "0".into(), | |
| root: ProfileRoot::Debug, | |
| lto: Lto::Bool(false), | |
| codegen_backend: None, | |
| codegen_units: None, | |
| debuginfo: DebugInfo::Resolved(TomlDebugInfo::None), | |
| debug_assertions: false, | |
| split_debuginfo: None, | |
| overflow_checks: false, | |
| rpath: false, | |
| incremental: false, | |
| panic: PanicStrategy::Unwind, | |
| strip: Strip::Deferred(StripInner::None), | |
| rustflags: vec![], | |
| trim_paths: TomlTrimPaths::None, | |
| hint_mostly_unused: None, | |
| } | |
| } | |
| } | |
| 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 | |
| trim_paths | |
| hint_mostly_unused | |
| )] | |
| } | |
| } | |
| } | |
| 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: "dev".into(), | |
| root: ProfileRoot::Debug, | |
| debuginfo: DebugInfo::Resolved(TomlDebugInfo::LineTablesOnly), | |
| debug_assertions: true, | |
| overflow_checks: true, | |
| incremental: true, | |
| ..Profile::default() | |
| } | |
| } | |
| /// Returns a built-in `release` profile. | |
| fn default_release() -> Profile { | |
| Profile { | |
| name: "release".into(), | |
| root: ProfileRoot::Release, | |
| opt_level: "3".into(), | |
| ..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), | |
| &self.rustflags, | |
| &self.trim_paths, | |
| ) | |
| } | |
| } | |
| /// The debuginfo level setting. | |
| /// | |
| /// This is semantically a [`TomlDebugInfo`], and should be used as so via the | |
| /// [`DebugInfo::into_inner`] method for all intents and purposes. | |
| /// | |
| /// 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 { | |
| /// A debuginfo level that is fixed and will not change. | |
| /// | |
| /// This can be set by a profile, user, or default value. | |
| Resolved(TomlDebugInfo), | |
| /// For internal purposes: a deferred debuginfo level that can be optimized | |
| /// away, but has this value otherwise. | |
| /// | |
| /// Behaves like `Resolved` 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(TomlDebugInfo), | |
| } | |
| impl DebugInfo { | |
| /// The main way to interact with this debuginfo level, turning it into a [`TomlDebugInfo`]. | |
| pub fn into_inner(self) -> TomlDebugInfo { | |
| match self { | |
| DebugInfo::Resolved(v) | DebugInfo::Deferred(v) => v, | |
| } | |
| } | |
| /// Returns true if any debuginfo will be generated. Helper | |
| /// for a common operation on the usual `Option` representation. | |
| pub(crate) fn is_turned_on(&self) -> bool { | |
| !matches!(self.into_inner(), TomlDebugInfo::None) | |
| } | |
| 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::Resolved(v), | |
| _ => self, | |
| } | |
| } | |
| /// Reset to the lowest level: no debuginfo. | |
| pub(crate) fn weaken(self) -> Self { | |
| DebugInfo::Resolved(TomlDebugInfo::None) | |
| } | |
| } | |
| impl PartialEq for DebugInfo { | |
| fn eq(&self, other: &DebugInfo) -> bool { | |
| self.into_inner().eq(&other.into_inner()) | |
| } | |
| } | |
| impl Eq for DebugInfo {} | |
| impl Hash for DebugInfo { | |
| fn hash<H: std::hash::Hasher>(&self, state: &mut H) { | |
| self.into_inner().hash(state); | |
| } | |
| } | |
| impl PartialOrd for DebugInfo { | |
| fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { | |
| self.into_inner().partial_cmp(&other.into_inner()) | |
| } | |
| } | |
| impl Ord for DebugInfo { | |
| fn cmp(&self, other: &Self) -> std::cmp::Ordering { | |
| self.into_inner().cmp(&other.into_inner()) | |
| } | |
| } | |
| /// 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 = "kebab-case")] | |
| pub enum PanicStrategy { | |
| Unwind, | |
| Abort, | |
| ImmediateAbort, | |
| } | |
| impl fmt::Display for PanicStrategy { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| match *self { | |
| PanicStrategy::Unwind => "unwind", | |
| PanicStrategy::Abort => "abort", | |
| PanicStrategy::ImmediateAbort => "immediate-abort", | |
| } | |
| .fmt(f) | |
| } | |
| } | |
| #[derive( | |
| Clone, Copy, PartialEq, Eq, Debug, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize, | |
| )] | |
| pub enum StripInner { | |
| /// Don't remove any symbols | |
| None, | |
| /// Named Strip settings | |
| Named(InternedString), | |
| } | |
| impl fmt::Display for StripInner { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| match *self { | |
| StripInner::None => "none", | |
| StripInner::Named(s) => s.as_str(), | |
| } | |
| .fmt(f) | |
| } | |
| } | |
| /// The setting for choosing which symbols to strip. | |
| /// | |
| /// This is semantically a [`StripInner`], and should be used as so via the | |
| /// [`Strip::into_inner`] method for all intents and purposes. | |
| /// | |
| /// Internally, it's used to model a strip option whose value can be deferred | |
| /// for optimization purposes: when no package being compiled requires debuginfo, | |
| /// then we can strip debuginfo to remove pre-existing debug symbols from the | |
| /// standard library. | |
| #[derive(Clone, Copy, Debug, Eq, serde::Serialize, serde::Deserialize)] | |
| #[serde(rename_all = "lowercase")] | |
| pub enum Strip { | |
| /// A strip option that is fixed and will not change. | |
| Resolved(StripInner), | |
| /// A strip option that might be overridden by Cargo for optimization | |
| /// purposes. | |
| Deferred(StripInner), | |
| } | |
| impl Strip { | |
| /// The main way to interact with this strip option, turning it into a [`StripInner`]. | |
| pub fn into_inner(self) -> StripInner { | |
| match self { | |
| Strip::Resolved(v) | Strip::Deferred(v) => v, | |
| } | |
| } | |
| pub(crate) fn is_deferred(&self) -> bool { | |
| matches!(self, Strip::Deferred(_)) | |
| } | |
| /// Reset to stripping debuginfo. | |
| pub(crate) fn strip_debuginfo(self) -> Self { | |
| Strip::Resolved(StripInner::Named("debuginfo".into())) | |
| } | |
| } | |
| impl PartialEq for Strip { | |
| fn eq(&self, other: &Self) -> bool { | |
| self.into_inner().eq(&other.into_inner()) | |
| } | |
| } | |
| impl Hash for Strip { | |
| fn hash<H: std::hash::Hasher>(&self, state: &mut H) { | |
| self.into_inner().hash(state); | |
| } | |
| } | |
| impl PartialOrd for Strip { | |
| fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { | |
| self.into_inner().partial_cmp(&other.into_inner()) | |
| } | |
| } | |
| impl Ord for Strip { | |
| fn cmp(&self, other: &Self) -> std::cmp::Ordering { | |
| self.into_inner().cmp(&other.into_inner()) | |
| } | |
| } | |
| /// 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-tuple>|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(gctx: &GlobalContext, 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 gctx.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(gctx: &GlobalContext, root_compile_kind: CompileKind) -> UnitFor { | |
| let mut unit_for = UnitFor::new_test(gctx, 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 | |
| }; | |
| let artifact_target_for_features = | |
| // build.rs and proc-macros are always for host. | |
| if dep_target.proc_macro() || parent.target.is_custom_build() { | |
| None | |
| } else { | |
| self.artifact_target_for_features | |
| }; | |
| UnitFor { | |
| host: self.host || dep_for_host, | |
| host_features, | |
| panic_setting, | |
| root_compile_kind, | |
| 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() | |
| .iter() | |
| .map(|(k, v)| (InternedString::new(k), v.clone())) | |
| .collect(), | |
| None => BTreeMap::new(), | |
| }; | |
| // Set of profile names to check if defined in config only. | |
| let mut check_to_add = HashSet::default(); | |
| 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.into()); | |
| } | |
| } | |
| // 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", "debug", "test", "bench"] { | |
| check_to_add.insert(name.into()); | |
| } | |
| // Add config-only profiles. | |
| // Need to iterate repeatedly to get all the inherits values. | |
| let mut current = HashSet::default(); | |
| 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.as_str()) { | |
| if let Some(config_profile) = get_config_profile(ws, &name)? { | |
| if let Some(inherits) = &config_profile.inherits { | |
| check_to_add.insert(inherits.into()); | |
| } | |
| 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<context::Value<TomlProfile>> = ws.gctx().get(["profile", name])?; | |
| let Some(profile) = profile else { | |
| return Ok(None); | |
| }; | |
| let mut warnings = Vec::new(); | |
| validate_profile( | |
| &profile.val, | |
| name, | |
| ws.gctx().cli_unstable(), | |
| ws.unstable_features(), | |
| &mut warnings, | |
| ) | |
| .with_context(|| { | |
| format!( | |
| "config profile `{}` is not valid (defined in `{}`)", | |
| name, profile.definition | |
| ) | |
| })?; | |
| warn_ignored_hint_mostly_unused(ws.gctx(), &profile, name, &mut warnings)?; | |
| for warning in warnings { | |
| ws.gctx().shell().warn(warning)?; | |
| } | |
| Ok(Some(profile.val)) | |
| } | |
| /// Warns about each `hint-mostly-unused = true` in a config profile, which is ignored without | |
| /// `-Zprofile-hint-mostly-unused`. Manifest profiles are covered by the parse pass instead, see | |
| /// [`crate::diagnostics::rules::PARSE_PASS_RULES`]. | |
| fn warn_ignored_hint_mostly_unused( | |
| gctx: &GlobalContext, | |
| profile: &context::Value<TomlProfile>, | |
| name: &str, | |
| warnings: &mut Vec<String>, | |
| ) -> CargoResult<()> { | |
| if gctx.cli_unstable().profile_hint_mostly_unused { | |
| return Ok(()); | |
| } | |
| let mut key = context::ConfigKey::from_str("profile"); | |
| key.push(name); | |
| let mut tables = vec![(&profile.val, key.clone())]; | |
| if let Some(build_override) = &profile.val.build_override { | |
| let mut key = key.clone(); | |
| key.push("build-override"); | |
| tables.push((build_override, key)); | |
| } | |
| for (spec, package) in profile.val.package.iter().flatten() { | |
| let mut key = key.clone(); | |
| key.push("package"); | |
| key.push(&spec.to_string()); | |
| tables.push((package, key)); | |
| } | |
| for (table, key) in tables { | |
| if table.hint_mostly_unused == Some(true) { | |
| // Config sources are merged key by key, so `profile.definition` may not be where this | |
| // key was set. A legacy spec like `"foo:1.0.0"` is normalized by `to_string`, so its | |
| // lookup misses and the profile's definition is used instead. | |
| let mut hint_key = key.clone(); | |
| hint_key.push("hint-mostly-unused"); | |
| let hint = gctx.get_cv_with_env(&hint_key)?; | |
| let definition = hint | |
| .as_ref() | |
| .map_or(&profile.definition, |hint| hint.definition()); | |
| warnings.push(format!( | |
| "ignoring `hint-mostly-unused` in `{key}` (defined in {definition}), \ | |
| pass `-Zprofile-hint-mostly-unused` to enable it" | |
| )); | |
| } | |
| } | |
| Ok(()) | |
| } | |
| /// 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 Some(toml) = toml else { | |
| return Ok(HashSet::default()); | |
| }; | |
| let Some(overrides) = toml.package.as_ref() else { | |
| return Ok(HashSet::default()); | |
| }; | |
| // Verify that a package doesn't match multiple spec overrides. | |
| let mut found = HashSet::default(); | |
| 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 Some(overrides) = toml.package.as_ref() else { | |
| 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(), | |
| "package", | |
| ); | |
| 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") | |
| } |