8000 ci(macos): consolidate libkrun on our own pinned source build by norrietaylor · Pull Request #694 · gominimal/minimal · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
7fca328
ci: extract shared setup composites for rust, libkrun, and guest arti…
norrietaylor Jul 9, 2026
655ea22
ci(release): extract minvmd linkage rewrite into a script
norrietaylor Jul 9, 2026
4cd7c2a
ci(setup-minimal): drop dead cache-minimal install conditional
norrietaylor Jul 9, 2026
fff6d9d
fix(scripts): harden rewrite-macos-linkage.sh
norrietaylor Jul 9, 2026
35bc4d5
ci: address review findings in shared composites and their callers
norrietaylor Jul 9, 2026
f9204c4
Merge branch 'main' into ci/shared-composites
norrietaylor Jul 9, 2026
a764471
ci: drop the fetch retries — lane history shows zero fetch failures
norrietaylor Jul 9, 2026
3c79642
ci(macos): reap the whole Session-E2E process tree before auto-spawn
norrietaylor Jul 9, 2026
40b2bc7
Merge branch 'main' into ci/shared-composites
norrietaylor Jul 9, 2026
637ca84
ci(macos): consolidate libkrun on our own pinned source build
norrietaylor Jul 9, 2026
a727611
Merge branch 'main' into ci/shared-composites
norrietaylor Jul 9, 2026
6fc9ad8
Merge remote-tracking branch 'origin/ci/shared-composites'
norrietaylor Jul 9, 2026
bfe3f9c
Merge branch 'ci/shared-composites' into ci/libkrun-own-build
norrietaylor Jul 9, 2026
7c4704f
Merge branch 'main' into ci/libkrun-own-build
norrietaylor Jul 9, 2026
2c3ff8f
ci(macos): address CodeRabbit review on the libkrun build
norrietaylor Jul 9, 2026
ce25653
Merge branch 'main' into ci/libkrun-own-build
norrietaylor Jul 9, 2026
5b28788
ci(macos): build minimal in its own cargo invocation in the e2e job
norrietaylor Jul 10, 2026
3a49c4d
Merge remote-tracking branch 'origin/ci/libkrun-own-build' into ci/li…
norrietaylor Jul 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 60 additions & 19 deletions .github/actions/setup-libkrun-macos/action.yml
10BC8
Original file line number Diff line number Diff line change
@@ -1,32 +1,73 @@
name: Setup libkrun (macOS)
description: >
Provision libkrun from the slp/krun Homebrew tap. Self-installing keeps
macOS jobs from depending on hand-provisioned runner state — a missing
libkrun.dylib silently broke every code PR. Idempotent (a no-op when
already present); same tap the runner setup used, so the supply-chain
surface is unchanged. The existence check matters even after a successful
install: brew exits 0 for an installed-but-UNLINKED keg ("it's just not
linked"), a plausible state on a persistent shared runner, and minvmd's
build.rs links against /opt/homebrew/lib unconditionally on macOS.
Provision the libkrun.dylib we SHIP — built from source at the commit
pinned in vendor/libkrun/libkrun.lock via scripts/build-libkrun-macos.sh —
into a commit-keyed prefix, and publish LIBKRUN_PREFIX to GITHUB_ENV.
minvmd's build.rs prefers LIBKRUN_PREFIX over its /opt/homebrew fallback
and bakes the prefix as an rpath, so every later cargo build links, and
every test boots, the exact dylib users receive — a leftover brew libkrun
on a runner is ignored, and the old slp/krun tap is no longer a CI
dependency. Build-on-miss: on GitHub-hosted runners the prefix rides
actions/cache; on the persistent self-hosted mini the directory itself
survives between runs, so the source build happens once per pin bump.
Requires a Rust toolchain (run this after dtolnay/rust-toolchain) and the
repository checked out.

runs:
using: composite
steps:
- name: Provision libkrun (slp/krun tap)
- name: Resolve libkrun pin
id: pin
shell: bash
run: brew install slp/krun/libkrun
- name: Verify libkrun is available (>= 1.19.0)
# krun_add_disk3 (the /dev/vdb attach) needs libkrun >= 1.19.0. Assert
# the runner's libkrun exports it, so a stale runner fails here with an
# actionable message instead of a cryptic dyld symbol-not-found at boot.
run: |
commit="$(sed -n 's/^commit=//p' vendor/libkrun/libkrun.lock)"
if [ -z "$commit" ]; then
echo "::error::no commit= line in vendor/libkrun/libkrun.lock" >&2
exit 1
fi
script_hash="$(shasum -a 256 scripts/build-libkrun-macos.sh | cut -d' ' -f1)"
{
echo "commit=$commit"
# The prefix embeds BOTH build inputs (pinned commit + build-script
# hash) so the persistent mini rebuilds when either changes — the
# same invalidation the actions/cache key gives GitHub-hosted
# runners. Stale sibling prefixes on the mini are inert (nothing
# references them) and may be deleted by hand.
echo "prefix=$HOME/.cache/minimal-ci/libkrun/$commit-${script_hash:0:12}"
echo "script_hash=$script_hash"
} >> "$GITHUB_OUTPUT"
- name: Cache built libkrun (GitHub-hosted)
# The mini needs no cache action: the input-keyed prefix under $HOME
# persists across runs on its own.
if: runner.environment == 'github-hosted'
uses: actions/cache@v6
with:
path: ~/.cache/minimal-ci/libkrun
key: libkrun-macos-${{ runner.arch }}-${{ steps.pin.outputs.commit }}-${{ steps.pin.outputs.script_hash }}
- name: Build libkrun from source (on miss)
shell: bash
env:
PREFIX: ${{ steps.pin.outputs.prefix }}
run: |
if [ ! -f "$PREFIX/libkrun.1.dylib" ]; then
./scripts/build-libkrun-macos.sh "$PREFIX"
else
echo "libkrun already staged at $PREFIX (cache hit)"
fi
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
- name: Verify staged libkrun + export LIBKRUN_PREFIX
# Re-assert the API floor on the staged (possibly cache-restored) dylib
# so a corrupt or hand-tampered prefix fails here with an actionable
# message instead of a cryptic dyld error at VM boot.
shell: bash
env:
PREFIX: ${{ steps.pin.outputs.prefix }}
run: |
dylib=/opt/homebrew/lib/libkrun.dylib
if [ ! -f "$dylib" ]; then
echo "::error::libkrun.dylib not found under /opt/homebrew/lib despite brew exiting 0 (installed-but-unlinked keg?). Fix with: brew link slp/krun/libkrun" >&2
if [ ! -f "$PREFIX/libkrun.1.dylib" ]; then
echo "::error::libkrun.1.dylib missing from $PREFIX after build/restore" >&2
exit 1
fi
if ! nm -gU "$dylib" 2>/dev/null | grep -q '_krun_add_disk3'; then
echo "::error::runner libkrun lacks krun_add_disk3 (need >= 1.19.0); upgrade with: brew upgrade slp/krun/libkrun" >&2
if ! nm -gU "$PREFIX/libkrun.1.dylib" 2>/dev/null | grep -q '_krun_add_disk3'; then
echo "::error::staged libkrun lacks krun_add_disk3 (need >= 1.19.0); delete $PREFIX and re-run to rebuild" >&2
exit 1
fi
echo "LIBKRUN_PREFIX=$PREFIX" >> "$GITHUB_ENV"
30 changes: 27 additions & 3 deletions .github/workflows/ci-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ on:
# a composite-only edit would skip this lane and break it post-merge.
- ".github/actions/setup-libkrun-macos/**"
- ".github/actions/materialize/**"
# The libkrun pin: a bump must rebuild + re-run the hypervisor e2e.
- "vendor/libkrun/**"
pull_request:
branches: ["main"]
paths:
Expand All @@ -35,6 +37,8 @@ on:
# a composite-only edit would skip this lane and break it post-merge.
- ".github/actions/setup-libkrun-macos/**"
- ".github/actions/materialize/**"
# The libkrun pin: a bump must rebuild + re-run the hypervisor e2e.
- "vendor/libkrun/**"
workflow_dispatch:

env:
Expand Down Expand Up @@ -155,7 +159,7 @@ jobs:
persist-credentials: false
- name: Toolchain
uses: dtolnay/rust-toolchain@stable
- name: Provision libkrun (slp/krun tap)
- name: Provision libkrun (pinned own build)
uses: ./.github/actions/setup-libkrun-macos
- name: Download virtio-linux kernel
uses: actions/download-artifact@v8
Expand All @@ -180,7 +184,27 @@ jobs:
# minvmd needs the hypervisor entitlement.
run: |
cargo test -p minvmd --test minimald_session_e2e --no-run
cargo build -p minvmd --bin minvmd -p minimal --bin minimal
cargo build -p minvmd --bin minvmd
# SEPARATE invocation for the CLI (mirrors release.yml): built
# together, cargo unifies minvmd's default `libkrun` feature
# into minimal's default-features=false opt-out, and the CLI
# links libkrun. The brew era masked this (absolute install
# name loaded from anywhere); our @rpath dylib turns it into
# dyld "Library not loaded" at autospawn, since minimal bakes
# no rpaths.
cargo build -p minimal --bin minimal
- name: Verify minimal links only system libraries
# The CLI spawns minvmd as a subprocess and calls no libkrun; a
# krun load command means the feature unification above regressed.
# Catch it here with a clear message, not as a dyld error mid-e2e.
run: |
bad="$(otool -L target/debug/minimal | tail -n +2 | awk '{print $1}' \
| grep -vE '^(/usr/lib/|/System/)' || true)"
if [ -n "$bad" ]; then
echo "::error::minimal links non-system libraries (feature unification with minvmd's libkrun?):" >&2
printf '%s\n' "$bad" >&2
exit 1
fi
Comment on lines +196 to +207
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fail closed when dependency inspection fails.

Because || true covers the entire pipeline, an otool failure can be converted into an empty bad value and pass the check. Capture otool -L separately so only “no non-system matches” is tolerated.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci-macos.yml around lines 196 - 207, Update the “Verify
minimal links only system libraries” step so `otool -L` runs separately and its
failure immediately fails the workflow; avoid applying `|| true` to the entire
inspection pipeline. Then filter the captured dependency output for non-system
libraries, tolerating only an empty match result, while preserving the existing
diagnostic and exit behavior.

- name: Codesign minvmd (hypervisor entitlement, R1.4)
run: codesign --entitlements crates/minvmd/minvmd.entitlements --force -s - target/debug/minvmd
- name: Session E2E (boot + full session over the bridge)
Expand Down Expand Up @@ -327,7 +351,7 @@ jobs:
uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- name: Provision libkrun (slp/krun tap)
- name: Provision libkrun (pinned own build)
uses: ./.github/actions/setup-libkrun-macos
- name: Clippy (minvmd)
run: cargo clippy -p minvmd --all-targets -- -D warnings
Expand Down
77 changes: 23 additions & 54 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ jobs:
persist-credentials: false
- name: Toolchain
uses: dtolnay/rust-toolchain@stable
- name: Provision libkrun (slp/krun tap)
- name: Provision libkrun (pinned own build)
uses: ./.github/actions/setup-libkrun-macos
- name: Build release minvmd binary
run: cargo build --release -p minvmd --bin minvmd
Expand Down Expand Up @@ -294,66 +294,35 @@ jobs:
retention-days: 7

build-libkrun-macos-arm64:
# Build a TRIMMED libkrun.dylib from source to SHIP in the release, so
# macOS users don't have to `brew install` it themselves. GitHub-hosted
# Build the TRIMMED libkrun.dylib to SHIP in the release, so macOS users
# don't have to `brew install` it themselves. Same setup-libkrun-macos
# composite (scripts/build-libkrun-macos.sh at the vendor/libkrun/
# libkrun.lock pin) that every macOS CI lane links and boots against —
# the shipped dylib is BY CONSTRUCTION the one CI tested. GitHub-hosted
# macos-latest is Apple Silicon (arm64).
runs-on: macos-latest
timeout-minutes: 45
env:
# Pin to the libkrun the slp/krun tap currently ships, so the dylib we
# ship matches the ABI the macOS CI lanes build and test against.
LIBKRUN_REF: v1.19.4
steps:
- name: Checkout libkrun (${{ env.LIBKRUN_REF }})
uses: actions/checkout@v7
- uses: actions/checkout@v7
with:
repository: containers/libkrun
ref: ${{ env.LIBKRUN_REF }}
persist-credentials: false
- name: Toolchain
uses: dtolnay/rust-toolchain@stable
- name: Build trimmed libkrun (blk + net; no gpu, no init-blob)
# Direct cargo build — no Makefile, no env, no lld/xz/sysroot.
# -p libkrun scope to the libkrun crate, so the
# src/input & src/display workspace members
# (whose bindgen build scripts need libclang)
# are never built.
# --no-default-features drop `init-blob`, whose build script
# cross-compiles a Linux init — the ONLY reason
# the Makefile needs lld + a Debian sysroot.
# minvmd supplies its own initramfs, so
# libkrun's bundled init is unused. (This is
# exactly the Makefile's INIT_BLOB=0 config.)
# --features blk,net blk = rootfs disk (krun_add_disk2),
# net = virtio-net. No gpu => no libepoxy/
# virglrenderer. Hypervisor.framework is linked
# automatically by src/vmm/build.rs.
# Output: target/release/libkrun.dylib (install_name libkrun.1.dylib,
# set by src/libkrun/build.rs).
run: cargo build --release -p libkrun --no-default-features --features blk,net
- name: Stage the dylib and assert it is self-contained
# A trimmed build must link only system libraries. Any /opt/homebrew or
# /usr/local reference means a dependency leaked (e.g. a stray GPU lib)
# and the dylib would fail to load on a machine without Homebrew — fail
# loudly rather than ship an unloadable artifact. `otool -D` prints the
# install name for cross-checking against minvmd's recorded load
# command (see the minvmd macOS job); informational.
run: |
dylib=target/release/libkrun.dylib
echo "otool -L $dylib"; otool -L "$dylib"
echo "otool -D $dylib (install name):"; otool -D "$dylib"
if otool -L "$dylib" | grep -E '/opt/homebrew|/usr/local'; then
echo "::error::trimmed libkrun links a non-system (Homebrew) dylib; see otool -L above" >&2
exit 1
fi
cp "$dylib" "$RUNNER_TEMP/temp-libkrun-macos-arm64.dylib"
- name: Upload unsigned libkrun dylib (temp)
- name: Provision libkrun (pinned own build)
uses: ./.github/actions/setup-libkrun-macos
- name: Stage the dylib for signing
# The build script already asserted self-containment (no
# /opt/homebrew or /usr/local deps) and the krun_add_disk3 API
# floor, set the @rpath/libkrun.1.dylib install name, and ad-hoc
# signed it.
run: cp "$LIBKRUN_PREFIX/libkrun.1.dylib" "$RUNNER_TEMP/temp-libkrun-macos-arm64.dylib"
- name: Upload ad-hoc-signed libkrun dylib (temp)
# This GitHub-hosted runner has no access to the Developer ID signing
# keychain, so the dylib leaves here UNSIGNED under a `temp-` prefixed
# name. sign-macos-artifacts (self-hosted) downloads it, signs it,
# and re-uploads under the final `libkrun-macos-arm64` name. The
# release job drops any leftover `temp-` artifact so the unsigned copy
# never ships.
# keychain, so the dylib leaves here only ad-hoc signed, under a
# `temp-` prefixed name. sign-macos-artifacts (self-hosted) downloads
# it, re-signs with Developer ID, and re-uploads under the final
# `libkrun-macos-arm64` name. The release job drops any leftover
# `temp-` artifact so the ad-hoc copy never ships.
uses: actions/upload-artifact@v7
with:
name: temp-libkrun-macos-arm64
Expand Down Expand Up @@ -382,8 +351,8 @@ jobs:
# Sign with the same Developer ID identity, secure --timestamp, and
# hardened runtime (--options runtime) as the binaries so the whole
# install is notarization-ready. --force replaces each file's existing
# signature (cargo's ad-hoc on libkrun, the Go linker's ad-hoc on
# gvproxy). No entitlements: neither libkrun (a library) nor gvproxy
# signature (build-libkrun-macos.sh's ad-hoc on libkrun, the Go
# linker's ad-hoc on gvproxy). No entitlements: neither libkrun (a library) nor gvproxy
# (the network switch) is the hypervisor entry point. The temp- prefix
# is dropped so each file carries the final basename the
# archive/installer expect.
Expand Down
98 changes: 98 additions & 0 deletions scripts/build-libkrun-macos.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#!/usr/bin/env bash
# Build the trimmed libkrun.dylib for macOS from source, at the commit pinned
# in vendor/libkrun/libkrun.lock, and stage it into PREFIX. This is the ONE
# libkrun macOS CI links/boots against and the release ships — CI exercises
# the exact dylib users receive, and no third-party bottle (the old slp/krun
# Homebrew tap) is in the supply chain.
#
# Trimmed build (this is exactly the Makefile's INIT_BLOB=0 config):
# -p libkrun scope to the libkrun crate, so the src/input &
# src/display workspace members (whose bindgen build
# scripts need libclang) are never built.
# --no-default-features drop `init-blob`, whose build script cross-compiles
# a Linux init — the ONLY reason the Makefile needs
# lld + a Debian sysroot. minvmd supplies its own
# initramfs, so libkrun's bundled init is unused.
# --features blk,net blk = rootfs disk (krun_add_disk2/3), net =
# virtio-net. No gpu => no libepoxy/virglrenderer.
# Hypervisor.framework is linked by src/vmm/build.rs.
#
# The staged dylib's install name is set to @rpath/libkrun.1.dylib so anything
# linking it (minvmd) records a relocatable load command from the start — the
# shipped bin/../lib layout then needs only an rpath, not a load-command
# rewrite. Ad-hoc signed (arm64 macOS refuses to load an invalid signature);
# the release pipeline re-signs with Developer ID before shipping.
#
# Stages:
# PREFIX/libkrun.1.dylib the dylib (dyld resolves @rpath/libkrun.1.dylib)
# PREFIX/libkrun.dylib symlink for the build-time linker (-l krun)
#
# Requires: git, a Rust toolchain, Xcode CLT (codesign/otool/install_name_tool).
#
# Usage: scripts/build-libkrun-macos.sh <prefix-dir>
set -euo pipefail

PREFIX="${1:?usage: build-libkrun-macos.sh <prefix-dir>}"

case "$(uname -s)" in
Darwin) ;;
*) echo "build-libkrun-macos.sh is macOS-only (got $(uname -s))" >&2; exit 1 ;;
esac

