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