Skip to main content

cargo/workspace/
profiles.rs

1//! Handles built-in and customizable compiler flag presets.
2//!
3//! [`Profiles`] is a collections of built-in profiles, and profiles defined
4//! in the root manifest and configurations.
5//!
6//! To start using a profile, most of the time you start from [`Profiles::new`],
7//! which does the followings:
8//!
9//! - Create a `Profiles` by merging profiles from configs onto the profile
10//!   from root manifest (see [`merge_config_profiles`]).
11//! - Add built-in profiles onto it (see [`Profiles::add_root_profiles`]).
12//! - Process profile inheritance for each profiles. (see [`Profiles::add_maker`]).
13//!
14//! Then you can query a [`Profile`] via [`Profiles::get_profile`], which respects
15//! the profile overridden hierarchy described in below. The [`Profile`] you get
16//! is basically an immutable struct containing the compiler flag presets.
17//!
18//! ## Profile overridden hierarchy
19//!
20//! Profile settings can be overridden for specific packages and build-time crates.
21//! The precedence is explained in [`ProfileMaker`].
22//! The algorithm happens within [`ProfileMaker::get_profile`].
23
24use crate::compiler::{CompileKind, CompileTarget, Unit};
25use crate::context;
26use crate::resolver::Resolve;
27use crate::resolver::features::FeaturesFor;
28use crate::util::data_structures::{HashMap, HashSet};
29use crate::util::interning::InternedString;
30use crate::util::{CargoResult, GlobalContext, closest_msg};
31use crate::workspace::dependency::Artifact;
32use crate::workspace::parser::validate_profile;
33use crate::workspace::{PackageId, PackageIdSpec, PackageIdSpecQuery, Target, Workspace};
34use anyhow::{Context as _, bail};
35use cargo_util::is_ci;
36use cargo_util_schemas::manifest::{Hints, TomlTrimPaths};
37use cargo_util_schemas::manifest::{
38    ProfilePackageSpec, StringOrBool, TomlDebugInfo, TomlProfile, TomlProfiles,
39};
40use cargo_util_terminal::Shell;
41use std::collections::BTreeMap;
42use std::hash::Hash;
43use std::ops::RangeInclusive;
44use std::{cmp, fmt, hash};
45
46/// Collection of all profiles.
47///
48/// To get a specific [`Profile`], you usually create this and call [`get_profile`] then.
49///
50/// [`get_profile`]: Profiles::get_profile
51#[derive(Clone, Debug)]
52pub struct Profiles {
53    /// Incremental compilation can be overridden globally via:
54    /// - `CARGO_INCREMENTAL` environment variable.
55    /// - `build.incremental` config value.
56    incremental: Option<bool>,
57    /// Map of profile name to directory name for that profile.
58    dir_names: HashMap<InternedString, InternedString>,
59    /// The profile makers. Key is the profile name.
60    by_name: HashMap<InternedString, ProfileMaker>,
61    /// The original profiles written by the user in the manifest and config.
62    ///
63    /// This is here to assist with error reporting, as the `ProfileMaker`
64    /// values have the inherits chains all merged together.
65    original_profiles: BTreeMap<InternedString, TomlProfile>,
66    /// The profile the user requested to use.
67    requested_profile: InternedString,
68    /// The host target for rustc being used by this `Profiles`.
69    rustc_host: InternedString,
70    /// Whether `-Zhint-min-opt-level` was passed, enabling the
71    /// `hints.min-opt-level` manifest key.
72    hint_min_opt_level: bool,
73}
74
75impl Profiles {
76    pub fn new(ws: &Workspace<'_>, requested_profile: InternedString) -> CargoResult<Profiles> {
77        let gctx = ws.gctx();
78        let incremental = match gctx.get_env_os("CARGO_INCREMENTAL") {
79            Some(v) => Some(v == "1"),
80            None => gctx
81                .build_config()?
82                .incremental
83                .or_else(|| is_ci().then_some(false)),
84        };
85        let mut profiles = merge_config_profiles(ws, requested_profile)?;
86        let rustc_host = ws.gctx().load_global_rustc(Some(ws))?.host;
87
88        let mut profile_makers = Profiles {
89            incremental,
90            dir_names: Self::predefined_dir_names(),
91            by_name: HashMap::default(),
92            original_profiles: profiles.clone(),
93            requested_profile,
94            rustc_host,
95            hint_min_opt_level: gctx.cli_unstable().hint_min_opt_level,
96        };
97
98        Self::add_root_profiles(&mut profile_makers, &profiles);
99
100        // Merge with predefined profiles.
101        use std::collections::btree_map::Entry;
102        for (predef_name, mut predef_prof) in Self::predefined_profiles().into_iter() {
103            match profiles.entry(predef_name.into()) {
104                Entry::Vacant(vac) => {
105                    vac.insert(predef_prof);
106                }
107                Entry::Occupied(mut oc) => {
108                    // Override predefined with the user-provided Toml.
109                    let r = oc.get_mut();
110                    predef_prof.merge(r);
111                    *r = predef_prof;
112                }
113            }
114        }
115
116        for (name, profile) in &profiles {
117            profile_makers.add_maker(*name, profile, &profiles)?;
118        }
119        // Verify that the requested profile is defined *somewhere*.
120        // This simplifies the API (no need for CargoResult), and enforces
121        // assumptions about how config profiles are loaded.
122        profile_makers.get_profile_maker(&requested_profile)?;
123        Ok(profile_makers)
124    }
125
126    /// Returns the hard-coded directory names for built-in profiles.
127    fn predefined_dir_names() -> HashMap<InternedString, InternedString> {
128        HashMap::from_iter([
129            ("dev".into(), "debug".into()),
130            ("test".into(), "debug".into()),
131            ("bench".into(), "release".into()),
132        ])
133    }
134
135    /// Initialize `by_name` with the two "root" profiles, `dev`, and
136    /// `release` given the user's definition.
137    fn add_root_profiles(
138        profile_makers: &mut Profiles,
139        profiles: &BTreeMap<InternedString, TomlProfile>,
140    ) {
141        profile_makers.by_name.insert(
142            "dev".into(),
143            ProfileMaker::new(Profile::default_dev(), profiles.get("dev").cloned()),
144        );
145
146        profile_makers.by_name.insert(
147            "release".into(),
148            ProfileMaker::new(Profile::default_release(), profiles.get("release").cloned()),
149        );
150    }
151
152    /// Returns the built-in profiles (not including dev/release, which are
153    /// "root" profiles).
154    fn predefined_profiles() -> Vec<(&'static str, TomlProfile)> {
155        vec![
156            (
157                "debug",
158                TomlProfile {
159                    inherits: Some(String::from("dev")),
160                    ..TomlProfile::default()
161                },
162            ),
163            (
164                "bench",
165                TomlProfile {
166                    inherits: Some(String::from("release")),
167                    ..TomlProfile::default()
168                },
169            ),
170            (
171                "test",
172                TomlProfile {
173                    inherits: Some(String::from("dev")),
174                    ..TomlProfile::default()
175                },
176            ),
177            (
178                "doc",
179                TomlProfile {
180                    inherits: Some(String::from("dev")),
181                    ..TomlProfile::default()
182                },
183            ),
184        ]
185    }
186
187    /// Creates a `ProfileMaker`, and inserts it into `self.by_name`.
188    fn add_maker(
189        &mut self,
190        name: InternedString,
191        profile: &TomlProfile,
192        profiles: &BTreeMap<InternedString, TomlProfile>,
193    ) -> CargoResult<()> {
194        match &profile.dir_name {
195            None => {}
196            Some(dir_name) => {
197                self.dir_names.insert(name, dir_name.into());
198            }
199        }
200
201        // dev/release are "roots" and don't inherit.
202        if name == "dev" || name == "release" {
203            if profile.inherits.is_some() {
204                bail!(
205                    "`inherits` must not be specified in root profile `{}`",
206                    name
207                );
208            }
209            // Already inserted from `add_root_profiles`, no need to do anything.
210            return Ok(());
211        }
212
213        // Keep track for inherits cycles.
214        let mut set = HashSet::default();
215        set.insert(name);
216        let maker = self.process_chain(name, profile, &mut set, profiles)?;
217        self.by_name.insert(name, maker);
218        Ok(())
219    }
220
221    /// Build a `ProfileMaker` by recursively following the `inherits` setting.
222    ///
223    /// * `name`: The name of the profile being processed.
224    /// * `profile`: The TOML profile being processed.
225    /// * `set`: Set of profiles that have been visited, used to detect cycles.
226    /// * `profiles`: Map of all TOML profiles.
227    ///
228    /// Returns a `ProfileMaker` to be used for the given named profile.
229    fn process_chain(
230        &mut self,
231        name: InternedString,
232        profile: &TomlProfile,
233        set: &mut HashSet<InternedString>,
234        profiles: &BTreeMap<InternedString, TomlProfile>,
235    ) -> CargoResult<ProfileMaker> {
236        let mut maker = match &profile.inherits {
237            Some(inherits_name) if inherits_name == "dev" || inherits_name == "release" => {
238                // These are the root profiles added in `add_root_profiles`.
239                self.get_profile_maker(&inherits_name).unwrap().clone()
240            }
241            Some(inherits_name) => {
242                let inherits_name = inherits_name.into();
243                if !set.insert(inherits_name) {
244                    bail!(
245                        "profile inheritance loop detected with profile `{}` inheriting `{}`",
246                        name,
247                        inherits_name
248                    );
249                }
250
251                match profiles.get(&inherits_name) {
252                    None => {
253                        bail!(
254                            "profile `{}` inherits from `{}`, but that profile is not defined",
255                            name,
256                            inherits_name
257                        );
258                    }
259                    Some(parent) => self.process_chain(inherits_name, parent, set, profiles)?,
260                }
261            }
262            None => {
263                bail!(
264                    "profile `{}` is missing an `inherits` directive \
265                     (`inherits` is required for all profiles except `dev` or `release`)",
266                    name
267                );
268            }
269        };
270        match &mut maker.toml {
271            Some(toml) => toml.merge(profile),
272            None => maker.toml = Some(profile.clone()),
273        };
274        Ok(maker)
275    }
276
277    /// Retrieves the profile for a target.
278    /// `is_member` is whether or not this package is a member of the
279    /// workspace.
280    pub fn get_profile(
281        &self,
282        pkg_id: PackageId,
283        pkg_hints: Option<&Hints>,
284        is_member: bool,
285        is_local: bool,
286        unit_for: UnitFor,
287        kind: CompileKind,
288    ) -> Profile {
289        let maker = self.get_profile_maker(&self.requested_profile).unwrap();
290        let min_opt_level = if self.hint_min_opt_level {
291            parse_min_opt_level_hint(pkg_hints.and_then(|hints| hints.min_opt_level.as_ref()))
292                .ok()
293                .flatten()
294        } else {
295            // The `min_opt_level_hint` parse pass rule warns that the hint is being ignored.
296            None
297        };
298
299        let mut profile = maker.get_profile(
300            Some(pkg_id),
301            is_member,
302            unit_for.is_for_host(),
303            min_opt_level,
304        );
305
306        // Dealing with `panic=abort` and `panic=unwind` requires some special
307        // treatment. Be sure to process all the various options here.
308        match unit_for.panic_setting() {
309            PanicSetting::AlwaysUnwind => profile.panic = PanicStrategy::Unwind,
310            PanicSetting::ReadProfile => {}
311        }
312
313        // Default macOS debug information to being stored in the "unpacked"
314        // split-debuginfo format. At the time of this writing that's the only
315        // platform which has a stable `-Csplit-debuginfo` option for rustc,
316        // and it's typically much faster than running `dsymutil` on all builds
317        // in incremental cases.
318        if profile.debuginfo.is_turned_on() && profile.split_debuginfo.is_none() {
319            let target = match &kind {
320                CompileKind::Host => self.rustc_host.as_str(),
321                CompileKind::Target(target) => target.short_name(),
322            };
323            if target.contains("-apple-") {
324                profile.split_debuginfo = Some("unpacked".into());
325            }
326        }
327
328        // Incremental can be globally overridden.
329        if let Some(v) = self.incremental {
330            profile.incremental = v;
331        }
332
333        // Only enable incremental compilation for sources the user can
334        // modify (aka path sources). For things that change infrequently,
335        // non-incremental builds yield better performance in the compiler
336        // itself (aka crates.io / git dependencies)
337        //
338        // (see also https://github.com/rust-lang/cargo/issues/3972)
339        if !is_local {
340            profile.incremental = false;
341        }
342        profile.name = self.requested_profile;
343        profile
344    }
345
346    /// The profile for *running* a `build.rs` script is only used for setting
347    /// a few environment variables. To ensure proper de-duplication of the
348    /// running `Unit`, this uses a stripped-down profile (so that unrelated
349    /// profile flags don't cause `build.rs` to needlessly run multiple
350    /// times).
351    pub fn get_profile_run_custom_build(&self, for_unit_profile: &Profile) -> Profile {
352        let mut result = Profile::default();
353        result.name = for_unit_profile.name;
354        result.root = for_unit_profile.root;
355        result.debuginfo = for_unit_profile.debuginfo;
356        result.opt_level = for_unit_profile.opt_level;
357        result.debug_assertions = for_unit_profile.debug_assertions;
358        result.trim_paths = for_unit_profile.trim_paths.clone();
359        result
360    }
361
362    /// This returns the base profile. This is currently used for the
363    /// `[Finished]` line. It is not entirely accurate, since it doesn't
364    /// select for the package that was actually built.
365    pub fn base_profile(&self) -> Profile {
366        let profile_name = self.requested_profile;
367        let maker = self.get_profile_maker(&profile_name).unwrap();
368        maker.get_profile(
369            None, /*is_member*/ true, /*is_for_host*/ false, None,
370        )
371    }
372
373    /// Gets the directory name for a profile, like `debug` or `release`.
374    pub fn get_dir_name(&self) -> InternedString {
375        *self
376            .dir_names
377            .get(&self.requested_profile)
378            .unwrap_or(&self.requested_profile)
379    }
380
381    /// Used to check for overrides for non-existing packages.
382    pub fn validate_packages(
383        &self,
384        profiles: Option<&TomlProfiles>,
385        shell: &mut Shell,
386        resolve: &Resolve,
387    ) -> CargoResult<()> {
388        for (name, profile) in &self.by_name {
389            // If the user did not specify an override, skip this. This is here
390            // to avoid generating errors for inherited profiles which don't
391            // specify package overrides. The `by_name` profile has had the inherits
392            // chain merged, so we need to look at the original source to check
393            // if an override was specified.
394            if self
395                .original_profiles
396                .get(name)
397                .and_then(|orig| orig.package.as_ref())
398                .is_none()
399            {
400                continue;
401            }
402            let found = validate_packages_unique(resolve, name, &profile.toml)?;
403            // We intentionally do not validate unmatched packages for config
404            // profiles, in case they are defined in a central location. This
405            // iterates over the manifest profiles only.
406            if let Some(profiles) = profiles {
407                if let Some(toml_profile) = profiles.get(name) {
408                    validate_packages_unmatched(shell, resolve, name, toml_profile, &found)?;
409                }
410            }
411        }
412        Ok(())
413    }
414
415    /// Returns the profile maker for the given profile name.
416    fn get_profile_maker(&self, name: &str) -> CargoResult<&ProfileMaker> {
417        self.by_name
418            .get(name)
419            .ok_or_else(|| anyhow::format_err!("profile `{}` is not defined", name))
420    }
421
422    /// Returns an iterator over all profile names known to Cargo.
423    pub fn profile_names(&self) -> impl Iterator<Item = InternedString> + '_ {
424        self.by_name.keys().copied()
425    }
426}
427
428pub(crate) enum MinOptLevelHintError {
429    OutOfRange {
430        level: i64,
431        expected: RangeInclusive<i64>,
432    },
433    WrongType(&'static str),
434}
435
436pub(crate) fn parse_min_opt_level_hint(
437    value: Option<&toml::Value>,
438) -> Result<Option<u32>, MinOptLevelHintError> {
439    let Some(value) = value else {
440        return Ok(None);
441    };
442    let Some(level) = value.as_integer() else {
443        return Err(MinOptLevelHintError::WrongType(value.type_str()));
444    };
445    let expected = 0..=3;
446    if expected.contains(&level) {
447        Ok(Some(level as u32))
448    } else {
449        Err(MinOptLevelHintError::OutOfRange { level, expected })
450    }
451}
452
453/// An object used for handling the profile hierarchy.
454///
455/// The precedence of profiles are (first one wins):
456///
457/// - Profiles in `.cargo/config` files (using same order as below).
458/// - `[profile.dev.package.name]` -- a named package.
459/// - `[profile.dev.package."*"]` -- this cannot apply to workspace members.
460/// - `[profile.dev.build-override]` -- this can only apply to `build.rs` scripts
461///   and their dependencies.
462/// - `[profile.dev]`
463/// - Default (hard-coded) values.
464#[derive(Debug, Clone)]
465struct ProfileMaker {
466    /// The starting, hard-coded defaults for the profile.
467    default: Profile,
468    /// The TOML profile defined in `Cargo.toml` or config.
469    ///
470    /// This is None if the user did not specify one, in which case the
471    /// `default` is used. Note that the built-in defaults for test/bench/doc
472    /// always set this since they need to declare the `inherits` value.
473    toml: Option<TomlProfile>,
474}
475
476impl ProfileMaker {
477    /// Creates a new `ProfileMaker`.
478    ///
479    /// Note that this does not process `inherits`, the caller is responsible for that.
480    fn new(default: Profile, toml: Option<TomlProfile>) -> ProfileMaker {
481        ProfileMaker { default, toml }
482    }
483
484    /// Generates a new `Profile`.
485    fn get_profile(
486        &self,
487        pkg_id: Option<PackageId>,
488        is_member: bool,
489        is_for_host: bool,
490        min_opt_level: Option<u32>,
491    ) -> Profile {
492        let mut profile = self.default.clone();
493
494        // First apply profile-specific settings, things like
495        // `[profile.release]`
496        if let Some(toml) = &self.toml {
497            merge_profile(&mut profile, toml);
498        }
499
500        // Next start overriding those settings. First comes build dependencies
501        // which default to opt-level 0...
502        if is_for_host {
503            // For-host units are things like procedural macros, build scripts, and
504            // their dependencies. For these units most projects simply want them
505            // to compile quickly and the runtime doesn't matter too much since
506            // they tend to process very little data. For this reason we default
507            // them to a "compile as quickly as possible" mode which for now means
508            // basically turning down the optimization level and avoid limiting
509            // codegen units. This ensures that we spend little time optimizing as
510            // well as enabling parallelism by not constraining codegen units.
511            profile.opt_level = "0".into();
512            profile.codegen_units = None;
513
514            // For build dependencies, we usually don't need debuginfo, and
515            // removing it will compile faster. However, that can conflict with
516            // a unit graph optimization, reusing units that are shared between
517            // build dependencies and runtime dependencies: when the runtime
518            // target is the same as the build host, we only need to build a
519            // dependency once and reuse the results, instead of building twice.
520            // We defer the choice of the debuginfo level until we can check if
521            // a unit is shared. If that's the case, we'll use the deferred value
522            // below so the unit can be reused, otherwise we can avoid emitting
523            // the unit's debuginfo.
524            profile.debuginfo = DebugInfo::Deferred(profile.debuginfo.into_inner());
525        }
526        if let (Some(min_opt_level), Ok(opt_level)) =
527            (min_opt_level, profile.opt_level.as_str().parse::<u32>())
528        {
529            if opt_level < min_opt_level {
530                profile.opt_level = min_opt_level.to_string().into();
531            }
532        }
533        // ... and next comes any other sorts of overrides specified in
534        // profiles, such as `[profile.release.build-override]` or
535        // `[profile.release.package.foo]`
536        if let Some(toml) = &self.toml {
537            merge_toml_overrides(pkg_id, is_member, is_for_host, &mut profile, toml);
538        }
539        profile
540    }
541}
542
543/// Merge package and build overrides from the given TOML profile into the given `Profile`.
544fn merge_toml_overrides(
545    pkg_id: Option<PackageId>,
546    is_member: bool,
547    is_for_host: bool,
548    profile: &mut Profile,
549    toml: &TomlProfile,
550) {
551    if is_for_host {
552        if let Some(build_override) = &toml.build_override {
553            merge_profile(profile, build_override);
554        }
555    }
556    if let Some(overrides) = toml.package.as_ref() {
557        if !is_member {
558            if let Some(all) = overrides.get(&ProfilePackageSpec::All) {
559                merge_profile(profile, all);
560            }
561        }
562        if let Some(pkg_id) = pkg_id {
563            let mut matches = overrides
564                .iter()
565                .filter_map(|(key, spec_profile)| match *key {
566                    ProfilePackageSpec::All => None,
567                    ProfilePackageSpec::Spec(ref s) => {
568                        if s.matches(pkg_id) {
569                            Some(spec_profile)
570                        } else {
571                            None
572                        }
573                    }
574                });
575            if let Some(spec_profile) = matches.next() {
576                merge_profile(profile, spec_profile);
577                // `validate_packages` should ensure that there are
578                // no additional matches.
579                assert!(
580                    matches.next().is_none(),
581                    "package `{}` matched multiple package profile overrides",
582                    pkg_id
583                );
584            }
585        }
586    }
587}
588
589/// Merge the given TOML profile into the given `Profile`.
590///
591/// Does not merge overrides (see `merge_toml_overrides`).
592fn merge_profile(profile: &mut Profile, toml: &TomlProfile) {
593    if let Some(ref opt_level) = toml.opt_level {
594        profile.opt_level = opt_level.0.as_str().into();
595    }
596    match toml.lto {
597        Some(StringOrBool::Bool(b)) => profile.lto = Lto::Bool(b),
598        Some(StringOrBool::String(ref n)) if is_off(n.as_str()) => profile.lto = Lto::Off,
599        Some(StringOrBool::String(ref n)) => profile.lto = Lto::Named(n.into()),
600        None => {}
601    }
602    if toml.codegen_backend.is_some() {
603        profile.codegen_backend = toml.codegen_backend.as_ref().map(InternedString::from);
604    }
605    if toml.codegen_units.is_some() {
606        profile.codegen_units = toml.codegen_units;
607    }
608    if let Some(debuginfo) = toml.debug {
609        profile.debuginfo = DebugInfo::Resolved(debuginfo);
610    }
611    if let Some(debug_assertions) = toml.debug_assertions {
612        profile.debug_assertions = debug_assertions;
613    }
614    if let Some(split_debuginfo) = &toml.split_debuginfo {
615        profile.split_debuginfo = Some(split_debuginfo.into());
616    }
617    if let Some(rpath) = toml.rpath {
618        profile.rpath = rpath;
619    }
620    if let Some(panic) = &toml.panic {
621        profile.panic = match panic.as_str() {
622            "unwind" => PanicStrategy::Unwind,
623            "abort" => PanicStrategy::Abort,
624            "immediate-abort" => PanicStrategy::ImmediateAbort,
625            // This should be validated in TomlProfile::validate
626            _ => panic!("Unexpected panic setting `{}`", panic),
627        };
628    }
629    if let Some(overflow_checks) = toml.overflow_checks {
630        profile.overflow_checks = overflow_checks;
631    }
632    if let Some(incremental) = toml.incremental {
633        profile.incremental = incremental;
634    }
635    if let Some(flags) = &toml.rustflags {
636        profile.rustflags = flags.iter().map(InternedString::from).collect();
637    }
638    if let Some(trim_paths) = &toml.trim_paths {
639        profile.trim_paths = trim_paths.clone();
640    }
641    if let Some(hint_mostly_unused) = toml.hint_mostly_unused {
642        profile.hint_mostly_unused = Some(hint_mostly_unused);
643    }
644    profile.strip = match toml.strip {
645        Some(StringOrBool::Bool(true)) => Strip::Resolved(StripInner::Named("symbols".into())),
646        Some(StringOrBool::Bool(false)) => Strip::Resolved(StripInner::None),
647        Some(StringOrBool::String(ref n)) if n.as_str() == "none" => {
648            Strip::Resolved(StripInner::None)
649        }
650        Some(StringOrBool::String(ref n)) => Strip::Resolved(StripInner::Named(n.into())),
651        None => Strip::Deferred(StripInner::None),
652    };
653}
654
655/// The root profile (dev/release).
656///
657/// This is currently only used for the `PROFILE` env var for build scripts
658/// for backwards compatibility. We should probably deprecate `PROFILE` and
659/// encourage using things like `DEBUG` and `OPT_LEVEL` instead.
660#[derive(Clone, Copy, Eq, PartialOrd, Ord, PartialEq, Debug)]
661pub enum ProfileRoot {
662    Release,
663    Debug,
664}
665
666/// Profile settings used to determine which compiler flags to use for a
667/// target.
668#[derive(Clone, Eq, PartialOrd, Ord, serde::Serialize)]
669pub struct Profile {
670    pub name: InternedString,
671    pub opt_level: InternedString,
672    #[serde(skip)] // named profiles are unstable
673    pub root: ProfileRoot,
674    pub lto: Lto,
675    // `None` means use rustc default.
676    pub codegen_backend: Option<InternedString>,
677    // `None` means use rustc default.
678    pub codegen_units: Option<u32>,
679    pub debuginfo: DebugInfo,
680    pub split_debuginfo: Option<InternedString>,
681    pub debug_assertions: bool,
682    pub overflow_checks: bool,
683    pub rpath: bool,
684    pub incremental: bool,
685    pub panic: PanicStrategy,
686    pub strip: Strip,
687    #[serde(skip_serializing_if = "Vec::is_empty")] // remove when `rustflags` is stabilized
688    // Note that `rustflags` is used for the cargo-feature `profile_rustflags`
689    pub rustflags: Vec<InternedString>,
690    pub trim_paths: TomlTrimPaths,
691    #[serde(skip_serializing_if = "Option::is_none")]
692    pub hint_mostly_unused: Option<bool>,
693}
694
695impl Default for Profile {
696    fn default() -> Profile {
697        Profile {
698            name: "".into(),
699            opt_level: "0".into(),
700            root: ProfileRoot::Debug,
701            lto: Lto::Bool(false),
702            codegen_backend: None,
703            codegen_units: None,
704            debuginfo: DebugInfo::Resolved(TomlDebugInfo::None),
705            debug_assertions: false,
706            split_debuginfo: None,
707            overflow_checks: false,
708            rpath: false,
709            incremental: false,
710            panic: PanicStrategy::Unwind,
711            strip: Strip::Deferred(StripInner::None),
712            rustflags: vec![],
713            trim_paths: TomlTrimPaths::None,
714            hint_mostly_unused: None,
715        }
716    }
717}
718
719compact_debug! {
720    impl fmt::Debug for Profile {
721        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
722            let (default, default_name) = match self.name.as_str() {
723                "dev" => (Profile::default_dev(), "default_dev()"),
724                "release" => (Profile::default_release(), "default_release()"),
725                _ => (Profile::default(), "default()"),
726            };
727            [debug_the_fields(
728                name
729                opt_level
730                lto
731                root
732                codegen_backend
733                codegen_units
734                debuginfo
735                split_debuginfo
736                debug_assertions
737                overflow_checks
738                rpath
739                incremental
740                panic
741                strip
742                rustflags
743                trim_paths
744                hint_mostly_unused
745            )]
746        }
747    }
748}
749
750impl fmt::Display for Profile {
751    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
752        write!(f, "Profile({})", self.name)
753    }
754}
755
756impl hash::Hash for Profile {
757    fn hash<H>(&self, state: &mut H)
758    where
759        H: hash::Hasher,
760    {
761        self.comparable().hash(state);
762    }
763}
764
765impl cmp::PartialEq for Profile {
766    fn eq(&self, other: &Self) -> bool {
767        self.comparable() == other.comparable()
768    }
769}
770
771impl Profile {
772    /// Returns a built-in `dev` profile.
773    fn default_dev() -> Profile {
774        Profile {
775            name: "dev".into(),
776            root: ProfileRoot::Debug,
777            debuginfo: DebugInfo::Resolved(TomlDebugInfo::Full),
778            debug_assertions: true,
779            overflow_checks: true,
780            incremental: true,
781            ..Profile::default()
782        }
783    }
784
785    /// Returns a built-in `release` profile.
786    fn default_release() -> Profile {
787        Profile {
788            name: "release".into(),
789            root: ProfileRoot::Release,
790            opt_level: "3".into(),
791            ..Profile::default()
792        }
793    }
794
795    /// Compares all fields except `name`, which doesn't affect compilation.
796    /// This is necessary for `Unit` deduplication for things like "test" and
797    /// "dev" which are essentially the same.
798    fn comparable(&self) -> impl Hash + Eq + '_ {
799        (
800            self.opt_level,
801            self.lto,
802            self.codegen_backend,
803            self.codegen_units,
804            self.debuginfo,
805            self.split_debuginfo,
806            self.debug_assertions,
807            self.overflow_checks,
808            self.rpath,
809            (self.incremental, self.panic, self.strip),
810            &self.rustflags,
811            &self.trim_paths,
812        )
813    }
814}
815
816/// The debuginfo level setting.
817///
818/// This is semantically a [`TomlDebugInfo`], and should be used as so via the
819/// [`DebugInfo::into_inner`] method for all intents and purposes.
820///
821/// Internally, it's used to model a debuginfo level whose value can be deferred
822/// for optimization purposes: host dependencies usually don't need the same
823/// level as target dependencies. For dependencies that are shared between the
824/// two however, that value also affects reuse: different debuginfo levels would
825/// cause to build a unit twice. By deferring the choice until we know
826/// whether to choose the optimized value or the default value, we can make sure
827/// the unit is only built once and the unit graph is still optimized.
828#[derive(Debug, Copy, Clone, serde::Serialize)]
829#[serde(untagged)]
830pub enum DebugInfo {
831    /// A debuginfo level that is fixed and will not change.
832    ///
833    /// This can be set by a profile, user, or default value.
834    Resolved(TomlDebugInfo),
835    /// For internal purposes: a deferred debuginfo level that can be optimized
836    /// away, but has this value otherwise.
837    ///
838    /// Behaves like `Resolved` in all situations except for the default build
839    /// dependencies profile: whenever a build dependency is not shared with
840    /// runtime dependencies, this level is weakened to a lower level that is
841    /// faster to build (see [`DebugInfo::weaken`]).
842    ///
843    /// In all other situations, this level value will be the one to use.
844    Deferred(TomlDebugInfo),
845}
846
847impl DebugInfo {
848    /// The main way to interact with this debuginfo level, turning it into a [`TomlDebugInfo`].
849    pub fn into_inner(self) -> TomlDebugInfo {
850        match self {
851            DebugInfo::Resolved(v) | DebugInfo::Deferred(v) => v,
852        }
853    }
854
855    /// Returns true if any debuginfo will be generated. Helper
856    /// for a common operation on the usual `Option` representation.
857    pub(crate) fn is_turned_on(&self) -> bool {
858        !matches!(self.into_inner(), TomlDebugInfo::None)
859    }
860
861    pub(crate) fn is_deferred(&self) -> bool {
862        matches!(self, DebugInfo::Deferred(_))
863    }
864
865    /// Force the deferred, preferred, debuginfo level to a finalized explicit value.
866    pub(crate) fn finalize(self) -> Self {
867        match self {
868            DebugInfo::Deferred(v) => DebugInfo::Resolved(v),
869            _ => self,
870        }
871    }
872
873    /// Reset to the lowest level: no debuginfo.
874    pub(crate) fn weaken(self) -> Self {
875        DebugInfo::Resolved(TomlDebugInfo::None)
876    }
877}
878
879impl PartialEq for DebugInfo {
880    fn eq(&self, other: &DebugInfo) -> bool {
881        self.into_inner().eq(&other.into_inner())
882    }
883}
884
885impl Eq for DebugInfo {}
886
887impl Hash for DebugInfo {
888    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
889        self.into_inner().hash(state);
890    }
891}
892
893impl PartialOrd for DebugInfo {
894    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
895        self.into_inner().partial_cmp(&other.into_inner())
896    }
897}
898
899impl Ord for DebugInfo {
900    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
901        self.into_inner().cmp(&other.into_inner())
902    }
903}
904
905/// The link-time-optimization setting.
906#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)]
907pub enum Lto {
908    /// Explicitly no LTO, disables thin-LTO.
909    Off,
910    /// True = "Fat" LTO
911    /// False = rustc default (no args), currently "thin LTO"
912    Bool(bool),
913    /// Named LTO settings like "thin".
914    Named(InternedString),
915}
916
917impl serde::ser::Serialize for Lto {
918    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
919    where
920        S: serde::ser::Serializer,
921    {
922        match self {
923            Lto::Off => "off".serialize(s),
924            Lto::Bool(b) => b.to_string().serialize(s),
925            Lto::Named(n) => n.serialize(s),
926        }
927    }
928}
929
930/// The `panic` setting.
931#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, PartialOrd, Ord, serde::Serialize)]
932#[serde(rename_all = "kebab-case")]
933pub enum PanicStrategy {
934    Unwind,
935    Abort,
936    ImmediateAbort,
937}
938
939impl fmt::Display for PanicStrategy {
940    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
941        match *self {
942            PanicStrategy::Unwind => "unwind",
943            PanicStrategy::Abort => "abort",
944            PanicStrategy::ImmediateAbort => "immediate-abort",
945        }
946        .fmt(f)
947    }
948}
949
950#[derive(
951    Clone, Copy, PartialEq, Eq, Debug, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
952)]
953pub enum StripInner {
954    /// Don't remove any symbols
955    None,
956    /// Named Strip settings
957    Named(InternedString),
958}
959
960impl fmt::Display for StripInner {
961    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
962        match *self {
963            StripInner::None => "none",
964            StripInner::Named(s) => s.as_str(),
965        }
966        .fmt(f)
967    }
968}
969
970/// The setting for choosing which symbols to strip.
971///
972/// This is semantically a [`StripInner`], and should be used as so via the
973/// [`Strip::into_inner`] method for all intents and purposes.
974///
975/// Internally, it's used to model a strip option whose value can be deferred
976/// for optimization purposes: when no package being compiled requires debuginfo,
977/// then we can strip debuginfo to remove pre-existing debug symbols from the
978/// standard library.
979#[derive(Clone, Copy, Debug, Eq, serde::Serialize, serde::Deserialize)]
980#[serde(rename_all = "lowercase")]
981pub enum Strip {
982    /// A strip option that is fixed and will not change.
983    Resolved(StripInner),
984    /// A strip option that might be overridden by Cargo for optimization
985    /// purposes.
986    Deferred(StripInner),
987}
988
989impl Strip {
990    /// The main way to interact with this strip option, turning it into a [`StripInner`].
991    pub fn into_inner(self) -> StripInner {
992        match self {
993            Strip::Resolved(v) | Strip::Deferred(v) => v,
994        }
995    }
996
997    pub(crate) fn is_deferred(&self) -> bool {
998        matches!(self, Strip::Deferred(_))
999    }
1000
1001    /// Reset to stripping debuginfo.
1002    pub(crate) fn strip_debuginfo(self) -> Self {
1003        Strip::Resolved(StripInner::Named("debuginfo".into()))
1004    }
1005}
1006
1007impl PartialEq for Strip {
1008    fn eq(&self, other: &Self) -> bool {
1009        self.into_inner().eq(&other.into_inner())
1010    }
1011}
1012
1013impl Hash for Strip {
1014    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1015        self.into_inner().hash(state);
1016    }
1017}
1018
1019impl PartialOrd for Strip {
1020    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1021        self.into_inner().partial_cmp(&other.into_inner())
1022    }
1023}
1024
1025impl Ord for Strip {
1026    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1027        self.into_inner().cmp(&other.into_inner())
1028    }
1029}
1030
1031/// Flags used in creating `Unit`s to indicate the purpose for the target, and
1032/// to ensure the target's dependencies have the correct settings.
1033///
1034/// This means these are passed down from the root of the dependency tree to apply
1035/// to most child dependencies.
1036#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
1037pub struct UnitFor {
1038    /// A target for `build.rs` or any of its dependencies, or a proc-macro or
1039    /// any of its dependencies. This enables `build-override` profiles for
1040    /// these targets.
1041    ///
1042    /// An invariant is that if `host_features` is true, `host` must be true.
1043    ///
1044    /// Note that this is `true` for `RunCustomBuild` units, even though that
1045    /// unit should *not* use build-override profiles. This is a bit of a
1046    /// special case. When computing the `RunCustomBuild` unit, it manually
1047    /// uses the `get_profile_run_custom_build` method to get the correct
1048    /// profile information for the unit. `host` needs to be true so that all
1049    /// of the dependencies of that `RunCustomBuild` unit have this flag be
1050    /// sticky (and forced to `true` for all further dependencies) — which is
1051    /// the whole point of `UnitFor`.
1052    host: bool,
1053    /// A target for a build dependency or proc-macro (or any of its
1054    /// dependencies). This is used for computing features of build
1055    /// dependencies and proc-macros independently of other dependency kinds.
1056    ///
1057    /// The subtle difference between this and `host` is that the build script
1058    /// for a non-host package sets this to `false` because it wants the
1059    /// features of the non-host package (whereas `host` is true because the
1060    /// build script is being built for the host). `host_features` becomes
1061    /// `true` for build-dependencies or proc-macros, or any of their
1062    /// dependencies. For example, with this dependency tree:
1063    ///
1064    /// ```text
1065    /// foo
1066    /// ├── foo build.rs
1067    /// │   └── shared_dep (BUILD dependency)
1068    /// │       └── shared_dep build.rs
1069    /// └── shared_dep (Normal dependency)
1070    ///     └── shared_dep build.rs
1071    /// ```
1072    ///
1073    /// In this example, `foo build.rs` is `HOST=true`, `HOST_FEATURES=false`.
1074    /// This is so that `foo build.rs` gets the profile settings for build
1075    /// scripts (`HOST=true`) and features of foo (`HOST_FEATURES=false`) because
1076    /// build scripts need to know which features their package is being built
1077    /// with.
1078    ///
1079    /// But in the case of `shared_dep`, when built as a build dependency,
1080    /// both flags are true (it only wants the build-dependency features).
1081    /// When `shared_dep` is built as a normal dependency, then `shared_dep
1082    /// build.rs` is `HOST=true`, `HOST_FEATURES=false` for the same reasons that
1083    /// foo's build script is set that way.
1084    host_features: bool,
1085    /// How Cargo processes the `panic` setting or profiles.
1086    panic_setting: PanicSetting,
1087
1088    /// The compile kind of the root unit for which artifact dependencies are built.
1089    /// This is required particularly for the `target = "target"` setting of artifact
1090    /// dependencies which mean to inherit the `--target` specified on the command-line.
1091    /// However, that is a multi-value argument and root units are already created to
1092    /// reflect one unit per --target. Thus we have to build one artifact with the
1093    /// correct target for each of these trees.
1094    /// Note that this will always be set as we don't initially know if there are
1095    /// artifacts that make use of it.
1096    root_compile_kind: CompileKind,
1097
1098    /// This is only set for artifact dependencies which have their
1099    /// `<target-tuple>|target` set.
1100    /// If so, this information is used as part of the key for resolving their features,
1101    /// allowing for target-dependent feature resolution within the entire dependency tree.
1102    /// Note that this target corresponds to the target used to build the units in that
1103    /// dependency tree, too, but this copy of it is specifically used for feature lookup.
1104    artifact_target_for_features: Option<CompileTarget>,
1105}
1106
1107/// How Cargo processes the `panic` setting or profiles.
1108///
1109/// This is done to handle test/benches inheriting from dev/release,
1110/// as well as forcing `for_host` units to always unwind.
1111/// It also interacts with [`-Z panic-abort-tests`].
1112///
1113/// [`-Z panic-abort-tests`]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#panic-abort-tests
1114#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
1115enum PanicSetting {
1116    /// Used to force a unit to always be compiled with the `panic=unwind`
1117    /// strategy, notably for build scripts, proc macros, etc.
1118    AlwaysUnwind,
1119
1120    /// Indicates that this unit will read its `profile` setting and use
1121    /// whatever is configured there.
1122    ReadProfile,
1123}
1124
1125impl UnitFor {
1126    /// A unit for a normal target/dependency (i.e., not custom build,
1127    /// proc macro/plugin, or test/bench).
1128    pub fn new_normal(root_compile_kind: CompileKind) -> UnitFor {
1129        UnitFor {
1130            host: false,
1131            host_features: false,
1132            panic_setting: PanicSetting::ReadProfile,
1133            root_compile_kind,
1134            artifact_target_for_features: None,
1135        }
1136    }
1137
1138    /// A unit for a custom build script or proc-macro or its dependencies.
1139    ///
1140    /// The `host_features` parameter is whether or not this is for a build
1141    /// dependency or proc-macro (something that requires being built "on the
1142    /// host"). Build scripts for non-host units should use `false` because
1143    /// they want to use the features of the package they are running for.
1144    pub fn new_host(host_features: bool, root_compile_kind: CompileKind) -> UnitFor {
1145        UnitFor {
1146            host: true,
1147            host_features,
1148            // Force build scripts to always use `panic=unwind` for now to
1149            // maximally share dependencies with procedural macros.
1150            panic_setting: PanicSetting::AlwaysUnwind,
1151            root_compile_kind,
1152            artifact_target_for_features: None,
1153        }
1154    }
1155
1156    /// A unit for a compiler plugin or their dependencies.
1157    pub fn new_compiler(root_compile_kind: CompileKind) -> UnitFor {
1158        UnitFor {
1159            host: false,
1160            // The feature resolver doesn't know which dependencies are
1161            // plugins, so for now plugins don't split features. Since plugins
1162            // are mostly deprecated, just leave this as false.
1163            host_features: false,
1164            // Force plugins to use `panic=abort` so panics in the compiler do
1165            // not abort the process but instead end with a reasonable error
1166            // message that involves catching the panic in the compiler.
1167            panic_setting: PanicSetting::AlwaysUnwind,
1168            root_compile_kind,
1169            artifact_target_for_features: None,
1170        }
1171    }
1172
1173    /// A unit for a test/bench target or their dependencies.
1174    ///
1175    /// Note that `config` is taken here for unstable CLI features to detect
1176    /// whether `panic=abort` is supported for tests. Historical versions of
1177    /// rustc did not support this, but newer versions do with an unstable
1178    /// compiler flag.
1179    pub fn new_test(gctx: &GlobalContext, root_compile_kind: CompileKind) -> UnitFor {
1180        UnitFor {
1181            host: false,
1182            host_features: false,
1183            // We're testing out an unstable feature (`-Zpanic-abort-tests`)
1184            // which inherits the panic setting from the dev/release profile
1185            // (basically avoid recompiles) but historical defaults required
1186            // that we always unwound.
1187            panic_setting: if gctx.cli_unstable().panic_abort_tests {
1188                PanicSetting::ReadProfile
1189            } else {
1190                PanicSetting::AlwaysUnwind
1191            },
1192            root_compile_kind,
1193            artifact_target_for_features: None,
1194        }
1195    }
1196
1197    /// This is a special case for unit tests of a proc-macro.
1198    ///
1199    /// Proc-macro unit tests are forced to be run on the host.
1200    pub fn new_host_test(gctx: &GlobalContext, root_compile_kind: CompileKind) -> UnitFor {
1201        let mut unit_for = UnitFor::new_test(gctx, root_compile_kind);
1202        unit_for.host = true;
1203        unit_for.host_features = true;
1204        unit_for
1205    }
1206
1207    /// Returns a new copy updated based on the target dependency.
1208    ///
1209    /// This is where the magic happens that the `host`/`host_features` settings
1210    /// transition in a sticky fashion. As the dependency graph is being
1211    /// built, once those flags are set, they stay set for the duration of
1212    /// that portion of tree.
1213    pub fn with_dependency(
1214        self,
1215        parent: &Unit,
1216        dep_target: &Target,
1217        root_compile_kind: CompileKind,
1218    ) -> UnitFor {
1219        // A build script or proc-macro transitions this to being built for the host.
1220        let dep_for_host = dep_target.for_host();
1221        // This is where feature decoupling of host versus target happens.
1222        //
1223        // Once host features are desired, they are always desired.
1224        //
1225        // A proc-macro should always use host features.
1226        //
1227        // Dependencies of a build script should use host features (subtle
1228        // point: the build script itself does *not* use host features, that's
1229        // why the parent is checked here, and not the dependency).
1230        let host_features =
1231            self.host_features || parent.target.is_custom_build() || dep_target.proc_macro();
1232        // Build scripts and proc macros, and all of their dependencies are
1233        // AlwaysUnwind.
1234        let panic_setting = if dep_for_host {
1235            PanicSetting::AlwaysUnwind
1236        } else {
1237            self.panic_setting
1238        };
1239        let artifact_target_for_features =
1240            // build.rs and proc-macros are always for host.
1241            if dep_target.proc_macro() || parent.target.is_custom_build() {
1242                None
1243            } else {
1244                self.artifact_target_for_features
1245            };
1246        UnitFor {
1247            host: self.host || dep_for_host,
1248            host_features,
1249            panic_setting,
1250            root_compile_kind,
1251            artifact_target_for_features,
1252        }
1253    }
1254
1255    pub fn for_custom_build(self) -> UnitFor {
1256        UnitFor {
1257            host: true,
1258            host_features: self.host_features,
1259            // Force build scripts to always use `panic=unwind` for now to
1260            // maximally share dependencies with procedural macros.
1261            panic_setting: PanicSetting::AlwaysUnwind,
1262            root_compile_kind: self.root_compile_kind,
1263            artifact_target_for_features: self.artifact_target_for_features,
1264        }
1265    }
1266
1267    /// Set the artifact compile target for use in features using the given `artifact`.
1268    pub(crate) fn with_artifact_features(mut self, artifact: &Artifact) -> UnitFor {
1269        self.artifact_target_for_features = artifact.target().and_then(|t| t.to_compile_target());
1270        self
1271    }
1272
1273    /// Set the artifact compile target as determined by a resolved compile target. This is used if `target = "target"`.
1274    pub(crate) fn with_artifact_features_from_resolved_compile_kind(
1275        mut self,
1276        kind: Option<CompileKind>,
1277    ) -> UnitFor {
1278        self.artifact_target_for_features = kind.and_then(|kind| match kind {
1279            CompileKind::Host => None,
1280            CompileKind::Target(triple) => Some(triple),
1281        });
1282        self
1283    }
1284
1285    /// Returns `true` if this unit is for a build script or any of its
1286    /// dependencies, or a proc macro or any of its dependencies.
1287    pub fn is_for_host(&self) -> bool {
1288        self.host
1289    }
1290
1291    pub fn is_for_host_features(&self) -> bool {
1292        self.host_features
1293    }
1294
1295    /// Returns how `panic` settings should be handled for this profile
1296    fn panic_setting(&self) -> PanicSetting {
1297        self.panic_setting
1298    }
1299
1300    /// We might contain a parent artifact compile kind for features already, but will
1301    /// gladly accept the one of this dependency as an override as it defines how
1302    /// the artifact is built.
1303    /// If we are an artifact but don't specify a `target`, we assume the default
1304    /// compile kind that is suitable in this situation.
1305    pub(crate) fn map_to_features_for(&self, dep_artifact: Option<&Artifact>) -> FeaturesFor {
1306        FeaturesFor::from_for_host_or_artifact_target(
1307            self.is_for_host_features(),
1308            match dep_artifact {
1309                Some(artifact) => artifact
1310                    .target()
1311                    .and_then(|t| t.to_resolved_compile_target(self.root_compile_kind)),
1312                None => self.artifact_target_for_features,
1313            },
1314        )
1315    }
1316
1317    pub(crate) fn root_compile_kind(&self) -> CompileKind {
1318        self.root_compile_kind
1319    }
1320}
1321
1322/// Takes the manifest profiles, and overlays the config profiles on-top.
1323///
1324/// Returns a new copy of the profile map with all the mergers complete.
1325fn merge_config_profiles(
1326    ws: &Workspace<'_>,
1327    requested_profile: InternedString,
1328) -> CargoResult<BTreeMap<InternedString, TomlProfile>> {
1329    let mut profiles = match ws.profiles() {
1330        Some(profiles) => profiles
1331            .get_all()
1332            .iter()
1333            .map(|(k, v)| (InternedString::new(k), v.clone()))
1334            .collect(),
1335        None => BTreeMap::new(),
1336    };
1337    // Set of profile names to check if defined in config only.
1338    let mut check_to_add = HashSet::default();
1339    check_to_add.insert(requested_profile);
1340    // Merge config onto manifest profiles.
1341    for (name, profile) in &mut profiles {
1342        if let Some(config_profile) = get_config_profile(ws, name)? {
1343            profile.merge(&config_profile);
1344        }
1345        if let Some(inherits) = &profile.inherits {
1346            check_to_add.insert(inherits.into());
1347        }
1348    }
1349    // Add the built-in profiles. This is important for things like `cargo
1350    // test` which implicitly use the "dev" profile for dependencies.
1351    for name in ["dev", "release", "debug", "test", "bench"] {
1352        check_to_add.insert(name.into());
1353    }
1354    // Add config-only profiles.
1355    // Need to iterate repeatedly to get all the inherits values.
1356    let mut current = HashSet::default();
1357    while !check_to_add.is_empty() {
1358        std::mem::swap(&mut current, &mut check_to_add);
1359        for name in current.drain() {
1360            if !profiles.contains_key(name.as_str()) {
1361                if let Some(config_profile) = get_config_profile(ws, &name)? {
1362                    if let Some(inherits) = &config_profile.inherits {
1363                        check_to_add.insert(inherits.into());
1364                    }
1365                    profiles.insert(name, config_profile);
1366                }
1367            }
1368        }
1369    }
1370    Ok(profiles)
1371}
1372
1373/// Helper for fetching a profile from config.
1374fn get_config_profile(ws: &Workspace<'_>, name: &str) -> CargoResult<Option<TomlProfile>> {
1375    let profile: Option<context::Value<TomlProfile>> = ws.gctx().get(["profile", name])?;
1376    let Some(profile) = profile else {
1377        return Ok(None);
1378    };
1379    let mut warnings = Vec::new();
1380    validate_profile(
1381        &profile.val,
1382        name,
1383        ws.gctx().cli_unstable(),
1384        ws.unstable_features(),
1385        &mut warnings,
1386    )
1387    .with_context(|| {
1388        format!(
1389            "config profile `{}` is not valid (defined in `{}`)",
1390            name, profile.definition
1391        )
1392    })?;
1393    warn_ignored_hint_mostly_unused(ws.gctx(), &profile, name, &mut warnings)?;
1394    for warning in warnings {
1395        ws.gctx().shell().warn(warning)?;
1396    }
1397    Ok(Some(profile.val))
1398}
1399
1400/// Warns about each `hint-mostly-unused = true` in a config profile, which is ignored without
1401/// `-Zprofile-hint-mostly-unused`. Manifest profiles are covered by the parse pass instead, see
1402/// [`crate::diagnostics::rules::PARSE_PASS_RULES`].
1403fn warn_ignored_hint_mostly_unused(
1404    gctx: &GlobalContext,
1405    profile: &context::Value<TomlProfile>,
1406    name: &str,
1407    warnings: &mut Vec<String>,
1408) -> CargoResult<()> {
1409    if gctx.cli_unstable().profile_hint_mostly_unused {
1410        return Ok(());
1411    }
1412    let mut key = context::ConfigKey::from_str("profile");
1413    key.push(name);
1414    let mut tables = vec![(&profile.val, key.clone())];
1415    if let Some(build_override) = &profile.val.build_override {
1416        let mut key = key.clone();
1417        key.push("build-override");
1418        tables.push((build_override, key));
1419    }
1420    for (spec, package) in profile.val.package.iter().flatten() {
1421        let mut key = key.clone();
1422        key.push("package");
1423        key.push(&spec.to_string());
1424        tables.push((package, key));
1425    }
1426    for (table, key) in tables {
1427        if table.hint_mostly_unused == Some(true) {
1428            // Config sources are merged key by key, so `profile.definition` may not be where this
1429            // key was set. A legacy spec like `"foo:1.0.0"` is normalized by `to_string`, so its
1430            // lookup misses and the profile's definition is used instead.
1431            let mut hint_key = key.clone();
1432            hint_key.push("hint-mostly-unused");
1433            let hint = gctx.get_cv_with_env(&hint_key)?;
1434            let definition = hint
1435                .as_ref()
1436                .map_or(&profile.definition, |hint| hint.definition());
1437            warnings.push(format!(
1438                "ignoring `hint-mostly-unused` in `{key}` (defined in {definition}), \
1439                 pass `-Zprofile-hint-mostly-unused` to enable it"
1440            ));
1441        }
1442    }
1443    Ok(())
1444}
1445
1446/// Validate that a package does not match multiple package override specs.
1447///
1448/// For example `[profile.dev.package.bar]` and `[profile.dev.package."bar:0.5.0"]`
1449/// would both match `bar:0.5.0` which would be ambiguous.
1450fn validate_packages_unique(
1451    resolve: &Resolve,
1452    name: &str,
1453    toml: &Option<TomlProfile>,
1454) -> CargoResult<HashSet<PackageIdSpec>> {
1455    let Some(toml) = toml else {
1456        return Ok(HashSet::default());
1457    };
1458    let Some(overrides) = toml.package.as_ref() else {
1459        return Ok(HashSet::default());
1460    };
1461    // Verify that a package doesn't match multiple spec overrides.
1462    let mut found = HashSet::default();
1463    for pkg_id in resolve.iter() {
1464        let matches: Vec<&PackageIdSpec> = overrides
1465            .keys()
1466            .filter_map(|key| match *key {
1467                ProfilePackageSpec::All => None,
1468                ProfilePackageSpec::Spec(ref spec) => {
1469                    if spec.matches(pkg_id) {
1470                        Some(spec)
1471                    } else {
1472                        None
1473                    }
1474                }
1475            })
1476            .collect();
1477        match matches.len() {
1478            0 => {}
1479            1 => {
1480                found.insert(matches[0].clone());
1481            }
1482            _ => {
1483                let specs = matches
1484                    .iter()
1485                    .map(|spec| spec.to_string())
1486                    .collect::<Vec<_>>()
1487                    .join(", ");
1488                bail!(
1489                    "multiple package overrides in profile `{}` match package `{}`\n\
1490                     found package specs: {}",
1491                    name,
1492                    pkg_id,
1493                    specs
1494                );
1495            }
1496        }
1497    }
1498    Ok(found)
1499}
1500
1501/// Check for any profile override specs that do not match any known packages.
1502///
1503/// This helps check for typos and mistakes.
1504fn validate_packages_unmatched(
1505    shell: &mut Shell,
1506    resolve: &Resolve,
1507    name: &str,
1508    toml: &TomlProfile,
1509    found: &HashSet<PackageIdSpec>,
1510) -> CargoResult<()> {
1511    let Some(overrides) = toml.package.as_ref() else {
1512        return Ok(());
1513    };
1514
1515    // Verify every override matches at least one package.
1516    let missing_specs = overrides.keys().filter_map(|key| {
1517        if let ProfilePackageSpec::Spec(ref spec) = *key {
1518            if !found.contains(spec) {
1519                return Some(spec);
1520            }
1521        }
1522        None
1523    });
1524    for spec in missing_specs {
1525        // See if there is an exact name match.
1526        let name_matches: Vec<String> = resolve
1527            .iter()
1528            .filter_map(|pkg_id| {
1529                if pkg_id.name() == spec.name() {
1530                    Some(pkg_id.to_string())
1531                } else {
1532                    None
1533                }
1534            })
1535            .collect();
1536        if name_matches.is_empty() {
1537            let suggestion = closest_msg(
1538                &spec.name(),
1539                resolve.iter(),
1540                |p| p.name().as_str(),
1541                "package",
1542            );
1543            shell.warn(format!(
1544                "profile package spec `{}` in profile `{}` did not match any packages{}",
1545                spec, name, suggestion
1546            ))?;
1547        } else {
1548            shell.warn(format!(
1549                "profile package spec `{}` in profile `{}` \
1550                 has a version or URL that does not match any of the packages: {}",
1551                spec,
1552                name,
1553                name_matches.join(", ")
1554            ))?;
1555        }
1556    }
1557    Ok(())
1558}
1559
1560/// Returns `true` if a string is a toggle that turns an option off.
1561fn is_off(s: &str) -> bool {
1562    matches!(s, "off" | "n" | "no" | "none")
1563}