Skip to content

Commit bded9f7

Browse files
Tianyu Songclaude
andcommitted
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) <noreply@anthropic.com>
1 parent a2adb76 commit bded9f7

5 files changed

Lines changed: 210 additions & 18 deletions

File tree

.github/workflows/build.yml

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,9 @@ jobs:
5050
build-essential cmake ninja-build pkg-config \
5151
libopencv-dev \
5252
libgl1-mesa-dev libglu1-mesa-dev \
53+
libx11-dev libxext-dev \
5354
libxinerama-dev libxcursor-dev libxi-dev libxrandr-dev \
54-
libwayland-dev libxkbcommon-dev \
55+
libwayland-dev libxkbcommon-dev wayland-protocols \
5556
libusb-1.0-0-dev libudev-dev \
5657
libgtk-3-dev
5758
@@ -67,7 +68,21 @@ jobs:
6768
curl -fsSL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xFB0B24895113F120&options=mr" \
6869
| sudo gpg --dearmor -o /etc/apt/keyrings/librealsense.gpg
6970
sudo chmod 0644 /etc/apt/keyrings/librealsense.gpg
70-
echo "deb [signed-by=/etc/apt/keyrings/librealsense.gpg] https://librealsense.intel.com/Debian/apt-repo $(lsb_release -cs) main" \
71+
# Intel only publishes the apt repo for select Ubuntu LTS codenames
72+
# (bionic/focal/jammy/noble). On a newer/interim release the repo 404s
73+
# and `apt-get update` fails, so fall back to the newest supported LTS.
74+
RS_CODENAME="$(lsb_release -cs)"
75+
# Fall back to the newest supported LTS ONLY when the repo definitively
76+
# lacks this codename (Intel returns 403/404 for unsupported releases).
77+
# Retry first so a transient network error doesn't silently retarget the
78+
# distro — a real outage then fails loudly at apt-get update instead.
79+
RS_HTTP="$(curl -sSL -o /dev/null -w '%{http_code}' --retry 3 --retry-connrefused \
80+
"https://librealsense.intel.com/Debian/apt-repo/dists/${RS_CODENAME}/Release" || echo 000)"
81+
if [ "$RS_HTTP" = "403" ] || [ "$RS_HTTP" = "404" ]; then
82+
echo "librealsense apt repo has no '${RS_CODENAME}' (HTTP $RS_HTTP); falling back to noble"
83+
RS_CODENAME=noble
84+
fi
85+
echo "deb [signed-by=/etc/apt/keyrings/librealsense.gpg] https://librealsense.intel.com/Debian/apt-repo ${RS_CODENAME} main" \
7186
| sudo tee /etc/apt/sources.list.d/librealsense.list
7287
sudo apt-get update
7388
sudo apt-get install -y --no-install-recommends librealsense2-dev librealsense2-utils

.github/workflows/release.yml

