Skip to content

Commit 754c1a0

Browse files
authored
fix: probe flag support without OUT_DIR via tempfile (#1875)
1 parent b1c1794 commit 754c1a0

4 files changed

Lines changed: 129 additions & 16 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Fixed
11+
12+
- 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))
13+
1014
## [1.4.4](https://github.com/rust-lang/cc-rs/compare/cc-v1.4.3...cc-v1.4.4) - 2026-08-21
1115

1216
### Fixed

src/lib.rs

Lines changed: 83 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1445,6 +1445,19 @@ impl Build {
14451445
}
14461446
}
14471447

1448+
/// Source, object, and working directory for an `is_flag_supported` probe.
1449+
///
1450+
/// Tempfiles, when used, are removed when this value is dropped.
1451+
struct FlagSupportProbeFiles<'a> {
1452+
dir: Cow<'a, Path>,
1453+
src: PathBuf,
1454+
obj: PathBuf,
1455+
_temp_files: Option<(
1456+
crate::tempfile::NamedTempfile,
1457+
crate::tempfile::NamedTempfile,
1458+
)>,
1459+
}
1460+
14481461
/// Invoke or fetch the compiler or archiver.
14491462
impl Build {
14501463
/// Run the compiler to test if it accepts the given flag.
@@ -1466,25 +1479,81 @@ impl Build {
14661479
)
14671480
}
14681481

1469-
fn ensure_check_file(&self) -> Result<PathBuf, Error> {
1470-
let out_dir = self.get_out_dir()?;
1471-
let src = if self.cuda {
1482+
fn flag_check_src_name(&self) -> &'static str {
1483+
if self.cuda {
14721484
assert!(self.cpp);
1473-
out_dir.join("flag_check.cu")
1485+
"flag_check.cu"
14741486
} else if self.cpp {
1475-
out_dir.join("flag_check.cpp")
1487+
"flag_check.cpp"
14761488
} else {
1477-
out_dir.join("flag_check.c")
1478-
};
1489+
"flag_check.c"
1490+
}
1491+
}
1492+
1493+
fn write_flag_check_src(file: &mut fs::File) -> io::Result<()> {
1494+
write!(file, "int main(void) {{ return 0; }}")?;
1495+
file.flush()?;
1496+
file.sync_data()
1497+
}
1498+
1499+
fn ensure_check_file(&self) -> Result<PathBuf, Error> {
1500+
let src = self.get_out_dir()?.join(self.flag_check_src_name());
14791501

14801502
if !src.exists() {
14811503
let mut f = fs::File::create(&src)?;
1482-
write!(f, "int main(void) {{ return 0; }}")?;
1504+
Self::write_flag_check_src(&mut f)?;
14831505
}
14841506

14851507
Ok(src)
14861508
}
14871509