ROOT="$(cd "$(dirname "$0")/.." && pwd)"
LOCK="$ROOT/vendor/libkrun/libkrun.lock"
VERSION="$(sed -n 's/^version=//p' "$LOCK")"
COMMIT="$(sed -n 's/^commit=//p' "$LOCK")"
if [ -z "$VERSION" ] || [ -z "$COMMIT" ]; then
echo "::error::malformed $LOCK: need version= and commit= lines" >&2
exit 1
fi

WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT

# Fetch the pinned commit directly (not the tag): a moved or deleted tag can
# never change what we build, and GitHub serves arbitrary-sha fetches.
echo "fetching containers/libkrun @ $COMMIT ($VERSION)"
git -C "$WORK" init -q
git -C "$WORK" remote add origin https://github.com/containers/libkrun
git -C "$WORK" fetch -q --depth 1 origin "$COMMIT"
git -C "$WORK" checkout -q FETCH_HEAD
actual="$(git -C "$WORK" rev-parse HEAD)"
if [ "$actual" != "$COMMIT" ]; then
echo "::error::fetched libkrun HEAD $actual != pinned commit $COMMIT" >&2
exit 1
fi

echo "building trimmed libkrun (blk,net; no gpu, no init-blob)"
# --locked: build exactly upstream's committed Cargo.lock — a silent
# re-resolve would undermine the pinned, reproducible-build guarantee.
cargo build --release --locked -p libkrun --no-default-features --features blk,net \
--manifest-path "$WORK/Cargo.toml"
DYLIB="$WORK/target/release/libkrun.dylib"
test -f "$DYLIB" || { echo "::error::build produced no $DYLIB" >&2; exit 1; }