Lines changed: 114 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,12 @@ jobs:
4141
sudo apt-get update
4242
sudo apt-get install -y --no-install-recommends \
4343
ca-certificates curl gnupg lsb-release \
44-
build-essential cmake ninja-build pkg-config patchelf \
44+
build-essential cmake ninja-build pkg-config patchelf icoutils \
4545
libopencv-dev \
4646
libgl1-mesa-dev libglu1-mesa-dev \
47+
libx11-dev libxext-dev \
4748
libxinerama-dev libxcursor-dev libxi-dev libxrandr-dev \
48-
libwayland-dev libxkbcommon-dev \
49+
libwayland-dev libxkbcommon-dev wayland-protocols \
4950
libusb-1.0-0-dev libudev-dev \
5051
libgtk-3-dev
5152
@@ -54,10 +55,26 @@ jobs:
5455
curl -fsSL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xFB0B24895113F120&options=mr" \
5556
| sudo gpg --dearmor -o /etc/apt/keyrings/librealsense.gpg
5657
sudo chmod 0644 /etc/apt/keyrings/librealsense.gpg
57-
echo "deb [signed-by=/etc/apt/keyrings/librealsense.gpg] https://librealsense.intel.com/Debian/apt-repo $(lsb_release -cs) main" \
58+
# Intel only publishes the apt repo for select Ubuntu LTS codenames
59+
# (bionic/focal/jammy/noble). On a newer/interim release the repo 404s
60+
# and `apt-get update` fails, so fall back to the newest supported LTS
61+
# (its binaries are forward-compatible).
62+
RS_CODENAME="$(lsb_release -cs)"
63+
# Fall back to the newest supported LTS ONLY when the repo definitively
64+
# lacks this codename (Intel returns 403/404 for unsupported releases).
65+
# Retry first so a transient network error doesn't silently retarget the
66+
# distro — a real outage then fails loudly at apt-get update instead.
67+
RS_HTTP="$(curl -sSL -o /dev/null -w '%{http_code}' --retry 3 --retry-connrefused \
68+
"https://librealsense.intel.com/Debian/apt-repo/dists/${RS_CODENAME}/Release" || echo 000)"
69+
if [ "$RS_HTTP" = "403" ] || [ "$RS_HTTP" = "404" ]; then
70+
echo "librealsense apt repo has no '${RS_CODENAME}' (HTTP $RS_HTTP); falling back to noble"
71+
RS_CODENAME=noble
72+
fi
73+
echo "deb [signed-by=/etc/apt/keyrings/librealsense.gpg] https://librealsense.intel.com/Debian/apt-repo ${RS_CODENAME} main" \
5874
| sudo tee /etc/apt/sources.list.d/librealsense.list
5975
sudo apt-get update
60-
sudo apt-get install -y --no-install-recommends librealsense2-dev librealsense2-utils
76+
sudo apt-get install -y --no-install-recommends \
77+
librealsense2-dev librealsense2-utils librealsense2-udev-rules
6178
6279
- name: Install macOS dependencies
6380
if: runner.os == 'macOS'
@@ -160,11 +177,53 @@ jobs:
160177
161178
# Binary + bundled libs under /usr/lib/<pkg>/ with $ORIGIN rpath.
162179
cp build/ir-tracking-app "$STAGE/usr/lib/$PKG/"
163-
ldd build/ir-tracking-app \
164-
| awk '/(librealsense|libopencv|libtbb|libusb-1\.0)/ { print $3 }' \
165-
| grep -v '^$' | sort -u \
166-
| while read -r lib; do cp -L "$lib" "$STAGE/usr/lib/$PKG/"; done
180+
181+
# Bundle the application's private compute libraries — RealSense,
182+
# OpenCV, and whatever THOSE pull in (TBB, protobuf, image codecs, ...)
183+
# — so the package never relies on the host shipping the exact sonames
184+
# we built against. Those drift across Ubuntu 22.04/24.04/26.04
185+
# (libtbb.so.2 -> .so.12, libprotobuf.so.23 absent on 26.04, etc.).
186+
#
187+
# We compute this set as the *dependency closure of the RealSense +
188+
# OpenCV libraries only*, not of the whole binary. That deliberately
189+
# excludes the GL / X11 / Wayland / GTK / C++-runtime stack (the binary
190+
# links those directly, but RealSense/OpenCV do not depend on them), so
191+
# we can never accidentally bundle e.g. libgtk-3 and break the user's
192+
# desktop integration. Those host libraries come from Depends instead.
193+
# (The previous hand-written allow-list missed libprotobuf.so.23 and
194+
# crashed on any host without it.)
195+
mapfile -t ROOTS < <(ldd build/ir-tracking-app \
196+
| awk '/=> \// && /lib(realsense2|opencv)/ { print $3 }' | sort -u)
197+
declare -A SEEN
198+
queue=("${ROOTS[@]}")
199+
while ((${#queue[@]})); do
200+
cur="${queue[0]}"; queue=("${queue[@]:1}")
201+
if [[ -n "${SEEN[$cur]:-}" ]]; then continue; fi
202+
SEEN["$cur"]=1
203+
while read -r dep; do
204+
if [[ -n "$dep" && -z "${SEEN[$dep]:-}" ]]; then queue+=("$dep"); fi
205+
done < <(ldd "$cur" 2>/dev/null | awk '/=> \//{print $3}')
206+
done
207+
# Copy the closure, except the host C/C++/system runtime (ABI-stable
208+
# and present on every target — bundling an older copy would be wrong).
209+
RUNTIME='^(ld-linux|libc|libm|libdl|libpthread|librt|libresolv|libstdc\+\+|libgcc_s|libatomic|libz|liblzma|libzstd|libbz2|libudev|libusb-1\.0|libcrypt)\.'
210+
for lib in "${!SEEN[@]}"; do
211+
base="$(basename "$lib")"
212+
if printf '%s' "$base" | grep -qE "$RUNTIME"; then continue; fi
213+
cp -L "$lib" "$STAGE/usr/lib/$PKG/"
214+
done
215+
216+
# $ORIGIN rpath on the binary AND every bundled lib. RUNPATH does not
217+
# propagate to a library's *own* dependencies, so without rpath on the
218+
# bundled .so files their private deps (libopencv_core -> libtbb.so.2,
219+
# libopencv_dnn -> libprotobuf.so.23) would be searched only on the
220+
# host and fail wherever those sonames are gone.
167221
patchelf --set-rpath '$ORIGIN' "$STAGE/usr/lib/$PKG/ir-tracking-app"
222+
shopt -s nullglob # don't iterate a literal '*.so*' if the closure were ever empty
223+
for so in "$STAGE/usr/lib/$PKG/"*.so*; do
224+
patchelf --set-rpath '$ORIGIN' "$so"
225+
done
226+
shopt -u nullglob
168227
169228
# Launcher shim on PATH.
170229
cat > "$STAGE/usr/bin/$PKG" <<SH
@@ -185,8 +244,52 @@ jobs:
185244
Terminal=false
186245
EOF
187246
188-
# dpkg control metadata. Depends covers the runtime system libs we
189-
# rely on (GTK for nfd, OpenGL, libusb for RealSense USB).
247+
# App icon for desktop launchers (the .desktop above references it by
248+
# name). Extract the 256px frame from the multi-resolution .ico.
249+
icotool -x -w 256 -h 256 \
250+
-o "$STAGE/usr/share/icons/hicolor/256x256/apps/$PKG.png" \
251+
resources/app_icon.ico
252+
253+
# RealSense udev rules so a NON-root user can access the camera. Without
254+
# these the USB device nodes are root-only and the app just reports
255+
# "No RealSense devices found" even with a camera attached. Shipped by
256+
# Intel's librealsense2-udev-rules (installed on the runner) and
257+
# reloaded by the postinst below.
258+
RULES_SRC="$(ls /lib/udev/rules.d/60-librealsense2-udev-rules.rules \
259+
/usr/lib/udev/rules.d/60-librealsense2-udev-rules.rules 2>/dev/null | head -1)"
260+
install -Dm644 "$RULES_SRC" \
261+
"$STAGE/lib/udev/rules.d/60-librealsense2-udev-rules.rules"
262+
263+
# postinst/postrm: reload udev so the rules take effect without a reboot.
264+
cat > "$STAGE/DEBIAN/postinst" <<'SH'
265+
#!/bin/sh
266+
set -e
267+
if command -v udevadm >/dev/null 2>&1; then
268+
udevadm control --reload-rules || true
269+
udevadm trigger || true
270+
fi
271+
# Refresh the icon cache / desktop database so the launcher entry and
272+
# icon appear without a re-login.
273+
command -v gtk-update-icon-cache >/dev/null 2>&1 && gtk-update-icon-cache -q -f /usr/share/icons/hicolor 2>/dev/null || true
274+
command -v update-desktop-database >/dev/null 2>&1 && update-desktop-database -q /usr/share/applications 2>/dev/null || true
275+
exit 0
276+
SH
277+
cat > "$STAGE/DEBIAN/postrm" <<'SH'
278+
#!/bin/sh
279+
set -e
280+
if command -v udevadm >/dev/null 2>&1; then
281+
udevadm control --reload-rules || true
282+
fi
283+
exit 0
284+
SH
285+
chmod 0755 "$STAGE/DEBIAN/postinst" "$STAGE/DEBIAN/postrm"
286+
287+
# dpkg control metadata. Depends covers the host-provided runtime libs
288+
# we deliberately do NOT bundle (the GL/GTK/X11/C++-runtime/USB stack).
289+
# Names are the Ubuntu 22.04 ones; on 24.04/26.04 the t64 packages
290+
# (e.g. libgtk-3-0t64) satisfy them via Provides, so one .deb installs
291+
# on all three. libopengl0 provides libOpenGL.so.0, which the binary
292+
# links directly — its omission was the original launch crash.
190293
INSTALLED_KB=$(du -sk "$STAGE" --exclude=DEBIAN | cut -f1)
191294
cat > "$STAGE/DEBIAN/control" <<EOF
192295
Package: $PKG
@@ -196,7 +299,7 @@ jobs:
196299
Architecture: amd64
197300
Maintainer: Tianyu Song <tianyu@medivis.com>
198301
Homepage: https://github.com/stytim/RealSense-ToolTracker
199-
Depends: libgtk-3-0, libgl1, libusb-1.0-0
302+
Depends: libc6, libstdc++6, libgcc-s1, libgtk-3-0, libgl1, libopengl0, libx11-6, libusb-1.0-0, libudev1, zlib1g
200303
Installed-Size: $INSTALLED_KB
201304
Description: Intel RealSense IR retro-reflective marker tool tracker
202305
Tracks passive IR sphere markers from an Intel RealSense camera and

CMakeLists.txt

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,20 @@ else()
2929
# Static library: -fPIC
3030
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")
3131

32-
set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 -march=native -fstack-protector")
33-
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -march=native")
32+
# -march=native tunes the binary to the CPU that builds it. That is fine for
33+
# a local build, but wrong for a redistributable artifact: the CI release
34+
# binary would pick up instructions (e.g. AVX-512) that fault with SIGILL on
35+
# a user's older CPU. Default OFF so one Linux build runs on any x86-64 box;
36+
# opt in with -DIRTRACK_NATIVE_ARCH=ON for a tuned local performance build.
37+
option(IRTRACK_NATIVE_ARCH "Tune the build for the local CPU (-march=native); not portable" OFF)
38+
if(IRTRACK_NATIVE_ARCH)
39+
set(IRTRACK_ARCH_FLAG "-march=native")
40+
else()
41+
set(IRTRACK_ARCH_FLAG "")
42+
endif()
43+
44+
set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 ${IRTRACK_ARCH_FLAG} -fstack-protector")
45+
set(CMAKE_CXX_FLAGS_RELEASE "-O3 ${IRTRACK_ARCH_FLAG}")
3446
endif()
3547

3648
IF(MSVC)

README.md

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,60 @@ This project leverages the versatility of Intel RealSense cameras, enabling high
3131
## Building
3232
Ensure all the prerequisites are installed before proceeding with the build process.
3333

34-
### For Linux and MacOS:
34+
### Linux (Ubuntu 22.04 / 24.04 / 26.04)
35+
36+
Install the build dependencies. Intel's RealSense apt repo only publishes for
37+
select LTS codenames (`bionic`/`focal`/`jammy`/`noble`); on a newer release
38+
(e.g. 26.04 "resolute") it 404s, so the snippet below falls back to the newest
39+
supported LTS — those packages are forward-compatible.
40+
41+
```bash
42+
sudo apt-get update
43+
sudo apt-get install -y \
44+
ca-certificates curl gnupg lsb-release \
45+
build-essential cmake ninja-build pkg-config patchelf \
46+
libopencv-dev \
47+
libgl1-mesa-dev libglu1-mesa-dev \
48+
libx11-dev libxext-dev \
49+
libxinerama-dev libxcursor-dev libxi-dev libxrandr-dev \
50+
libwayland-dev libxkbcommon-dev wayland-protocols \
51+
libusb-1.0-0-dev libudev-dev \
52+
libgtk-3-dev
53+
54+
# Intel RealSense SDK from Intel's apt repo (with LTS-codename fallback)
55+
sudo install -m 0755 -d /etc/apt/keyrings
56+
curl -fsSL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xFB0B24895113F120&options=mr" \
57+
| sudo gpg --dearmor -o /etc/apt/keyrings/librealsense.gpg
58+
sudo chmod 0644 /etc/apt/keyrings/librealsense.gpg
59+
RS_CODENAME="$(lsb_release -cs)"
60+
curl -fsSL -o /dev/null "https://librealsense.intel.com/Debian/apt-repo/dists/${RS_CODENAME}/Release" || RS_CODENAME=noble
61+
echo "deb [signed-by=/etc/apt/keyrings/librealsense.gpg] https://librealsense.intel.com/Debian/apt-repo ${RS_CODENAME} main" \
62+
| sudo tee /etc/apt/sources.list.d/librealsense.list
63+
sudo apt-get update
64+
# librealsense2-udev-rules lets a non-root user access the camera (otherwise the
65+
# USB device nodes are root-only and the app reports "No RealSense devices found").
66+
sudo apt-get install -y librealsense2-dev librealsense2-utils librealsense2-udev-rules
67+
```
68+
69+
> The prebuilt `.deb` already installs these udev rules for you.
70+
71+
Then build:
72+
73+
```bash
74+
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
75+
cmake --build build --parallel
76+
```
77+
78+
> The build targets a portable x86-64 baseline so the binary runs on any
79+
> machine. For a CPU-tuned local build, add `-DIRTRACK_NATIVE_ARCH=ON` (uses
80+
> `-march=native`; the result is not redistributable).
81+
82+
> Prefer the prebuilt `.deb` from the
83+
> [Releases page](https://github.com/stytim/RealSense-ToolTracker/releases) if
84+
> you just want to run the app — it bundles RealSense/OpenCV and installs on
85+
> Ubuntu 22.04/24.04/26.04.
86+
87+
### For MacOS:
3588

3689
```bash
3790
mkdir build && cd build

extern/imgui/src/backends/imgui_impl_opengl3_loader.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -666,8 +666,17 @@ static GL3WglProc (*glx_get_proc_address)(const GLubyte *);
666666

667667
static int open_libgl(void)
668668
{
669-
// While most systems use libGL.so.1, NetBSD seems to use that libGL.so.3. See https://github.com/ocornut/imgui/issues/6983
670-
libgl = dlopen("libGL.so", RTLD_LAZY | RTLD_LOCAL);
669+
// Open the versioned runtime soname FIRST. The unversioned "libGL.so" only
670+
// ships with a -dev package (libgl-dev/mesa), so an end-user machine that
671+
// has just the runtime libgl1 would otherwise fail here with
672+
// "Failed to initialize OpenGL loader!" and abort. Most Linux systems use
673+
// libGL.so.1; NetBSD uses libGL.so.3 (see ocornut/imgui#6983); fall back to
674+
// the unversioned name last for dev environments.
675+
libgl = dlopen("libGL.so.1", RTLD_LAZY | RTLD_LOCAL);
676+
if (!libgl)
677+
libgl = dlopen("libGL.so.3", RTLD_LAZY | RTLD_LOCAL);
678+
if (!libgl)
679+
libgl = dlopen("libGL.so", RTLD_LAZY | RTLD_LOCAL);
671680
if (!libgl)
672681
return GL3W_ERROR_LIBRARY_OPEN;
673682
*(void **)(&glx_get_proc_address) = dlsym(libgl, "glXGetProcAddressARB");

0 commit comments

Comments
 (0)