fix(linux): auto-detect and handle .relr.dyn sections for AppImage builds#4772
Conversation
WalkthroughAdds documentation and runtime detection to avoid stripping AppImage GTK libraries with .relr.dyn ELF sections by setting NO_STRIP=1 at packaging time; updates AppImage build logic to detect such sections and adjusts error output printing. No other behavioral API changes. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings, 1 inconclusive)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro 📒 Files selected for processing (3)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (1)
🧰 Additional context used🧠 Learnings (2)📓 Common learnings📚 Learning: 2024-10-08T22:11:37.054ZApplied to files:
🧬 Code graph analysis (1)v3/internal/commands/appimage.go (2)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
🔇 Additional comments (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
…ilds Modern Linux distributions (Arch, Fedora 39+, Ubuntu 24.04+) compile libraries with .relr.dyn ELF sections. The bundled strip binary in linuxdeploy cannot process these sections, causing AppImage builds to fail. This commit: - Adds hasRelrDynSections() to proactively detect modern toolchains - Automatically disables stripping (NO_STRIP=1) when detected - Fixes error output to properly display as string - Adds documentation explaining the issue and workaround Fixes #4642 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
275ea95 to
a7f16d2
Compare
Deploying wails with
|
| Latest commit: |
a7f16d2
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://ff11ba03.wails.pages.dev |
| Branch Preview URL: | https://fix-appimage-strip-compatibi.wails.pages.dev |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
docs/src/content/docs/guides/build/linux.mdx (1)
148-160: Make the “newer strip” path actionable (or link to upstream docs).
Right now it says “configurelinuxdeployto use it” but doesn’t explain how or where to learn it. Consider adding either (a) a short snippet with the exact env var / mechanism (if stable), or (b) a link to linuxdeploy docs/issues describing overriding the bundledstrip.v3/internal/commands/appimage.go (1)
260-280:hasRelrDynSections()should key off the actual GTK lib path (or at least GTK version), and quote paths.
Hardcoding just GTK3 paths makes this brittle across GTK2/GTK4 and across distros/libdirs; alsoreadelf -S %sshould quote the path to avoid shlex splitting edge-cases.Concrete refactor (still cheap): derive a candidate GTK lib path from
lddStringbased onDeployGtkVersion, then fallback to your current list.-func hasRelrDynSections() bool { +func hasRelrDynSections(deployGtkVersion string, lddString string) bool { + // Try to use the exact lib path from ldd first (more robust than hardcoded libdirs) + // Example ldd line: "libgtk-3.so.0 => /usr/lib/... (0x...)" + var gtkNeedle string + switch deployGtkVersion { + case "2": + gtkNeedle = "libgtk-x11-2.0.so.0 => " + case "3": + gtkNeedle = "libgtk-3.so.0 => " + case "4": + gtkNeedle = "libgtk-4.so.1 => " + } + if gtkNeedle != "" { + if i := strings.Index(lddString, gtkNeedle); i >= 0 { + rest := lddString[i+len(gtkNeedle):] + if fields := strings.Fields(rest); len(fields) > 0 && strings.HasPrefix(fields[0], "/") { + if hasRelrDynInFile(fields[0]) { + return true + } + } + } + } + // Check common GTK library that will be bundled testLibs := []string{ "/usr/lib/libgtk-3.so.0", "/usr/lib64/libgtk-3.so.0", "/usr/lib/x86_64-linux-gnu/libgtk-3.so.0", } for _, lib := range testLibs { if _, err := os.Stat(lib); err == nil { - output, err := s.EXEC(fmt.Sprintf("readelf -S %s", lib)) + output, err := s.EXEC(fmt.Sprintf("readelf -S %q", lib)) if err == nil && strings.Contains(string(output), ".relr.dyn") { return true } } } return false } + +func hasRelrDynInFile(path string) bool { + if _, err := os.Stat(path); err != nil { + return false + } + out, err := s.EXEC(fmt.Sprintf("readelf -S %q", path)) + return err == nil && strings.Contains(string(out), ".relr.dyn") +}(And if
readelfis missing, consider at least logging once, or adding a fallback like “retry linuxdeploy withNO_STRIP=1when the failure contains.relr.dyn”, to avoid silent false negatives.)
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
docs/src/content/docs/guides/build/linux.mdx(1 hunks)v3/internal/commands/appimage.go(2 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: Mihara
Repo: wailsapp/wails PR: 4481
File: v3/internal/commands/updatable_build_assets/linux/nfpm/nfpm.yaml.tmpl:29-30
Timestamp: 2025-08-08T10:25:32.415Z
Learning: For Wails v3 Linux nfpm template, Debian/Ubuntu WebKitGTK runtime package names differ: Debian 12 (Bookworm) uses libwebkit2gtk-4.1-0 while Ubuntu 22.04 (Jammy) uses libwebkit2gtk-4.0-37. Prefer an OR dependency "libwebkit2gtk-4.1-0 | libwebkit2gtk-4.0-37" in v3/internal/commands/updatable_build_assets/linux/nfpm/nfpm.yaml.tmpl to support both.
📚 Learning: 2025-08-08T10:25:32.415Z
Learnt from: Mihara
Repo: wailsapp/wails PR: 4481
File: v3/internal/commands/updatable_build_assets/linux/nfpm/nfpm.yaml.tmpl:29-30
Timestamp: 2025-08-08T10:25:32.415Z
Learning: For Wails v3 Linux nfpm template, Debian/Ubuntu WebKitGTK runtime package names differ: Debian 12 (Bookworm) uses libwebkit2gtk-4.1-0 while Ubuntu 22.04 (Jammy) uses libwebkit2gtk-4.0-37. Prefer an OR dependency "libwebkit2gtk-4.1-0 | libwebkit2gtk-4.0-37" in v3/internal/commands/updatable_build_assets/linux/nfpm/nfpm.yaml.tmpl to support both.
Applied to files:
docs/src/content/docs/guides/build/linux.mdx
📚 Learning: 2025-08-08T11:10:17.787Z
Learnt from: Mihara
Repo: wailsapp/wails PR: 4481
File: v3/internal/commands/updatable_build_assets/linux/nfpm/nfpm.yaml.tmpl:37-38
Timestamp: 2025-08-08T11:10:17.787Z
Learning: In Wails v3 nfpm templates for RPM (RHEL-family), do not use a boolean OR between webkit2gtk3 and webkit2gtk4.1. Binaries built against WebKitGTK 4.1 are not ABI-compatible with 4.0, so the dependency must be pinned to the exact ABI: use webkit2gtk4.1 for 4.1 builds (e.g., RHEL/Rocky 10) and webkit2gtk3 for 4.0 builds (RHEL/Rocky 8/9).
Applied to files:
docs/src/content/docs/guides/build/linux.mdx
📚 Learning: 2024-10-08T22:11:37.054Z
Learnt from: leaanthony
Repo: wailsapp/wails PR: 3763
File: v3/internal/commands/appimage_testfiles/main.go:295-299
Timestamp: 2024-10-08T22:11:37.054Z
Learning: In `v3/internal/commands/appimage_testfiles/main.go`, `time.Sleep` is used within a goroutine and does not block the UI thread.
Applied to files:
v3/internal/commands/appimage.go
🧬 Code graph analysis (1)
v3/internal/commands/appimage.go (2)
v3/internal/term/term.go (2)
Infof(64-66)Println(109-111)v3/internal/s/s.go (2)
SETENV(84-88)EXEC(269-280)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Run Go Tests v3 (ubuntu-latest, 1.24)
- GitHub Check: Run Go Tests v3 (windows-latest, 1.24)
- GitHub Check: Run Go Tests v3 (macos-latest, 1.24)
- GitHub Check: semgrep-cloud-platform/scan
- GitHub Check: Analyze (go)
- GitHub Check: Cloudflare Pages
🔇 Additional comments (1)
v3/internal/commands/appimage.go (1)
201-205: Good: print linuxdeploy output as text on failure.
Switching tofmt.Println(string(output))makes the error output readable and consistent withs.EXECreturning bytes.
|
|
||
| // Check if system libraries use .relr.dyn sections (modern toolchains) | ||
| // If so, disable stripping as linuxdeploy's bundled strip can't handle them | ||
| if hasRelrDynSections() { | ||
| term.Infof("Detected modern toolchain (.relr.dyn sections), disabling stripping for compatibility. See: https://v3.wails.io/guides/build/linux#appimage-strip-compatibility") | ||
| s.SETENV("NO_STRIP", "1") | ||
| } | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cd /tmp && find . -type f -name "appimage.go" | head -5Repository: wailsapp/wails
Length of output: 40
🏁 Script executed:
cat /tmp/appimage.go 2>/dev/null || (find . -type f -name "appimage.go" -path "*/v3/internal/commands/*" | head -1 | xargs cat 2>/dev/null)Repository: wailsapp/wails
Length of output: 7519
🏁 Script executed:
find . -type f -name "appimage.go" | grep -E "v3|commands"Repository: wailsapp/wails
Length of output: 92
🏁 Script executed:
fd appimage.goRepository: wailsapp/wails
Length of output: 90
🏁 Script executed:
# List files in the expected directory structure
ls -la v3/internal/commands/ 2>/dev/null | head -20 || echo "Directory not found, searching..."
fd -t f appimage.goRepository: wailsapp/wails
Length of output: 1320
🏁 Script executed:
# Search for the file and read it
if [ -f "v3/internal/commands/appimage.go" ]; then
wc -l v3/internal/commands/appimage.go
fiRepository: wailsapp/wails
Length of output: 94
🏁 Script executed:
git ls-files | grep -i appimage | head -10Repository: wailsapp/wails
Length of output: 549
🏁 Script executed:
# Get the appimage.go file
git ls-files | grep appimage.goRepository: wailsapp/wails
Length of output: 90
🏁 Script executed:
# Find and display the appimage.go file
git show HEAD:v3/internal/commands/appimage.go | head -300Repository: wailsapp/wails
Length of output: 7519
🏁 Script executed:
# Get the full file
git show HEAD:v3/internal/commands/appimage.go > /tmp/appimage_full.txt 2>&1
wc -l /tmp/appimage_full.txt
head -20 /tmp/appimage_full.txtRepository: wailsapp/wails
Length of output: 370
🏁 Script executed:
# Search for hasRelrDynSections function
git show HEAD:v3/internal/commands/appimage.go | grep -n -A 20 "hasRelrDynSections"Repository: wailsapp/wails
Length of output: 1534
🏁 Script executed:
# Get lines around 193-200 and the hasRelrDynSections function
git show HEAD:v3/internal/commands/appimage.go | sed -n '180,220p'Repository: wailsapp/wails
Length of output: 1499
🏁 Script executed:
# Get the hasRelrDynSections implementation
git show HEAD:v3/internal/commands/appimage.go | sed -n '250,290p'Repository: wailsapp/wails
Length of output: 950
🏁 Script executed:
# Let me get the full context - search for the function definition
git show HEAD:v3/internal/commands/appimage.go | grep -n "func hasRelrDynSections"Repository: wailsapp/wails
Length of output: 94
🏁 Script executed:
# Get the entire file to understand context
git show HEAD:v3/internal/commands/appimage.go 2>&1 | tail -100Repository: wailsapp/wails
Length of output: 2807
🏁 Script executed:
# Simple approach - get full file and search
git show HEAD:v3/internal/commands/appimage.go 2>&1 | wc -lRepository: wailsapp/wails
Length of output: 61
🏁 Script executed:
# Get the file in full
git show HEAD:v3/internal/commands/appimage.go 2>&1 > /tmp/appimage_check.txt
cat /tmp/appimage_check.txt | head -1Repository: wailsapp/wails
Length of output: 74
🏁 Script executed:
# Direct approach
git ls-files | xargs grep -l "hasRelrDynSections" 2>/dev/nullRepository: wailsapp/wails
Length of output: 92
🏁 Script executed:
# Search in v3 directory
find v3 -name "*.go" -type f 2>/dev/null | xargs grep -l "hasRelrDynSections" 2>/dev/nullRepository: wailsapp/wails
Length of output: 90
🏁 Script executed:
# Try simpler git command
git log --oneline -1
git diff HEAD~1 HEAD -- v3/internal/commands/appimage.go 2>/dev/null | head -100Repository: wailsapp/wails
Length of output: 140
🏁 Script executed:
# Check if file exists and read it
test -f v3/internal/commands/appimage.go && cat v3/internal/commands/appimage.go | head -50Repository: wailsapp/wails
Length of output: 1210
🏁 Script executed:
git ls-files --full-name | grep -E "v3.*appimage"Repository: wailsapp/wails
Length of output: 763
🏁 Script executed:
# List all files
git ls-files | head -20Repository: wailsapp/wails
Length of output: 740
🏁 Script executed:
# Check repository structure
ls -la | head -20Repository: wailsapp/wails
Length of output: 1262
🏁 Script executed:
# Try to find appimage.go in the repo
find . -name "appimage.go" -type f 2>/dev/nullRepository: wailsapp/wails
Length of output: 92
🏁 Script executed:
# Search for NO_STRIP usage patterns
rg "NO_STRIP" --type go -B 2 -A 2Repository: wailsapp/wails
Length of output: 457
🏁 Script executed:
# Search for hasRelrDynSections
rg "hasRelrDynSections" --type go -B 3 -A 15Repository: wailsapp/wails
Length of output: 2784
🏁 Script executed:
# Search for the conditional block mentioned in the review
rg "Detected modern toolchain" --type go -B 5 -A 5Repository: wailsapp/wails
Length of output: 902
🏁 Script executed:
# Look for DeployGtkVersion and lddString
rg "DeployGtkVersion|lddString" --type go -B 2 -A 2Repository: wailsapp/wails
Length of output: 1269
🏁 Script executed:
# Check general NO_STRIP patterns in the codebase
rg "os\.Getenv.*NO_STRIP|NO_STRIP.*os\.Getenv" --type go -B 3 -A 3Repository: wailsapp/wails
Length of output: 40
🏁 Script executed:
# Search for readelf usage (mentioned in the review's analysis)
rg "readelf" --type go -B 5 -A 10Repository: wailsapp/wails
Length of output: 781
🏁 Script executed:
# Look for GTK path patterns
rg "/usr/lib.*gtk|gtk.*lib|relr\.dyn" --type go -B 3 -A 3Repository: wailsapp/wails
Length of output: 5769
Respect user-provided NO_STRIP and detect .relr.dyn across all supported GTK versions.
The auto-detection has two limitations: (1) it unconditionally forces NO_STRIP=1 even if the user explicitly configured their build environment to strip successfully, and (2) hasRelrDynSections() checks only GTK3 library paths, leaving GTK2 and GTK4 builds vulnerable to the same linuxdeploy failure.
- // Check if system libraries use .relr.dyn sections (modern toolchains)
- // If so, disable stripping as linuxdeploy's bundled strip can't handle them
- if hasRelrDynSections() {
- term.Infof("Detected modern toolchain (.relr.dyn sections), disabling stripping for compatibility. See: https://v3.wails.io/guides/build/linux#appimage-strip-compatibility")
- s.SETENV("NO_STRIP", "1")
- }
+ // Check if system libraries use .relr.dyn sections (modern toolchains).
+ // If so, disable stripping as linuxdeploy's bundled strip can't handle them.
+ // Respect a user-provided NO_STRIP to allow explicit overrides.
+ if os.Getenv("NO_STRIP") == "" && hasRelrDynSections(DeployGtkVersion, lddString) {
+ term.Infof("Detected modern toolchain (.relr.dyn sections), disabling stripping for compatibility. See: https://v3.wails.io/guides/build/linux#appimage-strip-compatibility")
+ s.SETENV("NO_STRIP", "1")
+ }Also test on a GTK4-linked setup to ensure detection still triggers when it should.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
v3/internal/commands/appimage.go lines 193-200: the current logic
unconditionally sets NO_STRIP when hasRelrDynSections() returns true and only
inspects GTK3 paths; update it to first respect any user-provided NO_STRIP (do
not overwrite if os.Getenv("NO_STRIP") is non-empty) and only set NO_STRIP="1"
when it's unset and relr.dyn sections are detected; expand hasRelrDynSections()
(or add a helper) to search library paths for GTK2, GTK3 and GTK4 installations
(typical pkg-config paths and common lib dirs) so detection covers all supported
GTK versions, and run/verify this on a GTK4-linked environment to confirm
detection triggers as expected.
|
…ilds (wailsapp#4772) Modern Linux distributions (Arch, Fedora 39+, Ubuntu 24.04+) compile libraries with .relr.dyn ELF sections. The bundled strip binary in linuxdeploy cannot process these sections, causing AppImage builds to fail. This commit: - Adds hasRelrDynSections() to proactively detect modern toolchains - Automatically disables stripping (NO_STRIP=1) when detected - Fixes error output to properly display as string - Adds documentation explaining the issue and workaround Fixes wailsapp#4642 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
…5475) (#5476) * fix(v3/appimage): detect GTK stack from binary before bundling (#5475) Since alpha.93, the default Linux build target is GTK4 / WebKitGTK 6.0 (GTK3 / WebKit2GTK 4.1 is now the legacy `-tags gtk3` path). The AppImage bundler still hard-coded the GTK3 set of runtime files, so on a runner with only the GTK4 toolchain installed `wails3 generate appimage` aborted with: ERROR Unable to locate all required files: libwebkit2gtkinjectedbundle.so Move the ldd-based version detection in front of `findGTKFiles`, run it on the source binary (not the post-copy path, which `filepath.Join` mis- handles when `-binary` includes a path), and pick the injected-bundle filename to match: `libwebkitgtkinjectedbundle.so` for GTK4, the older `libwebkit2gtkinjectedbundle.so` for GTK3. `WebKitWebProcess` and `WebKitNetworkProcess` are unchanged - both stacks ship them. Also extend `hasRelrDynSections` to probe `libgtk-4.so.1` (plus aarch64 paths) so the strip workaround introduced in #4772 still triggers on modern toolchains regardless of which stack the user built against. Verified on Ubuntu 26.04 lin-node1: - Default (GTK4): `go build` then `wails3 generate appimage` bundles `libwebkitgtkinjectedbundle.so` under `webkitgtk-6.0/injected-bundle/` in the AppDir; the resulting AppImage launches under xvfb and logs `GTK=4.22.2 WebKitGTK=2.52.3`. - Legacy (`-tags gtk3`): same flow bundles `libwebkit2gtkinjectedbundle.so` under `webkit2gtk-4.1/injected-bundle/` and the AppImage launches with `Webkit2Gtk=v2.52.3` and `-tags=gtk3`. Two pre-existing unrelated issues observed during verification, flagged here so reviewers don't conflate them with this fix: - `wails3 generate appimage` with a relative `-builddir` races on the AppRun download goroutine because `s.CD(BuildDir)` is called between the goroutine launch and its file write. Workaround: pass absolute paths. - The post-bundle `s.MOVE` expects the AppImage to be named after `normaliseName(filepath.Base(Binary))`, but `linuxdeploy` names it after the desktop `Name=` field, so the move fails when those don't match. Workaround: align `Name=` with the binary basename. Will be addressed in a follow-up. Refs #5475. * fix(v3/appimage): absolute input paths and forced output filename Two pre-existing bugs surfaced while verifying #5475: 1. `wails3 generate appimage` with a relative `-builddir` would fail inside the AppRun download goroutine. The flow does `s.CD(BuildDir)` between launching the goroutines and their file writes, so any path computed before the CD (e.g. `appDir = filepath.Join(BuildDir, "...AppDir")`) was being resolved relative to the wrong CWD by the downstream `s.DOWNLOAD(target)` and the `ldd <binary>` shell-out. Fix: resolve `-binary`, `-icon`, `-desktopfile`, `-builddir` and `-outputdir` to absolute paths up-front, alongside the existing `OutputDir` resolution. Now CWD-sensitive operations downstream all see stable absolute paths. 2. The post-bundle `s.MOVE(targetFile, OutputDir)` would fail when the desktop file's `Name=` field didn't match `normaliseName(basename( Binary))`. linuxdeploy's appimage plugin names the output AppImage from the desktop `Name=`, but the Go code expected `<binary>-<arch>.AppImage` and looked it up by that path. Fix: set the `OUTPUT` env var (honored by linuxdeploy-plugin-appimage) to the binary-derived name we already expect, so the produced filename matches what we then move. Verified on Ubuntu 26.04 lin-node1: - Relative `-builddir` + a desktop `Name=Differently Named App` (which used to fail twice over): exit 0, AppImage produced at `out/appimage-smoke-gtk4-x86_64.AppImage`, launches under xvfb, logs `GTK=4.22.2 WebKitGTK=2.52.3`. - The original absolute-paths flow (both GTK4 default and `-tags gtk3`) still produces the matching AppImage in `-outputdir` and exits 0 - no regression of the #5475 fix. Refs #5475. * fix(v3/appimage): quote shell args and surface ldd output on detect failure Addresses CodeRabbit and Copilot review on #5476: - Quote the binary path passed to `ldd` (`%s` -> `%q`). `s.EXEC` splits the command string with shlex; an unquoted path with spaces gets fragmented and the ldd call fails. - Same treatment for the `readelf` probe in `hasRelrDynSections` and for the `linuxdeploy ... --appdir <dir>` command. The linuxdeploy call site was the latent breakage — invoking it with a `-builddir` containing a space produced `fork/exec /tmp/space: no such file or directory` even after the abs-path resolution from the previous commit. - Include a truncated ldd-output snippet in the "unable to determine GTK version" error so users hitting this branch can see which libraries the binary actually links against. Verified on lin-node1 (Ubuntu 26.04): - Paths with spaces (`-binary "/tmp/space test/my app"` ...): now exit 0 and produce `out/my-app-x86_64.AppImage`. Previously failed at the linuxdeploy step. - The original absolute-path GTK4 and `-tags gtk3` flows still pass and the GTK4 AppImage still launches under xvfb with `GTK=4.22.2 WebKitGTK=2.52.3` - no regression. Refs #5475.



Summary
.relr.dynELF sectionsNO_STRIP=1) when detected to prevent linuxdeploy failuresProblem
Modern Linux distributions (Arch Linux, Fedora 39+, Ubuntu 24.04+) compile libraries with
.relr.dynELF sections for more efficient relocations. Thelinuxdeploytool bundles an olderstripbinary that cannot process these sections, causing AppImage builds to fail with:This is a known issue: linuxdeploy#272
Solution
Rather than always disabling stripping (which increases AppImage size by ~20-40%), this PR:
.relr.dynsections usingreadelfTest plan
Fixes #4642
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.