Steam Frame build, packaging and deployment scripts; render test

- build_scripts/steamframe/: Debian trixie (Qt 6.8) containers for the
  aarch64 cross build, native build and tests; toolchain; scripts to build,
  test (Vulkan validation layers), package (system Qt, ~3 MB) and deploy over
  SSH. Adapted from the vrdashboard template.
- test/render/: render test for the OpenGL and Vulkan backends, driven like
  OverlayController (option OVRAS_BUILD_TESTS).
- RPATH only $ORIGIN, without the build machine's link directories.
- Ignore dist/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Matthias Janetschek
2026-10-07 13:50:57 +02:00
co-authored by Claude Opus 5.5
parent b22c35ae29
commit bde675f1b8
15 changed files with 610 additions and 14 deletions
@@ -0,0 +1,27 @@
# CMake toolchain: cross-compile for aarch64 Linux (Steam Frame) inside
# cross-arm64.Dockerfile (Debian multiarch). Used by container-build.sh.
# Adapted from the vrdashboard template.
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++)
# Debian multiarch: target libraries live in /usr/lib/aarch64-linux-gnu;
# headers are shared.
set(CMAKE_LIBRARY_ARCHITECTURE aarch64-linux-gnu)
set(CMAKE_FIND_ROOT_PATH /usr/aarch64-linux-gnu)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH)
set(ENV{PKG_CONFIG_LIBDIR} /usr/lib/aarch64-linux-gnu/pkgconfig:/usr/share/pkgconfig)
# Qt 6 cross builds need a host Qt for its code generators (moc, rcc,
# qmlcachegen, ...).
set(QT_HOST_PATH /usr CACHE PATH "Host Qt prefix")
set(QT_HOST_PATH_CMAKE_DIR /usr/lib/x86_64-linux-gnu/cmake CACHE PATH "Host Qt CMake dir")
set(CMAKE_PREFIX_PATH /usr/lib/aarch64-linux-gnu/cmake)
set(CMAKE_OBJDUMP aarch64-linux-gnu-objdump)
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env bash
# Builds OVRAS for the Steam Frame inside a Debian container (Qt 6.8, the
# series of the Frame's system Qt). Result: build/steamframe-<mode>/Release/,
# packaged by package-release.sh. Adapted from the vrdashboard template.
#
# build_scripts/steamframe/container-build.sh cross x86_64 host -> aarch64
# build_scripts/steamframe/container-build.sh native for the host's own
# architecture (on the Frame: native arm64)
# build_scripts/steamframe/container-build.sh <mode> shell
# interactive shell in that container
#
# Uses podman if available, else docker (override with CONTAINER_ENGINE).
set -euo pipefail
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
root="$(cd "$here/../.." && pwd)"
mode="${1:-}"
case "$mode" in
cross) dockerfile=cross-arm64.Dockerfile
toolchain="-DCMAKE_TOOLCHAIN_FILE=build_scripts/steamframe/aarch64-linux-gnu.cmake" ;;
native) dockerfile=native.Dockerfile; toolchain="" ;;
*) echo "usage: $0 cross|native [shell]" >&2; exit 2 ;;
esac
image="ovras-steamframe-${dockerfile%.Dockerfile}"
engine="${CONTAINER_ENGINE:-$(command -v podman || command -v docker)}"
build="build/steamframe-$mode"
"$engine" build -q -t "$image" -f "$here/$dockerfile" "$here" >/dev/null
# Run as the calling user, so the build tree isn't owned by root.
run=("$engine" run --rm -v "$root:$root:z" -w "$root")
if [[ "$(basename "$engine")" == docker ]]; then
run+=(--user "$(id -u):$(id -g)")
else
run+=(--userns=keep-id)
fi
[[ -t 0 ]] && run+=(-it)
if [[ "${2:-}" == shell ]]; then
exec "${run[@]}" "$image" bash
fi
# OVRAS_STEAM_FRAME: Frame capabilities and defaults
# (src/utils/platform_capabilities.h).
"${run[@]}" "$image" bash -c "
set -e
cmake -S . -B $build -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DOVRAS_STEAM_FRAME=ON $toolchain
cmake --build $build
"
echo "Built: $root/$build/Release"
+50
View File
@@ -0,0 +1,50 @@
#!/usr/bin/env bash
# Builds and runs the tests (OVRAS_BUILD_TESTS) in a Debian container with
# both render backends, Vulkan with the Khronos validation layers. Fails on any
# validation message. Adapted from the vrdashboard template.
#
# build_scripts/steamframe/container-test.sh GPU via /dev/dri if
# available, else software
# VK_DRIVER=lvp build_scripts/steamframe/container-test.sh
# force lavapipe (software)
set -euo pipefail
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
root="$(cd "$here/../.." && pwd)"
engine="${CONTAINER_ENGINE:-$(command -v podman || command -v docker)}"
"$engine" build -q -t ovras-steamframe-native -f "$here/native.Dockerfile" "$here" >/dev/null
"$engine" build -q -t ovras-steamframe-test -f "$here/test.Dockerfile" "$here" >/dev/null
run=("$engine" run --rm -v "$root:$root:z" -w "$root" -e HOME=/tmp -e "VK_DRIVER=${VK_DRIVER:-}")
if [[ "$(basename "$engine")" == docker ]]; then
run+=(--user "$(id -u):$(id -g)")
else
run+=(--userns=keep-id)
fi
[[ -e /dev/dri/renderD128 ]] && run+=(--device /dev/dri/renderD128)
"${run[@]}" ovras-steamframe-test bash -c '
set -e
build=build/steamframe-test
cmake -S . -B $build -G Ninja -DCMAKE_BUILD_TYPE=Debug -DOVRAS_BUILD_TESTS=ON >/dev/null
cmake --build $build --target tst_render
if [[ -n "$VK_DRIVER" ]]; then export VK_DRIVER_FILES=/usr/share/vulkan/icd.d/${VK_DRIVER}_icd.json; fi
vulkaninfo --summary 2>/dev/null | grep -m1 deviceName || true
Xvfb :99 >/dev/null 2>&1 &
sleep 1
export DISPLAY=:99 OVRAS_VULKAN_VALIDATION=1
ctest --test-dir $build --output-on-failure -V > $build/ctest.log 2>&1 \
|| { tail -40 $build/ctest.log; exit 1; }
grep -E "tests passed|Test +#|Totals" $build/ctest.log
# A skipped test (no GPU context) must not count as passed.
if grep -q "SKIP" $build/ctest.log; then
echo "Tests were skipped (see $build/ctest.log)"; exit 1
fi
messages=$(grep -oE "(VUID|UNASSIGNED)-[A-Za-z0-9_.-]+" $build/ctest.log | sort | uniq -c || true)
if [[ -n "$messages" ]]; then
echo "Vulkan validation messages (see $build/ctest.log):"
echo "$messages"
exit 1
fi
echo "No Vulkan validation messages."
'
@@ -0,0 +1,20 @@
# Cross-compilation environment for the Steam Frame (aarch64), from an x86_64
# host via Debian multiarch: the compiler and Qt's code generators (moc, rcc,
# qmlcachegen) run natively as amd64 builds, the target libraries are the
# arm64 builds. Debian trixie ships Qt 6.8, the series of the Frame's system
# Qt (SteamOS holo 0.4.3: 6.8.0). Adapted from the vrdashboard template.
FROM debian:trixie
ENV LANG=C.UTF-8
RUN dpkg --add-architecture arm64 \
&& apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates cmake ninja-build pkgconf file \
g++-aarch64-linux-gnu binutils-aarch64-linux-gnu \
qt6-base-dev qt6-base-dev-tools qt6-declarative-dev qt6-declarative-dev-tools \
qt6-base-dev:arm64 qt6-declarative-dev:arm64 qt6-multimedia-dev:arm64 \
qt6-websockets-dev:arm64 qt6-svg-dev:arm64 \
libgl-dev:arm64 libegl-dev:arm64 libvulkan-dev:arm64 libxkbcommon-dev:arm64 \
libx11-dev:arm64 libxt-dev:arm64 libxtst-dev:arm64 libpulse-dev:arm64 \
&& rm -rf /var/lib/apt/lists/*
+24
View File
@@ -0,0 +1,24 @@
#!/usr/bin/env bash
# Copies the newest package (package-release.sh) to the Steam Frame over SSH
# and runs its install.sh there (registers with SteamVR and starts it; an
# existing installation in the same place is updated). SteamVR must be running
# on the Frame. Adapted from the vrdashboard template.
#
# build_scripts/steamframe/deploy-to-frame.sh <ssh-target> [package-dir] [remote-dir]
# e.g. build_scripts/steamframe/deploy-to-frame.sh steamos@frame
#
# remote-dir defaults to "AdvancedSettings" in the remote home directory.
set -euo pipefail
target="${1:?usage: $0 <ssh-target> [package-dir] [remote-dir]}"
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
package="${2:-$(ls -dt "$root"/dist/AdvancedSettings-*-linux-arm64 2>/dev/null | head -1)}"
[[ -n "$package" && -x "$package/install.sh" ]] || {
echo "No package found (run build_scripts/steamframe/package-release.sh first)" >&2; exit 1; }
# Without the version: updates go to the same place and replace the old one.
remote="${3:-AdvancedSettings}"
# Keep the log of earlier runs on the Frame.
rsync -a --delete --exclude AdvancedSettings.log "$package/" "$target:$remote/"
ssh "$target" "\$HOME/$remote/install.sh"
echo "Installed $(basename "$package") to $target:~/$remote"
@@ -0,0 +1,19 @@
# Native build environment (any architecture). On x86_64 it is the base of the
# test container (test.Dockerfile); on the Steam Frame itself (aarch64, e.g.
# with podman) it builds the same package as the cross build. Debian trixie
# ships Qt 6.8, the series of the Frame's system Qt. Adapted from the
# vrdashboard template.
FROM debian:trixie
ENV LANG=C.UTF-8
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates cmake ninja-build pkgconf file g++ \
qt6-base-dev qt6-base-dev-tools qt6-declarative-dev qt6-declarative-dev-tools \
qt6-multimedia-dev qt6-websockets-dev qt6-svg-dev \
libgl-dev libegl-dev libvulkan-dev libxkbcommon-dev \
libx11-dev libxt-dev libxtst-dev libpulse-dev \
qml6-module-qtquick qml6-module-qtqml-workerscript qml6-module-qtquick-window \
qt6-qpa-plugins \
&& rm -rf /var/lib/apt/lists/*
+93
View File
@@ -0,0 +1,93 @@
#!/usr/bin/env bash
# Builds OVRAS for the Steam Frame (aarch64) and packages it:
# dist/AdvancedSettings-<version>-linux-arm64/ (+ .tar.gz)
# The package uses SteamOS's own Qt (no Qt bundled). It contains the program
# folder as OVRAS expects it (binary, libopenvr_api.so, manifests, res/) plus
# install.sh, uninstall.sh, run.sh and README.txt. Adapted from the
# vrdashboard template.
#
# build_scripts/steamframe/package-release.sh [--no-build]
set -euo pipefail
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
root="$(cd "$here/../.." && pwd)"
build=1
for arg in "$@"; do
case "$arg" in
--no-build) build=0 ;;
*) echo "usage: $0 [--no-build]" >&2; exit 2 ;;
esac
done
# On the Frame itself build natively, elsewhere cross-compile.
if [[ "$(uname -m)" == aarch64 ]]; then mode=native; else mode=cross; fi
(( build )) && "$here/container-build.sh" "$mode"
exe=AdvancedSettings_app
bin="$root/build/steamframe-$mode/Release"
version="$(sed -n 's/^CMAKE_PROJECT_VERSION:[A-Z]*=//p' "$root/build/steamframe-$mode/CMakeCache.txt")"
[[ "$(file -b "$bin/$exe")" == *aarch64* ]] || { echo "$bin/$exe is not an aarch64 build" >&2; exit 1; }
package="AdvancedSettings-$version-linux-arm64"
out="$root/dist/$package"
rm -rf "$out" "$out.tar.gz"
mkdir -p "$out"
cp -a "$bin/." "$out/"
# The scripts start the program from the package folder: OVRAS writes its log
# (AdvancedSettings.log) to the working directory. Process names are cut to
# 15 characters, so running instances are found by command line.
cat > "$out/install.sh" <<EOF
#!/bin/sh
# Installs or updates OVR Advanced Settings: registers it with SteamVR (starts
# automatically with SteamVR from now on) and starts it now. SteamVR must be
# running. Works over SSH.
cd "\$(dirname "\$0")" || exit 1
# An update replaces the files of a running instance: stop it first.
pkill -f "/$exe( |\\\$)" 2>/dev/null && sleep 1
./$exe --force-install-manifest || exit 1
# Start now. Over SSH there is no display; Qt needs one even for off-screen
# rendering, so use the Frame's (:0).
DISPLAY="\${DISPLAY:-:0}" nohup ./$exe >/dev/null 2>&1 &
echo "Installed and started."
EOF
cat > "$out/uninstall.sh" <<EOF
#!/bin/sh
# Unregisters OVR Advanced Settings from SteamVR and stops it. The folder can
# be deleted afterwards.
cd "\$(dirname "\$0")" || exit 1
pkill -f "/$exe( |\\\$)" 2>/dev/null
exec ./$exe --force-remove-manifest
EOF
cat > "$out/run.sh" <<EOF
#!/bin/sh
# Restarts OVR Advanced Settings with the given graphics API, for testing:
# ./run.sh [opengl|vulkan] (default: opengl)
cd "\$(dirname "\$0")" || exit 1
pkill -f "/$exe( |\\\$)" 2>/dev/null && sleep 1
DISPLAY="\${DISPLAY:-:0}" nohup ./$exe --graphics-api "\${1:-opengl}" >/dev/null 2>&1 &
echo "Started with \${1:-opengl}; log: \$(pwd)/AdvancedSettings.log"
EOF
chmod +x "$out/install.sh" "$out/uninstall.sh" "$out/run.sh"
cat > "$out/README.txt" <<EOF
OVR Advanced Settings $version for the Steam Frame (SteamOS, aarch64)
Uses the system's Qt (SteamOS: Qt 6.8 or newer).
Install / update: ./install.sh (SteamVR must be running)
SteamVR remembers this folder's location: after moving it,
run ./install.sh again.
Uninstall: ./uninstall.sh
Test a graphics API: ./run.sh opengl | ./run.sh vulkan
Log: AdvancedSettings.log in the working directory (this folder
when started by the scripts)
License: GPL-3.0 (LICENSE); OpenVR: LICENSE-VALVE.
EOF
tar -C "$root/dist" -czf "$out.tar.gz" "$package"
echo "Release folder: $out"
echo "Archive: $out.tar.gz ($(du -h "$out.tar.gz" | cut -f1))"
+10
View File
@@ -0,0 +1,10 @@
# Test environment on top of native.Dockerfile: Mesa Vulkan/GL drivers (incl.
# the lavapipe software rasterizer), the Khronos validation layers and Xvfb.
# See container-test.sh. Adapted from the vrdashboard template.
FROM ovras-steamframe-native
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
mesa-vulkan-drivers vulkan-validationlayers vulkan-tools libgl1-mesa-dri \
xvfb xauth \
&& rm -rf /var/lib/apt/lists/*