mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-17 05:31:16 +08:00
285 lines
9.3 KiB
Bash
Executable File
285 lines
9.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
# Build libkrun + libkrunfw from source on macOS with portable rpath.
|
|
#
|
|
# This script compiles a pre-built kernel.c (from the Linux ARM64 CI job) into
|
|
# libkrunfw.dylib, then builds libkrun.dylib (the VMM) WITHOUT GPU support.
|
|
# The resulting binaries are fully portable and self-contained.
|
|
#
|
|
# The kernel.c file contains the aarch64 Linux kernel as a byte array. It is
|
|
# architecture-specific but OS-agnostic — any C compiler can turn it into a
|
|
# shared library. This eliminates the need to build the kernel on macOS via
|
|
# krunvm, cutting the macOS CI job from ~45 minutes to ~5 minutes.
|
|
#
|
|
# Prerequisites:
|
|
# - macOS ARM64 (Apple Silicon)
|
|
# - Xcode Command Line Tools
|
|
# - Homebrew: brew install rust lld dtc xz
|
|
# - Pre-built kernel.c and ABI_VERSION from the Linux ARM64 build
|
|
#
|
|
# Usage:
|
|
# ./build-libkrun-macos.sh --kernel-dir <DIR>
|
|
#
|
|
# --kernel-dir Directory containing kernel.c and ABI_VERSION (from Linux build)
|
|
#
|
|
# Output:
|
|
# target/libkrun-build/libkrun.dylib - portable dylib with @loader_path rpath
|
|
# target/libkrun-build/libkrunfw.dylib - portable dylib compiled from kernel.c
|
|
|
|
set -euo pipefail
|
|
|
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
|
|
BUILD_DIR="${ROOT}/target/libkrun-build"
|
|
OUTPUT_DIR="${BUILD_DIR}"
|
|
BREW_PREFIX="$(brew --prefix 2>/dev/null || echo /opt/homebrew)"
|
|
KERNEL_DIR=""
|
|
|
|
# Parse arguments
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--kernel-dir)
|
|
KERNEL_DIR="$(cd "$2" && pwd)"; shift 2 ;;
|
|
--help|-h)
|
|
echo "Usage: $0 --kernel-dir <DIR>"
|
|
echo ""
|
|
echo "Build libkrun + libkrunfw on macOS using a pre-built kernel.c."
|
|
echo ""
|
|
echo "Options:"
|
|
echo " --kernel-dir DIR Directory containing kernel.c and ABI_VERSION"
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "Unknown argument: $1" >&2; exit 1 ;;
|
|
esac
|
|
done
|
|
|
|
if [ "$(uname -s)" != "Darwin" ]; then
|
|
echo "Error: This script only runs on macOS" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [ "$(uname -m)" != "arm64" ]; then
|
|
echo "Error: libkrun on macOS only supports ARM64 (Apple Silicon)" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [ -z "$KERNEL_DIR" ]; then
|
|
echo "Error: --kernel-dir is required" >&2
|
|
echo " This should point to the directory containing kernel.c and ABI_VERSION" >&2
|
|
echo " from the Linux ARM64 build job." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -f "${KERNEL_DIR}/kernel.c" ]; then
|
|
echo "Error: kernel.c not found in ${KERNEL_DIR}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -f "${KERNEL_DIR}/ABI_VERSION" ]; then
|
|
echo "Error: ABI_VERSION not found in ${KERNEL_DIR}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
ABI_VERSION="$(cat "${KERNEL_DIR}/ABI_VERSION")"
|
|
|
|
ARCH="$(uname -m)"
|
|
echo "==> Building libkrun + libkrunfw for macOS ${ARCH} (no GPU support)"
|
|
echo " Build directory: ${BUILD_DIR}"
|
|
echo " Kernel source: ${KERNEL_DIR}/kernel.c ($(du -sh "${KERNEL_DIR}/kernel.c" | cut -f1))"
|
|
echo " ABI version: ${ABI_VERSION}"
|
|
echo ""
|
|
|
|
# ── Check dependencies ──────────────────────────────────────────────────
|
|
|
|
check_deps() {
|
|
echo "==> Checking build dependencies..."
|
|
|
|
MISSING=""
|
|
|
|
# Check for Rust
|
|
if ! command -v cargo &>/dev/null; then
|
|
MISSING="$MISSING rust"
|
|
fi
|
|
|
|
# Check for lld (LLVM linker)
|
|
if ! command -v ld.lld &>/dev/null && ! [ -x "${BREW_PREFIX}/opt/llvm/bin/ld.lld" ]; then
|
|
MISSING="$MISSING lld"
|
|
fi
|
|
|
|
# Check for dtc (device tree compiler)
|
|
if ! command -v dtc &>/dev/null; then
|
|
MISSING="$MISSING dtc"
|
|
fi
|
|
|
|
if [ -n "$MISSING" ]; then
|
|
echo "Error: Missing dependencies:$MISSING" >&2
|
|
echo "" >&2
|
|
echo "Install with: brew install$MISSING" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo " All dependencies found"
|
|
}
|
|
|
|
check_deps
|
|
|
|
# ── Setup build directory ───────────────────────────────────────────────
|
|
|
|
mkdir -p "$BUILD_DIR"
|
|
cd "$BUILD_DIR"
|
|
|
|
# ── Compile libkrunfw.dylib from pre-built kernel.c ────────────────────
|
|
|
|
echo ""
|
|
echo "==> Compiling libkrunfw.dylib from pre-built kernel.c..."
|
|
|
|
# kernel.c is a C source file containing the Linux kernel as a byte array.
|
|
# It was generated by the Linux ARM64 build job. We just need to compile it
|
|
# into a shared library with the correct ABI version.
|
|
cc -fPIC -DABI_VERSION="${ABI_VERSION}" -shared \
|
|
-o "${OUTPUT_DIR}/libkrunfw.${ABI_VERSION}.dylib" \
|
|
"${KERNEL_DIR}/kernel.c"
|
|
|
|
# Create the unversioned symlink that libkrun links against
|
|
cp "${OUTPUT_DIR}/libkrunfw.${ABI_VERSION}.dylib" "${OUTPUT_DIR}/libkrunfw.dylib"
|
|
|
|
echo " Built: libkrunfw.${ABI_VERSION}.dylib ($(du -sh "${OUTPUT_DIR}/libkrunfw.${ABI_VERSION}.dylib" | cut -f1))"
|
|
|
|
# ── Clone libkrun ───────────────────────────────────────────────────────
|
|
|
|
LIBKRUN_REF="${LIBKRUN_REF:-e5922f6}"
|
|
|
|
if [ ! -d libkrun ]; then
|
|
echo "==> Cloning libkrun..."
|
|
git clone https://github.com/containers/libkrun.git
|
|
fi
|
|
|
|
echo "==> Checking out ${LIBKRUN_REF}..."
|
|
cd libkrun
|
|
git fetch origin --tags
|
|
git checkout "${LIBKRUN_REF}" 2>/dev/null || git checkout "tags/${LIBKRUN_REF}" 2>/dev/null || {
|
|
echo "Error: Could not checkout ${LIBKRUN_REF}" >&2
|
|
exit 1
|
|
}
|
|
cd ..
|
|
|
|
LIBKRUN_COMMIT=$(git -C libkrun rev-parse HEAD)
|
|
echo " Commit: ${LIBKRUN_COMMIT}"
|
|
|
|
cd libkrun
|
|
|
|
# ── Build libkrun ───────────────────────────────────────────────────────
|
|
|
|
echo ""
|
|
echo "==> Building libkrun with NET=1 BLK=1 (no GPU)..."
|
|
|
|
# Point the linker at our freshly-compiled libkrunfw
|
|
export LIBRARY_PATH="${OUTPUT_DIR}:${BREW_PREFIX}/lib:${LIBRARY_PATH:-}"
|
|
export DYLD_LIBRARY_PATH="${OUTPUT_DIR}:${BREW_PREFIX}/lib:${DYLD_LIBRARY_PATH:-}"
|
|
|
|
# Set up LLVM/clang for bindgen
|
|
LLVM_PREFIX="${BREW_PREFIX}/opt/llvm"
|
|
if [ -d "$LLVM_PREFIX" ]; then
|
|
export LIBCLANG_PATH="${LLVM_PREFIX}/lib"
|
|
export DYLD_LIBRARY_PATH="${LLVM_PREFIX}/lib:${DYLD_LIBRARY_PATH:-}"
|
|
fi
|
|
|
|
# Build with BLK and NET features only (no GPU)
|
|
# This avoids the virglrenderer -> libepoxy -> MoltenVK dependency chain
|
|
make clean 2>/dev/null || true
|
|
make BLK=1 NET=1 -j"$(sysctl -n hw.ncpu)"
|
|
|
|
# ── Rewrite dylib paths for portability ─────────────────────────────────
|
|
|
|
echo ""
|
|
echo "==> Making dylibs portable with @loader_path..."
|
|
|
|
DYLIB="target/release/libkrun.dylib"
|
|
if [ ! -f "$DYLIB" ]; then
|
|
echo "Error: Build did not produce $DYLIB" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Copy to output
|
|
cp "$DYLIB" "${OUTPUT_DIR}/libkrun.dylib"
|
|
DYLIB="${OUTPUT_DIR}/libkrun.dylib"
|
|
|
|
# Show current dependencies
|
|
echo " Original dependencies:"
|
|
otool -L "$DYLIB" | grep -v "^/" | sed 's/^/ /'
|
|
|
|
# Rewrite the install name to use @loader_path (makes it relocatable)
|
|
install_name_tool -id "@loader_path/libkrun.dylib" "$DYLIB"
|
|
|
|
# Rewrite libkrunfw path to @loader_path (will be bundled alongside)
|
|
KRUNFW_PATH=$(otool -L "$DYLIB" | grep libkrunfw | awk '{print $1}' || true)
|
|
if [ -n "$KRUNFW_PATH" ]; then
|
|
install_name_tool -change "$KRUNFW_PATH" "@loader_path/libkrunfw.dylib" "$DYLIB"
|
|
echo " Rewrote: $KRUNFW_PATH -> @loader_path/libkrunfw.dylib"
|
|
fi
|
|
|
|
# Re-codesign after modifications (required on macOS)
|
|
codesign -f -s - "$DYLIB"
|
|
|
|
# Make libkrunfw portable too
|
|
install_name_tool -id "@loader_path/libkrunfw.dylib" "${OUTPUT_DIR}/libkrunfw.dylib"
|
|
codesign -f -s - "${OUTPUT_DIR}/libkrunfw.dylib"
|
|
|
|
if [ -f "${OUTPUT_DIR}/libkrunfw.${ABI_VERSION}.dylib" ]; then
|
|
install_name_tool -id "@loader_path/libkrunfw.${ABI_VERSION}.dylib" "${OUTPUT_DIR}/libkrunfw.${ABI_VERSION}.dylib"
|
|
codesign -f -s - "${OUTPUT_DIR}/libkrunfw.${ABI_VERSION}.dylib"
|
|
fi
|
|
|
|
# Show final dependencies
|
|
echo ""
|
|
echo " Final dependencies:"
|
|
otool -L "$DYLIB" | grep -v "^/" | sed 's/^/ /'
|
|
|
|
# Verify no hardcoded homebrew paths remain
|
|
if otool -L "$DYLIB" | grep -q "/opt/homebrew"; then
|
|
echo ""
|
|
echo "Warning: Homebrew paths still present in dylib!" >&2
|
|
otool -L "$DYLIB" | grep "/opt/homebrew" | sed 's/^/ /'
|
|
else
|
|
echo ""
|
|
echo " No hardcoded Homebrew paths"
|
|
fi
|
|
|
|
# ── Summary ─────────────────────────────────────────────────────────────
|
|
|
|
cd "$BUILD_DIR"
|
|
|
|
echo ""
|
|
echo "==> Build complete!"
|
|
echo " Output directory: ${OUTPUT_DIR}"
|
|
echo ""
|
|
echo " Artifacts:"
|
|
ls -lah "${OUTPUT_DIR}"/*.dylib
|
|
|
|
# Verify portability
|
|
echo ""
|
|
echo "==> Verifying portability..."
|
|
ALL_GOOD=true
|
|
|
|
for lib in "${OUTPUT_DIR}"/*.dylib; do
|
|
if otool -L "$lib" | grep -q "/opt/homebrew"; then
|
|
echo " x $(basename "$lib") has hardcoded paths"
|
|
ALL_GOOD=false
|
|
else
|
|
echo " ok $(basename "$lib") is portable"
|
|
fi
|
|
done
|
|
|
|
if $ALL_GOOD; then
|
|
echo ""
|
|
echo "All libraries are portable!"
|
|
echo ""
|
|
echo "Next step: mise run vm:supervisor && cargo build -p openshell-driver-vm"
|
|
else
|
|
echo ""
|
|
echo "Warning: Some libraries have non-portable paths"
|
|
echo "They may not work on machines without Homebrew"
|
|
fi
|