#![allow(unused_attributes)]
extern crate glob;
use std::env;
use std::fs::{self, File};
use std::io::{Read, Seek, SeekFrom};
use std::path::{Path, PathBuf};
use std::process::{Command};
use glob::{MatchOptions};
fn find_version(file: &str) -> Option<&str> {
if file.starts_with("libclang.so.") {
Some(&file[12..])
} else if file.starts_with("libclang-") {
Some(&file[9..])
} else {
None
}
}
fn parse_version(file: &Path) -> Vec<u32> {
let file = file.file_name().and_then(|f| f.to_str()).unwrap_or("");
let version = find_version(file).unwrap_or("");
version.split('.').map(|s| s.parse::<u32>().unwrap_or(0)).collect()
}
fn contains(directory: &Path, files: &[String]) -> Option<PathBuf> {
let patterns = files.iter().filter_map(|f| directory.join(f).to_str().map(ToOwned::to_owned));
let mut options = MatchOptions::new();
options.require_literal_separator = true;
let mut matches = patterns.flat_map(|p| {
if let Ok(paths) = glob::glob_with(&p, &options) {
paths.filter_map(Result::ok).collect()
} else {
vec![]
}
}).collect::<Vec<_>>();
matches.sort_by_key(|m| parse_version(m));
matches.pop()
}
fn run(command: &str, arguments: &[&str]) -> Option<String> {
Command::new(command).args(arguments).output().map(|o| {
String::from_utf8_lossy(&o.stdout).into_owned()
}).ok()
}
fn run_llvm_config(arguments: &[&str]) -> Result<String, String> {
match run(&env::var("LLVM_CONFIG_PATH").unwrap_or_else(|_| "llvm-config".into()), arguments) {
Some(output) => Ok(output),
None => {
let message = format!(
"couldn't execute `llvm-config {}`, set the LLVM_CONFIG_PATH environment variable \
to a path to a valid `llvm-config` executable",
arguments.join(" "),
);
Err(message)
},
}
}
const SEARCH_LINUX: &[&str] = &[
"/usr/lib*",
"/usr/lib*/*",
"/usr/lib*/*/*",
"/usr/local/lib*",
"/usr/local/lib*/*",
"/usr/local/lib*/*/*",
"/usr/local/llvm*/lib",
];
const SEARCH_OSX: &[&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 SEARCH_WINDOWS: &[&str] = &[
"C:\\LLVM\\lib",
"C:\\Program Files*\\LLVM\\lib",
"C:\\MSYS*\\MinGW*\\lib",
];
fn parse_elf_header(file: &PathBuf) -> Result<u8, String> {
let mut file = try!(File::open(file).map_err(|e| e.to_string()));
let mut elf = [0; 5];
try!(file.read_exact(&mut elf).map_err(|e| e.to_string()));
if elf[..4] == [127, 69, 76, 70] {
Ok(elf[4])
} else {
Err("invalid ELF header".into())
}
}
fn parse_pe_header(file: &PathBuf) -> Result<u16, String> {
let mut file = try!(File::open(file).map_err(|e| e.to_string()));
let mut pe = [0; 4];
try!(file.seek(SeekFrom::Start(0x3C)).map_err(|e| e.to_string()));
try!(file.read_exact(&mut pe).map_err(|e| e.to_string()));
let offset = i32::from(pe[0]) + (i32::from(pe[1]) << 8) + (i32::from(pe[2]) << 16) + (i32::from(pe[3]) << 24);
try!(file.seek(SeekFrom::Start(offset as u64)).map_err(|e| e.to_string()));
try!(file.read_exact(&mut pe).map_err(|e| e.to_string()));
if pe != [80, 69, 0, 0] {
return Err("invalid PE header".into());
}
try!(file.seek(SeekFrom::Current(20)).map_err(|e| e.to_string()));
try!(file.read_exact(&mut pe).map_err(|e| e.to_string()));
Ok(u16::from(pe[0]) + (u16::from(pe[1]) << 8))
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum Library {
Dynamic,
Static,
}
impl Library {
fn check(&self, file: &PathBuf) -> Result<(), String> {
if cfg!(any(target_os="freebsd", target_os="linux")) {
if *self == Library::Static {
return Ok(());
}
let class = try!(parse_elf_header(file));
if cfg!(target_pointer_width="32") && class != 1 {
return Err("invalid ELF class (64-bit)".into());
}
if cfg!(target_pointer_width="64") && class != 2 {
return Err("invalid ELF class (32-bit)".into());
}
Ok(())
} else if cfg!(target_os="windows") {
if *self == Library::Static {
return Ok(());
}
let magic = try!(parse_pe_header(file));
if cfg!(target_pointer_width="32") && magic != 267 {
return Err("invalid DLL (64-bit)".into());
}
if cfg!(target_pointer_width="64") && magic != 523 {
return Err("invalid DLL (32-bit)".into());
}
Ok(())
} else {
Ok(())
}
}
}
fn find(library: Library, files: &[String], env: &str) -> Result<PathBuf, String> {
let mut skipped = vec![];
macro_rules! try_file {
($file:expr) => ({
match library.check(&$file) {
Ok(_) => return Ok($file),
Err(message) => skipped.push(format!("({}: {})", $file.display(), message)),
}
});
}
macro_rules! search_directory {
($directory:ident) => {
if let Some(file) = contains(&$directory, files) {
try_file!(file);
}
if cfg!(target_os="windows") && $directory.ends_with("lib") {
let sibling = $directory.parent().unwrap().join("bin");
if let Some(file) = contains(&sibling, files) {
try_file!(file);
}
}
}
}
if let Ok(directory) = env::var(env).map(|d| Path::new(&d).to_path_buf()) {
search_directory!(directory);
}
if let Ok(output) = run_llvm_config(&["--prefix"]) {
let directory = Path::new(output.lines().next().unwrap()).to_path_buf();
let bin = directory.join("bin");
if let Some(file) = contains(&bin, files) {
try_file!(file);
}
let lib = directory.join("lib");
if let Some(file) = contains(&lib, files) {
try_file!(file);
}
}
if let Ok(path) = env::var("LD_LIBRARY_PATH") {
for directory in path.split(':').map(Path::new) {
search_directory!(directory);
}
}
let search = if cfg!(any(target_os="freebsd", target_os="linux")) {
SEARCH_LINUX
} else if cfg!(target_os="macos") {
SEARCH_OSX
} else if cfg!(target_os="windows") {
SEARCH_WINDOWS
} else {
&[]
};
for pattern in search {
let mut options = MatchOptions::new();
options.case_sensitive = false;
options.require_literal_separator = true;
if let Ok(paths) = glob::glob_with(pattern, &options) {
for path in paths.filter_map(Result::ok).filter(|p| p.is_dir()) {
search_directory!(path);
}
}
}
let message = format!(
"couldn't find any of [{}], set the {} environment variable to a path where one of these \
files can be found (skipped: [{}])",
files.iter().map(|f| format!("'{}'", f)).collect::<Vec<_>>().join(", "),
env,
skipped.join(", "),
);
Err(message)
}
pub fn find_shared_library() -> Result<PathBuf, String> {
let mut files = vec![format!("{}clang{}", env::consts::DLL_PREFIX, env::consts::DLL_SUFFIX)];
if cfg!(any(target_os="freebsd", target_os="linux", target_os="openbsd")) {
files.push("libclang.so.*".into());
files.push("libclang-*.so".into());
}
if cfg!(target_os="windows") {
files.push("libclang.dll".into());
}
find(Library::Dynamic, &files, "LIBCLANG_PATH")
}
fn get_library_name(path: &Path) -> Option<String> {
path.file_stem().map(|p| {
let string = p.to_string_lossy();
if string.starts_with("lib") {
string[3..].to_owned()
} else {
string.to_string()
}
})
}
fn get_llvm_libraries() -> Vec<String> {
run_llvm_config(&["--libs"]).unwrap().split_whitespace().filter_map(|p| {
if p.starts_with("-l") {
Some(p[2..].into())
} else {
get_library_name(Path::new(p))
}
}).collect()
}
const CLANG_LIBRARIES: &[&str] = &[
"clang",
"clangAST",
"clangAnalysis",
"clangBasic",
"clangDriver",
"clangEdit",
"clangFrontend",
"clangIndex",
"clangLex",
"clangParse",
"clangRewrite",
"clangSema",
"clangSerialization",
];
fn get_clang_libraries<P: AsRef<Path>>(directory: P) -> Vec<String> {
let pattern = directory.as_ref().join("libclang*.a").to_string_lossy().to_string();
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()
}
}
#[cfg_attr(feature="runtime", allow(dead_code))]
fn link_static() {
let name = if cfg!(target_os="windows") { "libclang.lib" } else { "libclang.a" };
let file = find(Library::Static, &[name.into()], "LIBCLANG_STATIC_PATH").unwrap();
let directory = file.parent().unwrap();
println!("cargo:rustc-link-search=native={}", directory.display());
for library in get_clang_libraries(directory) {
println!("cargo:rustc-link-lib=static={}", library);
}
let mode = run_llvm_config(&["--shared-mode"]).map(|m| m.trim().to_owned());
let prefix = if mode.ok().map_or(false, |m| m == "static") { "static=" } else { "" };
println!("cargo:rustc-link-search=native={}", run_llvm_config(&["--libdir"]).unwrap().trim_right());
for library in get_llvm_libraries() {
println!("cargo:rustc-link-lib={}{}", prefix, library);
}
if cfg!(target_os="freebsd") {
println!("cargo:rustc-flags=-l ffi -l ncursesw -l c++ -l z");
} else if cfg!(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");
}
}
#[cfg_attr(feature="runtime", allow(dead_code))]
fn link_dynamic() {
let file = find_shared_library().unwrap();
let directory = file.parent().unwrap();
println!("cargo:rustc-link-search={}", directory.display());
if cfg!(all(target_os="windows", target_env="msvc")) {
let libdir = if !directory.ends_with("bin") {
directory.to_owned()
} else {
directory.parent().unwrap().join("lib")
};
if libdir.join("libclang.lib").exists() {
println!("cargo:rustc-link-search={}", libdir.display());
} else if libdir.join("libclang.dll.a").exists() {
let out = env::var("OUT_DIR").unwrap();
fs::copy(libdir.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 {}",
file.display(),
libdir.display(),
);
}
println!("cargo:rustc-link-lib=dylib=libclang");
} else {
println!("cargo:rustc-link-lib=dylib=clang");
}
}
#[cfg_attr(feature="runtime", allow(dead_code))]
fn main() {
if cfg!(feature="runtime") {
if cfg!(feature="static") {
panic!("`runtime` and `static` features can't be combined");
}
} else if cfg!(feature="static") {
link_static();
} else {
link_dynamic();
}
if let Ok(output) = run_llvm_config(&["--includedir"]) {
let directory = Path::new(output.trim_right());
println!("cargo:include={}", directory.display());
}
}