use std::{
env,
ffi::OsString,
fs,
io,
io::{ Error, ErrorKind },
path::{ Path, PathBuf },
process::{ exit, Command },
};
#[cfg(target_family = "unix")]
use std::{
os::unix::ffi::OsStrExt,
ffi::OsStr,
};
#[cfg(target_family = "windows")]
use std::os::windows::ffi::OsStringExt;
#[allow(dead_code)]
struct InstallationPaths {
r_home: PathBuf,
include: PathBuf,
library: PathBuf,
}
#[allow(dead_code)]
#[derive(Debug)]
struct RVersionInfo {
major: String,
minor: String,
patch: String,
devel: String,
}
#[cfg(target_family = "unix")]
fn byte_array_to_os_string(bytes: &[u8]) -> OsString {
let os_str = OsStr::from_bytes(bytes);
os_str.to_os_string()
}
#[cfg(target_family = "windows")]
fn wide_from_console_string(bytes: &[u8]) -> Vec<u16> {
assert!(bytes.len() < std::i32::MAX as usize);
let mut wide;
let mut len;
unsafe {
let cp = kernel32::GetConsoleCP();
len = kernel32::MultiByteToWideChar(cp, 0, bytes.as_ptr() as *const i8, bytes.len() as i32, std::ptr::null_mut(), 0);
wide = Vec::with_capacity(len as usize);
len = kernel32::MultiByteToWideChar(cp, 0, bytes.as_ptr() as *const i8, bytes.len() as i32, wide.as_mut_ptr(), len);
wide.set_len(len as usize);
}
wide
}
#[cfg(target_family = "windows")]
fn byte_array_to_os_string(bytes: &[u8]) -> OsString {
let wide = wide_from_console_string(bytes);
OsString::from_wide(&wide)
}
fn probe_r_paths() -> io::Result<InstallationPaths> {
let r_home = match env::var_os("R_HOME") {
Some(s) => PathBuf::from(s),
_ => {
let rout = Command::new("R")
.args(&[
"-s",
"-e",
r#"cat(normalizePath(R.home()))"#
])
.output()?;
let rout = byte_array_to_os_string(&rout.stdout);
if !rout.is_empty() {
PathBuf::from(rout)
} else {
return Err(Error::new(ErrorKind::Other, "Cannot find R home."));
}
}
};
let pkg_target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
let library = if cfg!(target_os = "windows") {
if pkg_target_arch == "x86_64" {
Path::new(&r_home)
.join("bin")
.join("x64")
} else if pkg_target_arch == "x86" {
Path::new(&r_home)
.join("bin")
.join("i386")
} else {
panic!("Unknown architecture")
}
} else {
Path::new(&r_home).join("lib")
};
let include = match env::var_os("R_INCLUDE_DIR") {
Some(s) => PathBuf::from(s),
_ => {
let r_binary = if cfg!(target_os = "windows") {
Path::new(&library)
.join("R.exe")
} else {
Path::new(&r_home)
.join("bin")
.join("R")
};
let out = Command::new(&r_binary)
.args(&[
"-s",
"-e",
r#"cat(normalizePath(R.home('include')))"#
])
.output()?;
if !out.stderr.is_empty() {
println!("> {}",
byte_array_to_os_string(&out.stderr)
.as_os_str()
.to_string_lossy()
);
}
let rout = byte_array_to_os_string(&out.stdout);
if !rout.is_empty() {
PathBuf::from(rout)
} else {
return Err(Error::new(ErrorKind::Other, "Cannot find R include."));
}
}
};
Ok(InstallationPaths {
r_home,
include,
library,
})
}
fn get_r_version_strings(r_paths: &InstallationPaths) -> io::Result<RVersionInfo> {
let r_binary = if cfg!(target_os = "windows") {
Path::new(&r_paths.library)
.join("R.exe")
} else {
Path::new(&r_paths.r_home)
.join("bin")
.join("R")
};
let out = Command::new(&r_binary)
.args(&[
"-s",
"-e",
r#"v <- strsplit(R.version$minor, '.', fixed = TRUE)[[1]];devel <- isTRUE(grepl('devel', R.version$status, fixed = TRUE));cat(R.version$major, v[1], paste0(v[2:length(v)], collapse = '.'), devel, sep = '\n')"#
])
.output()?;
let out = byte_array_to_os_string(&out.stdout)
.as_os_str()
.to_string_lossy()
.into_owned();
let mut lines = out.lines();
let major = match lines.next() {
Some(line) => line.to_string(),
_ => return Err(Error::new(ErrorKind::Other, "Cannot find R major version")),
};
let minor = match lines.next() {
Some(line) => line.to_string(),
_ => return Err(Error::new(ErrorKind::Other, "Cannot find R minor version")),
};
let patch = match lines.next() {
Some(line) => line.to_string(),
_ => return Err(Error::new(ErrorKind::Other, "Cannot find R patch level")),
};
let devel = match lines.next() {
Some(line) => if line == "TRUE" {
"-devel".to_string()
} else {
"".to_string()
},
_ => return Err(Error::new(ErrorKind::Other, "Cannot find R development status")),
};
Ok(RVersionInfo {
major,
minor,
patch,
devel,
})
}
#[cfg(feature = "use-bindgen")]
fn generate_bindings(r_paths: &InstallationPaths) {
let mut bindgen_builder = bindgen::Builder::default()
.blacklist_item("FP_NAN")
.blacklist_item("FP_INFINITE")
.blacklist_item("FP_ZERO")
.blacklist_item("FP_SUBNORMAL")
.blacklist_item("FP_NORMAL")
.header("wrapper.h")
.parse_callbacks(Box::new(bindgen::CargoCallbacks));
let target = env::var("TARGET").expect("Could not get the target triple");
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
println!("Generating bindings for target: {}, os: {}, architecture: {}", target, target_os, target_arch);
bindgen_builder = bindgen_builder.clang_args(&[
format!("-I{}", r_paths.include.display()),
format!("--target={}", target)
]);
if let Some(alt_include) = env::var_os("LIBRSYS_LIBCLANG_INCLUDE_PATH") {
bindgen_builder = bindgen_builder.clang_arg(
format!("-I{}", PathBuf::from(alt_include).display()),
);
}
if target_os == "windows" && target_arch == "x86" {
bindgen_builder =
bindgen_builder
.blacklist_item("max_align_t")
.blacklist_item("__mingw_ldbl_type_t");
}
let bindings = bindgen_builder
.generate()
.expect("Unable to generate bindings");
let version_matcher = regex::Regex::new(r"pub const R_VERSION ?: ?u32 = (\d+)").unwrap();
if let Some(version) = version_matcher.captures(bindings.to_string().as_str()) {
let version = version.get(1).unwrap().as_str().parse::<u32>().unwrap();
println!("cargo:r_version={}", version);
} else {
panic!("failed to find R_VERSION");
}
let out_path = PathBuf::from(env::var_os("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings to default output path!");
if let Some(alt_target) = env::var_os("LIBRSYS_BINDINGS_OUTPUT_PATH") {
let out_path = PathBuf::from(alt_target);
if !out_path.exists() {
fs::create_dir(&out_path)
.expect(&format!("Couldn't create output directory for bindings: {}", out_path.display()));
}
let version_info = get_r_version_strings(r_paths).expect("Could not obtain R version");
let out_file = out_path.join(
format!(
"bindings-{}-{}-R{}.{}{}.rs",
target_os, target_arch, version_info.major, version_info.minor, version_info.devel
)
);
bindings
.write_to_file(&out_file)
.expect(&format!("Couldn't write bindings: {}", out_file.display()));
}
}
#[allow(dead_code)]
fn retrieve_prebuild_bindings(r_paths: &InstallationPaths) {
let version_info = get_r_version_strings(r_paths).expect("Could not obtain R version");
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
let bindings_path = PathBuf::from(
env::var_os("LIBRSYS_BINDINGS_PATH")
.unwrap_or(OsString::from("bindings"))
);
let bindings_file_full = PathBuf::from(
format!(
"bindings-{}-{}-R{}.{}{}.rs",
target_os, target_arch, version_info.major, version_info.minor, version_info.devel
)
);
let bindings_file_novers = PathBuf::from(
format!("bindings-{}-{}.rs", target_os, target_arch)
);
let mut from = bindings_path.join(bindings_file_full);
if !from.exists() {
from = bindings_path.join(bindings_file_novers);
if !from.exists() {
panic!(
format!(
"Cannot find libR-sys bindings file for R {}.{}.{}{} on {} in {}. Consider compiling with default features enabled.",
version_info.major, version_info.minor, version_info.patch, version_info.devel, target_os, bindings_path.display()
)
)
} else {
println!(
"Warning: using generic {}-{} libR-sys bindings. These may not work for R {}.{}.{}{}.",
target_os, target_arch, version_info.major, version_info.minor, version_info.patch, version_info.devel
);
}
}
fs::copy(
&from,
PathBuf::from(env::var_os("OUT_DIR").unwrap())
.join("bindings.rs")
).expect("No precomputed bindings available!");
println!("cargo:rerun-if-changed={}", from.display());
}
fn main() {
let r_paths = probe_r_paths();
let r_paths = match r_paths {
Ok(result) => result,
Err(error) => {
println!("Problem locating local R install: {:?}", error);
exit(1);
}
};
println!("cargo:rustc-env=R_HOME={}", r_paths.r_home.display());
println!("cargo:r_home={}", r_paths.r_home.display()); println!("cargo:rustc-link-search={}", r_paths.library.display());
println!("cargo:rustc-link-lib=dylib=R");
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=wrapper.h");
#[cfg(feature = "use-bindgen")]
generate_bindings(&r_paths);
#[cfg(not(feature = "use-bindgen"))]
retrieve_prebuild_bindings(&r_paths);
}