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};
thread_local! {
static COMMAND_ERRORS: RefCell<HashMap<String, Vec<String>>> = RefCell::default();
}
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,
))
});
}
#[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 "),
)
}
}
}
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
}
}
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)
}
pub fn run_xcode_select(arguments: &[&str]) -> Option<String> {
run_command("xcode-select", "xcode-select", arguments)
}
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",
];
const DIRECTORIES_LINUX: &[&str] = &[
"/usr/lib*",
"/usr/lib*/*",
"/usr/lib*/*/*",
"/usr/local/lib*",
"/usr/local/lib*/*",
"/usr/local/lib*/*/*",
"/usr/local/llvm*/lib*",
];
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",
];
const DIRECTORIES_WINDOWS: &[&str] = &[
"C:\\LLVM\\lib",
"C:\\Program Files*\\LLVM\\lib",
"C:\\MSYS*\\MinGW*\\lib",
"C:\\Program Files*\\Microsoft Visual Studio\\*\\BuildTools\\VC\\Tools\\Llvm\\**\\bin",
"C:\\Users\\*\\scoop\\apps\\llvm\\current\\bin",
];
fn search_directory(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, String)> {
let directory = Pattern::escape(directory.to_str().unwrap());
let directory = Path::new(&directory);
let paths = filenames
.iter()
.map(|f| directory.join(f).to_str().unwrap().to_owned());
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();
if filename.contains("-cpp.") {
return None;
}
Some((directory.to_owned(), filename.into()))
})
.collect::<Vec<_>>()
}
fn search_directories(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, String)> {
let mut results = search_directory(directory, filenames);
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
}
pub fn search_libclang_directories(filenames: &[String], variable: &str) -> Vec<(PathBuf, String)> {
if let Ok(path) = env::var(variable).map(|d| Path::new(&d).to_path_buf()) {
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())];
}
}
return search_directories(&path, filenames);
}
let mut found = vec![];
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));
}
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));
}
}
if let Ok(path) = env::var("LD_LIBRARY_PATH") {
for directory in env::split_paths(&path) {
found.extend(search_directories(&directory, filenames));
}
}
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 {
&[]
};
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
}