8000 feat: stage libkrun in lib directory instead of bin directory by twitchyliquid64 · Pull Request #670 · gominimal/minimal · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
28 changes: 22 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -229,19 +229,19 @@ jobs:
- name: Build release minvmd binary
run: cargo build --release -p minvmd --bin minvmd
- name: Point minvmd's libkrun load command at @rpath
# Homebrew's libkrun bakes an ABSOLUTE install name into minvmd; dyld
# resolves it directly and never consults the rpath, ignoring the
# shipped bin/libkrun.1.dylib. Rewrite it to @rpath so dyld walks
# minvmd's rpath (@loader_path first, /opt/homebrew/lib fallback).
# Derive the path from otool so a libkrun bump can't skip the rewrite.
# install_name_tool breaks the signature, so re-sign.
# Homebrew's libkrun bakes an ABSOLUTE install name into minvmd; rewrite
# it so that minvmd looks at ./minvmd/../lib/libkrun.1.dylib, as
# per the layout the installer drops.
#
# build.rs bakes a bin-relative @loader_path rpath for development.
run: |
current="$(otool -L target/release/minvmd | awk '/libkrun/ {print $1; exit}')"
if [ -z "$current" ]; then
echo "::error::minvmd has no libkrun load command; did it link the stub?" >&2
exit 1
fi
install_name_tool -change "$current" @rpath/libkrun.1.dylib target/release/minvmd
install_name_tool -rpath @loader_path @loader_path/../lib target/release/minvmd
codesign --sign - --force target/release/minvmd
- name: Verify minvmd linkage
# libkrun must resolve via @rpath (rewrite stuck), and every other
Expand All @@ -253,6 +253,22 @@ jobs:
echo "::error::minvmd does not reference @rpath/libkrun.1.dylib; the shipped dylib will not be found" >&2
exit 1
fi
# The shipped dylib lives in lib/ (a sibling of bin/), so the
# @loader_path -> @loader_path/../lib retarget above must have
# stuck; without it dyld never looks one dir up from bin/minvmd
# and the load fails at runtime. Pull just the LC_RPATH `path`
# values so the checks match exact entries, not substrings.
otool -l target/release/minvmd | grep -A2 LC_RPATH
rpaths="$(otool -l target/release/minvmd \
| awk '$1=="path" && $2 ~ /@loader_path/ {print $2}')"
if ! printf '%s\n' "$rpaths" | grep -qx '@loader_path/../lib'; then
echo "::error::minvmd is missing the @loader_path/../lib rpath; lib/libkrun.1.dylib will not be found" >&2
exit 1
fi
if printf '%s\n' "$rpaths" | grep -qx '@loader_path'; then
echo "::error::minvmd still has a bare @loader_path rpath; the lib retarget did not replace it" >&2
exit 1
fi
bad="$(otool -L target/release/minvmd | tail -n +2 | awk '{print $1}' \
| grep -vE '^(/usr/lib/|/System/|@rpath/libkrun\.1\.dylib$)' || true)"
if [ -n "$bad" ]; then
Expand Down
11 changes: 7 additions & 4 deletions crates/minvmd/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,13 @@ fn main() {
fn emit_rpaths(target_os: &str, prefix: &str) {
let is_release = std::env::var("PROFILE").as_deref() == Ok("release");

// 1. Binary-relative: finds libkrun shipped alongside a relocated binary
// (release-staged `bin/libkrun.1.dylib`). dyld honours it only for an
// `@rpath/` load command — the macOS release rewrites minvmd's to that.
// Recorded verbatim (literal argv token, no shell expansion).
// 1. Binary-relative: finds a libkrun shipped alongside a relocated binary
// (e.g. a dev build with the dylib next to it). dyld honours it only for an
// `@rpath/` load command — the macOS release rewrites minvmd's to that. The
// macOS *release* stages the dylib in a `lib/` sibling of `bin/` and
// retargets this very entry to `@loader_path/../lib` post-build (see
// release.yml), so the same rpath covers the dev layout here and the ship
// layout there. Recorded verbatim (literal argv token, no shell expansion).
rpath(if target_os == "macos" {
"@loader_path"
} else {
Expand Down
24 changes: 13 additions & 11 deletions scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ esac
resolve_prefix() {
case "$1" in
bin) printf '%s\n' "${MINIMAL_BIN:-$HOME/.local/bin}" ;;
lib) printf '%s\n' "${XDG_LIB_HOME:-$HOME/.local/lib}" ;;
data) printf '%s\n' "${XDG_DATA_HOME:-$HOME/.local/share}/minimal" ;;
state) printf '%s\n' "${XDG_STATE_HOME:-$HOME/.local/state}/minimal" ;;
cache) printf '%s\n' "${XDG_CACHE_HOME:-$HOME/.cache}/minimal" ;;
Expand Down Expand Up @@ -241,10 +242,12 @@ do_uninstall() {
fi

# R8.1 — prune now-empty minimal-owned dirs with rmdir only (never rm -rf):
# the shared bin dir is removed only if empty, and a non-empty dir fails
# the shared bin/lib dirs are removed only if empty, and a non-empty dir fails
# rmdir harmlessly. --purge (above) already cleared the data/state/cache trees.
# lib (~/.local/lib) is shared like bin, so it is only ever rmdir'd-if-empty,
# never purged.
if [ "$dry_run" -eq 0 ]; then
for p in bin data state cache; do
for p in bin lib data state cache; do
d="$(resolve_prefix "$p")"
if [ -d "$d" ]; then
rmdir "$d" 2>/dev/null || true
Expand Down Expand Up @@ -407,15 +410,14 @@ while read -r comp _ _ _ want kind dest src; do
# chmod the temp file, then rename.
[ "$prefix" = bin ] && chmod +x "$tmp"

# macOS: make a freshly-downloaded bin executable actually runnable before it
# is placed. Strip the Gatekeeper quarantine attribute (metadata, so it never
# affected the hash above; a no-op if absent — hence the swallowed error),
# then ad-hoc self-sign: arm64 macOS refuses to exec an unsigned or
# transfer-invalidated Mach-O, and our release binaries are not yet signed
# with a developer identity. Signing rewrites the Mach-O, so the installed
# bytes deliberately diverge from the manifest hash — the skip oracle and the
# installed-hash record both account for this.
if [ "$os" = darwin ] && [ "$prefix" = bin ]; then
# macOS: make a freshly-downloaded Mach-O (a bin executable or a shipped lib
# dylib) actually loadable before it is placed.
#
# * Strip the Gatekeeper quarantine attribute so it can run
# * ad-hoc self-sign: arm64 macOS refuses to exec/dlopen an unsigned or
# transfer-invalidated Mach-O, and our release artifacts are not yet signed
# with a developer identity.
if [ "$os" = darwin ] && { [ "$prefix" = bin ] || [ "$prefix" = lib ]; }; then
xattr -d com.apple.quarantine "$tmp" 2>/dev/null || true
# Special case: minvmd needs the hypervisor entitlement.
if [ "$comp" = minvmd ]; then
Expand Down
43 changes: 43 additions & 0 deletions scripts/install_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,26 @@ want_ok "data resolves under XDG_DATA_HOME/minimal (R4.1)" \
test -f "$H5/xdg-data/minimal/rootfs.img"
cp "$root/good-components" "$mock/versions/v1/components"

# The `lib` prefix (R4.1) is a bin SIBLING: it resolves to ~/.local/lib with NO
# `/minimal` suffix (unlike data/state/cache), so a bin/<x> binary reaches a
# shipped lib/<y> via a `@loader_path/../lib` rpath. XDG_LIB_HOME is unset in the
# run env, so it must fall back to $HOME/.local/lib. A lib file is also not `bin`,
# so it must NOT be marked executable.
{
printf '# format: 1\n'
printf '# c o a v s k d s\n'
printf '%-12s %-7s %-7s %-9s %-64s %-6s %-20s %s\n' \
libkrun linux amd64 v1 "$h_rootfs" file lib/libkrun.1.dylib versions/v1/rootfs-arm64.img
} >"$mock/versions/v1/components"
H6="$root/h6"; mkdir -p "$H6"
run libdest "$H6"
check 0 "$rc" "lib-prefixed component installs (R4.1)"
want_ok "lib falls back to HOME/.local/lib, no /minimal suffix (R4.1)" \
test -f "$H6/.local/lib/libkrun.1.dylib"
want_err "lib component is not marked executable (only bin gets +x)" \
test -x "$H6/.local/lib/libkrun.1.dylib"
cp "$root/good-components" "$mock/versions/v1/components"

# --- Unit 6: install record (R6.1) + PATH advisory (R6.2) ------------------
record="$H1/xdg-state/minimal/installed"
want_ok "install record lists components (R6.1)" grep -q "minimald" "$record"
Expand Down Expand Up @@ -336,6 +356,29 @@ want_ok "quarantine stripped from bin (xattr)" grep -q "com.apple.quarantine" "$
want_ok "bin file ad-hoc codesigned" grep -q "/bin/minimal" "$root/codesign.calls"
want_err "data component not codesigned" grep -q "rootfs" "$root/codesign.calls"

# A darwin `lib` dylib gets the SAME Mach-O treatment as a bin file — quarantine
# strip + ad-hoc codesign (the plain else-branch sign, no minvmd entitlement) —
# so the shipped libkrun.1.dylib is loadable. Isolated home + one-off manifest
# with a single darwin lib row, so it doesn't perturb the rerun counts below.
{
printf '# format: 1\n'
printf '# c o a v s k d s\n'
printf '%-12s %-7s %-7s %-9s %-64s %-6s %-20s %s\n' \
libkrun darwin arm64 v1 "$h_rootfs" file lib/libkrun.1.dylib versions/v1/rootfs-arm64.img
} >"$mock/versions/v1/components"
: >"$root/codesign.calls"; : >"$root/xattr.calls"
HDL="$root/hdl"; mkdir -p "$HDL"
reset_dl
run darwinlib "$HDL"
check 0 "$rc" "darwin lib install exits 0"
want_ok "darwin lib dylib installed" test -f "$HDL/.local/lib/libkrun.1.dylib"
want_ok "quarantine stripped from lib dylib (xattr)" \
grep -q "com.apple.quarantine" "$root/xattr.calls"
want_ok "lib dylib ad-hoc codesigned" \
grep -q "/lib/libkrun.1.dylib" "$root/codesign.calls"
cp "$root/good-components" "$mock/versions/v1/components" # restore
: >"$root/codesign.calls"; : >"$root/xattr.calls"

# Signing diverged the on-disk bytes from the manifest hash. A rerun must still
# recognize the component as installed (via the recorded hash) and not download
# or re-sign it — otherwise macOS reruns would never be cheap (Goal 2).
Expand Down
14 changes: 7 additions & 7 deletions scripts/stage-release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,9 @@ fi
# One entry per (component, os, arch) variant the installer should place, as
# `component|os|arch|kind|dest|artifact-basename`:
# - os/arch use the installer's normalized names (linux|darwin, amd64|arm64).
# - dest is `<prefix-token>/<subpath>`; the installer maps `bin` -> ~/.local/bin
# and `data` -> ~/.local/share/minimal, and marks anything under `bin` +x.
# - dest is `<prefix-token>/<subpath>`; the installer maps `bin` -> ~/.local/bin,
# `lib` -> ~/.local/lib (a bin sibling, for @rpath/../lib), and `data` ->
# ~/.local/share/minimal, and marks anything under `bin` +x.
# - artifact-basename is the file's name inside ARTIFACTS_DIR (the release
# workflow's merge-multiple flattens every upload to its basename).
#
Expand All @@ -114,11 +115,10 @@ COMPONENTS=(
"minimal|darwin|arm64|file|bin/minimal|minimal-macos-arm64"
"minvmd|darwin|arm64|file|bin/minvmd|minvmd-macos-arm64"
# The trimmed libkrun minvmd links against (built by the release workflow's
# build-libkrun-macos-arm64 job), staged into bin/ next to minvmd. Basename
# MUST be libkrun.1.dylib: minvmd's load command is @rpath/libkrun.1.dylib and
# @loader_path (bin/) is its first rpath, so the loader looks for that exact
# name beside the binary.
"libkrun|darwin|arm64|file|bin/libkrun.1.dylib|libkrun-macos-arm64.dylib"
# build-libkrun-macos-arm64 job), staged into lib/ (ie: minvmd/../lib).
# Basename MUST be libkrun.1.dylib: minvmd's load command is @rpath/libkrun.1.dylib
# and the release job adds a @loader_path/../lib rpath.
"libkrun|darwin|arm64|file|lib/libkrun.1.dylib|libkrun-macos-arm64.dylib"
"gvproxy|darwin|arm64|file|bin/gvproxy|gvproxy-darwin-arm64"
"initramfs|darwin|arm64|file|data/initramfs.cpio|initramfs-arm64.cpio"
"rootfs|darwin|arm64|file|data/rootfs.img|rootfs-arm64.img"
Expand Down
Loading
0