use std::env;
use std::fs;
use std::io::Read;
use std::path::PathBuf;
use sha2::{Digest, Sha256};
const VERSION: &str = env!("CARGO_PKG_VERSION");
const REPO: &str = "Empyrean-Dynamics/empyrean";
const CHECKSUMS: &str = include_str!("checksums.txt");
fn target_asset() -> Option<(String, String)> {
let stem = match (target_os().as_str(), target_arch().as_str()) {
("macos", "aarch64") => "libempyrean-macos-aarch64",
("macos", "x86_64") => "libempyrean-macos-x86_64",
("linux", "x86_64") => "libempyrean-linux-x86_64",
("linux", "aarch64") => "libempyrean-linux-aarch64",
_ => return None,
};
let sha = CHECKSUMS.lines().find_map(|line| {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
return None;
}
let (name, hash) = line.split_once(char::is_whitespace)?;
(name.trim() == stem).then(|| hash.trim().to_string())
})?;
Some((stem.to_string(), sha))
}
fn target_os() -> String {
env::var("CARGO_CFG_TARGET_OS").unwrap_or_default()
}
fn target_arch() -> String {
env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default()
}
fn lib_filename() -> &'static str {
match target_os().as_str() {
"macos" => "libempyrean.dylib",
"windows" => "empyrean.dll",
_ => "libempyrean.so",
}
}
fn main() {
println!("cargo:rerun-if-env-changed=EMPYREAN_LIB_DIR");
println!("cargo:rerun-if-env-changed=EMPYREAN_FORCE_DOWNLOAD");
let out = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR")).join("lib_path.rs");
if env::var_os("DOCS_RS").is_some() {
fs::write(&out, "pub const LIB_PATH: &str = \"\";\n").expect("write lib_path.rs");
return;
}
let lib_file = lib_filename();
let lib_dir = resolve_lib_dir(lib_file);
let lib_path = lib_dir.join(lib_file);
assert!(
lib_path.exists(),
"libempyrean not found at {}. Set EMPYREAN_LIB_DIR to a directory containing {lib_file}.",
lib_path.display(),
);
let abs = lib_path.canonicalize().unwrap_or(lib_path);
fs::write(
&out,
format!("pub const LIB_PATH: &str = {:?};\n", abs.to_string_lossy()),
)
.expect("write lib_path.rs");
println!("cargo:rerun-if-changed={}", abs.display());
}
fn resolve_lib_dir(lib_file: &str) -> PathBuf {
if let Ok(dir) = env::var("EMPYREAN_LIB_DIR") {
return PathBuf::from(dir);
}
let force = matches!(
env::var("EMPYREAN_FORCE_DOWNLOAD").as_deref(),
Ok("1") | Ok("true")
);
if !force {
let ws = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()).join("../target/release");
if ws.join(lib_file).exists() {
return ws;
}
}
download_prebuilt(lib_file)
}
fn download_prebuilt(lib_file: &str) -> PathBuf {
let (stem, expected_sha) = target_asset().unwrap_or_else(|| {
panic!(
"No prebuilt libempyrean is published for target {}-{}. Build it from the engine \
and point EMPYREAN_LIB_DIR at the directory containing {lib_file}.",
target_arch(),
target_os(),
)
});
let cache = cache_dir();
fs::create_dir_all(&cache).expect("create libempyrean cache dir");
let lib_path = cache.join(lib_file);
if lib_path.exists() {
return cache;
}
let url = format!("https://github.com/{REPO}/releases/download/v{VERSION}/{stem}.tar.gz");
eprintln!("empyrean-sys: fetching prebuilt {stem} from {url}");
let resp = ureq::get(&url)
.call()
.unwrap_or_else(|e| panic!("download libempyrean from {url}: {e}"));
let mut bytes = Vec::new();
resp.into_reader()
.read_to_end(&mut bytes)
.unwrap_or_else(|e| panic!("read libempyrean download from {url}: {e}"));
let got = sha256_hex(&bytes);
if got != expected_sha {
panic!(
"libempyrean checksum mismatch for {stem}.tar.gz\n expected {expected_sha}\n got {got}\n\
Refusing to use an unverified binary."
);
}
let decoder = flate2::read::GzDecoder::new(&bytes[..]);
tar::Archive::new(decoder)
.unpack(&cache)
.unwrap_or_else(|e| panic!("extract {stem}.tar.gz: {e}"));
assert!(
lib_path.exists(),
"{stem}.tar.gz did not contain {lib_file}"
);
cache
}
fn cache_dir() -> PathBuf {
let base = env::var("XDG_CACHE_HOME")
.ok()
.map(PathBuf::from)
.or_else(|| {
env::var("HOME")
.ok()
.map(|h| PathBuf::from(h).join(".cache"))
})
.unwrap_or_else(env::temp_dir);
base.join("empyrean")
.join("libempyrean")
.join(VERSION)
.join(format!("{}-{}", target_arch(), target_os()))
}
fn sha256_hex(bytes: &[u8]) -> String {
let digest = Sha256::digest(bytes);
digest.iter().map(|b| format!("{b:02x}")).collect()
}