# A trimmed build must link only system libraries. Any /opt/homebrew or
# /usr/local reference means a dependency leaked (e.g. a stray GPU lib) and
# the dylib would fail to load on a machine without Homebrew.
otool -L "$DYLIB"
if otool -L "$DYLIB" | grep -E '/opt/homebrew|/usr/local'; then
echo "::error::trimmed libkrun links a non-system dylib; see otool -L above" >&2
exit 1
fi

# krun_add_disk3 (the /dev/vdb attach) needs libkrun >= 1.19.0 — assert the
# pin never silently regresses below the API surface minvmd uses.
if ! nm -gU "$DYLIB" | grep -q '_krun_add_disk3'; then
echo "::error::built libkrun lacks krun_add_disk3 (need >= 1.19.0); check the pin in $LOCK" >&2
exit 1
fi

# Relocatable install name + a valid (ad-hoc) signature, then stage.
install_name_tool -id @rpath/libkrun.1.dylib "$DYLIB"
codesign --sign - --force "$DYLIB"

mkdir -p "$PREFIX"
cp "$DYLIB" "$PREFIX/libkrun.1.dylib"
ln -sfn libkrun.1.dylib "$PREFIX/libkrun.dylib"
echo "staged libkrun $VERSION ($COMMIT) -> $PREFIX"
8 changes: 8 additions & 0 deletions vendor/libkrun/libkrun.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# libkrun (containers/libkrun) pinned source build for macOS.
# Single source of truth for the libkrun that macOS CI links/boots against
# AND that the release ships — built by scripts/build-libkrun-macos.sh
# (trimmed: --no-default-features --features blk,net).
# `commit` is the resolved commit of `version` at pin time; the build fetches
# the commit, so a moved/deleted tag cannot change what we build.
version=v1.19.4
commit=728df8125077d0db44265f6e997c72b81b65c015
Loading
0