Skip to main content

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