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