diff --git a/bindgen-tests/tests/tests.rs b/bindgen-tests/tests/tests.rs index 6e3c358d3e..cf1875c8d2 100644 --- a/bindgen-tests/tests/tests.rs +++ b/bindgen-tests/tests/tests.rs @@ -617,6 +617,186 @@ fn test_macro_fallback_non_system_dir() { } } +#[test] +fn test_macro_fallback_header_contents() { + if let Some((9, _)) = clang_version().parsed { + return; + } + + let tmpdir = tempfile::tempdir().unwrap(); + let actual = builder() + .disable_header_comment() + .header_contents( + "test.h", + "#define UINT32_C(c) c ## U\n\ + #define SIMPLE 42\n\ + #define COMPOUND UINT32_C(69)\n", + ) + .clang_macro_fallback() + .clang_macro_fallback_build_dir(tmpdir.path()) + .clang_arg("--target=x86_64-unknown-linux") + .generate() + .unwrap() + .to_string(); + + let actual = format_code(actual).unwrap(); + let expected = format_code( + "pub const SIMPLE: u32 = 42;\npub const COMPOUND: u32 = 69;\n", + ) + .unwrap(); + + assert_eq!(expected, actual); +} + +#[test] +fn test_macro_fallback_header_contents_preserves_build_dir_files() { + if let Some((9, _)) = clang_version().parsed { + return; + } + + let tmpdir = tempfile::tempdir().unwrap(); + let victim = tmpdir.path().join(".macro_eval_header_0.h"); + fs::write(&victim, "must survive\n").unwrap(); + + let actual = builder() + .disable_header_comment() + .header_contents( + "test.h", + "#define UINT32_C(c) c ## U\n\ + #define COMPOUND UINT32_C(69)\n", + ) + .clang_macro_fallback() + .clang_macro_fallback_build_dir(tmpdir.path()) + .clang_arg("--target=x86_64-unknown-linux") + .generate() + .unwrap() + .to_string(); + + let actual = format_code(actual).unwrap(); + let expected = format_code("pub const COMPOUND: u32 = 69;\n").unwrap(); + + assert_eq!(expected, actual); + assert_eq!("must survive\n", fs::read_to_string(victim).unwrap()); +} + +#[test] +fn test_macro_fallback_header_contents_preserves_primary_header_order() { + if let Some((9, _)) = clang_version().parsed { + return; + } + + let tmpdir = tempfile::tempdir().unwrap(); + let header = tmpdir.path().join("primary.h"); + fs::write( + &header, + "#undef ORDER_VALUE\n#define ORDER_VALUE UINT32_C(1)\n", + ) + .unwrap(); + + let actual = builder() + .disable_header_comment() + .header(header.to_str().unwrap()) + .header_contents( + "override.h", + "#define UINT32_C(c) c ## U\n\ + #undef ORDER_VALUE\n\ + #define ORDER_VALUE UINT32_C(2)\n", + ) + .clang_macro_fallback() + .clang_macro_fallback_build_dir(tmpdir.path()) + .clang_arg("--target=x86_64-unknown-linux") + .generate() + .unwrap() + .to_string(); + + let actual = format_code(actual).unwrap(); + let expected = format_code("pub const ORDER_VALUE: u32 = 1;\n").unwrap(); + + assert_eq!(expected, actual); +} + +#[test] +fn test_macro_fallback_header_contents_preserves_include_identity() { + if let Some((9, _)) = clang_version().parsed { + return; + } + + let tmpdir = tempfile::tempdir().unwrap(); + let header = tmpdir.path().join("primary.h"); + fs::write( + &header, + "#undef BINDGEN_VIRTUAL_FUNC_3353\n\ + #include \"bindgen_virtual_dependency_3353.h\"\n\ + #define FROM_INCLUDE BINDGEN_VIRTUAL_FUNC_3353(7)\n", + ) + .unwrap(); + let virtual_dir = tmpdir.path().join("virtual"); + fs::create_dir(&virtual_dir).unwrap(); + let virtual_header = virtual_dir.join("bindgen_virtual_dependency_3353.h"); + + let actual = builder() + .disable_header_comment() + .header(header.to_str().unwrap()) + .header_contents( + virtual_header.to_str().unwrap(), + "#define BINDGEN_VIRTUAL_FUNC_3353(c) c ## U\n", + ) + .clang_macro_fallback() + .clang_macro_fallback_build_dir(tmpdir.path()) + .clang_arg(format!("-I{}", virtual_dir.display())) + .clang_arg("--target=x86_64-unknown-linux") + .generate() + .unwrap() + .to_string(); + + let actual = format_code(actual).unwrap(); + let expected = format_code("pub const FROM_INCLUDE: u32 = 7;\n").unwrap(); + + assert_eq!(expected, actual); +} + +#[test] +fn test_macro_fallback_header_contents_preserves_full_include_identity() { + if let Some((9, _)) = clang_version().parsed { + return; + } + + let tmpdir = tempfile::tempdir().unwrap(); + let header = tmpdir.path().join("primary.h"); + fs::write(&header, "#define FROM_COMMON COMMON_MACRO(9)\n").unwrap(); + + let virtual_dir = tmpdir.path().join("virtual"); + let a_dir = virtual_dir.join("a"); + let b_dir = virtual_dir.join("b"); + fs::create_dir_all(&a_dir).unwrap(); + fs::create_dir_all(&b_dir).unwrap(); + let a_header = a_dir.join("common.h"); + let b_header = b_dir.join("common.h"); + + let actual = builder() + .disable_header_comment() + .header(header.to_str().unwrap()) + .header_contents( + a_header.to_str().unwrap(), + "#define COMMON_MACRO(c) 0\n", + ) + .header_contents( + b_header.to_str().unwrap(), + "#undef COMMON_MACRO\n#define COMMON_MACRO(c) c ## U\n", + ) + .clang_macro_fallback() + .clang_macro_fallback_build_dir(tmpdir.path()) + .clang_arg("--target=x86_64-unknown-linux") + .generate() + .unwrap() + .to_string(); + + let actual = format_code(actual).unwrap(); + let expected = format_code("pub const FROM_COMMON: u32 = 9;\n").unwrap(); + + assert_eq!(expected, actual); +} + #[test] // Doesn't support executing sh file on Windows. // We may want to implement it in Rust so that we support all systems. diff --git a/bindgen/clang.rs b/bindgen/clang.rs index 9e614da9f8..786568ead5 100644 --- a/bindgen/clang.rs +++ b/bindgen/clang.rs @@ -14,9 +14,14 @@ use std::fs::OpenOptions; use std::hash::Hash; use std::hash::Hasher; use std::os::raw::{c_char, c_int, c_longlong, c_uint, c_ulong, c_ulonglong}; +use std::path::Path; +use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::OnceLock; use std::{mem, ptr, slice}; +/// Counter used to create collision-free macro fallback scratch directories. +static NEXT_FALLBACK_ARTIFACT_DIR: AtomicUsize = AtomicUsize::new(0); + /// Type representing a clang attribute. /// /// Values of this type can be used to check for different attributes using the `has_attrs` @@ -1911,12 +1916,61 @@ impl Drop for TranslationUnit { } } -/// Translation unit used for macro fallback parsing -pub(crate) struct FallbackTranslationUnit { +/// Scratch files used by macro fallback parsing. +pub(crate) struct FallbackArtifacts { file_path: String, pch_path: String, + dir_path: String, +} + +impl FallbackArtifacts { + /// Create a private artifact directory under `build_dir`. + pub(crate) fn new(build_dir: &str) -> Option { + let process_id = std::process::id(); + let dir = loop { + let dir_index = + NEXT_FALLBACK_ARTIFACT_DIR.fetch_add(1, Ordering::Relaxed); + let path = Path::new(build_dir) + .join(format!(".macro_eval_{process_id}_{dir_index}")); + match std::fs::create_dir(&path) { + Ok(()) => break path, + Err(error) + if error.kind() == std::io::ErrorKind::AlreadyExists => {} + Err(_) => return None, + } + }; + let dir_path = dir.to_str()?.to_owned(); + Some(FallbackArtifacts { + file_path: format!("{dir_path}/.macro_eval.c"), + pch_path: format!("{dir_path}/.macro_eval-precompile.h.pch"), + dir_path, + }) + } + + /// Path to the C file used for fallback macro parsing. + pub(crate) fn file_path(&self) -> &str { + &self.file_path + } + + /// Path to the precompiled header used for fallback macro parsing. + pub(crate) fn pch_path(&self) -> &str { + &self.pch_path + } +} + +impl Drop for FallbackArtifacts { + fn drop(&mut self) { + let _ = std::fs::remove_file(&self.file_path); + let _ = std::fs::remove_file(&self.pch_path); + let _ = std::fs::remove_dir(&self.dir_path); + } +} + +/// Translation unit used for macro fallback parsing. +pub(crate) struct FallbackTranslationUnit { idx: Box, tu: TranslationUnit, + artifacts: FallbackArtifacts, } impl fmt::Debug for FallbackTranslationUnit { @@ -1928,8 +1982,7 @@ impl fmt::Debug for FallbackTranslationUnit { impl FallbackTranslationUnit { /// Create a new fallback translation unit pub(crate) fn new( - file: String, - pch_path: String, + artifacts: FallbackArtifacts, c_args: &[Box], ) -> Option { // Create empty file @@ -1937,22 +1990,21 @@ impl FallbackTranslationUnit { .write(true) .create(true) .truncate(true) - .open(&file) + .open(artifacts.file_path()) .ok()?; let f_index = Box::new(Index::new(true, false)); let f_translation_unit = TranslationUnit::parse( &f_index, - &file, + artifacts.file_path(), c_args, &[], CXTranslationUnit_None, )?; Some(FallbackTranslationUnit { - file_path: file, - pch_path, tu: f_translation_unit, idx: f_index, + artifacts, }) } @@ -1966,7 +2018,10 @@ impl FallbackTranslationUnit { &mut self, unsaved_contents: &str, ) -> Result<(), CXErrorCode> { - let unsaved = &[UnsavedFile::new(&self.file_path, unsaved_contents)]; + let unsaved = &[UnsavedFile::new( + self.artifacts.file_path(), + unsaved_contents, + )]; let mut c_unsaved: Vec = unsaved.iter().map(|f| f.x).collect(); let ret = unsafe { @@ -1985,13 +2040,6 @@ impl FallbackTranslationUnit { } } -impl Drop for FallbackTranslationUnit { - fn drop(&mut self) { - let _ = std::fs::remove_file(&self.file_path); - let _ = std::fs::remove_file(&self.pch_path); - } -} - /// A diagnostic message generated while parsing a translation unit. pub(crate) struct Diagnostic { x: CXDiagnostic, diff --git a/bindgen/ir/context.rs b/bindgen/ir/context.rs index b5b6b4a000..0a0be3ecbc 100644 --- a/bindgen/ir/context.rs +++ b/bindgen/ir/context.rs @@ -2045,26 +2045,55 @@ If you encounter an error missing from this list, please file an issue or a PR!" &mut self, ) -> Option<&mut clang::FallbackTranslationUnit> { if self.fallback_tu.is_none() { - let file = format!( - "{}/.macro_eval.c", - match self.options().clang_macro_fallback_build_dir { - Some(ref path) => path.as_os_str().to_str()?, - None => ".", - } - ); + let build_dir = match self.options().clang_macro_fallback_build_dir + { + Some(ref path) => path.as_os_str().to_str()?.to_owned(), + None => ".".to_owned(), + }; + let artifacts = clang::FallbackArtifacts::new(&build_dir)?; let index = clang::Index::new(false, false); - let mut header_names_to_compile = Vec::new(); + let input_unsaved_files = self + .options() + .input_header_contents + .iter() + .map(|(name, contents)| { + clang::UnsavedFile::new(name.as_ref(), contents.as_ref()) + }) + .collect::>(); + + let mut effective_headers = Vec::new(); + if self.options().input_headers.is_empty() { + if let Some(((single_header, _), input_headers)) = + self.options().input_header_contents.split_first() + { + effective_headers.extend( + input_headers.iter().map(|(path, _)| path.as_ref()), + ); + effective_headers.push(single_header.as_ref()); + } + } else { + let (single_header, input_headers) = + self.options().input_headers.split_last()?; + effective_headers + .extend(input_headers.iter().map(|path| path.as_ref())); + effective_headers.extend( + self.options() + .input_header_contents + .iter() + .map(|(path, _)| path.as_ref()), + ); + effective_headers.push(single_header.as_ref()); + } + let mut header_paths = Vec::new(); - let mut header_includes = Vec::new(); - let [input_headers @ .., single_header] = - &self.options().input_headers[..] + let [input_headers @ .., single_header] = &effective_headers[..] else { return None; }; for input_header in input_headers { - let path = Path::new(input_header.as_ref()); + let path = Path::new(*input_header); if let Some(header_path) = path.parent() { if header_path == Path::new("") { header_paths.push("."); @@ -2074,19 +2103,7 @@ If you encounter an error missing from this list, please file an issue or a PR!" } else { header_paths.push("."); } - let header_name = path.file_name()?.to_str()?; - header_includes.push(header_name.to_string()); - header_names_to_compile - .push(header_name.split(".h").next()?.to_string()); } - let pch = format!( - "{}/{}", - match self.options().clang_macro_fallback_build_dir { - Some(ref path) => path.as_os_str().to_str()?, - None => ".", - }, - header_names_to_compile.join("-") + "-precompile.h.pch" - ); let mut c_args = self.options.fallback_clang_args.clone(); c_args.push("-x".to_string().into_boxed_str()); @@ -2094,22 +2111,22 @@ If you encounter an error missing from this list, please file an issue or a PR!" for header_path in header_paths { c_args.push(format!("-I{header_path}").into_boxed_str()); } - for header_include in header_includes { + for header_include in input_headers { c_args.push("-include".to_string().into_boxed_str()); - c_args.push(header_include.into_boxed_str()); + c_args.push((*header_include).into()); } let mut tu = clang::TranslationUnit::parse( &index, single_header, &c_args, - &[], + &input_unsaved_files, clang_sys::CXTranslationUnit_ForSerialization, )?; - tu.save(&pch).ok()?; + tu.save(artifacts.pch_path()).ok()?; let mut c_args = vec![ "-include-pch".to_string().into_boxed_str(), - pch.clone().into_boxed_str(), + artifacts.pch_path().to_string().into_boxed_str(), ]; let mut skip_next = false; for arg in &self.options.fallback_clang_args { @@ -2121,8 +2138,9 @@ If you encounter an error missing from this list, please file an issue or a PR!" c_args.push(arg.clone()); } } - self.fallback_tu = - Some(clang::FallbackTranslationUnit::new(file, pch, &c_args)?); + let fallback_tu = + clang::FallbackTranslationUnit::new(artifacts, &c_args)?; + self.fallback_tu = Some(fallback_tu); } self.fallback_tu.as_mut() diff --git a/bindgen/lib.rs b/bindgen/lib.rs index 0305b5cd7b..b367b14b4c 100644 --- a/bindgen/lib.rs +++ b/bindgen/lib.rs @@ -361,13 +361,14 @@ impl Builder { .flat_map(|header| ["-include".into(), header.clone()]), ); - let input_unsaved_files = - std::mem::take(&mut self.options.input_header_contents) - .into_iter() - .map(|(name, contents)| { - clang::UnsavedFile::new(name.as_ref(), contents.as_ref()) - }) - .collect::>(); + let input_unsaved_files = self + .options + .input_header_contents + .iter() + .map(|(name, contents)| { + clang::UnsavedFile::new(name.as_ref(), contents.as_ref()) + }) + .collect::>(); Bindings::generate(self.options, &input_unsaved_files) } diff --git a/bindgen/options/mod.rs b/bindgen/options/mod.rs index baa541c5ac..936d44ce1f 100644 --- a/bindgen/options/mod.rs +++ b/bindgen/options/mod.rs @@ -2261,13 +2261,12 @@ options! { /// headers. clang_macro_fallback_build_dir: Option { methods: { - /// Set a path to a directory to which `.c` and `.h.pch` files should be written for the - /// purpose of using clang to evaluate macros that can't be easily parsed. + /// Set a path to a directory under which scratch `.c` and `.h.pch` files should be + /// written for the purpose of using clang to evaluate macros that can't be easily + /// parsed. /// - /// The default location for `.h.pch` files is the directory that the corresponding - /// `.h` file is located in. The default for the temporary `.c` file used for clang - /// parsing is the current working directory. Both of these defaults are overridden - /// by this option. + /// A private subdirectory is created under this path for each fallback translation + /// unit. By default, the current working directory is used. pub fn clang_macro_fallback_build_dir>(mut self, path: P) -> Self { self.options.clang_macro_fallback_build_dir = Some(path.as_ref().to_owned()); self