Repository navigation
ci(macos): consolidate libkrun on our own pinned source build #694
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
7fca328
655ea22
4cd7c2a
fff6d9d
35bc4d5
f9204c4
a764471
3c79642
40b2bc7
637ca84
a727611
6fc9ad8
bfe3f9c
7c4704f
2c3ff8f
ce25653
5b28788
3a49c4d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 }} | 10BC8||
| - 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 | ||
| - 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" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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: | ||
|
|
@@ -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: | ||
|
|
@@ -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 | ||
|
|
@@ -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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Fail closed when dependency inspection fails. Because 🤖 Prompt for AI Agents |
||
| - 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) | ||
|
|
@@ -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 | ||
|
|
||
| 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" |
| 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 |
Uh oh!
There was an error while loading. Please reload this page.