diff --git a/README.md b/README.md index 463e70a..1f0291d 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/build.zig b/build.zig index d18cdf6..812f629 100644 --- a/build.zig +++ b/build.zig @@ -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); diff --git a/fix_needed.sh b/fix_needed.sh new file mode 100755 index 0000000..3cb9c94 --- /dev/null +++ b/fix_needed.sh @@ -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