use std::fs;
use std::io::{self, Cursor, Read};
use std::path::{Path, PathBuf};
use std::process::Command;
const PREBUILT_VERSION: &str = env!("CARGO_PKG_VERSION");
const LIBKRUNFW_ABI: &str = "5";
const LIBKRUNFW_VERSION: &str = "5.2.1";
const GITHUB_ORG: &str = "superradcompany";
const REPO: &str = "microsandbox";
const MSB_BINARY: &str = "msb";
fn main() {
let home = home_dir();
if let Some(ref home) = home {
let base_dir = home.join(".microsandbox");
println!(
"cargo:rerun-if-changed={}",
base_dir.join("bin").join(MSB_BINARY).display()
);
println!(
"cargo:rerun-if-changed={}",
base_dir.join("lib").join(libkrunfw_filename()).display()
);
}
if std::env::var("CARGO_FEATURE_PREBUILT").is_err() {
return;
}
let Some(home) = home else {
println!("cargo:warning=could not determine home directory, skipping prebuilt download");
return;
};
let base_dir = home.join(".microsandbox");
let bin_dir = base_dir.join("bin");
let lib_dir = base_dir.join("lib");
let libkrunfw_name = libkrunfw_filename();
if lib_dir.join(&libkrunfw_name).exists()
&& installed_msb_version(&bin_dir.join(MSB_BINARY)).as_deref() == Some(PREBUILT_VERSION)
{
return;
}
fs::create_dir_all(&bin_dir).expect("failed to create bin dir");
fs::create_dir_all(&lib_dir).expect("failed to create lib dir");
if install_ci_local_bundle(&bin_dir, &lib_dir, &libkrunfw_name)
.expect("failed to install CI local microsandbox bundle")
{
return;
}
let url = bundle_url();
println!(
"cargo:warning=downloading microsandbox runtime dependencies (v{PREBUILT_VERSION})..."
);
let data = download(&url).expect("failed to download microsandbox bundle");
extract_bundle(&data, &bin_dir, &lib_dir).expect("failed to extract bundle");
create_symlinks(&lib_dir, &libkrunfw_name);
assert!(
bin_dir.join(MSB_BINARY).exists(),
"msb binary not found after extraction"
);
assert!(
lib_dir.join(&libkrunfw_name).exists(),
"{libkrunfw_name} not found after extraction"
);
}
fn home_dir() -> Option<PathBuf> {
#[cfg(any(target_os = "macos", target_os = "linux"))]
{
std::env::var("HOME").ok().map(PathBuf::from)
}
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
{
None
}
}
fn libkrunfw_filename() -> String {
if cfg!(target_os = "macos") {
format!("libkrunfw.{LIBKRUNFW_ABI}.dylib")
} else {
format!("libkrunfw.so.{LIBKRUNFW_VERSION}")
}
}
fn bundle_url() -> String {
let arch = std::env::consts::ARCH;
let target_os = if cfg!(target_os = "macos") {
"darwin"
} else {
"linux"
};
format!(
"https://github.com/{GITHUB_ORG}/{REPO}/releases/download/v{PREBUILT_VERSION}/{REPO}-{target_os}-{arch}.tar.gz"
)
}
fn installed_msb_version(path: &Path) -> Option<String> {
if !path.exists() {
return None;
}
let output = Command::new(path).arg("--version").output().ok()?;
if !output.status.success() {
return None;
}
let stdout = String::from_utf8(output.stdout).ok()?;
stdout
.trim()
.strip_prefix("msb ")
.map(std::string::ToString::to_string)
}
fn install_ci_local_bundle(
bin_dir: &Path,
lib_dir: &Path,
libkrunfw_name: &str,
) -> io::Result<bool> {
if std::env::var_os("CI").is_none() && std::env::var_os("GITHUB_ACTIONS").is_none() {
return Ok(false);
}
let Some(build_dir) = workspace_build_dir() else {
return Ok(false);
};
let msb_src = build_dir.join(MSB_BINARY);
let lib_src = build_dir.join(libkrunfw_name);
if !msb_src.is_file() || !lib_src.is_file() {
return Ok(false);
}
fs::copy(&msb_src, bin_dir.join(MSB_BINARY))?;
fs::copy(&lib_src, lib_dir.join(libkrunfw_name))?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
fs::set_permissions(bin_dir.join(MSB_BINARY), fs::Permissions::from_mode(0o755))?;
fs::set_permissions(
lib_dir.join(libkrunfw_name),
fs::Permissions::from_mode(0o755),
)?;
}
create_symlinks(lib_dir, libkrunfw_name);
println!("cargo:warning=installed microsandbox runtime dependencies from local CI build/");
Ok(true)
}
fn workspace_build_dir() -> Option<PathBuf> {
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let workspace_root = manifest_dir.parent()?.parent()?;
if !workspace_root.join("Cargo.toml").is_file() {
return None;
}
Some(workspace_root.join("build"))
}
fn download(url: &str) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let resp = ureq::get(url).call()?;
let mut buf = Vec::new();
resp.into_body().into_reader().read_to_end(&mut buf)?;
Ok(buf)
}
fn extract_bundle(data: &[u8], bin_dir: &Path, lib_dir: &Path) -> io::Result<()> {
let decoder = flate2::read::GzDecoder::new(Cursor::new(data));
let mut archive = tar::Archive::new(decoder);
for entry in archive.entries()? {
let mut entry = entry?;
let path = entry.path()?;
let Some(filename) = path.file_name().and_then(|f| f.to_str()) else {
continue;
};
let dest = if filename.starts_with("libkrunfw") {
lib_dir.join(filename)
} else {
bin_dir.join(filename)
};
entry.unpack(&dest)?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
fs::set_permissions(&dest, fs::Permissions::from_mode(0o755))?;
}
}
Ok(())
}
fn create_symlinks(lib_dir: &Path, libkrunfw_name: &str) {
#[cfg(unix)]
{
let symlinks: Vec<(String, String)> = if cfg!(target_os = "macos") {
vec![("libkrunfw.dylib".to_string(), libkrunfw_name.to_string())]
} else {
let soname = format!("libkrunfw.so.{LIBKRUNFW_ABI}");
vec![
(soname.clone(), libkrunfw_name.to_string()),
("libkrunfw.so".to_string(), soname),
]
};
for (link_name, target) in &symlinks {
let link_path = lib_dir.join(link_name);
if link_path.exists() || link_path.is_symlink() {
let _ = fs::remove_file(&link_path);
}
std::os::unix::fs::symlink(target, &link_path).ok();
}
}
}