use std::{
env,
ffi::OsString,
path::{Path, PathBuf},
process::Command,
};
use rust2go_cli::Args;
#[cfg(not(windows))]
const LIB_EXT: &str = ".a";
#[cfg(windows)]
const LIB_EXT: &str = ".lib";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LinkType {
Static,
Dynamic,
}
pub struct Builder<GOSRC = (), GOC = CustomArgGoCompiler> {
go_src: GOSRC,
out_dir: Option<PathBuf>,
binding_name: Option<String>,
link: LinkType,
regen_arg: Args,
copy_lib: CopyLib,
go_comp: GOC,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum CopyLib {
Disabled,
DefaultPath,
CustomPath(PathBuf),
}
impl Default for Builder {
fn default() -> Self {
Self::new()
}
}
impl Builder {
pub fn new() -> Self {
Builder {
go_src: (),
out_dir: None,
binding_name: None,
link: LinkType::Static,
regen_arg: Args::default(),
copy_lib: CopyLib::Disabled,
go_comp: CustomArgGoCompiler::new(),
}
}
}
impl<GOSRC, GOC> Builder<GOSRC, GOC> {
pub fn with_go_src<S: Into<PathBuf>>(self, go_src: S) -> Builder<PathBuf, GOC> {
Builder {
go_src: go_src.into(),
out_dir: self.out_dir,
binding_name: self.binding_name,
link: self.link,
regen_arg: self.regen_arg,
copy_lib: self.copy_lib,
go_comp: self.go_comp,
}
}
pub fn with_go_compiler<GOC2>(self, go_comp: GOC2) -> Builder<GOSRC, GOC2> {
Builder {
go_src: self.go_src,
out_dir: self.out_dir,
binding_name: self.binding_name,
link: self.link,
regen_arg: self.regen_arg,
copy_lib: self.copy_lib,
go_comp,
}
}
pub fn go_compiler_mut(&mut self) -> &mut GOC {
&mut self.go_comp
}
pub fn with_binding(mut self, binding_name: impl Into<String>) -> Self {
self.binding_name = Some(binding_name.into());
self
}
pub fn with_link(mut self, link: LinkType) -> Self {
self.link = link;
self
}
pub fn with_regen(mut self, src: &str, dst: &str) -> Self {
self.regen_arg.src = src.to_string();
self.regen_arg.dst = dst.to_string();
self
}
pub fn with_regen_arg(mut self, arg: Args) -> Self {
self.regen_arg = arg;
self
}
pub fn with_copy_lib(mut self, copy_lib: CopyLib) -> Self {
self.copy_lib = copy_lib;
self
}
}
impl<GOSRC> Builder<GOSRC, CustomArgGoCompiler> {
pub fn compiler_arg(&mut self, arg: impl Into<OsString>) -> &mut Self {
self.go_comp.arg(arg);
self
}
pub fn compiler_env(
&mut self,
key: impl Into<OsString>,
val: impl Into<OsString>,
) -> &mut Self {
self.go_comp.env(key, val);
self
}
}
pub trait GoCompiler {
fn go_build(&self, go_src: &Path, link: LinkType, output: &Path);
fn build(&self, go_src: &Path, binding_name: &str, link: LinkType, copy_lib: &CopyLib) {
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let out_filename = lib_filename(link);
let output = out_dir.join(&out_filename);
let header_filename = header_filename();
let header = out_dir.join(&header_filename);
{
let (header_content, header_time) = read_header_file(&header);
self.go_build(go_src, link, output.as_path());
let (header_content_after, _) = read_header_file(&header);
if header_content == header_content_after && !header_content_after.is_empty() {
if let Some((atime, mtime)) = header_time {
if let Some(atime) = atime {
let _ = fs_set_times::set_atime(&header, atime.into());
}
if let Some(mtime) = mtime {
let _ = fs_set_times::set_mtime(&header, mtime.into());
}
}
}
}
if link == LinkType::Dynamic {
fn get_cargo_target_dir() -> Result<PathBuf, Box<dyn std::error::Error>> {
let out_dir = PathBuf::from(env::var("OUT_DIR")?);
let profile = env::var("PROFILE")?;
let mut target_dir = None;
let mut sub_path = out_dir.as_path();
while let Some(parent) = sub_path.parent() {
if parent.ends_with(&profile) {
target_dir = Some(parent);
break;
}
sub_path = parent;
}
let target_dir = target_dir.ok_or("not found")?;
Ok(target_dir.to_path_buf())
}
match copy_lib {
CopyLib::Disabled => (),
CopyLib::DefaultPath => {
let target_dir = get_cargo_target_dir().unwrap();
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
std::fs::copy(out_dir.join(&out_filename), target_dir.join(&out_filename))
.expect("unable to copy dynamic library");
}
CopyLib::CustomPath(p) => {
std::fs::copy(out_dir.join(&out_filename), p.join(&out_filename))
.expect("unable to copy dynamic library");
}
}
}
let bindings = bindgen::Builder::default()
.header(header.to_str().unwrap())
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.generate()
.expect("Unable to generate bindings");
bindings
.write_to_file(out_dir.join(binding_name))
.expect("Couldn't write bindings!");
println!("cargo:rerun-if-changed={}", go_src.to_str().unwrap());
println!(
"cargo:rustc-link-search=native={}",
out_dir.to_str().unwrap()
);
if link == LinkType::Static {
println!("cargo:rustc-link-lib=static=go");
} else {
println!("cargo:rustc-link-lib=dylib=go");
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct DefaultGoCompiler;
impl GoCompiler for DefaultGoCompiler {
fn go_build(&self, go_src: &Path, link: LinkType, output: &Path) {
let mut go_build = Command::new("go");
go_build
.env("GO111MODULE", "on")
.current_dir(go_src)
.arg("build")
.arg(if link == LinkType::Static {
"-buildmode=c-archive"
} else {
"-buildmode=c-shared"
})
.arg("-o")
.arg(output)
.arg(".");
assert!(
go_build.status().expect("Go build failed").success(),
"Go build failed"
);
}
}
#[derive(Debug, Clone)]
pub struct CustomArgGoCompiler {
args: Vec<OsString>,
envs: Vec<(OsString, OsString)>,
}
impl CustomArgGoCompiler {
pub fn new() -> Self {
Self {
args: Vec::new(),
envs: vec![("GO111MODULE".into(), "on".into())],
}
}
pub fn arg(&mut self, arg: impl Into<OsString>) -> &mut Self {
self.args.push(arg.into());
self
}
pub fn env(&mut self, key: impl Into<OsString>, val: impl Into<OsString>) -> &mut Self {
self.envs.push((key.into(), val.into()));
self
}
pub fn args_mut(&mut self) -> &mut Vec<OsString> {
&mut self.args
}
pub fn envs_mut(&mut self) -> &mut Vec<(OsString, OsString)> {
&mut self.envs
}
}
impl Default for CustomArgGoCompiler {
fn default() -> Self {
Self::new()
}
}
impl GoCompiler for CustomArgGoCompiler {
fn go_build(&self, go_src: &Path, link: LinkType, output: &Path) {
let mut go_build = Command::new("go");
let mut cmd = &mut go_build;
for (key, val) in &self.envs {
cmd = cmd.env(key, val);
}
cmd.current_dir(go_src)
.arg("build")
.arg(if link == LinkType::Static {
"-buildmode=c-archive"
} else {
"-buildmode=c-shared"
});
for arg in &self.args {
cmd = cmd.arg(arg);
}
cmd.arg("-o").arg(output).arg(".");
assert!(
go_build.status().expect("Go build failed").success(),
"Go build failed"
);
}
}
impl<GOC: GoCompiler> Builder<PathBuf, GOC> {
pub fn build(self) {
let binding_name = self
.binding_name
.as_deref()
.unwrap_or(crate::DEFAULT_BINDING_FILE);
if !self.regen_arg.src.is_empty() && !self.regen_arg.dst.is_empty() {
rust2go_cli::generate(&self.regen_arg);
}
self.go_comp
.build(&self.go_src, binding_name, self.link, &self.copy_lib);
}
}
fn lib_filename(link_type: LinkType) -> String {
use std::env::consts::{DLL_PREFIX, DLL_SUFFIX};
match link_type {
LinkType::Static => format!("{DLL_PREFIX}go{LIB_EXT}"),
LinkType::Dynamic => format!("{DLL_PREFIX}go{DLL_SUFFIX}"),
}
}
fn header_filename() -> String {
use std::env::consts::DLL_PREFIX;
format!("{DLL_PREFIX}go.h")
}
#[allow(clippy::type_complexity)]
fn read_header_file(
path: &Path,
) -> (
Vec<u8>,
Option<(Option<std::time::SystemTime>, Option<std::time::SystemTime>)>,
) {
use std::io::Read;
let mut file = std::fs::File::open(path).ok();
let mut content = Vec::new();
file.as_mut().and_then(|h| h.read_to_end(&mut content).ok());
let time = file
.as_ref()
.and_then(|f| f.metadata().ok())
.map(|m| (m.accessed().ok(), m.modified().ok()));
(content, time)
}