Skip to main content

cargo/core/
profiles.rs

1use crate::core::compiler::CompileMode;
2use crate::core::interning::InternedString;
3use crate::core::resolver::features::FeaturesFor;
4use crate::core::{Feature, Features, PackageId, PackageIdSpec, Resolve, Shell};
5use crate::util::errors::CargoResultExt;
6use crate::util::toml::{ProfilePackageSpec, StringOrBool, TomlProfile, TomlProfiles, U32OrBool};
7use crate::util::{closest_msg, config, CargoResult, Config};
8use anyhow::bail;
9use std::collections::{BTreeMap, HashMap, HashSet};
10use std::{cmp, env, fmt, hash};
11
12/// Collection of all profiles.
13#[derive(Clone, Debug)]
14pub struct Profiles {
15    /// Incremental compilation can be overridden globally via:
16    /// - `CARGO_INCREMENTAL` environment variable.
17    /// - `build.incremental` config value.
18    incremental: Option<bool>,
19    /// Map of profile name to directory name for that profile.
20    dir_names: HashMap<InternedString, InternedString>,
21    /// The profile makers. Key is the profile name.
22    by_name: HashMap<InternedString, ProfileMaker>,
23    /// Whether or not unstable "named" profiles are enabled.
24    named_profiles_enabled: bool,
25    /// The profile the user requested to use.
26    requested_profile: InternedString,
27}
28
29impl Profiles {
30    pub fn new(
31        profiles: Option<&TomlProfiles>,
32        config: &Config,
33        requested_profile: InternedString,
34        features: &Features,
35    ) -> CargoResult<Profiles> {
36        let incremental = match env::var_os("CARGO_INCREMENTAL") {
37            Some(v) => Some(v == "1"),
38            None => config.build_config()?.incremental,
39        };
40        let mut profiles = merge_config_profiles(profiles, config, requested_profile, features)?;
41
42        if !features.is_enabled(Feature::named_profiles()) {
43            let mut profile_makers = Profiles {
44                incremental,
45                named_profiles_enabled: false,
46                dir_names: Self::predefined_dir_names(),
47                by_name: HashMap::new(),
48                requested_profile,
49            };
50
51            profile_makers.by_name.insert(
52                InternedString::new("dev"),
53                ProfileMaker::new(Profile::default_dev(), profiles.remove("dev")),
54            );
55            profile_makers
56                .dir_names
57                .insert(InternedString::new("dev"), InternedString::new("debug"));
58
59            profile_makers.by_name.insert(
60                InternedString::new("release"),
61                ProfileMaker::new(Profile::default_release(), profiles.remove("release")),
62            );
63            profile_makers.dir_names.insert(
64                InternedString::new("release"),
65                InternedString::new("release"),
66            );
67
68            profile_makers.by_name.insert(
69                InternedString::new("test"),
70                ProfileMaker::new(Profile::default_test(), profiles.remove("test")),
71            );
72            profile_makers
73                .dir_names
74                .insert(InternedString::new("test"), InternedString::new("debug"));
75
76            profile_makers.by_name.insert(
77                InternedString::new("bench"),
78                ProfileMaker::new(Profile::default_bench(), profiles.remove("bench")),
79            );
80            profile_makers
81                .dir_names
82                .insert(InternedString::new("bench"), InternedString::new("release"));
83
84            profile_makers.by_name.insert(
85                InternedString::new("doc"),
86                ProfileMaker::new(Profile::default_doc(), profiles.remove("doc")),
87            );
88            profile_makers
89                .dir_names
90                .insert(InternedString::new("doc"), InternedString::new("debug"));
91
92            return Ok(profile_makers);
93        }
94
95        let mut profile_makers = Profiles {
96            incremental,
97            named_profiles_enabled: true,
98            dir_names: Self::predefined_dir_names(),
99            by_name: HashMap::new(),
100            requested_profile,
101        };
102
103        Self::add_root_profiles(&mut profile_makers, &profiles)?;
104
105        // Merge with predefined profiles.
106        use std::collections::btree_map::Entry;
107        for (predef_name, mut predef_prof) in Self::predefined_profiles().into_iter() {
108            match profiles.entry(InternedString::new(predef_name)) {
109                Entry::Vacant(vac) => {
110                    vac.insert(predef_prof);
111                }
112                Entry::Occupied(mut oc) => {
113                    // Override predefined with the user-provided Toml.
114                    let r = oc.get_mut();
115                    predef_prof.merge(r);
116                    *r = predef_prof;
117                }
118            }
119        }
120
121        for (name, profile) in &profiles {
122            profile_makers.add_maker(*name, profile, &profiles)?;
123        }
124        // Verify that the requested profile is defined *somewhere*.
125        // This simplifies the API (no need for CargoResult), and enforces
126        // assumptions about how config profiles are loaded.
127        profile_makers.get_profile_maker(requested_profile)?;
128        Ok(profile_makers)
129    }
130
131    /// Returns the hard-coded directory names for built-in profiles.
132    fn predefined_dir_names() -> HashMap<InternedString, InternedString> {
133        let mut dir_names = HashMap::new();
134        dir_names.insert(InternedString::new("dev"), InternedString::new("debug"));
135        dir_names.insert(InternedString::new("check"), InternedString::new("debug"));
136        dir_names.insert(InternedString::new("test"), InternedString::new("debug"));
137        dir_names.insert(InternedString::new("bench"), InternedString::new("release"));
138        dir_names
139    }
140
141    /// Initialize `by_name` with the two "root" profiles, `dev`, and
142    /// `release` given the user's definition.
143    fn add_root_profiles(
144        profile_makers: &mut Profiles,
145        profiles: &BTreeMap<InternedString, TomlProfile>,
146    ) -> CargoResult<()> {
147        profile_makers.by_name.insert(
148            InternedString::new("dev"),
149            ProfileMaker::new(Profile::default_dev(), profiles.get("dev").cloned()),
150        );
151
152        profile_makers.by_name.insert(
153            InternedString::new("release"),
154            ProfileMaker::new(Profile::default_release(), profiles.get("release").cloned()),
155        );
156        Ok(())
157    }
158
159    /// Returns the built-in profiles (not including dev/release, which are
160    /// "root" profiles).
161    fn predefined_profiles() -> Vec<(&'static str, TomlProfile)> {
162        vec![
163            (
164                "bench",
165                TomlProfile {
166                    inherits: Some(InternedString::new("release")),
167                    ..TomlProfile::default()
168                },
169            ),
170            (
171                "test",
172                TomlProfile {
173                    inherits: Some(InternedString::new("dev")),
174                    ..TomlProfile::default()
175                },
176            ),
177            (
178                "check",
179                TomlProfile {
180                    inherits: Some(InternedString::new("dev")),
181                    ..TomlProfile::default()
182                },
183            ),
184            (
185                "doc",
186                TomlProfile {
187                    inherits: Some(InternedString::new("dev")),
188                    ..TomlProfile::default()
189                },
190            ),
191        ]
192    }
193
194    /// Creates a `ProfileMaker`, and inserts it into `self.by_name`.
195    fn add_maker(
196        &mut self,
197        name: InternedString,
198        profile: &TomlProfile,
199        profiles: &BTreeMap<InternedString, TomlProfile>,
200    ) -> CargoResult<()> {
201        match &profile.dir_name {
202            None => {}
203            Some(dir_name) => {
204                self.dir_names.insert(name, dir_name.to_owned());
205            }
206        }
207
208        // dev/release are "roots" and don't inherit.
209        if name == "dev" || name == "release" {
210            if profile.inherits.is_some() {
211                bail!(
212                    "`inherits` must not be specified in root profile `{}`",
213                    name
214                );
215            }
216            // Already inserted from `add_root_profiles`, no need to do anything.
217            return Ok(());
218        }
219
220        // Keep track for inherits cycles.
221        let mut set = HashSet::new();
222        set.insert(name);
223        let maker = self.process_chain(name, profile, &mut set, profiles)?;
224        self.by_name.insert(name, maker);
225        Ok(())
226    }
227
228    /// Build a `ProfileMaker` by recursively following the `inherits` setting.
229    ///
230    /// * `name`: The name of the profile being processed.
231    /// * `profile`: The TOML profile being processed.
232    /// * `set`: Set of profiles that have been visited, used to detect cycles.
233    /// * `profiles`: Map of all TOML profiles.
234    ///
235    /// Returns a `ProfileMaker` to be used for the given named profile.
236    fn process_chain(
237        &mut self,
238        name: InternedString,
239        profile: &TomlProfile,
240        set: &mut HashSet<InternedString>,
241        profiles: &BTreeMap<InternedString, TomlProfile>,
242    ) -> CargoResult<ProfileMaker> {
243        let mut maker = match profile.inherits {
244            Some(inherits_name) if inherits_name == "dev" || inherits_name == "release" => {
245                // These are the root profiles added in `add_root_profiles`.
246                self.get_profile_maker(inherits_name).unwrap().clone()
247            }
248            Some(inherits_name) => {
249                if !set.insert(inherits_name) {
250                    bail!(
251                        "profile inheritance loop detected with profile `{}` inheriting `{}`",
252                        name,
253                        inherits_name
254                    );
255                }
256
257                match profiles.get(&inherits_name) {
258                    None => {
259                        bail!(
260                            "profile `{}` inherits from `{}`, but that profile is not defined",
261                            name,
262                            inherits_name
263                        );
264                    }
265                    Some(parent) => self.process_chain(inherits_name, parent, set, profiles)?,
266                }
267            }
268            None => {
269                bail!(
270                    "profile `{}` is missing an `inherits` directive \
271                     (`inherits` is required for all profiles except `dev` or `release`)",
272                    name
273                );
274            }
275        };
276        match &mut maker.toml {
277            Some(toml) => toml.merge(profile),
278            None => maker.toml = Some(profile.clone()),
279        };
280        Ok(maker)
281    }
282
283    /// Retrieves the profile for a target.
284    /// `is_member` is whether or not this package is a member of the
285    /// workspace.
286    pub fn get_profile(
287        &self,
288        pkg_id: PackageId,
289        is_member: bool,
290        unit_for: UnitFor,
291        mode: CompileMode,
292    ) -> Profile {
293        let (profile_name, inherits) = if !self.named_profiles_enabled {
294            // With the feature disabled, we degrade `--profile` back to the
295            // `--release` and `--debug` predicates, and convert back from
296            // ProfileKind::Custom instantiation.
297
298            let release = match self.requested_profile.as_str() {
299                "release" | "bench" => true,
300                _ => false,
301            };
302
303            match mode {
304                CompileMode::Test | CompileMode::Bench => {
305                    if release {
306                        (
307                            InternedString::new("bench"),
308                            Some(InternedString::new("release")),
309                        )
310                    } else {
311                        (
312                            InternedString::new("test"),
313                            Some(InternedString::new("dev")),
314                        )
315                    }
316                }
317                CompileMode::Build
318                | CompileMode::Check { .. }
319                | CompileMode::Doctest
320                | CompileMode::RunCustomBuild => {
321                    // Note: `RunCustomBuild` doesn't normally use this code path.
322                    // `build_unit_profiles` normally ensures that it selects the
323                    // ancestor's profile. However, `cargo clean -p` can hit this
324                    // path.
325                    if release {
326                        (InternedString::new("release"), None)
327                    } else {
328                        (InternedString::new("dev"), None)
329                    }
330                }
331                CompileMode::Doc { .. } => (InternedString::new("doc"), None),
332            }
333        } else {
334            (self.requested_profile, None)
335        };
336        let maker = self.get_profile_maker(profile_name).unwrap();
337        let mut profile = maker.get_profile(Some(pkg_id), is_member, unit_for);
338
339        // Dealing with `panic=abort` and `panic=unwind` requires some special
340        // treatment. Be sure to process all the various options here.
341        match unit_for.panic_setting() {
342            PanicSetting::AlwaysUnwind => profile.panic = PanicStrategy::Unwind,
343            PanicSetting::ReadProfile => {}
344            PanicSetting::Inherit => {
345                if let Some(inherits) = inherits {
346                    // TODO: Fixme, broken with named profiles.
347                    let maker = self.get_profile_maker(inherits).unwrap();
348                    profile.panic = maker.get_profile(Some(pkg_id), is_member, unit_for).panic;
349                }
350            }
351        }
352
353        // Incremental can be globally overridden.
354        if let Some(v) = self.incremental {
355            profile.incremental = v;
356        }
357        // Only enable incremental compilation for sources the user can
358        // modify (aka path sources). For things that change infrequently,
359        // non-incremental builds yield better performance in the compiler
360        // itself (aka crates.io / git dependencies)
361        //
362        // (see also https://github.com/rust-lang/cargo/issues/3972)
363        if !pkg_id.source_id().is_path() {
364            profile.incremental = false;
365        }
366        profile.name = profile_name;
367        profile
368    }
369
370    /// The profile for *running* a `build.rs` script is only used for setting
371    /// a few environment variables. To ensure proper de-duplication of the
372    /// running `Unit`, this uses a stripped-down profile (so that unrelated
373    /// profile flags don't cause `build.rs` to needlessly run multiple
374    /// times).
375    pub fn get_profile_run_custom_build(&self, for_unit_profile: &Profile) -> Profile {
376        let mut result = Profile::default();
377        result.name = for_unit_profile.name;
378        result.root = for_unit_profile.root;
379        result.debuginfo = for_unit_profile.debuginfo;
380        result.opt_level = for_unit_profile.opt_level;
381        result
382    }
383
384    /// This returns the base profile. This is currently used for the
385    /// `[Finished]` line. It is not entirely accurate, since it doesn't
386    /// select for the package that was actually built.
387    pub fn base_profile(&self) -> Profile {
388        let profile_name = if !self.named_profiles_enabled {
389            match self.requested_profile.as_str() {
390                "release" | "bench" => self.requested_profile,
391                _ => InternedString::new("dev"),
392            }
393        } else {
394            self.requested_profile
395        };
396
397        let maker = self.get_profile_maker(profile_name).unwrap();
398        maker.get_profile(None, true, UnitFor::new_normal())
399    }
400
401    /// Gets the directory name for a profile, like `debug` or `release`.
402    pub fn get_dir_name(&self) -> InternedString {
403        *self
404            .dir_names
405            .get(&self.requested_profile)
406            .unwrap_or(&self.requested_profile)
407    }
408
409    /// Used to check for overrides for non-existing packages.
410    pub fn validate_packages(
411        &self,
412        profiles: Option<&TomlProfiles>,
413        shell: &mut Shell,
414        resolve: &Resolve,
415    ) -> CargoResult<()> {
416        for (name, profile) in &self.by_name {
417            let found = validate_packages_unique(resolve, name, &profile.toml)?;
418            // We intentionally do not validate unmatched packages for config
419            // profiles, in case they are defined in a central location. This
420            // iterates over the manifest profiles only.
421            if let Some(profiles) = profiles {
422                if let Some(toml_profile) = profiles.get(name) {
423                    validate_packages_unmatched(shell, resolve, name, toml_profile, &found)?;
424                }
425            }
426        }
427        Ok(())
428    }
429
430    /// Returns the profile maker for the given profile name.
431    fn get_profile_maker(&self, name: InternedString) -> CargoResult<&ProfileMaker> {
432        self.by_name
433            .get(&name)
434            .ok_or_else(|| anyhow::format_err!("profile `{}` is not defined", name))
435    }
436}
437
438/// An object used for handling the profile hierarchy.
439///
440/// The precedence of profiles are (first one wins):
441/// - Profiles in `.cargo/config` files (using same order as below).
442/// - [profile.dev.package.name] -- a named package.
443/// - [profile.dev.package."*"] -- this cannot apply to workspace members.
444/// - [profile.dev.build-override] -- this can only apply to `build.rs` scripts
445///   and their dependencies.
446/// - [profile.dev]
447/// - Default (hard-coded) values.
448#[derive(Debug, Clone)]
449struct ProfileMaker {
450    /// The starting, hard-coded defaults for the profile.
451    default: Profile,
452    /// The TOML profile defined in `Cargo.toml` or config.
453    toml: Option<TomlProfile>,
454}
455
456impl ProfileMaker {
457    /// Creates a new `ProfileMaker`.
458    ///
459    /// Note that this does not process `inherits`, the caller is responsible for that.
460    fn new(default: Profile, toml: Option<TomlProfile>) -> ProfileMaker {
461        ProfileMaker { default, toml }
462    }
463
464    /// Generates a new `Profile`.
465    fn get_profile(
466        &self,
467        pkg_id: Option<PackageId>,
468        is_member: bool,
469        unit_for: UnitFor,
470    ) -> Profile {
471        let mut profile = self.default;
472        if let Some(toml) = &self.toml {
473            merge_profile(&mut profile, toml);
474            merge_toml_overrides(pkg_id, is_member, unit_for, &mut profile, toml);
475        }
476        profile
477    }
478}
479
480/// Merge package and build overrides from the given TOML profile into the given `Profile`.
481fn merge_toml_overrides(
482    pkg_id: Option<PackageId>,
483    is_member: bool,
484    unit_for: UnitFor,
485    profile: &mut Profile,
486    toml: &TomlProfile,
487) {
488    if unit_for.is_for_host() {
489        if let Some(ref build_override) = toml.build_override {
490            merge_profile(profile, build_override);
491        }
492    }
493    if let Some(overrides) = toml.package.as_ref() {
494        if !is_member {
495            if let Some(all) = overrides.get(&ProfilePackageSpec::All) {
496                merge_profile(profile, all);
497            }
498        }
499        if let Some(pkg_id) = pkg_id {
500            let mut matches = overrides
501                .iter()
502                .filter_map(|(key, spec_profile)| match *key {
503                    ProfilePackageSpec::All => None,
504                    ProfilePackageSpec::Spec(ref s) => {
505                        if s.matches(pkg_id) {
506                            Some(spec_profile)
507                        } else {
508                            None
509                        }
510                    }
511                });
512            if let Some(spec_profile) = matches.next() {
513                merge_profile(profile, spec_profile);
514                // `validate_packages` should ensure that there are
515                // no additional matches.
516                assert!(
517                    matches.next().is_none(),
518                    "package `{}` matched multiple package profile overrides",
519                    pkg_id
520                );
521            }
522        }
523    }
524}
525
526/// Merge the given TOML profile into the given `Profile`.
527///
528/// Does not merge overrides (see `merge_toml_overrides`).
529fn merge_profile(profile: &mut Profile, toml: &TomlProfile) {
530    if let Some(ref opt_level) = toml.opt_level {
531        profile.opt_level = InternedString::new(&opt_level.0);
532    }
533    match toml.lto {
534        Some(StringOrBool::Bool(b)) => profile.lto = Lto::Bool(b),
535        Some(StringOrBool::String(ref n)) => profile.lto = Lto::Named(InternedString::new(n)),
536        None => {}
537    }
538    if toml.codegen_units.is_some() {
539        profile.codegen_units = toml.codegen_units;
540    }
541    match toml.debug {
542        Some(U32OrBool::U32(debug)) => profile.debuginfo = Some(debug),
543        Some(U32OrBool::Bool(true)) => profile.debuginfo = Some(2),
544        Some(U32OrBool::Bool(false)) => profile.debuginfo = None,
545        None => {}
546    }
547    if let Some(debug_assertions) = toml.debug_assertions {
548        profile.debug_assertions = debug_assertions;
549    }
550    if let Some(rpath) = toml.rpath {
551        profile.rpath = rpath;
552    }
553    if let Some(panic) = &toml.panic {
554        profile.panic = match panic.as_str() {
555            "unwind" => PanicStrategy::Unwind,
556            "abort" => PanicStrategy::Abort,
557            // This should be validated in TomlProfile::validate
558            _ => panic!("Unexpected panic setting `{}`", panic),
559        };
560    }
561    if let Some(overflow_checks) = toml.overflow_checks {
562        profile.overflow_checks = overflow_checks;
563    }
564    if let Some(incremental) = toml.incremental {
565        profile.incremental = incremental;
566    }
567}
568
569/// The root profile (dev/release).
570///
571/// This is currently only used for the `PROFILE` env var for build scripts
572/// for backwards compatibility. We should probably deprecate `PROFILE` and
573/// encourage using things like `DEBUG` and `OPT_LEVEL` instead.
574#[derive(Clone, Copy, Eq, PartialOrd, Ord, PartialEq, Debug)]
575pub enum ProfileRoot {
576    Release,
577    Debug,
578}
579
580/// Profile settings used to determine which compiler flags to use for a
581/// target.
582#[derive(Clone, Copy, Eq, PartialOrd, Ord, serde::Serialize)]
583pub struct Profile {
584    pub name: InternedString,
585    pub opt_level: InternedString,
586    #[serde(skip)] // named profiles are unstable
587    pub root: ProfileRoot,
588    pub lto: Lto,
589    // `None` means use rustc default.
590    pub codegen_units: Option<u32>,
591    pub debuginfo: Option<u32>,
592    pub debug_assertions: bool,
593    pub overflow_checks: bool,
594    pub rpath: bool,
595    pub incremental: bool,
596    pub panic: PanicStrategy,
597}
598
599impl Default for Profile {
600    fn default() -> Profile {
601        Profile {
602            name: InternedString::new(""),
603            opt_level: InternedString::new("0"),
604            root: ProfileRoot::Debug,
605            lto: Lto::Bool(false),
606            codegen_units: None,
607            debuginfo: None,
608            debug_assertions: false,
609            overflow_checks: false,
610            rpath: false,
611            incremental: false,
612            panic: PanicStrategy::Unwind,
613        }
614    }
615}
616
617compact_debug! {
618    impl fmt::Debug for Profile {
619        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
620            let (default, default_name) = match self.name.as_str() {
621                "dev" => (Profile::default_dev(), "default_dev()"),
622                "release" => (Profile::default_release(), "default_release()"),
623                _ => (Profile::default(), "default()"),
624            };
625            [debug_the_fields(
626                name
627                opt_level
628                lto
629                root
630                codegen_units
631                debuginfo
632                debug_assertions
633                overflow_checks
634                rpath
635                incremental
636                panic
637            )]
638        }
639    }
640}
641
642impl fmt::Display for Profile {
643    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
644        write!(f, "Profile({})", self.name)
645    }
646}
647
648impl hash::Hash for Profile {
649    fn hash<H>(&self, state: &mut H)
650    where
651        H: hash::Hasher,
652    {
653        self.comparable().hash(state);
654    }
655}
656
657impl cmp::PartialEq for Profile {
658    fn eq(&self, other: &Self) -> bool {
659        self.comparable() == other.comparable()
660    }
661}
662
663impl Profile {
664    fn default_dev() -> Profile {
665        Profile {
666            name: InternedString::new("dev"),
667            root: ProfileRoot::Debug,
668            debuginfo: Some(2),
669            debug_assertions: true,
670            overflow_checks: true,
671            incremental: true,
672            ..Profile::default()
673        }
674    }
675
676    fn default_release() -> Profile {
677        Profile {
678            name: InternedString::new("release"),
679            root: ProfileRoot::Release,
680            opt_level: InternedString::new("3"),
681            ..Profile::default()
682        }
683    }
684
685    // NOTE: Remove the following three once `named_profiles` is default:
686
687    fn default_test() -> Profile {
688        Profile {
689            name: InternedString::new("test"),
690            ..Profile::default_dev()
691        }
692    }
693
694    fn default_bench() -> Profile {
695        Profile {
696            name: InternedString::new("bench"),
697            ..Profile::default_release()
698        }
699    }
700
701    fn default_doc() -> Profile {
702        Profile {
703            name: InternedString::new("doc"),
704            ..Profile::default_dev()
705        }
706    }
707
708    /// Compares all fields except `name`, which doesn't affect compilation.
709    /// This is necessary for `Unit` deduplication for things like "test" and
710    /// "dev" which are essentially the same.
711    fn comparable(
712        &self,
713    ) -> (
714        InternedString,
715        Lto,
716        Option<u32>,
717        Option<u32>,
718        bool,
719        bool,
720        bool,
721        bool,
722        PanicStrategy,
723    ) {
724        (
725            self.opt_level,
726            self.lto,
727            self.codegen_units,
728            self.debuginfo,
729            self.debug_assertions,
730            self.overflow_checks,
731            self.rpath,
732            self.incremental,
733            self.panic,
734        )
735    }
736}
737
738/// The link-time-optimization setting.
739#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)]
740pub enum Lto {
741    /// False = no LTO
742    /// True = "Fat" LTO
743    Bool(bool),
744    /// Named LTO settings like "thin".
745    Named(InternedString),
746}
747
748impl serde::ser::Serialize for Lto {
749    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
750    where
751        S: serde::ser::Serializer,
752    {
753        match self {
754            Lto::Bool(b) => b.to_string().serialize(s),
755            Lto::Named(n) => n.serialize(s),
756        }
757    }
758}
759
760/// The `panic` setting.
761#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, PartialOrd, Ord, serde::Serialize)]
762#[serde(rename_all = "lowercase")]
763pub enum PanicStrategy {
764    Unwind,
765    Abort,
766}
767
768impl fmt::Display for PanicStrategy {
769    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
770        match *self {
771            PanicStrategy::Unwind => "unwind",
772            PanicStrategy::Abort => "abort",
773        }
774        .fmt(f)
775    }
776}
777
778/// Flags used in creating `Unit`s to indicate the purpose for the target, and
779/// to ensure the target's dependencies have the correct settings.
780#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
781pub struct UnitFor {
782    /// A target for `build.rs` or any of its dependencies, or a proc-macro or
783    /// any of its dependencies. This enables `build-override` profiles for
784    /// these targets.
785    ///
786    /// An invariant is that if `host_features` is true, `host` must be true.
787    ///
788    /// Note that this is `true` for `RunCustomBuild` units, even though that
789    /// unit should *not* use build-override profiles. This is a bit of a
790    /// special case. When computing the `RunCustomBuild` unit, it manually
791    /// uses the `get_profile_run_custom_build` method to get the correct
792    /// profile information for the unit. `host` needs to be true so that all
793    /// of the dependencies of that `RunCustomBuild` unit have this flag be
794    /// sticky (and forced to `true` for all further dependencies) — which is
795    /// the whole point of `UnitFor`.
796    host: bool,
797    /// A target for a build dependency or proc-macro (or any of its
798    /// dependencies). This is used for computing features of build
799    /// dependencies and proc-macros independently of other dependency kinds.
800    ///
801    /// The subtle difference between this and `host` is that the build script
802    /// for a non-host package sets this to `false` because it wants the
803    /// features of the non-host package (whereas `host` is true because the
804    /// build script is being built for the host). `host_features` becomes
805    /// `true` for build-dependencies or proc-macros, or any of their
806    /// dependencies. For example, with this dependency tree:
807    ///
808    /// ```text
809    /// foo
810    /// ├── foo build.rs
811    /// │   └── shared_dep (BUILD dependency)
812    /// │       └── shared_dep build.rs
813    /// └── shared_dep (Normal dependency)
814    ///     └── shared_dep build.rs
815    /// ```
816    ///
817    /// In this example, `foo build.rs` is HOST=true, HOST_FEATURES=false.
818    /// This is so that `foo build.rs` gets the profile settings for build
819    /// scripts (HOST=true) and features of foo (HOST_FEATURES=false) because
820    /// build scripts need to know which features their package is being built
821    /// with.
822    ///
823    /// But in the case of `shared_dep`, when built as a build dependency,
824    /// both flags are true (it only wants the build-dependency features).
825    /// When `shared_dep` is built as a normal dependency, then `shared_dep
826    /// build.rs` is HOST=true, HOST_FEATURES=false for the same reasons that
827    /// foo's build script is set that way.
828    host_features: bool,
829    /// How Cargo processes the `panic` setting or profiles. This is done to
830    /// handle test/benches inheriting from dev/release, as well as forcing
831    /// `for_host` units to always unwind.
832    panic_setting: PanicSetting,
833}
834
835#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
836enum PanicSetting {
837    /// Used to force a unit to always be compiled with the `panic=unwind`
838    /// strategy, notably for build scripts, proc macros, etc.
839    AlwaysUnwind,
840
841    /// Indicates that this unit will read its `profile` setting and use
842    /// whatever is configured there.
843    ReadProfile,
844
845    /// This unit will ignore its `panic` setting in its profile and will
846    /// instead inherit it from the `dev` or `release` profile, as appropriate.
847    Inherit,
848}
849
850impl UnitFor {
851    /// A unit for a normal target/dependency (i.e., not custom build,
852    /// proc macro/plugin, or test/bench).
853    pub fn new_normal() -> UnitFor {
854        UnitFor {
855            host: false,
856            host_features: false,
857            panic_setting: PanicSetting::ReadProfile,
858        }
859    }
860
861    /// A unit for a custom build script or proc-macro or its dependencies.
862    ///
863    /// The `host_features` parameter is whether or not this is for a build
864    /// dependency or proc-macro (something that requires being built "on the
865    /// host"). Build scripts for non-host units should use `false` because
866    /// they want to use the features of the package they are running for.
867    pub fn new_host(host_features: bool) -> UnitFor {
868        UnitFor {
869            host: true,
870            host_features,
871            // Force build scripts to always use `panic=unwind` for now to
872            // maximally share dependencies with procedural macros.
873            panic_setting: PanicSetting::AlwaysUnwind,
874        }
875    }
876
877    /// A unit for a compiler plugin or their dependencies.
878    pub fn new_compiler() -> UnitFor {
879        UnitFor {
880            host: false,
881            // The feature resolver doesn't know which dependencies are
882            // plugins, so for now plugins don't split features. Since plugins
883            // are mostly deprecated, just leave this as false.
884            host_features: false,
885            // Force plugins to use `panic=abort` so panics in the compiler do
886            // not abort the process but instead end with a reasonable error
887            // message that involves catching the panic in the compiler.
888            panic_setting: PanicSetting::AlwaysUnwind,
889        }
890    }
891
892    /// A unit for a test/bench target or their dependencies.
893    ///
894    /// Note that `config` is taken here for unstable CLI features to detect
895    /// whether `panic=abort` is supported for tests. Historical versions of
896    /// rustc did not support this, but newer versions do with an unstable
897    /// compiler flag.
898    pub fn new_test(config: &Config) -> UnitFor {
899        UnitFor {
900            host: false,
901            host_features: false,
902            // We're testing out an unstable feature (`-Zpanic-abort-tests`)
903            // which inherits the panic setting from the dev/release profile
904            // (basically avoid recompiles) but historical defaults required
905            // that we always unwound.
906            panic_setting: if config.cli_unstable().panic_abort_tests {
907                PanicSetting::Inherit
908            } else {
909                PanicSetting::AlwaysUnwind
910            },
911        }
912    }
913
914    /// Returns a new copy based on `for_host` setting.
915    ///
916    /// When `for_host` is true, this clears `panic_abort_ok` in a sticky
917    /// fashion so that all its dependencies also have `panic_abort_ok=false`.
918    /// This'll help ensure that once we start compiling for the host platform
919    /// (build scripts, plugins, proc macros, etc) we'll share the same build
920    /// graph where everything is `panic=unwind`.
921    pub fn with_for_host(self, for_host: bool) -> UnitFor {
922        UnitFor {
923            host: self.host || for_host,
924            host_features: self.host_features,
925            panic_setting: if for_host {
926                PanicSetting::AlwaysUnwind
927            } else {
928                self.panic_setting
929            },
930        }
931    }
932
933    /// Returns a new copy updating it whether or not it should use features
934    /// for build dependencies and proc-macros.
935    ///
936    /// This is part of the machinery responsible for handling feature
937    /// decoupling for build dependencies in the new feature resolver.
938    pub fn with_host_features(mut self, host_features: bool) -> UnitFor {
939        if host_features {
940            assert!(self.host);
941        }
942        self.host_features = self.host_features || host_features;
943        self
944    }
945
946    /// Returns `true` if this unit is for a build script or any of its
947    /// dependencies, or a proc macro or any of its dependencies.
948    pub fn is_for_host(&self) -> bool {
949        self.host
950    }
951
952    pub fn is_for_host_features(&self) -> bool {
953        self.host_features
954    }
955
956    /// Returns how `panic` settings should be handled for this profile
957    fn panic_setting(&self) -> PanicSetting {
958        self.panic_setting
959    }
960
961    /// All possible values, used by `clean`.
962    pub fn all_values() -> &'static [UnitFor] {
963        static ALL: &[UnitFor] = &[
964            UnitFor {
965                host: false,
966                host_features: false,
967                panic_setting: PanicSetting::ReadProfile,
968            },
969            UnitFor {
970                host: true,
971                host_features: false,
972                panic_setting: PanicSetting::AlwaysUnwind,
973            },
974            UnitFor {
975                host: false,
976                host_features: false,
977                panic_setting: PanicSetting::AlwaysUnwind,
978            },
979            UnitFor {
980                host: false,
981                host_features: false,
982                panic_setting: PanicSetting::Inherit,
983            },
984            // host_features=true must always have host=true
985            // `Inherit` is not used in build dependencies.
986            UnitFor {
987                host: true,
988                host_features: true,
989                panic_setting: PanicSetting::ReadProfile,
990            },
991            UnitFor {
992                host: true,
993                host_features: true,
994                panic_setting: PanicSetting::AlwaysUnwind,
995            },
996        ];
997        ALL
998    }
999
1000    pub(crate) fn map_to_features_for(&self) -> FeaturesFor {
1001        if self.is_for_host_features() {
1002            FeaturesFor::HostDep
1003        } else {
1004            FeaturesFor::NormalOrDev
1005        }
1006    }
1007}
1008
1009/// Takes the manifest profiles, and overlays the config profiles on-top.
1010///
1011/// Returns a new copy of the profile map with all the mergers complete.
1012fn merge_config_profiles(
1013    profiles: Option<&TomlProfiles>,
1014    config: &Config,
1015    requested_profile: InternedString,
1016    features: &Features,
1017) -> CargoResult<BTreeMap<InternedString, TomlProfile>> {
1018    let mut profiles = match profiles {
1019        Some(profiles) => profiles.get_all().clone(),
1020        None => BTreeMap::new(),
1021    };
1022    // Set of profile names to check if defined in config only.
1023    let mut check_to_add = HashSet::new();
1024    check_to_add.insert(requested_profile);
1025    // Merge config onto manifest profiles.
1026    for (name, profile) in &mut profiles {
1027        if let Some(config_profile) = get_config_profile(name, config, features)? {
1028            profile.merge(&config_profile);
1029        }
1030        if let Some(inherits) = &profile.inherits {
1031            check_to_add.insert(*inherits);
1032        }
1033    }
1034    // Add the built-in profiles. This is important for things like `cargo
1035    // test` which implicitly use the "dev" profile for dependencies.
1036    for name in &["dev", "release", "test", "bench"] {
1037        check_to_add.insert(InternedString::new(name));
1038    }
1039    // Add config-only profiles.
1040    // Need to iterate repeatedly to get all the inherits values.
1041    let mut current = HashSet::new();
1042    while !check_to_add.is_empty() {
1043        std::mem::swap(&mut current, &mut check_to_add);
1044        for name in current.drain() {
1045            if !profiles.contains_key(&name) {
1046                if let Some(config_profile) = get_config_profile(&name, config, features)? {
1047                    if let Some(inherits) = &config_profile.inherits {
1048                        check_to_add.insert(*inherits);
1049                    }
1050                    profiles.insert(name, config_profile);
1051                }
1052            }
1053        }
1054    }
1055    Ok(profiles)
1056}
1057
1058/// Helper for fetching a profile from config.
1059fn get_config_profile(
1060    name: &str,
1061    config: &Config,
1062    features: &Features,
1063) -> CargoResult<Option<TomlProfile>> {
1064    let profile: Option<config::Value<TomlProfile>> = config.get(&format!("profile.{}", name))?;
1065    let profile = match profile {
1066        Some(profile) => profile,
1067        None => return Ok(None),
1068    };
1069    let mut warnings = Vec::new();
1070    profile
1071        .val
1072        .validate(name, features, &mut warnings)
1073        .chain_err(|| {
1074            anyhow::format_err!(
1075                "config profile `{}` is not valid (defined in `{}`)",
1076                name,
1077                profile.definition
1078            )
1079        })?;
1080    for warning in warnings {
1081        config.shell().warn(warning)?;
1082    }
1083    Ok(Some(profile.val))
1084}
1085
1086/// Validate that a package does not match multiple package override specs.
1087///
1088/// For example `[profile.dev.package.bar]` and `[profile.dev.package."bar:0.5.0"]`
1089/// would both match `bar:0.5.0` which would be ambiguous.
1090fn validate_packages_unique(
1091    resolve: &Resolve,
1092    name: &str,
1093    toml: &Option<TomlProfile>,
1094) -> CargoResult<HashSet<PackageIdSpec>> {
1095    let toml = match toml {
1096        Some(ref toml) => toml,
1097        None => return Ok(HashSet::new()),
1098    };
1099    let overrides = match toml.package.as_ref() {
1100        Some(overrides) => overrides,
1101        None => return Ok(HashSet::new()),
1102    };
1103    // Verify that a package doesn't match multiple spec overrides.
1104    let mut found = HashSet::new();
1105    for pkg_id in resolve.iter() {
1106        let matches: Vec<&PackageIdSpec> = overrides
1107            .keys()
1108            .filter_map(|key| match *key {
1109                ProfilePackageSpec::All => None,
1110                ProfilePackageSpec::Spec(ref spec) => {
1111                    if spec.matches(pkg_id) {
1112                        Some(spec)
1113                    } else {
1114                        None
1115                    }
1116                }
1117            })
1118            .collect();
1119        match matches.len() {
1120            0 => {}
1121            1 => {
1122                found.insert(matches[0].clone());
1123            }
1124            _ => {
1125                let specs = matches
1126                    .iter()
1127                    .map(|spec| spec.to_string())
1128                    .collect::<Vec<_>>()
1129                    .join(", ");
1130                bail!(
1131                    "multiple package overrides in profile `{}` match package `{}`\n\
1132                     found package specs: {}",
1133                    name,
1134                    pkg_id,
1135                    specs
1136                );
1137            }
1138        }
1139    }
1140    Ok(found)
1141}
1142
1143/// Check for any profile override specs that do not match any known packages.
1144///
1145/// This helps check for typos and mistakes.
1146fn validate_packages_unmatched(
1147    shell: &mut Shell,
1148    resolve: &Resolve,
1149    name: &str,
1150    toml: &TomlProfile,
1151    found: &HashSet<PackageIdSpec>,
1152) -> CargoResult<()> {
1153    let overrides = match toml.package.as_ref() {
1154        Some(overrides) => overrides,
1155        None => return Ok(()),
1156    };
1157
1158    // Verify every override matches at least one package.
1159    let missing_specs = overrides.keys().filter_map(|key| {
1160        if let ProfilePackageSpec::Spec(ref spec) = *key {
1161            if !found.contains(spec) {
1162                return Some(spec);
1163            }
1164        }
1165        None
1166    });
1167    for spec in missing_specs {
1168        // See if there is an exact name match.
1169        let name_matches: Vec<String> = resolve
1170            .iter()
1171            .filter_map(|pkg_id| {
1172                if pkg_id.name() == spec.name() {
1173                    Some(pkg_id.to_string())
1174                } else {
1175                    None
1176                }
1177            })
1178            .collect();
1179        if name_matches.is_empty() {
1180            let suggestion = closest_msg(&spec.name(), resolve.iter(), |p| p.name().as_str());
1181            shell.warn(format!(
1182                "profile package spec `{}` in profile `{}` did not match any packages{}",
1183                spec, name, suggestion
1184            ))?;
1185        } else {
1186            shell.warn(format!(
1187                "profile package spec `{}` in profile `{}` \
1188                 has a version or URL that does not match any of the packages: {}",
1189                spec,
1190                name,
1191                name_matches.join(", ")
1192            ))?;
1193        }
1194    }
1195    Ok(())
1196}