Skip to content

Commit 1c4cf39

Browse files
authored
Merge pull request #43 from stacklok/bundling
Bundle GPU dylibs for portable macOS builds
2 parents 455a904 + ace033d commit 1c4cf39

3 files changed

Lines changed: 104 additions & 7 deletions

File tree

Taskfile.yaml

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,20 +83,75 @@ tasks:
8383
platforms: [darwin]
8484
cmds:
8585
- task: build-dev-darwin
86+
# Copy all required dylibs from Homebrew
8687
- cp -f $(brew --prefix libkrun)/lib/libkrun.{{.LIBKRUN_MAJOR}}.dylib bin/
8788
- cp -f $(brew --prefix libkrunfw)/lib/libkrunfw.{{.LIBKRUNFW_MAJOR}}.dylib bin/
88-
# Rewrite absolute Homebrew install name to @loader_path for portable bundles
89+
- cp -f $(brew --prefix libepoxy)/lib/libepoxy.0.dylib bin/
90+
- cp -f $(brew --prefix virglrenderer)/lib/libvirglrenderer.1.dylib bin/
91+
- cp -f $(brew --prefix molten-vk)/lib/libMoltenVK.dylib bin/
92+
# Rewrite LC_ID_DYLIB (each dylib's own identity) for portable bundles
93+
- install_name_tool -id @loader_path/libkrun.{{.LIBKRUN_MAJOR}}.dylib bin/libkrun.{{.LIBKRUN_MAJOR}}.dylib
94+
- install_name_tool -id @loader_path/libkrunfw.{{.LIBKRUNFW_MAJOR}}.dylib bin/libkrunfw.{{.LIBKRUNFW_MAJOR}}.dylib
95+
- install_name_tool -id @loader_path/libepoxy.0.dylib bin/libepoxy.0.dylib
96+
- install_name_tool -id @loader_path/libvirglrenderer.1.dylib bin/libvirglrenderer.1.dylib
97+
- install_name_tool -id @loader_path/libMoltenVK.dylib bin/libMoltenVK.dylib
98+
# Rewrite propolis-runner → libkrun reference
8999
- >-
90100
install_name_tool -change
91101
/opt/homebrew/opt/libkrun/lib/libkrun.{{.LIBKRUN_MAJOR}}.dylib
92102
@loader_path/libkrun.{{.LIBKRUN_MAJOR}}.dylib
93103
bin/{{.RUNNER_NAME}}
94-
# Re-sign after binary modification (install_name_tool invalidates signature)
104+
# Rewrite libkrun → libepoxy, virglrenderer references
105+
- >-
106+
install_name_tool -change
107+
/opt/homebrew/opt/libepoxy/lib/libepoxy.0.dylib
108+
@loader_path/libepoxy.0.dylib
109+
bin/libkrun.{{.LIBKRUN_MAJOR}}.dylib
110+
- >-
111+
install_name_tool -change
112+
/opt/homebrew/opt/virglrenderer/lib/libvirglrenderer.1.dylib
113+
@loader_path/libvirglrenderer.1.dylib
114+
bin/libkrun.{{.LIBKRUN_MAJOR}}.dylib
115+
# Rewrite virglrenderer → MoltenVK, libepoxy references
116+
- >-
117+
install_name_tool -change
118+
/opt/homebrew/opt/molten-vk/lib/libMoltenVK.dylib
119+
@loader_path/libMoltenVK.dylib
120+
bin/libvirglrenderer.1.dylib
121+
- >-
122+
install_name_tool -change
123+
/opt/homebrew/opt/libepoxy/lib/libepoxy.0.dylib
124+
@loader_path/libepoxy.0.dylib
125+
bin/libvirglrenderer.1.dylib
126+
# Remove stale Homebrew rpaths (ignore errors if none exist)
127+
- for f in bin/libkrun.{{.LIBKRUN_MAJOR}}.dylib bin/libkrunfw.{{.LIBKRUNFW_MAJOR}}.dylib bin/libepoxy.0.dylib bin/libvirglrenderer.1.dylib bin/libMoltenVK.dylib; do
128+
for rp in $(otool -l "$f" 2>/dev/null | grep -A2 LC_RPATH | grep 'path /opt/homebrew' | awk '{print $2}'); do
129+
install_name_tool -delete_rpath "$rp" "$f" 2>/dev/null || true;
130+
done;
131+
done
132+
# Code-sign dylibs ad-hoc (no entitlements), then runner last with entitlements
133+
- codesign --force -s - bin/libMoltenVK.dylib
134+
- codesign --force -s - bin/libepoxy.0.dylib
135+
- codesign --force -s - bin/libvirglrenderer.1.dylib
136+
- codesign --force -s - bin/libkrunfw.{{.LIBKRUNFW_MAJOR}}.dylib
137+
- codesign --force -s - bin/libkrun.{{.LIBKRUN_MAJOR}}.dylib
95138
- codesign --entitlements assets/entitlements.plist --force -s - bin/{{.RUNNER_NAME}}
139+
# Verify no Homebrew references remain in bundled files
140+
- |
141+
for f in bin/{{.RUNNER_NAME}} bin/libkrun.{{.LIBKRUN_MAJOR}}.dylib bin/libkrunfw.{{.LIBKRUNFW_MAJOR}}.dylib bin/libepoxy.0.dylib bin/libvirglrenderer.1.dylib bin/libMoltenVK.dylib; do
142+
if otool -L "$f" | grep -q /opt/homebrew; then
143+
echo "FAIL: $f still references /opt/homebrew"
144+
otool -L "$f" | grep /opt/homebrew
145+
exit 1
146+
fi
147+
done
96148
generates:
97149
- bin/{{.RUNNER_NAME}}
98150
- bin/libkrun.{{.LIBKRUN_MAJOR}}.dylib
99151
- bin/libkrunfw.{{.LIBKRUNFW_MAJOR}}.dylib
152+
- bin/libepoxy.0.dylib
153+
- bin/libvirglrenderer.1.dylib
154+
- bin/libMoltenVK.dylib
100155

