#!/bin/bash # Run on the ARM64 terminal. All sources, libraries and output stay in TASK_ROOT. # No apt install, system library replacement, desktop restart or sudo is needed. set -euo pipefail TASK_ROOT="${VDI_RKMPP_BUILD_ROOT:-$HOME/vdi-build/rkmpp-experiment}" SDK="$TASK_ROOT/deps/usr" PREFIX="$TASK_ROOT/runtime" JOBS="${VDI_BUILD_JOBS:-2}" mkdir -p "$TASK_ROOT"/{src,debs,build} "$PREFIX" clone_source() { local name="$1" ref="$2" url="$3" if [ ! -d "$TASK_ROOT/src/$name/.git" ]; then git clone --depth=1 --branch "$ref" "$url" "$TASK_ROOT/src/$name" fi test "$(git -C "$TASK_ROOT/src/$name" rev-parse HEAD)" = "$4" || { echo "Unexpected $name source revision; inspect before building" >&2; exit 1; } } clone_source mpp develop https://github.com/rockchip-linux/mpp.git 7916c3cc000fefc0bdf96636b7d54fe174724f44 clone_source ffmpeg 6.1 https://github.com/nyanmisaka/ffmpeg-rockchip.git 797387de1c9caa9170100d7217ee093d1897f750 clone_source moonlight v6.2.0 https://github.com/moonlight-stream/moonlight-qt.git de2467e433821664cdd2224aad8c89a625be1ad9 git -C "$TASK_ROOT/src/moonlight" submodule update --init --recursive --depth=1 # Versioned VDI interaction patches. Explicit directory supports copying this # builder to the box without the rest of the repository. Refuse unknown drift. PATCH_DIR="${VDI_MOONLIGHT_PATCH_DIR:-$(cd "$(dirname "$0")/../terminal/moonlight/patches" && pwd)}" apply_vdi_patch() { local source="$1" patch="$2" test -r "$patch" || { echo "Missing VDI patch: $patch" >&2; exit 1; } # A Windows checkout can give patch files CRLF. Normalize patch transport # only; source drift still has to pass the ordinary exact-context check. if sed 's/\r$//' "$patch" | git -C "$source" apply --reverse --check - 2>/dev/null; then return fi sed 's/\r$//' "$patch" | git -C "$source" apply --check - sed 's/\r$//' "$patch" | git -C "$source" apply - } apply_vdi_patch "$TASK_ROOT/src/moonlight" "$PATCH_DIR/0001-vdi-input-and-latest-frame.patch" apply_vdi_patch "$TASK_ROOT/src/moonlight/moonlight-common-c/moonlight-common-c" \ "$PATCH_DIR/0002-vdi-absolute-click-barrier.patch" apply_vdi_patch "$TASK_ROOT/src/moonlight" "$PATCH_DIR/0003-vdi-input-priority.patch" apply_vdi_patch "$TASK_ROOT/src/moonlight" "$PATCH_DIR/0004-vdi-power-key-policy.patch" apply_vdi_patch "$TASK_ROOT/src/moonlight" "$PATCH_DIR/0005-vdi-native-linux-cursor.patch" # Moonlight 6.2.0 uses std::optional in drm.h but relies on a transitive # include from Qt 6. Our Qt 5 build needs the standard header explicitly. export VDI_MOONLIGHT_SOURCE="$TASK_ROOT/src/moonlight" python3 - <<'PY' import os from pathlib import Path header = Path(os.environ['VDI_MOONLIGHT_SOURCE']) / 'app/streaming/video/ffmpeg-renderers/drm.h' text = header.read_text() if '#include ' not in text: assert '#include ' in text header.write_text(text.replace('#include ', '#include \n#include ', 1)) PY # Download only the required headers and private runtime libraries from the # configured distribution repository. apt does not install or upgrade anything. cd "$TASK_ROOT/debs" apt-get download libdrm-dev libopus-dev libsdl2-dev libsdl2-2.0-0 \ libsdl2-ttf-dev libsdl2-ttf-2.0-0 libudev-dev libxkbcommon-dev \ libegl-dev libgles-dev libgles1 libgbm-dev libwayland-dev libwayland-bin wayland-protocols \ qtdeclarative5-dev qtdeclarative5-dev-tools qt5-qmltooling-plugins \ qtquickcontrols2-5-dev libqt5quickcontrols2-5 libqt5quicktemplates2-5 \ libqt5svg5-dev libqt5svg5 libqt5qmlworkerscript5 libqt5quickparticles5 \ libqt5quickshapes5 libqt5quicktest5 libx264-dev \ qml-module-qtquick2 qml-module-qtquick-window2 qml-module-qtquick-layouts \ qml-module-qtqml-models2 qml-module-qtquick-controls2 qml-module-qtquick-templates2 \ libharfbuzz-dev libfreetype-dev libpng-dev zlib1g-dev libbrotli-dev \ libglib2.0-dev libgraphite2-dev libffi-dev for package in ./*.deb; do dpkg-deb -x "$package" "$TASK_ROOT/deps"; done # Relocate pkg-config prefixes, preserving the system /usr and its libraries. export VDI_PRIVATE_SDK="$SDK" python3 - <<'PY' import os from pathlib import Path sdk = Path(os.environ['VDI_PRIVATE_SDK']) for pc in sdk.rglob('*.pc'): text = pc.read_text() if str(sdk) not in text: pc.write_text(text.replace('/usr', str(sdk))) for pri in sdk.rglob('qt_lib_*.pri'): pri.write_text(pri.read_text().replace('$$QT_MODULE_LIB_BASE', str(sdk / 'lib/aarch64-linux-gnu')) .replace('$$QT_MODULE_INCLUDE_BASE', str(sdk / 'include/aarch64-linux-gnu/qt5')) .replace('$$QT_MODULE_BIN_BASE', str(sdk / 'lib/qt5/bin'))) # A -dev symlink can target a different distro patch version than is installed. # Link to the existing matching SONAME; never replace the system runtime. for link in (sdk / 'lib/aarch64-linux-gnu').glob('*.so'): if link.is_symlink() and not link.exists(): target = Path(os.readlink(link)).name if '.so.' not in target: continue soname = target.split('.so.')[0] + '.so.' + target.split('.so.')[1].split('.')[0] system_lib = Path('/lib/aarch64-linux-gnu') / soname if system_lib.exists(): link.unlink() link.symlink_to(system_lib) PY export PKG_CONFIG_PATH="$PREFIX/lib/pkgconfig:$SDK/lib/aarch64-linux-gnu/pkgconfig:$SDK/share/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}" export LD_LIBRARY_PATH="$PREFIX/lib:$SDK/lib/aarch64-linux-gnu${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" export QMAKEPATH="$SDK/lib/aarch64-linux-gnu/qt5" export QML2_IMPORT_PATH="$SDK/lib/aarch64-linux-gnu/qt5/qml" export CPLUS_INCLUDE_PATH="$SDK/include/aarch64-linux-gnu:$SDK/include${CPLUS_INCLUDE_PATH:+:$CPLUS_INCLUDE_PATH}" export C_INCLUDE_PATH="$SDK/include/aarch64-linux-gnu:$SDK/include${C_INCLUDE_PATH:+:$C_INCLUDE_PATH}" if [ "${VDI_BUILD_STAGE:-all}" != moonlight ]; then echo '=== build MPP ===' cmake -S "$TASK_ROOT/src/mpp" -B "$TASK_ROOT/build/mpp" \ -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="$PREFIX" \ -DRKPLATFORM=ON -DHAVE_DRM=ON -DBUILD_SHARED_LIBS=ON -DBUILD_TEST=OFF cmake --build "$TASK_ROOT/build/mpp" -j "$JOBS" cmake --install "$TASK_ROOT/build/mpp" echo '=== build FFmpeg with RKMPP ===' mkdir -p "$TASK_ROOT/build/ffmpeg" cd "$TASK_ROOT/build/ffmpeg" "$TASK_ROOT/src/ffmpeg/configure" --prefix="$PREFIX" \ --enable-gpl --enable-version3 --enable-shared --disable-static \ --enable-libdrm --enable-rkmpp --disable-rkrga --enable-libx264 \ --disable-doc --disable-debug --disable-autodetect --disable-network \ --disable-everything --enable-avcodec --enable-avformat --enable-avfilter \ --enable-swscale --enable-swresample --enable-ffmpeg --enable-ffprobe \ --enable-decoder=h264,hevc,h264_rkmpp,hevc_rkmpp,rawvideo,wrapped_avframe \ --enable-parser=h264,hevc --enable-demuxer=h264,hevc \ --enable-muxer=h264,null,rawvideo --enable-protocol=file \ --enable-encoder=libx264,wrapped_avframe,rawvideo --enable-indev=lavfi \ --enable-filter=testsrc2,null,format,hwdownload,scale \ --extra-cflags="-I$PREFIX/include -I$SDK/include" \ --extra-ldflags="-L$PREFIX/lib -L$SDK/lib/aarch64-linux-gnu -Wl,-rpath,$PREFIX/lib -Wl,-rpath,$SDK/lib/aarch64-linux-gnu" make -j "$JOBS" make install "$PREFIX/bin/ffmpeg" -hide_banner -decoders | grep rkmpp fi echo '=== build Moonlight ===' mkdir -p "$TASK_ROOT/build/moonlight" cd "$TASK_ROOT/build/moonlight" qmake "$TASK_ROOT/src/moonlight/moonlight-qt.pro" \ CONFIG+=release CONFIG+=recheck CONFIG+=disable-libva CONFIG+=disable-libvdpau \ CONFIG+=disable-libplacebo CONFIG+=disable-discord \ "QMAKE_LIBDIR+=$SDK/lib/aarch64-linux-gnu" \ "QMAKE_LIBDIR+=$PREFIX/lib" \ "QMAKE_RPATHDIR+=$SDK/lib/aarch64-linux-gnu" \ "QMAKE_RPATHDIR+=$PREFIX/lib" \ "QMAKE_INCDIR+=$SDK/include/aarch64-linux-gnu/qt5" \ "QMAKE_INCDIR+=$SDK/include/aarch64-linux-gnu" \ "QMAKE_INCDIR+=$SDK/include" make qmake_all if ! grep -q -- '-DHAVE_EGL' app/Makefile.Release; then echo 'EGL renderer missing; refusing to build a client without the DRM PRIME display path' >&2 exit 1 fi # qmake/make does not invalidate old objects when feature defines change. # In particular a stale ffmpeg.o can silently omit EGL despite a successful # configure check. Clean the app whenever its compiler/link flags change. flags_hash="$(grep -E '^(DEFINES|INCPATH|LIBS|CXXFLAGS)[[:space:]]*=' app/Makefile.Release | sha256sum | cut -d ' ' -f 1)" if [ "$(cat .vdi-build-flags.sha256 2>/dev/null || true)" != "$flags_hash" ]; then make -C app -f Makefile.Release clean printf '%s\n' "$flags_hash" > .vdi-build-flags.sha256 fi make -j "$JOBS" echo '=== complete; experimental binary only, production unchanged ===' echo "$TASK_ROOT/build/moonlight/app/moonlight" git -C "$TASK_ROOT/src/mpp" rev-parse HEAD git -C "$TASK_ROOT/src/ffmpeg" rev-parse HEAD git -C "$TASK_ROOT/src/moonlight" rev-parse HEAD sha256sum "$TASK_ROOT"/debs/*.deb > "$TASK_ROOT/dependency-sha256.txt"