extern crate cc;
#[macro_use]
extern crate lazy_static;
extern crate regex;
extern crate semver;
use regex::Regex;
use semver::Version;
use std::env;
use std::ffi::OsStr;
use std::io::{self, ErrorKind};
use std::path::{Path, PathBuf};
use std::process::Command;
lazy_static! {
static ref ENV_LLVM_PREFIX: String =
format!("LLVM_SYS_{}_PREFIX", env!("CARGO_PKG_VERSION_MAJOR"));
static ref ENV_IGNORE_BLACKLIST: String =
format!("LLVM_SYS_{}_IGNORE_BLACKLIST", env!("CARGO_PKG_VERSION_MAJOR"));
static ref ENV_STRICT_VERSIONING: String =
format!("LLVM_SYS_{}_STRICT_VERSIONING", env!("CARGO_PKG_VERSION_MAJOR"));
static ref ENV_NO_CLEAN_CFLAGS: String =
format!("LLVM_SYS_{}_NO_CLEAN_CFLAGS", env!("CARGO_PKG_VERSION_MAJOR"));
static ref ENV_USE_DEBUG_MSVCRT: String =
format!("LLVM_SYS_{}_USE_DEBUG_MSVCRT", env!("CARGO_PKG_VERSION_MAJOR"));
static ref ENV_FORCE_FFI: String =
format!("LLVM_SYS_{}_FFI_WORKAROUND", env!("CARGO_PKG_VERSION_MAJOR"));
}
lazy_static! {
static ref CRATE_VERSION: Version = {
let crate_version = Version::parse(env!("CARGO_PKG_VERSION"))
.expect("Crate version is somehow not valid semver");
Version {
major: crate_version.major / 10,
minor: crate_version.major % 10,
.. crate_version
}
};
static ref LLVM_CONFIG_PATH: Option<PathBuf> = {
if let Some(name) = locate_system_llvm_config() {
return Some(name.into());
} else {
println!("Didn't find usable system-wide LLVM.");
}
if let Some(path) = env::var_os(&*ENV_LLVM_PREFIX) {
for binary_name in llvm_config_binary_names() {
let mut pb: PathBuf = path.clone().into();
pb.push("bin");
pb.push(binary_name);
let ver = llvm_version(&pb)
.expect(&format!("Failed to execute {:?}", &pb));
if is_compatible_llvm(&ver) {
return Some(pb);
} else {
println!("LLVM binaries specified by {} are the wrong version.
(Found {}, need {}.)", *ENV_LLVM_PREFIX, ver, *CRATE_VERSION);
}
}
}
None
};
}
fn locate_system_llvm_config() -> Option<String> {
for binary_name in llvm_config_binary_names() {
match llvm_version(&binary_name) {
Ok(ref version) if is_compatible_llvm(version) => {
return Some(binary_name);
}
Ok(version) => {
println!(
"Found LLVM version {} on PATH, but need {}.",
version, *CRATE_VERSION
);
}
Err(ref e) if e.kind() == ErrorKind::NotFound => {
}
Err(e) => panic!("Failed to search PATH for llvm-config: {}", e),
}
}
None
}
fn llvm_config_binary_names() -> std::vec::IntoIter<String> {
let mut base_names = vec![
"llvm-config".into(),
format!("llvm-config-{}", CRATE_VERSION.major),
format!(
"llvm-config-{}.{}",
CRATE_VERSION.major, CRATE_VERSION.minor
),
format!("llvm-config{}{}", CRATE_VERSION.major, CRATE_VERSION.minor),
];
if cfg!(target_os = "windows") {
let mut exe_names = base_names.clone();
for name in exe_names.iter_mut() {
name.push_str(".exe");
}
base_names.extend(exe_names);
}
base_names.into_iter()
}
fn is_blacklisted_llvm(llvm_version: &Version) -> Option<&'static str> {
static BLACKLIST: &'static [(u64, u64, u64, &'static str)] = &[];
if let Some(x) = env::var_os(&*ENV_IGNORE_BLACKLIST) {
if &x == "YES" {
println!(
"cargo:warning=Ignoring blacklist entry for LLVM {}",
llvm_version
);
return None;
} else {
println!(
"cargo:warning={} is set but not exactly \"YES\"; blacklist is still honored.",
*ENV_IGNORE_BLACKLIST
);
}
}
for &(major, minor, patch, reason) in BLACKLIST.iter() {
let bad_version = Version {
major: major,
minor: minor,
patch: patch,
pre: vec![],
build: vec![],
};
if &bad_version == llvm_version {
return Some(reason);
}
}
None
}
fn is_compatible_llvm(llvm_version: &Version) -> bool {
if let Some(reason) = is_blacklisted_llvm(llvm_version) {
println!(
"Found LLVM {}, which is blacklisted: {}",
llvm_version, reason
);
return false;
}
let strict =
env::var_os(&*ENV_STRICT_VERSIONING).is_some() || cfg!(feature = "strict-versioning");
if strict {
llvm_version.major == CRATE_VERSION.major && llvm_version.minor == CRATE_VERSION.minor
} else {
llvm_version.major >= CRATE_VERSION.major
|| (llvm_version.major == CRATE_VERSION.major
&& llvm_version.minor >= CRATE_VERSION.minor)
}
}
fn llvm_config(arg: &str) -> String {
llvm_config_ex(&*LLVM_CONFIG_PATH.clone().unwrap(), arg)
.expect("Surprising failure from llvm-config")
}
fn llvm_config_ex<S: AsRef<OsStr>>(binary: S, arg: &str) -> io::Result<String> {
Command::new(binary)
.arg(arg)
.arg("--link-static") .output()
.map(|output| {
String::from_utf8(output.stdout).expect("Output from llvm-config was not valid UTF-8")
})
}
fn llvm_version<S: AsRef<OsStr>>(binary: S) -> io::Result<Version> {
let version_str = try!(llvm_config_ex(binary.as_ref(), "--version"));
let re = Regex::new(r"^(?P<major>\d+)\.(?P<minor>\d+)(?:\.(?P<patch>\d+))??").unwrap();
let c = re
.captures(&version_str)
.expect("Could not determine LLVM version from llvm-config.");
let s = match c.name("patch") {
None => format!("{}.0", &c[0]),
Some(_) => c[0].to_string(),
};
Ok(Version::parse(&s).unwrap())
}
fn get_system_libraries() -> Vec<String> {
llvm_config("--system-libs")
.split(&[' ', '\n'] as &[char])
.filter(|s| !s.is_empty())
.map(|flag| {
if cfg!(target_env = "msvc") {
assert!(flag.ends_with(".lib"));
&flag[..flag.len() - 4]
} else if cfg!(target_os = "macos") {
assert!(flag.starts_with("-l"));
if flag.ends_with(".tbd") && flag.starts_with("-llib") {
&flag[5..flag.len() - 4]
} else {
&flag[2..]
}
} else {
if flag.starts_with("-l") {
return flag[2..].to_owned();
}
let maybe_lib = Path::new(&flag);
if maybe_lib.is_file() {
maybe_lib
.parent()
.map(|p| println!("cargo:rustc-link-search={}", p.display()));
&maybe_lib.file_stem().unwrap().to_str().unwrap()[3..]
} else {
panic!("Unable to parse result of llvm-config --system-libs")
}
}
.to_owned()
})
.chain(get_system_libcpp().map(str::to_owned))
.collect::<Vec<String>>()
}
fn get_system_libcpp() -> Option<&'static str> {
if cfg!(target_env = "msvc") {
None
} else if cfg!(target_os = "macos") {
Some("c++")
} else if cfg!(target_os = "freebsd") {
Some("c++")
} else {
Some("stdc++")
}
}
fn get_link_libraries() -> Vec<String> {
llvm_config("--libnames")
.split(&[' ', '\n'] as &[char])
.filter(|s| !s.is_empty())
.map(|name| {
if cfg!(target_env = "msvc") {
assert!(name.ends_with(".lib"));
&name[..name.len() - 4]
} else {
assert!(name.starts_with("lib") && name.ends_with(".a"));
&name[3..name.len() - 2]
}
})
.map(str::to_owned)
.collect::<Vec<String>>()
}
fn get_llvm_cflags() -> String {
let output = llvm_config("--cflags");
let no_clean = env::var_os(&*ENV_NO_CLEAN_CFLAGS).is_some();
if no_clean || cfg!(target_env = "msvc") {
return output;
}
llvm_config("--cflags")
.split(&[' ', '\n'][..])
.filter(|word| !word.starts_with("-W"))
.collect::<Vec<_>>()
.join(" ")
}
fn is_llvm_debug() -> bool {
llvm_config("--build-mode").contains("Debug")
}
fn main() {
println!("cargo:rerun-if-env-changed={}", &*ENV_LLVM_PREFIX);
println!("cargo:rerun-if-env-changed={}", &*ENV_IGNORE_BLACKLIST);
println!("cargo:rerun-if-env-changed={}", &*ENV_STRICT_VERSIONING);
println!("cargo:rerun-if-env-changed={}", &*ENV_NO_CLEAN_CFLAGS);
println!("cargo:rerun-if-env-changed={}", &*ENV_USE_DEBUG_MSVCRT);
println!("cargo:rerun-if-env-changed={}", &*ENV_FORCE_FFI);
if LLVM_CONFIG_PATH.is_none() {
println!("cargo:rustc-cfg=LLVM_SYS_NOT_FOUND");
return;
}
if !cfg!(feature = "disable-alltargets-init") {
std::env::set_var("CFLAGS", get_llvm_cflags());
cc::Build::new()
.file("wrappers/target.c")
.compile("targetwrappers");
}
if cfg!(feature = "no-llvm-linking") {
return;
}
let libdir = llvm_config("--libdir");
println!(
"cargo:config_path={}",
LLVM_CONFIG_PATH.clone().unwrap().display()
); println!("cargo:libdir={}", libdir);
println!("cargo:rustc-link-search=native={}", libdir);
for name in get_link_libraries() {
println!("cargo:rustc-link-lib=static={}", name);
}
for name in get_system_libraries() {
println!("cargo:rustc-link-lib=dylib={}", name);
}
let use_debug_msvcrt = env::var_os(&*ENV_USE_DEBUG_MSVCRT).is_some();
if cfg!(target_env = "msvc") && (use_debug_msvcrt || is_llvm_debug()) {
println!("cargo:rustc-link-lib={}", "msvcrtd");
}
let force_ffi = env::var_os(&*ENV_FORCE_FFI).is_some();
if force_ffi {
println!("cargo:rustc-link-lib=dylib={}", "ffi");
}
}