| use crate::core::compiler::CompileMode; |
| use crate::core::interning::InternedString; |
| use crate::core::resolver::features::FeaturesFor; |
| use crate::core::{Feature, Features, PackageId, PackageIdSpec, Resolve, Shell}; |
| use crate::util::errors::CargoResultExt; |
| use crate::util::toml::{ProfilePackageSpec, StringOrBool, TomlProfile, TomlProfiles, U32OrBool}; |
| use crate::util::{closest_msg, config, CargoResult, Config}; |
| use anyhow::bail; |
| use std::collections::{BTreeMap, HashMap, HashSet}; |
| use std::{cmp, env, fmt, hash}; |
| |
| /// Collection of all profiles. |
| #[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>, |
| /// Whether or not unstable "named" profiles are enabled. |
| named_profiles_enabled: bool, |
| /// The profile the user requested to use. |
| requested_profile: InternedString, |
| } |
| |
| impl Profiles { |
| pub fn new( |
| profiles: Option<&TomlProfiles>, |
| config: &Config, |
| requested_profile: InternedString, |
| features: &Features, |
| ) -> CargoResult<Profiles> { |
| let incremental = match env::var_os("CARGO_INCREMENTAL") { |
| Some(v) => Some(v == "1"), |
| None => config.build_config()?.incremental, |
| }; |
| let mut profiles = merge_config_profiles(profiles, config, requested_profile, features)?; |
| |
| if !features.is_enabled(Feature::named_profiles()) { |
| let mut profile_makers = Profiles { |
| incremental, |
| named_profiles_enabled: false, |
| dir_names: Self::predefined_dir_names(), |
| by_name: HashMap::new(), |
| requested_profile, |
| }; |
| |
| profile_makers.by_name.insert( |
| InternedString::new("dev"), |
| ProfileMaker::new(Profile::default_dev(), profiles.remove("dev")), |
| ); |
| profile_makers |
| .dir_names |
| .insert(InternedString::new("dev"), InternedString::new("debug")); |
| |
| profile_makers.by_name.insert( |
| InternedString::new("release"), |
| ProfileMaker::new(Profile::default_release(), profiles.remove("release")), |
| ); |
| profile_makers.dir_names.insert( |
| InternedString::new("release"), |
| InternedString::new("release"), |
| ); |
| |
| profile_makers.by_name.insert( |
| InternedString::new("test"), |
| ProfileMaker::new(Profile::default_test(), profiles.remove("test")), |
| ); |
| profile_makers |
| .dir_names |
| .insert(InternedString::new("test"), InternedString::new("debug")); |
| |
| profile_makers.by_name.insert( |
| InternedString::new("bench"), |
| ProfileMaker::new(Profile::default_bench(), profiles.remove("bench")), |
| ); |
| profile_makers |
| .dir_names |
| .insert(InternedString::new("bench"), InternedString::new("release")); |
| |
| profile_makers.by_name.insert( |
| InternedString::new("doc"), |
| ProfileMaker::new(Profile::default_doc(), profiles.remove("doc")), |
| ); |
| profile_makers |
| .dir_names |
| .insert(InternedString::new("doc"), InternedString::new("debug")); |
| |
| return Ok(profile_makers); |
| } |
| |
| let mut profile_makers = Profiles { |
| incremental, |
| named_profiles_enabled: true, |
| dir_names: Self::predefined_dir_names(), |
| by_name: HashMap::new(), |
| requested_profile, |
| }; |
| |
| 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> { |
| let mut dir_names = HashMap::new(); |
| dir_names.insert(InternedString::new("dev"), InternedString::new("debug")); |
| dir_names.insert(InternedString::new("check"), InternedString::new("debug")); |
| dir_names.insert(InternedString::new("test"), InternedString::new("debug")); |
| dir_names.insert(InternedString::new("bench"), InternedString::new("release")); |
| dir_names |
| } |
| |
| /// 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>, |
| ) -> CargoResult<()> { |
| 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()), |
| ); |
| Ok(()) |
| } |
| |
| /// 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() |
| }, |
| ), |
| ( |
| "check", |
| 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, |
| unit_for: UnitFor, |
| mode: CompileMode, |
| ) -> Profile { |
| let (profile_name, inherits) = if !self.named_profiles_enabled { |
| // With the feature disabled, we degrade `--profile` back to the |
| // `--release` and `--debug` predicates, and convert back from |
| // ProfileKind::Custom instantiation. |
| |
| let release = match self.requested_profile.as_str() { |
| "release" | "bench" => true, |
| _ => false, |
| }; |
| |
| match mode { |
| CompileMode::Test | CompileMode::Bench => { |
| if release { |
| ( |
| InternedString::new("bench"), |
| Some(InternedString::new("release")), |
| ) |
| } else { |
| ( |
| InternedString::new("test"), |
| Some(InternedString::new("dev")), |
| ) |
| } |
| } |
| CompileMode::Build |
| | CompileMode::Check { .. } |
| | CompileMode::Doctest |
| | CompileMode::RunCustomBuild => { |
| // Note: `RunCustomBuild` doesn't normally use this code path. |
| // `build_unit_profiles` normally ensures that it selects the |
| // ancestor's profile. However, `cargo clean -p` can hit this |
| // path. |
| if release { |
| (InternedString::new("release"), None) |
| } else { |
| (InternedString::new("dev"), None) |
| } |
| } |
| CompileMode::Doc { .. } => (InternedString::new("doc"), None), |
| } |
| } else { |
| (self.requested_profile, None) |
| }; |
| let maker = self.get_profile_maker(profile_name).unwrap(); |
| let mut profile = maker.get_profile(Some(pkg_id), is_member, unit_for); |
| |
| // 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 => {} |
| PanicSetting::Inherit => { |
| if let Some(inherits) = inherits { |
| // TODO: Fixme, broken with named profiles. |
| let maker = self.get_profile_maker(inherits).unwrap(); |
| profile.panic = maker.get_profile(Some(pkg_id), is_member, unit_for).panic; |
| } |
| } |
| } |
| |
| // Incremental can be globally overridden. |
| if let Some(v) = self.incremental { |
| profile.incremental = v; |
| } |
| // Only enable incremental compilation for sources the user can |
| // modify (aka path sources). For things that change infrequently, |
| // non-incremental builds yield better performance in the compiler |
| // itself (aka crates.io / git dependencies) |
| // |
| // (see also https://github.com/rust-lang/cargo/issues/3972) |
| if !pkg_id.source_id().is_path() { |
| profile.incremental = false; |
| } |
| profile.name = profile_name; |
| 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.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 = if !self.named_profiles_enabled { |
| match self.requested_profile.as_str() { |
| "release" | "bench" => self.requested_profile, |
| _ => InternedString::new("dev"), |
| } |
| } else { |
| self.requested_profile |
| }; |
| |
| let maker = self.get_profile_maker(profile_name).unwrap(); |
| maker.get_profile(None, true, UnitFor::new_normal()) |
| } |
| |
| /// 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 { |
| 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. |
| 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, |
| unit_for: UnitFor, |
| ) -> Profile { |
| let mut profile = self.default; |
| if let Some(toml) = &self.toml { |
| merge_profile(&mut profile, toml); |
| merge_toml_overrides(pkg_id, is_member, unit_for, &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, |
| unit_for: UnitFor, |
| profile: &mut Profile, |
| toml: &TomlProfile, |
| ) { |
| if unit_for.is_for_host() { |
| if let Some(ref 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)) => profile.lto = Lto::Named(InternedString::new(n)), |
| None => {} |
| } |
| if toml.codegen_units.is_some() { |
| profile.codegen_units = toml.codegen_units; |
| } |
| match toml.debug { |
| Some(U32OrBool::U32(debug)) => profile.debuginfo = Some(debug), |
| Some(U32OrBool::Bool(true)) => profile.debuginfo = Some(2), |
| Some(U32OrBool::Bool(false)) => profile.debuginfo = None, |
| None => {} |
| } |
| if let Some(debug_assertions) = toml.debug_assertions { |
| profile.debug_assertions = debug_assertions; |
| } |
| if let Some(rpath) = toml.rpath { |
| profile.rpath = rpath; |
| } |
| if let Some(panic) = &toml.panic { |
| profile.panic = match panic.as_str() { |
| "unwind" => PanicStrategy::Unwind, |
| "abort" => PanicStrategy::Abort, |
| // This should be validated in TomlProfile::validate |
| _ => panic!("Unexpected panic setting `{}`", panic), |
| }; |
| } |
| if let Some(overflow_checks) = toml.overflow_checks { |
| profile.overflow_checks = overflow_checks; |
| } |
| if let Some(incremental) = toml.incremental { |
| profile.incremental = incremental; |
| } |
| } |
| |
| /// 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, Copy, Eq, PartialOrd, Ord)] |
| pub struct Profile { |
| pub name: InternedString, |
| pub opt_level: InternedString, |
| pub root: ProfileRoot, |
| pub lto: Lto, |
| // `None` means use rustc default. |
| pub codegen_units: Option<u32>, |
| pub debuginfo: Option<u32>, |
| pub debug_assertions: bool, |
| pub overflow_checks: bool, |
| pub rpath: bool, |
| pub incremental: bool, |
| pub panic: PanicStrategy, |
| } |
| |
| impl Default for Profile { |
| fn default() -> Profile { |
| Profile { |
| name: InternedString::new(""), |
| opt_level: InternedString::new("0"), |
| root: ProfileRoot::Debug, |
| lto: Lto::Bool(false), |
| codegen_units: None, |
| debuginfo: None, |
| debug_assertions: false, |
| overflow_checks: false, |
| rpath: false, |
| incremental: false, |
| panic: PanicStrategy::Unwind, |
| } |
| } |
| } |
| |
| compact_debug! { |
| impl fmt::Debug for Profile { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| let (default, default_name) = match self.name.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_units |
| debuginfo |
| debug_assertions |
| overflow_checks |
| rpath |
| incremental |
| panic |
| )] |
| } |
| } |
| } |
| |
| impl fmt::Display for Profile { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| write!(f, "Profile({})", self.name) |
| } |
| } |
| |
| impl hash::Hash for Profile { |
| fn hash<H>(&self, state: &mut H) |
| where |
| H: hash::Hasher, |
| { |
| self.comparable().hash(state); |
| } |
| } |
| |
| impl cmp::PartialEq for Profile { |
| fn eq(&self, other: &Self) -> bool { |
| self.comparable() == other.comparable() |
| } |
| } |
| |
| impl Profile { |
| fn default_dev() -> Profile { |
| Profile { |
| name: InternedString::new("dev"), |
| root: ProfileRoot::Debug, |
| debuginfo: Some(2), |
| debug_assertions: true, |
| overflow_checks: true, |
| incremental: true, |
| ..Profile::default() |
| } |
| } |
| |
| fn default_release() -> Profile { |
| Profile { |
| name: InternedString::new("release"), |
| root: ProfileRoot::Release, |
| opt_level: InternedString::new("3"), |
| ..Profile::default() |
| } |
| } |
| |
| // NOTE: Remove the following three once `named_profiles` is default: |
| |
| fn default_test() -> Profile { |
| Profile { |
| name: InternedString::new("test"), |
| ..Profile::default_dev() |
| } |
| } |
| |
| fn default_bench() -> Profile { |
| Profile { |
| name: InternedString::new("bench"), |
| ..Profile::default_release() |
| } |
| } |
| |
| fn default_doc() -> Profile { |
| Profile { |
| name: InternedString::new("doc"), |
| ..Profile::default_dev() |
| } |
| } |
| |
| /// Compares all fields except `name`, which doesn't affect compilation. |
| /// This is necessary for `Unit` deduplication for things like "test" and |
| /// "dev" which are essentially the same. |
| fn comparable( |
| &self, |
| ) -> ( |
| InternedString, |
| Lto, |
| Option<u32>, |
| Option<u32>, |
| bool, |
| bool, |
| bool, |
| bool, |
| PanicStrategy, |
| ) { |
| ( |
| self.opt_level, |
| self.lto, |
| self.codegen_units, |
| self.debuginfo, |
| self.debug_assertions, |
| self.overflow_checks, |
| self.rpath, |
| self.incremental, |
| self.panic, |
| ) |
| } |
| } |
| |
| /// The link-time-optimization setting. |
| #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)] |
| pub enum Lto { |
| /// False = no LTO |
| /// True = "Fat" LTO |
| Bool(bool), |
| /// Named LTO settings like "thin". |
| Named(InternedString), |
| } |
| |
| /// The `panic` setting. |
| #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)] |
| pub enum PanicStrategy { |
| Unwind, |
| Abort, |
| } |
| |
| impl fmt::Display for PanicStrategy { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| match *self { |
| PanicStrategy::Unwind => "unwind", |
| PanicStrategy::Abort => "abort", |
| } |
| .fmt(f) |
| } |
| } |
| |
| /// Flags used in creating `Unit`s to indicate the purpose for the target, and |
| /// to ensure the target's dependencies have the correct settings. |
| #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, 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 `build_dep` 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 any of its dependencies). This is |
| /// used for computing features of build dependencies 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). `build_dep` becomes `true` |
| /// for build-dependencies, 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, BUILD_DEP=false. This is |
| /// so that `foo build.rs` gets the profile settings for build scripts |
| /// (HOST=true) and features of foo (BUILD_DEP=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, BUILD_DEP=false for the same reasons that |
| /// foo's build script is set that way. |
| build_dep: bool, |
| /// 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. |
| panic_setting: PanicSetting, |
| } |
| |
| #[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, |
| |
| /// This unit will ignore its `panic` setting in its profile and will |
| /// instead inherit it from the `dev` or `release` profile, as appropriate. |
| Inherit, |
| } |
| |
| impl UnitFor { |
| /// A unit for a normal target/dependency (i.e., not custom build, |
| /// proc macro/plugin, or test/bench). |
| pub fn new_normal() -> UnitFor { |
| UnitFor { |
| host: false, |
| build_dep: false, |
| panic_setting: PanicSetting::ReadProfile, |
| } |
| } |
| |
| /// A unit for a custom build script or its dependencies. |
| /// |
| /// The `build_dep` parameter is whether or not this is for a build |
| /// dependency. 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_build(build_dep: bool) -> UnitFor { |
| UnitFor { |
| host: true, |
| build_dep, |
| // Force build scripts to always use `panic=unwind` for now to |
| // maximally share dependencies with procedural macros. |
| panic_setting: PanicSetting::AlwaysUnwind, |
| } |
| } |
| |
| /// A unit for a proc macro or compiler plugin or their dependencies. |
| pub fn new_compiler() -> UnitFor { |
| UnitFor { |
| host: false, |
| build_dep: 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, |
| } |
| } |
| |
| /// 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) -> UnitFor { |
| UnitFor { |
| host: false, |
| build_dep: 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::Inherit |
| } else { |
| PanicSetting::AlwaysUnwind |
| }, |
| } |
| } |
| |
| /// Returns a new copy based on `for_host` setting. |
| /// |
| /// When `for_host` is true, this clears `panic_abort_ok` in a sticky |
| /// fashion so that all its dependencies also have `panic_abort_ok=false`. |
| /// This'll help ensure that once we start compiling for the host platform |
| /// (build scripts, plugins, proc macros, etc) we'll share the same build |
| /// graph where everything is `panic=unwind`. |
| pub fn with_for_host(self, for_host: bool) -> UnitFor { |
| UnitFor { |
| host: self.host || for_host, |
| build_dep: self.build_dep, |
| panic_setting: if for_host { |
| PanicSetting::AlwaysUnwind |
| } else { |
| self.panic_setting |
| }, |
| } |
| } |
| |
| /// Returns a new copy updating it for a build dependency. |
| /// |
| /// This is part of the machinery responsible for handling feature |
| /// decoupling for build dependencies in the new feature resolver. |
| pub fn with_build_dep(mut self, build_dep: bool) -> UnitFor { |
| if build_dep { |
| assert!(self.host); |
| } |
| self.build_dep = self.build_dep || build_dep; |
| 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_build_dep(&self) -> bool { |
| self.build_dep |
| } |
| |
| /// Returns how `panic` settings should be handled for this profile |
| fn panic_setting(&self) -> PanicSetting { |
| self.panic_setting |
| } |
| |
| /// All possible values, used by `clean`. |
| pub fn all_values() -> &'static [UnitFor] { |
| static ALL: &[UnitFor] = &[ |
| UnitFor { |
| host: false, |
| build_dep: false, |
| panic_setting: PanicSetting::ReadProfile, |
| }, |
| UnitFor { |
| host: true, |
| build_dep: false, |
| panic_setting: PanicSetting::AlwaysUnwind, |
| }, |
| UnitFor { |
| host: false, |
| build_dep: false, |
| panic_setting: PanicSetting::AlwaysUnwind, |
| }, |
| UnitFor { |
| host: false, |
| build_dep: false, |
| panic_setting: PanicSetting::Inherit, |
| }, |
| // build_dep=true must always have host=true |
| // `Inherit` is not used in build dependencies. |
| UnitFor { |
| host: true, |
| build_dep: true, |
| panic_setting: PanicSetting::ReadProfile, |
| }, |
| UnitFor { |
| host: true, |
| build_dep: true, |
| panic_setting: PanicSetting::AlwaysUnwind, |
| }, |
| ]; |
| ALL |
| } |
| |
| pub(crate) fn map_to_features_for(&self) -> FeaturesFor { |
| if self.is_for_build_dep() { |
| FeaturesFor::BuildDep |
| } else { |
| FeaturesFor::NormalOrDev |
| } |
| } |
| } |
| |
| /// 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( |
| profiles: Option<&TomlProfiles>, |
| config: &Config, |
| requested_profile: InternedString, |
| features: &Features, |
| ) -> CargoResult<BTreeMap<InternedString, TomlProfile>> { |
| let mut profiles = match 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(name, config, features)? { |
| 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(&name, config, features)? { |
| 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( |
| name: &str, |
| config: &Config, |
| features: &Features, |
| ) -> CargoResult<Option<TomlProfile>> { |
| let profile: Option<config::Value<TomlProfile>> = 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, features, &mut warnings) |
| .chain_err(|| { |
| anyhow::format_err!( |
| "config profile `{}` is not valid (defined in `{}`)", |
| name, |
| profile.definition |
| ) |
| })?; |
| for warning in warnings { |
| 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(()) |
| } |