#!/bin/bash
set -e
LUA_VERSION=5.4.8
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
OUTPUT_DIR=dist
FEATURES="${FEATURES:-auto}"
setup_windows_temp() {
if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" || "$OSTYPE" == "win32" ]]; then
if [[ -z "${TMP:-}" || ! -w "${TMP:-}" ]]; then
local user_temp
if [[ -n "${USERPROFILE:-}" ]]; then
user_temp="${USERPROFILE}/AppData/Local/Temp"
elif [[ -n "${HOME:-}" ]]; then
user_temp="${HOME}/.cache/tmp"
else
user_temp="/tmp"
fi
mkdir -p "$user_temp" 2>/dev/null || true
export TMP="$user_temp"
export TEMP="$user_temp"
echo "Windows: set TMP/TEMP to $user_temp"
fi
fi
}
setup_windows_temp
if [[ -d "$HOME/.cargo/bin" ]]; then
export PATH="$HOME/.cargo/bin:$PATH"
fi
RUSTUP_CARGO_DIR="${RUSTUP_HOME:-$HOME/.rustup}/toolchains/stable-$(uname -m | sed 's/amd64/x86_64/;s/arm64/aarch64/')-apple-darwin/bin"
if [[ -d "$RUSTUP_CARGO_DIR" ]]; then
export PATH="$RUSTUP_CARGO_DIR:$PATH"
fi
if [[ -d "/mingw64/bin" ]]; then
export PATH="/mingw64/bin:$PATH"
fi
RUST_TARGET_X86_64=x86_64-unknown-linux-musl
RUST_TARGET_AARCH64=aarch64-unknown-linux-musl
RUST_TARGET_RISCV64=riscv64gc-unknown-linux-musl
RUST_TARGET_LOONGARCH64=loongarch64-unknown-linux-musl
RUST_TARGET_X86_64_DARWIN=x86_64-apple-darwin
RUST_TARGET_AARCH64_DARWIN=aarch64-apple-darwin
RUST_TARGET_X86_64_WINDOWS=x86_64-pc-windows-gnu
RUST_TARGET_AARCH64_WINDOWS=aarch64-pc-windows-gnu
BINARY_NAME=epkg
detect_native_arch() {
local uname_m=$(uname -m)
case "$uname_m" in
x86_64|amd64)
echo "x86_64"
;;
i386|i686)
echo "x86_64"
;;
arm64|aarch64)
echo "aarch64"
;;
armv7l|armv8l)
echo "arm"
;;
riscv64)
echo "riscv64"
;;
loongarch64)
echo "loongarch64"
;;
*)
echo "$uname_m"
;;
esac
}
DEV_ENV_BIN_DIR="$HOME/.epkg/envs/self/usr/bin"
DEV_ENV_SRC_DIR="$HOME/.epkg/envs/self/usr/src/epkg"
safe_cp() {
local src="$1"
local dst="$2"
local cp_output cp_status
mkdir -p "$(dirname "$dst")" || return $?
if [[ -f "$dst" ]] && [[ ! "$src" -nt "$dst" ]]; then
return 0
fi
cp_output=$(cp "$src" "$dst" 2>&1) && return 0 || {
cp_status=$?
if echo "$cp_output" | grep -q "Text file busy\|Permission denied"; then
rm -f "$dst" 2>/dev/null || true
if cp "$src" "$dst" 2>&1; then
return 0
fi
echo "Warning: failed to copy to $dst (permission issue)" >&2
return 1
else
echo "$cp_output" >&2
return $cp_status
fi
}
}
install_hardlink() {
local src="$1"
local dst="$2"
local tmp="${dst}.tmp.$$"
mkdir -p "$(dirname "$dst")" || return $?
if [[ -f "$dst" ]] && [[ "$src" -ef "$dst" ]]; then
return 0
fi
if ln -f "$src" "$tmp" 2>/dev/null; then
mv -f "$tmp" "$dst" || {
rm -f "$tmp" 2>/dev/null
safe_cp "$src" "$dst"
}
else
rm -f "$tmp" 2>/dev/null
safe_cp "$src" "$dst"
fi
}
deploy_release_binary() {
local src="$1"
local out_name="$2"
local out_path="$OUTPUT_DIR/$out_name"
mkdir -p "$OUTPUT_DIR"
if [[ -f "$out_path" ]]; then
rm -f "$out_path"
fi
if ln "$src" "$out_path" 2>/dev/null; then
:
elif ! safe_cp "$src" "$out_path"; then
echo "Warning: failed to deploy $out_name to $out_path" >&2
return 1
fi
pushd "$OUTPUT_DIR" >/dev/null
sha256sum "$out_name" > "$out_name.sha256"
popd >/dev/null
printf "[DEPLOY-hardlink] %s/%s/%s\n" "$PROJECT_ROOT" "$OUTPUT_DIR" "$out_name"
}
deploy_to_windows_self() {
local src="$1"
local win_install_name="$2"
local win_self_usr_bin_dir=""
local win_profile_wsl=""
if ! win_profile_wsl="$(get_windows_user_profile_wsl 2>/dev/null)"; then
return 1
fi
win_self_usr_bin_dir="${win_profile_wsl}/.epkg/envs/self/usr/bin"
[[ -d "$win_self_usr_bin_dir" ]] || mkdir -p "$win_self_usr_bin_dir"
local dst="$win_self_usr_bin_dir/$win_install_name"
if cp "$src" "$dst" 2>/dev/null; then
printf "[DEPLOY-XDEV-copy] %s\n" "$dst"
return 0
fi
echo ""
echo "=========================================="
echo "WARNING: Cannot update $win_install_name"
echo "File is locked by running Windows process(es)."
echo "Location: $dst"
echo ""
echo "Detected running epkg.exe process(es):"
/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe -Command "Get-CimInstance Win32_Process -Filter \"Name='epkg.exe'\" | Select-Object ProcessId,ProcessName,CommandLine | Format-List" 2>/dev/null | sed 's/\r$//' | grep -v '^[[:space:]]*$' || /mnt/c/Windows/System32/tasklist.exe /FI "IMAGENAME eq epkg.exe" /FO TABLE 2>/dev/null || echo " (cannot get process details)"
echo ""
echo "Auto-killing epkg.exe process(es)..."
/mnt/c/Windows/System32/taskkill.exe /F /IM epkg.exe 2>/dev/null || \
/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe -Command "Get-Process epkg -ErrorAction SilentlyContinue | Stop-Process -Force" 2>/dev/null || true
sleep 1
echo "Removing locked file..."
rm -f "$dst" 2>/dev/null || true
echo "=========================================="
if cp "$src" "$dst" 2>/dev/null; then
printf "[DEPLOY-XDEV-copy] %s\n" "$dst"
return 0
fi
echo "Warning: still failed to deploy to $dst" >&2
return 1
}
get_windows_user_profile_wsl() {
local win_profile="${USERPROFILE:-}"
if [[ -n "$win_profile" && "$win_profile" == *"\\Users\\"* && -x /usr/bin/wslpath ]]; then
wslpath -u "$win_profile" 2>/dev/null || true
return 0
fi
local cmd="/mnt/c/Windows/System32/cmd.exe"
if [[ ! -x "$cmd" ]]; then
return 1
fi
local out win
out="$("$cmd" /c echo %USERPROFILE% 2>/dev/null || true)"
win="$(printf "%s" "$out" | awk 'match($0,/[A-Za-z]:\\\\Users\\\\[^[:space:]]+/){print substr($0,RSTART,RLENGTH); exit}')"
if [[ -n "$win" ]]; then
local win_profile_wsl
win_profile_wsl="$(wslpath -u "$win" 2>/dev/null)" || win_profile_wsl=""
if [[ -n "$win_profile_wsl" ]]; then
echo "$win_profile_wsl"
return 0
fi
fi
local whoami_cmd="/mnt/c/Windows/System32/whoami.exe"
if [[ -x "$whoami_cmd" ]]; then
local whoami_out win_user
whoami_out="$("$whoami_cmd" 2>/dev/null || true)"
win_user="$(printf "%s" "$whoami_out" | awk -F'\\\\' 'NF>=2 {u=$NF; gsub(/\r/, "", u); print u; exit}')"
if [[ -n "$win_user" ]]; then
local user_profile_guess="/mnt/c/Users/${win_user}"
if [[ -d "$user_profile_guess" ]]; then
echo "$user_profile_guess"
return 0
fi
fi
fi
return 1
}
detect_os() {
local uname_s=$(uname -s)
case "$uname_s" in
Linux)
OS_FAMILY="linux"
if [[ -f /etc/os-release ]]; then
OS_ID=$(grep -E '^ID=' /etc/os-release | cut -d= -f2 | tr -d '"')
OS_VERSION=$(grep -E '^VERSION_ID=' /etc/os-release | cut -d= -f2 | tr -d '"')
else
OS_ID="linux"
OS_VERSION="unknown"
fi
;;
Darwin)
OS_FAMILY="darwin"
OS_ID="darwin"
OS_VERSION=$(sw_vers -productVersion 2>/dev/null || echo "unknown")
;;
CYGWIN*|MINGW*|MSYS*)
OS_FAMILY="windows"
OS_ID="windows"
OS_VERSION="unknown"
;;
*)
OS_FAMILY="unknown"
OS_ID="unknown"
OS_VERSION="unknown"
;;
esac
echo "Detected OS: $OS_FAMILY $OS_ID $OS_VERSION"
}
has_cmd()
{
command -v "$1" >/dev/null
}
is_wsl2() {
if [[ -f /proc/version ]]; then
if grep -q "microsoft-standard-WSL" /proc/version 2>/dev/null; then
return 0
fi
fi
return 1
}
ensure_cross_windows_depends() {
local mingw_prefix=$(detect_mingw)
if [[ -n "$mingw_prefix" ]]; then
echo "[CHECK] mingw-w64 cross-compiler found: ${mingw_prefix}-gcc"
return 0
fi
echo "[CHECK] mingw-w64 cross-compiler not found"
if is_wsl2 && has_cmd apt; then
echo "[INSTALL] Installing mingw-w64 for Windows cross-compilation..."
local SUDO=""
[[ $(id -u) -ne 0 ]] && SUDO="sudo"
$SUDO apt-get update -qq
$SUDO apt-get install -y gcc-mingw-w64-x86-64
echo "[INSTALL-OK] mingw-w64 installed"
return 0
fi
echo "[WARN] mingw-w64 cross-compiler not available"
echo " On Debian/Ubuntu: sudo apt-get install gcc-mingw-w64-x86-64"
echo " On Fedora/RHEL: sudo dnf install mingw64-gcc"
echo " On Arch Linux: sudo pacman -S mingw-w64-gcc"
return 1
}
detect_package_manager() {
case "$OS_FAMILY" in
linux)
if has_cmd apt; then PKG_MANAGER="apt"
elif has_cmd dnf; then PKG_MANAGER="dnf"
elif has_cmd yum; then PKG_MANAGER="yum"
elif has_cmd zypper; then PKG_MANAGER="zypper"
elif has_cmd pacman; then PKG_MANAGER="pacman"
elif has_cmd apk; then PKG_MANAGER="apk"
else
PKG_MANAGER="unknown"
echo "Warning: Could not detect Linux package manager"
exit 1
fi
;;
darwin)
if has_cmd brew; then
PKG_MANAGER="brew"
else
PKG_MANAGER="unknown"
echo "Warning: Homebrew not found. Some dependencies may need manual installation."
fi
;;
windows)
if has_cmd pacman; then
PKG_MANAGER="pacman"
elif has_cmd choco; then
PKG_MANAGER="choco"
elif has_cmd scoop; then
PKG_MANAGER="scoop"
else
PKG_MANAGER="unknown"
echo "Warning: No package manager detected (pacman/choco/scoop). Some dependencies may need manual installation."
fi
;;
*)
PKG_MANAGER="unknown"
echo "Warning: Unknown OS family, cannot detect package manager"
;;
esac
echo "Detected package manager: $PKG_MANAGER"
}
install_kernel_for_libkrun() {
local arch="$1"
[[ "$arch" != $(arch) ]] && return
case "$arch" in
x86_64|aarch64|riscv64)
;;
loongarch64)
echo "Warning: libkrun not available for loongarch64, VM feature won't be usable" >&2
return 0
;;
*)
echo "Warning: libkrun not available for $arch, VM feature won't be usable" >&2
return 0
;;
esac
local self_boot_dir="${HOME}/.epkg/envs/self/boot"
if [[ -n "${USERPROFILE:-}" ]]; then
self_boot_dir="${USERPROFILE//\\//}/.epkg/envs/self/boot"
fi
local self_boot_kernel="${self_boot_dir}/kernel"
if [[ -L "$self_boot_kernel" ]] || [[ -f "$self_boot_kernel" ]]; then
return 0
fi
if [[ -f "${HOME}/.epkg/envs/self/boot/kernel" ]]; then
return 0
fi
local kernel_prefix="vmlinux"
local kernel_path="$PROJECT_ROOT/git/sandbox-kernel/linux-stable/vmlinux"
if [[ "$arch" == "aarch64" || "$arch" == "riscv64" ]]; then
kernel_prefix="Image"
if [[ -f "$PROJECT_ROOT/git/sandbox-kernel/linux-stable/arch/arm64/boot/Image" ]]; then
kernel_path="$PROJECT_ROOT/git/sandbox-kernel/linux-stable/arch/arm64/boot/Image"
elif [[ -f "$PROJECT_ROOT/git/sandbox-kernel/linux-stable/arch/riscv/boot/Image" ]]; then
kernel_path="$PROJECT_ROOT/git/sandbox-kernel/linux-stable/arch/riscv/boot/Image"
fi
fi
if [[ -f "$kernel_path" ]]; then
mkdir -p "$self_boot_dir"
local version
version=$(strings "$kernel_path" 2>/dev/null | grep -m1 "Linux version " | awk '{print $3}')
version=$(echo "$version" | tr -cd '0-9.-')
if [[ -n "$version" ]]; then
local named_kernel="${self_boot_dir}/${kernel_prefix}-${version}-${arch}"
cp -l "$kernel_path" "$named_kernel" 2>/dev/null || cp "$kernel_path" "$named_kernel"
ln -sf "${kernel_prefix}-${version}-${arch}" "$self_boot_dir/kernel"
echo "Installed kernel from local build: ${kernel_prefix}-${version}-${arch}"
else
local generic_name="${self_boot_dir}/${kernel_prefix}"
cp -l "$kernel_path" "$generic_name" 2>/dev/null || cp "$kernel_path" "$generic_name"
ln -sf "$kernel_prefix" "$self_boot_dir/kernel"
echo "Installed kernel from local build: $kernel_prefix"
fi
return 0
fi
echo "Note: No kernel found for libkrun VM. Run 'epkg self install' to download one." >&2
echo " Or build one with: cd git/sandbox-kernel && ./scripts/build.sh $arch" >&2
}
clone_or_update_repo() {
local repo_url="$1"
local dir_name="$2"
if ! has_cmd git; then
echo "Error: git command not found. Please install git or run './make.sh dev-depends' to install dependencies." >&2
exit 1
fi
if [[ -z "$dir_name" ]]; then
dir_name="${repo_url##*/}"
dir_name="${dir_name%.git}"
fi
if [[ -d "$dir_name" ]]; then
if [[ -n "$(ls -A "$dir_name" 2>/dev/null)" ]]; then
echo "Directory $dir_name already exists and is not empty, attempting to update..."
if [[ -d "$dir_name/.git" ]]; then
(cd "$dir_name" && git pull)
else
echo "Warning: $dir_name exists but is not a git repository, skipping update"
fi
else
echo "Directory $dir_name exists but is empty, removing..."
rmdir "$dir_name"
echo "Cloning $repo_url..."
git clone "$repo_url" "$dir_name"
fi
else
echo "Cloning $repo_url..."
git clone "$repo_url" "$dir_name"
fi
}
cd "$PROJECT_ROOT"
install_to_dev_env() {
local binary_path="$1"
local arch="${2:-$HOST_ARCH}"
[[ -d "$DEV_ENV_BIN_DIR" ]] || return 0
if [[ ! -L "$DEV_ENV_SRC_DIR" ]] || [[ "$(readlink "$DEV_ENV_SRC_DIR")" != "$(pwd)" ]]; then
local src_rc="$PROJECT_ROOT/assets/shell/epkg.sh"
local dst_rc="$DEV_ENV_SRC_DIR/assets/shell/epkg.sh"
if [[ "$(readlink -f "$src_rc")" != "$(readlink -f "$dst_rc")" ]]; then
safe_cp "$src_rc" "$dst_rc"
fi
local src_ps1="$PROJECT_ROOT/assets/shell/epkg.ps1"
local dst_ps1="$DEV_ENV_SRC_DIR/assets/shell/epkg.ps1"
if [[ ! -f "$dst_ps1" ]] || ! cmp -s "$src_ps1" "$dst_ps1"; then
safe_cp "$src_ps1" "$dst_ps1"
fi
fi
install_hardlink "$binary_path" "$DEV_ENV_BIN_DIR/$BINARY_NAME"
local host_os=$(uname -s)
if [[ "$host_os" == "Linux" ]]; then
local epkg_linux_name="epkg-linux-${arch}"
if [[ "$(basename "$binary_path")" != "$epkg_linux_name" ]]; then
install_hardlink "$DEV_ENV_BIN_DIR/$BINARY_NAME" "$DEV_ENV_BIN_DIR/$epkg_linux_name"
fi
update_all_env_hardlinks "$DEV_ENV_BIN_DIR/$epkg_linux_name"
fi
}
update_all_env_hardlinks() {
local self_epkg_linux="$1"
local envs_dir="${DEV_ENV_BIN_DIR%/*/*/*}"
local updated_count=0
[[ -f "$self_epkg_linux" ]] || return 0
for env_dir in "$envs_dir"/*; do
[[ -d "$env_dir" ]] || continue
local env_name="${env_dir##*/}"
[[ "$env_name" == "self" ]] && continue
local env_usr_bin="$env_dir/usr/bin"
[[ -d "$env_usr_bin" ]] || continue
local host_os=$(uname -s)
if [[ "$host_os" == "Linux" ]]; then
local filenames="epkg init"
else
local filenames="epkg init"
fi
for filename in $filenames; do
local target_path="$env_usr_bin/$filename"
local should_update=false
if [[ -f "$target_path" ]]; then
should_update=true
elif [[ "$filename" == "epkg" && -f "$env_usr_bin/init" ]]; then
should_update=true
echo "[SYNC-create] $target_path (missing epkg, init exists)"
fi
if [[ "$should_update" == "true" ]]; then
if [[ -f "$target_path" ]] && [[ "$target_path" -ef "$self_epkg_linux" ]]; then
continue
fi
rm -f "$target_path" 2>/dev/null || true
if ln "$self_epkg_linux" "$target_path" 2>/dev/null; then
updated_count=$((updated_count + 1))
echo "[SYNC-hardlink] $target_path"
else
echo "[WARN-hardlink] $target_path: failed"
fi
fi
done
done
if [[ $updated_count -gt 0 ]]; then
echo "[SYNC-total] Updated $updated_count hardlinks in environments"
fi
local win_profile_wsl
win_profile_wsl="$(get_windows_user_profile_wsl 2>/dev/null)" || true
if [[ -n "$win_profile_wsl" ]]; then
local win_envs_dir="${win_profile_wsl}/.epkg/envs"
if [[ -d "$win_envs_dir" ]]; then
local win_source_file=""
for env_dir in "$win_envs_dir"/*; do
[[ -d "$env_dir" ]] || continue
local env_name="${env_dir##*/}"
[[ "$env_name" == "self" ]] && continue
local env_usr_bin="$env_dir/usr/bin"
[[ -d "$env_usr_bin" ]] || continue
for filename in epkg init; do
local target_path="$env_usr_bin/$filename"
local should_update=false
if [[ -f "$target_path" ]]; then
should_update=true
elif [[ "$filename" == "epkg" && -f "$env_usr_bin/init" ]]; then
should_update=true
echo "[SYNC-create] $target_path (missing epkg, init exists)"
fi
if [[ "$should_update" == "true" ]]; then
local src_size target_size
src_size=$(stat -c%s "$self_epkg_linux" 2>/dev/null) || continue
if [[ -f "$target_path" ]]; then
target_size=$(stat -c%s "$target_path" 2>/dev/null) || continue
if [[ "$src_size" == "$target_size" ]]; then
local src_mtime target_mtime
src_mtime=$(stat -c%Y "$self_epkg_linux" 2>/dev/null) || continue
target_mtime=$(stat -c%Y "$target_path" 2>/dev/null) || continue
if [[ "$src_mtime" -le "$target_mtime" ]]; then
continue
fi
fi
rm -f "$target_path"
fi
if [[ -n "$win_source_file" ]]; then
if ln "$win_source_file" "$target_path" 2>/dev/null; then
updated_count=$((updated_count + 1))
echo "[SYNC-hardlink] $target_path"
continue
fi
fi
if cp "$self_epkg_linux" "$target_path"; then
updated_count=$((updated_count + 1))
if [[ -z "$win_source_file" ]]; then
win_source_file="$target_path"
echo "[SYNC-XDEV-copy] $target_path (source for hardlinks)"
else
echo "[SYNC-copy] $target_path"
fi
else
echo "[WARN-copy] $target_path: failed"
fi
fi
done
done
fi
fi
if [[ $updated_count -gt 0 ]]; then
echo "[SYNC-total] $updated_count files updated (WSL + Windows environments)"
fi
}
download_and_extract_lua() {
local lua_download_dir="$1"
local lua_build_dir="$2"
if ! has_cmd wget; then
echo "Error: wget command not found. Please install wget or run './make.sh dev-depends' to install dependencies." >&2
exit 1
fi
mkdir -p "$lua_download_dir"
local tarball="$lua_download_dir/lua-$LUA_VERSION.tar.gz"
[ -f "$tarball" ] || wget --no-verbose "https://www.lua.org/ftp/lua-$LUA_VERSION.tar.gz" -O "$tarball"
mkdir -p "$lua_build_dir"
cd "$lua_build_dir"
[ -d "lua-$LUA_VERSION" ] || tar xzf "$tarball"
}
build_and_deploy_lua() {
local lua_build_dir="$1"
local lua_lib_dir="$2"
local compiler=""
set_lua_compiler
cd "$lua_build_dir/lua-$LUA_VERSION"
rm -f src/liblua.a
make clean
make CC="$compiler" CFLAGS="-O2 -Wall -fPIC -D_FILE_OFFSET_BITS=64 -U_LARGEFILE64_SOURCE" linux
echo "Adding musl compatibility shims..."
$compiler -O2 -Wall -fPIC -D_FILE_OFFSET_BITS=64 -U_LARGEFILE64_SOURCE -c -o src/musl_compat.o "$PROJECT_ROOT/lib/musl_compat.c" || echo "Failed to compile musl_compat.c"
ar rs src/liblua.a src/musl_compat.o || echo "Failed to add musl_compat.o to liblua.a"
mkdir -p "$lua_lib_dir"
cp src/liblua.a "$lua_lib_dir/"
cp src/lua.h src/lualib.h src/lauxlib.h src/lua.hpp src/luaconf.h "$lua_lib_dir/"
}
set_lua_compiler() {
compiler=$(get_c_compiler "$arch" "$lib_type")
echo "Building Lua library for $arch ($lib_type) using $compiler..."
}
build_lua_lib() {
local arch=$(get_arch "$1")
local lib_type="${2:-musl}"
local lua_download_dir="$PROJECT_ROOT/target/lua-download"
local lua_build_dir="$PROJECT_ROOT/target/lua-build-$arch-$lib_type"
local lua_lib_dir="$PROJECT_ROOT/target/lua-$lib_type-$arch"
download_and_extract_lua "$lua_download_dir" "$lua_build_dir"
build_and_deploy_lua "$lua_build_dir" "$lua_lib_dir"
}
get_package_manager_config() {
local mode="$1"
local common_packages
packages=""
update_cmd=""
install_cmd=""
case "$PKG_MANAGER" in
brew)
common_packages="wget jq"
;;
*)
common_packages="git wget curl jq tar"
;;
esac
case "$PKG_MANAGER" in
apt)
update_cmd="apt-get update"
install_cmd="apt-get install -y"
case "$mode" in
dev)
packages="rustup build-essential libssl-dev musl-tools liblua5.4-dev"
;;
crossdev)
packages="rustup build-essential libssl-dev musl-tools liblua5.4-dev gcc-aarch64-linux-gnu gcc-riscv64-linux-gnu gcc-loongarch64-linux-gnu gcc-mingw-w64-x86-64 xar libxar-dev clang cmake libxml2-dev fuse3 libfuse3-dev liblzma-dev libbz2-dev zlib1g-dev llvm-dev uuid-dev"
;;
sandbox)
packages="uidmap"
;;
qemu)
packages="qemu-system-x86 virtiofsd"
;;
*)
packages=""
;;
esac
;;
dnf|yum)
update_cmd="$PKG_MANAGER update -y"
install_cmd="$PKG_MANAGER install -y"
case "$mode" in
dev|crossdev)
packages="cargo gcc openssl-devel musl-gcc libstdc++-static lua-devel"
;;
sandbox)
packages="shadow-utils"
;;
qemu)
packages="qemu-system-x86_64 qemu-virtiofsd"
;;
*)
packages=""
;;
esac
;;
zypper)
update_cmd="zypper refresh"
install_cmd="zypper install -y"
case "$mode" in
dev|crossdev)
packages="rustup gcc openssl-devel musl-gcc lua-devel"
;;
sandbox)
packages="shadow"
;;
qemu)
packages="qemu-x86 qemu-virtiofsd"
;;
*)
packages=""
;;
esac
;;
pacman)
update_cmd="pacman -Sy"
install_cmd="pacman -S --needed --noconfirm"
if [[ "$OS_FAMILY" == "windows" ]]; then
common_packages="git wget curl jq tar"
case "$mode" in
dev|crossdev)
packages="base-devel mingw-w64-x86_64-gcc mingw-w64-x86_64-make mingw-w64-x86_64-pkgconf mingw-w64-x86_64-rust mingw-w64-x86_64-openssl"
;;
sandbox)
packages=""
;;
qemu)
packages="mingw-w64-x86_64-qemu"
;;
*)
packages=""
;;
esac
else
common_packages="git wget curl jq tar"
case "$mode" in
dev|crossdev)
packages="rustup base-devel openssl musl lua"
;;
sandbox)
packages="shadow"
;;
qemu)
packages="qemu-desktop virtiofsd"
;;
*)
packages=""
;;
esac
fi
;;
apk)
update_cmd="apk update"
install_cmd="apk add"
case "$mode" in
dev|crossdev)
packages="rustup build-base openssl-dev musl-dev lua-dev"
;;
sandbox)
packages="shadow-uidmap"
;;
*)
packages=""
;;
esac
;;
brew)
update_cmd="brew update"
install_cmd="brew install"
case "$mode" in
dev|crossdev)
packages="rustup lua openssl pkg-config"
packages="$packages messense/macos-cross-toolchains/aarch64-unknown-linux-musl"
;;
sandbox)
packages=""
;;
qemu)
packages="qemu"
;;
*)
packages=""
;;
esac
;;
choco)
update_cmd="choco update -y"
install_cmd="choco install -y"
case "$mode" in
dev|crossdev)
packages="rustup lua openssl git wget"
;;
sandbox)
packages=""
;;
qemu)
packages="qemu"
;;
*)
packages=""
;;
esac
;;
scoop)
update_cmd="scoop update"
install_cmd="scoop install"
case "$mode" in
dev|crossdev)
packages="rustup lua openssl git wget"
;;
sandbox)
packages=""
;;
qemu)
packages="qemu"
;;
*)
packages=""
;;
esac
;;
*)
echo "Unsupported package manager: $PKG_MANAGER"
exit 1
;;
esac
packages="$packages $common_packages"
}
install_os_packages() {
local SUDO
if [[ $(id -u) -eq 0 ]]; then
SUDO=""
elif [[ "$PKG_MANAGER" == "brew" ]]; then
SUDO=""
elif [[ "$PKG_MANAGER" == "pacman" && "$OS_FAMILY" == "windows" ]]; then
SUDO=""
else
SUDO="sudo"
fi
if [[ -n "$update_cmd" ]]; then
echo "Updating package lists..."
$SUDO $update_cmd || echo "Warning: Package update failed, continuing..."
fi
if [[ -n "$packages" ]]; then
echo "Installing packages: $packages"
$SUDO $install_cmd $packages || {
echo "Error: Package installation failed"
exit 1
}
fi
}
install_rust_toolchain() {
local mode="$1"
local current_arch="$2"
echo "Installing Rust toolchain..."
if has_cmd rustup; then
echo "Using rustup installation"
rustup default stable
if [[ "$mode" == "dev" ]]; then
local rust_target=$(get_rust_target "$current_arch")
rustup target add "$rust_target"
else
rustup target add "$RUST_TARGET_X86_64"
rustup target add "$RUST_TARGET_AARCH64"
rustup target add "$RUST_TARGET_RISCV64"
rustup target add "$RUST_TARGET_LOONGARCH64"
fi
else
echo "rustup not found, using system cargo if available"
if ! has_cmd cargo; then
echo "Warning: Neither rustup nor cargo found. Rust toolchain may be missing."
fi
fi
}
install_packages() {
local mode="${1:-dev}"
detect_os
detect_package_manager
echo "Detected OS: $OS_ID $OS_VERSION"
echo "Detected package manager: $PKG_MANAGER"
local current_arch=$(arch)
echo "Detected architecture: $current_arch"
echo "Installing dependencies ($mode mode)..."
get_package_manager_config "$mode"
install_os_packages
}
clone_repos() {
mkdir -p git
cd git || exit
clone_or_update_repo "https://gitee.com/wu_fengguang/rpm-rs"
clone_or_update_repo "https://gitee.com/wu_fengguang/resolvo"
clone_or_update_repo "https://gitee.com/wu_fengguang/elf-loader"
clone_or_update_repo "https://gitee.com/wu_fengguang/libkrun"
clone_or_update_repo "https://gitee.com/wu_fengguang/vm-memory"
clone_or_update_repo "https://gitee.com/wu_fengguang/imago"
clone_or_update_repo "https://gitee.com/wu_fengguang/sandbox-kernel"
if [[ "$mode" = "crossdev" ]]; then
clone_or_update_repo "https://github.com/tpoechtrager/osxcross.git"
(
cd osxcross/tarballs
wget https://github.com/joseluisq/macosx-sdks/releases/download/26.1/sha256sum.txt
wget https://github.com/joseluisq/macosx-sdks/releases/download/26.1/MacOSX26.1.sdk.tar.xz
sha256sum -c sha256sum.txt
)
else
true
fi
}
install_depends() {
install_packages "$@"
install_rust_toolchain "$mode" "$current_arch"
clone_repos
echo "Installation complete!"
}
dev_depends() {
install_depends dev
}
crossdev_depends() {
install_depends crossdev
}
clean() {
echo "Cleaning build artifacts..."
cargo clean
}
clean_all() {
clean
echo "Cleaning distribution files..."
rm -rf "$OUTPUT_DIR"
}
get_rust_target() {
local arch="$1"
case "$arch" in
x86_64)
echo "$RUST_TARGET_X86_64"
;;
aarch64)
echo "$RUST_TARGET_AARCH64"
;;
riscv64)
echo "$RUST_TARGET_RISCV64"
;;
loongarch64)
echo "$RUST_TARGET_LOONGARCH64"
;;
*)
echo "Unknown architecture: $arch" >&2
exit 1
;;
esac
}
export_linker_var() {
local arch="$1"
local cross_compiler=$(get_cross_compiler "$arch")
if [[ -n "$cross_compiler" ]]; then
local rust_target=$(get_rust_target "$arch")
local target_var=$(echo "${rust_target//-/_}" | tr '[:lower:]' '[:upper:]')
export "CARGO_TARGET_${target_var}_LINKER=$cross_compiler"
fi
}
get_rustflags() {
local arch="$1"
local common_opts="-C target-feature=+crt-static"
case "$arch" in
x86_64|aarch64|riscv64|loongarch64)
;;
*)
echo "Unknown architecture: $arch" >&2
exit 1
;;
esac
local cross_compiler=$(get_cross_compiler "$arch")
if [[ -z "$cross_compiler" ]]; then
echo "$common_opts"
return
fi
local cross_opts="$common_opts -C linker=$cross_compiler -C link-arg=-lgcc -C link-arg=-lc"
case "$arch" in
riscv64|loongarch64)
cross_opts="$cross_opts -C link-arg=-lm"
;;
esac
echo "$cross_opts"
}
add_musl_compat_to_mlua() {
local arch="$1"
local rust_target=$(get_rust_target "$arch")
local build_dir="$2"
local mlua_lib_dirs=$(find "$PROJECT_ROOT/target/$rust_target/$build_dir/build" -type d -name "out" -path "*mlua-sys*" 2>/dev/null)
if [[ -z "$mlua_lib_dirs" ]]; then
return 0
fi
local compiler=$(get_c_compiler "$arch" "musl")
local added_count=0
while IFS= read -r mlua_lib_dir; do
local mlua_lib="$mlua_lib_dir/lib/liblua5.4.a"
if [[ ! -f "$mlua_lib" ]]; then
continue
fi
if ar t "$mlua_lib" 2>/dev/null | grep -q "musl_compat.o"; then
continue
fi
local musl_compat_o="$mlua_lib_dir/musl_compat.o"
if [[ $added_count -eq 0 ]]; then
echo "Adding musl compatibility shims to mlua-sys's Lua library..."
fi
$compiler -O2 -Wall -fPIC -D_FILE_OFFSET_BITS=64 -U_LARGEFILE64_SOURCE \
-c -o "$musl_compat_o" "$PROJECT_ROOT/lib/musl_compat.c" || {
echo "Warning: Failed to compile musl_compat.c for $arch" >&2
return 1
}
ar rs "$mlua_lib" "$musl_compat_o" || {
echo "Warning: Failed to add musl_compat.o to mlua-sys library" >&2
return 1
}
echo "Added musl compatibility shims to $mlua_lib"
added_count=$((added_count + 1))
done <<< "$mlua_lib_dirs"
return 0
}
should_enable_libkrun() {
local arch="$1"
local os="$2"
case "$os" in
linux)
case "$arch" in
x86_64|aarch64|riscv64)
return 0
;;
loongarch64)
return 1
;;
*)
return 1
;;
esac
;;
darwin)
case "$arch" in
aarch64) return 0 ;;
*) return 1 ;;
esac
;;
windows)
case "$arch" in
x86_64) return 0 ;;
*) return 1 ;;
esac
;;
*)
return 1
;;
esac
}
get_default_cargo_features() {
local arch="$1"
local os="$2"
if should_enable_libkrun "$arch" "$os"; then
echo "libkrun"
else
echo ""
fi
}
detect_os_from_target() {
local target="$1"
if [[ "$target" == *"apple-darwin"* ]]; then
echo "darwin"
elif [[ "$target" == *"pc-windows-"* ]]; then
echo "windows"
else
echo "linux"
fi
}
build_static_linux() {
local arch=$(get_arch "$1")
local mode="$2"
local linux_target=$(get_rust_target "$arch")
local build_dir="debug"
[[ "$mode" == "release" ]] && build_dir="release"
echo "[BUILD] Linux ELF ($arch, $mode) for VM mode..."
local cc_for_linux=""
local cross_compiler=$(get_cross_compiler "$arch")
if [[ "$arch" == "aarch64" ]] && has_cmd aarch64-linux-musl-gcc; then
cc_for_linux="aarch64-linux-musl-gcc"
elif [[ "$arch" == "aarch64" ]] && has_cmd aarch64-unknown-linux-musl-gcc; then
cc_for_linux="aarch64-unknown-linux-musl-gcc"
elif [[ -n "$cross_compiler" ]] && has_cmd "$cross_compiler"; then
cc_for_linux="$cross_compiler"
elif has_cmd musl-gcc; then
cc_for_linux="musl-gcc"
else
echo "[WARN] No musl cross-compiler found for $arch"
echo " Linux ELF binary not available. VM mode will download it on first use."
echo " To enable cross-compilation, install: brew install messense/macos-cross-toolchains/aarch64-unknown-linux-musl"
return 0
fi
if has_cmd rustup; then
rustup target add "$linux_target"
fi
local saved_CC="$CC"
local saved_CFLAGS="$CFLAGS"
local saved_RUSTFLAGS="${RUSTFLAGS:-}"
export CC="$cc_for_linux"
export CFLAGS="-D_FILE_OFFSET_BITS=64 -U_LARGEFILE64_SOURCE"
local target_var="${linux_target//-/_}"
export "CFLAGS_${target_var}"="$CFLAGS"
export "CC_${target_var}"="$CC"
local target_upper=$(echo "${linux_target//-/_}" | tr '[:lower:]' '[:upper:]')
export "CARGO_TARGET_${target_upper}_LINKER=$cc_for_linux"
export RUSTFLAGS="-C target-feature=+crt-static -C linker=$cc_for_linux"
local cargo_feature_args=()
echo "Building Linux ELF for VM guest mode"
local cargo_args=()
if [[ "$mode" == "release" ]]; then
cargo_args=(--release)
fi
cargo build --target "$linux_target" --ignore-rust-version "${cargo_args[@]}" "${cargo_feature_args[@]}"
echo "[BUILD-OK] Linux ELF ($arch, $mode): target/$linux_target/$build_dir/$BINARY_NAME"
export CC="$saved_CC"
export CFLAGS="$saved_CFLAGS"
export RUSTFLAGS="$saved_RUSTFLAGS"
unset "CFLAGS_${target_var}"
unset "CC_${target_var}"
unset "CARGO_TARGET_${target_upper}_LINKER"
local linux_epkg="target/$linux_target/$build_dir/$BINARY_NAME"
if [[ -f "$linux_epkg" ]]; then
deploy_release_binary "$linux_epkg" "epkg-linux-${arch}"
install_hardlink "$linux_epkg" "$DEV_ENV_BIN_DIR/epkg-linux-${arch}"
echo "[DEPLOY-hardlink] $DEV_ENV_BIN_DIR/epkg-linux-${arch}"
local host_os=$(uname -s)
if [[ "$host_os" == "Linux" ]]; then
update_all_env_hardlinks "$DEV_ENV_BIN_DIR/epkg-linux-${arch}"
else
local envs_dir="${DEV_ENV_BIN_DIR%/*/*/*}"
for env_dir in "$envs_dir"/*; do
[[ -d "$env_dir" ]] || continue
[[ "${env_dir##*/}" == "self" ]] && continue
local init_path="$env_dir/usr/bin/init"
if [[ -f "$init_path" ]]; then
for filename in epkg init; do
local target_path="$env_dir/usr/bin/$filename"
if [[ -f "$target_path" ]] && [[ ! "$target_path" -ef "$DEV_ENV_BIN_DIR/epkg-linux-${arch}" ]]; then
rm -f "$target_path" && ln "$DEV_ENV_BIN_DIR/epkg-linux-${arch}" "$target_path" && \
echo "[SYNC-hardlink] $target_path"
fi
done
fi
done
fi
fi
}
build_static() {
local arch=$(get_arch "$1")
local mode="$2"
local rust_target
local rustflags
local cargo_features=""
local host_os=$(uname -s)
local is_macos=false
local is_windows=false
[[ "$host_os" == "Darwin" ]] && is_macos=true
[[ "$host_os" == MINGW* || "$host_os" == MSYS* || "$host_os" == CYGWIN* ]] && is_windows=true
if [[ "$is_macos" == "true" ]]; then
build_static_linux "$arch" "$mode"
fi
if [[ "$host_os" == "Darwin" ]]; then
rust_target=$(get_rust_target_for_platform "$arch" "darwin")
rustflags=""
echo "Building for macOS ($arch)..."
elif [[ "$host_os" == MINGW* || "$host_os" == MSYS* || "$host_os" == CYGWIN* ]]; then
rust_target=$(get_rust_target_for_platform "$arch" "windows")
rustflags=""
echo "Building for Windows ($arch)..."
else
rust_target=$(get_rust_target "$arch")
rustflags=$(get_rustflags "$arch")
fi
if [[ "$FEATURES" == "auto" ]]; then
local target_os="linux"
[[ "$host_os" == "Darwin" ]] && target_os="darwin"
[[ "$host_os" == MINGW* || "$host_os" == MSYS* || "$host_os" == CYGWIN* ]] && target_os="windows"
if should_enable_libkrun "$arch" "$target_os"; then
cargo_features="libkrun"
echo "Auto-enabling libkrun feature for $arch $target_os"
fi
else
cargo_features="${FEATURES:-}"
fi
if [[ "$cargo_features" == *"libkrun"* || "$cargo_features" == *"embedded_init"* ]]; then
local target_os="linux"
[[ "$host_os" == "Darwin" ]] && target_os="darwin"
[[ "$host_os" == MINGW* || "$host_os" == MSYS* || "$host_os" == CYGWIN* ]] && target_os="windows"
if ! should_enable_libkrun "$arch" "$target_os"; then
echo "Warning: libkrun is not supported on $arch $target_os, build may fail"
fi
fi
echo "Building $arch binary ($mode)..."
if [[ "$is_macos" == "false" && "$is_windows" == "false" ]]; then
export LUA_LIB_NAME=lua
export LUA_LIB="$PROJECT_ROOT/target/lua-musl-$arch"
export LUA_LINK=static
export LUA_NO_PKG_CONFIG=1
fi
export_linker_var "$arch"
if [[ "$is_macos" == "true" ]]; then
export CC="clang"
export CFLAGS=""
elif [[ "$is_windows" == "true" ]]; then
export CC="gcc"
export CFLAGS=""
else
export CC=$(get_c_compiler "$arch" "musl")
export CFLAGS="-D_FILE_OFFSET_BITS=64 -U_LARGEFILE64_SOURCE"
local target_var="${rust_target//-/_}"
export "CFLAGS_${target_var}"="$CFLAGS"
export "CC_${target_var}"="$CC"
fi
if [[ -n "$rustflags" ]]; then
export RUSTFLAGS="$rustflags"
fi
if [[ "$cargo_features" == *"libkrun"* || "$cargo_features" == *"embedded_init"* ]]; then
if [[ "$is_macos" == "false" ]]; then
install_kernel_for_libkrun "$arch"
fi
fi
local build_dir
if [[ "$mode" == "release" ]]; then
build_dir="release"
local cargo_args=(--release)
else
build_dir="debug"
local cargo_args=()
fi
local need_musl_compat=false
if [[ "$is_macos" == "false" && "$is_windows" == "false" ]] && [[ "$CC" != "musl-gcc" ]]; then
need_musl_compat=true
fi
local cargo_feature_args=()
if [[ -n "$cargo_features" ]]; then
cargo_feature_args=(--features "$cargo_features")
fi
cargo build --target "$rust_target" --ignore-rust-version "${cargo_args[@]}" "${cargo_feature_args[@]}" || true
if [[ "$need_musl_compat" == "true" ]]; then
add_musl_compat_to_mlua "$arch" "$build_dir"
cargo build --target "$rust_target" --ignore-rust-version "${cargo_args[@]}" "${cargo_feature_args[@]}"
fi
if [[ "$mode" == "release" ]]; then
mkdir -p "$OUTPUT_DIR"
local output_os="linux"
local exe_suffix=""
if [[ "$is_macos" == "true" ]]; then
output_os="macos"
fi
if [[ "$is_windows" == "true" ]]; then
output_os="windows"
exe_suffix=".exe"
fi
safe_cp "target/$rust_target/$build_dir/${BINARY_NAME}${exe_suffix}" \
"$OUTPUT_DIR/${BINARY_NAME}-${output_os}-${arch}${exe_suffix}"
echo "Generating checksum for $output_os/$arch binary..."
local out_name="${BINARY_NAME}-${output_os}-${arch}${exe_suffix}"
pushd "$OUTPUT_DIR" >/dev/null
sha256sum "$out_name" > "$out_name.sha256"
echo "$output_os/$arch release completed: $PROJECT_ROOT/$OUTPUT_DIR/$out_name"
popd >/dev/null
fi
if is_native_arch "$arch"; then
mkdir -p "target/$mode"
ln -sf "$PROJECT_ROOT/target/$rust_target/$build_dir/$BINARY_NAME" target/$mode/epkg
install_to_dev_env "$PROJECT_ROOT/target/$rust_target/$build_dir/$BINARY_NAME" "$arch"
fi
if [[ "$is_macos" == "true" ]]; then
echo "[BUILD-OK] macOS ($arch, $mode): target/$rust_target/$build_dir/$BINARY_NAME"
local entitlements="$PROJECT_ROOT/assets/macos/epkg.entitlements"
codesign --force --sign - --entitlements "$entitlements" "$PROJECT_ROOT/target/$rust_target/$build_dir/$BINARY_NAME" && \
echo "[CODESIGN] Signed with hypervisor entitlements"
if [[ "$mode" == "release" ]]; then
deploy_release_binary "target/$rust_target/$build_dir/$BINARY_NAME" "epkg-macos-${arch}"
fi
install_hardlink "target/$rust_target/$build_dir/$BINARY_NAME" "$DEV_ENV_BIN_DIR/$BINARY_NAME"
echo "[DEPLOY-hardlink] $DEV_ENV_BIN_DIR/$BINARY_NAME"
local entitlements="$PROJECT_ROOT/assets/macos/epkg.entitlements"
if [[ -f "$DEV_ENV_BIN_DIR/$BINARY_NAME" ]]; then
codesign --force --sign - --entitlements "$entitlements" "$DEV_ENV_BIN_DIR/$BINARY_NAME" && \
echo "[CODESIGN] Re-signed $DEV_ENV_BIN_DIR/$BINARY_NAME with hypervisor entitlements"
fi
local envs_dir="${DEV_ENV_BIN_DIR%/*/*/*}"
for env_dir in "$envs_dir"/*; do
[[ -d "$env_dir" ]] || continue
[[ "${env_dir##*/}" == "self" ]] && continue
local epkg_path="$env_dir/usr/bin/epkg"
local init_path="$env_dir/usr/bin/init"
[[ -f "$init_path" ]] && continue
if [[ -f "$epkg_path" ]] && [[ ! "$epkg_path" -ef "$DEV_ENV_BIN_DIR/$BINARY_NAME" ]]; then
rm -f "$epkg_path" && ln "$DEV_ENV_BIN_DIR/$BINARY_NAME" "$epkg_path" && \
echo "[SYNC-hardlink] $epkg_path"
fi
done
echo "[DONE] native macOS build completed"
fi
}
setup_glibc_lua() {
local arch="${HOST_ARCH:-$(arch)}"
local lua_lib_dir="$PROJECT_ROOT/target/lua-glibc-$arch"
if [[ ! -f "$lua_lib_dir/liblua.a" ]]; then
echo "Lua static library not found at $lua_lib_dir/liblua.a"
echo "Building Lua library for $arch (glibc)..."
build_lua_lib "$arch" "glibc"
fi
export LUA_LIB_NAME=lua
export LUA_LIB="$lua_lib_dir"
export LUA_LINK=static
export LUA_NO_PKG_CONFIG=1
}
build() {
echo "Building debug binary (dynamic linking - legacy)..."
detect_os
if [[ "$OS_FAMILY" == "linux" ]]; then
setup_glibc_lua
else
export LUA_LINK=dynamic
unset LUA_NO_PKG_CONFIG 2>/dev/null || true
fi
local cargo_features=""
local current_arch=$(detect_native_arch)
if [[ "$FEATURES" == "auto" ]]; then
if should_enable_libkrun "$current_arch" "$OS_FAMILY"; then
cargo_features="libkrun"
echo "Auto-enabling libkrun feature for $OS_FAMILY $current_arch"
fi
else
cargo_features="${FEATURES:-}"
fi
local cargo_feature_args=()
if [[ -n "$cargo_features" ]]; then
cargo_feature_args=(--features "$cargo_features")
fi
cargo build --ignore-rust-version "${cargo_feature_args[@]}"
echo "Development build completed. Binary is in $PROJECT_ROOT/target/debug/$BINARY_NAME"
install_to_dev_env "$PROJECT_ROOT/target/debug/$BINARY_NAME"
}
build_release() {
echo "Building release binary (dynamic linking - legacy)..."
detect_os
if [[ "$OS_FAMILY" == "linux" ]]; then
setup_glibc_lua
else
export LUA_LINK=dynamic
unset LUA_NO_PKG_CONFIG 2>/dev/null || true
fi
local cargo_features=""
local current_arch=$(detect_native_arch)
if [[ "$FEATURES" == "auto" ]]; then
if should_enable_libkrun "$current_arch" "$OS_FAMILY"; then
cargo_features="libkrun"
echo "Auto-enabling libkrun feature for $OS_FAMILY $current_arch"
fi
else
cargo_features="${FEATURES:-}"
fi
local cargo_feature_args=()
if [[ -n "$cargo_features" ]]; then
cargo_feature_args=(--features "$cargo_features")
fi
cargo build --release --ignore-rust-version "${cargo_feature_args[@]}"
echo "Release build completed. Binary is in $PROJECT_ROOT/target/release/$BINARY_NAME"
install_to_dev_env "$PROJECT_ROOT/target/release/$BINARY_NAME"
}
cross-macos() {
local arch="${1:-aarch64}"
local mode="${2:-debug}"
local host_os=$(uname -s)
local is_macos=false
[[ "$host_os" == "Darwin" ]] && is_macos=true
if [[ "$arch" != "aarch64" ]]; then
echo "Notice: macOS x86_64 is not supported by libkrun (Hypervisor.framework limitation)"
echo " Building without libkrun support..."
fi
if [[ "$is_macos" == "true" ]]; then
build_static "$arch" "$mode"
fi
local target=""
case "$arch" in
x86_64) target="$RUST_TARGET_X86_64_DARWIN" ;;
aarch64) target="$RUST_TARGET_AARCH64_DARWIN" ;;
*) echo "Unsupported architecture for macOS: $arch"; exit 1 ;;
esac
echo "[BUILD] macOS ($arch, $mode)..."
if has_cmd rustup; then
rustup target add "$target"
fi
setup_cross_env "$target"
local cargo_features=""
if [[ "$FEATURES" == "auto" ]]; then
if should_enable_libkrun "$arch" "darwin"; then
cargo_features="libkrun"
echo "Auto-enabling libkrun feature for macOS $arch"
fi
else
cargo_features="${FEATURES:-}"
fi
if [[ "$cargo_features" == *"libkrun"* || "$cargo_features" == *"embedded_init"* ]] && ! should_enable_libkrun "$arch" "darwin"; then
echo "Warning: libkrun is not supported on macOS $arch, build may fail"
fi
local cargo_feature_args=()
if [[ -n "$cargo_features" ]]; then
cargo_feature_args=(--features "$cargo_features")
fi
local build_dir
local cargo_args=()
if [[ "$mode" == "release" ]]; then
build_dir="release"
cargo_args=(--release)
else
build_dir="debug"
fi
cargo build --target "$target" --ignore-rust-version "${cargo_args[@]}" "${cargo_feature_args[@]}"
echo "[BUILD-OK] macOS ($arch, $mode): target/$target/$build_dir/$BINARY_NAME"
if [[ "$mode" == "release" ]]; then
deploy_release_binary "target/$target/$build_dir/$BINARY_NAME" "epkg-macos-${arch}"
fi
if [[ "$is_macos" == "true" ]]; then
local linux_target=$(get_rust_target "$arch")
local linux_epkg="${PROJECT_ROOT}/target/${linux_target}/${build_dir}/${BINARY_NAME}"
if [[ -f "$linux_epkg" ]]; then
deploy_release_binary "$linux_epkg" "epkg-linux-${arch}"
install_hardlink "$linux_epkg" "$DEV_ENV_BIN_DIR/epkg-linux-${arch}"
echo "[DEPLOY-hardlink] $DEV_ENV_BIN_DIR/epkg-linux-${arch}"
update_all_env_hardlinks "$DEV_ENV_BIN_DIR/epkg-linux-${arch}"
else
echo "[WARN] Linux epkg not found at $linux_epkg"
fi
fi
echo "[DONE] cross-macos completed"
}
cross-windows() {
local arch="${1:-x86_64}"
local mode="${2:-debug}"
local is_wsl2=false
if is_wsl2; then
is_wsl2=true
fi
if [[ "$arch" != "x86_64" ]]; then
echo "Error: Windows cross-compilation only supports x86_64 architecture"
echo " aarch64 is not supported due to missing mingw-w64 libraries"
exit 1
fi
ensure_cross_windows_depends || exit 1
if [[ "$is_wsl2" == "true" ]]; then
build_static x86_64 "$mode"
fi
local target="$RUST_TARGET_X86_64_WINDOWS"
echo "[BUILD] Windows ($arch, $mode)..."
if has_cmd rustup; then
rustup target add "$target"
fi
setup_cross_env "$target"
local cargo_features=""
if [[ "$FEATURES" == "auto" ]]; then
cargo_features="$(get_default_cargo_features "$arch" "windows")"
if [[ -n "$cargo_features" ]]; then
echo "Auto-enabling libkrun feature for Windows $arch"
fi
else
cargo_features="${FEATURES:-}"
fi
if [[ "$cargo_features" == *"libkrun"* || "$cargo_features" == *"embedded_init"* ]] && ! should_enable_libkrun "$arch" "windows"; then
echo "Warning: libkrun is not supported on Windows $arch, build may fail"
fi
if [[ "$cargo_features" == *"libkrun"* || "$cargo_features" == *"embedded_init"* ]]; then
install_kernel_for_libkrun "$arch"
if [[ "$cargo_features" == *"embedded_init"* ]]; then
(cd "$PROJECT_ROOT/git/libkrun" && make init/init) || {
echo "Error: failed to build git/libkrun/init/init (required for cargo feature embedded_init)" >&2
exit 1
}
fi
fi
local cargo_feature_args=()
if [[ -n "$cargo_features" ]]; then
cargo_feature_args=(--features "$cargo_features")
fi
local build_dir
local cargo_args=()
if [[ "$mode" == "release" ]]; then
build_dir="release"
cargo_args=(--release)
else
build_dir="debug"
fi
cargo build --target "$target" --ignore-rust-version "${cargo_args[@]}" "${cargo_feature_args[@]}"
mkdir -p "target/$build_dir"
ln -sf "$PROJECT_ROOT/target/$target/$build_dir/${BINARY_NAME}.exe" "target/$build_dir/${BINARY_NAME}.exe"
echo "[BUILD-OK] Windows ($arch, $mode): target/$target/$build_dir/${BINARY_NAME}.exe"
deploy_release_binary "target/$target/$build_dir/${BINARY_NAME}.exe" "epkg-windows-${arch}.exe"
deploy_to_windows_self "target/$target/$build_dir/${BINARY_NAME}.exe" "epkg.exe" || true
if [[ "$is_wsl2" == "true" ]]; then
local linux_epkg="${PROJECT_ROOT}/target/${RUST_TARGET_X86_64}/${build_dir}/${BINARY_NAME}"
if [[ -f "$linux_epkg" ]]; then
deploy_release_binary "$linux_epkg" "epkg-linux-${arch}"
deploy_to_windows_self "$linux_epkg" "epkg-linux-${arch}" || true
update_all_env_hardlinks "$DEV_ENV_BIN_DIR/epkg-linux-${arch}"
else
echo "[WARN] Linux epkg not found at $linux_epkg"
fi
fi
if [[ "$mode" == "release" ]]; then
:
fi
echo "[DONE] cross-windows completed"
}
run_tests() {
RUSTFLAGS="-A dead_code -A unused_imports -A unused_variables" cargo test
}
HOST_ARCH=$(detect_native_arch)
is_native_arch() {
local arch="$1"
[[ "$arch" == "$HOST_ARCH" ]]
}
get_cross_compiler() {
local arch="$1"
if [[ "$arch" == "x86_64" ]] || is_native_arch "$arch"; then
echo ""
else
echo "$arch-linux-gnu-gcc"
fi
}
get_c_compiler() {
local arch="$1"
local lib_type="${2:-musl}"
case "$lib_type" in
musl)
case "$arch" in
x86_64)
echo "musl-gcc"
;;
aarch64|riscv64|loongarch64)
local cross_compiler=$(get_cross_compiler "$arch")
if [[ -z "$cross_compiler" ]]; then
echo "gcc"
else
echo "$cross_compiler"
fi
;;
*)
echo "Unknown architecture: $arch" >&2
exit 1
;;
esac
;;
glibc)
echo "gcc"
;;
*)
echo "Unknown library type: $lib_type (must be 'musl' or 'glibc')" >&2
exit 1
;;
esac
}
get_arch() {
local provided_arch="$1"
if [[ -z "$provided_arch" ]]; then
local arch=$(detect_native_arch)
echo "Auto-detected architecture: $arch" >&2
echo "$arch"
else
echo "$provided_arch"
fi
}
detect_osxcross() {
local osxcross_dir=""
for dir in "/opt/osxcross" "$HOME/osxcross" "$HOME/.osxcross" "/c/rust/osxcross"; do
if [[ -d "$dir/target/bin" ]]; then
local found=false
if [[ -f "$dir/target/bin/o64-clang" ]]; then
found=true
elif compgen -G "$dir/target/bin/*-apple-darwin*-clang" >/dev/null; then
found=true
fi
if $found; then
osxcross_dir="$dir/target"
break
fi
fi
if [[ -d "$dir/bin" ]]; then
local found=false
if [[ -f "$dir/bin/o64-clang" ]]; then
found=true
elif compgen -G "$dir/bin/*-apple-darwin*-clang" >/dev/null; then
found=true
fi
if $found; then
osxcross_dir="$dir"
break
fi
fi
done
if [[ -n "$osxcross_dir" ]]; then
echo "$osxcross_dir"
return 0
fi
for dir in "/opt/osxcross" "$HOME/osxcross" "$HOME/.osxcross" "/c/rust/osxcross"; do
if [[ -d "$dir" && -f "$dir/build.sh" ]]; then
local sdk_tarball=""
if [[ -f "$dir/tarballs/MacOSX26.1.sdk.tar.xz" ]]; then
sdk_tarball="$dir/tarballs/MacOSX26.1.sdk.tar.xz"
elif [[ -f "$dir/MacOSX26.1.sdk.tar.xz" ]]; then
sdk_tarball="$dir/MacOSX26.1.sdk.tar.xz"
fi
if [[ -n "$sdk_tarball" ]]; then
echo "Warning: osxcross found at $dir but not built. SDK tarball: $sdk_tarball" >&2
echo "Run 'cd $dir && ./build.sh' to build osxcross." >&2
fi
fi
done
return 1
}
detect_mingw() {
if has_cmd x86_64-w64-mingw32-gcc; then
echo "x86_64-w64-mingw32"
return 0
fi
return 1
}
get_rust_target_for_platform() {
local arch="$1"
local os="$2"
case "$os" in
linux)
case "$arch" in
x86_64) echo "$RUST_TARGET_X86_64" ;;
aarch64) echo "$RUST_TARGET_AARCH64" ;;
riscv64) echo "$RUST_TARGET_RISCV64" ;;
loongarch64) echo "$RUST_TARGET_LOONGARCH64" ;;
*) echo "" ;;
esac
;;
darwin)
case "$arch" in
x86_64) echo "$RUST_TARGET_X86_64_DARWIN" ;;
aarch64) echo "$RUST_TARGET_AARCH64_DARWIN" ;;
*) echo "" ;;
esac
;;
windows)
case "$arch" in
x86_64) echo "$RUST_TARGET_X86_64_WINDOWS" ;;
aarch64) echo "$RUST_TARGET_AARCH64_WINDOWS" ;;
*) echo "" ;;
esac
;;
*)
echo ""
;;
esac
}
setup_cross_env() {
local target="$1"
local arch="${target%%-*}"
local os=""
if [[ "$target" == *"apple-darwin"* ]]; then
os="darwin"
elif [[ "$target" == *"pc-windows-"* ]]; then
os="windows"
else
echo "Error: setup_cross_env only supports macOS and Windows targets, got: $target" >&2
exit 1
fi
unset CC CFLAGS LUA_LIB LUA_LIB_NAME LUA_LINK LUA_NO_PKG_CONFIG RUSTFLAGS
unset CARGO_TARGET_X86_64_APPLE_DARWIN_LINKER
unset CARGO_TARGET_AARCH64_APPLE_DARWIN_LINKER
unset CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER
unset CARGO_TARGET_AARCH64_PC_WINDOWS_MSVC_LINKER
unset CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER
unset CARGO_TARGET_AARCH64_PC_WINDOWS_GNU_LINKER
export PKG_CONFIG_ALLOW_CROSS=1
case "$os" in
darwin)
local osxcross_dir=$(detect_osxcross)
if [[ -n "$osxcross_dir" ]]; then
export PATH="$osxcross_dir/bin:$PATH"
local cc_name=""
local pattern="$arch-apple-darwin*-clang"
local match=$(compgen -G "$osxcross_dir/bin/$pattern" 2>/dev/null | head -1)
if [[ -n "$match" && -f "$match" ]]; then
cc_name="$(basename "$match")"
elif [[ "$arch" == "x86_64" && -f "$osxcross_dir/bin/o64-clang" ]]; then
cc_name="o64-clang"
else
cc_name="$arch-apple-darwin-clang"
fi
export CC="$cc_name"
local target_var=$(echo "${target//-/_}" | tr '[:lower:]' '[:upper:]')
export "CARGO_TARGET_${target_var}_LINKER=$cc_name"
local sdk_path=""
if [[ -L "$osxcross_dir/SDK/MacOSX.sdk" ]]; then
sdk_path="$osxcross_dir/SDK/MacOSX.sdk"
else
local sdk_dir
for sdk_dir in "$osxcross_dir/SDK"/MacOSX*.sdk; do
if [[ -d "$sdk_dir" ]]; then
sdk_path="$sdk_dir"
break
fi
done
fi
if [[ -n "$sdk_path" ]]; then
export SDK_PATH="$sdk_path"
export LIBRARY_PATH="$sdk_path/usr/lib"
else
echo "Warning: Could not find macOS SDK in $osxcross_dir/SDK"
fi
else
echo "Warning: osxcross not found, using system clang (may not work)"
export CC="clang"
fi
;;
windows)
local mingw_prefix=$(detect_mingw)
if [[ -n "$mingw_prefix" ]]; then
export CC="${mingw_prefix}-gcc"
local target_var=$(echo "${target//-/_}" | tr '[:lower:]' '[:upper:]')
export "CARGO_TARGET_${target_var}_LINKER=${mingw_prefix}-gcc"
else
echo "Warning: mingw-w64 not found, using default (may not work)"
fi
;;
esac
}
cmd="${1:-build}"
case $cmd in
lua|build_lua_lib)
build_lua_lib "$2"
;;
build)
build_static "$2" debug
;;
release)
build_static "$2" release
;;
static|static-debug)
build_static "$2" debug
;;
static-libkrun)
arch=$(get_arch "$2")
if [[ -n "$FEATURES" && "$FEATURES" != "auto" ]]; then
FEATURES="libkrun,$FEATURES"
else
FEATURES="libkrun"
fi
build_static "$arch" debug
;;
static-release)
build_static "$2" release
;;
dynamic-build)
build
;;
dynamic-release)
build_release
;;
dev-depends)
dev_depends
;;
crossdev-depends)
crossdev_depends
;;
dev-pkgs)
install_packages dev
;;
crossdev-pkgs)
install_packages crossdev
;;
qemu-pkgs)
install_packages qemu
;;
sandbox-pkgs)
install_packages sandbox
;;
clone-repos)
clone_repos
;;
cross-macos)
cross-macos "$2" "$3"
;;
cross-windows)
cross-windows "$2" "$3"
;;
test)
run_tests
;;
clean)
clean
;;
clean_all)
clean_all
;;
*)
echo "Usage: $0 [command] [options...]"
echo ""
echo "Commands (static linking - DEFAULT):"
echo " build [<arch>] Build static debug binary (default)"
echo " release [<arch>] Build static release binary"
echo " static [<arch>] (alias for 'build')"
echo " static-debug [<arch>] Build static debug binary (explicit, same with 'static')"
echo " static-release [<arch>] Build static release binary (explicit)"
echo " static-libkrun [<arch>] Build static debug with libkrun (auto-enabled anyway in some platform/archs)"
echo ""
echo "Commands (cross-platform builds - Linux x86_64 host only):"
echo " cross-macos [<arch>] [debug|release] Cross-compile to macOS (aarch64/x86_64)"
echo " cross-windows [debug|release] Cross-compile to Windows (x86_64 only)"
echo ""
echo "Commands (dynamic linking - LEGACY):"
echo " dynamic-build Build dynamic debug binary (not recommended)"
echo " dynamic-release Build dynamic release binary (not recommended)"
echo ""
echo "Other commands:"
echo " lua [<arch>] Build Lua library for architecture"
echo " dev-depends Install development dependencies (current arch only)"
echo " crossdev-depends Install cross-development dependencies (all arch cross-compilers)"
echo " clone-repos Clone required repositories (rpm-rs, resolvo, elf-loader)"
echo " qemu-pkgs Install qemu dependency packages"
echo " sandbox-pkgs Install sandbox dependency packages"
echo " test Run module-level unit tests"
echo " clean Clean build artifacts"
echo " clean_all Clean all artifacts and distribution files"
echo ""
echo "Build types:"
echo " - Native build: build on host arch (supported on all platforms/archs)"
echo " - Cross-arch build: build for different Linux arch on x86_64 Linux host"
echo " - Cross-platform: build macOS/Windows binary on x86_64 Linux host"
echo ""
echo "Note: Cross builds require crossdev-depends, mainly supported on Debian distros"
echo ""
echo "Architectures: x86_64, aarch64, riscv64, loongarch64"
echo ""
echo "libkrun auto-enable matrix:"
echo " Linux x86_64/aarch64/riscv64: enabled"
echo " macOS aarch64: enabled"
echo " Windows x86_64: enabled"
echo " Linux loongarch64: disabled (not supported)"
echo " macOS x86_64 / Windows !x86_64: disabled"
echo ""
echo "Set FEATURES to override default features."
exit 1
;;
esac