Skip to content

Commit ed4568e

Browse files
committed
Add split-debuginfo profile option
This commit adds a new `split-debuginfo` option to Cargo compilation profiles which gets forwarded to the `-Csplit-debuginfo` codegen option in rustc. This commit also sets the default, only on macOS, to be `-Csplit-debuginfo=unpacked`. The purpose of this change is to leverage rust-lang/rust#79570 to avoid running `dsymutil` on incremental builds while also preserving a pleasant debugging experience by default. This should lead to much faster incremental build times on macOS since `dsymutil` isn't exactly the speediest tool in the world. This is technically a breaking change in Cargo because we're no longer by-default producing the `*.dSYM` folders on macOS. If those are still desired, however, authors can always run `dsymutil` themselves or otherwise configure `split-debuginfo = 'packed'` in their manifest/profile configuration.
1 parent 8a3361c commit ed4568e

File tree

15 files changed

+212
-119
lines changed

15 files changed

+212
-119
lines changed

crates/cargo-test-support/src/lib.rs

+10
Original file line numberDiff line numberDiff line change
@@ -722,6 +722,16 @@ impl Execs {
722722
self
723723
}
724724

725+
pub fn enable_mac_dsym(&mut self) -> &mut Self {
726+
if cfg!(target_os = "macos") {
727+
self.env("CARGO_PROFILE_DEV_SPLIT_DEBUGINFO", "packed")
728+
.env("CARGO_PROFILE_TEST_SPLIT_DEBUGINFO", "packed")
729+
.env("CARGO_PROFILE_RELEASE_SPLIT_DEBUGINFO", "packed")
730+
.env("CARGO_PROFILE_BENCH_SPLIT_DEBUGINFO", "packed");
731+
}
732+
self
733+
}
734+
725735
pub fn run(&mut self) {
726736
self.ran = true;
727737
let p = (&self.process_builder).clone().unwrap();

src/cargo/core/compiler/build_context/target_info.rs

+6
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ pub struct TargetInfo {
4040
pub rustflags: Vec<String>,
4141
/// Extra flags to pass to `rustdoc`, see `env_args`.
4242
pub rustdocflags: Vec<String>,
43+
/// Whether or not rustc supports the `-Csplit-debuginfo` flag.
44+
pub supports_split_debuginfo: bool,
4345
}
4446

4547
/// Kind of each file generated by a Unit, part of `FileType`.
@@ -157,6 +159,9 @@ impl TargetInfo {
157159
for crate_type in KNOWN_CRATE_TYPES.iter() {
158160
process.arg("--crate-type").arg(crate_type.as_str());
159161
}
162+
let supports_split_debuginfo = rustc
163+
.cached_output(process.clone().arg("-Csplit-debuginfo=packed"))
164+
.is_ok();
160165

161166
process.arg("--print=sysroot");
162167
process.arg("--print=cfg");
@@ -231,6 +236,7 @@ impl TargetInfo {
231236
"RUSTDOCFLAGS",
232237
)?,
233238
cfg,
239+
supports_split_debuginfo,
234240
})
235241
}
236242

src/cargo/core/compiler/mod.rs

