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