use std::env;
use std::fs;
use std::path::{Path, PathBuf};
#[cfg(feature = "regenerate")]
const HEADER_URL: &str =
"https://raw.githubusercontent.com/containers/libkrun/v1.17.4/include/libkrun.h";
const GITHUB_REPO: &str = "qntx/bux";
fn main() {
println!("cargo:rerun-if-env-changed=BUX_DEPS_DIR");
println!("cargo:rerun-if-env-changed=BUX_DEPS_VERSION");
println!("cargo:rerun-if-env-changed=BUX_UPDATE_BINDINGS");
println!("cargo:rerun-if-env-changed=DOCS_RS");
if env::var("DOCS_RS").is_ok() {
return;
}
let target = env::var("TARGET").expect("TARGET not set");
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR not set"));
#[cfg(feature = "regenerate")]
{
let header = download_header(&out_dir);
generate_bindings(&header, &out_dir);
}
if !is_supported_target(&target) {
return;
}
let lib_dir = obtain_libraries(&target, &out_dir);
println!("cargo:rustc-link-search=native={}", lib_dir.display());
println!("cargo:rustc-link-lib=dylib=krun");
println!("cargo:LIB_DIR={}", lib_dir.display());
}
#[cfg(feature = "regenerate")]
fn download_header(out_dir: &Path) -> PathBuf {
let path = out_dir.join("libkrun.h");
if path.exists() {
return path;
}
eprintln!("bux-sys: downloading header from {HEADER_URL}");
let resp = ureq::get(HEADER_URL)
.call()
.unwrap_or_else(|e| panic!("Failed to download libkrun.h: {e}"));
let mut buf = Vec::new();
std::io::Read::read_to_end(&mut resp.into_body().into_reader(), &mut buf)
.expect("Failed to read header");
fs::write(&path, &buf).expect("Failed to write libkrun.h");
path
}
#[cfg(feature = "regenerate")]
fn generate_bindings(header: &Path, out_dir: &Path) {
let out_file = out_dir.join("bindings.rs");
let bindings = bindgen::Builder::default()
.header(header.to_str().expect("path is not valid UTF-8"))
.use_core()
.allowlist_function("krun_.*")
.allowlist_var("KRUN_.*")
.allowlist_var("NET_.*")
.allowlist_var("COMPAT_.*")
.allowlist_var("VIRGLRENDERER_.*")
.derive_debug(true)
.derive_default(true)
.derive_eq(true)
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.generate()
.expect("bindgen failed to generate bindings from libkrun.h");
bindings
.write_to_file(&out_file)
.expect("Failed to write bindings.rs");
if env::var("BUX_UPDATE_BINDINGS").is_ok() {
let manifest =
PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"));
let committed = manifest.join("src").join("bindings.rs");
fs::copy(&out_file, &committed).expect("Failed to copy bindings.rs to src/");
println!(
"cargo:warning=Updated committed bindings: {}",
committed.display()
);
}
}
fn obtain_libraries(target: &str, out_dir: &Path) -> PathBuf {
if let Ok(dir) = env::var("BUX_DEPS_DIR") {
eprintln!("bux-sys: using local deps: {dir}");
return PathBuf::from(dir);
}
let version = env::var("BUX_DEPS_VERSION")
.unwrap_or_else(|_| env::var("CARGO_PKG_VERSION").expect("CARGO_PKG_VERSION not set"));
let lib_dir = out_dir.join("lib");
if !lib_dir.join(lib_filename(target)).exists() {
download_libs(&version, target, &lib_dir);
}
lib_dir
}
fn is_supported_target(target: &str) -> bool {
let linux =
target.contains("linux") && (target.contains("x86_64") || target.contains("aarch64"));
let macos = target.contains("apple") && target.contains("aarch64");
linux || macos
}
fn lib_filename(target: &str) -> &'static str {
if target.contains("apple") {
"libkrun.dylib"
} else {
"libkrun.so"
}
}
fn download_libs(version: &str, target: &str, dest: &Path) {
let url = format!(
"https://github.com/{GITHUB_REPO}/releases/download/deps-v{version}/bux-deps-{target}.tar.gz"
);
eprintln!("bux-sys: downloading {url}");
let resp = ureq::get(&url)
.call()
.unwrap_or_else(|e| panic!("Failed to download deps: {e}"));
fs::create_dir_all(dest).expect("Failed to create lib dir");
tar::Archive::new(flate2::read::GzDecoder::new(resp.into_body().into_reader()))
.unpack(dest)
.expect("Failed to extract archive");
assert!(
dest.join(lib_filename(target)).exists(),
"Library not found after extraction. Check GitHub Release deps-v{version}."
);
}