mod error;
mod util;
use std::process::Command;
use std::{fs, str};
use camino::{Utf8Path, Utf8PathBuf};
use regex::Regex;
use util::{out_dir, package, CommandStdoutExt, DYLIB_EXT};
pub use bindgen;
pub use error::*;
#[derive(Debug)]
pub struct Build {
cabal: Utf8PathBuf,
ghc_pkg: Utf8PathBuf,
rts_version: RTSVersion,
}
#[derive(Debug)]
pub struct Lib<'b> {
build: &'b Build,
path: Utf8PathBuf,
hs_deps: Vec<HSDep>,
}
#[derive(Debug, Clone, Copy)]
enum HSDep {
Ghc,
Base,
}
pub type BindgenBuilder = bindgen::Builder;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Clone, Copy)]
pub enum RTSVersion {
NonThreaded,
NonThreadedL,
NonThreadedDebug,
Threaded,
ThreadedL,
ThreadedDebug,
}
impl RTSVersion {
fn default() -> Self {
Self::NonThreaded
}
}
impl Build {
pub fn new() -> Result<Self> {
let cabal = util::which("cabal").map_err(Error::CabalError)?;
let ghc_pkg = util::which("ghc-pkg").map_err(Error::GHCPkgError)?;
Ok(Self {
cabal,
ghc_pkg,
rts_version: RTSVersion::default(),
})
}
pub fn use_cabal(&mut self, path: impl AsRef<Utf8Path>) -> &mut Self {
self.cabal = path.as_ref().to_path_buf();
self
}
pub fn use_ghc_pkg(&mut self, path: impl AsRef<Utf8Path>) -> &mut Self {
self.ghc_pkg = path.as_ref().to_path_buf();
self
}
pub fn use_rts(&mut self, rts_version: RTSVersion) -> &mut Self {
self.rts_version = rts_version;
self
}
pub fn build(&mut self) -> Result<Lib> {
let status = self
.cabal_cmd("build")
.status()
.map_err(|err| Error::BuildError(Some(err)))?;
if !status.success() {
return Err(Error::BuildError(None));
}
let path = self
.cabal_cmd("list-bin")
.arg(util::package())
.stdout_trim()
.map(Utf8PathBuf::from)
.map_err(|err| Error::BuildError(Some(err)))?;
Ok(Lib {
build: self,
path,
hs_deps: vec![HSDep::Ghc, HSDep::Base],
})
}
fn cabal_cmd(&self, cmd: &str) -> Command {
let mut cabal = Command::new(&self.cabal);
cabal.args([cmd, "--builddir", &out_dir()]);
cabal
}
fn ghc_pkg_cmd(&self, cmd: &str) -> Command {
let mut ghc_pkg = Command::new(&self.ghc_pkg);
ghc_pkg.args([cmd]);
ghc_pkg
}
}
impl<'b> Lib<'b> {
pub fn link(&self, rpath: bool) -> Result<()> {
let dir = self.path.parent().unwrap();
println!("cargo:rustc-link-search=native={}", dir);
println!("cargo:rustc-link-lib=dylib={}", &package());
if rpath {
println!("cargo:rustc-link-arg=-Wl,-rpath,{}", dir);
}
Ok(())
}
pub fn bindings(&self) -> Result<bindgen::Builder> {
let rts_headers = self
.build
.ghc_pkg_cmd("field")
.args(["rts", "include-dirs", "--simple-output"])
.stdout_trim()
.map(Utf8PathBuf::from)
.map_err(InvocationError::IoError)
.map_err(Error::GHCPkgError)?;
let stub = self
.path
.parent()
.unwrap()
.join(format!("{}-tmp", package()))
.join("Lib_stub.h");
let builder = bindgen::Builder::default()
.clang_args(["-isystem", rts_headers.as_str()])
.header(stub)
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()));
Ok(builder)
}
pub fn link_system(&self, rpath: bool) -> Result<()> {
let ghc_lib_dir = self
.build
.ghc_pkg_cmd("field")
.args(["rts", "dynamic-library-dirs", "--simple-output"])
.stdout_trim()
.map_err(InvocationError::IoError)
.map_err(Error::GHCPkgError)?;
let ghc_lib_dir = fs::canonicalize(ghc_lib_dir).unwrap();
let version_regex = Regex::new(r"((\d+)\.)+?(\d+)").unwrap();
let non_rts_prefixes = self
.hs_deps
.iter()
.map(HSDep::prefix)
.collect::<Vec<_>>()
.join("|");
let non_rts_regex = Regex::new(&format!(
r"^lib({prefix})-({version})-ghc({version})\.{ext}$",
prefix = non_rts_prefixes,
version = version_regex,
ext = DYLIB_EXT
))
.unwrap();
let rts_suffix = self.build.rts_version.suffix();
let rts_regex = Regex::new(&format!(
r"^libHSrts-({version})({suffix})-ghc({version})\.{ext}$",
version = version_regex,
suffix = rts_suffix,
ext = DYLIB_EXT
))
.unwrap();
println!("cargo:rustc-link-search=native={}", ghc_lib_dir.display());
for entry in fs::read_dir(&ghc_lib_dir).unwrap() {
let entry = entry.unwrap();
if let Some(i) = entry.file_name().to_str() {
if non_rts_regex.is_match(i) || rts_regex.is_match(i) {
let temp = i.split_at(3).1;
let trimmed = temp.split_at(temp.len() - DYLIB_EXT.len() - 1).0;
println!("cargo:rustc-link-lib=dylib={}", trimmed);
}
}
}
if rpath {
println!("cargo:rustc-link-arg=-Wl,-rpath,{}", ghc_lib_dir.display());
}
Ok(())
}
}
impl RTSVersion {
fn suffix(&self) -> &str {
match self {
RTSVersion::NonThreaded => "",
RTSVersion::NonThreadedL => "_l",
RTSVersion::NonThreadedDebug => "_debug",
RTSVersion::Threaded => "_thr",
RTSVersion::ThreadedL => "_thr_l",
RTSVersion::ThreadedDebug => "_thr_debug",
}
}
}
impl HSDep {
fn prefix(&self) -> &str {
match self {
HSDep::Ghc => "HSghc",
HSDep::Base => "HSbase",
}
}
}