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