use std::{
env,
ffi::{OsStr, OsString},
io::{self, Error, ErrorKind},
path::{Path, PathBuf},
process::{exit, Command},
};
#[cfg(target_family = "unix")]
use std::os::unix::ffi::OsStrExt;
#[cfg(target_family = "windows")]
use std::os::windows::ffi::OsStringExt;
const ENVVAR_R_HOME: &str = "R_HOME";
#[derive(Debug)]
struct InstallationPaths {
r_home: PathBuf,
library: PathBuf,
}
#[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()
}
#[link(name = "kernel32")]
#[cfg(target_family = "windows")]
extern "system" {
#[link_name = "GetConsoleCP"]
fn get_console_code_page() -> u32;
#[link_name = "MultiByteToWideChar"]
fn multi_byte_to_wide_char(
CodePage: u32,
dwFlags: u32,
lpMultiByteStr: *const u8,
cbMultiByte: i32,
lpWideCharStr: *mut u16,
cchWideChar: i32,
) -> i32;
}
#[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 = get_console_code_page();
len = multi_byte_to_wide_char(
cp,
0,
bytes.as_ptr() as *const u8,
bytes.len() as i32,
std::ptr::null_mut(),
0,
);
wide = Vec::with_capacity(len as usize);
len = multi_byte_to_wide_char(
cp,
0,
bytes.as_ptr() as *const u8,
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 r_command<S: AsRef<OsStr>>(r_binary: S, script: &str) -> io::Result<OsString> {
let out = Command::new(r_binary)
.args(&["-s", "-e", script])
.output()?;
if !out.stderr.is_empty() {
println!(
"> {}",
byte_array_to_os_string(&out.stderr)
.as_os_str()
.to_string_lossy()
);
}
Ok(byte_array_to_os_string(&out.stdout))
}
fn get_r_home() -> io::Result<PathBuf> {
if let Some(r_home) = env::var_os(ENVVAR_R_HOME) {
return Ok(PathBuf::from(r_home));
}
let rout = r_command("R", r#"cat(normalizePath(R.home()))"#)?;
if !rout.is_empty() {
Ok(PathBuf::from(rout))
} else {
Err(Error::new(ErrorKind::Other, "Cannot find R home."))
}
}
fn get_r_library(r_home: &Path) -> PathBuf {
let pkg_target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
match (cfg!(windows), pkg_target_arch.as_str()) {
(true, "x86_64") => Path::new(r_home).join("bin").join("x64"),
(true, "x86") => Path::new(r_home).join("bin").join("i386"),
(true, _) => panic!("Unknown architecture"),
(false, _) => Path::new(r_home).join("lib"),
}
}
fn probe_r_paths() -> io::Result<InstallationPaths> {
let r_home = get_r_home()?;
let library = get_r_library(&r_home);
Ok(InstallationPaths { r_home, library })
}
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());
if let Ok(r_library) = r_paths.library.canonicalize() {
println!("cargo:rustc-link-search={}", r_library.display());
}
println!("cargo:rerun-if-changed=build.rs");
}