use std::process::Command;
use std::fs;
use std::path::Path;
const LINUX_ONLY: &[&str] = &[
"init",
"vm_daemon",
"insmod",
"modprobe",
"mount",
"mountpoint",
"umount",
"df",
"ifconfig",
"route",
"deb_systemd_helper",
"dpkg",
"dpkg_divert",
"dpkg_maintscript_helper",
"dpkg_query",
"dpkg_realpath",
"dpkg_statoverride",
"update_alternatives",
"rpm",
"rpmlua",
"apk",
"apt",
"apt_get",
"dnf",
"yum",
"systemd_sysusers",
"systemd_tmpfiles",
];
const UNIX_ONLY: &[&str] = &[
"chgrp",
"chmod",
"chown",
"chroot",
"addgroup",
"adduser",
"delgroup",
"deluser",
"groupadd",
"groupdel",
"useradd",
"userdel",
"usermod",
"kill",
"killall",
"nice",
"nohup",
"pidof",
"pkill",
"install",
"mkfifo",
"stat",
"sync",
"tar",
"tty",
"dpkg_trigger",
];
fn main() {
let git_hash = get_git_hash();
let build_date = get_build_date();
let build_time = get_build_time();
let epkg_version_info = format!("version {} (build date {}, commit {})",
env!("CARGO_PKG_VERSION"),
build_date,
git_hash);
println!("cargo:rustc-env=GIT_HASH={}", git_hash);
println!("cargo:rustc-env=BUILD_DATE={}", build_date);
println!("cargo:rustc-env=BUILD_TIME={}", build_time);
println!("cargo:rustc-env=EPKG_VERSION_TAG=v{}", env!("CARGO_PKG_VERSION"));
println!("cargo:rustc-env=EPKG_VERSION_INFO={}", epkg_version_info);
println!("cargo:rerun-if-changed=.git/HEAD");
println!("cargo:rerun-if-changed=.git/index");
println!("cargo::rustc-check-cfg=cfg(epkg_ntfs_ea)");
println!("cargo::rustc-check-cfg=cfg(feature, values(\"embedded_init\"))");
println!("cargo:rustc-cfg=epkg_ntfs_ea");
println!(
"cargo:rerun-if-changed=git/libkrun/src/devices/src/virtio/fs/windows/win32_pua_paths.rs"
);
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
if target_os == "macos" && std::env::var("CARGO_FEATURE_LIBKRUN").is_ok() {
println!("cargo:rustc-link-lib=framework=Hypervisor");
}
generate_busybox_modules();
}
fn generate_busybox_modules() {
let busybox_dir = Path::new("src/busybox");
let out_dir = std::env::var("OUT_DIR").unwrap();
let registrations_path = Path::new(&out_dir).join("busybox_modules.rs");
let mut modules = Vec::new();
let mut registrations = Vec::new();
if let Ok(entries) = fs::read_dir(busybox_dir) {
for entry in entries {
if let Ok(entry) = entry {
let path = entry.path();
if path.is_file() {
if let Some(filename) = path.file_name().and_then(|n| n.to_str()) {
if filename == "mod.rs"
|| filename.starts_with("_")
|| !filename.ends_with(".rs") {
continue;
}
let module_name = filename.trim_end_matches(".rs");
let cmd_name = if module_name == "bracket" {
"[".to_string()
} else {
let cmd_name_str = if module_name.ends_with("_cmd") {
&module_name[..module_name.len() - 4]
} else {
module_name
};
cmd_name_str.replace('_', "-")
};
let is_linux_only = LINUX_ONLY.contains(&module_name);
let is_unix_only = UNIX_ONLY.contains(&module_name);
modules.push((module_name.to_string(), is_linux_only, is_unix_only));
registrations.push((module_name.to_string(), cmd_name.to_string(), is_linux_only, is_unix_only));
}
}
}
}
}
modules.sort();
registrations.sort_by(|a, b| a.1.cmp(&b.1));
let mut decl_code = String::new();
decl_code.push_str("// Auto-generated module declarations - do not edit manually\n");
for (module, is_linux_only, is_unix_only) in &modules {
if *is_linux_only {
decl_code.push_str(&format!("#[cfg(target_os = \"linux\")]\n"));
} else if *is_unix_only {
decl_code.push_str(&format!("#[cfg(unix)]\n"));
}
decl_code.push_str(&format!("pub mod {};\n", module));
}
let mut reg_code = String::new();
reg_code.push_str("// Auto-generated by build.rs - do not edit manually\n");
reg_code.push_str("// Auto-register all applets found in src/busybox/\n\n");
let mut linux_only_cmds: Vec<&str> = Vec::new();
let mut unix_only_cmds: Vec<&str> = Vec::new();
let mut common_cmds: Vec<&str> = Vec::new();
for (_module_name, cmd_name, is_linux_only, is_unix_only) in ®istrations {
if *is_linux_only {
linux_only_cmds.push(cmd_name.as_str());
} else if *is_unix_only {
unix_only_cmds.push(cmd_name.as_str());
} else {
common_cmds.push(cmd_name.as_str());
}
}
reg_code.push_str("/// Applets that only make sense on Linux (kernel-specific features, RPM/Debian integration).\n");
reg_code.push_str("pub const LINUX_ONLY_APPLETS: &[&str] = &[\n");
for applet in &linux_only_cmds {
reg_code.push_str(&format!(" \"{}\",\n", applet));
}
reg_code.push_str("];\n\n");
reg_code.push_str("/// Applets that use POSIX/Unix APIs not available on Windows.\n");
reg_code.push_str("pub const UNIX_ONLY_APPLETS: &[&str] = &[\n");
for applet in &unix_only_cmds {
reg_code.push_str(&format!(" \"{}\",\n", applet));
}
reg_code.push_str("];\n\n");
reg_code.push_str("/// Applets available on all platforms (not Linux-only or Unix-only).\n");
reg_code.push_str("pub const COMMON_APPLETS: &[&str] = &[\n");
for applet in &common_cmds {
reg_code.push_str(&format!(" \"{}\",\n", applet));
}
reg_code.push_str("];\n\n");
reg_code.push_str("register_busybox_applets! {\n");
for (module, cmd_name, is_linux_only, is_unix_only) in ®istrations {
if *is_linux_only {
reg_code.push_str(&format!("#[cfg(target_os = \"linux\")]\n"));
} else if *is_unix_only {
reg_code.push_str(&format!("#[cfg(unix)]\n"));
}
reg_code.push_str(&format!(" ({}, \"{}\"),\n", module, cmd_name));
}
reg_code.push_str("}\n");
fs::write(®istrations_path, reg_code).expect("Failed to write generated busybox_modules.rs");
let decl_file = busybox_dir.join("_modules_gen.rs");
let should_write = match fs::read_to_string(&decl_file) {
Ok(existing) => existing != decl_code,
Err(_) => true,
};
if should_write {
fs::write(&decl_file, decl_code).expect("Failed to write generated _modules_gen.rs");
}
println!("cargo:rerun-if-changed=src/busybox");
}
fn get_git_hash() -> String {
Command::new("git")
.args(&["rev-parse", "--short", "HEAD"])
.output()
.ok()
.and_then(|output| {
if output.status.success() {
String::from_utf8(output.stdout).ok()
} else {
None
}
})
.map(|s| s.trim().to_string())
.unwrap_or_else(|| "unknown".to_string())
}
fn get_build_date() -> String {
use time::OffsetDateTime;
OffsetDateTime::now_utc()
.format(&time::format_description::parse("[year]-[month]-[day]").unwrap())
.unwrap_or_else(|_| "unknown".to_string())
}
fn get_build_time() -> String {
use time::OffsetDateTime;
let format = time::format_description::parse("[year]-[month]-[day] [hour]:[minute]:[second] [offset_hour sign:mandatory][offset_minute]")
.unwrap();
OffsetDateTime::now_local()
.ok()
.and_then(|t| t.format(&format).ok())
.unwrap_or_else(|| "unknown".to_string())
}