authorbors <bors@rust-lang.org> 2026-07-05 12:04:25 UTC
committerbors <bors@rust-lang.org> 2026-07-05 12:04:25 UTC
log7148b31956911d5d5496900bf564ca5212bf199b
treeba776dcb2b9a2b61dbc40d99b4d8d89dc8282499
parent4eb62535fc12dd1a63fd43a4173e224e79313c4d
parent7c1d8b8204b199060ee31b3c684dd64185a51467

Auto merge of #158169 - Kobzol:fix-cc-rs-flag-if-supported, r=bjorn3

Fix debuginfo compression in bootstrap Found through https://rust-lang.zulipchat.com/#narrow/channel/326414-t-infra.2Fbootstrap/topic/weird.20code.20in.20fill_target_compiler/with/604588655. This PR solves a few issues with debuginfo compression in bootstrap. The main issue was that debuginfo compression through the `-gz` flag wasn't enabled, by mistake. When `cc-rs` checks if a compiler flag is supported, it tries to read `out_dir` to generate the source file in. However, when this option is not set, then the check silently fails and the flag is considered to be unsupported. Since we didn't set `out_dir`, all such added flags were simply ignored. Normally, it reads `OUT_DIR`, which is fine if `cc-rs` is used within a build script. But if it is used elsewhere, then this is a pretty gnarly footgun in `cc-rs`, because the failure is [completely silent](https://github.com/rust-lang/cc-rs/blob/main/src/lib.rs#L1483). CC @madsmtm or @NobodyXu in case you have thoughts on that :) After that was fixed, I had to change the used compression flag from `-gz` to `--compress-debug-sections`, to support both BFD and LLD linkers. And to solve it properly, and allow `dist` CI jobs to configure debuginfo compression for all targets that they build, I added a new bootstrap option to configure debuginfo compression. I wanted the flag to be configurable both globally and per target. At the same time, the flag applies both to C/C++ and Rust. We currently don't have such config area in `bootstrap.toml`, and `[build]` didn't seem like the right place, so I shoved it into `[rust]` (while documenting that it also applies to C/C++). r? @bjorn3 try-job: dist-x86_64-msvc try-job: dist-x86_64-apple try-job: dist-x86_64-linux try-job: dist-various-1 try-job: dist-various-2

13 files changed, 134 insertions(+), 20 deletions(-)

