diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f4431871..ee2551ce 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,6 +4,7 @@ on: [push, pull_request] env: CARGO_INCREMENTAL: 0 CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse + CC_ENABLE_DEBUG_OUTPUT: true jobs: test: diff --git a/dev-tools/cc-test/build.rs b/dev-tools/cc-test/build.rs index 8968eb53..7ebeeab2 100644 --- a/dev-tools/cc-test/build.rs +++ b/dev-tools/cc-test/build.rs @@ -156,7 +156,15 @@ fn run_action_if_forked() -> bool { true } +fn disable_debug_output() { + // That env would break tests for warning/debug output, + // and it is set in the CI, to make debugging CI failure easier. + std::env::remove_var("CC_ENABLE_DEBUG_OUTPUT"); +} + fn build_cargo_warnings(warnings: bool) { + disable_debug_output(); + cc::Build::new() .cargo_metadata(false) .cargo_warnings(warnings) @@ -166,6 +174,8 @@ fn build_cargo_warnings(warnings: bool) { } fn build_cargo_metadata(metadata: bool) { + disable_debug_output(); + cc::Build::new() .cargo_metadata(metadata) .file("src/dummy.c") diff --git a/dev-tools/gen-windows-sys-binding/windows_sys.list b/dev-tools/gen-windows-sys-binding/windows_sys.list index 327bed83..bc188957 100644 --- a/dev-tools/gen-windows-sys-binding/windows_sys.list +++ b/dev-tools/gen-windows-sys-binding/windows_sys.list @@ -37,6 +37,8 @@ Windows.Win32.System.Registry.REG_SZ Windows.Win32.System.SystemInformation.IMAGE_FILE_MACHINE_AMD64 +Windows.Win32.Storage.FileSystem.FILE_ATTRIBUTE_TEMPORARY + Windows.Win32.System.Threading.GetMachineTypeAttributes Windows.Win32.System.Threading.ReleaseSemaphore Windows.Win32.System.Threading.WaitForSingleObject diff --git a/src/detect_compiler_family.c b/src/detect_compiler_family.c new file mode 100644 index 00000000..290d2fc7 --- /dev/null +++ b/src/detect_compiler_family.c @@ -0,0 +1,11 @@ +#ifdef __clang__ +#pragma message "clang" +#endif + +#ifdef __GNUC__ +#pragma message "gcc" +#endif + +#ifdef _MSC_VER +#pragma message "msvc" +#endif diff --git a/src/lib.rs b/src/lib.rs index 190141e9..b539abd0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -248,6 +248,7 @@ pub use tool::Tool; use tool::ToolFamily; mod target_info; +mod tempfile; /// A builder for compilation of a native library. /// @@ -314,6 +315,8 @@ enum ErrorKind { ToolNotFound, /// One of the function arguments failed validation. InvalidArgument, + /// No known macro is defined for the compiler when discovering tool family + ToolFamilyMacroNotFound, /// Invalid target InvalidTarget, #[cfg(feature = "parallel")] @@ -621,7 +624,7 @@ impl Build { if compiler.family.verbose_stderr() { compiler.remove_arg("-v".into()); } - if compiler.family == ToolFamily::Clang { + if compiler.is_like_clang() { // 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()); @@ -629,7 +632,7 @@ impl Build { let mut cmd = compiler.to_command(); let is_arm = target.contains("aarch64") || target.contains("arm"); - let clang = compiler.family == ToolFamily::Clang; + let clang = compiler.is_like_clang(); let gnu = compiler.family == ToolFamily::Gnu; command_add_output_file( &mut cmd, @@ -1576,7 +1579,7 @@ impl Build { let target = self.get_target()?; let msvc = target.contains("msvc"); let compiler = self.try_get_compiler()?; - let clang = compiler.family == ToolFamily::Clang; + let clang = compiler.is_like_clang(); let gnu = compiler.family == ToolFamily::Gnu; let is_assembler_msvc = msvc && asm_ext == Some(AsmFileExt::DotAsm); @@ -1732,7 +1735,7 @@ impl Build { if let Some(ref std) = self.std { let separator = match cmd.family { ToolFamily::Msvc { .. } => ':', - ToolFamily::Gnu | ToolFamily::Clang => '=', + ToolFamily::Gnu | ToolFamily::Clang { .. } => '=', }; cmd.push_cc_arg(format!("-std{}{}", separator, std).into()); } @@ -1825,23 +1828,23 @@ impl Build { _ => {} } } - ToolFamily::Gnu | ToolFamily::Clang => { + ToolFamily::Gnu | ToolFamily::Clang { .. } => { // arm-linux-androideabi-gcc 4.8 shipped with Android NDK does // not support '-Oz' - if opt_level == "z" && cmd.family != ToolFamily::Clang { + if opt_level == "z" && !cmd.is_like_clang() { cmd.push_opt_unless_duplicate("-Os".into()); } else { cmd.push_opt_unless_duplicate(format!("-O{}", opt_level).into()); } - if cmd.family == ToolFamily::Clang && target.contains("windows") { + if cmd.is_like_clang() && target.contains("windows") { // Disambiguate mingw and msvc on Windows. Problem is that // depending on the origin clang can default to a mismatchig // run-time. cmd.push_cc_arg(format!("--target={}", target).into()); } - if cmd.family == ToolFamily::Clang && target.contains("android") { + if cmd.is_like_clang() && target.contains("android") { // For compatibility with code that doesn't use pre-defined `__ANDROID__` macro. // If compiler used via ndk-build or cmake (officially supported build methods) // this macros is defined. @@ -1899,7 +1902,7 @@ impl Build { // Target flags match cmd.family { - ToolFamily::Clang => { + ToolFamily::Clang { .. } => { if !cmd.has_internal_target_arg && !(target.contains("android") && android_clang_compiler_uses_target_arg_internally(&cmd.path)) @@ -2290,7 +2293,7 @@ impl Build { if self.cpp { match (self.cpp_set_stdlib.as_ref(), cmd.family) { (None, _) => {} - (Some(stdlib), ToolFamily::Gnu) | (Some(stdlib), ToolFamily::Clang) => { + (Some(stdlib), ToolFamily::Gnu) | (Some(stdlib), ToolFamily::Clang { .. }) => { cmd.push_cc_arg(format!("-stdlib=lib{}", stdlib).into()); } _ => { @@ -2666,11 +2669,15 @@ impl Build { } fn get_base_compiler(&self) -> Result { + let out_dir = self.get_out_dir().ok(); + let out_dir = out_dir.as_deref(); + if let Some(c) = &self.compiler { return Ok(Tool::new( (**c).to_owned(), &self.cached_compiler_family, &self.cargo_output, + out_dir, )); } let host = self.get_host()?; @@ -2712,6 +2719,7 @@ impl Build { driver_mode, &self.cached_compiler_family, &self.cargo_output, + out_dir, ); if let Some(cc_wrapper) = wrapper { t.cc_wrapper_path = Some(PathBuf::from(cc_wrapper)); @@ -2730,6 +2738,7 @@ impl Build { PathBuf::from("cmd"), &self.cached_compiler_family, &self.cargo_output, + out_dir, ); t.args.push("/c".into()); t.args.push(format!("{}.bat", tool).into()); @@ -2739,6 +2748,7 @@ impl Build { PathBuf::from(tool), &self.cached_compiler_family, &self.cargo_output, + out_dir, )) } } else { @@ -2798,6 +2808,7 @@ impl Build { PathBuf::from(compiler), &self.cached_compiler_family, &self.cargo_output, + out_dir, ); if let Some(cc_wrapper) = Self::rustc_wrapper_fallback() { t.cc_wrapper_path = Some(PathBuf::from(cc_wrapper)); @@ -2821,6 +2832,7 @@ impl Build { self.cuda, &self.cached_compiler_family, &self.cargo_output, + out_dir, ); nvcc_tool .args @@ -3144,7 +3156,7 @@ impl Build { // And even extend it to gcc targets by searching for "ar" instead // of "llvm-ar"... let compiler = self.get_base_compiler().ok()?; - if compiler.family == ToolFamily::Clang { + if compiler.is_like_clang() { name = format!("llvm-{}", tool); search_programs(&mut self.cmd(&compiler.path), &name, &self.cargo_output) .map(|name| self.cmd(name)) diff --git a/src/tempfile.rs b/src/tempfile.rs new file mode 100644 index 00000000..ab0ac877 --- /dev/null +++ b/src/tempfile.rs @@ -0,0 +1,84 @@ +use std::{ + collections::hash_map::RandomState, + fs::{remove_file, File, OpenOptions}, + hash::{BuildHasher, Hasher}, + io, os, + path::{Path, PathBuf}, +}; + +#[cfg(not(any(unix, target_os = "wasi", windows)))] +compile_error!("Your system is not supported since cc cannot create named tempfile"); + +fn rand() -> u64 { + RandomState::new().build_hasher().finish() +} + +fn tmpname(suffix: &str) -> String { + format!("{}{}", rand(), suffix) +} + +fn create_named(path: &Path) -> io::Result { + let mut open_options = OpenOptions::new(); + + open_options.read(true).write(true).create_new(true); + + #[cfg(all(unix, not(target_os = "wasi")))] + ::mode(&mut open_options, 0o600); + + #[cfg(windows)] + ::custom_flags( + &mut open_options, + crate::windows::windows_sys::FILE_ATTRIBUTE_TEMPORARY, + ); + + open_options.open(path) +} + +pub(super) struct NamedTempfile { + path: PathBuf, + file: Option, +} + +impl NamedTempfile { + pub(super) fn new(base: &Path, suffix: &str) -> io::Result { + for _ in 0..10 { + let path = base.join(tmpname(suffix)); + match create_named(&path) { + Ok(file) => { + return Ok(Self { + file: Some(file), + path, + }) + } + Err(e) if e.kind() == io::ErrorKind::AlreadyExists => continue, + Err(e) => return Err(e), + }; + } + + Err(io::Error::new( + io::ErrorKind::AlreadyExists, + format!( + "too many temporary files exist in base `{}` with suffix `{}`", + base.display(), + suffix + ), + )) + } + + pub(super) fn path(&self) -> &Path { + &self.path + } + + pub(super) fn file(&self) -> &File { + self.file.as_ref().unwrap() + } +} + +impl Drop for NamedTempfile { + fn drop(&mut self) { + // On Windows you have to close all handle to it before + // removing the file. + self.file.take(); + let _ = remove_file(&self.path); + } +} diff --git a/src/tool.rs b/src/tool.rs index bb7de512..d8623ccc 100644 --- a/src/tool.rs +++ b/src/tool.rs @@ -1,12 +1,19 @@ use std::{ + borrow::Cow, collections::HashMap, - ffi::OsString, + env, + ffi::{OsStr, OsString}, + io::Write, path::{Path, PathBuf}, process::Command, sync::Mutex, }; -use crate::command_helpers::{run_output, CargoOutput}; +use crate::{ + command_helpers::{run_output, CargoOutput}, + tempfile::NamedTempfile, + Error, ErrorKind, +}; /// Configuration used to represent an invocation of a C compiler. /// @@ -34,8 +41,16 @@ impl Tool { path: PathBuf, cached_compiler_family: &Mutex, ToolFamily>>, cargo_output: &CargoOutput, + out_dir: Option<&Path>, ) -> Self { - Self::with_features(path, None, false, cached_compiler_family, cargo_output) + Self::with_features( + path, + None, + false, + cached_compiler_family, + cargo_output, + out_dir, + ) } pub(crate) fn with_clang_driver( @@ -43,6 +58,7 @@ impl Tool { clang_driver: Option<&str>, cached_compiler_family: &Mutex, ToolFamily>>, cargo_output: &CargoOutput, + out_dir: Option<&Path>, ) -> Self { Self::with_features( path, @@ -50,6 +66,7 @@ impl Tool { false, cached_compiler_family, cargo_output, + out_dir, ) } @@ -74,70 +91,102 @@ impl Tool { cuda: bool, cached_compiler_family: &Mutex, ToolFamily>>, cargo_output: &CargoOutput, + out_dir: Option<&Path>, ) -> Self { - fn detect_family_inner(path: &Path, cargo_output: &CargoOutput) -> ToolFamily { - let mut cmd = Command::new(path); - cmd.arg("--version"); - - let stdout = match run_output( - &mut cmd, + fn is_zig_cc(path: &Path, cargo_output: &CargoOutput) -> bool { + run_output( + Command::new(&path).arg("--version"), path, // tool detection issues should always be shown as warnings cargo_output, ) - .ok() - .and_then(|o| String::from_utf8(o).ok()) - { - Some(s) => s, - None => { - // --version failed. fallback to gnu - cargo_output.print_warning(&format_args!("Failed to run: {:?}", cmd)); - return ToolFamily::Gnu; + .map(|o| String::from_utf8_lossy(&o).contains("ziglang")) + .unwrap_or_default() + } + + fn detect_family_inner( + path: &Path, + cargo_output: &CargoOutput, + out_dir: Option<&Path>, + ) -> Result { + let tmp = NamedTempfile::new( + &out_dir + .map(Cow::Borrowed) + .unwrap_or_else(|| Cow::Owned(env::temp_dir())), + "detect_compiler_family.c", + )?; + tmp.file() + .write_all(include_bytes!("detect_compiler_family.c"))?; + + let stdout = run_output( + Command::new(path).arg("-E").arg(tmp.path()), + path, + // When expanding the file, the compiler prints a lot of information to stderr + // that it is not an error, but related to expanding itself. + // + // cc would have to disable warning here to prevent generation of too many warnings. + &{ + let mut cargo_output = cargo_output.clone(); + cargo_output.warnings = cargo_output.debug; + cargo_output + }, + )?; + let stdout = String::from_utf8_lossy(&stdout); + + cargo_output.print_debug(&stdout); + + let clang = stdout.contains("clang"); + let msvc = stdout.contains("msvc"); + let gcc = stdout.contains("gcc"); + + match (clang, msvc, gcc) { + (clang_cl, true, _) => Ok(ToolFamily::Msvc { clang_cl }), + (true, false, _) => Ok(ToolFamily::Clang { + zig_cc: is_zig_cc(path, cargo_output), + }), + (false, false, true) => Ok(ToolFamily::Gnu), + (false, false, false) => { + cargo_output.print_warning(&"Compiler family detection failed since it does not define `__clang__`, `__GNUC__` or `_MSC_VER`, fallback to treating it as GNU"); + Err(Error::new( + ErrorKind::ToolFamilyMacroNotFound, + "Expects macro `__clang__`, `__GNUC__` or `_MSC_VER`, but found none", + )) } - }; - if stdout.contains("clang") { - ToolFamily::Clang - } else if stdout.contains("GCC") { - ToolFamily::Gnu - } else { - // --version doesn't include clang for GCC - cargo_output.print_warning(&format_args!( - "Compiler version doesn't include clang or GCC: {:?}", - cmd - )); - ToolFamily::Gnu } } - let detect_family = |path: &Path| -> ToolFamily { + let detect_family = |path: &Path| -> Result { if let Some(family) = cached_compiler_family.lock().unwrap().get(path) { - return *family; + return Ok(*family); } - let family = detect_family_inner(path, cargo_output); + let family = detect_family_inner(path, cargo_output, out_dir)?; cached_compiler_family .lock() .unwrap() .insert(path.into(), family); - family + Ok(family) }; - // Try to detect family of the tool from its name, falling back to Gnu. - let family = if let Some(fname) = path.file_name().and_then(|p| p.to_str()) { - if fname.contains("clang-cl") { - ToolFamily::Msvc { clang_cl: true } - } else if fname.ends_with("cl") || fname == "cl.exe" { - ToolFamily::Msvc { clang_cl: false } - } else if fname.contains("clang") { - match clang_driver { - Some("cl") => ToolFamily::Msvc { clang_cl: true }, - _ => ToolFamily::Clang, + let family = detect_family(&path).unwrap_or_else(|e| { + cargo_output.print_warning(&format_args!( + "Compiler family detection failed due to error: {}", + e + )); + match path.file_name().map(OsStr::to_string_lossy) { + Some(fname) if fname.contains("clang-cl") => ToolFamily::Msvc { clang_cl: true }, + Some(fname) if fname.ends_with("cl") || fname == "cl.exe" => { + ToolFamily::Msvc { clang_cl: false } } - } else { - detect_family(&path) + Some(fname) if fname.contains("clang") => match clang_driver { + Some("cl") => ToolFamily::Msvc { clang_cl: true }, + _ => ToolFamily::Clang { + zig_cc: is_zig_cc(&path, cargo_output), + }, + }, + Some(fname) if fname.contains("zig") => ToolFamily::Clang { zig_cc: true }, + _ => ToolFamily::Gnu, } - } else { - detect_family(&path) - }; + }); Tool { path, @@ -303,7 +352,7 @@ impl Tool { /// Whether the tool is Clang-like. pub fn is_like_clang(&self) -> bool { - self.family == ToolFamily::Clang + matches!(self.family, ToolFamily::Clang { .. }) } /// Whether the tool is AppleClang under .xctoolchain @@ -337,7 +386,7 @@ pub enum ToolFamily { Gnu, /// Tool is Clang-like. It differs from the GCC in a sense that it accepts superset of flags /// and its cross-compilation approach is different. - Clang, + Clang { zig_cc: bool }, /// Tool is the MSVC cl.exe. Msvc { clang_cl: bool }, } @@ -349,7 +398,7 @@ impl ToolFamily { ToolFamily::Msvc { .. } => { cmd.push_cc_arg("-Z7".into()); } - ToolFamily::Gnu | ToolFamily::Clang => { + ToolFamily::Gnu | ToolFamily::Clang { .. } => { cmd.push_cc_arg( dwarf_version .map_or_else(|| "-g".into(), |v| format!("-gdwarf-{}", v)) @@ -362,7 +411,7 @@ impl ToolFamily { /// What the flag to force frame pointers. pub(crate) fn add_force_frame_pointer(&self, cmd: &mut Tool) { match *self { - ToolFamily::Gnu | ToolFamily::Clang => { + ToolFamily::Gnu | ToolFamily::Clang { .. } => { cmd.push_cc_arg("-fno-omit-frame-pointer".into()); } _ => (), @@ -373,7 +422,7 @@ impl ToolFamily { pub(crate) fn warnings_flags(&self) -> &'static str { match *self { ToolFamily::Msvc { .. } => "-W4", - ToolFamily::Gnu | ToolFamily::Clang => "-Wall", + ToolFamily::Gnu | ToolFamily::Clang { .. } => "-Wall", } } @@ -381,7 +430,7 @@ impl ToolFamily { pub(crate) fn extra_warnings_flags(&self) -> Option<&'static str> { match *self { ToolFamily::Msvc { .. } => None, - ToolFamily::Gnu | ToolFamily::Clang => Some("-Wextra"), + ToolFamily::Gnu | ToolFamily::Clang { .. } => Some("-Wextra"), } } @@ -389,11 +438,11 @@ impl ToolFamily { pub(crate) fn warnings_to_errors_flag(&self) -> &'static str { match *self { ToolFamily::Msvc { .. } => "-WX", - ToolFamily::Gnu | ToolFamily::Clang => "-Werror", + ToolFamily::Gnu | ToolFamily::Clang { .. } => "-Werror", } } pub(crate) fn verbose_stderr(&self) -> bool { - *self == ToolFamily::Clang + matches!(*self, ToolFamily::Clang { .. }) } } diff --git a/src/windows/windows_sys.rs b/src/windows/windows_sys.rs index 8b98ce97..56b54424 100644 --- a/src/windows/windows_sys.rs +++ b/src/windows/windows_sys.rs @@ -6,7 +6,7 @@ // cd generate-windows-sys/ // cargo run // ``` -// Bindings generated by `windows-bindgen` 0.53.0 +// Bindings generated by `windows-bindgen` 0.55.0 #![allow( non_snake_case, @@ -80,7 +80,7 @@ extern "system" { extern "system" { pub fn PeekNamedPipe( hnamedpipe: HANDLE, - lpbuffer: *mut ::core::ffi::c_void, + lpbuffer: *mut core::ffi::c_void, nbuffersize: u32, lpbytesread: *mut u32, lptotalbytesavail: *mut u32, @@ -103,15 +103,15 @@ extern "system" { extern "system" { pub fn CoCreateInstance( rclsid: *const GUID, - punkouter: *mut ::core::ffi::c_void, + punkouter: *mut core::ffi::c_void, dwclscontext: CLSCTX, riid: *const GUID, - ppv: *mut *mut ::core::ffi::c_void, + ppv: *mut *mut core::ffi::c_void, ) -> HRESULT; } #[link(name = "ole32")] extern "system" { - pub fn CoInitializeEx(pvreserved: *const ::core::ffi::c_void, dwcoinit: u32) -> HRESULT; + pub fn CoInitializeEx(pvreserved: *const core::ffi::c_void, dwcoinit: u32) -> HRESULT; } #[link(name = "oleaut32")] extern "system" { @@ -131,18 +131,20 @@ pub const COINIT_MULTITHREADED: COINIT = 0i32; pub const ERROR_NO_MORE_ITEMS: WIN32_ERROR = 259u32; pub const ERROR_SUCCESS: WIN32_ERROR = 0u32; pub const FALSE: BOOL = 0i32; -pub type FARPROC = ::core::option::Option isize>; +pub type FARPROC = Option isize>; #[repr(C)] pub struct FILETIME { pub dwLowDateTime: u32, pub dwHighDateTime: u32, } -impl ::core::marker::Copy for FILETIME {} -impl ::core::clone::Clone for FILETIME { +impl Copy for FILETIME {} +impl Clone for FILETIME { fn clone(&self) -> Self { *self } } +pub const FILE_ATTRIBUTE_TEMPORARY: FILE_FLAGS_AND_ATTRIBUTES = 256u32; +pub type FILE_FLAGS_AND_ATTRIBUTES = u32; #[repr(C)] pub struct GUID { pub data1: u32, @@ -150,8 +152,8 @@ pub struct GUID { pub data3: u16, pub data4: [u8; 8], } -impl ::core::marker::Copy for GUID {} -impl ::core::clone::Clone for GUID { +impl Copy for GUID {} +impl Clone for GUID { fn clone(&self) -> Self { *self } @@ -166,10 +168,10 @@ impl GUID { } } } -pub type HANDLE = *mut ::core::ffi::c_void; -pub type HKEY = *mut ::core::ffi::c_void; +pub type HANDLE = *mut core::ffi::c_void; +pub type HKEY = *mut core::ffi::c_void; pub const HKEY_LOCAL_MACHINE: HKEY = -2147483646i32 as _; -pub type HMODULE = *mut ::core::ffi::c_void; +pub type HMODULE = *mut core::ffi::c_void; pub type HRESULT = i32; pub type IMAGE_FILE_MACHINE = u16; pub const IMAGE_FILE_MACHINE_AMD64: IMAGE_FILE_MACHINE = 34404u16; @@ -188,11 +190,11 @@ pub struct SAFEARRAY { pub fFeatures: ADVANCED_FEATURE_FLAGS, pub cbElements: u32, pub cLocks: u32, - pub pvData: *mut ::core::ffi::c_void, + pub pvData: *mut core::ffi::c_void, pub rgsabound: [SAFEARRAYBOUND; 1], } -impl ::core::marker::Copy for SAFEARRAY {} -impl ::core::clone::Clone for SAFEARRAY { +impl Copy for SAFEARRAY {} +impl Clone for SAFEARRAY { fn clone(&self) -> Self { *self } @@ -202,8 +204,8 @@ pub struct SAFEARRAYBOUND { pub cElements: u32, pub lLbound: i32, } -impl ::core::marker::Copy for SAFEARRAYBOUND {} -impl ::core::clone::Clone for SAFEARRAYBOUND { +impl Copy for SAFEARRAYBOUND {} +impl Clone for SAFEARRAYBOUND { fn clone(&self) -> Self { *self }