101156
build-dev-race:
102157
desc: Build runner with race detector (requires libkrun-devel)
@@ -265,7 +320,7 @@ tasks:
265320
rm -rf "${staging}"
266321
267322
package-runtime-darwin:
268-
desc: Package macOS runtime tarball (runner + libkrun)
323+
desc: Package macOS runtime tarball (runner + libkrun + GPU dylibs)
269324
platforms: [darwin]
270325
vars:
271326
TAG: '{{.TAG | default .VERSION}}'
@@ -274,7 +329,9 @@ tasks:
274329
- |
275330
staging="propolis-runtime-darwin-{{.HOST_ARCH}}"
276331
mkdir -p "${staging}"
277-
cp bin/propolis-runner bin/libkrun.{{.LIBKRUN_MAJOR}}.dylib "${staging}/"
332+
cp bin/propolis-runner bin/libkrun.{{.LIBKRUN_MAJOR}}.dylib \
333+
bin/libepoxy.0.dylib bin/libvirglrenderer.1.dylib bin/libMoltenVK.dylib \
334+
"${staging}/"
278335
echo "{{.TAG}}" > "${staging}/VERSION"
279336
tar czf "dist/${staging}.tar.gz" "${staging}"
280337
rm -rf "${staging}"

extract/source.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,15 @@ func (s *dirSource) Ensure(_ context.Context, _ string) (string, error) {
6262
// into a versioned cache directory. The runner and libkrun byte slices are
6363
// the file contents to extract. The libkrun major soname version is always 1
6464
// because the runner binary is built against a specific libkrun ABI.
65-
func RuntimeBundle(version string, runner, libkrun []byte) Source {
66-
return &bundleSource{bundle: NewBundle(version, []File{
65+
// Additional dylibs (e.g. libepoxy, virglrenderer, MoltenVK on macOS) can be
66+
// passed via extraLibs and will be extracted alongside the core files.
67+
func RuntimeBundle(version string, runner, libkrun []byte, extraLibs ...File) Source {
68+
files := []File{
6769
{Name: RunnerBinaryName, Content: runner, Mode: 0o755},
6870
{Name: LibName("krun", 1), Content: libkrun, Mode: 0o755},
69-
})}
71+
}
72+
files = append(files, extraLibs...)
73+
return &bundleSource{bundle: NewBundle(version, files)}
7074
}
7175

7276
// FirmwareBundle creates a Source that extracts libkrunfw into a versioned

extract/source_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,42 @@ func TestBundleSource_EmptyCacheDir(t *testing.T) {
155155
assert.Contains(t, err.Error(), "cache directory must not be empty")
156156
}
157157

158+
func TestRuntimeBundle_ExtraLibs(t *testing.T) {
159+
t.Parallel()
160+
161+
cacheDir := t.TempDir()
162+
runnerData := []byte("runner-binary")
163+
libkrunData := []byte("libkrun-data")
164+
epoxyData := []byte("libepoxy-data")
165+
virglData := []byte("virgl-data")
166+
167+
src := RuntimeBundle("v1.0.0", runnerData, libkrunData,
168+
File{Name: "libepoxy.0.dylib", Content: epoxyData, Mode: 0o755},
169+
File{Name: "libvirglrenderer.1.dylib", Content: virglData, Mode: 0o755},
170+
)
171+
172+
dir, err := src.Ensure(context.Background(), cacheDir)
173+
require.NoError(t, err)
174+
175+
// Verify core files still present.
176+
got, err := os.ReadFile(filepath.Join(dir, RunnerBinaryName))
177+
require.NoError(t, err)
178+
assert.Equal(t, runnerData, got)
179+
180+
got, err = os.ReadFile(filepath.Join(dir, LibName("krun", 1)))
181+
require.NoError(t, err)
182+
assert.Equal(t, libkrunData, got)
183+
184+
// Verify extra libs.
185+
got, err = os.ReadFile(filepath.Join(dir, "libepoxy.0.dylib"))
186+
require.NoError(t, err)
187+
assert.Equal(t, epoxyData, got)
188+
189+
got, err = os.ReadFile(filepath.Join(dir, "libvirglrenderer.1.dylib"))
190+
require.NoError(t, err)
191+
assert.Equal(t, virglData, got)
192+
}
193+
158194
func TestRuntimeBundle_ConcurrentEnsure(t *testing.T) {
159195
t.Parallel()
160196

0 commit comments

Comments
 (0)