+10
Original file line numberDiff line numberDiff line change
@@ -773,6 +773,7 @@ fn build_base_args(
773773
codegen_units,
774774
debuginfo,
775775
debug_assertions,
776+
split_debuginfo,
776777
overflow_checks,
777778
rpath,
778779
ref panic,
@@ -825,6 +826,15 @@ fn build_base_args(
825826

826827
cmd.args(&lto_args(cx, unit));
827828

829+
// This is generally just an optimization on build time so if we don't pass
830+
// it then it's ok. As of the time of this writing it's a very new flag, so
831+
// we need to dynamically check if it's available.
832+
if cx.bcx.target_data.info(unit.kind).supports_split_debuginfo {
833+
if let Some(split) = split_debuginfo {
834+
cmd.arg("-C").arg(format!("split-debuginfo={}", split));
835+
}
836+
}
837+
828838
if let Some(n) = codegen_units {
829839
cmd.arg("-C").arg(&format!("codegen-units={}", n));
830840
}

src/cargo/core/compiler/standard_lib.rs

+8-7
Original file line numberDiff line numberDiff line change
@@ -162,16 +162,17 @@ pub fn generate_std_roots(
162162
// in time is minimal, and the difference in caching is
163163
// significant.
164164
let mode = CompileMode::Build;
165-
let profile = profiles.get_profile(
166-
pkg.package_id(),
167-
/*is_member*/ false,
168-
/*is_local*/ false,
169-
unit_for,
170-
mode,
171-
);
172165
let features = std_features.activated_features(pkg.package_id(), FeaturesFor::NormalOrDev);
173166

174167
for kind in kinds {
168+
let profile = profiles.get_profile(
169+
pkg.package_id(),
170+
/*is_member*/ false,
171+
/*is_local*/ false,
172+
unit_for,
173+
mode,
174+
*kind,
175+
);
175176
let list = ret.entry(*kind).or_insert_with(Vec::new);
176177
list.push(interner.intern(
177178
pkg,

src/cargo/core/compiler/unit_dependencies.rs

+1
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,7 @@ fn new_unit_dep(
593593
is_local,
594594
unit_for,
595595
mode,
596+
kind,
596597
);
597598
new_unit_dep_with_profile(state, parent, pkg, target, unit_for, kind, mode, profile)
598599
}

src/cargo/core/profiles.rs

+48-38
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
use crate::core::compiler::{CompileMode, Unit};
1+
use crate::core::compiler::{CompileKind, CompileMode, Unit};
22
use crate::core::resolver::features::FeaturesFor;
3-
use crate::core::{Feature, Features, PackageId, PackageIdSpec, Resolve, Shell, Target};
3+
use crate::core::{Feature, PackageId, PackageIdSpec, Resolve, Shell, Target, Workspace};
44
use crate::util::errors::CargoResultExt;
55
use crate::util::interning::InternedString;
66
use crate::util::toml::{ProfilePackageSpec, StringOrBool, TomlProfile, TomlProfiles, U32OrBool};
77
use crate::util::{closest_msg, config, CargoResult, Config};
88
use anyhow::bail;
99
use std::collections::{BTreeMap, HashMap, HashSet};
10+
use std::hash::Hash;
1011
use std::{cmp, env, fmt, hash};
1112

1213
/// Collection of all profiles.
@@ -24,28 +25,28 @@ pub struct Profiles {
2425
named_profiles_enabled: bool,
2526
/// The profile the user requested to use.
2627
requested_profile: InternedString,
28+
/// The host target for rustc being used by this `Profiles`.
29+
rustc_host: InternedString,
2730
}
2831

2932
impl Profiles {
30-
pub fn new(
31-
profiles: Option<&TomlProfiles>,
32-
config: &Config,
33-
requested_profile: InternedString,
34-
features: &Features,
35-
) -> CargoResult<Profiles> {
33+
pub fn new(ws: &Workspace<'_>, requested_profile: InternedString) -> CargoResult<Profiles> {
34+
let config = ws.config();
3635
let incremental = match env::var_os("CARGO_INCREMENTAL") {
3736
Some(v) => Some(v == "1"),
3837
None => config.build_config()?.incremental,
3938
};
40-
let mut profiles = merge_config_profiles(profiles, config, requested_profile, features)?;
39+
let mut profiles = merge_config_profiles(ws, requested_profile)?;
40+
let rustc_host = ws.config().load_global_rustc(Some(ws))?.host;
4141

42-
if !features.is_enabled(Feature::named_profiles()) {
42+
if !ws.unstable_features().is_enabled(Feature::named_profiles()) {
4343
let mut profile_makers = Profiles {
4444
incremental,
4545
named_profiles_enabled: false,
4646
dir_names: Self::predefined_dir_names(),
4747
by_name: HashMap::new(),
4848
requested_profile,
49+
rustc_host,
4950
};
5051

5152
profile_makers.by_name.insert(
@@ -98,6 +99,7 @@ impl Profiles {
9899
dir_names: Self::predefined_dir_names(),
99100
by_name: HashMap::new(),
100101
requested_profile,
102+
rustc_host,
101103
};
102104

103105
Self::add_root_profiles(&mut profile_makers, &profiles);
@@ -289,6 +291,7 @@ impl Profiles {
289291
is_local: bool,
290292
unit_for: UnitFor,
291293
mode: CompileMode,
294+
kind: CompileKind,
292295
) -> Profile {
293296
let (profile_name, inherits) = if !self.named_profiles_enabled {
294297
// With the feature disabled, we degrade `--profile` back to the
@@ -344,10 +347,28 @@ impl Profiles {
344347
}
345348
}
346349

350+
// Default macOS debug information to being stored in the "packed"
351+
// split-debuginfo format. At the time of this writing that's the only
352+
// platform which has a stable `-Csplit-debuginfo` option for rustc,
353+
// and it's typically much faster than running `dsymutil` on all builds
354+
// in incremental cases.
355+
if let Some(debug) = profile.debuginfo {
356+
if profile.split_debuginfo.is_none() && debug > 0 {
357+
let target = match &kind {
358+
CompileKind::Host => self.rustc_host.as_str(),
359+
CompileKind::Target(target) => target.short_name(),
360+
};
361+
if target.contains("-apple-") {
362+
profile.split_debuginfo = Some(InternedString::new("unpacked"));
363+
}
364+
}
365+
}
366+
347367
// Incremental can be globally overridden.
348368
if let Some(v) = self.incremental {
349369
profile.incremental = v;
350370
}
371+
351372
// Only enable incremental compilation for sources the user can
352373
// modify (aka path sources). For things that change infrequently,
353374
// non-incremental builds yield better performance in the compiler
@@ -567,6 +588,9 @@ fn merge_profile(profile: &mut Profile, toml: &TomlProfile) {
567588
if let Some(debug_assertions) = toml.debug_assertions {
568589
profile.debug_assertions = debug_assertions;
569590
}
591+
if let Some(split_debuginfo) = &toml.split_debuginfo {
592+
profile.split_debuginfo = Some(InternedString::new(split_debuginfo));
593+
}
570594
if let Some(rpath) = toml.rpath {
571595
profile.rpath = rpath;
572596
}
@@ -612,6 +636,7 @@ pub struct Profile {
612636
// `None` means use rustc default.
613637
pub codegen_units: Option<u32>,
614638
pub debuginfo: Option<u32>,
639+
pub split_debuginfo: Option<InternedString>,
615640
pub debug_assertions: bool,
616641
pub overflow_checks: bool,
617642
pub rpath: bool,
@@ -630,6 +655,7 @@ impl Default for Profile {
630655
codegen_units: None,
631656
debuginfo: None,
632657
debug_assertions: false,
658+
split_debuginfo: None,
633659
overflow_checks: false,
634660
rpath: false,
635661
incremental: false,
@@ -654,6 +680,7 @@ compact_debug! {
654680
root
655681
codegen_units
656682
debuginfo
683+
split_debuginfo
657684
debug_assertions
658685
overflow_checks
659686
rpath
@@ -734,25 +761,13 @@ impl Profile {
734761
/// Compares all fields except `name`, which doesn't affect compilation.
735762
/// This is necessary for `Unit` deduplication for things like "test" and
736763
/// "dev" which are essentially the same.
737-
fn comparable(
738-
&self,
739-
) -> (
740-
InternedString,
741-
Lto,
742-
Option<u32>,
743-
Option<u32>,
744-
bool,
745-
bool,
746-
bool,
747-
bool,
748-
PanicStrategy,
749-
Strip,
750-
) {
764+
fn comparable(&self) -> impl Hash + Eq {
751765
(
752766
self.opt_level,
753767
self.lto,
754768
self.codegen_units,
755769
self.debuginfo,
770+
self.split_debuginfo,
756771
self.debug_assertions,
757772
self.overflow_checks,
758773
self.rpath,
@@ -1073,12 +1088,10 @@ impl UnitFor {
10731088
///
10741089
/// Returns a new copy of the profile map with all the mergers complete.
10751090
fn merge_config_profiles(
1076-
profiles: Option<&TomlProfiles>,
1077-
config: &Config,
1091+
ws: &Workspace<'_>,
10781092
requested_profile: InternedString,
1079-
features: &Features,
10801093
) -> CargoResult<BTreeMap<InternedString, TomlProfile>> {
1081-
let mut profiles = match profiles {
1094+
let mut profiles = match ws.profiles() {
10821095
Some(profiles) => profiles.get_all().clone(),
10831096
None => BTreeMap::new(),
10841097
};
@@ -1087,7 +1100,7 @@ fn merge_config_profiles(
10871100
check_to_add.insert(requested_profile);
10881101
// Merge config onto manifest profiles.
10891102
for (name, profile) in &mut profiles {
1090-
if let Some(config_profile) = get_config_profile(name, config, features)? {
1103+
if let Some(config_profile) = get_config_profile(ws, name)? {
10911104
profile.merge(&config_profile);
10921105
}
10931106
if let Some(inherits) = &profile.inherits {
@@ -1106,7 +1119,7 @@ fn merge_config_profiles(
11061119
std::mem::swap(&mut current, &mut check_to_add);
11071120
for name in current.drain() {
11081121
if !profiles.contains_key(&name) {
1109-
if let Some(config_profile) = get_config_profile(&name, config, features)? {
1122+
if let Some(config_profile) = get_config_profile(ws, &name)? {
11101123
if let Some(inherits) = &config_profile.inherits {
11111124
check_to_add.insert(*inherits);
11121125
}
@@ -1119,20 +1132,17 @@ fn merge_config_profiles(
11191132
}
11201133

11211134
/// Helper for fetching a profile from config.
1122-
fn get_config_profile(
1123-
name: &str,
1124-
config: &Config,
1125-
features: &Features,
1126-
) -> CargoResult<Option<TomlProfile>> {
1127-
let profile: Option<config::Value<TomlProfile>> = config.get(&format!("profile.{}", name))?;
1135+
fn get_config_profile(ws: &Workspace<'_>, name: &str) -> CargoResult<Option<TomlProfile>> {
1136+
let profile: Option<config::Value<TomlProfile>> =
1137+
ws.config().get(&format!("profile.{}", name))?;
11281138
let profile = match profile {
11291139
Some(profile) => profile,
11301140
None => return Ok(None),
11311141
};
11321142
let mut warnings = Vec::new();
11331143
profile
11341144
.val
1135-
.validate(name, features, &mut warnings)
1145+
.validate(name, ws.unstable_features(), &mut warnings)
11361146
.chain_err(|| {
11371147
anyhow::format_err!(
11381148
"config profile `{}` is not valid (defined in `{}`)",
@@ -1141,7 +1151,7 @@ fn get_config_profile(
11411151
)
11421152
})?;
11431153
for warning in warnings {
1144-
config.shell().warn(warning)?;
1154+
ws.config().shell().warn(warning)?;
11451155
}
11461156
Ok(Some(profile.val))
11471157
}

src/cargo/ops/cargo_clean.rs

+7-7
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,7 @@ pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> {
3535
return rm_rf(&target_dir.into_path_unlocked(), config);
3636
}
3737

38-
let profiles = Profiles::new(
39-
ws.profiles(),
40-
config,
41-
opts.requested_profile,
42-
ws.unstable_features(),
43-
)?;
38+
let profiles = Profiles::new(ws, opts.requested_profile)?;
4439

4540
if opts.profile_specified {
4641
// After parsing profiles we know the dir-name of the profile, if a profile
@@ -181,9 +176,14 @@ pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> {
181176
// Remove dep-info file generated by rustc. It is not tracked in
182177
// file_types. It does not have a prefix.
183178
let hashed_dep_info = dir.join(format!("{}-*.d", crate_name));
184-
let unhashed_dep_info = dir.join(format!("{}.d", crate_name));
185179
rm_rf_glob(&hashed_dep_info, config)?;
180+
let unhashed_dep_info = dir.join(format!("{}.d", crate_name));
186181
rm_rf(&unhashed_dep_info, config)?;
182+
// Remove split-debuginfo files generated by rustc.
183+
let split_debuginfo_obj = dir.join(format!("{}.*.o", crate_name));
184+
rm_rf_glob(&split_debuginfo_obj, config)?;
185+
let split_debuginfo_dwo = dir.join(format!("{}.*.dwo", crate_name));
186+
rm_rf_glob(&split_debuginfo_dwo, config)?;
187187

188188
// Remove the uplifted copy.
189189
if let Some(uplift_dir) = uplift_dir {

src/cargo/ops/cargo_compile.rs

+9-13
Original file line numberDiff line numberDiff line change
@@ -429,12 +429,7 @@ pub fn create_bcx<'a, 'cfg>(
429429
);
430430
}
431431

432-
let profiles = Profiles::new(
433-
ws.profiles(),
434-
config,
435-
build_config.requested_profile,
436-
ws.unstable_features(),
437-
)?;
432+
let profiles = Profiles::new(ws, build_config.requested_profile)?;
438433
profiles.validate_packages(
439434
ws.profiles(),
440435
&mut config.shell(),
@@ -887,19 +882,20 @@ fn generate_targets(
887882
};
888883

889884
let is_local = pkg.package_id().source_id().is_path();
890-
let profile = profiles.get_profile(
891-
pkg.package_id(),
892-
ws.is_member(pkg),
893-
is_local,
894-
unit_for,
895-
target_mode,
896-
);
897885

898886
// No need to worry about build-dependencies, roots are never build dependencies.
899887
let features_for = FeaturesFor::from_for_host(target.proc_macro());
900888
let features = resolved_features.activated_features(pkg.package_id(), features_for);
901889

902890
for kind in requested_kinds {
891+
let profile = profiles.get_profile(
892+
pkg.package_id(),
893+
ws.is_member(pkg),
894+
is_local,
895+
unit_for,
896+
target_mode,
897+
kind.for_target(target),
898+
);
903899
let unit = interner.intern(
904900
pkg,
905901
target,

0 commit comments

Comments
 (0)