use std::collections::HashMap;
use std::env;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
const LIBKRUNFW_PREBUILT_URL: &str =
"https://github.com/boxlite-ai/libkrunfw/releases/download/v5.1.0/libkrunfw-prebuilt-aarch64.tgz";
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
const LIBKRUNFW_SHA256: &str = "2b2801d2e414140d8d0a30d7e30a011077b7586eabbbecdca42aea804b59de8b";
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
const LIBKRUNFW_SO_URL: &str =
"https://github.com/boxlite-ai/libkrunfw/releases/download/v5.1.0/libkrunfw-x86_64.tgz";
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
const LIBKRUNFW_SHA256: &str = "faca64a3581ce281498b8ae7eccc6bd0da99b167984f9ee39c47754531d4b37d";
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
const LIBKRUNFW_SO_URL: &str =
"https://github.com/boxlite-ai/libkrunfw/releases/download/v5.1.0/libkrunfw-aarch64.tgz";
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
const LIBKRUNFW_SHA256: &str = "e254bc3fb07b32e26a258d9958967b2f22eb6c3136cfedf358c332308b6d35ea";
#[cfg(target_os = "macos")]
const LIB_DIR: &str = "lib";
#[cfg(target_os = "linux")]
const LIB_DIR: &str = "lib64";
fn run_command(cmd: &mut Command, description: &str) {
let status = cmd
.status()
.unwrap_or_else(|e| panic!("Failed to execute {}: {}", description, e));
if !status.success() {
panic!("{} failed with exit code: {:?}", description, status.code());
}
}
fn verify_vendored_sources(manifest_dir: &Path, require_libkrunfw: bool) {
let libkrun_src = manifest_dir.join("vendor/libkrun");
let libkrunfw_src = manifest_dir.join("vendor/libkrunfw");
let missing_libkrun = !libkrun_src.join("Makefile").exists();
let missing_libkrunfw = require_libkrunfw && !libkrunfw_src.join("Makefile").exists();
if missing_libkrun || missing_libkrunfw {
eprintln!("ERROR: Vendored sources not found");
eprintln!();
eprintln!("Initialize git submodules:");
eprintln!(" git submodule update --init --recursive");
std::process::exit(1);
}
}
struct Fetcher;
impl Fetcher {
pub fn fetch(
url: &str,
sha256: &str,
tarball_path: &Path,
extract_dir: &Path,
) -> io::Result<()> {
if !tarball_path.exists() {
Self::download(url, tarball_path)?;
Self::verify_sha256(tarball_path, sha256)?;
}
Self::extract_tarball(tarball_path, extract_dir)
}
fn download(url: &str, dest: &Path) -> io::Result<()> {
println!("cargo:warning=Downloading {}...", url);
let output = Command::new("curl")
.args(["-fsSL", "-o", dest.to_str().unwrap(), url])
.output()?;
if !output.status.success() {
return Err(io::Error::other(format!(
"curl failed: {}",
String::from_utf8_lossy(&output.stderr)
)));
}
Ok(())
}
fn verify_sha256(file: &Path, expected: &str) -> io::Result<()> {
let (cmd, args): (&str, Vec<&str>) = if cfg!(target_os = "linux") {
("sha256sum", vec![file.to_str().unwrap()])
} else {
("shasum", vec!["-a", "256", file.to_str().unwrap()])
};
let output = Command::new(cmd).args(&args).output()?;
if !output.status.success() {
return Err(io::Error::other(format!("{} failed", cmd)));
}
let actual = String::from_utf8_lossy(&output.stdout)
.split_whitespace()
.next()
.unwrap_or("")
.to_string();
if actual != expected {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("SHA256 mismatch: expected {}, got {}", expected, actual),
));
}
println!("cargo:warning=SHA256 verified: {}", expected);
Ok(())
}
fn extract_tarball(tarball: &Path, dest: &Path) -> io::Result<()> {
fs::create_dir_all(dest)?;
let status = Command::new("tar")
.args([
"-xzf",
tarball.to_str().unwrap(),
"-C",
dest.to_str().unwrap(),
])
.status()?;
if !status.success() {
return Err(io::Error::other("tar extraction failed"));
}
Ok(())
}
}
#[cfg(target_os = "macos")]
fn download_libkrunfw_prebuilt(out_dir: &Path) -> PathBuf {
let tarball_path = out_dir.join("libkrunfw-prebuilt.tar.gz");
let extract_dir = out_dir.join("libkrunfw-src");
let src_dir = extract_dir.join("libkrunfw");
if src_dir.join("kernel.c").exists() {
println!("cargo:warning=Using cached libkrunfw source");
return src_dir;
}
if extract_dir.exists() {
fs::remove_dir_all(&extract_dir).ok();
}
Fetcher::fetch(
LIBKRUNFW_PREBUILT_URL,
LIBKRUNFW_SHA256,
&tarball_path,
&extract_dir,
)
.unwrap_or_else(|e| panic!("Failed to fetch libkrunfw: {}", e));
println!("cargo:warning=Extracted libkrunfw to {}", src_dir.display());
src_dir
}
#[cfg(target_os = "linux")]
fn download_libkrunfw_so(install_dir: &Path) {
let lib_dir = install_dir.join(LIB_DIR);
let already_cached = lib_dir
.read_dir()
.ok()
.map(|entries| {
entries
.filter_map(Result::ok)
.any(|e| e.file_name().to_string_lossy().starts_with("libkrunfw.so"))
})
.unwrap_or(false);
if already_cached {
println!("cargo:warning=Using cached libkrunfw.so");
return;
}
fs::create_dir_all(install_dir)
.unwrap_or_else(|e| panic!("Failed to create install dir: {}", e));
let tarball_path = install_dir.join("libkrunfw.tgz");
Fetcher::fetch(
LIBKRUNFW_SO_URL,
LIBKRUNFW_SHA256,
&tarball_path,
install_dir,
)
.unwrap_or_else(|e| panic!("Failed to fetch libkrunfw: {}", e));
println!(
"cargo:warning=Extracted libkrunfw.so to {}",
lib_dir.display()
);
}
fn make_command(source_dir: &Path, extra_env: &HashMap<String, String>) -> Command {
let mut cmd = Command::new("make");
cmd.stdout(Stdio::inherit());
cmd.stderr(Stdio::inherit());
cmd.args(["-j", &num_cpus::get().to_string()])
.arg("MAKEFLAGS=") .current_dir(source_dir);
for (key, value) in extra_env {
cmd.env(key, value);
}
cmd
}
fn build_with_make(
source_dir: &Path,
install_dir: &Path,
lib_name: &str,
extra_env: &HashMap<String, String>,
extra_make_args: &[String],
) {
println!("cargo:warning=Building {} from source...", lib_name);
fs::create_dir_all(install_dir)
.unwrap_or_else(|e| panic!("Failed to create install directory: {}", e));
let mut make_cmd = make_command(source_dir, extra_env);
make_cmd.env("PREFIX", install_dir);
make_cmd.args(extra_make_args);
run_command(&mut make_cmd, &format!("make {}", lib_name));
let mut install_cmd = make_command(source_dir, extra_env);
install_cmd.env("PREFIX", install_dir);
install_cmd.args(extra_make_args);
install_cmd.arg("install");
run_command(&mut install_cmd, &format!("make install {}", lib_name));
}
struct LibBuilder;
impl LibBuilder {
pub fn build(
libkrun_src: &Path,
libkrun_install: &Path,
libkrunfw_install: &Path,
init_env: &HashMap<String, String>,
init_make_args: &[String],
) {
Self::build_init_binary(libkrun_src, init_env, init_make_args);
Self::build_libkrun_static(libkrun_src, libkrun_install, libkrunfw_install);
}
fn build_init_binary(
libkrun_src: &Path,
extra_env: &HashMap<String, String>,
extra_make_args: &[String],
) {
println!("cargo:warning=Building init binary...");
let mut cmd = make_command(libkrun_src, extra_env);
cmd.args(extra_make_args);
cmd.arg("init/init");
run_command(&mut cmd, "make init/init");
}
fn build_libkrun_static(libkrun_src: &Path, install_dir: &Path, libkrunfw_install: &Path) {
println!("cargo:warning=Building libkrun as static library...");
let lib_dir = install_dir.join(LIB_DIR);
fs::create_dir_all(&lib_dir)
.unwrap_or_else(|e| panic!("Failed to create lib directory: {}", e));
let target = env::var("TARGET").ok();
let mut cmd = Command::new("cargo");
cmd.args([
"rustc",
"-p",
"libkrun",
"--release",
"--crate-type",
"staticlib",
]);
cmd.args([
"--features",
"net,blk,vmm/net,vmm/blk,devices/net,devices/blk",
]);
if let Some(ref target) = target {
cmd.args(["--target", target]);
}
cmd.current_dir(libkrun_src);
cmd.env(
"PKG_CONFIG_PATH",
format!("{}/{}/pkgconfig", libkrunfw_install.display(), LIB_DIR),
);
cmd.stdout(Stdio::inherit());
cmd.stderr(Stdio::inherit());
cmd.env_remove("RUSTFLAGS");
cmd.env_remove("CARGO_ENCODED_RUSTFLAGS");
run_command(&mut cmd, "cargo rustc (libkrun staticlib)");
let output_dir = if let Some(ref target) = target {
libkrun_src.join(format!("target/{}/release", target))
} else {
libkrun_src.join("target/release")
};
let src = output_dir.join("libkrun.a");
let dst = lib_dir.join("libkrun.a");
fs::copy(&src, &dst).unwrap_or_else(|e| {
panic!(
"Failed to copy libkrun.a from {} to {}: {}",
src.display(),
dst.display(),
e
)
});
println!("cargo:warning=Built static libkrun at {}", dst.display());
}
}
struct LibFixup;
impl LibFixup {
fn fix_install_name(lib_name: &str, lib_path: &Path) {
let lib_path_str = lib_path.to_str().expect("Invalid library path");
#[cfg(target_os = "macos")]
let mut cmd = {
let mut c = Command::new("install_name_tool");
c.args(["-id", &format!("@rpath/{}", lib_name), lib_path_str]);
c
};
#[cfg(target_os = "linux")]
let mut cmd = {
println!("cargo:warning=Fixing {} in {}", lib_name, lib_path_str);
let mut c = Command::new("patchelf");
c.args(["--set-soname", lib_name, lib_path_str]);
c
};
run_command(&mut cmd, &format!("fix install name for {}", lib_name));
}
#[cfg(target_os = "linux")]
fn extract_major_soname(filename: &str) -> Option<String> {
if let Some(so_pos) = filename.find(".so.") {
let base = &filename[..so_pos + 3];
let versions = &filename[so_pos + 4..];
if let Some(major) = versions.split('.').next() {
return Some(format!("{}.{}", base, major));
}
}
None
}
pub fn fix(lib_dir: &Path, lib_prefix: &str) -> Result<(), String> {
let ext = if cfg!(target_os = "macos") {
".dylib"
} else {
".so"
};
for entry in
fs::read_dir(lib_dir).map_err(|e| format!("Failed to read directory: {}", e))?
{
let entry = entry.map_err(|e| format!("Failed to read entry: {}", e))?;
let path = entry.path();
let filename = path.file_name().unwrap().to_string_lossy().to_string();
if filename.starts_with(lib_prefix) && filename.contains(ext) {
let metadata = fs::symlink_metadata(&path)
.map_err(|e| format!("Failed to get metadata: {}", e))?;
if metadata.file_type().is_symlink() {
continue;
}
#[cfg(target_os = "linux")]
if lib_prefix == "libkrunfw" {
if let Some(soname) = Self::extract_major_soname(&filename) {
if soname != filename {
let new_path = lib_dir.join(&soname);
fs::rename(&path, &new_path)
.map_err(|e| format!("Failed to rename file: {}", e))?;
println!("cargo:warning=Renamed {} to {}", filename, soname);
Self::fix_install_name(&soname, &new_path);
continue;
}
}
}
Self::fix_install_name(&filename, &path);
#[cfg(target_os = "macos")]
{
let sign_status = Command::new("codesign")
.args(["-s", "-", "--force"])
.arg(&path)
.status()
.map_err(|e| format!("Failed to run codesign: {}", e))?;
if !sign_status.success() {
return Err(format!("codesign failed for {}", filename));
}
println!("cargo:warning=Fixed and signed {}", filename);
}
}
}
Ok(())
}
}
#[cfg(target_os = "macos")]
struct MacToolchain {
clang: PathBuf,
path_dirs: Vec<PathBuf>,
}
#[cfg(target_os = "macos")]
impl MacToolchain {
fn setup_libclang_path() {
if env::var("LIBCLANG_PATH").is_ok() {
return;
}
if Command::new("llvm-config")
.arg("--version")
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.is_ok_and(|s| s.success())
{
return;
}
for prefix in ["/opt/homebrew/opt/llvm", "/usr/local/opt/llvm"] {
let lib_path = Path::new(prefix).join("lib");
if lib_path.join("libclang.dylib").exists() {
env::set_var("LIBCLANG_PATH", &lib_path);
return;
}
}
if let Ok(output) = Command::new("brew").args(["--prefix", "llvm"]).output() {
if output.status.success() {
let prefix = String::from_utf8_lossy(&output.stdout).trim().to_string();
let lib_path = format!("{}/lib", prefix);
if Path::new(&lib_path).join("libclang.dylib").exists() {
env::set_var("LIBCLANG_PATH", &lib_path);
}
}
}
}
fn brew_prefix(formula: &str) -> Option<PathBuf> {
let output = Command::new("brew")
.args(["--prefix", formula])
.output()
.ok()?;
if !output.status.success() {
return None;
}
let prefix = String::from_utf8_lossy(&output.stdout).trim().to_string();
if prefix.is_empty() {
return None;
}
Some(PathBuf::from(prefix))
}
fn find_non_apple_clang_in_path() -> Option<PathBuf> {
let version = Command::new("clang").arg("--version").output().ok()?;
if !version.status.success() {
return None;
}
let version_stdout = String::from_utf8_lossy(&version.stdout);
if version_stdout.starts_with("Apple clang") {
return None;
}
let output = Command::new("which").arg("clang").output().ok()?;
if !output.status.success() {
return None;
}
let path = String::from_utf8_lossy(&output.stdout).trim().to_string();
if path.is_empty() {
return None;
}
let path = PathBuf::from(path);
path.exists().then_some(path)
}
fn find_llvm_clang() -> Option<PathBuf> {
if let Some(clang) = Self::find_non_apple_clang_in_path() {
return Some(clang);
}
if let Ok(output) = Command::new("llvm-config").arg("--bindir").output() {
if output.status.success() {
let bindir = String::from_utf8_lossy(&output.stdout).trim().to_string();
if !bindir.is_empty() {
let clang = PathBuf::from(bindir).join("clang");
if clang.exists() {
return Some(clang);
}
}
}
}
for prefix in ["/opt/homebrew/opt/llvm", "/usr/local/opt/llvm"] {
let clang = Path::new(prefix).join("bin/clang");
if clang.exists() {
return Some(clang);
}
}
Self::brew_prefix("llvm")
.map(|prefix| prefix.join("bin/clang"))
.filter(|clang| clang.exists())
}
fn find_lld_bin_dir() -> Option<PathBuf> {
if Command::new("ld.lld")
.arg("--version")
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.is_ok_and(|s| s.success())
{
return None;
}
for prefix in ["/opt/homebrew/opt/lld", "/usr/local/opt/lld"] {
let ld_lld = Path::new(prefix).join("bin/ld.lld");
if ld_lld.exists() {
return ld_lld.parent().map(Path::to_path_buf);
}
}
let ld_lld = Self::brew_prefix("lld")
.map(|prefix| prefix.join("bin/ld.lld"))
.filter(|path| path.exists())?;
ld_lld.parent().map(Path::to_path_buf)
}
fn prepend_path_dirs(path_dirs: &[PathBuf]) -> Option<String> {
if path_dirs.is_empty() {
return None;
}
let existing = env::var("PATH").unwrap_or_default();
let mut merged = String::new();
for dir in path_dirs {
if merged.is_empty() {
merged.push_str(&dir.to_string_lossy());
} else {
merged.push(':');
merged.push_str(&dir.to_string_lossy());
}
}
if existing.is_empty() {
return Some(merged);
}
merged.push(':');
merged.push_str(&existing);
Some(merged)
}
fn discover() -> Result<Self, String> {
if let Ok(cc_linux) = env::var("BOXLITE_LIBKRUN_CC_LINUX") {
let cc_linux = cc_linux.trim().to_string();
if cc_linux.is_empty() {
return Err("BOXLITE_LIBKRUN_CC_LINUX is set but empty".to_string());
}
return Ok(Self {
clang: PathBuf::from(cc_linux),
path_dirs: Vec::new(),
});
}
let clang = Self::find_llvm_clang().ok_or_else(|| {
"libkrun cross-compilation on macOS requires LLVM clang + lld. Run `make setup` (or `brew install llvm lld`) and retry."
.to_string()
})?;
let mut path_dirs = Vec::new();
if let Some(dir) = clang.parent() {
path_dirs.push(dir.to_path_buf());
}
if let Some(lld_dir) = Self::find_lld_bin_dir() {
path_dirs.push(lld_dir);
}
Ok(Self { clang, path_dirs })
}
fn into_make_args(self) -> Result<(String, HashMap<String, String>), String> {
if env::var("BOXLITE_LIBKRUN_CC_LINUX").is_ok() {
let cc_linux = self.clang.to_string_lossy().to_string();
return Ok((format!("CC_LINUX={}", cc_linux), HashMap::new()));
}
let path_override = Self::prepend_path_dirs(&self.path_dirs);
let mut ld_lld_cmd = Command::new("ld.lld");
ld_lld_cmd
.arg("--version")
.stdout(Stdio::null())
.stderr(Stdio::null());
if let Some(ref path) = path_override {
ld_lld_cmd.env("PATH", path);
}
if !ld_lld_cmd.status().is_ok_and(|s| s.success()) {
return Err(
"Missing `ld.lld` (LLVM linker). Install it with `make setup` (or `brew install lld`)."
.to_string(),
);
}
println!(
"cargo:warning=Using LLVM clang for libkrun init cross-compile: {}",
self.clang.display()
);
let linux_target_triple = match env::var("CARGO_CFG_TARGET_ARCH")
.unwrap_or_else(|_| "$(ARCH)".to_string())
.as_str()
{
"arm64" | "aarch64" => "aarch64-linux-gnu".to_string(),
"x86_64" => "x86_64-linux-gnu".to_string(),
arch => format!("{arch}-linux-gnu"),
};
let clang_escaped = {
let s = self.clang.to_string_lossy();
format!("'{}'", s.replace('\'', "'\\''"))
};
let cc_linux = format!(
"{} -target {} -fuse-ld=lld -Wl,-strip-debug --sysroot $(SYSROOT_LINUX) -Wno-c23-extensions",
clang_escaped,
linux_target_triple
);
let mut env_overrides = HashMap::new();
if let Some(path) = path_override {
env_overrides.insert("PATH".to_string(), path);
}
Ok((format!("CC_LINUX={}", cc_linux), env_overrides))
}
pub fn resolve() -> Result<(String, HashMap<String, String>), String> {
Self::setup_libclang_path();
Self::discover()?.into_make_args()
}
}
#[cfg(target_os = "macos")]
fn build() {
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let libkrunfw_install = out_dir.join("libkrunfw");
let libkrun_install = out_dir.join("libkrun");
let libkrunfw_lib = libkrunfw_install.join(LIB_DIR);
println!("cargo:warning=Building libkrunfw for macOS...");
let libkrunfw_src = download_libkrunfw_prebuilt(&out_dir);
build_with_make(
&libkrunfw_src,
&libkrunfw_install,
"libkrunfw",
&HashMap::new(),
&[],
);
LibFixup::fix(&libkrunfw_lib, "libkrunfw")
.unwrap_or_else(|e| panic!("Failed to fix libkrunfw: {}", e));
println!("cargo:LIBKRUNFW_BOXLITE_DEP={}", libkrunfw_lib.display());
if cfg!(feature = "krun") {
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
println!("cargo:warning=Building libkrun for macOS (static)...");
verify_vendored_sources(&manifest_dir, false);
let libkrun_src = manifest_dir.join("vendor/libkrun");
let (cc_linux_make_arg, env_overrides) =
MacToolchain::resolve().unwrap_or_else(|e| panic!("{}", e));
LibBuilder::build(
&libkrun_src,
&libkrun_install,
&libkrunfw_install,
&env_overrides,
&[cc_linux_make_arg],
);
let libkrun_lib = libkrun_install.join(LIB_DIR);
println!("cargo:LIBKRUN_BOXLITE_DEP={}", libkrun_lib.display());
println!("cargo:rustc-link-search=native={}", libkrun_lib.display());
println!("cargo:rustc-link-lib=static=krun");
println!("cargo:rustc-link-lib=framework=Hypervisor");
}
}
#[cfg(target_os = "linux")]
fn build() {
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let libkrunfw_install = out_dir.join("libkrunfw");
let libkrun_install = out_dir.join("libkrun");
let libkrunfw_lib_dir = libkrunfw_install.join(LIB_DIR);
let build_from_source = env::var("BOXLITE_BUILD_LIBKRUNFW").is_ok();
if build_from_source {
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
println!("cargo:warning=Building libkrunfw from source (BOXLITE_BUILD_LIBKRUNFW=1)");
verify_vendored_sources(&manifest_dir, true);
let libkrunfw_src = manifest_dir.join("vendor/libkrunfw");
build_with_make(
&libkrunfw_src,
&libkrunfw_install,
"libkrunfw",
&HashMap::new(),
&[],
);
} else {
println!("cargo:warning=Downloading pre-compiled libkrunfw...");
download_libkrunfw_so(&libkrunfw_install);
}
LibFixup::fix(&libkrunfw_lib_dir, "libkrunfw")
.unwrap_or_else(|e| panic!("Failed to fix libkrunfw: {}", e));
println!(
"cargo:LIBKRUNFW_BOXLITE_DEP={}",
libkrunfw_lib_dir.display()
);
if cfg!(feature = "krun") {
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
println!("cargo:warning=Building libkrun for Linux (static)...");
verify_vendored_sources(&manifest_dir, false);
let libkrun_src = manifest_dir.join("vendor/libkrun");
LibBuilder::build(
&libkrun_src,
&libkrun_install,
&libkrunfw_install,
&HashMap::new(),
&[],
);
let libkrun_lib = libkrun_install.join(LIB_DIR);
println!("cargo:LIBKRUN_BOXLITE_DEP={}", libkrun_lib.display());
println!("cargo:rustc-link-search=native={}", libkrun_lib.display());
println!("cargo:rustc-link-lib=static=krun");
}
}
fn main() {
println!("cargo:rerun-if-changed=vendor/libkrun");
println!("cargo:rerun-if-changed=vendor/libkrunfw");
println!("cargo:rerun-if-env-changed=BOXLITE_DEPS_STUB");
#[cfg(target_os = "macos")]
println!("cargo:rerun-if-env-changed=BOXLITE_LIBKRUN_CC_LINUX");
if env::var("BOXLITE_DEPS_STUB").is_err() {
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
if manifest_dir.join(".cargo_vcs_info.json").exists() {
unsafe { env::set_var("BOXLITE_DEPS_STUB", "1") };
}
}
if env::var("BOXLITE_DEPS_STUB").is_ok() {
println!("cargo:warning=BOXLITE_DEPS_STUB mode: skipping libkrun build");
println!("cargo:LIBKRUN_BOXLITE_DEP=/nonexistent");
println!("cargo:LIBKRUNFW_BOXLITE_DEP=/nonexistent");
return;
}
let need_build = cfg!(feature = "krunfw") || cfg!(feature = "krun");
if !need_build {
println!("cargo:warning=libkrun-sys: no build features enabled, skipping native builds");
return;
}
build();
}