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