summaryrefslogtreecommitdiffstats
path: root/third_party/rust/clang-sys/build
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/clang-sys/build
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/clang-sys/build')
-rw-r--r--third_party/rust/clang-sys/build/common.rs355
-rw-r--r--third_party/rust/clang-sys/build/dynamic.rs257
-rw-r--r--third_party/rust/clang-sys/build/macros.rs38
-rw-r--r--third_party/rust/clang-sys/build/static.rs140
4 files changed, 790 insertions, 0 deletions
diff --git a/third_party/rust/clang-sys/build/common.rs b/third_party/rust/clang-sys/build/common.rs
new file mode 100644
index 0000000000..bd94447c9e
--- /dev/null
+++ b/third_party/rust/clang-sys/build/common.rs
@@ -0,0 +1,355 @@
+// SPDX-License-Identifier: Apache-2.0
+
+extern crate glob;
+
+use std::cell::RefCell;
+use std::collections::HashMap;
+use std::env;
+use std::path::{Path, PathBuf};
+use std::process::Command;
+
+use glob::{MatchOptions, Pattern};
+
+//================================================
+// Commands
+//================================================
+
+thread_local! {
+ /// The errors encountered by the build script while executing commands.
+ static COMMAND_ERRORS: RefCell<HashMap<String, Vec<String>>> = RefCell::default();
+}
+
+/// Adds an error encountered by the build script while executing a command.
+fn add_command_error(name: &str, path: &str, arguments: &[&str], message: String) {
+ COMMAND_ERRORS.with(|e| {
+ e.borrow_mut()
+ .entry(name.into())
+ .or_insert_with(Vec::new)
+ .push(format!(
+ "couldn't execute `{} {}` (path={}) ({})",
+ name,
+ arguments.join(" "),
+ path,
+ message,
+ ))
+ });
+}
+
+/// A struct that prints the errors encountered by the build script while
+/// executing commands when dropped (unless explictly discarded).
+///
+/// This is handy because we only want to print these errors when the build
+/// script fails to link to an instance of `libclang`. For example, if
+/// `llvm-config` couldn't be executed but an instance of `libclang` was found
+/// anyway we don't want to pollute the build output with irrelevant errors.
+#[derive(Default)]
+pub struct CommandErrorPrinter {
+ discard: bool,
+}
+
+impl CommandErrorPrinter {
+ pub fn discard(mut self) {
+ self.discard = true;
+ }
+}
+
+impl Drop for CommandErrorPrinter {
+ fn drop(&mut self) {
+ if self.discard {
+ return;
+ }
+
+ let errors = COMMAND_ERRORS.with(|e| e.borrow().clone());
+
+ if let Some(errors) = errors.get("llvm-config") {
+ println!(
+ "cargo:warning=could not execute `llvm-config` one or more \
+ times, if the LLVM_CONFIG_PATH environment variable is set to \
+ a full path to valid `llvm-config` executable it will be used \
+ to try to find an instance of `libclang` on your system: {}",
+ errors
+ .iter()
+ .map(|e| format!("\"{}\"", e))
+ .collect::<Vec<_>>()
+ .join("\n "),
+ )
+ }
+
+ if let Some(errors) = errors.get("xcode-select") {
+ println!(
+ "cargo:warning=could not execute `xcode-select` one or more \
+ times, if a valid instance of this executable is on your PATH \
+ it will be used to try to find an instance of `libclang` on \
+ your system: {}",
+ errors
+ .iter()
+ .map(|e| format!("\"{}\"", e))
+ .collect::<Vec<_>>()
+ .join("\n "),
+ )
+ }
+ }
+}
+
+#[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) => {
+ let message = format!("error: {}", error);
+ add_command_error(name, path, arguments, message);
+ return None;
+ }
+ };
+
+ if output.status.success() {
+ Some(String::from_utf8_lossy(&output.stdout).into_owned())
+ } else {
+ let message = format!("exit code: {}", output.status);
+ add_command_error(name, path, arguments, message);
+ None
+ }
+}
+
+/// Executes the `llvm-config` command and returns the `stdout` output if the
+/// command was successfully executed (errors are added to `COMMAND_ERRORS`).
+pub fn run_llvm_config(arguments: &[&str]) -> Option<String> {
+ let path = env::var("LLVM_CONFIG_PATH").unwrap_or_else(|_| "llvm-config".into());
+ run_command("llvm-config", &path, arguments)
+}
+
+/// Executes the `xcode-select` command and returns the `stdout` output if the
+/// command was successfully executed (errors are added to `COMMAND_ERRORS`).
+pub fn run_xcode_select(arguments: &[&str]) -> Option<String> {
+ run_command("xcode-select", "xcode-select", arguments)
+}
+
+//================================================
+// 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/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/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/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.
+///
+/// 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\\**\\lib", true),
+];
+
+/// `libclang` directory patterns for illumos
+const DIRECTORIES_ILLUMOS: &[&str] = &[
+ "/opt/ooce/llvm-*/lib",
+ "/opt/ooce/clang-*/lib",
+];
+
+//================================================
+// Searching
+//================================================
+
+/// Finds the files in a directory that match one or more filename glob patterns
+/// and returns the paths to and filenames of those files.
+fn search_directory(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, String)> {
+ // Escape the specified directory in case it contains characters that have
+ // special meaning in glob patterns (e.g., `[` or `]`).
+ let directory = Pattern::escape(directory.to_str().unwrap());
+ let directory = Path::new(&directory);
+
+ // Join the escaped directory to the filename glob patterns to obtain
+ // complete glob patterns for the files being searched for.
+ let paths = filenames
+ .iter()
+ .map(|f| directory.join(f).to_str().unwrap().to_owned());
+
+ // Prevent wildcards from matching path separators to ensure that the search
+ // is limited to the specified directory.
+ let mut options = MatchOptions::new();
+ options.require_literal_separator = true;
+
+ paths
+ .map(|p| glob::glob_with(&p, options))
+ .filter_map(Result::ok)
+ .flatten()
+ .filter_map(|p| {
+ let path = p.ok()?;
+ let filename = path.file_name()?.to_str().unwrap();
+
+ // The `libclang_shared` library has been renamed to `libclang-cpp`
+ // in Clang 10. This can cause instances of this library (e.g.,
+ // `libclang-cpp.so.10`) to be matched by patterns looking for
+ // instances of `libclang`.
+ if filename.contains("-cpp.") {
+ return None;
+ }
+
+ Some((directory.to_owned(), filename.into()))
+ })
+ .collect::<Vec<_>>()
+}
+
+/// Finds the files in a directory (and any relevant sibling directories) that
+/// match one or more filename glob patterns and returns the paths to and
+/// filenames of those files.
+fn search_directories(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, String)> {
+ let mut results = search_directory(directory, filenames);
+
+ // On Windows, `libclang.dll` is usually found in the LLVM `bin` directory
+ // while `libclang.lib` is usually found in the LLVM `lib` directory. To
+ // 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 target_os!("windows") && directory.ends_with("lib") {
+ let sibling = directory.parent().unwrap().join("bin");
+ results.extend(search_directory(&sibling, filenames).into_iter());
+ }
+
+ results
+}
+
+/// Finds the `libclang` static or dynamic libraries matching one or more
+/// filename glob patterns and returns the paths to and filenames of those files.
+pub fn search_libclang_directories(filenames: &[String], variable: &str) -> Vec<(PathBuf, String)> {
+ // Search only the path indicated by the relevant environment variable
+ // (e.g., `LIBCLANG_PATH`) if it is set.
+ if let Ok(path) = env::var(variable).map(|d| Path::new(&d).to_path_buf()) {
+ // Check if the path is a matching file.
+ if let Some(parent) = path.parent() {
+ let filename = path.file_name().unwrap().to_str().unwrap();
+ let libraries = search_directories(parent, filenames);
+ if libraries.iter().any(|(_, f)| f == filename) {
+ return vec![(parent.into(), filename.into())];
+ }
+ }
+
+ // Check if the path is directory containing a matching file.
+ return search_directories(&path, filenames);
+ }
+
+ let mut found = vec![];
+
+ // Search the `bin` and `lib` directories in the directory returned by
+ // `llvm-config --prefix`.
+ if let Some(output) = run_llvm_config(&["--prefix"]) {
+ let directory = Path::new(output.lines().next().unwrap()).to_path_buf();
+ found.extend(search_directories(&directory.join("bin"), filenames));
+ found.extend(search_directories(&directory.join("lib"), filenames));
+ found.extend(search_directories(&directory.join("lib64"), filenames));
+ }
+
+ // Search the toolchain directory in the directory returned by
+ // `xcode-select --print-path`.
+ 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");
+ found.extend(search_directories(&directory, filenames));
+ }
+ }
+
+ // Search the directories in the `LD_LIBRARY_PATH` environment variable.
+ if let Ok(path) = env::var("LD_LIBRARY_PATH") {
+ for directory in env::split_paths(&path) {
+ found.extend(search_directories(&directory, filenames));
+ }
+ }
+
+ // Determine the `libclang` directory patterns.
+ 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
+ .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.into()
+ };
+
+ // 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() {
+ 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));
+ }
+ }
+ }
+
+ found
+}
diff --git a/third_party/rust/clang-sys/build/dynamic.rs b/third_party/rust/clang-sys/build/dynamic.rs
new file mode 100644
index 0000000000..8039364299
--- /dev/null
+++ b/third_party/rust/clang-sys/build/dynamic.rs
@@ -0,0 +1,257 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use std::env;
+use std::fs::File;
+use std::io::{self, Error, ErrorKind, Read, Seek, SeekFrom};
+use std::path::{Path, PathBuf};
+
+use super::common;
+
+//================================================
+// Validation
+//================================================
+
+/// Extracts the ELF class from the ELF header in a shared library.
+fn parse_elf_header(path: &Path) -> io::Result<u8> {
+ let mut file = File::open(path)?;
+ let mut buffer = [0; 5];
+ file.read_exact(&mut buffer)?;
+ if buffer[..4] == [127, 69, 76, 70] {
+ Ok(buffer[4])
+ } else {
+ Err(Error::new(ErrorKind::InvalidData, "invalid ELF header"))
+ }
+}
+
+/// Extracts the magic number from the PE header in a shared library.
+fn parse_pe_header(path: &Path) -> io::Result<u16> {
+ let mut file = File::open(path)?;
+
+ // Extract the header offset.
+ let mut buffer = [0; 4];
+ let start = SeekFrom::Start(0x3C);
+ file.seek(start)?;
+ file.read_exact(&mut buffer)?;
+ let offset = i32::from_le_bytes(buffer);
+
+ // Check the validity of the header.
+ file.seek(SeekFrom::Start(offset as u64))?;
+ file.read_exact(&mut buffer)?;
+ if buffer != [80, 69, 0, 0] {
+ return Err(Error::new(ErrorKind::InvalidData, "invalid PE header"));
+ }
+
+ // Extract the magic number.
+ let mut buffer = [0; 2];
+ file.seek(SeekFrom::Current(20))?;
+ file.read_exact(&mut buffer)?;
+ Ok(u16::from_le_bytes(buffer))
+}
+
+/// Checks that a `libclang` shared library matches the target platform.
+fn validate_library(path: &Path) -> Result<(), String> {
+ if target_os!("linux") || target_os!("freebsd") {
+ let class = parse_elf_header(path).map_err(|e| e.to_string())?;
+
+ if target_pointer_width!("32") && class != 1 {
+ return Err("invalid ELF class (64-bit)".into());
+ }
+
+ if target_pointer_width!("64") && class != 2 {
+ return Err("invalid ELF class (32-bit)".into());
+ }
+
+ Ok(())
+ } else if target_os!("windows") {
+ let magic = parse_pe_header(path).map_err(|e| e.to_string())?;
+
+ if target_pointer_width!("32") && magic != 267 {
+ return Err("invalid DLL (64-bit)".into());
+ }
+
+ if target_pointer_width!("64") && magic != 523 {
+ return Err("invalid DLL (32-bit)".into());
+ }
+
+ Ok(())
+ } else {
+ Ok(())
+ }
+}
+
+//================================================
+// Searching
+//================================================
+
+/// Extracts the version components in a `libclang` shared library filename.
+fn parse_version(filename: &str) -> Vec<u32> {
+ let version = if let Some(version) = filename.strip_prefix("libclang.so.") {
+ version
+ } else if filename.starts_with("libclang-") {
+ &filename[9..filename.len() - 3]
+ } else {
+ return vec![];
+ };
+
+ version.split('.').map(|s| s.parse().unwrap_or(0)).collect()
+}
+
+/// Finds `libclang` shared libraries and returns the paths to, filenames of,
+/// and versions of those shared libraries.
+fn search_libclang_directories(runtime: bool) -> Result<Vec<(PathBuf, String, Vec<u32>)>, String> {
+ let mut files = vec![format!(
+ "{}clang{}",
+ env::consts::DLL_PREFIX,
+ env::consts::DLL_SUFFIX
+ )];
+
+ 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());
+
+ // Some Linux distributions don't create a `libclang.so` symlink and
+ // don't have versioned files as described above, so we need to look for
+ // suffix versioned files (e.g., `libclang.so.1`). However, `ld` cannot
+ // link to these files, so this will only be included when linking at
+ // runtime.
+ if runtime {
+ files.push("libclang.so.*".into());
+ files.push("libclang-*.so.*".into());
+ }
+ }
+
+ 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 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());
+ }
+
+ // Find and validate `libclang` shared libraries and collect the versions.
+ let mut valid = vec![];
+ let mut invalid = vec![];
+ for (directory, filename) in common::search_libclang_directories(&files, "LIBCLANG_PATH") {
+ let path = directory.join(&filename);
+ match validate_library(&path) {
+ Ok(()) => {
+ let version = parse_version(&filename);
+ valid.push((directory, filename, version))
+ }
+ Err(message) => invalid.push(format!("({}: {})", path.display(), message)),
+ }
+ }
+
+ if !valid.is_empty() {
+ return Ok(valid);
+ }
+
+ let message = format!(
+ "couldn't find any valid shared libraries matching: [{}], set the \
+ `LIBCLANG_PATH` environment variable to a path where one of these files \
+ can be found (invalid: [{}])",
+ files
+ .iter()
+ .map(|f| format!("'{}'", f))
+ .collect::<Vec<_>>()
+ .join(", "),
+ invalid.join(", "),
+ );
+
+ Err(message)
+}
+
+/// Finds the "best" `libclang` shared library and returns the directory and
+/// filename of that library.
+pub fn find(runtime: bool) -> Result<(PathBuf, String), String> {
+ search_libclang_directories(runtime)?
+ .iter()
+ // We want to find the `libclang` shared library with the highest
+ // version number, hence `max_by_key` below.
+ //
+ // However, in the case where there are multiple such `libclang` shared
+ // libraries, we want to use the order in which they appeared in the
+ // list returned by `search_libclang_directories` as a tiebreaker since
+ // that function returns `libclang` shared libraries in descending order
+ // of preference by how they were found.
+ //
+ // `max_by_key`, perhaps surprisingly, returns the *last* item with the
+ // maximum key rather than the first which results in the opposite of
+ // the tiebreaking behavior we want. This is easily fixed by reversing
+ // the list first.
+ .rev()
+ .max_by_key(|f| &f.2)
+ .cloned()
+ .map(|(path, filename, _)| (path, filename))
+ .ok_or_else(|| "unreachable".into())
+}
+
+//================================================
+// Linking
+//================================================
+
+/// Finds and links to a `libclang` shared library.
+#[cfg(not(feature = "runtime"))]
+pub fn link() {
+ let cep = common::CommandErrorPrinter::default();
+
+ use std::fs;
+
+ let (directory, filename) = find(false).unwrap();
+ println!("cargo:rustc-link-search={}", directory.display());
+
+ if cfg!(all(target_os = "windows", target_env = "msvc")) {
+ // Find the `libclang` stub static library required for the MSVC
+ // toolchain.
+ let lib = if !directory.ends_with("bin") {
+ directory
+ } else {
+ directory.parent().unwrap().join("lib")
+ };
+
+ if lib.join("libclang.lib").exists() {
+ println!("cargo:rustc-link-search={}", lib.display());
+ } else if lib.join("libclang.dll.a").exists() {
+ // MSYS and MinGW use `libclang.dll.a` instead of `libclang.lib`.
+ // It is linkable with the MSVC linker, but Rust doesn't recognize
+ // the `.a` suffix, so we need to copy it with a different name.
+ //
+ // FIXME: Maybe we can just hardlink or symlink it?
+ let out = env::var("OUT_DIR").unwrap();
+ fs::copy(
+ lib.join("libclang.dll.a"),
+ Path::new(&out).join("libclang.lib"),
+ )
+ .unwrap();
+ println!("cargo:rustc-link-search=native={}", out);
+ } else {
+ panic!(
+ "using '{}', so 'libclang.lib' or 'libclang.dll.a' must be \
+ available in {}",
+ filename,
+ lib.display(),
+ );
+ }
+
+ println!("cargo:rustc-link-lib=dylib=libclang");
+ } else {
+ let name = filename.trim_start_matches("lib");
+
+ // Strip extensions and trailing version numbers (e.g., the `.so.7.0` in
+ // `libclang.so.7.0`).
+ let name = match name.find(".dylib").or_else(|| name.find(".so")) {
+ Some(index) => &name[0..index],
+ None => name,
+ };
+
+ println!("cargo:rustc-link-lib=dylib={}", name);
+ }
+
+ cep.discard();
+}
diff --git a/third_party/rust/clang-sys/build/macros.rs b/third_party/rust/clang-sys/build/macros.rs
new file mode 100644
index 0000000000..b70aa6897e
--- /dev/null
+++ b/third_party/rust/clang-sys/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/third_party/rust/clang-sys/build/static.rs b/third_party/rust/clang-sys/build/static.rs
new file mode 100644
index 0000000000..6d86b9a8d8
--- /dev/null
+++ b/third_party/rust/clang-sys/build/static.rs
@@ -0,0 +1,140 @@
+// SPDX-License-Identifier: Apache-2.0
+
+extern crate glob;
+
+use std::path::{Path, PathBuf};
+
+use glob::Pattern;
+
+use super::common;
+
+//================================================
+// Searching
+//================================================
+
+/// Clang static libraries required to link to `libclang` 3.5 and later.
+const CLANG_LIBRARIES: &[&str] = &[
+ "clang",
+ "clangAST",
+ "clangAnalysis",
+ "clangBasic",
+ "clangDriver",
+ "clangEdit",
+ "clangFrontend",
+ "clangIndex",
+ "clangLex",
+ "clangParse",
+ "clangRewrite",
+ "clangSema",
+ "clangSerialization",
+];
+
+/// Gets the name of an LLVM or Clang static library from a path.
+fn get_library_name(path: &Path) -> Option<String> {
+ path.file_stem().map(|p| {
+ let string = p.to_string_lossy();
+ if let Some(name) = string.strip_prefix("lib") {
+ name.to_owned()
+ } else {
+ string.to_string()
+ }
+ })
+}
+
+/// Gets the LLVM static libraries required to link to `libclang`.
+fn get_llvm_libraries() -> Vec<String> {
+ common::run_llvm_config(&["--libs"])
+ .unwrap()
+ .split_whitespace()
+ .filter_map(|p| {
+ // Depending on the version of `llvm-config` in use, listed
+ // libraries may be in one of two forms, a full path to the library
+ // or simply prefixed with `-l`.
+ if let Some(path) = p.strip_prefix("-l") {
+ Some(path.into())
+ } else {
+ get_library_name(Path::new(p))
+ }
+ })
+ .collect()
+}
+
+/// Gets the Clang static libraries required to link to `libclang`.
+fn get_clang_libraries<P: AsRef<Path>>(directory: P) -> Vec<String> {
+ // Escape the directory in case it contains characters that have special
+ // meaning in glob patterns (e.g., `[` or `]`).
+ let directory = Pattern::escape(directory.as_ref().to_str().unwrap());
+ let directory = Path::new(&directory);
+
+ let pattern = directory.join("libclang*.a").to_str().unwrap().to_owned();
+ if let Ok(libraries) = glob::glob(&pattern) {
+ libraries
+ .filter_map(|l| l.ok().and_then(|l| get_library_name(&l)))
+ .collect()
+ } else {
+ CLANG_LIBRARIES.iter().map(|l| (*l).to_string()).collect()
+ }
+}
+
+/// Finds a directory containing LLVM and Clang static libraries and returns the
+/// path to that directory.
+fn find() -> PathBuf {
+ let name = if target_os!("windows") {
+ "libclang.lib"
+ } else {
+ "libclang.a"
+ };
+
+ let files = common::search_libclang_directories(&[name.into()], "LIBCLANG_STATIC_PATH");
+ if let Some((directory, _)) = files.into_iter().next() {
+ directory
+ } else {
+ panic!("could not find any static libraries");
+ }
+}
+
+//================================================
+// Linking
+//================================================
+
+/// Finds and links to `libclang` static libraries.
+pub fn link() {
+ let cep = common::CommandErrorPrinter::default();
+
+ let directory = find();
+
+ // Specify required Clang static libraries.
+ println!("cargo:rustc-link-search=native={}", directory.display());
+ for library in get_clang_libraries(directory) {
+ println!("cargo:rustc-link-lib=static={}", library);
+ }
+
+ // Determine the shared mode used by LLVM.
+ let mode = common::run_llvm_config(&["--shared-mode"]).map(|m| m.trim().to_owned());
+ let prefix = if mode.map_or(false, |m| m == "static") {
+ "static="
+ } else {
+ ""
+ };
+
+ // Specify required LLVM static libraries.
+ println!(
+ "cargo:rustc-link-search=native={}",
+ common::run_llvm_config(&["--libdir"]).unwrap().trim_end()
+ );
+ for library in get_llvm_libraries() {
+ println!("cargo:rustc-link-lib={}{}", prefix, library);
+ }
+
+ // Specify required system libraries.
+ // MSVC doesn't need this, as it tracks dependencies inside `.lib` files.
+ if cfg!(target_os = "freebsd") {
+ println!("cargo:rustc-flags=-l ffi -l ncursesw -l c++ -l z");
+ } else if cfg!(any(target_os = "haiku", target_os = "linux")) {
+ println!("cargo:rustc-flags=-l ffi -l ncursesw -l stdc++ -l z");
+ } else if cfg!(target_os = "macos") {
+ println!("cargo:rustc-flags=-l ffi -l ncurses -l c++ -l z");
+ }
+
+ cep.discard();
+}