From 0a556c17205cdbd7c11f0f6807f391bce863fe55 Mon Sep 17 00:00:00 2001 From: Tianyu Song Date: Sun, 14 Jun 2026 16:50:50 +0200 Subject: [PATCH] Fix Linux .deb runtime crashes and local build on Ubuntu 22.04/24.04/26.04 The prebuilt Linux .deb crashed on launch on clean Ubuntu 24.04/26.04 (libOpenGL.so.0 and transitive libs not found, then ImGui "Failed to initialize OpenGL loader!"), and building from source failed. One Linux build now serves all three LTS releases. Runtime (.deb packaging in release.yml): - Depend on libopengl0: the binary links GLVND's libOpenGL.so.0, but the package only depended on libgl1 -> instant load-time crash on a clean box. - Bundle the dependency *closure* of the RealSense+OpenCV libraries (this catches libprotobuf.so.23 and libtbb.so.2 that the old name allow-list missed) and patchelf $ORIGIN onto every bundled .so. RUNPATH does not propagate to a library's own deps, and those sonames are gone on 24/26. - Ship librealsense udev rules (non-root camera access) and a 256px icon; reload udev + icon/desktop caches in postinst. Depends widened to the host GL/GTK/X11/runtime/USB stack (names resolve on 24/26 via t64 Provides). ImGui GL loader (imgui_impl_opengl3_loader.h): - dlopen libGL.so.1 (the runtime soname) before the dev-only libGL.so symlink, which is absent on end-user machines. This crashed every clean Linux box, not just 26.04. Linux/GLX branch only; macOS/Windows untouched. Build (build.yml / release.yml / README.md / CMakeLists.txt): - Add libx11-dev/libxext-dev: find_package(X11) aborted configure on 26.04. - librealsense apt repo falls back to noble when the running codename is not served (Intel returns 403 for resolute/oracular/plucky). - -march=native is now opt-in via IRTRACK_NATIVE_ARCH (default OFF); the distributed binary targets a portable x86-64 baseline. - Document the Ubuntu 22.04/24.04/26.04 build recipe. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/build.yml | 19 ++- .github/workflows/release.yml | 125 ++++++++++++++++-- CMakeLists.txt | 16 ++- README.md | 55 +++++++- .../src/backends/imgui_impl_opengl3_loader.h | 13 +- 5 files changed, 210 insertions(+), 18 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 64a0af0..e3ceaf8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -50,8 +50,9 @@ jobs: build-essential cmake ninja-build pkg-config \ libopencv-dev \ libgl1-mesa-dev libglu1-mesa-dev \ + libx11-dev libxext-dev \ libxinerama-dev libxcursor-dev libxi-dev libxrandr-dev \ - libwayland-dev libxkbcommon-dev \ + libwayland-dev libxkbcommon-dev wayland-protocols \ libusb-1.0-0-dev libudev-dev \ libgtk-3-dev @@ -67,7 +68,21 @@ jobs: curl -fsSL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xFB0B24895113F120&options=mr" \ | sudo gpg --dearmor -o /etc/apt/keyrings/librealsense.gpg sudo chmod 0644 /etc/apt/keyrings/librealsense.gpg - echo "deb [signed-by=/etc/apt/keyrings/librealsense.gpg] https://librealsense.intel.com/Debian/apt-repo $(lsb_release -cs) main" \ + # Intel only publishes the apt repo for select Ubuntu LTS codenames + # (bionic/focal/jammy/noble). On a newer/interim release the repo 404s + # and `apt-get update` fails, so fall back to the newest supported LTS. + RS_CODENAME="$(lsb_release -cs)" + # Fall back to the newest supported LTS ONLY when the repo definitively + # lacks this codename (Intel returns 403/404 for unsupported releases). + # Retry first so a transient network error doesn't silently retarget the + # distro — a real outage then fails loudly at apt-get update instead. + RS_HTTP="$(curl -sSL -o /dev/null -w '%{http_code}' --retry 3 --retry-connrefused \ + "https://librealsense.intel.com/Debian/apt-repo/dists/${RS_CODENAME}/Release" || echo 000)" + if [ "$RS_HTTP" = "403" ] || [ "$RS_HTTP" = "404" ]; then + echo "librealsense apt repo has no '${RS_CODENAME}' (HTTP $RS_HTTP); falling back to noble" + RS_CODENAME=noble + fi + echo "deb [signed-by=/etc/apt/keyrings/librealsense.gpg] https://librealsense.intel.com/Debian/apt-repo ${RS_CODENAME} main" \ | sudo tee /etc/apt/sources.list.d/librealsense.list sudo apt-get update sudo apt-get install -y --no-install-recommends librealsense2-dev librealsense2-utils diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7ce89a9..fcc7ab6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -41,11 +41,12 @@ jobs: sudo apt-get update sudo apt-get install -y --no-install-recommends \ ca-certificates curl gnupg lsb-release \ - build-essential cmake ninja-build pkg-config patchelf \ + build-essential cmake ninja-build pkg-config patchelf icoutils \ libopencv-dev \ libgl1-mesa-dev libglu1-mesa-dev \ + libx11-dev libxext-dev \ libxinerama-dev libxcursor-dev libxi-dev libxrandr-dev \ - libwayland-dev libxkbcommon-dev \ + libwayland-dev libxkbcommon-dev wayland-protocols \ libusb-1.0-0-dev libudev-dev \ libgtk-3-dev @@ -54,10 +55,26 @@ jobs: curl -fsSL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xFB0B24895113F120&options=mr" \ | sudo gpg --dearmor -o /etc/apt/keyrings/librealsense.gpg sudo chmod 0644 /etc/apt/keyrings/librealsense.gpg - echo "deb [signed-by=/etc/apt/keyrings/librealsense.gpg] https://librealsense.intel.com/Debian/apt-repo $(lsb_release -cs) main" \ + # Intel only publishes the apt repo for select Ubuntu LTS codenames + # (bionic/focal/jammy/noble). On a newer/interim release the repo 404s + # and `apt-get update` fails, so fall back to the newest supported LTS + # (its binaries are forward-compatible). + RS_CODENAME="$(lsb_release -cs)" + # Fall back to the newest supported LTS ONLY when the repo definitively + # lacks this codename (Intel returns 403/404 for unsupported releases). + # Retry first so a transient network error doesn't silently retarget the + # distro — a real outage then fails loudly at apt-get update instead. + RS_HTTP="$(curl -sSL -o /dev/null -w '%{http_code}' --retry 3 --retry-connrefused \ + "https://librealsense.intel.com/Debian/apt-repo/dists/${RS_CODENAME}/Release" || echo 000)" + if [ "$RS_HTTP" = "403" ] || [ "$RS_HTTP" = "404" ]; then + echo "librealsense apt repo has no '${RS_CODENAME}' (HTTP $RS_HTTP); falling back to noble" + RS_CODENAME=noble + fi + echo "deb [signed-by=/etc/apt/keyrings/librealsense.gpg] https://librealsense.intel.com/Debian/apt-repo ${RS_CODENAME} main" \ | sudo tee /etc/apt/sources.list.d/librealsense.list sudo apt-get update - sudo apt-get install -y --no-install-recommends librealsense2-dev librealsense2-utils + sudo apt-get install -y --no-install-recommends \ + librealsense2-dev librealsense2-utils librealsense2-udev-rules - name: Install macOS dependencies if: runner.os == 'macOS' @@ -160,11 +177,53 @@ jobs: # Binary + bundled libs under /usr/lib// with $ORIGIN rpath. cp build/ir-tracking-app "$STAGE/usr/lib/$PKG/" - ldd build/ir-tracking-app \ - | awk '/(librealsense|libopencv|libtbb|libusb-1\.0)/ { print $3 }' \ - | grep -v '^$' | sort -u \ - | while read -r lib; do cp -L "$lib" "$STAGE/usr/lib/$PKG/"; done + + # Bundle the application's private compute libraries — RealSense, + # OpenCV, and whatever THOSE pull in (TBB, protobuf, image codecs, ...) + # — so the package never relies on the host shipping the exact sonames + # we built against. Those drift across Ubuntu 22.04/24.04/26.04 + # (libtbb.so.2 -> .so.12, libprotobuf.so.23 absent on 26.04, etc.). + # + # We compute this set as the *dependency closure of the RealSense + + # OpenCV libraries only*, not of the whole binary. That deliberately + # excludes the GL / X11 / Wayland / GTK / C++-runtime stack (the binary + # links those directly, but RealSense/OpenCV do not depend on them), so + # we can never accidentally bundle e.g. libgtk-3 and break the user's + # desktop integration. Those host libraries come from Depends instead. + # (The previous hand-written allow-list missed libprotobuf.so.23 and + # crashed on any host without it.) + mapfile -t ROOTS < <(ldd build/ir-tracking-app \ + | awk '/=> \// && /lib(realsense2|opencv)/ { print $3 }' | sort -u) + declare -A SEEN + queue=("${ROOTS[@]}") + while ((${#queue[@]})); do + cur="${queue[0]}"; queue=("${queue[@]:1}") + if [[ -n "${SEEN[$cur]:-}" ]]; then continue; fi + SEEN["$cur"]=1 + while read -r dep; do + if [[ -n "$dep" && -z "${SEEN[$dep]:-}" ]]; then queue+=("$dep"); fi + done < <(ldd "$cur" 2>/dev/null | awk '/=> \//{print $3}') + done + # Copy the closure, except the host C/C++/system runtime (ABI-stable + # and present on every target — bundling an older copy would be wrong). + RUNTIME='^(ld-linux|libc|libm|libdl|libpthread|librt|libresolv|libstdc\+\+|libgcc_s|libatomic|libz|liblzma|libzstd|libbz2|libudev|libusb-1\.0|libcrypt)\.' + for lib in "${!SEEN[@]}"; do + base="$(basename "$lib")" + if printf '%s' "$base" | grep -qE "$RUNTIME"; then continue; fi + cp -L "$lib" "$STAGE/usr/lib/$PKG/" + done + + # $ORIGIN rpath on the binary AND every bundled lib. RUNPATH does not + # propagate to a library's *own* dependencies, so without rpath on the + # bundled .so files their private deps (libopencv_core -> libtbb.so.2, + # libopencv_dnn -> libprotobuf.so.23) would be searched only on the + # host and fail wherever those sonames are gone. patchelf --set-rpath '$ORIGIN' "$STAGE/usr/lib/$PKG/ir-tracking-app" + shopt -s nullglob # don't iterate a literal '*.so*' if the closure were ever empty + for so in "$STAGE/usr/lib/$PKG/"*.so*; do + patchelf --set-rpath '$ORIGIN' "$so" + done + shopt -u nullglob # Launcher shim on PATH. cat > "$STAGE/usr/bin/$PKG" < "$STAGE/DEBIAN/postinst" <<'SH' + #!/bin/sh + set -e + if command -v udevadm >/dev/null 2>&1; then + udevadm control --reload-rules || true + udevadm trigger || true + fi + # Refresh the icon cache / desktop database so the launcher entry and + # icon appear without a re-login. + command -v gtk-update-icon-cache >/dev/null 2>&1 && gtk-update-icon-cache -q -f /usr/share/icons/hicolor 2>/dev/null || true + command -v update-desktop-database >/dev/null 2>&1 && update-desktop-database -q /usr/share/applications 2>/dev/null || true + exit 0 + SH + cat > "$STAGE/DEBIAN/postrm" <<'SH' + #!/bin/sh + set -e + if command -v udevadm >/dev/null 2>&1; then + udevadm control --reload-rules || true + fi + exit 0 + SH + chmod 0755 "$STAGE/DEBIAN/postinst" "$STAGE/DEBIAN/postrm" + + # dpkg control metadata. Depends covers the host-provided runtime libs + # we deliberately do NOT bundle (the GL/GTK/X11/C++-runtime/USB stack). + # Names are the Ubuntu 22.04 ones; on 24.04/26.04 the t64 packages + # (e.g. libgtk-3-0t64) satisfy them via Provides, so one .deb installs + # on all three. libopengl0 provides libOpenGL.so.0, which the binary + # links directly — its omission was the original launch crash. INSTALLED_KB=$(du -sk "$STAGE" --exclude=DEBIAN | cut -f1) cat > "$STAGE/DEBIAN/control" < Homepage: https://github.com/stytim/RealSense-ToolTracker - Depends: libgtk-3-0, libgl1, libusb-1.0-0 + Depends: libc6, libstdc++6, libgcc-s1, libgtk-3-0, libgl1, libopengl0, libx11-6, libusb-1.0-0, libudev1, zlib1g Installed-Size: $INSTALLED_KB Description: Intel RealSense IR retro-reflective marker tool tracker Tracks passive IR sphere markers from an Intel RealSense camera and diff --git a/CMakeLists.txt b/CMakeLists.txt index e924b2e..e2f4ad4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,8 +29,20 @@ else() # Static library: -fPIC set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") - set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 -march=native -fstack-protector") - set(CMAKE_CXX_FLAGS_RELEASE "-O3 -march=native") + # -march=native tunes the binary to the CPU that builds it. That is fine for + # a local build, but wrong for a redistributable artifact: the CI release + # binary would pick up instructions (e.g. AVX-512) that fault with SIGILL on + # a user's older CPU. Default OFF so one Linux build runs on any x86-64 box; + # opt in with -DIRTRACK_NATIVE_ARCH=ON for a tuned local performance build. + option(IRTRACK_NATIVE_ARCH "Tune the build for the local CPU (-march=native); not portable" OFF) + if(IRTRACK_NATIVE_ARCH) + set(IRTRACK_ARCH_FLAG "-march=native") + else() + set(IRTRACK_ARCH_FLAG "") + endif() + + set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 ${IRTRACK_ARCH_FLAG} -fstack-protector") + set(CMAKE_CXX_FLAGS_RELEASE "-O3 ${IRTRACK_ARCH_FLAG}") endif() IF(MSVC) diff --git a/README.md b/README.md index 103ed9b..76ac1e2 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,60 @@ This project leverages the versatility of Intel RealSense cameras, enabling high ## Building Ensure all the prerequisites are installed before proceeding with the build process. -### For Linux and MacOS: +### Linux (Ubuntu 22.04 / 24.04 / 26.04) + +Install the build dependencies. Intel's RealSense apt repo only publishes for +select LTS codenames (`bionic`/`focal`/`jammy`/`noble`); on a newer release +(e.g. 26.04 "resolute") it 404s, so the snippet below falls back to the newest +supported LTS — those packages are forward-compatible. + +```bash +sudo apt-get update +sudo apt-get install -y \ + ca-certificates curl gnupg lsb-release \ + build-essential cmake ninja-build pkg-config patchelf \ + libopencv-dev \ + libgl1-mesa-dev libglu1-mesa-dev \ + libx11-dev libxext-dev \ + libxinerama-dev libxcursor-dev libxi-dev libxrandr-dev \ + libwayland-dev libxkbcommon-dev wayland-protocols \ + libusb-1.0-0-dev libudev-dev \ + libgtk-3-dev + +# Intel RealSense SDK from Intel's apt repo (with LTS-codename fallback) +sudo install -m 0755 -d /etc/apt/keyrings +curl -fsSL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xFB0B24895113F120&options=mr" \ + | sudo gpg --dearmor -o /etc/apt/keyrings/librealsense.gpg +sudo chmod 0644 /etc/apt/keyrings/librealsense.gpg +RS_CODENAME="$(lsb_release -cs)" +curl -fsSL -o /dev/null "https://librealsense.intel.com/Debian/apt-repo/dists/${RS_CODENAME}/Release" || RS_CODENAME=noble +echo "deb [signed-by=/etc/apt/keyrings/librealsense.gpg] https://librealsense.intel.com/Debian/apt-repo ${RS_CODENAME} main" \ + | sudo tee /etc/apt/sources.list.d/librealsense.list +sudo apt-get update +# librealsense2-udev-rules lets a non-root user access the camera (otherwise the +# USB device nodes are root-only and the app reports "No RealSense devices found"). +sudo apt-get install -y librealsense2-dev librealsense2-utils librealsense2-udev-rules +``` + +> The prebuilt `.deb` already installs these udev rules for you. + +Then build: + +```bash +cmake -S . -B build -DCMAKE_BUILD_TYPE=Release +cmake --build build --parallel +``` + +> The build targets a portable x86-64 baseline so the binary runs on any +> machine. For a CPU-tuned local build, add `-DIRTRACK_NATIVE_ARCH=ON` (uses +> `-march=native`; the result is not redistributable). + +> Prefer the prebuilt `.deb` from the +> [Releases page](https://github.com/stytim/RealSense-ToolTracker/releases) if +> you just want to run the app — it bundles RealSense/OpenCV and installs on +> Ubuntu 22.04/24.04/26.04. + +### For MacOS: ```bash mkdir build && cd build diff --git a/extern/imgui/src/backends/imgui_impl_opengl3_loader.h b/extern/imgui/src/backends/imgui_impl_opengl3_loader.h index 9fb8ffb..8dbc86d 100644 --- a/extern/imgui/src/backends/imgui_impl_opengl3_loader.h +++ b/extern/imgui/src/backends/imgui_impl_opengl3_loader.h @@ -666,8 +666,17 @@ static GL3WglProc (*glx_get_proc_address)(const GLubyte *); static int open_libgl(void) { - // While most systems use libGL.so.1, NetBSD seems to use that libGL.so.3. See https://github.com/ocornut/imgui/issues/6983 - libgl = dlopen("libGL.so", RTLD_LAZY | RTLD_LOCAL); + // Open the versioned runtime soname FIRST. The unversioned "libGL.so" only + // ships with a -dev package (libgl-dev/mesa), so an end-user machine that + // has just the runtime libgl1 would otherwise fail here with + // "Failed to initialize OpenGL loader!" and abort. Most Linux systems use + // libGL.so.1; NetBSD uses libGL.so.3 (see ocornut/imgui#6983); fall back to + // the unversioned name last for dev environments. + libgl = dlopen("libGL.so.1", RTLD_LAZY | RTLD_LOCAL); + if (!libgl) + libgl = dlopen("libGL.so.3", RTLD_LAZY | RTLD_LOCAL); + if (!libgl) + libgl = dlopen("libGL.so", RTLD_LAZY | RTLD_LOCAL); if (!libgl) return GL3W_ERROR_LIBRARY_OPEN; *(void **)(&glx_get_proc_address) = dlsym(libgl, "glXGetProcAddressARB");