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