use flate2::read::GzDecoder;
use std::env;
use std::fs::{self, File};
use std::io::{self, BufReader, BufWriter, Write};
use std::path::{Path, PathBuf};
use tar::Archive;
const GITHUB_REPO: &str = "printwell-dev/core";
const LIB_NAME: &str = "printwell_native";
fn main() {
let target = env::var("TARGET").unwrap_or_else(|_| "x86_64-unknown-linux-gnu".to_string());
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let workspace_root = Path::new(&manifest_dir).parent().unwrap().parent().unwrap();
let version = env!("CARGO_PKG_VERSION");
let native_dir = find_or_download_native_lib(&target, &out_dir, workspace_root, version);
println!(
"cargo:warning=Using native library from: {}",
native_dir.display()
);
println!("cargo:rustc-link-search=native={}", native_dir.display());
println!("cargo:rustc-link-lib=dylib={LIB_NAME}");
#[cfg(target_os = "linux")]
{
println!("cargo:rustc-link-arg=-Wl,-rpath,$ORIGIN");
println!("cargo:rustc-link-arg=-Wl,-rpath,$ORIGIN/../lib");
println!("cargo:rustc-link-arg=-Wl,-rpath,{}", native_dir.display());
}
#[cfg(target_os = "macos")]
{
println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path");
println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path/../lib");
println!("cargo:rustc-link-arg=-Wl,-rpath,{}", native_dir.display());
}
let lib_file = native_dir.join(lib_filename(&target));
println!("cargo:rerun-if-changed={}", lib_file.display());
println!("cargo:rerun-if-env-changed=PRINTWELL_NATIVE_DIR");
println!("cargo:rerun-if-env-changed=PRINTWELL_OFFLINE");
}
fn find_or_download_native_lib(
target: &str,
out_dir: &Path,
workspace_root: &Path,
version: &str,
) -> PathBuf {
let lib_file = lib_filename(target);
if let Ok(dir) = env::var("PRINTWELL_NATIVE_DIR") {
let path = Path::new(&dir).join(&lib_file);
if path.exists() {
return PathBuf::from(dir);
}
println!(
"cargo:warning=PRINTWELL_NATIVE_DIR set but library not found at {}",
path.display()
);
}
let out_lib = out_dir.join(&lib_file);
if out_lib.exists() {
return out_dir.to_path_buf();
}
let target_native = workspace_root.join("target/native").join(&lib_file);
if target_native.exists() {
return workspace_root.join("target/native");
}
let platform_dir = platform_dir(target);
let prebuilt = workspace_root
.join("native")
.join(platform_dir)
.join(&lib_file);
if prebuilt.exists() {
return workspace_root.join("native").join(platform_dir);
}
if env::var("PRINTWELL_OFFLINE").is_err()
&& let Some(downloaded_dir) = download_native_lib(target, out_dir, version)
{
return downloaded_dir;
}
println!("cargo:warning=Native library not found!");
println!("cargo:warning=Options:");
println!("cargo:warning= 1. Run `cargo xtask build` to build libprintwell_native");
println!("cargo:warning= 2. Run `git lfs pull` to fetch pre-built library");
println!("cargo:warning= 3. Set PRINTWELL_NATIVE_DIR to the library location");
println!("cargo:warning= 4. Ensure network access for automatic download");
out_dir.to_path_buf()
}
fn download_native_lib(target: &str, out_dir: &Path, version: &str) -> Option<PathBuf> {
let archive_name = format!("libprintwell_native-{target}.tar.gz");
let url =
format!("https://github.com/{GITHUB_REPO}/releases/download/v{version}/{archive_name}");
println!("cargo:warning=Downloading native library from {url}");
let response = match ureq::get(&url).call() {
Ok(r) => r,
Err(e) => {
println!("cargo:warning=Failed to download: {e}");
return None;
}
};
if response.status() != 200 {
println!(
"cargo:warning=Download failed with status {}",
response.status()
);
return None;
}
let archive_path = out_dir.join(&archive_name);
let (_, body) = response.into_parts();
let mut reader = body.into_reader();
let file = match File::create(&archive_path) {
Ok(f) => f,
Err(e) => {
println!("cargo:warning=Failed to create archive file: {e}");
return None;
}
};
let mut writer = BufWriter::new(file);
if let Err(e) = io::copy(&mut reader, &mut writer) {
println!("cargo:warning=Failed to write archive: {e}");
return None;
}
writer.flush().ok();
drop(writer);
let archive_file = match File::open(&archive_path) {
Ok(f) => f,
Err(e) => {
println!("cargo:warning=Failed to open archive: {e}");
return None;
}
};
let decoder = GzDecoder::new(BufReader::new(archive_file));
let mut archive = Archive::new(decoder);
if let Err(e) = archive.unpack(out_dir) {
println!("cargo:warning=Failed to extract archive: {e}");
return None;
}
fs::remove_file(&archive_path).ok();
let lib_path = out_dir.join(lib_filename(target));
if lib_path.exists() {
println!("cargo:warning=Successfully downloaded native library");
Some(out_dir.to_path_buf())
} else {
println!("cargo:warning=Archive extracted but library not found");
None
}
}
fn lib_filename(target: &str) -> String {
if target.contains("windows") {
format!("{LIB_NAME}.dll")
} else if target.contains("darwin") || target.contains("apple") {
format!("lib{LIB_NAME}.dylib")
} else {
format!("lib{LIB_NAME}.so")
}
}
fn platform_dir(target: &str) -> &'static str {
match target {
t if t.contains("x86_64") && t.contains("linux") => "linux-x64",
t if t.contains("aarch64") && t.contains("linux") => "linux-arm64",
t if t.contains("x86_64") && (t.contains("darwin") || t.contains("apple")) => "darwin-x64",
t if t.contains("aarch64") && (t.contains("darwin") || t.contains("apple")) => {
"darwin-arm64"
}
t if t.contains("x86_64") && t.contains("windows") => "win32-x64",
_ => "linux-x64", }
}