From 61ea96a9e83a5d04ad2b388b4e1b54f4de647fbf Mon Sep 17 00:00:00 2001 From: The412Banner Date: Fri, 2 Oct 2026 13:51:26 -0400 Subject: [PATCH] Build FEX for the runtime The x86-64 Steam client experiment needs FEX inside the guest, and neither Arch Linux ARM nor FEX publishes a Linux arm64 binary. Built in the same Arch Linux ARM container as gamescope, without thunks, tuned for generic armv8-a, with the runtime's own unsquashfs alongside to unpack FEX's x86 rootfs image. --- .github/workflows/build-fex.yml | 49 +++++++++++++++++++++++++++++++++ tools/fex/build-in-arch.sh | 35 +++++++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 .github/workflows/build-fex.yml create mode 100755 tools/fex/build-in-arch.sh diff --git a/.github/workflows/build-fex.yml b/.github/workflows/build-fex.yml new file mode 100644 index 000000000..43a6b5043 --- /dev/null +++ b/.github/workflows/build-fex.yml @@ -0,0 +1,49 @@ +name: Build FEX (x86-64 Steam client experiment) + +# FEX built for the runtime in an Arch Linux ARM container on an arm64 runner (tools/fex), for the +# opt-in x86-64 Steam client (bannerlator-steam-x64). Published as a release asset that script +# fetches; the runtime image and the default arm64 client are untouched. +on: + push: + paths: + - 'tools/fex/**' + - '.github/workflows/build-fex.yml' + workflow_dispatch: + inputs: + tag: + description: 'Release tag to publish as (e.g. fex-2609-r1); empty = artifact only' + required: false + default: '' + +permissions: + contents: write + +jobs: + build: + runs-on: ubuntu-24.04-arm + timeout-minutes: 120 + steps: + - uses: actions/checkout@v4 + - name: Build in Arch Linux ARM + run: | + set -euo pipefail + docker run --rm -v "$PWD:/work" -w /work menci/archlinuxarm:base-devel bash tools/fex/build-in-arch.sh + sudo chown -R "$(id -u):$(id -g)" . + - uses: actions/upload-artifact@v4 + with: + name: fex + path: | + fex.tzst + fex.tzst.sha256 + fex-needed.txt + fex-files.txt + - name: Publish + if: github.event_name == 'workflow_dispatch' && inputs.tag != '' + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + gh release create "${{ inputs.tag }}" --latest=false --prerelease=false --title "${{ inputs.tag }}" --notes "FEX for the runtime (tools/fex/build-in-arch.sh); fetched by bannerlator-steam-x64. sha256 $(cut -d' ' -f1 fex.tzst.sha256)" fex.tzst fex.tzst.sha256 || gh release upload "${{ inputs.tag }}" fex.tzst fex.tzst.sha256 --clobber + # A component the apk fetches by tag, never something users install: it must not + # become the repo's Latest release or appear as a pre-release, also when re-uploaded. + gh release edit "${{ inputs.tag }}" --latest=false --prerelease=false diff --git a/tools/fex/build-in-arch.sh b/tools/fex/build-in-arch.sh new file mode 100755 index 000000000..d36445a8a --- /dev/null +++ b/tools/fex/build-in-arch.sh @@ -0,0 +1,35 @@ +#!/usr/bin/bash +# Runs INSIDE an Arch Linux ARM container (menci/archlinuxarm:base-devel) on an arm64 runner. +# Builds FEX for the runtime (Arch Linux ARM ships no package and FEX publishes no Linux +# binaries), plus the runtime's own unsquashfs to unpack FEX's x86 rootfs image, and packs both +# as fex.tzst (usr/local/...) for bannerlator-steam-x64 to stage. No thunks: they need an x86 +# cross toolchain, and the x86-64 Steam client only needs FEX to run, not host GPU drivers. +set -euxo pipefail +VERSION=${FEX_VERSION:-FEX-2609} +WORK=/work +cd "$WORK" +# Same pacman workarounds as tools/gamescope/build-in-arch.sh. +grep -q '^DisableSandbox' /etc/pacman.conf || sed -i 's/^\[options\]/[options]\nDisableSandbox/' /etc/pacman.conf +{ for m in https://ca.us.mirror.archlinuxarm.org https://fl.us.mirror.archlinuxarm.org https://de3.mirror.archlinuxarm.org https://nl.mirror.archlinuxarm.org; do echo "Server = $m/\$arch/\$repo"; done; cat /etc/pacman.d/mirrorlist; } > /etc/pacman.d/mirrorlist.new +mv /etc/pacman.d/mirrorlist.new /etc/pacman.d/mirrorlist +pacman -Syu --noconfirm --needed git zstd binutils cmake ninja clang lld llvm python squashfs-tools file + +rm -rf fex-src out && git clone -q --depth 1 --branch "$VERSION" --recurse-submodules --shallow-submodules https://github.com/FEX-Emu/FEX.git fex-src +# TUNE_CPU none: the default (native) would tune for the runner's Neoverse cores, not the device. +cmake -S fex-src -B fex-build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local \ + -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DUSE_LINKER=lld \ + -DTUNE_CPU=none -DTUNE_ARCH=armv8-a \ + -DBUILD_TESTING=OFF -DBUILD_THUNKS=OFF -DBUILD_FEXCONFIG=OFF -DENABLE_ASSERTIONS=OFF \ + -DENABLE_CCACHE=OFF -DENABLE_OFFLINE_TELEMETRY=OFF +ninja -C fex-build +DESTDIR="$WORK/out" ninja -C fex-build install +install -Dm755 /usr/bin/unsquashfs out/usr/local/bin/unsquashfs +# Nothing outside the runtime's own libraries: every needed soname must resolve in a stock guest. +for f in out/usr/local/bin/*; do + file "$f" | grep -q ELF || continue + readelf -d "$f" | awk '/NEEDED/ { gsub(/[\[\]]/, "", $5); print $5 }' +done | sort -u | tee fex-needed.txt +(cd out && find . -type f -o -type l | sort) | tee fex-files.txt +tar -C out -c . | zstd -19 -T0 -o fex.tzst +sha256sum fex.tzst | tee fex.tzst.sha256