use anyhow::{anyhow, Result};
use std::env;
use std::ffi::OsStr;
use std::io;
use std::path::{Path, PathBuf};
use std::process::Command;
const ENV_LLVM_PREFIX: &str = "NYXSTONE_LLVM_PREFIX";
fn main() {
let headers = [
"nyxstone/include/nyxstone.h",
"nyxstone/src/ELFStreamerWrapper.h",
"nyxstone/src/ObjectWriterWrapper.h",
"nyxstone/src/Target/AArch64/MCTargetDesc/AArch64FixupKinds.h",
"nyxstone/src/Target/AArch64/MCTargetDesc/AArch64MCExpr.h",
"src/nyxstone_ffi.hpp",
];
let sources = [
"nyxstone/src/nyxstone.cpp",
"nyxstone/src/ObjectWriterWrapper.cpp",
"nyxstone/src/ELFStreamerWrapper.cpp",
"src/nyxstone_ffi.cpp",
];
println!("cargo:rerun-if-env-changed={}", ENV_LLVM_PREFIX);
if let Ok(path) = env::var(ENV_LLVM_PREFIX) {
println!("cargo:rerun-if-changed={}", path);
}
if std::env::var("DOCS_RS").is_ok() {
return;
}
let config = search_llvm_config();
if config.is_err() {
panic!("{} Please either install LLVM 15 with static libs into your PATH or supply the location via $NYXSTONE_LLVM_PREFIX", config.unwrap_err());
};
let config = config.unwrap();
let libdir = llvm_config(&config, ["--libdir"]);
println!("cargo:libdir={}", libdir);
println!("cargo:rustc-link-search=native={}", libdir);
let llvm_libs = get_link_libraries(&config);
for name in llvm_libs {
println!("cargo:rustc-link-lib=static={}", name);
}
let system_linking = if target_os_is("musl") { "static" } else { "dylib" };
for name in get_system_libraries(&config) {
println!("cargo:rustc-link-lib={}={}", system_linking, name);
}
let llvm_include_dir = llvm_config(&config, ["--includedir"]);
cxx_build::bridge("src/lib.rs")
.flag_if_supported("-std=c++17")
.include("nyxstone/include")
.include(llvm_include_dir.trim())
.files(sources)
.compile("nyxstone_wrap");
for file in headers.iter().chain(sources.iter()) {
println!("cargo:rerun-if-changed={}", file);
}
}
fn search_llvm_config() -> Result<PathBuf> {
let prefix = env::var(ENV_LLVM_PREFIX)
.map(|p| PathBuf::from(p).join("bin"))
.unwrap_or_else(|_| PathBuf::new());
let llvm_config = Path::new(&prefix).join("llvm-config");
let version = get_major_version(&llvm_config).map_err(|_| {
anyhow!(
"No llvm-config found in {}",
if prefix == PathBuf::new() {
"$PATH"
} else {
"$NYXSTONE_LLVM_PREFIX"
}
)
})?;
let can_link = can_link_static(&llvm_config);
if version != "15" {
return Err(anyhow!("LLVM major version is {}, must be 15.", version));
}
if !can_link {
return Err(anyhow!("LLVM install does not include static libraries.",));
}
Ok(llvm_config)
}
fn llvm_config_ex<I, S>(binary: &Path, args: I) -> anyhow::Result<String>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
Ok(Command::new(binary)
.args(args)
.arg("--link-static") .output()
.and_then(|output| {
if output.stdout.is_empty() {
Err(io::Error::new(
io::ErrorKind::NotFound,
"llvm-config returned empty output",
))
} else {
Ok(String::from_utf8(output.stdout).expect("Output from llvm-config was not valid UTF-8"))
}
})?)
}
fn llvm_config<I, S>(binary: &Path, args: I) -> String
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
llvm_config_ex(binary, args).expect("Surprising failure from llvm-config")
}
fn get_major_version(binary: &Path) -> Result<String> {
Ok(llvm_config_ex(binary, ["--version"])?
.split('.')
.next()
.ok_or(anyhow!("Unexpected llvm output"))?
.to_owned())
}
fn can_link_static(binary: &Path) -> bool {
llvm_config_ex(binary, ["--libs", "core", "mc", "all-targets"]).is_ok()
}
fn target_env_is(name: &str) -> bool {
match env::var_os("CARGO_CFG_TARGET_ENV") {
Some(s) => s == name,
None => false,
}
}
fn target_os_is(name: &str) -> bool {
match env::var_os("CARGO_CFG_TARGET_OS") {
Some(s) => s == name,
None => false,
}
}
fn target_dylib_extension() -> &'static str {
if target_os_is("macos") {
".dylib"
} else {
".so"
}
}
fn get_system_libcpp() -> Option<&'static str> {
if target_env_is("msvc") {
None
} else if target_os_is("macos") || target_os_is("freebsd") || target_env_is("musl") {
Some("c++")
} else {
Some("stdc++")
}
}
fn get_system_libraries(llvm_config_path: &Path) -> Vec<String> {
llvm_config(llvm_config_path, ["--system-libs"])
.split(&[' ', '\n'] as &[char])
.filter(|s| !s.is_empty())
.map(|flag| {
if target_env_is("msvc") {
flag.strip_suffix(".lib")
.unwrap_or_else(|| panic!("system library '{}' does not appear to be a MSVC library file", flag))
} else {
if let Some(flag) = flag.strip_prefix("-l") {
if target_os_is("macos") {
if let Some(flag) = flag.strip_prefix("lib").and_then(|flag| flag.strip_suffix(".tbd")) {
return flag;
}
}
return flag;
}
let maybe_lib = Path::new(flag);
if maybe_lib.is_file() {
println!("cargo:rustc-link-search={}", maybe_lib.parent().unwrap().display());
let soname = maybe_lib
.file_name()
.unwrap()
.to_str()
.expect("Shared library path must be a valid string");
let (stem, _rest) = soname
.rsplit_once(target_dylib_extension())
.expect("Shared library should be a .so file");
stem.strip_prefix("lib")
.unwrap_or_else(|| panic!("system library '{}' does not have a 'lib' prefix", soname))
} else {
panic!("Unable to parse result of llvm-config --system-libs: {}", flag)
}
}
})
.chain(get_system_libcpp())
.map(str::to_owned)
.collect()
}
fn get_link_libraries(llvm_config_path: &Path) -> Vec<String> {
fn get_link_libraries_impl(llvm_config_path: &Path) -> String {
llvm_config(llvm_config_path, ["--libnames", "core", "mc", "all-targets"])
}
extract_library(&get_link_libraries_impl(llvm_config_path))
}
fn extract_library(s: &str) -> Vec<String> {
s.split(&[' ', '\n'] as &[char])
.filter(|s| !s.is_empty())
.map(|name| {
{
if let Some(name) = name.strip_prefix("lib").and_then(|name| name.strip_suffix(".a")) {
name
} else if let Some(name) = name.strip_suffix(".lib") {
name
} else {
panic!("'{}' does not look like a static library name", name)
}
}
.to_string()
})
.collect::<Vec<String>>()
}