From 8bc9892ef152ad84828a9eddc15c170e6b92e5a2 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 28 Aug 2026 07:10:31 +0000 Subject: [PATCH 1/3] fix: probe flag support without OUT_DIR via tempfile Callers outside Cargo build scripts (e.g. rustc bootstrap) have no OUT_DIR, so flag-support probes errored and unwrap_or(false) silently dropped flags such as -gz. Fall back to unique tempfiles on the probe path only; compile() still requires OUT_DIR. Co-authored-by: Cestercian --- CHANGELOG.md | 4 ++++ src/lib.rs | 51 ++++++++++++++++++++++++++++++++++++++++---- tests/support/mod.rs | 11 +++++++++- tests/test.rs | 32 +++++++++++++++++++++++++++ 4 files changed, 93 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d512d6e9f..183638b3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Probe flag support without `OUT_DIR` via tempfile, so `flag_if_supported` no longer silently drops flags outside Cargo build scripts + ## [1.4.4](https://github.com/rust-lang/cc-rs/compare/cc-v1.4.3...cc-v1.4.4) - 2026-08-21 ### Fixed diff --git a/src/lib.rs b/src/lib.rs index 48496dfb2..b48a17c1e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1507,9 +1507,51 @@ impl Build { return Ok(is_supported); } - let out_dir = self.get_out_dir()?; - let src = self.ensure_check_file()?; - let obj = out_dir.join("flag_check"); + // Cargo build scripts have `OUT_DIR`; reuse `flag_check.c` there so + // probes stay cheap. Callers such as rustc bootstrap do not, and + // treating a missing dir as "unsupported" silently drops flags. + // Fall back to unique tempfiles instead of a shared name in `/tmp`. + // Held until `cmd.output()` returns so Drop can remove the temp files. + let mut temp_files = None; + let (out_dir, src, obj) = match self.get_out_dir() { + Ok(out_dir) => { + let src = self.ensure_check_file()?; + let obj = out_dir.join("flag_check"); + (out_dir, src, obj) + } + Err(_) => { + let tmp_dir = env::temp_dir(); + fs::create_dir_all(&tmp_dir)?; + + let suffix = if self.cuda { + assert!(self.cpp); + "flag_check.cu" + } else if self.cpp { + "flag_check.cpp" + } else { + "flag_check.c" + }; + + let mut tmp_src = crate::tempfile::NamedTempfile::new(&tmp_dir, suffix)?; + let mut tmp_file = tmp_src.take_file().unwrap(); + tmp_file.write_all(b"int main(void) { return 0; }")?; + // Close the handle before invoking the compiler; Windows + // cannot open a file that another handle still holds. + tmp_file.flush()?; + tmp_file.sync_data()?; + drop(tmp_file); + + let mut tmp_obj = crate::tempfile::NamedTempfile::new(&tmp_dir, "flag_check")?; + // Same as the source file: the compiler must be able to + // overwrite this path, so drop the open handle first. + drop(tmp_obj.take_file()); + + let src = tmp_src.path().to_owned(); + let obj = tmp_obj.path().to_owned(); + temp_files = Some((tmp_src, tmp_obj)); + (Cow::Owned(tmp_dir), src, obj) + } + }; let mut compiler = { let mut cfg = Build::new(); @@ -1520,7 +1562,7 @@ impl Build { .debug(false) .cpp(self.cpp) .cuda(self.cuda) - .out_dir(&out_dir) + .out_dir(&*out_dir) .inherit_rustflags(false) .inherit_trim_paths(false) .emit_rerun_if_env_changed(self.emit_rerun_if_env_changed); @@ -1594,6 +1636,7 @@ impl Build { self.cargo_output .print_debug(&format_args!("running: {cmd:?}")); let output = cmd.output()?; + drop(temp_files); let is_supported = output.status.success() && output.stderr.is_empty(); self.build_cache diff --git a/tests/support/mod.rs b/tests/support/mod.rs index a0853e7a8..cf94a6fea 100644 --- a/tests/support/mod.rs +++ b/tests/support/mod.rs @@ -125,6 +125,16 @@ impl Test { } pub fn gcc(&self) -> cc::Build { + let mut cfg = self.gcc_without_out_dir(); + cfg.out_dir(self.td.path()); + cfg + } + + /// Like [`Self::gcc`], but does not set [`cc::Build::out_dir`]. + /// + /// Flag-support probes must still work when `OUT_DIR` is unset, as in + /// rustc bootstrap which is not a Cargo build script. + pub fn gcc_without_out_dir(&self) -> cc::Build { let mut cfg = cc::Build::new(); let target = if self.msvc || self.msvc_autodetect { "x86_64-pc-windows-msvc" @@ -138,7 +148,6 @@ impl Test { .host(target) .opt_level(2) .debug(false) - .out_dir(self.td.path()) .env("PATH", self.path()) .env("CC_SHIM_OUT_DIR", self.td.path()); if self.family_detection_probes { diff --git a/tests/test.rs b/tests/test.rs index a5693bf58..808ac09a4 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -415,6 +415,38 @@ fn gnu_flag_if_supported() { .must_not_have("-Wflag-does-not-exist"); } +/// `flag_if_supported` must probe even when `OUT_DIR` is unset. +/// +#[test] +fn flag_if_supported_without_out_dir() { + let mut test = Test::gnu(); + test.env.remove("OUT_DIR"); + test.collect_flag_supported_probes(); + + let compiler = test + .gcc_without_out_dir() + .env("CC_SHIM_FAIL_IF_ARG", "-Wflag-does-not-exist") + .flag_if_supported("-Wall") + .flag_if_supported("-Wflag-does-not-exist") + .try_get_compiler() + .expect("try_get_compiler should succeed without OUT_DIR"); + + assert!( + compiler.args().iter().any(|a| a == "-Wall"), + "supported flag should be applied without OUT_DIR, args: {:?}", + compiler.args() + ); + assert!( + !compiler.args().iter().any(|a| a == "-Wflag-does-not-exist"), + "unsupported flag should still be rejected without OUT_DIR, args: {:?}", + compiler.args() + ); + + test.get_flag_supported_probes(0) + .must_have("-Wall") + .must_have("-c"); +} + /// cc's own probing invocations run in the environment `Build::env` sets up, /// and record only the class of probe a test asks for by name. /// From aa1f6a94161c1e68285aeca16288b3ca18d453df Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 1 Sep 2026 06:40:30 +0000 Subject: [PATCH 2/3] refactor: extract flag-support probe tempfile helpers Share check-file extension and contents between ensure_check_file and the OUT_DIR tempfile fallback, and extract probe-dir setup for reuse. Co-authored-by: Cestercian --- CHANGELOG.md | 2 +- src/lib.rs | 141 ++++++++++++++++++++++++++++++--------------------- 2 files changed, 84 insertions(+), 59 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 183638b3a..b2942454a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -- Probe flag support without `OUT_DIR` via tempfile, so `flag_if_supported` no longer silently drops flags outside Cargo build scripts +- Probe flag support without `OUT_DIR` via tempfile, so `flag_if_supported` no longer silently drops flags outside Cargo build scripts ([#1875](https://github.com/rust-lang/cc-rs/pull/1875)) ## [1.4.4](https://github.com/rust-lang/cc-rs/compare/cc-v1.4.3...cc-v1.4.4) - 2026-08-21 diff --git a/src/lib.rs b/src/lib.rs index b48a17c1e..e694ae153 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1445,6 +1445,19 @@ impl Build { } } +/// Source, object, and working directory for an `is_flag_supported` probe. +/// +/// Tempfiles, when used, are removed when this value is dropped. +struct FlagSupportProbeFiles<'a> { + dir: Cow<'a, Path>, + src: PathBuf, + obj: PathBuf, + _temp_files: Option<( + crate::tempfile::NamedTempfile, + crate::tempfile::NamedTempfile, + )>, +} + /// Invoke or fetch the compiler or archiver. impl Build { /// Run the compiler to test if it accepts the given flag. @@ -1466,25 +1479,81 @@ impl Build { ) } - fn ensure_check_file(&self) -> Result { - let out_dir = self.get_out_dir()?; - let src = if self.cuda { + fn flag_check_src_name(&self) -> &'static str { + if self.cuda { assert!(self.cpp); - out_dir.join("flag_check.cu") + "flag_check.cu" } else if self.cpp { - out_dir.join("flag_check.cpp") + "flag_check.cpp" } else { - out_dir.join("flag_check.c") - }; + "flag_check.c" + } + } + + fn write_flag_check_src(file: &mut fs::File) -> io::Result<()> { + write!(file, "int main(void) {{ return 0; }}") + } + + fn ensure_check_file(&self) -> Result { + let src = self.get_out_dir()?.join(self.flag_check_src_name()); if !src.exists() { let mut f = fs::File::create(&src)?; - write!(f, "int main(void) {{ return 0; }}")?; + Self::write_flag_check_src(&mut f)?; } Ok(src) } + /// Directory, source, and object for a flag-support probe. + /// + /// Cargo build scripts have `OUT_DIR`; reuse `flag_check.c` there so + /// probes stay cheap. Callers such as rustc bootstrap do not, and + /// treating a missing dir as "unsupported" silently drops flags. + /// Fall back to unique tempfiles instead of a shared name in `/tmp`. + fn flag_support_probe_files(&self) -> Result, Error> { + match self.get_out_dir() { + Ok(dir) => { + let src = self.ensure_check_file()?; + let obj = dir.join("flag_check"); + Ok(FlagSupportProbeFiles { + dir, + src, + obj, + _temp_files: None, + }) + } + Err(_) => { + let dir = env::temp_dir(); + fs::create_dir_all(&dir)?; + + let mut tmp_src = + crate::tempfile::NamedTempfile::new(&dir, self.flag_check_src_name())?; + let mut tmp_file = tmp_src.take_file().unwrap(); + Self::write_flag_check_src(&mut tmp_file)?; + // Close the handle before invoking the compiler; Windows + // cannot open a file that another handle still holds. + tmp_file.flush()?; + tmp_file.sync_data()?; + drop(tmp_file); + + let mut tmp_obj = crate::tempfile::NamedTempfile::new(&dir, "flag_check")?; + // Same as the source file: the compiler must be able to + // overwrite this path, so drop the open handle first. + drop(tmp_obj.take_file()); + + let src = tmp_src.path().to_owned(); + let obj = tmp_obj.path().to_owned(); + Ok(FlagSupportProbeFiles { + dir: Cow::Owned(dir), + src, + obj, + _temp_files: Some((tmp_src, tmp_obj)), + }) + } + } + } + fn is_flag_supported_inner( &self, flag: &OsStr, @@ -1507,51 +1576,7 @@ impl Build { return Ok(is_supported); } - // Cargo build scripts have `OUT_DIR`; reuse `flag_check.c` there so - // probes stay cheap. Callers such as rustc bootstrap do not, and - // treating a missing dir as "unsupported" silently drops flags. - // Fall back to unique tempfiles instead of a shared name in `/tmp`. - // Held until `cmd.output()` returns so Drop can remove the temp files. - let mut temp_files = None; - let (out_dir, src, obj) = match self.get_out_dir() { - Ok(out_dir) => { - let src = self.ensure_check_file()?; - let obj = out_dir.join("flag_check"); - (out_dir, src, obj) - } - Err(_) => { - let tmp_dir = env::temp_dir(); - fs::create_dir_all(&tmp_dir)?; - - let suffix = if self.cuda { - assert!(self.cpp); - "flag_check.cu" - } else if self.cpp { - "flag_check.cpp" - } else { - "flag_check.c" - }; - - let mut tmp_src = crate::tempfile::NamedTempfile::new(&tmp_dir, suffix)?; - let mut tmp_file = tmp_src.take_file().unwrap(); - tmp_file.write_all(b"int main(void) { return 0; }")?; - // Close the handle before invoking the compiler; Windows - // cannot open a file that another handle still holds. - tmp_file.flush()?; - tmp_file.sync_data()?; - drop(tmp_file); - - let mut tmp_obj = crate::tempfile::NamedTempfile::new(&tmp_dir, "flag_check")?; - // Same as the source file: the compiler must be able to - // overwrite this path, so drop the open handle first. - drop(tmp_obj.take_file()); - - let src = tmp_src.path().to_owned(); - let obj = tmp_obj.path().to_owned(); - temp_files = Some((tmp_src, tmp_obj)); - (Cow::Owned(tmp_dir), src, obj) - } - }; + let probe = self.flag_support_probe_files()?; let mut compiler = { let mut cfg = Build::new(); @@ -1562,7 +1587,7 @@ impl Build { .debug(false) .cpp(self.cpp) .cuda(self.cuda) - .out_dir(&*out_dir) + .out_dir(&*probe.dir) .inherit_rustflags(false) .inherit_trim_paths(false) .emit_rerun_if_env_changed(self.emit_rerun_if_env_changed); @@ -1597,7 +1622,7 @@ impl Build { cmd.set_flag_supported_env(&self.env); command_add_output_file( &mut cmd, - &obj, + &probe.obj, CmdAddOutputFileArgs { cuda: self.cuda, is_assembler_msvc: false, @@ -1619,7 +1644,7 @@ impl Build { cmd.arg("--"); } - cmd.arg(&src); + cmd.arg(&probe.src); if compiler.is_like_msvc() { // On MSVC we need to make sure the LIB directory is included @@ -1632,11 +1657,11 @@ impl Build { } } - cmd.current_dir(out_dir); + cmd.current_dir(&*probe.dir); self.cargo_output .print_debug(&format_args!("running: {cmd:?}")); let output = cmd.output()?; - drop(temp_files); + drop(probe); let is_supported = output.status.success() && output.stderr.is_empty(); self.build_cache From 2fe48916a785cf568cbd4b2099f8ac6d01d68550 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 1 Sep 2026 11:53:03 +0000 Subject: [PATCH 3/3] refactor: flush flag-check source inside write_flag_check_src Share flush and sync_data with ensure_check_file; keep the tempfile handle drop at the NamedTempfile call site so Windows can open the probe file. Co-authored-by: Cestercian --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e694ae153..949d7f673 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1491,7 +1491,9 @@ impl Build { } fn write_flag_check_src(file: &mut fs::File) -> io::Result<()> { - write!(file, "int main(void) {{ return 0; }}") + write!(file, "int main(void) {{ return 0; }}")?; + file.flush()?; + file.sync_data() } fn ensure_check_file(&self) -> Result { @@ -1533,8 +1535,6 @@ impl Build { Self::write_flag_check_src(&mut tmp_file)?; // Close the handle before invoking the compiler; Windows // cannot open a file that another handle still holds. - tmp_file.flush()?; - tmp_file.sync_data()?; drop(tmp_file); let mut tmp_obj = crate::tempfile::NamedTempfile::new(&dir, "flag_check")?;