fix release mode link path in final elf output

This commit is contained in:
Emil Lerch 2025-10-09 08:53:19 -07:00
parent 15522c41a8
commit d6cab46c99
Signed by: lobo
GPG key ID: A7B62D657EF764F8
3 changed files with 86 additions and 2 deletions

View file

@ -20,6 +20,7 @@ The application uses ALSA's default device, which is configured in `alsa.conf`.
### Prerequisites
- Zig 0.15.1 (configured via mise)
- Nix development environment configured for ALSA, and audio libraries
- patchelf (for fixing RPATH in release builds): `nix-env -iA nixpkgs.patchelf`
### Vosk Model Download
The application uses the Vosk small English model for speech recognition:
@ -33,6 +34,20 @@ The application uses the Vosk small English model for speech recognition:
2. Build application: `zig build`
3. Run: `zig build run`
### Release Builds and Portability
When building in release mode (`-Doptimize=ReleaseSafe`), Zig embeds the full path to libvosk.so in the ELF NEEDED entries, making the binary non-portable. The build system automatically fixes this by running `fix_needed.sh` which uses `patchelf` to replace the full path with just the library name.
**Automatic fix**: Just run `zig build -Doptimize=ReleaseSafe` - the NEEDED entries are fixed automatically.
**Manual fix**: If needed, you can run `./fix_needed.sh [binary_path] [library_name]` manually.
The script uses `patchelf` (via nix-shell if not installed) to replace entries like:
- Before: `NEEDED: [/home/user/.cache/zig/.../libvosk.so]`
- After: `NEEDED: [libvosk.so]`
This makes the binary portable while using the existing RPATH (`$ORIGIN/../lib`) to find the library at runtime.
## Usage
The application will:
- Initialize audio capture from default microphone

View file

@ -32,7 +32,7 @@ pub fn build(b: *std.Build) void {
// Create the STT library
const stt_lib = b.addLibrary(.{
.name = "stt",
.linkage = .static,
// .linkage = .static,
.root_module = b.createModule(.{
.root_source_file = b.path("src/stt.zig"),
.target = target,
@ -73,7 +73,21 @@ pub fn build(b: *std.Build) void {
exe.addLibraryPath(vosk_dep.path(""));
exe.linkSystemLibrary("vosk");
b.installArtifact(exe);
const install_exe = b.addInstallArtifact(exe, .{});
// Fix NEEDED entries in release builds to make binary portable
if (optimize != .Debug) {
const script_path = b.pathFromRoot("fix_needed.sh");
const fix_needed = b.addSystemCommand(&.{script_path});
fix_needed.step.dependOn(&install_exe.step);
_ = fix_needed.captureStdOut();
b.getInstallStep().dependOn(&fix_needed.step);
const fix_step = b.step("fix-needed", "Fix NEEDED entries to make binary portable");
fix_step.dependOn(&fix_needed.step);
} else {
b.getInstallStep().dependOn(&install_exe.step);
}
const run_step = b.step("run", "Run the app");
const run_cmd = b.addRunArtifact(exe);

55
fix_needed.sh Executable file
View file

@ -0,0 +1,55 @@
#!/bin/bash
# Fix NEEDED entries in release builds
# This script replaces full paths with just library names in NEEDED entries
if [[ "$1" == "--help" || "$1" == "-h" ]]; then
echo "Usage: $0 [binary_path] [library_name]"
echo " binary_path: Path to binary (default: script_dir/zig-out/bin/stt)"
echo " library_name: Library to fix (default: libvosk.so)"
echo ""
echo "Example: $0 my_binary libfoo.so"
exit 0
fi
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BINARY="${1:-$SCRIPT_DIR/zig-out/bin/stt}"
LIBRARY="${2:-libvosk.so}"
if [ ! -f "$BINARY" ]; then
echo "Binary not found: $BINARY" >&2
exit 1
fi
echo "Fixing NEEDED entries for $BINARY..."
# Get the current NEEDED entries
if command -v readelf >/dev/null 2>&1; then
FULL_PATH=$(readelf --dynamic "$BINARY" | grep "NEEDED" | grep "$LIBRARY" | head -1 | sed 's/.*\[\(.*\)\]/\1/')
elif command -v nix-shell >/dev/null 2>&1; then
echo "Using nix-shell to run readelf..."
FULL_PATH=$(nix-shell -p binutils --run "readelf --dynamic '$BINARY'" | grep "NEEDED" | grep "$LIBRARY" | head -1 | sed 's/.*\[\(.*\)\]/\1/')
else
echo "Error: Neither readelf nor nix-shell found" >&2
echo "Install binutils or nix to check NEEDED entries" >&2
exit 1
fi
if [[ "$FULL_PATH" == *"/"* ]]; then
echo "Found full path: $FULL_PATH"
echo "Replacing with: $LIBRARY"
# Try patchelf directly, fall back to nix-shell
if command -v patchelf >/dev/null 2>&1; then
patchelf --replace-needed "$FULL_PATH" "$LIBRARY" "$BINARY"
elif command -v nix-shell >/dev/null 2>&1; then
echo "Using nix-shell to run patchelf..."
nix-shell -p patchelf --run "patchelf --replace-needed '$FULL_PATH' '$LIBRARY' '$BINARY'"
else
echo "Error: Neither patchelf nor nix-shell found" >&2
echo "Install patchelf or nix to fix NEEDED entries" >&2
exit 1
fi
echo "Fixed!"
else
echo "No full path found, binary is already portable"
fi