ci: consolidate verified component preparation

This commit is contained in:
XxJSONDeruloxX committed 2026-10-23 14:31:13 -04:00
1 parent d111b2b3dd
commit d7bf0b274f
31 files changed
+1269 -503

No files matched your search

+1 -1
View File
@@ -23,7 +23,7 @@ permissions:
jobs:
validate:
name: Build APK
name: Validate source and build result
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
+249 -469
View File
@@ -31,17 +31,14 @@ concurrency:
group: build-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
PRELOAD_CACHE_VERSION: v8
jobs:
xbox:
name: Xbox ARM64 backend
source:
name: Pin source tree
if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }}
runs-on: ubuntu-24.04-arm
timeout-minutes: 30
runs-on: ubuntu-24.04
timeout-minutes: 5
outputs:
base-sha: ${{ steps.merge.outputs.base-sha }}
base-sha: ${{ steps.merge.outputs.base-sha || steps.trusted.outputs.base-sha }}
tree-sha: ${{ steps.source.outputs.tree-sha }}
steps:
- uses: actions/checkout@v4
@@ -59,9 +56,46 @@ jobs:
git config user.name "ci"
git config user.email "ci@users.noreply.github.com"
git merge --no-ff --no-edit origin/main
- name: Record backend source tree
- name: Resolve trusted manifest base
id: trusted
if: github.event_name != 'pull_request'
run: |
set -euo pipefail
git fetch --no-tags origin main
echo "base-sha=$(git rev-parse origin/main)" >> "$GITHUB_OUTPUT"
- name: Record pinned source tree
id: source
run: echo "tree-sha=$(git rev-parse 'HEAD^{tree}')" >> "$GITHUB_OUTPUT"
xbox:
name: Xbox ARM64 backend
needs: source
if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }}
runs-on: ubuntu-24.04-arm
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
fetch-depth: 0
persist-credentials: false
- name: Merge the pinned APK base
if: github.event_name == 'pull_request'
env:
BUILD_BASE_SHA: ${{ needs.source.outputs.base-sha }}
run: |
set -euo pipefail
git fetch --no-tags origin "$BUILD_BASE_SHA"
git config user.name "ci"
git config user.email "ci@users.noreply.github.com"
git merge --no-ff --no-edit FETCH_HEAD
- name: Verify pinned source tree
env:
SOURCE_TREE: ${{ needs.source.outputs.tree-sha }}
run: test "$(git rev-parse 'HEAD^{tree}')" = "$SOURCE_TREE"
- name: Plan component identities
id: plan
run: python3 tools/components/prepare.py plan
- uses: actions/cache@v4
id: xbox-cache
with:
@@ -69,13 +103,10 @@ jobs:
app/src/main/assets/linuxfs/usr/local/lib/droiddeck-xbox
app/build/xbox/xodus-droiddeck-source.tar.xz
app/build/xbox/runtime/*-source.tar.xz
key: xbox-arm64-${{ hashFiles('tools/xbox/**') }}
key: xbox-v2-${{ steps.plan.outputs.xbox }}
- name: Build Xbox backend
if: steps.xbox-cache.outputs.cache-hit != 'true'
run: |
rustup toolchain install 1.99.0 --profile minimal
bash tools/ci/install-ubuntu-packages.sh build-essential pkg-config libssl-dev protobuf-compiler cmake xz-utils
bash tools/xbox/build.sh
run: python3 tools/components/prepare.py xbox
- uses: actions/upload-artifact@v4
with:
name: xbox-arm64
@@ -90,8 +121,157 @@ jobs:
if-no-files-found: error
retention-days: 14
components:
name: Prepare verified components
needs: source
if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }}
runs-on: ubuntu-24.04
timeout-minutes: 40
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
fetch-depth: 0
persist-credentials: false
- name: Merge the pinned APK base
if: github.event_name == 'pull_request'
env:
BUILD_BASE_SHA: ${{ needs.source.outputs.base-sha }}
run: |
set -euo pipefail
git fetch --no-tags origin "$BUILD_BASE_SHA"
git config user.name "ci"
git config user.email "ci@users.noreply.github.com"
git merge --no-ff --no-edit FETCH_HEAD
- name: Verify pinned source tree
env:
SOURCE_TREE: ${{ needs.source.outputs.tree-sha }}
run: test "$(git rev-parse 'HEAD^{tree}')" = "$SOURCE_TREE"
- name: Resolve the pinned Android NDK
run: bash tools/ci/resolve-ndk.sh
- name: Plan component identities
id: plan
run: python3 tools/components/prepare.py plan
- name: Cache remaining Linux runtime assets
id: preloads
uses: actions/cache@v4
with:
path: |
app/src/main/assets/linuxfs
!app/src/main/assets/linuxfs/usr/local/lib/droiddeck-comet
!app/src/main/assets/linuxfs/usr/local/lib/droiddeck-xbox
!app/src/main/assets/linuxfs/i386/libblsession.so
!app/src/main/assets/linuxfs/i386/libfakeinput.so
!app/src/main/assets/linuxfs/i386/libfaultreport.so
!app/src/main/assets/linuxfs/libblaudit.so
!app/src/main/assets/linuxfs/libblfastpath.so
!app/src/main/assets/linuxfs/libblsession.so
!app/src/main/assets/linuxfs/libfakeinput.so
!app/src/main/assets/linuxfs/libssbs.so
!app/src/main/assets/linuxfs/usr/local/bin/droiddeck-clipboard
!app/src/main/assets/linuxfs/x86_64/libblsession.so
!app/src/main/assets/linuxfs/x86_64/libfakeinput.so
!app/src/main/assets/linuxfs/x86_64/libfaultreport.so
!app/src/main/assets/linuxfs/x86_64/libvulkan-thunk.so
!app/src/main/assets/linuxfs/x86_64/libwebkitinput.so
key: runtime-v9-${{ steps.plan.outputs.runtime }}
- name: Cache PulseAudio 13.0 source
id: pa-src
uses: actions/cache@v4
with:
path: pa13
key: pulseaudio-src-${{ steps.plan.outputs.pulseaudio-source }}
- name: Cache proot
id: proot
uses: actions/cache@v4
with:
path: |
app/src/main/jniLibs/arm64-v8a/libproot.so
app/src/main/jniLibs/arm64-v8a/libproot-loader.so
key: proot-v2-${{ steps.plan.outputs.proot }}
- name: Cache GOG Galaxy companion
id: comet
uses: actions/cache@v4
with:
path: app/src/main/assets/linuxfs/usr/local/lib/droiddeck-comet
key: comet-v2-${{ steps.plan.outputs.comet }}
- name: Restore verified preload snapshots
id: component-preloads
env:
GH_TOKEN: ${{ github.token }}
FORCE_PRELOAD_REBUILD: ${{ inputs.rebuild_preloads || 'false' }}
COMPONENT_TRUSTED_BASE: ${{ needs.source.outputs.base-sha }}
run: python3 tools/components/prepare.py preload-snapshot
- name: Prepare required Linux build tools once
env:
COMPONENTS_TO_BUILD: >-
${{ steps.preloads.outputs.cache-hit != 'true' && 'runtime' || '' }}
${{ steps.component-preloads.outputs.arm64 != 'true' && 'arm64-preloads' || '' }}
${{ steps.comet.outputs.cache-hit != 'true' && 'comet' || '' }}
run: python3 tools/components/prepare.py tools
- name: Build changed ARM64 preloads
if: steps.component-preloads.outputs.arm64 != 'true'
run: python3 tools/components/prepare.py arm64-preloads
- name: Prepare remaining Linux runtime assets
if: steps.preloads.outputs.cache-hit != 'true'
run: python3 tools/components/prepare.py runtime
- name: Fetch the Debian image the preloads build in
if: steps.component-preloads.outputs.x86 != 'true'
env:
GH_TOKEN: ${{ github.token }}
run: python3 tools/components/prepare.py debian-image
- name: Build the x86 preloads FEX guests load (glibc 2.31)
if: steps.component-preloads.outputs.x86 != 'true'
run: python3 tools/components/prepare.py x86-preloads
- name: Bundle the droiddeck-esync packs
run: python3 tools/components/prepare.py esync
- name: Fetch PulseAudio 13.0 source
if: steps.pa-src.outputs.cache-hit != 'true'
run: python3 tools/components/prepare.py pulseaudio-source
- name: Fetch DirectAudio (tools/directaudio/release.env)
run: python3 tools/components/prepare.py directaudio
- name: Build the AAudio sink into the audio bundle
run: python3 tools/components/prepare.py audio
- name: Build proot
if: steps.proot.outputs.cache-hit != 'true'
run: python3 tools/components/prepare.py proot
- name: Check the preloads
run: |
set -euo pipefail
bash tools/linuxfs/check-preloads.sh app/src/main/assets/linuxfs
- name: Build GOG Galaxy companion
if: steps.comet.outputs.cache-hit != 'true'
run: python3 tools/components/prepare.py comet
- name: Prepare graphics driver variants
run: python3 tools/quest/build-driver.py "$ANDROID_HOME/ndk/$NDK_VERSION"
- name: Record and pack prepared components
run: python3 tools/components/prepare.py pack
- uses: actions/upload-artifact@v4
with:
name: prepared-components
path: app/build/prepared-components.tar.gz
if-no-files-found: error
compression-level: 0
retention-days: 14
build:
needs: xbox
needs: [source, components, xbox]
name: Compile APK and Android tests
if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }}
runs-on: ubuntu-24.04
@@ -155,7 +335,7 @@ jobs:
- name: Merge onto main, or say what conflicts
if: github.event_name == 'pull_request'
env:
BUILD_BASE_SHA: ${{ needs.xbox.outputs.base-sha }}
BUILD_BASE_SHA: ${{ needs.source.outputs.base-sha }}
run: |
set -uo pipefail
git config user.name "ci"
@@ -213,7 +393,7 @@ jobs:
- name: Verify backend and APK source match
env:
XBOX_SOURCE_TREE: ${{ needs.xbox.outputs.tree-sha }}
XBOX_SOURCE_TREE: ${{ needs.source.outputs.tree-sha }}
run: test "$(git rev-parse 'HEAD^{tree}')" = "$XBOX_SOURCE_TREE"
- uses: actions/setup-java@v4
@@ -225,11 +405,8 @@ jobs:
with:
cache-cleanup: on-success
- name: Resolve the runner's NDK
run: |
NDK="${ANDROID_NDK_LATEST_HOME:-${ANDROID_NDK_HOME:-$ANDROID_NDK_ROOT}}"
VERSION=$(sed -n 's/^Pkg.Revision *= *//p' "$NDK/source.properties")
echo "NDK_VERSION=$VERSION" >> "$GITHUB_ENV"
- name: Resolve the pinned Android NDK
run: bash tools/ci/resolve-ndk.sh
- name: Set the APK build label
id: build-label
@@ -255,274 +432,6 @@ jobs:
echo "DROIDDECK_PR_NUMBER=${PR_NUMBER:-0}" >> "$GITHUB_ENV"
echo "Build: \`$label\`" >> "$GITHUB_STEP_SUMMARY"
- name: Cache the preloads
id: preloads
uses: actions/cache@v4
with:
path: |
app/src/main/assets/linuxfs
!app/src/main/assets/linuxfs/usr/local/lib/droiddeck-comet
!app/src/main/assets/linuxfs/usr/local/lib/droiddeck-xbox
!app/src/main/assets/linuxfs/i386/libblsession.so
!app/src/main/assets/linuxfs/i386/libfakeinput.so
!app/src/main/assets/linuxfs/i386/libfaultreport.so
!app/src/main/assets/linuxfs/libblaudit.so
!app/src/main/assets/linuxfs/libblfastpath.so
!app/src/main/assets/linuxfs/libblsession.so
!app/src/main/assets/linuxfs/libfakeinput.so
!app/src/main/assets/linuxfs/libssbs.so
!app/src/main/assets/linuxfs/usr/local/bin/droiddeck-clipboard
!app/src/main/assets/linuxfs/x86_64/libblsession.so
!app/src/main/assets/linuxfs/x86_64/libfakeinput.so
!app/src/main/assets/linuxfs/x86_64/libfaultreport.so
!app/src/main/assets/linuxfs/x86_64/libvulkan-thunk.so
!app/src/main/assets/linuxfs/x86_64/libwebkitinput.so
key: preloads-${{ env.PRELOAD_CACHE_VERSION }}-${{ hashFiles('app/src/main/cpp/adrenotools/src/hook/kgsl.h', 'tools/quest/*', 'tools/linuxfs/overlay/usr/local/bin/*', 'tools/linuxfs/overlay/usr/bin/**', 'tools/linuxfs/desktop/*', 'tools/gamescope/release.env', 'tools/wlroots/release.env', 'tools/linuxfs/uruntime.env', 'tools/linuxfs/licenses/*', 'tools/mangoapp/*', 'tools/msitools/**', 'tools/pipewire/**', 'tools/ci/install-ubuntu-packages.sh', 'tools/vr/**') }}
- name: Restore verified preload snapshots
id: component-preloads
env:
GH_TOKEN: ${{ github.token }}
FORCE_PRELOAD_REBUILD: ${{ inputs.rebuild_preloads || 'false' }}
COMPONENT_TRUSTED_BASE: ${{ needs.xbox.outputs.base-sha || github.sha }}
run: python3 tools/components/preloads.py restore
- name: Build changed ARM64 preloads
if: steps.component-preloads.outputs.arm64 != 'true'
run: |
bash tools/ci/install-ubuntu-packages.sh gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
bash tools/components/build-arm64-preloads.sh app/src/main/assets/linuxfs
- name: Prepare remaining Linux runtime assets
if: steps.preloads.outputs.cache-hit != 'true'
run: |
set -euo pipefail
bash tools/ci/install-ubuntu-packages.sh gcc-aarch64-linux-gnu g++-aarch64-linux-gnu libvulkan1 mesa-vulkan-drivers
D=app/src/main/assets/linuxfs
mkdir -p "$D"
VK_INCLUDE=$(ls -d "${ANDROID_NDK_LATEST_HOME:-${ANDROID_NDK_HOME:-$ANDROID_NDK_ROOT}}"/toolchains/llvm/prebuilt/*/sysroot/usr/include | head -1) \
tools/vr/build.sh --native "$D/usr/local/lib/droiddeck-vr"
for script in tools/linuxfs/overlay/usr/local/bin/droiddeck-* tools/linuxfs/overlay/usr/local/bin/steam-compatibility; do
install -Dm644 "$script" "$D/usr/local/bin/$(basename "$script")"
done
for f in tools/linuxfs/overlay/usr/bin/* tools/linuxfs/overlay/usr/bin/steamos-polkit-helpers/*; do
[ -f "$f" ] && install -Dm644 "$f" "$D/${f#tools/linuxfs/overlay/}"
done
if [ -f tools/gamescope/release.env ]; then
. tools/gamescope/release.env
curl -fsSL --retry 3 -o gamescope.tzst "https://github.com/${GAMESCOPE_REPO:-${GITHUB_REPOSITORY}}/releases/download/${GAMESCOPE_TAG}/gamescope.tzst"
echo "${GAMESCOPE_SHA256} gamescope.tzst" | sha256sum -c -
tar --use-compress-program=unzstd -xf gamescope.tzst -C "$D"
test -f "$D/usr/local/bin/gamescope"
rm gamescope.tzst
fi
. tools/linuxfs/uruntime.env
curl -fsSL --retry 3 -o uruntime "https://github.com/VHSgunzo/uruntime/releases/download/${URUNTIME_VERSION}/${URUNTIME_ASSET}"
echo "${URUNTIME_SHA256} uruntime" | sha256sum -c -
install -Dm644 uruntime "$D/usr/local/lib/droiddeck/uruntime"
install -Dm644 tools/linuxfs/licenses/uruntime-LICENSE "$D/usr/local/share/licenses/uruntime/LICENSE"
rm uruntime
M="$D/usr/local/lib/mangoapp"
mkdir -p "$M" mango-pkgs
grep -v '^#' tools/mangoapp/packages.txt | while read -r sum url; do
[ -n "$url" ] || continue
curl -fsSL --retry 3 -o mango-pkgs/pkg.tar.zst "$url"
echo "$sum mango-pkgs/pkg.tar.zst" | sha256sum -c -
tar --use-compress-program=unzstd -xf mango-pkgs/pkg.tar.zst -C mango-pkgs
rm mango-pkgs/pkg.tar.zst
done
install -m644 mango-pkgs/usr/bin/mangoapp "$M/mangoapp"
for so in libfmt.so.10 libspdlog.so.1.13 libglfw.so.3 libtraceevent.so.1; do
cp -L "mango-pkgs/usr/lib/$so" "$M/$so"
done
aarch64-linux-gnu-gcc -shared -fPIC -O2 -Wall -Wl,-soname,libtracefs.so.1 \
-o "$M/libtracefs.so.1" tools/mangoapp/libtracefs-shim.c
aarch64-linux-gnu-strip --strip-unneeded "$M/libtracefs.so.1"
install -Dm644 tools/mangoapp/mangoapp "$D/usr/local/bin/mangoapp"
rm -rf mango-pkgs
ls -l "$M"
mkdir -p pw-pkgs
grep -v '^#' tools/pipewire/packages.txt | while read -r sum url; do
[ -n "$url" ] || continue
curl -fsSL --retry 3 -o pw-pkgs/pkg.tar.zst "$url"
echo "$sum pw-pkgs/pkg.tar.zst" | sha256sum -c -
tar --use-compress-program=unzstd -xf pw-pkgs/pkg.tar.zst -C pw-pkgs
rm pw-pkgs/pkg.tar.zst
done
tools/pipewire/assemble.sh pw-pkgs "$D/usr/local/lib/droiddeck-pipewire.tzst"
rm -rf pw-pkgs
ls -l "$D/usr/local/lib/droiddeck-pipewire.tzst"
T="$D/usr/local/lib/droiddeck-msitools"
mkdir -p "$T" msi-pkgs
grep -v '^#' tools/msitools/packages.txt | while read -r sum url; do
[ -n "$url" ] || continue
curl -fsSL --retry 3 -o msi-pkgs/pkg.tar.xz "$url"
echo "$sum msi-pkgs/pkg.tar.xz" | sha256sum -c -
tar -xJf msi-pkgs/pkg.tar.xz -C msi-pkgs
rm msi-pkgs/pkg.tar.xz
done
install -m644 msi-pkgs/usr/bin/msiinfo "$T/msiinfo"
install -m644 msi-pkgs/usr/bin/cabextract "$T/cabextract"
install -m644 msi-pkgs/usr/lib/7zip/7z "$T/7z"
install -m644 msi-pkgs/usr/lib/7zip/7z.so "$T/7z.so"
for so in libmsi-1.0.so.0 libgsf-1.so.114 libgcab-1.0.so.0; do
cp -L "msi-pkgs/usr/lib/$so" "$T/$so"
done
for f in NOTICE GPL-2 GPL-3 LGPL-2.1 7zip-License 7zip-unRarLicense; do
install -Dm644 "tools/msitools/licenses/$f" "$D/usr/local/share/licenses/droiddeck-msitools/$f"
done
rm -rf msi-pkgs
ls -l "$T"
install -Dm644 tools/linuxfs/desktop/droiddeck-desktop "$D/usr/local/bin/droiddeck-desktop"
install -Dm644 tools/linuxfs/desktop/droiddeck-gpu "$D/usr/local/bin/droiddeck-gpu"
install -Dm644 tools/linuxfs/desktop/droiddeck-desktop-gpu "$D/usr/local/bin/droiddeck-desktop-gpu"
install -Dm644 tools/linuxfs/desktop/kwin_wayland_wrapper "$D/usr/local/bin/kwin_wayland_wrapper"
install -Dm644 tools/linuxfs/desktop/firefox-droiddeck.js "$D/usr/lib/firefox/defaults/pref/droiddeck.js"
ls -lR "$D"
- name: Fetch the Debian image the preloads build in
if: steps.component-preloads.outputs.x86 != 'true'
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
. tools/linuxfs/debian-image.env
image=public.ecr.aws/docker/library/debian:bullseye
if gh release download "$DEBIAN_IMAGE_TAG" -R "$GITHUB_REPOSITORY" -p "$DEBIAN_IMAGE_ASSET" -O /tmp/debian-image.tar.zst \
&& { [ -z "$DEBIAN_IMAGE_SHA256" ] || echo "$DEBIAN_IMAGE_SHA256 /tmp/debian-image.tar.zst" | sha256sum -c -; }; then
zstd -dc /tmp/debian-image.tar.zst | docker load
rm -f /tmp/debian-image.tar.zst
else
echo "::warning::the hosted Debian image could not be fetched; pulling from the registry instead"
for attempt in 1 2 3 4 5; do
docker pull "$image" && break
[ "$attempt" -lt 5 ] || { echo "::error::could not pull $image after $attempt attempts"; exit 1; }
echo "pull attempt $attempt failed (registry busy?); retrying in $((attempt * 30)) s"
sleep $((attempt * 30))
done
fi
docker image ls "$image"
- name: Build the x86 preloads FEX guests load (glibc 2.31)
if: steps.component-preloads.outputs.x86 != 'true'
run: |
set -euo pipefail
docker run --rm -v "$PWD:/src" -w /src public.ecr.aws/docker/library/debian:bullseye bash -c '
set -euo pipefail
install_compilers() {
apt-get -o Acquire::Check-Valid-Until=false update -qq &&
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends gcc g++ gcc-multilib g++-multilib binutils >/dev/null
}
printf "deb http://deb.debian.org/debian bullseye main\ndeb http://security.debian.org/debian-security bullseye-security main\n" > /etc/apt/sources.list
if ! install_compilers; then
printf "deb http://archive.debian.org/debian bullseye main\ndeb http://archive.debian.org/debian-security bullseye-security main\n" > /etc/apt/sources.list
install_compilers
fi
tools/linuxfs/build-x86-preloads.sh app/src/main/assets/linuxfs
'
sudo chown -R "$(id -u):$(id -g)" app/src/main/assets/linuxfs
ls -l app/src/main/assets/linuxfs/x86_64 app/src/main/assets/linuxfs/i386
- name: Bundle the droiddeck-esync packs
run: |
set -euo pipefail
if [ -f tools/droiddeck-esync/release.env ]; then
. tools/droiddeck-esync/release.env
release_url="https://github.com/${SYNC_BUNDLE_REPO}/releases/download/${SYNC_BUNDLE_TAG}"
curl -fsSL --retry 3 -o sync-bundle.tzst "$release_url/$SYNC_BUNDLE_ASSET"
echo "$SYNC_BUNDLE_SHA256 sync-bundle.tzst" | sha256sum -c -
mkdir -p app/src/main/assets/droiddeck-esync
tar --use-compress-program=unzstd -xf sync-bundle.tzst -C app/src/main/assets/droiddeck-esync
rm sync-bundle.tzst
curl -fsSL --retry 3 -o sync-index.json "$release_url/index.json"
revoked=$(python3 -c 'import json, sys; print(" ".join(p["id"] for p in json.load(open(sys.argv[1]))["packs"] if p.get("revoked") is True))' sync-index.json)
for id in $revoked; do
if [ -e "app/src/main/assets/droiddeck-esync/packs/$id.tzst" ]; then
echo "::warning::$SYNC_BUNDLE_ASSET carries revoked pack $id; it is left out of the APK (point tools/droiddeck-esync/release.env at a newer bundle)"
rm -f "app/src/main/assets/droiddeck-esync/packs/$id.tzst"
fi
done
rm sync-index.json
test -f app/src/main/assets/droiddeck-esync/index.json
test -f app/src/main/assets/droiddeck-esync/index.json.sig
ls -lR app/src/main/assets/droiddeck-esync
fi
- name: Cache PulseAudio 13.0 source
id: pa-src
uses: actions/cache@v4
with:
path: pa13
key: pulseaudio-src-13.0
- name: Fetch PulseAudio 13.0 source
if: steps.pa-src.outputs.cache-hit != 'true'
run: |
set -euo pipefail
curl -fsSL -o pa.tar.gz https://github.com/pulseaudio/pulseaudio/archive/refs/tags/v13.0.tar.gz
mkdir -p pa13 && tar -xzf pa.tar.gz -C pa13 --strip-components=1 && rm pa.tar.gz
- name: Fetch DirectAudio (tools/directaudio/release.env)
run: tools/directaudio/fetch.sh "$PWD" "$PWD/sink-out"
- name: Build the AAudio sink into the audio bundle
run: |
set -euo pipefail
export NDK="${ANDROID_NDK_LATEST_HOME:-${ANDROID_NDK_HOME:-$ANDROID_NDK_ROOT}}"
tools/aaudio-sink/build.sh "$PWD/pa13" "$PWD/sink-out"
rm -rf pa-bundle && mkdir -p pa-bundle
tar --use-compress-program=unzstd -xf app/src/main/assets/pulseaudio.tzst -C pa-bundle
test ! -f pa-bundle/modules/arm64/module-aaudio-sink.so
install -m755 sink-out/module-aaudio-sink.so pa-bundle/modules/arm64/module-aaudio-sink.so
install -m755 sink-out/module-directaudio-native-sink.so pa-bundle/modules/arm64/module-directaudio-native-sink.so
(cd pa-bundle && tar --use-compress-program='zstd -19' -cf ../pulseaudio.tzst .)
mkdir -p app/build/prepared-assets
mv pulseaudio.tzst app/build/prepared-assets/pulseaudio.tzst
tar --use-compress-program=unzstd -tf app/build/prepared-assets/pulseaudio.tzst | grep -c '\.so$'
rm -rf pa-bundle sink-out
- name: Cache proot
id: proot
uses: actions/cache@v4
with:
path: |
app/src/main/jniLibs/arm64-v8a/libproot.so
app/src/main/jniLibs/arm64-v8a/libproot-loader.so
key: proot-${{ env.NDK_VERSION }}-${{ hashFiles('tools/proot/**') }}
- name: Build proot
if: steps.proot.outputs.cache-hit != 'true'
run: |
set -euo pipefail
export NDK="${ANDROID_NDK_LATEST_HOME:-${ANDROID_NDK_HOME:-$ANDROID_NDK_ROOT}}"
tools/proot/build.sh app/src/main/jniLibs/arm64-v8a
- name: Check the preloads
run: |
set -euo pipefail
bash tools/linuxfs/check-preloads.sh app/src/main/assets/linuxfs
- name: Check Quest tracking IPC
run: |
set -euo pipefail
NDK="${ANDROID_NDK_LATEST_HOME:-${ANDROID_NDK_HOME:-$ANDROID_NDK_ROOT}}"
VK_INCLUDE=$(ls -d "$NDK"/toolchains/llvm/prebuilt/*/sysroot/usr/include | head -1)
mkdir -p build/vr/include
cp -R "$VK_INCLUDE/vulkan" build/vr/include/
if [ -d "$VK_INCLUDE/vk_video" ]; then cp -R "$VK_INCLUDE/vk_video" build/vr/include/; fi
g++ -std=c++17 -Wall -Wextra -Werror -pthread tools/vr/tests/test_xr_tracking.cpp -o /tmp/test-xr-tracking
/tmp/test-xr-tracking
g++ -std=c++17 -Wall -Wextra -Werror tools/vr/tests/test_xr_touch.cpp -o /tmp/test-xr-touch
/tmp/test-xr-touch
g++ -std=c++17 -Wall -Wextra -Werror tools/vr/tests/test_pad_controller_config.cpp -o /tmp/test-pad-controller-config
/tmp/test-pad-controller-config
g++ -std=c++17 -Wall -Wextra -Werror -pthread tools/vr/tests/test_xr_vsync.cpp -o /tmp/test-xr-vsync
/tmp/test-xr-vsync
g++ -std=c++17 -Wall -Wextra -Werror -pthread tools/vr/tests/test_xr_frames.cpp -o /tmp/test-xr-frames
/tmp/test-xr-frames
cc -Itools/vr/third_party/wayland -c tools/vr/src/wayland_client_stub.c -o /tmp/test-wayland-stub.o
cc -Itools/vr/third_party/wayland -c app/src/main/cpp/waylandcomp/generated/xdg-shell-protocol.c -o /tmp/test-xdg-shell.o
g++ -std=c++17 -Wall -Wextra -Werror -Wno-unused-parameter -Wno-missing-field-initializers -pthread -Ibuild/vr/include \
-Itools/vr/third_party/wayland -Iapp/src/main/cpp/waylandcomp/generated \
tools/vr/tests/test_presenter_shutdown.cpp tools/vr/src/presenter.cpp /tmp/test-wayland-stub.o /tmp/test-xdg-shell.o \
-ldl -o /tmp/test-presenter-shutdown
timeout 10 /tmp/test-presenter-shutdown
- name: Rust toolchain for the store engines (aarch64-linux-android)
id: rust
run: |
@@ -561,28 +470,20 @@ jobs:
restore-keys: |
cxx-${{ env.NDK_VERSION }}-
- uses: actions/download-artifact@v4
with:
name: prepared-components
path: app/build
- name: Verify and unpack prepared components
env:
SOURCE_TREE: ${{ needs.source.outputs.tree-sha }}
run: python3 tools/components/prepare.py unpack --tree "$SOURCE_TREE"
- uses: actions/download-artifact@v4
with:
name: xbox-arm64
path: app/src/main/assets/linuxfs/usr/local/lib/droiddeck-xbox
- name: Cache GOG Galaxy companion
id: comet
uses: actions/cache@v4
with:
path: app/src/main/assets/linuxfs/usr/local/lib/droiddeck-comet
key: comet-${{ runner.os }}-${{ runner.arch }}-rust1.90.0-${{ hashFiles('tools/comet/**') }}
- name: Build GOG Galaxy companion
if: steps.comet.outputs.cache-hit != 'true'
run: |
if ! command -v aarch64-linux-gnu-gcc >/dev/null; then
bash tools/ci/install-ubuntu-packages.sh gcc-aarch64-linux-gnu
fi
rustup toolchain install 1.90.0 --profile minimal
rustup target add --toolchain 1.90.0 aarch64-unknown-linux-gnu
RUSTUP_TOOLCHAIN=1.90.0 bash tools/comet/build.sh
- name: Build the add-on packs
run: bash tools/packs/build.sh app/build/packs
@@ -593,7 +494,6 @@ jobs:
SGDB_API_KEY: ${{ secrets.SGDB_API_KEY }}
run: |
set -o pipefail
python3 tools/quest/build-driver.py "$ANDROID_HOME/ndk/$NDK_VERSION"
python3 tools/release/runtime_inputs.py record
./gradlew assembleRelease --console=plain -PndkVersion="$NDK_VERSION" 2>&1 | tee /tmp/build.log
python3 tools/release/check_session_assets.py app/build/outputs/apk/release/app-release.apk
@@ -701,87 +601,11 @@ jobs:
TEST_DIAGNOSTICS_URL: ${{ steps.unit-test-diagnostics.outputs.artifact-url }}
APK_OUTCOME: ${{ steps.apk-upload.outcome }}
SAME_REPO: ${{ github.event.pull_request.head.repo.full_name == github.repository }}
run: |
set -uo pipefail
MARK="<!-- droiddeck-pr-build -->"
{
echo "$MARK"
echo "## Build of this pull request"
echo
[ -f /tmp/pr-merge.md ] && cat /tmp/pr-merge.md
echo
case "$BUILD_OUTCOME:$UNIT_TEST_OUTCOME:$APK_OUTCOME" in
success:success:success)
echo "### Build passed"
echo
echo "The \`droiddeck-apk\` artifact is [on this run]($RUN_URL), signed with the public test key."
echo
if [ "$SAME_REPO" = true ]; then
echo "After the parallel regression tests pass, a release-signed tester build that installs over Stable or Preview will be published and will appear in DroidDeck's **Test builds** list. Label the PR \`no-test-build\` to skip that."
else
echo "**Maintainer:** add the \`publish-test\` label to publish a release-signed tester build that installs over Stable or Preview and appears in DroidDeck's **Test builds** list. The label is removed automatically after publishing."
fi
echo
echo "Fallback: [open **PR Release Publish**]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/workflows/pr-release-publish.yml) on \`main\` and enter PR \`$PR\`."
;;
success:success:*)
echo "### APK publication incomplete"
echo
echo "Compilation and unit tests passed, but APK publication did not complete. See [the run]($RUN_URL)."
;;
success:failure:*)
echo "### ERROR: Unit tests failed"
echo
echo "The APK compiled, but unit tests failed. No installable artifact was published."
echo
if [ -n "$TEST_DIAGNOSTICS_URL" ]; then
echo "The [unit-test-results]($TEST_DIAGNOSTICS_URL) artifact contains the test log and any generated reports."
else
echo "The test diagnostics upload did not complete. See [the test log]($RUN_URL)."
fi
;;
success:*)
echo "### Build incomplete"
echo
echo "The APK compiled, but unit tests did not complete successfully. See [the run]($RUN_URL)."
;;
failure:*)
echo "### ERROR: Build failed"
echo
echo "The compiler's errors ([full log]($RUN_URL)):"
echo
echo '```'
{ grep -E '^e: |^w: .*error|error:|CMake Error|fatal error|ninja: error|FAILED|What went wrong|Execution failed for task|Unresolved reference|Type mismatch|Unresolved' /tmp/build.log 2>/dev/null \
| sed -e 's#file:///home/runner/work/[^/]*/[^/]*/##' -e 's#/home/runner/work/[^/]*/[^/]*/##' \
| grep -v '^w: ' | awk '!seen[$0]++' | head -80; } || true
echo '```'
echo
echo "<details><summary>What went wrong (Gradle)</summary>"
echo
echo '```'
awk '/^\* What went wrong/{p=1} /^\* Try:/{p=0} p{print}' /tmp/build.log 2>/dev/null | head -60
echo '```'
echo "</details>"
;;
skipped:*|cancelled:*)
echo "### Not built"
echo
echo "The build did not run (see above, or [the run]($RUN_URL))."
;;
esac
echo
} > /tmp/pr-report.md
cat /tmp/pr-report.md >> "$GITHUB_STEP_SUMMARY"
ID=$(gh api "repos/${GITHUB_REPOSITORY}/issues/${PR}/comments" --paginate --jq ".[] | select(.body | startswith(\"$MARK\")) | .id" | head -1 || true)
if [ -n "$ID" ]; then
gh api -X PATCH "repos/${GITHUB_REPOSITORY}/issues/comments/${ID}" -F body=@/tmp/pr-report.md >/dev/null
else
gh api -X POST "repos/${GITHUB_REPOSITORY}/issues/${PR}/comments" -F body=@/tmp/pr-report.md >/dev/null
fi
run: bash tools/release/report-build.sh
python-tests:
name: Python and native regression tests
needs: xbox
needs: source
if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }}
runs-on: ubuntu-24.04
timeout-minutes: 15
@@ -794,7 +618,7 @@ jobs:
- name: Merge the pinned APK base
if: github.event_name == 'pull_request'
env:
BUILD_BASE_SHA: ${{ needs.xbox.outputs.base-sha }}
BUILD_BASE_SHA: ${{ needs.source.outputs.base-sha }}
run: |
set -euo pipefail
git fetch --no-tags origin "$BUILD_BASE_SHA"
@@ -803,11 +627,40 @@ jobs:
git merge --no-ff --no-edit FETCH_HEAD
- name: Verify backend and test source match
env:
XBOX_SOURCE_TREE: ${{ needs.xbox.outputs.tree-sha }}
XBOX_SOURCE_TREE: ${{ needs.source.outputs.tree-sha }}
run: test "$(git rev-parse 'HEAD^{tree}')" = "$XBOX_SOURCE_TREE"
- name: Install Python test dependencies
timeout-minutes: 7
run: bash tools/ci/install-ubuntu-packages.sh python3-yaml gcc-aarch64-linux-gnu libc6-dev-arm64-cross qemu-user libvulkan-dev mesa-vulkan-drivers
- name: Resolve the pinned Android NDK
run: bash tools/ci/resolve-ndk.sh
- name: Check Quest tracking IPC
run: |
set -euo pipefail
NDK="${ANDROID_NDK_LATEST_HOME:-${ANDROID_NDK_HOME:-$ANDROID_NDK_ROOT}}"
VK_INCLUDE=$(ls -d "$NDK"/toolchains/llvm/prebuilt/*/sysroot/usr/include | head -1)
mkdir -p build/vr/include
cp -R "$VK_INCLUDE/vulkan" build/vr/include/
if [ -d "$VK_INCLUDE/vk_video" ]; then cp -R "$VK_INCLUDE/vk_video" build/vr/include/; fi
g++ -std=c++17 -Wall -Wextra -Werror -pthread tools/vr/tests/test_xr_tracking.cpp -o /tmp/test-xr-tracking
/tmp/test-xr-tracking
g++ -std=c++17 -Wall -Wextra -Werror tools/vr/tests/test_xr_touch.cpp -o /tmp/test-xr-touch
/tmp/test-xr-touch
g++ -std=c++17 -Wall -Wextra -Werror tools/vr/tests/test_pad_controller_config.cpp -o /tmp/test-pad-controller-config
/tmp/test-pad-controller-config
g++ -std=c++17 -Wall -Wextra -Werror -pthread tools/vr/tests/test_xr_vsync.cpp -o /tmp/test-xr-vsync
/tmp/test-xr-vsync
g++ -std=c++17 -Wall -Wextra -Werror -pthread tools/vr/tests/test_xr_frames.cpp -o /tmp/test-xr-frames
/tmp/test-xr-frames
cc -Itools/vr/third_party/wayland -c tools/vr/src/wayland_client_stub.c -o /tmp/test-wayland-stub.o
cc -Itools/vr/third_party/wayland -c app/src/main/cpp/waylandcomp/generated/xdg-shell-protocol.c -o /tmp/test-xdg-shell.o
g++ -std=c++17 -Wall -Wextra -Werror -Wno-unused-parameter -Wno-missing-field-initializers -pthread -Ibuild/vr/include \
-Itools/vr/third_party/wayland -Iapp/src/main/cpp/waylandcomp/generated \
tools/vr/tests/test_presenter_shutdown.cpp tools/vr/src/presenter.cpp /tmp/test-wayland-stub.o /tmp/test-xdg-shell.o \
-ldl -o /tmp/test-presenter-shutdown
timeout 10 /tmp/test-presenter-shutdown
- name: Steam import and game environment tests
run: python3 -m unittest discover -s tools/tests -p 'test_*.py'
@@ -857,6 +710,10 @@ jobs:
CI_REPO: Droid-Deck/DroidDeck-CI
KEEP: 10
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.sha }}
persist-credentials: false
- uses: actions/download-artifact@v4
with:
pattern: droiddeck-signed-*
@@ -874,72 +731,13 @@ jobs:
- name: Describe the change
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
pr=$(gh api "repos/$GITHUB_REPOSITORY/commits/$GITHUB_SHA/pulls" \
--jq '[.[] | select(.merged_at != null and .base.ref == "main")] | first // empty')
if [ -n "$pr" ]; then
number=$(jq -r .number <<<"$pr")
title=$(jq -r .title <<<"$pr")
merged=$(jq -r '.merged_at[:10]' <<<"$pr")
summary=$(jq -r '.body // ""' <<<"$pr" | tr -d '\r' | { grep -v '^🤖' || true; } |
awk '!p && /^#/ { next } NF { p = 1; print; next } p { exit }' | head -n 6 | cut -c1-400)
{
echo "**Merged to main** - [PR #$number]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/pull/$number): $title (merged $merged)"
if [ -n "$summary" ]; then
echo
echo "$summary"
fi
} > notes-head.md
else
subject=$(gh api "repos/$GITHUB_REPOSITORY/commits/$GITHUB_SHA" --jq '.commit.message | split("\n")[0]')
echo "**Pushed to main** without a PR - $subject" > notes-head.md
fi
cat notes-head.md
run: bash tools/release/describe-build.sh
- name: Publish to DroidDeck-CI
id: publish
env:
GH_TOKEN: ${{ steps.ci-token.outputs.token }}
run: |
set -euo pipefail
tag=main-${GITHUB_SHA::7}
cat signed/SHA256SUMS-*.txt > signed/SHA256SUMS.txt
rm signed/SHA256SUMS-*.txt
notes="$(cat notes-head.md)
Signed build of $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/commit/$GITHUB_SHA - in main, not yet in a DroidDeck release; for testing.
Commit: $GITHUB_SHA
VersionCode: $(sed -n 's/^ *versionCode \([0-9][0-9]*\)$/\1/p' app/build.gradle)
Each apk installs over a DroidDeck release and keeps its data. \`standard\` is the normal app; the others are the same app under another package name, installed beside it.
SHA-256:
\`\`\`
$(cat signed/SHA256SUMS.txt)
\`\`\`"
gh release delete "$tag" -R "$CI_REPO" --yes --cleanup-tag 2>/dev/null || true
gh release create "$tag" -R "$CI_REPO" --target main --prerelease \
--title "main ${GITHUB_SHA::7}" --notes "$notes" signed/*
gh release list -R "$CI_REPO" -L 200 --json tagName,publishedAt \
--jq '[.[] | select(.tagName | startswith("main-"))] | sort_by(.publishedAt) | reverse | .['"$KEEP"':][] | .tagName' |
while IFS= read -r old; do
if [ "$old" = "$tag" ]; then
continue
fi
if ! gh release delete "$old" -R "$CI_REPO" --yes --cleanup-tag 2>/dev/null; then
if gh release view "$old" -R "$CI_REPO" >/dev/null 2>&1; then
echo "::error::Could not delete old release $old"
exit 1
fi
fi
done
published=$(gh release view "$tag" -R "$CI_REPO" --json tagName,assets)
jq -e --arg tag "$tag" '.tagName == $tag and (.assets | length) > 0' <<< "$published" >/dev/null
url="$GITHUB_SERVER_URL/$CI_REPO/releases/tag/$tag"
apk=$(cd signed && ls DroidDeck-*.apk | awk 'length(a) == 0 || length($0) < length(a) { a = $0 } END { print a }')
echo "release=$url" >> "$GITHUB_OUTPUT"
echo "apk=$GITHUB_SERVER_URL/$CI_REPO/releases/download/$tag/$apk" >> "$GITHUB_OUTPUT"
echo "Published $url"
run: bash tools/release/publish-ci.sh
- name: Post the Preview build to Discord
env:
@@ -947,26 +745,8 @@ jobs:
GH_TOKEN: ${{ github.token }}
RELEASE: ${{ steps.publish.outputs.release }}
APK: ${{ steps.publish.outputs.apk }}
run: |
set -euo pipefail
if [ -z "$WEBHOOK" ]; then
echo "::notice::No DISCORD_BUILDS_WEBHOOK secret; nothing posted"
exit 0
fi
commit=$(gh api "repos/$GITHUB_REPOSITORY/commits/$GITHUB_SHA")
subject=$(jq -r '.commit.message | split("\n")[0]' <<<"$commit")
title=$(jq -r '.commit.message | split("\n") | map(select(length > 0)) | .[1] // ""' <<<"$commit")
pr=$(sed -n 's/^Merge pull request #\([0-9]*\).*/\1/p' <<<"$subject")
if [ -n "$pr" ]; then
what="[#$pr]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/pull/$pr): ${title:-$subject}"
else
what="[\`${GITHUB_SHA::7}\`]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/commit/$GITHUB_SHA): $subject"
fi
get=$(printf 'Download: [standard APK](<%s>) · [all variants](<%s>)' "$APK" "$RELEASE")
content=$(printf '**New signed Preview build** `main-%s`\n%s\nIn the app: **Updates → Preview**\n%s' "${GITHUB_SHA::7}" "$what" "$get")
jq -n --arg content "$content" '{content: $content, allowed_mentions: {parse: []}}' |
curl -fsS -H 'Content-Type: application/json' -d @- "$WEBHOOK" -o /dev/null
echo "Posted: $content"
run: bash tools/release/announce-preview.sh
catalog:
needs: announce