Snap for 12134224 from 0ca0dd0b60195fe4d259c50eaa99957164eea065 to simpleperf-release

Change-Id: I95b8a60f501b7840fffbca3455a393c441b89153
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 @@
       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 @@
         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 @@
       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
diff --git a/.gitignore b/.gitignore
index 09b05f5..654b48a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
 .docs/
+bindgen-test/target/
 target/
 
 Cargo.lock
diff --git a/Android.bp b/Android.bp
index 9be8df1..9c386fe 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,5 +1,7 @@
 // This file is generated by cargo_embargo.
-// Do not modify this file as changes will be overridden on upgrade.
+// Do not modify this file after the first "rust_*" or "genrule" module
+// because the changes will be overridden on upgrade.
+// Content before the first "rust_*" or "genrule" module is preserved.
 
 package {
     default_applicable_licenses: ["external_rust_crates_clang-sys_license"],
@@ -25,6 +27,7 @@
     out: [
         "common.rs",
         "dynamic.rs",
+        "macros.rs",
     ],
 }
 
@@ -32,11 +35,9 @@
     name: "libclang_sys",
     crate_name: "clang_sys",
     cargo_env_compat: true,
-    cargo_pkg_version: "1.4.0",
-    srcs: [
-        "src/lib.rs",
-        ":copy_clang-sys_build_out",
-    ],
+    cargo_pkg_version: "1.7.0",
+    crate_root: "src/lib.rs",
+    srcs: [":copy_clang-sys_build_out"],
     edition: "2015",
     features: [
         "clang_10_0",
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
diff --git a/Cargo.toml b/Cargo.toml
index a0d9cbb..49e15ce 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,7 +11,7 @@
 
 [package]
 name = "clang-sys"
-version = "1.4.0"
+version = "1.7.0"
 authors = ["Kyle Mayes <[email protected]>"]
 build = "build.rs"
 links = "clang"
@@ -23,7 +23,7 @@
 
 [package.metadata.docs.rs]
 features = [
-    "clang_16_0",
+    "clang_17_0",
     "runtime",
 ]
 
@@ -35,9 +35,18 @@
 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_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 <[email protected]>"]
 
-version = "1.4.0"
+version = "1.7.0"
 
 readme = "README.md"
 license = "Apache-2.0"
@@ -36,6 +36,7 @@
 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 @@
 
 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"]
diff --git a/METADATA b/METADATA
index ac49596..839736a 100644
--- a/METADATA
+++ b/METADATA
@@ -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"
   }
 }
diff --git a/README.md b/README.md
index ed5f52e..40252db 100644
--- a/README.md
+++ b/README.md
@@ -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`.
 
diff --git a/build.rs b/build.rs
index bfaaff6..6b15126 100644
--- a/build.rs
+++ b/build.rs
@@ -19,6 +19,10 @@
 
 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 @@
     }
 
     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 @@
     }
 }
 
+#[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 @@
 //================================================
 // 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",
+///
+/// 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 other listed
+    // system-wide directories.
+    ("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\\**\\bin",
-    // LLVM + Clang can be installed using Scoop (https://scoop.sh).
-    // Other Windows package managers install LLVM + Clang to previously listed
-    // system-wide directories.
-    "C:\\Users\\*\\scoop\\apps\\llvm\\current\\bin",
+    ("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 @@
     // 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 @@
 
     // 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 @@
     }
 
     // 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 @@
 
 /// 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 @@
         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 @@
         }
     }
 
-    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 glob::Pattern;
 
-use common;
+use super::common;
 
 //================================================
 // Searching
@@ -79,7 +79,7 @@
 /// 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 @@
     }
 }
 
+#[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 @@
 //================================================
 // 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",
+///
+/// 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 other listed
+    // system-wide directories.
+    ("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\\**\\bin",
-    // LLVM + Clang can be installed using Scoop (https://scoop.sh).
-    // Other Windows package managers install LLVM + Clang to previously listed
-    // system-wide directories.
-    "C:\\Users\\*\\scoop\\apps\\llvm\\current\\bin",
+    ("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 @@
     // 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 @@
 
     // 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 @@
     }
 
     // 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 @@
 
 /// 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 @@
         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 @@
         }
     }
 
-    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)
+        }
+    };
+}
diff --git a/src/lib.rs b/src/lib.rs
index c101ce6..31e2e96 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -48,6 +48,20 @@
 
 /// 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! {
+    /// 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! {
+    #[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 @@
         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 @@
         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 @@
         /// 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! {
+    /// 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 @@
     }
 }
 
+/// 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 @@
 
 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 @@
     #[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 @@
     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 @@
     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 @@
     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 @@
     #[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 @@
     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 @@
     /// 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 @@
         )+
     ) => (
         use std::cell::{RefCell};
+        use std::fmt;
         use std::sync::{Arc};
         use std::path::{Path, PathBuf};
 
@@ -58,6 +59,33 @@
             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 @@
                 }
 
                 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 @@
             #[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 @@
         /// * 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 @@
     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 @@
 
 /// 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 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())),
+    );
+}