name: CI on: push: branches: [main] pull_request: branches: [main] workflow_call: env: CARGO_TERM_COLOR: always jobs: # ========================================================================== # Quick checks (no platform-specific deps needed) # ========================================================================== fmt: name: Format runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable with: components: rustfmt - run: cargo fmt --all -- --check secrets-guards: name: Secrets invariants runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Check secrets guardrails run: ./scripts/check-secrets-guards.sh libkrun-provenance: name: libkrun freshness runs-on: ubuntu-latest steps: # LFS on: check-krun-exports.sh reads the actual bundled lib binaries. # (check-libkrun-provenance.sh itself only needs the pinned gitlinks.) - uses: actions/checkout@v4 with: lfs: true - name: Bundled libkrun matches the pinned submodule run: ./scripts/check-libkrun-provenance.sh # nm (ELF), llvm-nm (Mach-O dylib), mingw objdump (PE dll exports). - name: Install symbol-reading tools run: | sudo apt-get update sudo apt-get install -y binutils binutils-mingw-w64-x86-64 llvm - name: Bundled libkrun exports every symbol smolvm requires run: ./scripts/check-krun-exports.sh audit: name: Cargo Audit runs-on: ubuntu-latest # Scans Cargo.lock for RustSec advisories — no engine/libkrun needed. Honors # .cargo/audit.toml, which ignores documented unmaintained-transitive notices # so a real vulnerability stands out instead of hiding in warning noise. steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: taiki-e/install-action@cargo-audit - run: cargo audit # ========================================================================== # macOS Build & Test (Apple Silicon) # ========================================================================== macos: name: macOS (arm64) runs-on: macos-14 steps: - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable with: components: clippy - name: Cache cargo uses: actions/cache@v5 with: path: | ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ target/ key: macos-arm64-cargo-${{ hashFiles('**/Cargo.lock', '**/Cargo.toml') }} restore-keys: macos-arm64-cargo- - name: Setup Git LFS run: | git lfs install git lfs pull - name: Verify LFS dylibs run: | ls -la lib/ file lib/*.dylib - name: Build run: cargo build --release env: LIBRARY_PATH: ${{ github.workspace }}/lib - name: Clippy run: cargo clippy --all-targets -- -D warnings env: LIBRARY_PATH: ${{ github.workspace }}/lib - name: Run unit tests run: cargo test --lib env: LIBRARY_PATH: ${{ github.workspace }}/lib DYLD_LIBRARY_PATH: ${{ github.workspace }}/lib - name: Run agent unit tests run: cargo test -p smolvm-agent --bins env: LIBRARY_PATH: ${{ github.workspace }}/lib DYLD_LIBRARY_PATH: ${{ github.workspace }}/lib - name: Sign binary run: codesign --force --sign - --entitlements smolvm.entitlements target/release/smolvm - name: Verify binary runs run: ./target/release/smolvm --version env: DYLD_LIBRARY_PATH: ${{ github.workspace }}/lib # Note: Integration tests (E2E VM tests) require Hypervisor.framework access, # which GitHub runners don't provide. Run integration tests locally. # ========================================================================== # Linux Build & Test # ========================================================================== linux: name: Linux (${{ matrix.arch }}) runs-on: ${{ matrix.runner }} strategy: fail-fast: false matrix: include: - arch: x86_64 runner: ubuntu-latest target: x86_64-unknown-linux-gnu - arch: aarch64 runner: ubuntu-24.04-arm target: aarch64-unknown-linux-gnu steps: - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable with: components: clippy targets: ${{ matrix.target }} - name: Cache cargo uses: actions/cache@v5 with: path: | ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ target/ key: linux-${{ matrix.arch }}-cargo-${{ hashFiles('**/Cargo.lock', '**/Cargo.toml') }} restore-keys: linux-${{ matrix.arch }}-cargo- - name: Install build dependencies run: | sudo apt-get update sudo apt-get install -y \ build-essential \ libssl-dev \ pkg-config # libkrun is not easily available on Ubuntu, so we create a stub library # for linking. This allows us to compile and run unit tests. # Full integration tests require macOS or a Linux machine with KVM. - name: Create libkrun stub for linking run: | # Create minimal stub that satisfies the linker cat > /tmp/libkrun_stub.c << 'EOF' // Stub implementations - these won't be called in unit tests int krun_create_ctx() { return -1; } int krun_free_ctx(int ctx) { return 0; } int krun_set_vm_config(int ctx, int cpus, int mem) { return -1; } int krun_set_root(int ctx, const char* path) { return -1; } int krun_set_workdir(int ctx, const char* path) { return -1; } int krun_set_exec(int ctx, const char* path, char** argv, char** env) { return -1; } int krun_start_enter(int ctx) { return -1; } int krun_set_log_level(int level) { return 0; } int krun_add_disk2(int ctx, const char* id, const char* path, int flags, int ro) { return -1; } int krun_set_port_map(int ctx, char** map) { return -1; } int krun_add_vsock_port(int ctx, int port, const char* path) { return -1; } int krun_add_vsock_port2(int ctx, int port, const char* path, int flags) { return -1; } int krun_disable_implicit_vsock(int ctx) { return -1; } int krun_add_vsock(int ctx, int tsi_features) { return -1; } int krun_set_passt_fd(int ctx, int fd) { return -1; } int krun_add_virtiofs(int ctx, const char* tag, const char* path) { return -1; } int krun_add_virtiofs2(int ctx, const char* tag, const char* path, int flags) { return -1; } int krun_set_console_output(int ctx, const char* path) { return -1; } int krun_add_virtio_console_default(int ctx) { return -1; } EOF gcc -shared -fPIC -o /tmp/libkrun.so /tmp/libkrun_stub.c sudo cp /tmp/libkrun.so /usr/local/lib/ sudo ln -sf /usr/local/lib/libkrun.so /usr/local/lib/libkrun.1.dylib sudo ldconfig - name: Build run: cargo build --release --target ${{ matrix.target }} env: LIBRARY_PATH: /usr/local/lib - name: Clippy run: cargo clippy --all-targets --target ${{ matrix.target }} -- -D warnings env: LIBRARY_PATH: /usr/local/lib - name: Run unit tests run: cargo test --lib --target ${{ matrix.target }} env: LIBRARY_PATH: /usr/local/lib LD_LIBRARY_PATH: /usr/local/lib - name: Run agent unit tests run: cargo test -p smolvm-agent --bins --target ${{ matrix.target }} env: LIBRARY_PATH: /usr/local/lib LD_LIBRARY_PATH: /usr/local/lib # Note: Full VM integration tests require KVM + real libkrun. # Run integration tests locally on a machine with KVM access. # ========================================================================== # Windows Build & Lint (cross-compiled from Linux, mingw-w64 / GNU target) # ========================================================================== windows: name: Windows (x86_64, cross) runs-on: ubuntu-latest env: CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER: x86_64-w64-mingw32-gcc steps: - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable with: components: clippy targets: x86_64-pc-windows-gnu - name: Cache cargo uses: actions/cache@v5 with: path: | ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ target/ key: windows-gnu-cargo-${{ hashFiles('**/Cargo.lock', '**/Cargo.toml') }} restore-keys: windows-gnu-cargo- - name: Install mingw-w64 cross toolchain run: | sudo apt-get update sudo apt-get install -y gcc-mingw-w64-x86-64 clang libclang-dev # The Windows host dlopens libkrun at runtime (libloading), so unlike the # macOS/Linux jobs there is no link-time libkrun dependency and no stub is # needed. This gate catches host-side compile + lint regressions on # Windows; WHP VM integration tests run out-of-band on real hardware via # the WHP QA harness (unit tests can additionally be run under Wine with # CARGO_TARGET_X86_64_PC_WINDOWS_GNU_RUNNER=wine). - name: Build run: cargo build --release --target x86_64-pc-windows-gnu - name: Clippy run: cargo clippy --release --target x86_64-pc-windows-gnu --all-targets -- -D warnings # ========================================================================== # Build agent (Linux musl for static binary) # ========================================================================== agent: name: Agent (Linux musl) runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable with: targets: aarch64-unknown-linux-musl, x86_64-unknown-linux-musl - name: Install musl tools run: | sudo apt-get update sudo apt-get install -y musl-tools - name: Install cross-compilation tools run: | # For aarch64 cross-compilation sudo apt-get install -y gcc-aarch64-linux-gnu - name: Build agent (x86_64) run: cargo build --release --target x86_64-unknown-linux-musl -p smolvm-agent working-directory: . - name: Build agent (aarch64) run: | export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=aarch64-linux-gnu-gcc cargo build --release --target aarch64-unknown-linux-musl -p smolvm-agent working-directory: . - name: Verify static binaries run: | echo "x86_64 agent:" file target/x86_64-unknown-linux-musl/release/smolvm-agent echo "aarch64 agent:" file target/aarch64-unknown-linux-musl/release/smolvm-agent # ========================================================================== # Summary job for branch protection # ========================================================================== ci-success: name: CI Success needs: [fmt, macos, linux, windows, agent] runs-on: ubuntu-latest if: always() steps: - name: Check all jobs passed run: | if [[ "${{ needs.fmt.result }}" != "success" ]] || \ [[ "${{ needs.macos.result }}" != "success" ]] || \ [[ "${{ needs.linux.result }}" != "success" ]] || \ [[ "${{ needs.windows.result }}" != "success" ]] || \ [[ "${{ needs.agent.result }}" != "success" ]]; then echo "One or more jobs failed" exit 1 fi echo "All CI jobs passed!"