Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 66 additions & 30 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1425,25 +1425,6 @@ impl Build {
)
}

fn ensure_check_file(&self) -> Result<PathBuf, Error> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that msvc/cl.exe may not support compiling file from stdin? cc @ChrisDenton

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i have updated, to use a hybrid approach:

For MSVC: Keep the filebased compilati on (creates flag_check.c/cpp/cu files)

For GCC/Clang: Use stdin compilation with -x <lang> ( like -xassembler-with-cpp)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks that'd absolutely work for major use cases, though it'd be great if it's more like a dynamic fallback.

From the top of my head, the nvcc/sccache/ccache maybe a problem.

For nvcc, it seems that

nvcc -x cu -

Is needed.

And sccache does not support it at all, it's possible to use sccache with this crate

  • by specifying an env, or
  • configuring cargo to use it
  • symlink/hardlink sccache to cc/gcc/clang etc in /usr/local/bin

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that msvc/cl.exe may not support compiling file from stdin? cc @ChrisDenton

That is correct. I don't if there's some clever way to trick it into doing so but I doubt it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes basically we can only do this for clang/gcc, if there's no wrapper applied.

And if it fails, fallback to temp file is required.

let out_dir = self.get_out_dir()?;
let src = if self.cuda {
assert!(self.cpp);
out_dir.join("flag_check.cu")
} else if self.cpp {
out_dir.join("flag_check.cpp")
} else {
out_dir.join("flag_check.c")
};

if !src.exists() {
let mut f = fs::File::create(&src)?;
write!(f, "int main(void) {{ return 0; }}")?;
}

Ok(src)
}

fn is_flag_supported_inner(
&self,
flag: &OsStr,
Expand All @@ -1467,7 +1448,6 @@ impl Build {
}

let out_dir = self.get_out_dir()?;
let src = self.ensure_check_file()?;
let obj = out_dir.join("flag_check");

let mut compiler = {
Expand Down Expand Up @@ -1499,6 +1479,14 @@ impl Build {
// Avoid reporting that the arg is unsupported just because the
// compiler complains that it wasn't used.
compiler.push_cc_arg("-Wno-unused-command-line-argument".into());
// Turn unknown warning options into errors so invalid -W flags are properly rejected
compiler.push_cc_arg("-Werror=unknown-warning-option".into());
}

if compiler.is_like_gnu() || compiler.is_like_clang() {
// Turn warnings into errors to ensure invalid flags are rejected
// (e.g., -std=c++11 for C code, invalid warning flags)
compiler.push_cc_arg("-Werror".into());
}

let mut cmd = compiler.to_command();
Expand All @@ -1522,13 +1510,10 @@ impl Build {
// https://github.com/rust-lang/cc-rs/issues/1423
cmd.arg("-c");

if compiler.supports_path_delimiter() {
cmd.arg("--");
}

cmd.arg(&src);

if compiler.is_like_msvc() {
// MSVC doesn't support stdin compilation, so use file-based approach for it
// For GCC/Clang, use stdin to support language-selection flags like -xassembler-with-cpp
// See: https://github.com/rust-lang/cc-rs/issues/359
let is_supported = if compiler.is_like_msvc() {
// On MSVC we need to make sure the LIB directory is included
// so the CRT can be found.
for (key, value) in &tool.env {
Expand All @@ -1537,10 +1522,61 @@ impl Build {
break;
}
}
}

let output = cmd.current_dir(out_dir).output()?;
let is_supported = output.status.success() && output.stderr.is_empty();
// Use file-based compilation for MSVC
let src = if self.cuda {
assert!(self.cpp);
out_dir.join("flag_check.cu")
} else if self.cpp {
out_dir.join("flag_check.cpp")
} else {
out_dir.join("flag_check.c")
};

if !src.exists() {
let mut f = fs::File::create(&src)?;
write!(f, "int main(void) {{ return 0; }}")?;
}

if compiler.supports_path_delimiter() {
cmd.arg("--");
}
cmd.arg(&src);

let output = cmd.current_dir(&out_dir).output()?;
output.status.success() && output.stderr.is_empty()
} else {
// Use stdin compilation for GCC/Clang
let flag_str = flag.to_string_lossy();
let is_language_flag = flag_str.starts_with("-x");

if !is_language_flag {
// For non-language-selection flags, explicitly specify the language
// to ensure proper validation (e.g., -std=c++11 should fail for C code)
if self.cpp {
cmd.arg("-xc++");
} else if self.cuda {
cmd.arg("-xcuda");
} else {
cmd.arg("-xc");
}
}
// For language-selection flags (like -xassembler-with-cpp), don't specify
// a language - let the flag itself determine it

cmd.arg("-");

let mut child = cmd
.stdin(std::process::Stdio::piped())
.current_dir(&out_dir)
.spawn()?;

// Close stdin immediately (empty input is sufficient for flag validation)
drop(child.stdin.take());

let output = child.wait_with_output()?;
output.status.success() && output.stderr.is_empty()
};

self.build_cache
.known_flag_support_status_cache
Expand Down
25 changes: 25 additions & 0 deletions tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,31 @@ fn gnu_flag_if_supported_cpp() {
test.cmd(0).must_have("-std=c++11");
}

// Regression test for issue #359: is_flag_supported should work with
// language-specific flags (e.g., assembly flags) by using stdin compilation
// instead of file-based compilation to avoid file extension issues.
#[cfg(not(windows))]
#[test]
fn is_flag_supported_assembly_flags() {
let test = Test::gnu();

// Test that assembly-specific flags are correctly detected as supported
let build = test.gcc();
let result = build.is_flag_supported("-xassembler-with-cpp");
assert!(
result.is_ok() && result.unwrap(),
"Assembly flag -xassembler-with-cpp should be supported"
);

// Test that normal C flags still work
let build = test.gcc();
let result = build.is_flag_supported("-Wall");
assert!(
result.is_ok() && result.unwrap(),
"C flag -Wall should be supported"
);
}

#[test]
fn gnu_static() {
reset_env();
Expand Down