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