drm: refuse to ship a libdrmtap built without the EGL backend

libdrmtap treats egl/glesv2 as OPTIONAL: without their headers and
pkg-config files meson silently builds a CPU-only stub. The stub still
exports every symbol the loader gates on, so nothing downstream notices,
and the split capture depends entirely on the unprivileged side
EGL-detiling the scanout it receives. The result is a build where DRM
capture quietly degrades to PipeWire on every tiled-scanout host, which is
most of them. Our CI asserts this on the .so it builds; a developer or
packager running build.py got no such check.

Assert on the artifact rather than passing -Degl=enabled: that option only
exists in libdrmtap past the pinned 0.4.15, and checking what was actually
produced also catches a stale or substituted object, which a build flag
cannot. Same two markers CI looks for, and for the same reason an ELF-level
check does not work: EGL is reached by lazy dlopen so there is no
DT_NEEDED.
This commit is contained in:
Mariano Abad
2026-07-29 17:43:57 -03:00
parent cff25dc4d8
commit 392e7a88dd

View File

@@ -449,7 +449,39 @@ def build_libdrmtap_so():
sos = glob.glob(os.path.join(build_dir, 'libdrmtap.so.0.*'))
# keep the real object (libdrmtap.so.0.4.x), not the .so/.so.0 symlinks or meson's .p dir, and
# require exactly one so a stale object from an earlier build is never silently picked.
return _single_real_so(sos, f'the libdrmtap meson build dir {build_dir}')
so = _single_real_so(sos, f'the libdrmtap meson build dir {build_dir}')
_assert_so_has_egl(so)
return so
def _assert_so_has_egl(so_path):
# libdrmtap treats egl/glesv2 as OPTIONAL dependencies: without their headers and pkg-config
# files, meson silently builds a CPU-only stub. That stub still exports every symbol the loader
# checks for, so nothing downstream notices -- and the split architecture depends entirely on the
# unprivileged side EGL-detiling the scanout it receives. The result is a build where DRM capture
# quietly degrades to PipeWire on every tiled-scanout host, which is most of them.
#
# Assert on the ARTIFACT rather than passing an option that demands it: `-Degl=enabled` exists
# only in libdrmtap past 0.4.15, and checking what was actually produced also catches a stale or
# hand-substituted object, which a build flag cannot.
#
# EGL is reached by lazy dlopen, on purpose, so that the privileged service never links the GPU
# stack. That means there is no DT_NEEDED to look for and an ELF-level check reports "no EGL" on a
# perfectly good library; the dlopen name and an extension symbol are what a CPU-only stub really
# lacks. Same two markers the drm-capture workflow asserts in CI.
try:
with open(so_path, 'rb') as f:
blob = f.read()
except OSError as err:
raise Exception(f'cannot read the built libdrmtap at {so_path}: {err}')
missing = [m for m in (b'libEGL.so.1', b'eglCreateImageKHR') if m not in blob]
if missing:
raise Exception(
f'{so_path} looks like a CPU-only libdrmtap stub (missing '
f'{", ".join(m.decode() for m in missing)}): the EGL detile path the split capture '
'depends on is not in it, and DRM capture would silently fall back to PipeWire. '
'Install the EGL development packages and rebuild (Debian/Ubuntu: libegl-dev '
'libgles2-mesa-dev; Arch: mesa libglvnd).')
DRM_PACKAGE_NAME = 'rustdesk-unattended-wayland'