1510+
/// Directory, source, and object for a flag-support probe.
1511+
///
1512+
/// Cargo build scripts have `OUT_DIR`; reuse `flag_check.c` there so
1513+
/// probes stay cheap. Callers such as rustc bootstrap do not, and
1514+
/// treating a missing dir as "unsupported" silently drops flags.
1515+
/// Fall back to unique tempfiles instead of a shared name in `/tmp`.
1516+
fn flag_support_probe_files(&self) -> Result<FlagSupportProbeFiles<'_>, Error> {
1517+
match self.get_out_dir() {
1518+
Ok(dir) => {
1519+
let src = self.ensure_check_file()?;
1520+
let obj = dir.join("flag_check");
1521+
Ok(FlagSupportProbeFiles {
1522+
dir,
1523+
src,
1524+
obj,
1525+
_temp_files: None,
1526+
})
1527+
}
1528+
Err(_) => {
1529+
let dir = env::temp_dir();
1530+
fs::create_dir_all(&dir)?;
1531+
1532+
let mut tmp_src =
1533+
crate::tempfile::NamedTempfile::new(&dir, self.flag_check_src_name())?;
1534+
let mut tmp_file = tmp_src.take_file().unwrap();
1535+
Self::write_flag_check_src(&mut tmp_file)?;
1536+
// Close the handle before invoking the compiler; Windows
1537+
// cannot open a file that another handle still holds.
1538+
drop(tmp_file);
1539+
1540+
let mut tmp_obj = crate::tempfile::NamedTempfile::new(&dir, "flag_check")?;
1541+
// Same as the source file: the compiler must be able to
1542+
// overwrite this path, so drop the open handle first.
1543+
drop(tmp_obj.take_file());
1544+
1545+
let src = tmp_src.path().to_owned();
1546+
let obj = tmp_obj.path().to_owned();
1547+
Ok(FlagSupportProbeFiles {
1548+
dir: Cow::Owned(dir),
1549+
src,
1550+
obj,
1551+
_temp_files: Some((tmp_src, tmp_obj)),
1552+
})
1553+
}
1554+
}
1555+
}
1556+
14881557
fn is_flag_supported_inner(
14891558
&self,
14901559
flag: &OsStr,
@@ -1507,9 +1576,7 @@ impl Build {
15071576
return Ok(is_supported);
15081577
}
15091578

1510-
let out_dir = self.get_out_dir()?;
1511-
let src = self.ensure_check_file()?;
1512-
let obj = out_dir.join("flag_check");
1579+
let probe = self.flag_support_probe_files()?;
15131580

15141581
let mut compiler = {
15151582
let mut cfg = Build::new();
@@ -1520,7 +1587,7 @@ impl Build {
15201587
.debug(false)
15211588
.cpp(self.cpp)
15221589
.cuda(self.cuda)
1523-
.out_dir(&out_dir)
1590+
.out_dir(&*probe.dir)
15241591
.inherit_rustflags(false)
15251592
.inherit_trim_paths(false)
15261593
.emit_rerun_if_env_changed(self.emit_rerun_if_env_changed);
@@ -1555,7 +1622,7 @@ impl Build {
15551622
cmd.set_flag_supported_env(&self.env);
15561623
command_add_output_file(
15571624
&mut cmd,
1558-
&obj,
1625+
&probe.obj,
15591626
CmdAddOutputFileArgs {
15601627
cuda: self.cuda,
15611628
is_assembler_msvc: false,
@@ -1577,7 +1644,7 @@ impl Build {
15771644
cmd.arg("--");
15781645
}
15791646

1580-
cmd.arg(&src);
1647+
cmd.arg(&probe.src);
15811648

15821649
if compiler.is_like_msvc() {
15831650
// On MSVC we need to make sure the LIB directory is included
@@ -1590,10 +1657,11 @@ impl Build {
15901657
}
15911658
}
15921659

1593-
cmd.current_dir(out_dir);
1660+
cmd.current_dir(&*probe.dir);
15941661
self.cargo_output
15951662
.print_debug(&format_args!("running: {cmd:?}"));
15961663
let output = cmd.output()?;
1664+
drop(probe);
15971665
let is_supported = output.status.success() && output.stderr.is_empty();
15981666

15991667
self.build_cache

tests/support/mod.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,16 @@ impl Test {
125125
}
126126

127127
pub fn gcc(&self) -> cc::Build {
128+
let mut cfg = self.gcc_without_out_dir();
129+
cfg.out_dir(self.td.path());
130+
cfg
131+
}
132+
133+
/// Like [`Self::gcc`], but does not set [`cc::Build::out_dir`].
134+
///
135+
/// Flag-support probes must still work when `OUT_DIR` is unset, as in
136+
/// rustc bootstrap which is not a Cargo build script.
137+
pub fn gcc_without_out_dir(&self) -> cc::Build {
128138
let mut cfg = cc::Build::new();
129139
let target = if self.msvc || self.msvc_autodetect {
130140
"x86_64-pc-windows-msvc"
@@ -138,7 +148,6 @@ impl Test {
138148
.host(target)
139149
.opt_level(2)
140150
.debug(false)
141-
.out_dir(self.td.path())
142151
.env("PATH", self.path())
143152
.env("CC_SHIM_OUT_DIR", self.td.path());
144153
if self.family_detection_probes {

tests/test.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,38 @@ fn gnu_flag_if_supported() {
415415
.must_not_have("-Wflag-does-not-exist");
416416
}
417417

418+
/// `flag_if_supported` must probe even when `OUT_DIR` is unset.
419+
/// <https://github.com/rust-lang/cc-rs/issues/1765>
420+
#[test]
421+
fn flag_if_supported_without_out_dir() {
422+
let mut test = Test::gnu();
423+
test.env.remove("OUT_DIR");
424+
test.collect_flag_supported_probes();
425+
426+
let compiler = test
427+
.gcc_without_out_dir()
428+
.env("CC_SHIM_FAIL_IF_ARG", "-Wflag-does-not-exist")
429+
.flag_if_supported("-Wall")
430+
.flag_if_supported("-Wflag-does-not-exist")
431+
.try_get_compiler()
432+
.expect("try_get_compiler should succeed without OUT_DIR");
433+
434+
assert!(
435+
compiler.args().iter().any(|a| a == "-Wall"),
436+
"supported flag should be applied without OUT_DIR, args: {:?}",
437+
compiler.args()
438+
);
439+
assert!(
440+
!compiler.args().iter().any(|a| a == "-Wflag-does-not-exist"),
441+
"unsupported flag should still be rejected without OUT_DIR, args: {:?}",
442+
compiler.args()
443+
);
444+
445+
test.get_flag_supported_probes(0)
446+
.must_have("-Wall")
447+
.must_have("-c");
448+
}
449+
418450
/// cc's own probing invocations run in the environment `Build::env` sets up,
419451
/// and record only the class of probe a test asks for by name.
420452
/// <https://github.com/rust-lang/cc-rs/issues/1859>

0 commit comments

Comments
 (0)