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