Files
moonlight-qt/scripts/build-appimage.sh
T
luanweslley77 526bde5586 Stop bundling libva in the AppImage so VAAPI works on modern hosts
The AppImage bundles libva from the build environment (Ubuntu 22.04,
VA-API 1.20). VA-API driver modules (e.g. Mesa's radeonsi_drv_video.so)
are dlopen()ed by libva and export an entrypoint named after the libva
minor version they were built against (e.g. __vaDriverInit_1_23). An
older libva cannot load newer drivers, so on any host more recent than
the build container va_openDriver() fails:

    Trying .../radeonsi_drv_video.so
    libva error: has no function __vaDriverInit_1_0
    va_openDriver() returns -1

and Moonlight silently falls back to software decoding (reproduced on
Fedora 44 / Mesa 26.1 / RX 6500 XT; overriding the bundled libva with
LD_PRELOAD of the host libva immediately yields HW decode). Refreshing
the bundled libva at each release only postpones the breakage: the host
drivers always eventually outrun it (see #1398).

Exclude libva and its backends from linuxdeploy so Moonlight and the
bundled FFmpeg resolve them through the loader's normal search path and
pick up the host libva, which the distro's package manager keeps in step
with the host drivers. This mirrors how Mesa/GL/EGL are already
host-provided.

Hosts without a usable libva (e.g. bare NVIDIA systems) must still not
fail to launch, since libva.so.2 is a direct DT_NEEDED of Moonlight and
the bundled FFmpeg. Stage the build-environment libva in
opt/libva-fallback (outside every loader search path) together with
libva-probe, a tiny program whose only job is to carry the same
DT_NEEDED entries and VA_API_* versioned symbol references as the
binaries shipped in the AppImage. The generated AppRun runs the probe
first: if the dynamic loader can start it against the host, the host
libva is used (probe passing is, by construction, exactly Moonlight
being loadable); if not, it prepends the fallback directory and launches
with the bundled libva, matching pre-existing behavior. The build script
verifies that the probe references every version node required by the
shipped binaries, so the two cannot silently drift apart.
2026-09-02 23:45:07 -05:00

141 lines
6.1 KiB
Bash
Executable File

BUILD_CONFIG="release"
fail()
{
echo "$1" 1>&2
exit 1
}
BUILD_ROOT=$PWD/build
SOURCE_ROOT=$PWD
BUILD_FOLDER=$BUILD_ROOT/build-$BUILD_CONFIG
DEPLOY_FOLDER=$BUILD_ROOT/deploy-$BUILD_CONFIG
INSTALLER_FOLDER=$BUILD_ROOT/installer-$BUILD_CONFIG
LINUXDEPLOY=linuxdeploy-$(uname -m).AppImage
if [ -n "$CI_VERSION" ]; then
VERSION=$CI_VERSION
else
VERSION=`cat $SOURCE_ROOT/app/version.txt`
fi
command -v qmake6 >/dev/null 2>&1 || fail "Unable to find 'qmake6' in your PATH!"
command -v $LINUXDEPLOY >/dev/null 2>&1 || fail "Unable to find '$LINUXDEPLOY' in your PATH!"
echo Cleaning output directories
rm -rf $BUILD_FOLDER
rm -rf $DEPLOY_FOLDER
rm -rf $INSTALLER_FOLDER
mkdir $BUILD_ROOT
mkdir $BUILD_FOLDER
mkdir $DEPLOY_FOLDER
mkdir $INSTALLER_FOLDER
# Enable LTO for official builds
export CFLAGS=-flto=auto
export CXXFLAGS=-flto=auto
export LDFLAGS=-flto=auto
echo Configuring the project
pushd $BUILD_FOLDER
# Building with Wayland support will cause linuxdeploy to include libwayland-client.so in the AppImage.
# Since we always use the host implementation of EGL, this can cause libEGL_mesa.so to fail to load due
# to missing symbols from the host's version of libwayland-client.so that aren't present in the older
# version of libwayland-client.so from our AppImage build environment. When this happens, EGL fails to
# work even in X11. To avoid this, we will disable Wayland support for the AppImage.
#
# We disable DRM support because linuxdeploy doesn't bundle the appropriate libraries for Qt EGLFS.
qmake6 $SOURCE_ROOT/moonlight-qt.pro CONFIG+=disable-wayland CONFIG+=disable-libdrm PREFIX=$DEPLOY_FOLDER/usr DEFINES+=APP_IMAGE || fail "Qmake failed!"
popd
echo Compiling Moonlight in $BUILD_CONFIG configuration
pushd $BUILD_FOLDER
make -j$(nproc) $(echo "$BUILD_CONFIG" | tr '[:upper:]' '[:lower:]') || fail "Make failed!"
popd
echo Deploying to staging directory
pushd $BUILD_FOLDER
make install || fail "Make install failed!"
popd
export QML_SOURCES_PATHS=$SOURCE_ROOT/app/gui
export QMAKE=qmake6
# Stage the build-environment libva in opt/libva-fallback (outside usr/, so linuxdeploy
# does not scan it, and outside every loader search path), together with a small probe
# binary that carries the same libva ELF requirements as Moonlight. The AppRun below
# uses the probe to decide, via the dynamic loader itself, whether the host libva can
# satisfy Moonlight; when it cannot, the fallback copy is made visible. opt/ is a
# standard linuxdeploy location for application data that must not be processed.
LIBVA_FALLBACK_DIR=$DEPLOY_FOLDER/opt/libva-fallback
SYSTEM_LIBVA=$(ldconfig -p 2>/dev/null | awk '/libva\.so\.2/{print $NF; exit}')
mkdir -p $LIBVA_FALLBACK_DIR
if [ -n "$SYSTEM_LIBVA" ]; then
cp -a "$(dirname "$SYSTEM_LIBVA")"/libva*.so* $LIBVA_FALLBACK_DIR/ || fail "Unable to stage libva fallback copy!"
echo Compiling libva-probe
cc -O2 -L"$(dirname "$SYSTEM_LIBVA")" -Wl,--no-as-needed -o $LIBVA_FALLBACK_DIR/libva-probe \
$SOURCE_ROOT/app/deploy/linux/libva-probe.c -lva -lva-x11 || fail "Unable to compile libva-probe!"
# Keep the probe honest: it must reference every VA_API_* version node that the
# binaries shipped in the AppImage require, otherwise a host libva could pass the
# probe and still fail to load Moonlight or the bundled FFmpeg.
va_nodes() { LC_ALL=C readelf -V "$1" 2>/dev/null | grep -oE 'VA_API_[0-9]+\.[0-9]+\.[0-9]+' | sort -u; }
NEEDED_NODES=$(for b in $DEPLOY_FOLDER/usr/bin/moonlight \
/usr/local/lib*/libav*.so* /usr/local/lib*/libsw*.so* \
/usr/lib/x86_64-linux-gnu/libav*.so* /usr/lib/x86_64-linux-gnu/libsw*.so*; do
[ -f "$b" ] && va_nodes "$b"; done | sort -u)
PROBE_NODES=$(va_nodes $LIBVA_FALLBACK_DIR/libva-probe)
[ -z "$(comm -13 <(echo "$PROBE_NODES") <(echo "$NEEDED_NODES"))" ] || \
fail "libva-probe is missing version node(s): $(comm -13 <(echo "$PROBE_NODES") <(echo "$NEEDED_NODES")) - update app/deploy/linux/libva-probe.c!"
fi
APP_RUN=$BUILD_ROOT/AppRun-libva
cat > $APP_RUN <<'APPRUN_EOF'
#!/bin/bash
# AppRun: prefer the host libva; use the staged copy only if the host cannot run us.
#
# VA-API driver modules on the host are loaded by libva and export an entrypoint
# named after the libva version they were built against (__vaDriverInit_1_XX).
# A libva can only load drivers that are not newer than itself, so bundling our
# own (older) libva silently breaks hardware decoding on up-to-date distros.
# Distros keep host libva and host drivers in step, so whenever the host libva
# can satisfy Moonlight's own ELF version requirements it is the right choice.
#
# "Can satisfy" is answered here by libva-probe, a tiny program linked against
# the same versioned libva symbols as Moonlight and the bundled FFmpeg: if the
# dynamic loader can start it, the host libva works; if not (no libva installed,
# or one too old to link), we make the staged build-environment copy visible
# via LD_LIBRARY_PATH, matching the pre-existing bundled behavior.
APPDIR="${APPDIR:-$(dirname "$(dirname "$(readlink -f "$0")")")}"
LIBVA_FALLBACK="$APPDIR/opt/libva-fallback"
if [ -d "$LIBVA_FALLBACK" ] && ! "$LIBVA_FALLBACK/libva-probe" 2>/dev/null; then
export LD_LIBRARY_PATH="$LIBVA_FALLBACK${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
fi
exec "$APPDIR/usr/bin/moonlight" "$@"
APPRUN_EOF
chmod +x $APP_RUN
echo Creating AppImage
pushd $INSTALLER_FOLDER
# Don't bundle libva: the bundled build-environment version (jammy: VA-API 1.20/1.22)
# cannot dlopen GPU drivers compiled against newer libva on the host (which export
# __vaDriverInit_1_23+), so va_openDriver() always fails and VAAPI falls back to
# software decoding. The host always provides libva on systems where VA-API is
# usable, so link against it at runtime instead (the AppRun shim above keeps a
# bundled last-resort copy for hosts without libva).
VERSION=$VERSION $LINUXDEPLOY --appdir $DEPLOY_FOLDER \
--library=/usr/local/lib/libSDL3.so.0 \
--plugin qt \
--custom-apprun $APP_RUN \
--exclude-library=libva.so* \
--exclude-library=libva-drm.so* \
--exclude-library=libva-wayland.so* \
--exclude-library=libva-x11.so* \
--output appimage || fail "linuxdeploy failed!"
popd
echo Build successful