use std::path::PathBuf;
use std::process;
use wolfram_app_discovery::{SystemID, WolframApp, WolframVersion};
const WOLFRAM_VERSION: WolframVersion = WolframVersion::new(13, 0, 1);
fn main() {
env_logger::init();
wolfram_app_discovery::config::set_print_cargo_build_script_directives(true);
if std::env::var("DOCS_RS").is_ok() {
let bindings_path = make_bindings_path(&WOLFRAM_VERSION, SystemID::MacOSX_x86_64);
println!(
"cargo:rustc-env=CRATE_WSTP_SYS_BINDINGS={}",
bindings_path.display()
);
return;
}
let app: Option<WolframApp> = WolframApp::try_default().ok();
let target_system_id: SystemID =
SystemID::try_from_rust_target(&std::env::var("TARGET").unwrap())
.expect("unable to get System ID for target system");
link_to_wstp(app.as_ref(), target_system_id);
let wolfram_version = match target_system_id {
SystemID::Linux_ARM64 => WolframVersion::new(13, 2, 0),
_ => WOLFRAM_VERSION,
};
let bindings_path = use_pregenerated_bindings(wolfram_version, target_system_id);
println!(
"cargo:rustc-env=CRATE_WSTP_SYS_BINDINGS={}",
bindings_path.display()
);
}
#[allow(dead_code)]
fn use_pregenerated_bindings(
wolfram_version: WolframVersion,
target_system_id: SystemID,
) -> PathBuf {
let bindings_path = make_bindings_path(&wolfram_version, target_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, target_system_id
);
panic!("<See printed error>");
}
bindings_path
}
fn make_bindings_path(wolfram_version: &WolframVersion, system_id: SystemID) -> PathBuf {
let bindings_path = PathBuf::from("generated")
.join(&wolfram_version.to_string())
.join(system_id.as_str())
.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: Option<&WolframApp>, target_system_id: SystemID) {
let static_lib = target_wstp_static_library_path(app, target_system_id);
link_wstp_statically(&static_lib, target_system_id);
if matches!(
target_system_id,
SystemID::MacOSX_x86_64 | SystemID::MacOSX_ARM64
) {
println!("cargo:rustc-link-lib=framework=Foundation");
}
if matches!(
target_system_id,
SystemID::Windows_x86_64 | SystemID::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");
}
if matches!(
target_system_id,
SystemID::Linux_x86_64
| SystemID::Linux_ARM64
| SystemID::Linux_ARM
| SystemID::Linux
) {
println!("cargo:rustc-link-lib=uuid")
}
}
fn target_wstp_static_library_path(
app: Option<&WolframApp>,
target_system_id: SystemID,
) -> PathBuf {
if let Some(app) = app {
let sdk = app
.wstp_sdks()
.expect("unable to locate WSTP SDKs")
.into_iter()
.flat_map(|sdk| sdk.ok())
.find(|sdk| sdk.system_id() == target_system_id)
.unwrap_or_else(|| {
panic!("unable to locate WSTP SDK for target SystemID {target_system_id}")
});
return sdk.wstp_static_library_path();
}
let host_system_id = std::env::var("HOST")
.ok()
.and_then(|host| SystemID::try_from_rust_target(&host).ok());
if host_system_id != Some(target_system_id) {
panic!(
"unable to locate a WSTP static library for target SystemID \
{target_system_id}: no Wolfram installation was discovered, and the \
wolframscript fallback only provides the host library{}. Install a \
Wolfram System with an SDK for the target, or set the \
wolfram-app-discovery environment variables to point at one.",
match host_system_id {
Some(host) => format!(" (host SystemID {host})"),
None => String::new(),
}
);
}
wolfram_app_discovery::build_scripts::wstp_static_library_path(None)
.expect("unable to get WSTP static library path")
.into_path_buf()
}
fn link_wstp_statically(lib: &PathBuf, target_system_id: SystemID) {
let mut lib = lib.clone();
if target_system_id == SystemID::MacOSX_x86_64 {
lib = lipo_native_library(&lib, "x86_64");
} else if target_system_id == SystemID::MacOSX_ARM64 {
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);
}