use std::path::PathBuf;
use std::process;
use wolfram_app_discovery::{WolframApp, WolframVersion};
fn main() {
wolfram_app_discovery::config::set_print_cargo_build_script_instructions(true);
if std::env::var("DOCS_RS").is_ok() {
let bindings_path = make_bindings_path("13.0.0", "MacOSX-x86-64");
println!(
"cargo:rustc-env=CRATE_WSTP_SYS_BINDINGS={}",
bindings_path.display()
);
return;
}
let host = std::env::var("HOST").expect("expected 'HOST' env var to be set");
let target = std::env::var("TARGET").expect("expected 'TARGET' env var to be set");
if host != target {
panic!(
"error: crate wstp-sys does not support cross compilation. (host: {}, target: {})",
host,
target
);
}
let app = WolframApp::try_default().expect("unable to locate WolframApp");
link_to_wstp(&app);
let bindings_path = use_generated_bindings(&app);
println!(
"cargo:rustc-env=CRATE_WSTP_SYS_BINDINGS={}",
bindings_path.display()
);
}
fn use_generated_bindings(app: &WolframApp) -> PathBuf {
let wstp_h = app
.wstp_c_header_path()
.expect("unable to get 'wstp.h' location");
println!(
"cargo:warning=info: generating WSTP bindings from: {}",
wstp_h.display()
);
let out_path =
PathBuf::from(std::env::var("OUT_DIR").unwrap()).join("WSTP_bindings.rs");
generate_and_save_bindings_to_file(&wstp_h, &out_path);
out_path
}
fn generate_and_save_bindings_to_file(wstp_h: &PathBuf, out_path: &PathBuf) {
assert!(wstp_h.file_name().unwrap() == "wstp.h");
let bindings = bindgen::Builder::default()
.header(wstp_h.display().to_string())
.generate_comments(true)
.rustfmt_bindings(true)
.default_macro_constant_type(bindgen::MacroTypeVariation::Signed)
.generate()
.expect("unable to generate Rust bindings to WSTP using bindgen");
std::fs::create_dir_all(out_path.parent().unwrap())
.expect("failed to create parent directories for generating bindings file");
bindings
.write_to_file(&out_path)
.expect("failed to write Rust bindings with IO error");
}
#[allow(dead_code)]
fn use_pregenerated_bindings(wolfram_version: &WolframVersion) -> PathBuf {
let system_id =
wolfram_app_discovery::system_id_from_target(&std::env::var("TARGET").unwrap())
.expect("unable to get System ID for target system");
let bindings_path = make_bindings_path(&wolfram_version.to_string(), &system_id);
println!("cargo:rerun-if-changed={}", bindings_path.display());
if !bindings_path.is_file() {
println!(
"
==== ERROR: wstp-sys =====
Rust bindings for Wolfram WSTP for target configuration:
WolframVersion: {}
SystemID: {}
have not been pre-generated.
See wstp-sys/generated/ for a listing of currently available targets.
=========================================
",
wolfram_version, system_id
);
panic!("<See printed error>");
}
bindings_path
}
fn make_bindings_path(wolfram_version: &str, system_id: &str) -> PathBuf {
let bindings_path = PathBuf::from("generated")
.join(wolfram_version)
.join(system_id)
.join("WSTP_bindings.rs");
let absolute_bindings_path =
PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()).join(&bindings_path);
absolute_bindings_path
}
fn link_to_wstp(app: &WolframApp) {
let static_lib = &app
.wstp_static_library_path()
.expect("unable to get WSTP static library path");
link_wstp_statically(&static_lib);
if cfg!(target_os = "macos") {
println!("cargo:rustc-link-lib=framework=Foundation");
}
if cfg!(target_os = "windows") {
println!("cargo:rustc-link-lib=dylib=kernel32");
println!("cargo:rustc-link-lib=dylib=user32");
println!("cargo:rustc-link-lib=dylib=advapi32");
println!("cargo:rustc-link-lib=dylib=comdlg32");
println!("cargo:rustc-link-lib=dylib=ws2_32");
println!("cargo:rustc-link-lib=dylib=wsock32");
println!("cargo:rustc-link-lib=dylib=rpcrt4");
}
}
fn link_wstp_statically(lib: &PathBuf) {
let mut lib = lib.clone();
if cfg!(all(target_os = "macos", target_arch = "x86_64")) {
lib = lipo_native_library(&lib, "x86_64");
} else if cfg!(all(target_os = "macos", target_arch = "aarch64")) {
lib = lipo_native_library(&lib, "arm64");
}
link_library_file(lib);
}
fn lipo_native_library(wstp_lib: &PathBuf, lipo_arch: &str) -> PathBuf {
let wstp_lib = wstp_lib
.to_str()
.expect("could not convert WSTP archive path to str");
let is_universal_binary = {
let stdout = process::Command::new("file")
.args(&[wstp_lib])
.output()
.expect("failed to run `file` system utility")
.stdout;
let stdout = String::from_utf8(stdout).unwrap();
stdout.contains("Mach-O universal binary")
};
if !is_universal_binary {
return PathBuf::from(wstp_lib);
}
let output_lib = std::env::temp_dir().join("libWSTP-thin.a");
let output_lib = output_lib
.to_str()
.expect("could not convert WSTP archive path to str");
let output = process::Command::new("lipo")
.args(&[wstp_lib, "-thin", lipo_arch, "-output", output_lib])
.output()
.expect("failed to invoke macOS `lipo` command");
if !output.status.success() {
panic!("unable to lipo WSTP library: {:#?}", output);
}
PathBuf::from(output_lib)
}
fn link_library_file(libfile: PathBuf) {
let search_dir = libfile.parent().unwrap().display().to_string();
let libname = libfile
.file_stem()
.unwrap()
.to_str()
.unwrap()
.trim_start_matches("lib");
println!("cargo:rustc-link-search={}", search_dir);
println!("cargo:rustc-link-lib=static={}", libname);
}