//! Build script for smolvm. //! //! Handles macOS weak-link setup for libkrun. //! //! Linux loads libkrun at runtime with `dlopen` so packed stubs can start //! before bundled libraries are extracted. //! //! # Linking Options //! //! ## Dynamic (default) //! macOS can weak-link against libkrun installed on the system: //! ```sh //! brew install libkrun # macOS //! cargo build //! ``` //! //! ## Bundle Pre-built Library (Recommended for Distribution) //! Copy Homebrew's libkrun to your bundle directory and build: //! ```sh //! mkdir -p lib //! cp /opt/homebrew/opt/libkrun/lib/libkrun.dylib lib/ //! # Also copy libkrunfw if bundling //! cp /opt/homebrew/opt/libkrunfw/lib/libkrunfw.dylib lib/ //! LIBKRUN_BUNDLE=$PWD/lib cargo build --release //! ``` //! The binary will use @rpath to find libraries in ./lib or ../lib. //! //! ## Build from Submodule (Experimental) //! Build libkrun from the vendored submodule: //! ```sh //! LIBKRUN_BUILD=1 cargo build //! ``` //! Note: This builds a minimal libkrun. Block device support (`krun_add_disk2`) //! requires building libkrun separately with `make BLK=1` and using LIBKRUN_BUNDLE. //! //! ## Static (libkrun only) //! Statically link libkrun (still dynamically links libkrunfw): //! ```sh //! LIBKRUN_STATIC=/path/to/libkrun.a cargo build //! ``` #[cfg(target_os = "macos")] use std::path::PathBuf; #[cfg(target_os = "macos")] use std::process::Command; /// Link libkrun for legacy macOS weak-link behavior. /// /// Linux intentionally does not link libkrun at build time: packed stubs must /// be able to start before bundled libraries are extracted, so Linux VM launch /// paths use `dlopen` instead. #[cfg(target_os = "macos")] fn link_krun() { println!("cargo:rustc-link-arg=-Wl,-weak-lkrun"); } #[cfg(target_os = "macos")] fn has_library(name: &str) -> bool { pkg_config::Config::new().probe(name).is_ok() } fn main() { // Build scripts run on the HOST: `cfg!(target_os)` here describes the // build machine, not the artifact. Cross-compiling smolvm from macOS to // Linux (zigbuild) must NOT emit macOS-only linker args (-sectcreate, // -weak-lkrun) — gate every emission on the actual compile target. let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); if target_os == "macos" { // On macOS, create a placeholder __SMOLVM,__smolvm Mach-O section. // This section is replaced with real data by `smolvm pack --single-file`. // The placeholder marker is NOT the SMOLSECT magic, so detect.rs won't // false-positive on a normal smolvm binary. #[cfg(target_os = "macos")] { use std::io::Write; let out_dir = std::env::var("OUT_DIR").unwrap(); let placeholder_path = format!("{}/smolvm_placeholder.bin", out_dir); let mut f = std::fs::File::create(&placeholder_path).unwrap(); f.write_all(b"SMOLVM_SECTION_PLACEHOLDER_V1").unwrap(); f.write_all(&[0u8; 4]).unwrap(); println!( "cargo:rustc-link-arg=-Wl,-sectcreate,__SMOLVM,__smolvm,{}", placeholder_path ); } #[cfg(target_os = "macos")] link_libkrun(); } } #[cfg(target_os = "macos")] fn link_libkrun() { println!("cargo:rerun-if-env-changed=LIBKRUN_STATIC"); println!("cargo:rerun-if-env-changed=LIBKRUN_BUNDLE"); println!("cargo:rerun-if-env-changed=LIBKRUN_DIR"); println!("cargo:rerun-if-env-changed=LIBKRUN_BUILD"); // Option 0: Build from submodule if std::env::var("LIBKRUN_BUILD").is_ok() { if let Some(lib_path) = build_libkrun_from_submodule() { println!("cargo:rustc-link-search=native={}", lib_path.display()); link_krun(); // Set rpath to find libraries relative to executable #[cfg(target_os = "macos")] { println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path/lib"); println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path/../lib"); // Also add the build output path for development println!("cargo:rustc-link-arg=-Wl,-rpath,{}", lib_path.display()); } #[cfg(target_os = "linux")] { println!("cargo:rustc-link-arg=-Wl,-rpath,$ORIGIN/lib"); println!("cargo:rustc-link-arg=-Wl,-rpath,$ORIGIN/../lib"); } return; } } // Option 1: Bundle libraries with the binary if let Ok(bundle_path) = std::env::var("LIBKRUN_BUNDLE") { // On Linux, check that the library is not an LFS pointer before linking #[cfg(target_os = "linux")] { let lib_path = std::path::Path::new(&bundle_path).join("libkrun.so"); if lib_path.exists() && is_lfs_pointer(&lib_path) { println!("cargo:error=libkrun.so is a Git LFS pointer, not the actual library."); println!("cargo:error=Run 'git lfs pull' to fetch the actual library binary."); panic!("Git LFS pointer detected"); } } println!("cargo:rustc-link-search=native={}", bundle_path); link_krun(); // Set rpath to find libraries relative to executable #[cfg(target_os = "macos")] { println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path/lib"); println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path/../lib"); // Change the library's install_name to use @rpath and re-sign let lib_path = std::path::Path::new(&bundle_path).join("libkrun.dylib"); if lib_path.exists() { let _ = Command::new("install_name_tool") .args(["-id", "@rpath/libkrun.dylib", lib_path.to_str().unwrap()]) .status(); // Re-sign after modification (macOS requires valid signature) let _ = Command::new("codesign") .args(["--force", "--sign", "-", lib_path.to_str().unwrap()]) .status(); } } #[cfg(target_os = "linux")] { println!("cargo:rustc-link-arg=-Wl,-rpath,$ORIGIN/lib"); println!("cargo:rustc-link-arg=-Wl,-rpath,$ORIGIN/../lib"); } return; } // Option 2: Static linking if let Ok(static_path) = std::env::var("LIBKRUN_STATIC") { let path = std::path::Path::new(&static_path); if path.is_dir() { println!("cargo:rustc-link-search=native={}", static_path); } else if path.is_file() { if let Some(dir) = path.parent() { println!("cargo:rustc-link-search=native={}", dir.display()); } } else { panic!("LIBKRUN_STATIC path does not exist: {}", static_path); } println!("cargo:rustc-link-lib=static=krun"); // Static libkrun requires these frameworks on macOS #[cfg(target_os = "macos")] { println!("cargo:rustc-link-lib=framework=Hypervisor"); println!("cargo:rustc-link-lib=framework=vmnet"); } return; } // Option 3: Custom directory if let Ok(dir) = std::env::var("LIBKRUN_DIR") { println!("cargo:rustc-link-search=native={}", dir); link_krun(); return; } // Option 4: Bundled libraries in lib/linux-{arch}/ (for distribution builds) #[cfg(target_os = "linux")] { let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default(); let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".to_string()); let lib_dir = format!("{}/lib/linux-{}", manifest_dir, arch); let lib_path = std::path::Path::new(&lib_dir); let libkrun_path = lib_path.join("libkrun.so"); // Check if the library exists and is a real library (not an LFS pointer) if libkrun_path.exists() && !is_lfs_pointer(&libkrun_path) { println!( "cargo:warning=Using bundled Linux libraries from {}", lib_dir ); println!("cargo:rustc-link-search=native={}", lib_dir); link_krun(); // Set rpath to find libraries relative to executable println!("cargo:rustc-link-arg=-Wl,-rpath,$ORIGIN/lib"); println!("cargo:rustc-link-arg=-Wl,-rpath,$ORIGIN/../lib"); // Also add the source directory for development builds println!("cargo:rustc-link-arg=-Wl,-rpath,{}", lib_dir); return; } } // Option 5: System installation via pkg-config if pkg_config::Config::new() .atleast_version("1.0") .probe("libkrun") .is_ok() { return; } // Option 6: Common installation paths #[cfg(target_os = "macos")] { let paths = [ "/opt/homebrew/lib", "/usr/local/lib", "/opt/homebrew/opt/libkrun/lib", "/usr/local/opt/libkrun/lib", ]; for path in paths { if std::path::Path::new(path).join("libkrun.dylib").exists() { println!("cargo:rustc-link-search=native={}", path); link_krun(); // Set rpath so runtime linker can find dependencies println!("cargo:rustc-link-arg=-Wl,-rpath,{}", path); return; } } } #[cfg(target_os = "linux")] { let paths = [ "/usr/lib", "/usr/local/lib", "/usr/lib64", "/usr/local/lib64", "/usr/lib/x86_64-linux-gnu", "/usr/lib/aarch64-linux-gnu", ]; for path in paths { if std::path::Path::new(path).join("libkrun.so").exists() { println!("cargo:rustc-link-search=native={}", path); link_krun(); return; } } } // Fallback link_krun(); } /// Build libkrun from the vendored submodule. /// /// Returns the path to the directory containing the built library. #[cfg(target_os = "macos")] fn build_libkrun_from_submodule() -> Option { let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").ok()?); let libkrun_dir = manifest_dir.join("libkrun"); // Check if submodule exists if !libkrun_dir.join("Cargo.toml").exists() { println!( "cargo:warning=libkrun submodule not found at {}. \ Run: git submodule update --init", libkrun_dir.display() ); return None; } // On macOS, libkrun embeds the init binary via include_bytes!() // We need to ensure init/init exists (copy from init/init.krun if needed) #[cfg(target_os = "macos")] { let init_dst = libkrun_dir.join("init/init"); let init_src = libkrun_dir.join("init/init.krun"); if !init_dst.exists() && init_src.exists() { println!("cargo:warning=Copying init.krun to init for embedding..."); if let Err(e) = std::fs::copy(&init_src, &init_dst) { println!("cargo:warning=Failed to copy init binary: {}", e); return None; } } else if !init_dst.exists() { println!("cargo:warning=init binary not found. Need init/init or init/init.krun"); return None; } } // Determine profile let profile = std::env::var("PROFILE").unwrap_or_else(|_| "release".to_string()); println!( "cargo:warning=Building libkrun from submodule ({} build)...", profile ); // Build libkrun using cargo directly (make has issues on macOS) let libkrun_manifest = libkrun_dir.join("src/libkrun/Cargo.toml"); let mut cmd = Command::new("cargo"); cmd.arg("build") .arg("--manifest-path") .arg(&libkrun_manifest); if profile == "release" { cmd.arg("--release"); } // Auto-detect GPU support: enable if virglrenderer is installed on the host. // GPU feature requires virglrenderer + libclang (for krun_display bindgen). // On macOS, also needs MoltenVK for Vulkan → Metal translation. let gpu_available = has_library("virglrenderer"); if gpu_available { cmd.arg("--features").arg("gpu"); // krun_display uses bindgen which needs libclang. // Help it find libclang on macOS (Homebrew LLVM). #[cfg(target_os = "macos")] { let llvm_lib = std::path::Path::new("/opt/homebrew/opt/llvm/lib"); if llvm_lib.exists() { cmd.env("LIBCLANG_PATH", llvm_lib); } } println!("cargo:warning=GPU support enabled (virglrenderer found)"); } let status = cmd.status(); match status { Ok(s) if s.success() => { println!("cargo:warning=libkrun built successfully"); } Ok(s) => { println!( "cargo:warning=libkrun build failed with exit code: {:?}", s.code() ); return None; } Err(e) => { println!("cargo:warning=Failed to run cargo: {}", e); return None; } } // Find the built library let target_dir = if profile == "release" { libkrun_dir.join("target/release") } else { libkrun_dir.join("target/debug") }; #[cfg(target_os = "macos")] let lib_name = "libkrun.dylib"; #[cfg(target_os = "linux")] let lib_name = "libkrun.so"; if target_dir.join(lib_name).exists() { // Copy library to smolvm's target directory for bundling let out_dir = PathBuf::from(std::env::var("OUT_DIR").ok()?); let lib_out_dir = out_dir.join("lib"); std::fs::create_dir_all(&lib_out_dir).ok()?; let src = target_dir.join(lib_name); let dst = lib_out_dir.join(lib_name); std::fs::copy(&src, &dst).ok()?; println!( "cargo:warning=Copied {} to {}", src.display(), dst.display() ); // On macOS, change the install_name to use @rpath so the binary finds // the bundled library instead of a system-installed one #[cfg(target_os = "macos")] { let install_name = format!("@rpath/{}", lib_name); let status = Command::new("install_name_tool") .args(["-id", &install_name, dst.to_str().unwrap()]) .status(); if let Ok(s) = status { if s.success() { println!("cargo:warning=Set install_name to {}", install_name); // Re-sign after modification let _ = Command::new("codesign") .args(["--force", "--sign", "-", dst.to_str().unwrap()]) .status(); } } } // Tell cargo to rebuild if libkrun source changes println!( "cargo:rerun-if-changed={}", libkrun_dir.join("src").display() ); println!( "cargo:rerun-if-changed={}", libkrun_dir.join("init").display() ); Some(lib_out_dir) } else { println!( "cargo:warning=Built library not found at {}", target_dir.join(lib_name).display() ); None } }