diff options
Diffstat (limited to 'third_party/rust/clang-sys/build/common.rs')
-rw-r--r-- | third_party/rust/clang-sys/build/common.rs | 319 |
1 files changed, 319 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..c5e57c61b9 --- /dev/null +++ b/third_party/rust/clang-sys/build/common.rs @@ -0,0 +1,319 @@ +// 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 "),
+ )
+ }
+ }
+}
+
+/// 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> {
+ 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
+//================================================
+
+/// `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",
+];
+
+/// `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*",
+];
+
+/// `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",
+];
+
+/// `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",
+ // 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",
+];
+
+/// `libclang` directory patterns for illumos
+const DIRECTORIES_ILLUMOS: &[&str] = &[
+ "/opt/ooce/clang-*/lib",
+ "/opt/ooce/llvm-*/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 cfg!(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 cfg!(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 = 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") {
+ DIRECTORIES_WINDOWS
+ } else if cfg!(target_os = "illumos") {
+ DIRECTORIES_ILLUMOS
+ } else {
+ &[]
+ };
+
+ // 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() {
+ 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
+}
|