diff options
-rw-r--r-- | .cargo_vcs_info.json | 2 | ||||
-rw-r--r-- | .github/workflows/ci.yml | 7 | ||||
-rw-r--r-- | .github/workflows/ssh.yml | 2 | ||||
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Android.bp | 3 | ||||
-rw-r--r-- | CHANGELOG.md | 37 | ||||
-rw-r--r-- | Cargo.toml | 16 | ||||
-rw-r--r-- | Cargo.toml.orig | 13 | ||||
-rw-r--r-- | METADATA | 25 | ||||
-rw-r--r-- | README.md | 4 | ||||
-rw-r--r-- | build.rs | 5 | ||||
-rw-r--r-- | build/common.rs | 110 | ||||
-rw-r--r-- | build/dynamic.rs | 23 | ||||
-rw-r--r-- | build/macros.rs | 38 | ||||
-rw-r--r-- | build/static.rs | 4 | ||||
-rw-r--r-- | out/common.rs | 110 | ||||
-rw-r--r-- | out/dynamic.rs | 23 | ||||
-rw-r--r-- | out/macros.rs | 38 | ||||
-rw-r--r-- | src/lib.rs | 164 | ||||
-rw-r--r-- | src/link.rs | 66 | ||||
-rw-r--r-- | src/support.rs | 6 | ||||
-rw-r--r-- | tests/build.rs | 281 |
22 files changed, 834 insertions, 144 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json index d04b5e7..26dff97 100644 --- a/.cargo_vcs_info.json +++ b/.cargo_vcs_info.json @@ -1,6 +1,6 @@ { "git": { - "sha1": "1b9b8a71748aebd26180f1f16728ef111afce5ad" + "sha1": "b1854129fa7ae4b6d521feed5eca3eb6816c9342" }, "path_in_vcs": "" }
\ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 646f4c2..bbcc176 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: matrix: os: [macos-latest, ubuntu-latest, windows-latest] clang: [["14.0", "clang_14_0"]] - rust: ["1.40.0"] + rust: ["1.51.0"] steps: - name: Checkout Repository uses: actions/checkout@v2 @@ -42,3 +42,8 @@ jobs: with: command: test args: --verbose --features "${{ matrix.clang[1] }} runtime" -- --nocapture + - name: Cargo Run (bindgen-test) + uses: actions-rs/cargo@v1 + with: + command: run + args: --manifest-path bindgen-test/Cargo.toml diff --git a/.github/workflows/ssh.yml b/.github/workflows/ssh.yml index 5dfc251..db22ef4 100644 --- a/.github/workflows/ssh.yml +++ b/.github/workflows/ssh.yml @@ -16,7 +16,7 @@ jobs: matrix: os: [macos-latest, ubuntu-latest, windows-latest] clang: [["13.0", "clang_13_0"]] - rust: ["1.40.0"] + rust: ["1.51.0"] steps: - name: Checkout Repository uses: actions/checkout@v2 @@ -1,4 +1,5 @@ .docs/ +bindgen-test/target/ target/ Cargo.lock @@ -27,6 +27,7 @@ genrule { out: [ "common.rs", "dynamic.rs", + "macros.rs", ], } @@ -34,7 +35,7 @@ rust_library_host { name: "libclang_sys", crate_name: "clang_sys", cargo_env_compat: true, - cargo_pkg_version: "1.4.0", + cargo_pkg_version: "1.7.0", srcs: [ "src/lib.rs", ":copy_clang-sys_build_out", diff --git a/CHANGELOG.md b/CHANGELOG.md index d697220..d08b4bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,40 @@ +## [1.7.0] - 2023-12-31 + +### Added +- Added support for `clang` 17.0.x + +## [1.6.1] - 2023-03-29 + +### Fixed +- Improved error message when calling a `libclang` function that is not supported by the loaded `libclang` instance (https://github.com/rust-lang/rust-bindgen/issues/2446) + +## [1.6.0] - 2023-02-18 + +### Changed +- MinGW directories are not searched for `libclang` instances on Windows when +compiling for an MSVC target +- Bumped minimum supported Rust version (MSRV) to 1.51.0 +- Changed Windows search directory preferences (`libclang` instances from +Visual Studio installs are now the lowest priority rather than the second +highest) + +## ~~[1.5.1] - 2023-02-05~~ (YANKED) + +### Changed +- MinGW directories are not searched for `libclang` instances on Windows when +compiling for an MSVC target + +## ~~[1.5.0] - 2023-02-05~~ (YANKED) + +### Changed +- Bumped minimum supported Rust version (MSRV) to 1.51.0 +- Changed Windows search directory preferences (`libclang` instances from +Visual Studio installs are now the lowest priority rather than the second +highest) + +### Added +- Added additional support for `clang` 16.0.x + ## [1.4.0] - 2022-09-22 ### Changed @@ -11,7 +11,7 @@ [package] name = "clang-sys" -version = "1.4.0" +version = "1.7.0" authors = ["Kyle Mayes <kyle@mayeses.com>"] build = "build.rs" links = "clang" @@ -23,7 +23,7 @@ repository = "https://github.com/KyleMayes/clang-sys" [package.metadata.docs.rs] features = [ - "clang_16_0", + "clang_17_0", "runtime", ] @@ -35,9 +35,18 @@ version = "0.2.39" default-features = false [dependencies.libloading] -version = "0.7" +version = "0.8" optional = true +[dev-dependencies.glob] +version = "0.3" + +[dev-dependencies.serial_test] +version = "1" + +[dev-dependencies.tempfile] +version = "3" + [build-dependencies.glob] version = "0.3" @@ -49,6 +58,7 @@ clang_13_0 = ["clang_12_0"] clang_14_0 = ["clang_13_0"] clang_15_0 = ["clang_14_0"] clang_16_0 = ["clang_15_0"] +clang_17_0 = ["clang_16_0"] clang_3_5 = [] clang_3_6 = ["clang_3_5"] clang_3_7 = ["clang_3_6"] diff --git a/Cargo.toml.orig b/Cargo.toml.orig index 1041981..c70b380 100644 --- a/Cargo.toml.orig +++ b/Cargo.toml.orig @@ -3,7 +3,7 @@ name = "clang-sys" authors = ["Kyle Mayes <kyle@mayeses.com>"] -version = "1.4.0" +version = "1.7.0" readme = "README.md" license = "Apache-2.0" @@ -36,6 +36,7 @@ clang_13_0 = ["clang_12_0"] clang_14_0 = ["clang_13_0"] clang_15_0 = ["clang_14_0"] clang_16_0 = ["clang_15_0"] +clang_17_0 = ["clang_16_0"] runtime = ["libloading"] static = [] @@ -44,12 +45,18 @@ static = [] glob = "0.3" libc = { version = "0.2.39", default-features = false } -libloading = { version = "0.7", optional = true } +libloading = { version = "0.8", optional = true } [build-dependencies] glob = "0.3" +[dev-dependencies] + +glob = "0.3" +serial_test = "1" +tempfile = "3" + [package.metadata.docs.rs] -features = ["clang_16_0", "runtime"] +features = ["clang_17_0", "runtime"] @@ -1,23 +1,20 @@ # This project was upgraded with external_updater. -# Usage: tools/external_updater/updater.sh update rust/crates/clang-sys -# For more info, check https://cs.android.com/android/platform/superproject/+/master:tools/external_updater/README.md +# Usage: tools/external_updater/updater.sh update external/rust/crates/clang-sys +# For more info, check https://cs.android.com/android/platform/superproject/+/main:tools/external_updater/README.md name: "clang-sys" description: "Rust bindings for libclang." third_party { - url { - type: HOMEPAGE - value: "https://crates.io/crates/clang-sys" - } - url { - type: ARCHIVE - value: "https://static.crates.io/crates/clang-sys/clang-sys-1.4.0.crate" - } - version: "1.4.0" license_type: NOTICE last_upgrade_date { - year: 2022 - month: 12 - day: 8 + year: 2024 + month: 5 + day: 10 + } + homepage: "https://crates.io/crates/clang-sys" + identifier { + type: "Archive" + value: "https://static.crates.io/crates/clang-sys/clang-sys-1.7.0.crate" + version: "1.7.0" } } @@ -2,8 +2,8 @@ [![Crate](https://img.shields.io/crates/v/clang-sys.svg)](https://crates.io/crates/clang-sys) [![Documentation](https://docs.rs/clang-sys/badge.svg)](https://docs.rs/clang-sys) -[![CI](https://img.shields.io/github/workflow/status/KyleMayes/clang-sys/CI/master)](https://github.com/KyleMayes/vulkanalia/actions?query=workflow%3ACI) -![MSRV](https://img.shields.io/badge/MSRV-1.40.0-blue) +[![CI](https://img.shields.io/github/actions/workflow/status/KyleMayes/clang-sys/ci.yml?branch=master)](https://github.com/KyleMayes/clang-sys/actions?query=workflow%3ACI) +![MSRV](https://img.shields.io/badge/MSRV-1.51.0-blue) Rust bindings for `libclang`. @@ -19,6 +19,10 @@ extern crate glob; use std::path::Path; +#[macro_use] +#[path = "build/macros.rs"] +pub mod macros; + #[path = "build/common.rs"] pub mod common; #[path = "build/dynamic.rs"] @@ -54,6 +58,7 @@ fn main() { } let out = env::var("OUT_DIR").unwrap(); + copy("build/macros.rs", &Path::new(&out).join("macros.rs")); copy("build/common.rs", &Path::new(&out).join("common.rs")); copy("build/dynamic.rs", &Path::new(&out).join("dynamic.rs")); } diff --git a/build/common.rs b/build/common.rs index 735d5da..3005a8a 100644 --- a/build/common.rs +++ b/build/common.rs @@ -91,9 +91,19 @@ impl Drop for CommandErrorPrinter { } } +#[cfg(test)] +pub static RUN_COMMAND_MOCK: std::sync::Mutex< + Option<Box<dyn Fn(&str, &str, &[&str]) -> Option<String> + Send + Sync + 'static>>, +> = std::sync::Mutex::new(None); + /// Executes a command and returns the `stdout` output if the command was /// successfully executed (errors are added to `COMMAND_ERRORS`). fn run_command(name: &str, path: &str, arguments: &[&str]) -> Option<String> { + #[cfg(test)] + if let Some(command) = &*RUN_COMMAND_MOCK.lock().unwrap() { + return command(name, path, arguments); + } + let output = match Command::new(path).args(arguments).output() { Ok(output) => output, Err(error) => { @@ -128,54 +138,64 @@ pub fn run_xcode_select(arguments: &[&str]) -> Option<String> { //================================================ // Search Directories //================================================ +// These search directories are listed in order of +// preference, so if multiple `libclang` instances +// are found when searching matching directories, +// the `libclang` instances from earlier +// directories will be preferred (though version +// takes precedence over location). +//================================================ /// `libclang` directory patterns for Haiku. const DIRECTORIES_HAIKU: &[&str] = &[ - "/boot/system/lib", - "/boot/system/develop/lib", - "/boot/system/non-packaged/lib", - "/boot/system/non-packaged/develop/lib", - "/boot/home/config/non-packaged/lib", "/boot/home/config/non-packaged/develop/lib", + "/boot/home/config/non-packaged/lib", + "/boot/system/non-packaged/develop/lib", + "/boot/system/non-packaged/lib", + "/boot/system/develop/lib", + "/boot/system/lib", ]; /// `libclang` directory patterns for Linux (and FreeBSD). const DIRECTORIES_LINUX: &[&str] = &[ - "/usr/lib*", - "/usr/lib*/*", - "/usr/lib*/*/*", - "/usr/local/lib*", - "/usr/local/lib*/*", - "/usr/local/lib*/*/*", "/usr/local/llvm*/lib*", + "/usr/local/lib*/*/*", + "/usr/local/lib*/*", + "/usr/local/lib*", + "/usr/lib*/*/*", + "/usr/lib*/*", + "/usr/lib*", ]; /// `libclang` directory patterns for macOS. const DIRECTORIES_MACOS: &[&str] = &[ - "/usr/local/opt/llvm*/lib", - "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib", - "/Library/Developer/CommandLineTools/usr/lib", "/usr/local/opt/llvm*/lib/llvm*/lib", + "/Library/Developer/CommandLineTools/usr/lib", + "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib", + "/usr/local/opt/llvm*/lib", ]; /// `libclang` directory patterns for Windows. -const DIRECTORIES_WINDOWS: &[&str] = &[ - "C:\\LLVM\\lib", - "C:\\Program Files*\\LLVM\\lib", - "C:\\MSYS*\\MinGW*\\lib", - // LLVM + Clang can be installed as a component of Visual Studio. - // https://github.com/KyleMayes/clang-sys/issues/121 - "C:\\Program Files*\\Microsoft Visual Studio\\*\\BuildTools\\VC\\Tools\\Llvm\\**\\bin", +/// +/// The boolean indicates whether the directory pattern should be used when +/// compiling for an MSVC target environment. +const DIRECTORIES_WINDOWS: &[(&str, bool)] = &[ // LLVM + Clang can be installed using Scoop (https://scoop.sh). - // Other Windows package managers install LLVM + Clang to previously listed + // Other Windows package managers install LLVM + Clang to other listed // system-wide directories. - "C:\\Users\\*\\scoop\\apps\\llvm\\current\\bin", + ("C:\\Users\\*\\scoop\\apps\\llvm\\current\\lib", true), + ("C:\\MSYS*\\MinGW*\\lib", false), + ("C:\\Program Files*\\LLVM\\lib", true), + ("C:\\LLVM\\lib", true), + // LLVM + Clang can be installed as a component of Visual Studio. + // https://github.com/KyleMayes/clang-sys/issues/121 + ("C:\\Program Files*\\Microsoft Visual Studio\\*\\BuildTools\\VC\\Tools\\Llvm\\**\\lib", true), ]; /// `libclang` directory patterns for illumos const DIRECTORIES_ILLUMOS: &[&str] = &[ - "/opt/ooce/clang-*/lib", "/opt/ooce/llvm-*/lib", + "/opt/ooce/clang-*/lib", ]; //================================================ @@ -233,7 +253,7 @@ fn search_directories(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, S // keep things consistent with other platforms, only LLVM `lib` directories // are included in the backup search directory globs so we need to search // the LLVM `bin` directory here. - if cfg!(target_os = "windows") && directory.ends_with("lib") { + if target_os!("windows") && directory.ends_with("lib") { let sibling = directory.parent().unwrap().join("bin"); results.extend(search_directory(&sibling, filenames).into_iter()); } @@ -273,7 +293,7 @@ pub fn search_libclang_directories(filenames: &[String], variable: &str) -> Vec< // Search the toolchain directory in the directory returned by // `xcode-select --print-path`. - if cfg!(target_os = "macos") { + if target_os!("macos") { if let Some(output) = run_xcode_select(&["--print-path"]) { let directory = Path::new(output.lines().next().unwrap()).to_path_buf(); let directory = directory.join("Toolchains/XcodeDefault.xctoolchain/usr/lib"); @@ -289,25 +309,41 @@ pub fn search_libclang_directories(filenames: &[String], variable: &str) -> Vec< } // Determine the `libclang` directory patterns. - let directories = if cfg!(target_os = "haiku") { - DIRECTORIES_HAIKU - } else if cfg!(any(target_os = "linux", target_os = "freebsd")) { - DIRECTORIES_LINUX - } else if cfg!(target_os = "macos") { - DIRECTORIES_MACOS - } else if cfg!(target_os = "windows") { + let directories: Vec<&str> = if target_os!("haiku") { + DIRECTORIES_HAIKU.into() + } else if target_os!("linux") || target_os!("freebsd") { + DIRECTORIES_LINUX.into() + } else if target_os!("macos") { + DIRECTORIES_MACOS.into() + } else if target_os!("windows") { + let msvc = target_env!("msvc"); DIRECTORIES_WINDOWS - } else if cfg!(target_os = "illumos") { - DIRECTORIES_ILLUMOS + .iter() + .filter(|d| d.1 || !msvc) + .map(|d| d.0) + .collect() + } else if target_os!("illumos") { + DIRECTORIES_ILLUMOS.into() + } else { + vec![] + }; + + // We use temporary directories when testing the build script so we'll + // remove the prefixes that make the directories absolute. + let directories = if test!() { + directories + .iter() + .map(|d| d.strip_prefix('/').or_else(|| d.strip_prefix("C:\\")).unwrap_or(d)) + .collect::<Vec<_>>() } else { - &[] + directories }; // Search the directories provided by the `libclang` directory patterns. let mut options = MatchOptions::new(); options.case_sensitive = false; options.require_literal_separator = true; - for directory in directories.iter().rev() { + for directory in directories.iter() { if let Ok(directories) = glob::glob_with(directory, options) { for directory in directories.filter_map(Result::ok).filter(|p| p.is_dir()) { found.extend(search_directories(&directory, filenames)); diff --git a/build/dynamic.rs b/build/dynamic.rs index 39247c8..25e1c18 100644 --- a/build/dynamic.rs +++ b/build/dynamic.rs @@ -50,26 +50,26 @@ fn parse_pe_header(path: &Path) -> io::Result<u16> { /// Checks that a `libclang` shared library matches the target platform. fn validate_library(path: &Path) -> Result<(), String> { - if cfg!(any(target_os = "linux", target_os = "freebsd")) { + if target_os!("linux") || target_os!("freebsd") { let class = parse_elf_header(path).map_err(|e| e.to_string())?; - if cfg!(target_pointer_width = "32") && class != 1 { + if target_pointer_width!("32") && class != 1 { return Err("invalid ELF class (64-bit)".into()); } - if cfg!(target_pointer_width = "64") && class != 2 { + if target_pointer_width!("64") && class != 2 { return Err("invalid ELF class (32-bit)".into()); } Ok(()) - } else if cfg!(target_os = "windows") { + } else if target_os!("windows") { let magic = parse_pe_header(path).map_err(|e| e.to_string())?; - if cfg!(target_pointer_width = "32") && magic != 267 { + if target_pointer_width!("32") && magic != 267 { return Err("invalid DLL (64-bit)".into()); } - if cfg!(target_pointer_width = "64") && magic != 523 { + if target_pointer_width!("64") && magic != 523 { return Err("invalid DLL (32-bit)".into()); } @@ -105,7 +105,7 @@ fn search_libclang_directories(runtime: bool) -> Result<Vec<(PathBuf, String, Ve env::consts::DLL_SUFFIX )]; - if cfg!(target_os = "linux") { + if target_os!("linux") { // Some Linux distributions don't create a `libclang.so` symlink, so we // need to look for versioned files (e.g., `libclang-3.9.so`). files.push("libclang-*.so".into()); @@ -121,19 +121,14 @@ fn search_libclang_directories(runtime: bool) -> Result<Vec<(PathBuf, String, Ve } } - if cfg!(any( - target_os = "freebsd", - target_os = "haiku", - target_os = "netbsd", - target_os = "openbsd", - )) { + if target_os!("freebsd") || target_os!("haiku") || target_os!("netbsd") || target_os!("openbsd") { // Some BSD distributions don't create a `libclang.so` symlink either, // but use a different naming scheme for versioned files (e.g., // `libclang.so.7.0`). files.push("libclang.so.*".into()); } - if cfg!(target_os = "windows") { + if target_os!("windows") { // The official LLVM build uses `libclang.dll` on Windows instead of // `clang.dll`. However, unofficial builds such as MinGW use `clang.dll`. files.push("libclang.dll".into()); diff --git a/build/macros.rs b/build/macros.rs new file mode 100644 index 0000000..811c7c3 --- /dev/null +++ b/build/macros.rs @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 + +macro_rules! test { + () => (cfg!(test) && ::std::env::var("_CLANG_SYS_TEST").is_ok()); +} + +macro_rules! target_os { + ($os:expr) => { + if cfg!(test) && ::std::env::var("_CLANG_SYS_TEST").is_ok() { + let var = ::std::env::var("_CLANG_SYS_TEST_OS"); + var.map_or(false, |v| v == $os) + } else { + cfg!(target_os = $os) + } + }; +} + +macro_rules! target_pointer_width { + ($pointer_width:expr) => { + if cfg!(test) && ::std::env::var("_CLANG_SYS_TEST").is_ok() { + let var = ::std::env::var("_CLANG_SYS_TEST_POINTER_WIDTH"); + var.map_or(false, |v| v == $pointer_width) + } else { + cfg!(target_pointer_width = $pointer_width) + } + }; +} + +macro_rules! target_env { + ($env:expr) => { + if cfg!(test) && ::std::env::var("_CLANG_SYS_TEST").is_ok() { + let var = ::std::env::var("_CLANG_SYS_TEST_ENV"); + var.map_or(false, |v| v == $env) + } else { + cfg!(target_env = $env) + } + }; +} diff --git a/build/static.rs b/build/static.rs index 6af914f..1123189 100644 --- a/build/static.rs +++ b/build/static.rs @@ -6,7 +6,7 @@ use std::path::{Path, PathBuf}; use glob::Pattern; -use common; +use super::common; //================================================ // Searching @@ -79,7 +79,7 @@ fn get_clang_libraries<P: AsRef<Path>>(directory: P) -> Vec<String> { /// Finds a directory containing LLVM and Clang static libraries and returns the /// path to that directory. fn find() -> PathBuf { - let name = if cfg!(target_os = "windows") { + let name = if target_os!("windows") { "libclang.lib" } else { "libclang.a" diff --git a/out/common.rs b/out/common.rs index 735d5da..3005a8a 100644 --- a/out/common.rs +++ b/out/common.rs @@ -91,9 +91,19 @@ impl Drop for CommandErrorPrinter { } } +#[cfg(test)] +pub static RUN_COMMAND_MOCK: std::sync::Mutex< + Option<Box<dyn Fn(&str, &str, &[&str]) -> Option<String> + Send + Sync + 'static>>, +> = std::sync::Mutex::new(None); + /// Executes a command and returns the `stdout` output if the command was /// successfully executed (errors are added to `COMMAND_ERRORS`). fn run_command(name: &str, path: &str, arguments: &[&str]) -> Option<String> { + #[cfg(test)] + if let Some(command) = &*RUN_COMMAND_MOCK.lock().unwrap() { + return command(name, path, arguments); + } + let output = match Command::new(path).args(arguments).output() { Ok(output) => output, Err(error) => { @@ -128,54 +138,64 @@ pub fn run_xcode_select(arguments: &[&str]) -> Option<String> { //================================================ // Search Directories //================================================ +// These search directories are listed in order of +// preference, so if multiple `libclang` instances +// are found when searching matching directories, +// the `libclang` instances from earlier +// directories will be preferred (though version +// takes precedence over location). +//================================================ /// `libclang` directory patterns for Haiku. const DIRECTORIES_HAIKU: &[&str] = &[ - "/boot/system/lib", - "/boot/system/develop/lib", - "/boot/system/non-packaged/lib", - "/boot/system/non-packaged/develop/lib", - "/boot/home/config/non-packaged/lib", "/boot/home/config/non-packaged/develop/lib", + "/boot/home/config/non-packaged/lib", + "/boot/system/non-packaged/develop/lib", + "/boot/system/non-packaged/lib", + "/boot/system/develop/lib", + "/boot/system/lib", ]; /// `libclang` directory patterns for Linux (and FreeBSD). const DIRECTORIES_LINUX: &[&str] = &[ - "/usr/lib*", - "/usr/lib*/*", - "/usr/lib*/*/*", - "/usr/local/lib*", - "/usr/local/lib*/*", - "/usr/local/lib*/*/*", "/usr/local/llvm*/lib*", + "/usr/local/lib*/*/*", + "/usr/local/lib*/*", + "/usr/local/lib*", + "/usr/lib*/*/*", + "/usr/lib*/*", + "/usr/lib*", ]; /// `libclang` directory patterns for macOS. const DIRECTORIES_MACOS: &[&str] = &[ - "/usr/local/opt/llvm*/lib", - "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib", - "/Library/Developer/CommandLineTools/usr/lib", "/usr/local/opt/llvm*/lib/llvm*/lib", + "/Library/Developer/CommandLineTools/usr/lib", + "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib", + "/usr/local/opt/llvm*/lib", ]; /// `libclang` directory patterns for Windows. -const DIRECTORIES_WINDOWS: &[&str] = &[ - "C:\\LLVM\\lib", - "C:\\Program Files*\\LLVM\\lib", - "C:\\MSYS*\\MinGW*\\lib", - // LLVM + Clang can be installed as a component of Visual Studio. - // https://github.com/KyleMayes/clang-sys/issues/121 - "C:\\Program Files*\\Microsoft Visual Studio\\*\\BuildTools\\VC\\Tools\\Llvm\\**\\bin", +/// +/// The boolean indicates whether the directory pattern should be used when +/// compiling for an MSVC target environment. +const DIRECTORIES_WINDOWS: &[(&str, bool)] = &[ // LLVM + Clang can be installed using Scoop (https://scoop.sh). - // Other Windows package managers install LLVM + Clang to previously listed + // Other Windows package managers install LLVM + Clang to other listed // system-wide directories. - "C:\\Users\\*\\scoop\\apps\\llvm\\current\\bin", + ("C:\\Users\\*\\scoop\\apps\\llvm\\current\\lib", true), + ("C:\\MSYS*\\MinGW*\\lib", false), + ("C:\\Program Files*\\LLVM\\lib", true), + ("C:\\LLVM\\lib", true), + // LLVM + Clang can be installed as a component of Visual Studio. + // https://github.com/KyleMayes/clang-sys/issues/121 + ("C:\\Program Files*\\Microsoft Visual Studio\\*\\BuildTools\\VC\\Tools\\Llvm\\**\\lib", true), ]; /// `libclang` directory patterns for illumos const DIRECTORIES_ILLUMOS: &[&str] = &[ - "/opt/ooce/clang-*/lib", "/opt/ooce/llvm-*/lib", + "/opt/ooce/clang-*/lib", ]; //================================================ @@ -233,7 +253,7 @@ fn search_directories(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, S // keep things consistent with other platforms, only LLVM `lib` directories // are included in the backup search directory globs so we need to search // the LLVM `bin` directory here. - if cfg!(target_os = "windows") && directory.ends_with("lib") { + if target_os!("windows") && directory.ends_with("lib") { let sibling = directory.parent().unwrap().join("bin"); results.extend(search_directory(&sibling, filenames).into_iter()); } @@ -273,7 +293,7 @@ pub fn search_libclang_directories(filenames: &[String], variable: &str) -> Vec< // Search the toolchain directory in the directory returned by // `xcode-select --print-path`. - if cfg!(target_os = "macos") { + if target_os!("macos") { if let Some(output) = run_xcode_select(&["--print-path"]) { let directory = Path::new(output.lines().next().unwrap()).to_path_buf(); let directory = directory.join("Toolchains/XcodeDefault.xctoolchain/usr/lib"); @@ -289,25 +309,41 @@ pub fn search_libclang_directories(filenames: &[String], variable: &str) -> Vec< } // Determine the `libclang` directory patterns. - let directories = if cfg!(target_os = "haiku") { - DIRECTORIES_HAIKU - } else if cfg!(any(target_os = "linux", target_os = "freebsd")) { - DIRECTORIES_LINUX - } else if cfg!(target_os = "macos") { - DIRECTORIES_MACOS - } else if cfg!(target_os = "windows") { + let directories: Vec<&str> = if target_os!("haiku") { + DIRECTORIES_HAIKU.into() + } else if target_os!("linux") || target_os!("freebsd") { + DIRECTORIES_LINUX.into() + } else if target_os!("macos") { + DIRECTORIES_MACOS.into() + } else if target_os!("windows") { + let msvc = target_env!("msvc"); DIRECTORIES_WINDOWS - } else if cfg!(target_os = "illumos") { - DIRECTORIES_ILLUMOS + .iter() + .filter(|d| d.1 || !msvc) + .map(|d| d.0) + .collect() + } else if target_os!("illumos") { + DIRECTORIES_ILLUMOS.into() + } else { + vec![] + }; + + // We use temporary directories when testing the build script so we'll + // remove the prefixes that make the directories absolute. + let directories = if test!() { + directories + .iter() + .map(|d| d.strip_prefix('/').or_else(|| d.strip_prefix("C:\\")).unwrap_or(d)) + .collect::<Vec<_>>() } else { - &[] + directories }; // Search the directories provided by the `libclang` directory patterns. let mut options = MatchOptions::new(); options.case_sensitive = false; options.require_literal_separator = true; - for directory in directories.iter().rev() { + for directory in directories.iter() { if let Ok(directories) = glob::glob_with(directory, options) { for directory in directories.filter_map(Result::ok).filter(|p| p.is_dir()) { found.extend(search_directories(&directory, filenames)); diff --git a/out/dynamic.rs b/out/dynamic.rs index 39247c8..25e1c18 100644 --- a/out/dynamic.rs +++ b/out/dynamic.rs @@ -50,26 +50,26 @@ fn parse_pe_header(path: &Path) -> io::Result<u16> { /// Checks that a `libclang` shared library matches the target platform. fn validate_library(path: &Path) -> Result<(), String> { - if cfg!(any(target_os = "linux", target_os = "freebsd")) { + if target_os!("linux") || target_os!("freebsd") { let class = parse_elf_header(path).map_err(|e| e.to_string())?; - if cfg!(target_pointer_width = "32") && class != 1 { + if target_pointer_width!("32") && class != 1 { return Err("invalid ELF class (64-bit)".into()); } - if cfg!(target_pointer_width = "64") && class != 2 { + if target_pointer_width!("64") && class != 2 { return Err("invalid ELF class (32-bit)".into()); } Ok(()) - } else if cfg!(target_os = "windows") { + } else if target_os!("windows") { let magic = parse_pe_header(path).map_err(|e| e.to_string())?; - if cfg!(target_pointer_width = "32") && magic != 267 { + if target_pointer_width!("32") && magic != 267 { return Err("invalid DLL (64-bit)".into()); } - if cfg!(target_pointer_width = "64") && magic != 523 { + if target_pointer_width!("64") && magic != 523 { return Err("invalid DLL (32-bit)".into()); } @@ -105,7 +105,7 @@ fn search_libclang_directories(runtime: bool) -> Result<Vec<(PathBuf, String, Ve env::consts::DLL_SUFFIX )]; - if cfg!(target_os = "linux") { + if target_os!("linux") { // Some Linux distributions don't create a `libclang.so` symlink, so we // need to look for versioned files (e.g., `libclang-3.9.so`). files.push("libclang-*.so".into()); @@ -121,19 +121,14 @@ fn search_libclang_directories(runtime: bool) -> Result<Vec<(PathBuf, String, Ve } } - if cfg!(any( - target_os = "freebsd", - target_os = "haiku", - target_os = "netbsd", - target_os = "openbsd", - )) { + if target_os!("freebsd") || target_os!("haiku") || target_os!("netbsd") || target_os!("openbsd") { // Some BSD distributions don't create a `libclang.so` symlink either, // but use a different naming scheme for versioned files (e.g., // `libclang.so.7.0`). files.push("libclang.so.*".into()); } - if cfg!(target_os = "windows") { + if target_os!("windows") { // The official LLVM build uses `libclang.dll` on Windows instead of // `clang.dll`. However, unofficial builds such as MinGW use `clang.dll`. files.push("libclang.dll".into()); diff --git a/out/macros.rs b/out/macros.rs new file mode 100644 index 0000000..811c7c3 --- /dev/null +++ b/out/macros.rs @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 + +macro_rules! test { + () => (cfg!(test) && ::std::env::var("_CLANG_SYS_TEST").is_ok()); +} + +macro_rules! target_os { + ($os:expr) => { + if cfg!(test) && ::std::env::var("_CLANG_SYS_TEST").is_ok() { + let var = ::std::env::var("_CLANG_SYS_TEST_OS"); + var.map_or(false, |v| v == $os) + } else { + cfg!(target_os = $os) + } + }; +} + +macro_rules! target_pointer_width { + ($pointer_width:expr) => { + if cfg!(test) && ::std::env::var("_CLANG_SYS_TEST").is_ok() { + let var = ::std::env::var("_CLANG_SYS_TEST_POINTER_WIDTH"); + var.map_or(false, |v| v == $pointer_width) + } else { + cfg!(target_pointer_width = $pointer_width) + } + }; +} + +macro_rules! target_env { + ($env:expr) => { + if cfg!(test) && ::std::env::var("_CLANG_SYS_TEST").is_ok() { + let var = ::std::env::var("_CLANG_SYS_TEST_ENV"); + var.map_or(false, |v| v == $env) + } else { + cfg!(target_env = $env) + } + }; +} @@ -48,6 +48,20 @@ pub type CXInclusionVisitor = extern "C" fn(CXFile, *mut CXSourceLocation, c_uin /// Defines a C enum as a series of constants. macro_rules! cenum { + (#[repr($ty:ty)] $(#[$meta:meta])* enum $name:ident { + $($(#[$vmeta:meta])* const $variant:ident = $value:expr), +, + }) => ( + pub type $name = $ty; + + $($(#[$vmeta])* pub const $variant: $name = $value;)+ + ); + (#[repr($ty:ty)] $(#[$meta:meta])* enum $name:ident { + $($(#[$vmeta:meta])* const $variant:ident = $value:expr); +; + }) => ( + pub type $name = $ty; + + $($(#[$vmeta])* pub const $variant: $name = $value;)+ + ); ($(#[$meta:meta])* enum $name:ident { $($(#[$vmeta:meta])* const $variant:ident = $value:expr), +, }) => ( @@ -98,6 +112,47 @@ cenum! { } cenum! { + /// Only available on `libclang` 17.0 and later. + #[cfg(feature = "clang_17_0")] + enum CXBinaryOperatorKind { + const CXBinaryOperator_Invalid = 0, + const CXBinaryOperator_PtrMemD = 1, + const CXBinaryOperator_PtrMemI = 2, + const CXBinaryOperator_Mul = 3, + const CXBinaryOperator_Div = 4, + const CXBinaryOperator_Rem = 5, + const CXBinaryOperator_Add = 6, + const CXBinaryOperator_Sub = 7, + const CXBinaryOperator_Shl = 8, + const CXBinaryOperator_Shr = 9, + const CXBinaryOperator_Cmp = 10, + const CXBinaryOperator_LT = 11, + const CXBinaryOperator_GT = 12, + const CXBinaryOperator_LE = 13, + const CXBinaryOperator_GE = 14, + const CXBinaryOperator_EQ = 15, + const CXBinaryOperator_NE = 16, + const CXBinaryOperator_And = 17, + const CXBinaryOperator_Xor = 18, + const CXBinaryOperator_Or = 19, + const CXBinaryOperator_LAnd = 20, + const CXBinaryOperator_LOr = 21, + const CXBinaryOperator_Assign = 22, + const CXBinaryOperator_MulAssign = 23, + const CXBinaryOperator_DivAssign = 24, + const CXBinaryOperator_RemAssign = 25, + const CXBinaryOperator_AddAssign = 26, + const CXBinaryOperator_SubAssign = 27, + const CXBinaryOperator_ShlAssign = 28, + const CXBinaryOperator_ShrAssign = 29, + const CXBinaryOperator_AndAssign = 30, + const CXBinaryOperator_XorAssign = 31, + const CXBinaryOperator_OrAssign = 32, + const CXBinaryOperator_Comma = 33, + } +} + +cenum! { enum CXCallingConv { const CXCallingConv_Default = 0, const CXCallingConv_C = 1, @@ -141,6 +196,17 @@ cenum! { } cenum! { + #[repr(c_uchar)] + /// Only available on `libclang` 17.0 and later. + #[cfg(feature = "clang_17_0")] + enum CXChoice { + const CXChoice_Default = 0, + const CXChoice_Enabled = 1, + const CXChoice_Disabled = 2, + } +} + +cenum! { enum CXCommentInlineCommandRenderKind { const CXCommentInlineCommandRenderKind_Normal = 0, const CXCommentInlineCommandRenderKind_Bold = 1, @@ -327,6 +393,8 @@ cenum! { const CXCursor_ConceptSpecializationExpr = 153, /// Only produced by `libclang` 15.0 and later. const CXCursor_RequiresExpr = 154, + /// Only produced by `libclang` 16.0 and later. + const CXCursor_CXXParenListInitExpr = 155, const CXCursor_UnexposedStmt = 200, const CXCursor_LabelStmt = 201, const CXCursor_CompoundStmt = 202, @@ -490,6 +558,8 @@ cenum! { const CXCursor_OMPParallelMaskedTaskLoopDirective = 303, /// Only produced by `libclang` 15.0 and later. const CXCursor_OMPParallelMaskedTaskLoopSimdDirective = 304, + /// Only produced by `libclang` 16.0 and later. + const CXCursor_OMPErrorDirective = 305, #[cfg(not(feature="clang_15_0"))] const CXCursor_TranslationUnit = 300, #[cfg(feature="clang_15_0")] @@ -1045,7 +1115,7 @@ cenum! { /// Only produced by `libclang` 11.0 and later. const CXType_Atomic = 177, /// Only produced by `libclang` 15.0 and later. - const CXType_BTFTagAttributed = 178, + const CXType_BTFTagAttributed = 178, } } @@ -1086,6 +1156,28 @@ cenum! { } cenum! { + /// Only available on `libclang` 17.0 and later. + #[cfg(feature = "clang_17_0")] + enum CXUnaryOperatorKind { + const CXUnaryOperator_Invalid = 0, + const CXUnaryOperator_PostInc = 1, + const CXUnaryOperator_PostDec = 2, + const CXUnaryOperator_PreInc = 3, + const CXUnaryOperator_PreDec = 4, + const CXUnaryOperator_AddrOf = 5, + const CXUnaryOperator_Deref = 6, + const CXUnaryOperator_Plus = 7, + const CXUnaryOperator_Minus = 8, + const CXUnaryOperator_Not = 9, + const CXUnaryOperator_LNot = 10, + const CXUnaryOperator_Real = 11, + const CXUnaryOperator_Imag = 12, + const CXUnaryOperator_Extension = 13, + const CXUnaryOperator_Coawait = 14, + } +} + +cenum! { enum CXVisitorResult { const CXVisit_Break = 0, const CXVisit_Continue = 1, @@ -1197,6 +1289,28 @@ cenum! { } } +/// Only available on `libclang` 17.0 and later. +#[cfg(feature = "clang_17_0")] +#[cfg(not(target_os = "windows"))] +pub type CXIndexOptions_Flags = c_ushort; + +/// Only available on `libclang` 17.0 and later. +#[cfg(feature = "clang_17_0")] +#[cfg(target_os = "windows")] +pub type CXIndexOptions_Flags = c_uint; + +/// Only available on `libclang` 17.0 and later. +#[cfg(feature = "clang_17_0")] +pub const CXIndexOptions_ExcludeDeclarationsFromPCH: CXIndexOptions_Flags = 1; + +/// Only available on `libclang` 17.0 and later. +#[cfg(feature = "clang_17_0")] +pub const CXIndexOptions_DisplayDiagnostics: CXIndexOptions_Flags = 2; + +/// Only available on `libclang` 17.0 and later. +#[cfg(feature = "clang_17_0")] +pub const CXIndexOptions_StorePreamblesInMemory: CXIndexOptions_Flags = 4; + cenum! { enum CXNameRefFlags { const CXNameRange_WantQualifier = 1; @@ -1590,6 +1704,21 @@ pub struct CXIdxObjCProtocolRefListInfo { default!(CXIdxObjCProtocolRefListInfo); +#[cfg(feature = "clang_17_0")] +#[derive(Copy, Clone, Debug)] +#[repr(C)] +pub struct CXIndexOptions { + pub Size: c_uint, + pub ThreadBackgroundPriorityForIndexing: CXChoice, + pub ThreadBackgroundPriorityForEditing: CXChoice, + pub flags: CXIndexOptions_Flags, + pub PreambleStoragePath: *const c_char, + pub InvocationEmissionPath: *const c_char, +} + +#[cfg(feature = "clang_17_0")] +default!(CXIndexOptions); + #[derive(Copy, Clone, Debug)] #[repr(C)] pub struct CXPlatformAvailability { @@ -1752,12 +1881,24 @@ link! { #[cfg(feature = "clang_3_8")] pub fn clang_CXXField_isMutable(cursor: CXCursor) -> c_uint; pub fn clang_CXXMethod_isConst(cursor: CXCursor) -> c_uint; + /// Only available on `libclang` 16.0 and later. + #[cfg(feature = "clang_16_0")] + pub fn clang_CXXMethod_isCopyAssignmentOperator(cursor: CXCursor) -> c_uint; /// Only available on `libclang` 3.9 and later. #[cfg(feature = "clang_3_9")] pub fn clang_CXXMethod_isDefaulted(cursor: CXCursor) -> c_uint; + /// Only available on `libclang` 16.0 and later. + #[cfg(feature = "clang_16_0")] + pub fn clang_CXXMethod_isDeleted(cursor: CXCursor) -> c_uint; + /// Only available on `libclang` 16.0 and later. + #[cfg(feature = "clang_16_0")] + pub fn clang_CXXMethod_isMoveAssignmentOperator(cursor: CXCursor) -> c_uint; pub fn clang_CXXMethod_isPureVirtual(cursor: CXCursor) -> c_uint; pub fn clang_CXXMethod_isStatic(cursor: CXCursor) -> c_uint; pub fn clang_CXXMethod_isVirtual(cursor: CXCursor) -> c_uint; + /// Only available on `libclang` 17.0 and later. + #[cfg(feature = "clang_17_0")] + pub fn clang_CXXMethod_isExplicit(cursor: CXCursor) -> c_uint; /// Only available on `libclang` 6.0 and later. #[cfg(feature = "clang_6_0")] pub fn clang_CXXRecord_isAbstract(cursor: CXCursor) -> c_uint; @@ -1992,6 +2133,9 @@ link! { pub fn clang_constructUSR_ObjCProtocol(protocol: *const c_char) -> CXString; pub fn clang_createCXCursorSet() -> CXCursorSet; pub fn clang_createIndex(exclude: c_int, display: c_int) -> CXIndex; + /// Only available on `libclang` 17.0 and later. + #[cfg(feature = "clang_17_0")] + pub fn clang_createIndexWithOptions(options: CXIndexOptions) -> CXIndex; pub fn clang_createTranslationUnit(index: CXIndex, file: *const c_char) -> CXTranslationUnit; pub fn clang_createTranslationUnit2(index: CXIndex, file: *const c_char, tu: *mut CXTranslationUnit) -> CXErrorCode; pub fn clang_createTranslationUnitFromSourceFile(index: CXIndex, file: *const c_char, n_arguments: c_int, arguments: *const *const c_char, n_unsaved: c_uint, unsaved: *mut CXUnsavedFile) -> CXTranslationUnit; @@ -2036,6 +2180,9 @@ link! { pub fn clang_getArgType(type_: CXType, index: c_uint) -> CXType; pub fn clang_getArrayElementType(type_: CXType) -> CXType; pub fn clang_getArraySize(type_: CXType) -> c_longlong; + /// Only available on `libclang` 17.0 and later. + #[cfg(feature = "clang_17_0")] + pub fn clang_getBinaryOperatorKindSpelling(kind: CXBinaryOperatorKind) -> CXString; pub fn clang_getCString(string: CXString) -> *const c_char; pub fn clang_getCXTUResourceUsage(tu: CXTranslationUnit) -> CXTUResourceUsage; pub fn clang_getCXXAccessSpecifier(cursor: CXCursor) -> CX_CXXAccessSpecifier; @@ -2060,6 +2207,9 @@ link! { pub fn clang_getCompletionPriority(string: CXCompletionString) -> c_uint; pub fn clang_getCursor(tu: CXTranslationUnit, location: CXSourceLocation) -> CXCursor; pub fn clang_getCursorAvailability(cursor: CXCursor) -> CXAvailabilityKind; + /// Only available on `libclang` 17.0 and later. + #[cfg(feature = "clang_17_0")] + pub fn clang_getCursorBinaryOperatorKind(cursor: CXCursor) -> CXBinaryOperatorKind; pub fn clang_getCursorCompletionString(cursor: CXCursor) -> CXCompletionString; pub fn clang_getCursorDefinition(cursor: CXCursor) -> CXCursor; pub fn clang_getCursorDisplayName(cursor: CXCursor) -> CXString; @@ -2089,6 +2239,9 @@ link! { #[cfg(feature = "clang_6_0")] pub fn clang_getCursorTLSKind(cursor: CXCursor) -> CXTLSKind; pub fn clang_getCursorType(cursor: CXCursor) -> CXType; + /// Only available on `libclang` 17.0 and later. + #[cfg(feature = "clang_17_0")] + pub fn clang_getCursorUnaryOperatorKind(cursor: CXCursor) -> CXUnaryOperatorKind; pub fn clang_getCursorUSR(cursor: CXCursor) -> CXString; /// Only available on `libclang` 3.8 and later. #[cfg(feature = "clang_3_8")] @@ -2134,6 +2287,9 @@ link! { pub fn clang_getLocation(tu: CXTranslationUnit, file: CXFile, line: c_uint, column: c_uint) -> CXSourceLocation; pub fn clang_getLocationForOffset(tu: CXTranslationUnit, file: CXFile, offset: c_uint) -> CXSourceLocation; pub fn clang_getModuleForFile(tu: CXTranslationUnit, file: CXFile) -> CXModule; + /// Only available on `libclang` 16.0 and later. + #[cfg(feature = "clang_16_0")] + pub fn clang_getNonReferenceType(type_: CXType) -> CXType; pub fn clang_getNullCursor() -> CXCursor; pub fn clang_getNullLocation() -> CXSourceLocation; pub fn clang_getNullRange() -> CXSourceRange; @@ -2168,12 +2324,12 @@ link! { /// Only available on `libclang` 5.0 and later. #[cfg(feature = "clang_5_0")] pub fn clang_getTranslationUnitTargetInfo(tu: CXTranslationUnit) -> CXTargetInfo; + /// Only available on `libclang` 17.0 and later. + #[cfg(feature = "clang_17_0")] + pub fn clang_getUnaryOperatorKindSpelling(kind: CXUnaryOperatorKind) -> CXString; /// Only available on `libclang` 16.0 and later. #[cfg(feature = "clang_16_0")] pub fn clang_getUnqualifiedType(type_: CXType) -> CXType; - /// Only available on `libclang` 16.0 and later. - #[cfg(feature = "clang_16_0")] - pub fn clang_getNonReferenceType(type_: CXType) -> CXType; pub fn clang_getTypeDeclaration(type_: CXType) -> CXCursor; pub fn clang_getTypeKindSpelling(type_: CXTypeKind) -> CXString; pub fn clang_getTypeSpelling(type_: CXType) -> CXString; diff --git a/src/link.rs b/src/link.rs index c3b0830..79b3d49 100644 --- a/src/link.rs +++ b/src/link.rs @@ -40,6 +40,7 @@ macro_rules! link { )+ ) => ( use std::cell::{RefCell}; + use std::fmt; use std::sync::{Arc}; use std::path::{Path, PathBuf}; @@ -58,6 +59,33 @@ macro_rules! link { V7_0 = 70, V8_0 = 80, V9_0 = 90, + V11_0 = 110, + V12_0 = 120, + V16_0 = 160, + V17_0 = 170, + } + + impl fmt::Display for Version { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use Version::*; + match self { + V3_5 => write!(f, "3.5.x"), + V3_6 => write!(f, "3.6.x"), + V3_7 => write!(f, "3.7.x"), + V3_8 => write!(f, "3.8.x"), + V3_9 => write!(f, "3.9.x"), + V4_0 => write!(f, "4.0.x"), + V5_0 => write!(f, "5.0.x"), + V6_0 => write!(f, "6.0.x"), + V7_0 => write!(f, "7.0.x"), + V8_0 => write!(f, "8.0.x"), + V9_0 => write!(f, "9.0.x - 10.0.x"), + V11_0 => write!(f, "11.0.x"), + V12_0 => write!(f, "12.0.x - 15.0.x"), + V16_0 => write!(f, "16.0.x"), + V17_0 => write!(f, "17.0.x or later"), + } + } } /// The set of functions loaded dynamically. @@ -104,6 +132,10 @@ macro_rules! link { } unsafe { + check!(b"clang_CXXMethod_isExplicit", V17_0); + check!(b"clang_CXXMethod_isCopyAssignmentOperator", V16_0); + check!(b"clang_Cursor_getVarDeclInitializer", V12_0); + check!(b"clang_Type_getValueType", V11_0); check!(b"clang_Cursor_isAnonymousRecordDecl", V9_0); check!(b"clang_Cursor_getObjCPropertyGetterName", V8_0); check!(b"clang_File_tryGetRealPathName", V7_0); @@ -142,13 +174,31 @@ macro_rules! link { #[cfg_attr(feature="cargo-clippy", allow(clippy::too_many_arguments))] $(#[doc=$doc] #[cfg($cfg)])* pub unsafe fn $name($($pname: $pty), *) $(-> $ret)* { - let f = with_library(|l| { - l.functions.$name.expect(concat!( - "`libclang` function not loaded: `", - stringify!($name), - "`. This crate requires that `libclang` 3.9 or later be installed on your ", - "system. For more information on how to accomplish this, see here: ", - "https://rust-lang.github.io/rust-bindgen/requirements.html#installing-clang-39")) + let f = with_library(|library| { + if let Some(function) = library.functions.$name { + function + } else { + panic!( + r#" +A `libclang` function was called that is not supported by the loaded `libclang` instance. + + called function = `{0}` + loaded `libclang` instance = {1} + +This crate only supports `libclang` 3.5 and later. +The minimum `libclang` requirement for this particular function can be found here: +https://docs.rs/clang-sys/latest/clang_sys/{0}/index.html + +Instructions for installing `libclang` can be found here: +https://rust-lang.github.io/rust-bindgen/requirements.html +"#, + stringify!($name), + library + .version() + .map(|v| format!("{}", v)) + .unwrap_or_else(|| "unsupported version".into()), + ); + } }).expect("a `libclang` shared library is not loaded on this thread"); f($($pname), *) } @@ -175,7 +225,9 @@ macro_rules! link { /// * a `libclang` shared library could not be found /// * the `libclang` shared library could not be opened pub fn load_manually() -> Result<SharedLibrary, String> { + #[allow(dead_code)] mod build { + include!(concat!(env!("OUT_DIR"), "/macros.rs")); pub mod common { include!(concat!(env!("OUT_DIR"), "/common.rs")); } pub mod dynamic { include!(concat!(env!("OUT_DIR"), "/dynamic.rs")); } } diff --git a/src/support.rs b/src/support.rs index 2b27cc0..20005ba 100644 --- a/src/support.rs +++ b/src/support.rs @@ -59,7 +59,7 @@ impl Clang { pub fn find(path: Option<&Path>, args: &[String]) -> Option<Clang> { if let Ok(path) = env::var("CLANG_PATH") { let p = Path::new(&path); - if p.is_file() && is_executable(&p).unwrap_or(false) { + if p.is_file() && is_executable(p).unwrap_or(false) { return Some(Clang::new(p, args)); } } @@ -184,7 +184,7 @@ fn run(executable: &str, arguments: &[&str]) -> Result<(String, String), String> /// Runs `clang`, returning the `stdout` and `stderr` output. fn run_clang(path: &Path, arguments: &[&str]) -> (String, String) { - run(&path.to_string_lossy().into_owned(), arguments).unwrap() + run(&path.to_string_lossy(), arguments).unwrap() } /// Runs `llvm-config`, returning the `stdout` output if successful. @@ -197,7 +197,7 @@ fn run_llvm_config(arguments: &[&str]) -> Result<String, String> { fn parse_version_number(number: &str) -> Option<c_int> { number .chars() - .take_while(|c| c.is_digit(10)) + .take_while(|c| c.is_ascii_digit()) .collect::<String>() .parse() .ok() diff --git a/tests/build.rs b/tests/build.rs new file mode 100644 index 0000000..669c561 --- /dev/null +++ b/tests/build.rs @@ -0,0 +1,281 @@ +#![allow(dead_code)] + +extern crate glob; +extern crate serial_test; +extern crate tempfile; + +use std::collections::HashMap; +use std::env; +use std::fs; +use std::path::PathBuf; +use std::sync::Arc; +use std::sync::Mutex; + +use serial_test::serial; +use tempfile::TempDir; + +#[macro_use] +#[path = "../build/macros.rs"] +mod macros; + +#[path = "../build/common.rs"] +mod common; +#[path = "../build/dynamic.rs"] +mod dynamic; +#[path = "../build/static.rs"] +mod r#static; + +#[derive(Debug, Default)] +struct RunCommandMock { + invocations: Vec<(String, String, Vec<String>)>, + responses: HashMap<Vec<String>, String>, +} + +#[derive(Debug)] +struct Env { + os: String, + pointer_width: String, + env: Option<String>, + vars: HashMap<String, (Option<String>, Option<String>)>, + cwd: PathBuf, + tmp: TempDir, + files: Vec<String>, + commands: Arc<Mutex<RunCommandMock>>, +} + +impl Env { + fn new(os: &str, pointer_width: &str) -> Self { + Env { + os: os.into(), + pointer_width: pointer_width.into(), + env: None, + vars: HashMap::new(), + cwd: env::current_dir().unwrap(), + tmp: tempfile::Builder::new().prefix("clang_sys_test").tempdir().unwrap(), + files: vec![], + commands: Default::default(), + } + .var("CLANG_PATH", None) + .var("LD_LIBRARY_PATH", None) + .var("LIBCLANG_PATH", None) + .var("LIBCLANG_STATIC_PATH", None) + .var("LLVM_CONFIG_PATH", None) + .var("PATH", None) + } + + fn env(mut self, env: &str) -> Self { + self.env = Some(env.into()); + self + } + + fn var(mut self, name: &str, value: Option<&str>) -> Self { + let previous = env::var(name).ok(); + self.vars.insert(name.into(), (value.map(|v| v.into()), previous)); + self + } + + fn dir(mut self, path: &str) -> Self { + self.files.push(path.into()); + let path = self.tmp.path().join(path); + fs::create_dir_all(path).unwrap(); + self + } + + fn file(mut self, path: &str, contents: &[u8]) -> Self { + self.files.push(path.into()); + let path = self.tmp.path().join(path); + fs::create_dir_all(path.parent().unwrap()).unwrap(); + fs::write(self.tmp.path().join(path), contents).unwrap(); + self + } + + fn dll(self, path: &str, pointer_width: &str) -> Self { + // PE header. + let mut contents = [0; 64]; + contents[0x3C..0x3C + 4].copy_from_slice(&i32::to_le_bytes(10)); + contents[10..14].copy_from_slice(&[b'P', b'E', 0, 0]); + let magic = if pointer_width == "64" { 523 } else { 267 }; + contents[34..36].copy_from_slice(&u16::to_le_bytes(magic)); + + self.file(path, &contents) + } + + fn so(self, path: &str, pointer_width: &str) -> Self { + // ELF header. + let class = if pointer_width == "64" { 2 } else { 1 }; + let contents = [127, 69, 76, 70, class]; + + self.file(path, &contents) + } + + fn command(self, command: &str, args: &[&str], response: &str) -> Self { + let command = command.to_string(); + let args = args.iter().map(|a| a.to_string()).collect::<Vec<_>>(); + + let mut key = vec![command]; + key.extend(args); + self.commands.lock().unwrap().responses.insert(key, response.into()); + + self + } + + fn enable(self) -> Self { + env::set_var("_CLANG_SYS_TEST", "yep"); + env::set_var("_CLANG_SYS_TEST_OS", &self.os); + env::set_var("_CLANG_SYS_TEST_POINTER_WIDTH", &self.pointer_width); + if let Some(env) = &self.env { + env::set_var("_CLANG_SYS_TEST_ENV", env); + } + + for (name, (value, _)) in &self.vars { + if let Some(value) = value { + env::set_var(name, value); + } else { + env::remove_var(name); + } + } + + env::set_current_dir(&self.tmp).unwrap(); + + let commands = self.commands.clone(); + let mock = &mut *common::RUN_COMMAND_MOCK.lock().unwrap(); + *mock = Some(Box::new(move |command, path, args| { + let command = command.to_string(); + let path = path.to_string(); + let args = args.iter().map(|a| a.to_string()).collect::<Vec<_>>(); + + let mut commands = commands.lock().unwrap(); + commands.invocations.push((command.clone(), path, args.clone())); + + let mut key = vec![command]; + key.extend(args); + commands.responses.get(&key).cloned() + })); + + self + } +} + +impl Drop for Env { + fn drop(&mut self) { + env::remove_var("_CLANG_SYS_TEST"); + env::remove_var("_CLANG_SYS_TEST_OS"); + env::remove_var("_CLANG_SYS_TEST_POINTER_WIDTH"); + env::remove_var("_CLANG_SYS_TEST_ENV"); + + for (name, (_, previous)) in &self.vars { + if let Some(previous) = previous { + env::set_var(name, previous); + } else { + env::remove_var(name); + } + } + + if let Err(error) = env::set_current_dir(&self.cwd) { + println!("Failed to reset working directory: {:?}", error); + } + } +} + +//================================================ +// Dynamic +//================================================ + +// Linux ----------------------------------------- + +#[test] +#[serial] +fn test_linux_directory_preference() { + let _env = Env::new("linux", "64") + .so("usr/lib/libclang.so.1", "64") + .so("usr/local/lib/libclang.so.1", "64") + .enable(); + + assert_eq!( + dynamic::find(true), + Ok(("usr/local/lib".into(), "libclang.so.1".into())), + ); +} + +#[test] +#[serial] +fn test_linux_version_preference() { + let _env = Env::new("linux", "64") + .so("usr/lib/libclang-3.so", "64") + .so("usr/lib/libclang-3.5.so", "64") + .so("usr/lib/libclang-3.5.0.so", "64") + .enable(); + + assert_eq!( + dynamic::find(true), + Ok(("usr/lib".into(), "libclang-3.5.0.so".into())), + ); +} + +#[test] +#[serial] +fn test_linux_directory_and_version_preference() { + let _env = Env::new("linux", "64") + .so("usr/local/llvm/lib/libclang-3.so", "64") + .so("usr/local/lib/libclang-3.5.so", "64") + .so("usr/lib/libclang-3.5.0.so", "64") + .enable(); + + assert_eq!( + dynamic::find(true), + Ok(("usr/lib".into(), "libclang-3.5.0.so".into())), + ); +} + +// Windows --------------------------------------- + +#[cfg(target_os = "windows")] +#[test] +#[serial] +fn test_windows_bin_sibling() { + let _env = Env::new("windows", "64") + .dir("Program Files\\LLVM\\lib") + .dll("Program Files\\LLVM\\bin\\libclang.dll", "64") + .enable(); + + assert_eq!( + dynamic::find(true), + Ok(("Program Files\\LLVM\\bin".into(), "libclang.dll".into())), + ); +} + +#[cfg(target_os = "windows")] +#[test] +#[serial] +fn test_windows_mingw_gnu() { + let _env = Env::new("windows", "64") + .env("gnu") + .dir("MSYS\\MinGW\\lib") + .dll("MSYS\\MinGW\\bin\\clang.dll", "64") + .dir("Program Files\\LLVM\\lib") + .dll("Program Files\\LLVM\\bin\\libclang.dll", "64") + .enable(); + + assert_eq!( + dynamic::find(true), + Ok(("MSYS\\MinGW\\bin".into(), "clang.dll".into())), + ); +} + +#[cfg(target_os = "windows")] +#[test] +#[serial] +fn test_windows_mingw_msvc() { + let _env = Env::new("windows", "64") + .env("msvc") + .dir("MSYS\\MinGW\\lib") + .dll("MSYS\\MinGW\\bin\\clang.dll", "64") + .dir("Program Files\\LLVM\\lib") + .dll("Program Files\\LLVM\\bin\\libclang.dll", "64") + .enable(); + + assert_eq!( + dynamic::find(true), + Ok(("Program Files\\LLVM\\bin".into(), "libclang.dll".into())), + ); +} |