bootstrap.example.toml+16
......@@ -697,6 +697,14 @@
697697# FIXME(#61117): Some tests fail when this option is enabled.
698698#rust.debuginfo-level-tests = 0
699699
700# Compress debuginfo of Rust and C/C++ code.
701# Currently, this only works on Linux.
702# Valid options:
703# - "off" or false: disable compression
704# - true: compress debuginfo with the default compression method (currently zlib)
705# - "zlib": compress debuginfo with zlib
706#rust.compress-debuginfo = "off"
707
700708# Should rustc and the standard library be built with split debuginfo? Default
701709# is platform dependent.
702710#
......@@ -1019,6 +1027,14 @@
10191027# and enabling it causes issues.
10201028#split-debuginfo = if linux || windows-gnu { off } else if windows-msvc { packed } else if apple { unpacked }
10211029
1030# Compress debuginfo for Rust and C/C++ code of this target.
1031# Currently, this only works on Linux.
1032# Valid options:
1033# - "off" or false: disable compression
1034# - true: compress debuginfo with the default compression method (currently zlib)
1035# - "zlib": compress debuginfo with zlib
1036#compress-debuginfo = "off"
1037
10221038# Path to the `llvm-config` binary of the installation of a custom LLVM to link
10231039# against. Note that if this is specified we don't compile LLVM at all for this
10241040# target.
src/bootstrap/defaults/bootstrap.dist.toml+2
......@@ -26,6 +26,8 @@ download-rustc = false
2626llvm-bitcode-linker = true
2727# Required to make builds reproducible.
2828remap-debuginfo = true
29# Compress debuginfo in generated artifacts to reduce their size
30compress-debuginfo = "zlib"
2931
3032[dist]
3133# Use better compression when preparing tarballs.
src/bootstrap/src/core/builder/cargo.rs+18-3
......@@ -5,8 +5,8 @@ use std::path::{Path, PathBuf};
55use super::{Builder, Kind};
66use crate::core::build_steps::test;
77use crate::core::build_steps::tool::SourceType;
8use crate::core::config::SplitDebuginfo;
98use crate::core::config::flags::Color;
9use crate::core::config::{CompressDebuginfo, SplitDebuginfo};
1010use crate::utils::build_stamp;
1111use crate::utils::helpers::{self, LldThreads, check_cfg_arg, linker_flags};
1212use crate::{
......@@ -348,8 +348,23 @@ impl Cargo {
348348 self.rustdocflags.arg(&arg);
349349 }
350350
351 if !builder.config.dry_run() && builder.cc[&target].args().iter().any(|arg| arg == "-gz") {
352 self.rustflags.arg("-Clink-arg=-gz");
351 match builder.config.compress_debuginfo(target) {
352 CompressDebuginfo::Zlib => {
353 // Do not enable Zlib compression on:
354 // - Windows, because MSVC/PDB doesn't support it
355 // - macOS, because its linker doesn't know the flag
356 if !self.target.is_windows() && !self.target.is_apple() {
357 // If we link through cc, we need the -Wl prefix.
358 // If we don't, then we must not add it, because the linker wouldn't
359 // understand it.
360 if helpers::use_host_linker(target) {
361 self.rustflags.arg("-Clink-arg=-Wl,--compress-debug-sections=zlib");
362 } else {
363 self.rustflags.arg("-Clink-arg=--compress-debug-sections=zlib");
364 }
365 }
366 }
367 CompressDebuginfo::Off => {}
353368 }
354369
355370 // Ignore linker warnings for now. These are complicated to fix and don't affect the build.
src/bootstrap/src/core/config/config.rs+12-2
......@@ -49,8 +49,8 @@ use crate::core::config::toml::target::{
4949 DefaultLinuxLinkerOverride, Target, TomlTarget, default_linux_linker_overrides,
5050};
5151use crate::core::config::{
52 CompilerBuiltins, DebuginfoLevel, DryRun, GccCiMode, LlvmLibunwind, Merge, ReplaceOpt,
53 RustcLto, SplitDebuginfo, StringOrBool, threads_from_config,
52 CompilerBuiltins, CompressDebuginfo, DebuginfoLevel, DryRun, GccCiMode, LlvmLibunwind, Merge,
53 ReplaceOpt, RustcLto, SplitDebuginfo, StringOrBool, threads_from_config,
5454};
5555use crate::core::download::{
5656 DownloadContext, download_beta_toolchain, is_download_ci_available, maybe_download_rustfmt,
......@@ -210,6 +210,7 @@ pub struct Config {
210210 pub rust_debuginfo_level_std: DebuginfoLevel,
211211 pub rust_debuginfo_level_tools: DebuginfoLevel,
212212 pub rust_debuginfo_level_tests: DebuginfoLevel,
213 pub rust_compress_debuginfo: CompressDebuginfo,
213214 pub rust_rpath: bool,
214215 pub rust_strip: bool,
215216 pub rust_frame_pointers: bool,
......@@ -550,6 +551,7 @@ impl Config {
550551 debuginfo_level_std: rust_debuginfo_level_std,
551552 debuginfo_level_tools: rust_debuginfo_level_tools,
552553 debuginfo_level_tests: rust_debuginfo_level_tests,
554 compress_debuginfo: rust_compress_debuginfo,
553555 backtrace: rust_backtrace,
554556 incremental: rust_incremental,
555557 randomize_layout: rust_randomize_layout,
......@@ -1471,6 +1473,7 @@ NOTE: Please add `--stage 2` to your command line, or if you're sure you want to
14711473 .unwrap_or(vec![CodegenBackendKind::Llvm]),
14721474 rust_codegen_units: rust_codegen_units.map(threads_from_config),
14731475 rust_codegen_units_std: rust_codegen_units_std.map(threads_from_config),
1476 rust_compress_debuginfo: rust_compress_debuginfo.unwrap_or_default(),
14741477 rust_debug_logging: rust_debug_logging
14751478 .or(rust_rustc_debug_assertions)
14761479 .unwrap_or(rust_debug == Some(true)),
......@@ -1927,6 +1930,13 @@ NOTE: Please add `--stage 2` to your command line, or if you're sure you want to
19271930 .unwrap_or_else(|| SplitDebuginfo::default_for_platform(target))
19281931 }
19291932
1933 pub fn compress_debuginfo(&self, target: TargetSelection) -> CompressDebuginfo {
1934 self.target_config
1935 .get(&target)
1936 .and_then(|t| t.compress_debuginfo)
1937 .unwrap_or(self.rust_compress_debuginfo)
1938 }
1939
19301940 /// Checks if the given target is the same as the host target.
19311941 pub fn is_host_target(&self, target: TargetSelection) -> bool {
19321942 self.host_target == target
src/bootstrap/src/core/config/mod.rs+48
......@@ -32,6 +32,7 @@ use std::path::PathBuf;
3232
3333use build_helper::exit;
3434pub use config::*;
35use serde::de::Unexpected;
3536use serde::{Deserialize, Deserializer};
3637use serde_derive::Deserialize;
3738pub use target_selection::TargetSelection;
......@@ -378,6 +379,53 @@ impl std::str::FromStr for SplitDebuginfo {
378379 }
379380}
380381
382#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
383pub enum CompressDebuginfo {
384 Zlib,
385 #[default]
386 Off,
387}
388
389impl CompressDebuginfo {
390 fn default_on() -> Self {
391 Self::Zlib
392 }
393}
394
395impl std::str::FromStr for CompressDebuginfo {
396 type Err = ();
397
398 fn from_str(s: &str) -> Result<Self, Self::Err> {
399 match s {
400 "zlib" => Ok(CompressDebuginfo::Zlib),
401 "off" => Ok(CompressDebuginfo::Off),
402 _ => Err(()),
403 }
404 }
405}
406
407impl<'de> Deserialize<'de> for CompressDebuginfo {
408 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
409 where
410 D: Deserializer<'de>,
411 {
412 use serde::de::Error;
413
414 Ok(match Deserialize::deserialize(deserializer)? {
415 StringOrBool::Bool(value) => {
416 if value {
417 CompressDebuginfo::default_on()
418 } else {
419 CompressDebuginfo::Off
420 }
421 }
422 StringOrBool::String(value) => CompressDebuginfo::from_str(&value).map_err(|_| {
423 D::Error::invalid_value(Unexpected::Str(&value), &"`zlib` or `off`")
424 })?,
425 })
426 }
427}
428
381429/// Describes how to handle conflicts in merging two `TomlConfig`
382430#[derive(Copy, Clone, Debug)]
383431pub enum ReplaceOpt {
src/bootstrap/src/core/config/target_selection.rs+4
......@@ -86,6 +86,10 @@ impl TargetSelection {
8686 self.contains("windows")
8787 }
8888
89 pub fn is_apple(&self) -> bool {
90 self.contains("apple")
91 }
92
8993 pub fn is_windows_gnu(&self) -> bool {
9094 self.ends_with("windows-gnu")
9195 }
src/bootstrap/src/core/config/toml/rust.rs+4-1
......@@ -5,7 +5,7 @@ use build_helper::ci::CiEnv;
55use serde::{Deserialize, Deserializer};
66
77use crate::core::config::toml::TomlConfig;
8use crate::core::config::{DebuginfoLevel, Merge, ReplaceOpt, StringOrBool};
8use crate::core::config::{CompressDebuginfo, DebuginfoLevel, Merge, ReplaceOpt, StringOrBool};
99use crate::{BTreeSet, CodegenBackendKind, HashSet, PathBuf, TargetSelection, define_config, exit};
1010
1111define_config! {
......@@ -28,6 +28,7 @@ define_config! {
2828 debuginfo_level_std: Option<DebuginfoLevel> = "debuginfo-level-std",
2929 debuginfo_level_tools: Option<DebuginfoLevel> = "debuginfo-level-tools",
3030 debuginfo_level_tests: Option<DebuginfoLevel> = "debuginfo-level-tests",
31 compress_debuginfo: Option<CompressDebuginfo> = "compress-debuginfo",
3132 backtrace: Option<bool> = "backtrace",
3233 incremental: Option<bool> = "incremental",
3334 default_linker: Option<String> = "default-linker",
......@@ -322,6 +323,7 @@ pub fn check_incompatible_options_for_ci_rustc(
322323 randomize_layout,
323324 debug_logging,
324325 debuginfo_level_rustc,
326 compress_debuginfo,
325327 llvm_tools,
326328 llvm_bitcode_linker,
327329 stack_protector,
......@@ -389,6 +391,7 @@ pub fn check_incompatible_options_for_ci_rustc(
389391
390392 err!(current_rust_config.optimize, optimize, "rust");
391393 err!(current_rust_config.randomize_layout, randomize_layout, "rust");
394 err!(current_rust_config.compress_debuginfo, compress_debuginfo, "rust");
392395 err!(current_rust_config.debug_logging, debug_logging, "rust");
393396 err!(current_rust_config.debuginfo_level_rustc, debuginfo_level_rustc, "rust");
394397 err!(current_rust_config.rpath, rpath, "rust");
src/bootstrap/src/core/config/toml/target.rs+3-1
......@@ -15,7 +15,8 @@ use serde::de::Error;
1515use serde::{Deserialize, Deserializer};
1616
1717use crate::core::config::{
18 CompilerBuiltins, LlvmLibunwind, Merge, ReplaceOpt, SplitDebuginfo, StringOrBool,
18 CompilerBuiltins, CompressDebuginfo, LlvmLibunwind, Merge, ReplaceOpt, SplitDebuginfo,
19 StringOrBool,
1920};
2021use crate::{CodegenBackendKind, HashSet, PathBuf, define_config, exit};
2122
......@@ -68,6 +69,7 @@ pub struct Target {
6869 pub default_linker_linux_override: DefaultLinuxLinkerOverride,
6970 pub linker: Option<PathBuf>,
7071 pub split_debuginfo: Option<SplitDebuginfo>,
72 pub compress_debuginfo: Option<CompressDebuginfo>,
7173 pub sanitizers: Option<bool>,
7274 pub profiler: Option<StringOrBool>,
7375 pub rpath: Option<bool>,
src/bootstrap/src/utils/cc_detect.rs+17-11
......@@ -25,7 +25,7 @@ use std::collections::HashSet;
2525use std::iter;
2626use std::path::{Path, PathBuf};
2727
28use crate::core::config::TargetSelection;
28use crate::core::config::{CompressDebuginfo, TargetSelection};
2929use crate::utils::exec::{BootstrapCommand, command};
3030use crate::{Build, CLang, GitRepo};
3131
......@@ -36,10 +36,18 @@ fn new_cc_build(build: &Build, target: TargetSelection) -> cc::Build {
3636 .opt_level(2)
3737 .warnings(false)
3838 .debug(false)
39 // Compress debuginfo
40 .flag_if_supported("-gz")
39 // We have to configure out_dir, otherwise flag_if_supported will not work
40 .out_dir(build.tempdir().join("cc-rs-out-dir"))
4141 .target(&target.triple)
4242 .host(&build.host_target.triple);
43
44 match build.config.compress_debuginfo(target) {
45 CompressDebuginfo::Zlib => {
46 cfg.flag_if_supported("-gz");
47 }
48 CompressDebuginfo::Off => {}
49 }
50
4351 match build.crt_static(target) {
4452 Some(a) => {
4553 cfg.static_crt(a);
......@@ -100,17 +108,15 @@ pub fn fill_target_compiler(build: &mut Build, target: TargetSelection) {
100108 let config = build.config.target_config.get(&target);
101109 if let Some(cc) = config
102110 .and_then(|c| c.cc.clone())
103 .or_else(|| default_compiler(&mut cfg, Language::C, target, build))
111 .or_else(|| default_compiler(&cfg, Language::C, target, build))
104112 {
105113 cfg.compiler(cc);
106114 }
107115
108116 let compiler = cfg.get_compiler();
109 let ar = if let ar @ Some(..) = config.and_then(|c| c.ar.clone()) {
110 ar
111 } else {
112 cfg.try_get_archiver().map(|c| PathBuf::from(c.get_program())).ok()
113 };
117 let ar = config
118 .and_then(|c| c.ar.clone())
119 .or_else(|| cfg.try_get_archiver().map(|c| PathBuf::from(c.get_program())).ok());
114120
115121 build.cc.insert(target, compiler.clone());
116122 let mut cflags = build.cc_handled_clags(target, CLang::C);
......@@ -122,7 +128,7 @@ pub fn fill_target_compiler(build: &mut Build, target: TargetSelection) {
122128 cfg.cpp(true);
123129 let cxx_configured = if let Some(cxx) = config
124130 .and_then(|c| c.cxx.clone())
125 .or_else(|| default_compiler(&mut cfg, Language::CPlusPlus, target, build))
131 .or_else(|| default_compiler(&cfg, Language::CPlusPlus, target, build))
126132 {
127133 cfg.compiler(cxx);
128134 true
......@@ -158,7 +164,7 @@ pub fn fill_target_compiler(build: &mut Build, target: TargetSelection) {
158164/// Determines the default compiler for a given target and language when not explicitly
159165/// configured in `bootstrap.toml`.
160166fn default_compiler(
161 cfg: &mut cc::Build,
167 cfg: &cc::Build,
162168 compiler: Language,
163169 target: TargetSelection,
164170 build: &Build,
src/bootstrap/src/utils/cc_detect/tests.rs+2-2
......@@ -86,7 +86,7 @@ fn test_default_compiler_wasi() {
8686 build.wasi_sdk_path = Some(wasi_sdk.clone());
8787
8888 let mut cfg = cc::Build::new();
89 if let Some(result) = default_compiler(&mut cfg, Language::C, target.clone(), &build) {
89 if let Some(result) = default_compiler(&cfg, Language::C, target.clone(), &build) {
9090 let expected = {
9191 let compiler = format!("{}-clang", target.triple);
9292 wasi_sdk.join("bin").join(compiler)
......@@ -105,7 +105,7 @@ fn test_default_compiler_fallback() {
105105 let build = Build::new(config);
106106 let target = TargetSelection::from_user("x86_64-unknown-linux-gnu");
107107 let mut cfg = cc::Build::new();
108 let result = default_compiler(&mut cfg, Language::C, target, &build);
108 let result = default_compiler(&cfg, Language::C, target, &build);
109109 assert!(result.is_none(), "default_compiler should return None for generic targets");
110110}
111111
src/bootstrap/src/utils/change_tracker.rs+5
......@@ -631,4 +631,9 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[
631631 severity: ChangeSeverity::Info,
632632 summary: "New `--verbose-run-make-subprocess-output` flag for `x.py test` (defaults to true). Set `--verbose-run-make-subprocess-output=false` to suppress verbose subprocess output for passing run-make tests when using `--no-capture`.",
633633 },
634 ChangeInfo {
635 change_id: 158169,
636 severity: ChangeSeverity::Info,
637 summary: "New option `rust.compress-debuginfo` allows configuring whether Rust and C/C++ debuginfo should be compressed.",
638 },
634639];
src/ci/docker/host-x86_64/x86_64-gnu-distcheck/Dockerfile+1
......@@ -28,6 +28,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
2828 xz-utils \
2929 libssl-dev \
3030 pkg-config \
31 zlib1g-dev \
3132 && rm -rf /var/lib/apt/lists/*
3233
3334COPY scripts/sccache.sh /scripts/
src/ci/run.sh+2
......@@ -137,6 +137,8 @@ if [ "$DEPLOY$DEPLOY_ALT" = "1" ]; then
137137 fi
138138else
139139 RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --set rust.remap-debuginfo=false"
140 # No need to compress debuginfo for tests, and we do not want to depend on zlib
141 RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --set rust.compress-debuginfo=off"
140142
141143 # We almost always want debug assertions enabled, but sometimes this takes too
142144 # long for too little benefit, so we just turn them off.