mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-12 19:41:10 +00:00
+13








83eda433c8
* Add VITURE glasses support: hardware control, 3D, head tracking Glasses get their own controller over the MCU (permission, 120 Hz default, brightness, volume, sunblock, 2D/3D) and the game moves onto them when plugged in. The IMU drives Cardboard head tracking and the SteamVR driver. 3D falls back to one eye's view on phones that can't send the 3840x1080 signal. Options open from a glasses button in game and on the Steam page. * Drop the glasses settings like Steam's: Play-style button, Settings lip, tabs * Hang the glasses Settings lip over the top of the button * Space glasses MCU commands so the IMU rate sticks, fit fixed resolutions to the glasses * Keep Steam alive with SteamVR registered, run SteamVR at the glasses' rate, keep the glasses lip on top * Fit VR games on the glasses: full view, seated origin at the head, pad hands reach with the d-pad * Let the pad hands reach a metre out so buttons with deep colliders press * Show every VR frame in order at 120 fps: wait for SteamVR's GPU work, present only finished copies, skip the grace on idle shared counters * Glasses: SteamVR starts and stops with them, closer view with a field of view setting, Recenter on the pad * Glasses: SteamVR field of view defaults to 90° * VR pad hands: sticks move and aim the hands, left stick click switches the sticks to the game, d-pad pulls a held hand * Glasses: Play SteamVR beside the glasses button instead of starting SteamVR with them, pad hands drawn as Index controllers and off while other VR controllers are connected, glasses sheet tabs sized like Steam's * Track glasses mode, refresh-aware glasses window, align Play SteamVR with Play Steam * Fix 3D on the glasses: full-res SteamVR presenter, revert VR 3D at session end, coalesce vsync ticks, no session restart after end * Address review: glasses output after installs and session end, VR revert retry, display fallback, presenter swapchain retry, agent hex checks, drawer bumpers, glasses translations, docs * Harden VITURE stereo negotiation and output refresh tracking * Localize stereo recovery feedback and record Astra reconnect tests * Improve test failure diagnostics and refresh Astra firmware findings * Handle VITURE docks separately and reject truncated USB frames * Glasses: remember a 3D fallback per model, docks as display only, VR timing fixes - A 3D attempt that ends at the 640x480 fallback is remembered for that glasses model, so Play SteamVR no longer blacks out the glasses after every restart; Retry clears it and "3D for VR games" stays reachable - A VITURE Mobile Dock on its own no longer counts as glasses; through a dock Play SteamVR is hidden and the sheet shows the output and why there is no control - HDR is probed on the glasses when the session starts there - Glasses frames with a mismatched checksum are marked in the status frame log - SteamVR layer and driver vblank counters stay monotonic across refresh changes; one-eye render scale applies from activation and on mono/3D switches - Compositor binds announce the refresh clients were last told; the presenter retries a failed first swapchain - Dead strings and helpers removed; docs updated * Glasses: drop USB frames with a bad checksum * Glasses: count frames dropped for a bad checksum * Feat/viture xr (#907) * Add RayNeo Air and GT support to the XR glasses pipeline * Add RayNeo Air and GT support to the XR glasses pipeline * Checkpoint glasses tracking, calibration, and SteamVR launch integration * Checkpoint glasses tracking, calibration, and SteamVR launch integration * Add XREAL 1S support and correct GT and Pro 2 tracking * Add XREAL 1S support and correct GT and Pro 2 tracking * Fix mirrored glasses tracking and recover SteamVR after device loss * Fix mirrored glasses tracking and recover SteamVR after device loss * Document confirmed XREAL 1S brightness control * Document confirmed XREAL 1S brightness control * Fix SteamVR glasses defaults and rendering controls * Fix SteamVR glasses defaults and rendering controls * chore: keep only the 3 newest session log folders (#852) * chore: add agent comment rules (#748) Co-authored-by: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> * Fix SteamVR polling, VITURE checksums, and fence tracking Keep manual SteamVR launches polled, discard corrupt VITURE packets, and retire Vulkan fences out of order without blocking submissions at a fixed capacity. Preserve the contiguous completion watermark used by the presenter. Fix optional Windows launcher configuration and audited helper staging. Add Android, Windows, and native tracker regression coverage. * Fix SteamVR polling, VITURE checksums, and fence tracking Keep manual SteamVR launches polled, discard corrupt VITURE packets, and retire Vulkan fences out of order without blocking submissions at a fixed capacity. Preserve the contiguous completion watermark used by the presenter. Fix optional Windows launcher configuration and audited helper staging. Add Android, Windows, and native tracker regression coverage. * fix: wireless ADB page names the child-process switch the way Android shows it (#848) * l10n: refresh stale and untranslated strings in es, fr, ko, ru (#850) * l10n: refresh stale and untranslated strings in es, fr, ko, ru - mode_added_art_hint: now mentions Steam then SteamGridDB (all four locales). - wincomp_needs_installer / wincomp_installers_note: es and ko still described the old "coming soon" behaviour; align with the current English text. - summary_remove_game: was still English in all four locales. Machine-assisted translation; native-speaker review is welcome. * l10n(es): say registro, not grabación, for the missing installer record --------- Co-authored-by: ovurrsl <ovurrsl@users.noreply.github.com> * fix: a failed cloud save recovery no longer ends the app (#846) The cloud-recovery thread started at every app start had nothing to catch an error, so one went to CrashHandler, which halts the process with status 2. In unit tests the thread can outlive the test that started it, fail on the torn-down preferences, and take the whole test run down. The error is now logged and the thread ends. Co-authored-by: Kirk <12691869+KirkPatrickJunsay@users.noreply.github.com> * fix: an interrupted Linux runtime download resumes instead of starting again (#847) * fix: an interrupted Linux runtime download resumes instead of starting again installOnce() asked the downloader to resume but always deleted the archive in its finally block, so the next attempt began at zero. The archive is now named after the release version and checksum, a failed download keeps what it fetched, and archives of other releases (and the old fixed name) are removed. A checksum or extraction failure still deletes the file. * test: Downloader resumes a cut-off download from the bytes it kept Starts a socket server that promises the whole file, hangs up part way and then answers a Range request, and checks that the second downloadFile call resumes with the right offset and ends with identical bytes. * fix: a resumed download discards a 206 that does not start where the kept bytes end Downloader appended every 206 response to the partial file without looking at Content-Range. A server or proxy that answers a Range request with a different offset would leave a corrupt file. Check that the range starts at the kept length; otherwise delete the partial file and start again without a Range header. Adds a test with a server that answers the Range request from offset 0. --------- Co-authored-by: ovurrsl <ovurrsl@users.noreply.github.com> * l10n: German, French, Spanish, Italian, Portuguese and Russian for the zRAM, reset-settings and store free-space strings (#849) * l10n: German, French, Spanish, Italian, Portuguese and Russian for the zRAM, reset-settings and store free-space strings Adds the strings values-de, values-it and values-pt were missing (14 each) and the six that values-fr, values-es and values-ru were missing. Machine-assisted drafts that follow each file's existing register and terms; native review is welcome. * l10n: correct missing active swap translations --------- Co-authored-by: ovurrsl <ovurrsl@users.noreply.github.com> Co-authored-by: xXJsonDeruloXx <danielhimebauch@gmail.com> * fix: droiddeckctl reaches variant-package builds (recover #842) (#854) fix: droiddeckctl reaches builds shipped under a variant package The provider authority and the start Activity's component were fixed to com.droiddeck.launcher, so droiddeckctl could not talk to the antutu, ludashi or pubg builds from tools/release/variants.txt. DROIDDECK_PACKAGE now picks the package; the Activity's class name stays the same in every variant. Co-authored-by: angelrenefm <17342133+angelrenefm@users.noreply.github.com> * feat: droiddeckctl reports on-screen pacing (recover #843) (#855) feat: droiddeckctl pacing reports the frames that reached the screen Reads SurfaceFlinger's timestats for the game's layer (or the session's SurfaceView) over a capture: frames on screen, fps on screen, the present-to-present histogram and how many intervals were off the usual cadence. Records split by a refresh-rate change are summed. A game's own fps counter counts frames handed over, so it can't show dropped or doubled frames. Co-authored-by: angelrenefm <17342133+angelrenefm@users.noreply.github.com> * fix: Steam can restart more than twice in a session (#754) * fix: Steam can restart more than twice in a session Every exit 42 counted toward the cap that stops an update which will not install, and the count was never cleared, so the third restart of a session ended it however far apart they were. A client that ran for a minute before asking is not that loop: the count starts again. * fix: measure the client's run time as it exits The time spent collecting logs and checking FEX after the client exits counted toward its run, so a slow cleanup could make a quick exit 42 look like a long one and clear the count. --------- Co-authored-by: Kirk <12691869+KirkPatrickJunsay@users.noreply.github.com> Co-authored-by: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> * fix: the front end keeps its page when the Fold is folded or unfolded (#832) * fix: the front end keeps its page when the Fold is folded or unfolded MainActivity, FileManagerActivity, FilePickerActivity and StoreLoginActivity did not declare the screen-size configuration changes, so folding or unfolding a foldable recreated them and dropped their in-memory state. They now handle orientation, screenSize, screenLayout, smallestScreenSize and density themselves, like SessionActivity already does. uiMode is left out on purpose. * fix: store login keeps rebuilding its toolbar when the display density changes StoreLoginActivity builds its toolbar in code with dp-to-pixel values taken at creation time and has no onConfigurationChanged. It no longer declares density in configChanges, so a density change recreates it as before this PR. Size and layout changes are still handled in place. --------- Co-authored-by: ovurrsl <ovurrsl@users.noreply.github.com> * Release pointer buttons without replaying stale motion (#803) * Fix cursor warping on native touch down * Preserve input cleanup without replaying stale pointer motion --------- Co-authored-by: siyuan.qsy <siyuan.qsy@alibaba-inc.com> * l10n: translate the strings added since #284 into zh-CN, zh-HK, zh-TW and ja (#539) * l10n: translate the 184 strings added since #284 (zh-CN/HK/TW, ja) The stores, cloud saves, Windows components, Steam touch, desktop removal and background-downloads strings landed after #284's translation snapshot, so every one of them fell back to English. Translate all 184 into Simplified Chinese, Traditional Chinese (Taiwan and Hong Kong) and Japanese, keeping each locale file's own vocabulary (工作階段/會話, 解除安裝/卸載, 元件/組件, 設定). Also fix four Hong Kong strings that kept the simplified 着色器. * l10n(zh): fix stale and ambiguous strings found in review Severe: summary_remove_game was untranslated English in all three files; mode_added_art_hint, wincomp_installers_note and wincomp_needs_installer still described the pre-#480/#492 English. Also: distinguish the Setup wizard from Android system settings in zh-HK/zh-TW (初始設定 / 系統設定 / Android 設定), unify library as 收藏庫, component as 組件 in zh-HK, session as 會話 in zh-HK, account as 帳戶 in zh-HK, Trending as 趋势/趨勢, Forget as 清除, reword the re-applied-at-launch, action-failed and already-in-Games strings, and play as 執行/開啟遊戲 in zh-TW/zh-HK. * l10n(ja): fix stale and ambiguous strings found in review --------- Co-authored-by: Dantalian <Dantalian-dev@users.noreply.github.com> Co-authored-by: xXJsonDeruloXx <danielhimebauch@gmail.com> * Rebuild and verify Windows VR runtime components * Rebuild and verify Windows VR runtime components * Add top, bottom, and centered picture positions for Steam sessions (#871) * fix: dependency scan ignores folders above the game folder (#859) * DroidDeck-Proton: get releases from the Droid-Deck org repo DroidDeck-Proton is now built and released on Droid-Deck/DroidDeck-Proton (11-9.1 is Latest there), so the Components page, the update check and the guest installer read releases from the org repo instead of The412Banner's. * feat: low-latency controller and touch toggles in the session drawer (#808) * fix: SteamGridDB lookups no longer stick as "no match" after a failure; add a search to the art chooser (#876) * feat: Steam home wall fills from the signed-in library instead of store picks (#878) * test: cover GPU family and support classification (#880) * Fix local builds on Linux failing after the first Comet build (#866) * feat: sort the store library and installed tabs, and switch between card and list views (#798) * feat: three-finger tap toggles Steam's on-screen keyboard in game mode (#782) * fix: keep Quest-patched Turnip as the auto default (#853) * Fix deploy_local.sh and droiddeckctl not finding a phone connected over wireless debugging (#864) * fix: keep proot sessions alive when the kernel loses fork events (#457) * fix(quest): correct desktop and Steam display corruption across sessions (#733) fix(quest): stabilize desktop and Steam layer presentation Keep Quest SDR scenes and the black backdrop off the Android Vulkan swapchain regardless of session order or HDR preferences. Only report the first frame after client content is submitted. Add backdrop lifecycle and first-frame regression coverage. Record the user's successful Quest 3 Test 2 result. Co-authored-by: xXJsonDeruloXx <danielhimebauch@gmail.com> * fix: keep SteamVR syscall hooks and report Steam crashes (#905) * fix: preserve SteamVR syscall hooks * fix: report Steam crashes through gamescope * fix: honor glasses pacing and resolution in SteamVR launches --------- Co-authored-by: connor g <connorsspam1992@gmail.com> Co-authored-by: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> Co-authored-by: Yassine Gherbi <y.gherbi.dev@gmail.com> Co-authored-by: Resul Övür <92828257+ovurrsl@users.noreply.github.com> Co-authored-by: ovurrsl <ovurrsl@users.noreply.github.com> Co-authored-by: Kirk <kirtoy.junsay@gmail.com> Co-authored-by: Kirk <12691869+KirkPatrickJunsay@users.noreply.github.com> Co-authored-by: xXJsonDeruloXx <danielhimebauch@gmail.com> Co-authored-by: angelrenefm <17342133+angelrenefm@users.noreply.github.com> Co-authored-by: asdf17128 <asdf17128@gmail.com> Co-authored-by: siyuan.qsy <siyuan.qsy@alibaba-inc.com> Co-authored-by: Dantalian <wsu091227@qq.com> Co-authored-by: Dantalian <Dantalian-dev@users.noreply.github.com> Co-authored-by: The412Banner <the412banner@users.noreply.github.com> Co-authored-by: The412Banner <205237651+The412Banner@users.noreply.github.com> Co-authored-by: FX1922 <120828496+FX192@users.noreply.github.com> Co-authored-by: Muaz Kadan <32595066+muazkadan@users.noreply.github.com> Co-authored-by: kingokksa <47888451+kingokksa@users.noreply.github.com> Co-authored-by: Jamie Schenker <jam1esch3nk3r@gmail.com> Co-authored-by: Jagger15 <jagger2333@live.com> * Glasses: keep a pending 2D restore and the SteamVR recovery offer, show Play SteamVR only with a picture * Translate the glasses calibration, XREAL and SteamVR recovery strings * SteamVR recovery: give a finished teardown time to collect logs before restarting * Credit the projects the XR glasses support builds on * Joy-Cons: SteamVR hands from their gyro while SteamVR runs, one combined pad otherwise * Headsets: settings badge on Play SteamVR instead of a top-right button, both shown only once connected * Hand tracking: camera-tracked Joy-Cons and bare hands in SteamVR, drawn as white gloves - Joy-Con movement from the accelerometer (report reordering, per-Joy-Con calibration, zero-velocity updates) - MediaPipe pose and hand landmarks from the device camera, placement check, per-hand Kalman filter with the Joy-Con - No controller connected: bare camera hands with Quest-style gestures, 1€ filtered, rendered as a white glove model built from a distance field - SteamVR safe mode cleared before each start so one crash no longer blocks the driver --------- Co-authored-by: MaxsTechReview <6025299+maxjivi05@users.noreply.github.com> Co-authored-by: informalTechCode <informaltechyt@gmail.com> Co-authored-by: connor g <connorsspam1992@gmail.com> Co-authored-by: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> Co-authored-by: Yassine Gherbi <y.gherbi.dev@gmail.com> Co-authored-by: Resul Övür <92828257+ovurrsl@users.noreply.github.com> Co-authored-by: ovurrsl <ovurrsl@users.noreply.github.com> Co-authored-by: Kirk <kirtoy.junsay@gmail.com> Co-authored-by: Kirk <12691869+KirkPatrickJunsay@users.noreply.github.com> Co-authored-by: xXJsonDeruloXx <danielhimebauch@gmail.com> Co-authored-by: angelrenefm <17342133+angelrenefm@users.noreply.github.com> Co-authored-by: asdf17128 <asdf17128@gmail.com> Co-authored-by: siyuan.qsy <siyuan.qsy@alibaba-inc.com> Co-authored-by: Dantalian <wsu091227@qq.com> Co-authored-by: Dantalian <Dantalian-dev@users.noreply.github.com> Co-authored-by: The412Banner <the412banner@users.noreply.github.com> Co-authored-by: The412Banner <205237651+The412Banner@users.noreply.github.com> Co-authored-by: FX1922 <120828496+FX192@users.noreply.github.com> Co-authored-by: Muaz Kadan <32595066+muazkadan@users.noreply.github.com> Co-authored-by: kingokksa <47888451+kingokksa@users.noreply.github.com> Co-authored-by: Jamie Schenker <jam1esch3nk3r@gmail.com> Co-authored-by: Jagger15 <jagger2333@live.com>
795 lines
38 KiB
YAML
795 lines
38 KiB
YAML
name: Build APK
|
|
run-name: ${{ github.event_name == 'pull_request' && format('building pr {0} on {1}', github.event.pull_request.number, github.event.pull_request.head.sha) || format('building {0} on {1}', github.ref_name, github.sha) }}
|
|
|
|
on:
|
|
push:
|
|
branches: [ main ]
|
|
paths-ignore:
|
|
- '**/*.md'
|
|
- 'LICENSE'
|
|
- '.gitignore'
|
|
pull_request:
|
|
branches: [ main ]
|
|
types: [ opened, synchronize, reopened, ready_for_review ]
|
|
paths-ignore:
|
|
- '**/*.md'
|
|
- 'LICENSE'
|
|
- '.gitignore'
|
|
workflow_dispatch:
|
|
|
|
permissions:
|
|
contents: read
|
|
pull-requests: write
|
|
|
|
concurrency:
|
|
group: build-${{ github.event.pull_request.number || github.run_id }}
|
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
|
|
env:
|
|
PRELOAD_CACHE_VERSION: v3
|
|
|
|
jobs:
|
|
build:
|
|
name: Build APK
|
|
if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }}
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 45
|
|
outputs:
|
|
variants: ${{ steps.variants.outputs.list }}
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
|
fetch-depth: 0
|
|
|
|
- name: Check co-authors
|
|
if: github.event_name == 'pull_request'
|
|
run: |
|
|
set -euo pipefail
|
|
git fetch --no-tags origin main
|
|
base=$(git merge-base origin/main HEAD)
|
|
messages=$(git log --format=%B "$base..HEAD")
|
|
messages+=$'\n'"$(jq -r '.pull_request.title, (.pull_request.body // "")' "$GITHUB_EVENT_PATH")"
|
|
if grep -Eiq '^[[:space:]]*Co-authored-by:.*(Claude|Anthropic|OpenAI|ChatGPT|GPT|Copilot|Cursor|Gemini|Codex|Devin|Windsurf|Codeium|Amazon Q|Tabnine|Cody|\[bot\])' <<<"$messages"; then
|
|
echo "::error::AI and bot identities cannot be co-authors; remove the Co-authored-by trailer."
|
|
exit 1
|
|
fi
|
|
|
|
- name: The packages a signed build ships as
|
|
id: variants
|
|
run: echo "list=$(awk '!/^#/ && NF { print $1 }' tools/release/variants.txt | jq -R . | jq -sc .)" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: No private key in the tree
|
|
run: |
|
|
set -euo pipefail
|
|
keys=$(git ls-files | grep -iE '\.(jks|keystore|p12|pfx|pk8|pem|key)$' | grep -vx 'keystore/testkey.p12' || true)
|
|
if [ -n "$keys" ]; then
|
|
echo "::error::a private key is committed - remove it from the history and make a new one: $keys"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Version bumps are isolated
|
|
if: github.event_name == 'pull_request'
|
|
run: |
|
|
set -euo pipefail
|
|
git fetch --no-tags origin main
|
|
base=$(git merge-base origin/main HEAD)
|
|
diff=$(git diff -U0 "$base..HEAD" -- app/build.gradle)
|
|
grep -Eq '^[+-][[:space:]]*version(Code|Name)[[:space:]]' <<<"$diff" || exit 0
|
|
other_files=$(git diff --name-only "$base..HEAD" | grep -v '^app/build.gradle$' || true)
|
|
other_gradle=$(grep -E '^[+-]' <<<"$diff" | grep -Ev '^(---|\+\+\+)' | grep -Ev '^[+-][[:space:]]*version(Code|Name)[[:space:]]' || true)
|
|
if [[ -n "$other_files" || -n "$other_gradle" ]]; then
|
|
echo "::error::versionCode/versionName changes must be isolated in their own PR"
|
|
echo "A version PR may only change the versionCode/versionName lines in app/build.gradle." >> "$GITHUB_STEP_SUMMARY"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Merge onto main, or say what conflicts
|
|
if: github.event_name == 'pull_request'
|
|
run: |
|
|
set -uo pipefail
|
|
git config user.name "ci"
|
|
git config user.email "ci@users.noreply.github.com"
|
|
git fetch --no-tags origin main
|
|
BASE=$(git rev-parse --short origin/main)
|
|
HEAD=$(git rev-parse --short HEAD)
|
|
if git merge --no-ff --no-edit origin/main >/tmp/merge.out 2>&1; then
|
|
{
|
|
echo "### Mergeable"
|
|
echo
|
|
echo "\`$HEAD\` merges cleanly onto main (\`$BASE\`). What follows was built from the merged result."
|
|
} > /tmp/pr-merge.md
|
|
cat /tmp/pr-merge.md >> "$GITHUB_STEP_SUMMARY"
|
|
exit 0
|
|
fi
|
|
FILES=$(git diff --name-only --diff-filter=U)
|
|
if [ -z "$FILES" ]; then
|
|
{
|
|
echo "### ERROR: Could not merge onto main (\`$BASE\`)"
|
|
echo
|
|
echo '```'
|
|
cat /tmp/merge.out
|
|
echo '```'
|
|
} > /tmp/pr-merge.md
|
|
cat /tmp/pr-merge.md >> "$GITHUB_STEP_SUMMARY"
|
|
echo "::error::git could not merge main: $(head -3 /tmp/merge.out | tr '\n' ' ')"
|
|
exit 1
|
|
fi
|
|
{
|
|
echo "### ERROR: Not mergeable - conflicts with main (\`$BASE\`)"
|
|
echo
|
|
echo "Files that conflict:"
|
|
echo
|
|
for f in $FILES; do echo "- \`$f\`"; done
|
|
echo
|
|
echo "Each conflicting hunk, as git leaves it: \`<<<<<<< HEAD\` is this pull request, \`>>>>>>> origin/main\` is main."
|
|
echo
|
|
for f in $FILES; do
|
|
echo "<details><summary><code>$f</code></summary>"
|
|
echo
|
|
echo '```'
|
|
awk '/^<<<<<<< /{p=1} p{print} /^>>>>>>> /{p=0}' "$f" | head -300
|
|
echo '```'
|
|
echo "</details>"
|
|
echo
|
|
done
|
|
echo "Not built: rebase or merge main into the branch and push again."
|
|
} > /tmp/pr-merge.md
|
|
cat /tmp/pr-merge.md >> "$GITHUB_STEP_SUMMARY"
|
|
git merge --abort || true
|
|
echo "::error::the pull request conflicts with main: $(echo $FILES | tr '\n' ' ')"
|
|
exit 1
|
|
|
|
- uses: actions/setup-java@v4
|
|
with:
|
|
distribution: temurin
|
|
java-version: '17'
|
|
|
|
- uses: gradle/actions/setup-gradle@v4
|
|
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: Set the APK build label
|
|
id: build-label
|
|
env:
|
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
|
BUILD_REF: ${{ github.ref_name }}
|
|
BUILD_SHA: ${{ github.sha }}
|
|
run: |
|
|
if [[ -n "$(git status --porcelain)" ]]; then
|
|
tree_state=dirty
|
|
else
|
|
tree_state=clean
|
|
fi
|
|
echo "DROIDDECK_BUILD_TREE_STATE=$tree_state" >> "$GITHUB_ENV"
|
|
if [[ -n "$PR_NUMBER" ]]; then
|
|
label="pr${PR_NUMBER} at ${PR_HEAD_SHA:0:7}"
|
|
else
|
|
label="${BUILD_REF} at ${BUILD_SHA:0:7}"
|
|
fi
|
|
echo "label=$label" >> "$GITHUB_OUTPUT"
|
|
echo "DROIDDECK_SOURCE_COMMIT=${PR_HEAD_SHA:-$BUILD_SHA}" >> "$GITHUB_ENV"
|
|
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
|
|
key: preloads-${{ env.PRELOAD_CACHE_VERSION }}-${{ hashFiles('app/src/main/cpp/fakeinput_steam.cpp', 'app/src/main/cpp/adrenotools/src/hook/kgsl.h', 'tools/quest/*', 'tools/linuxfs/preload/*.c', 'tools/linuxfs/build-x86-preloads.sh', 'tools/linuxfs/fex/*', 'tools/linuxfs/clipboard/*.c', 'tools/linuxfs/ssbs/*.c', 'tools/proot/fastpath/*.c', '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/comet/**', 'tools/linuxfs/debian-image.env', 'tools/vr/**') }}
|
|
|
|
- name: Build libfakeinput.so and libblsession.so (glibc aarch64)
|
|
if: steps.preloads.outputs.cache-hit != 'true'
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
sudo apt-get update -qq
|
|
sudo apt-get install -y -qq gcc-aarch64-linux-gnu g++-aarch64-linux-gnu libvulkan1 mesa-vulkan-drivers >/dev/null
|
|
D=app/src/main/assets/linuxfs
|
|
mkdir -p "$D"
|
|
aarch64-linux-gnu-g++ -shared -fPIC -O2 -Wall -Wno-attributes -Wno-nonnull-compare \
|
|
-pthread -std=c++17 -static-libstdc++ -static-libgcc -Wl,--exclude-libs,ALL \
|
|
-o "$D/libfakeinput.so" app/src/main/cpp/fakeinput_steam.cpp -ldl
|
|
aarch64-linux-gnu-strip --strip-unneeded "$D/libfakeinput.so"
|
|
aarch64-linux-gnu-gcc -shared -fPIC -O2 -Wall -pthread \
|
|
-o "$D/libblsession.so" tools/linuxfs/preload/*.c -ldl
|
|
aarch64-linux-gnu-strip --strip-unneeded "$D/libblsession.so"
|
|
aarch64-linux-gnu-gcc -shared -fPIC -O2 -Wall -pthread \
|
|
-o "$D/libblfastpath.so" tools/proot/fastpath/fastpath.c -ldl
|
|
aarch64-linux-gnu-strip --strip-unneeded "$D/libblfastpath.so"
|
|
aarch64-linux-gnu-gcc -shared -fPIC -O2 -Wall -nostdlib -ffreestanding -fno-stack-protector \
|
|
-fno-tree-loop-distribute-patterns -Wl,-z,defs -o "$D/libblaudit.so" tools/proot/fastpath/audit.c
|
|
aarch64-linux-gnu-strip --strip-unneeded "$D/libblaudit.so"
|
|
aarch64-linux-gnu-gcc -shared -fPIC -O2 -Wall -pthread \
|
|
-o "$D/libssbs.so" tools/linuxfs/ssbs/ssbs_adapter.c -ldl
|
|
aarch64-linux-gnu-strip --strip-unneeded "$D/libssbs.so"
|
|
mkdir -p "$D/usr/local/bin"
|
|
aarch64-linux-gnu-gcc -O2 -Wall -Wextra -o "$D/usr/local/bin/droiddeck-clipboard" tools/linuxfs/clipboard/clipboard.c -ldl
|
|
aarch64-linux-gnu-strip --strip-unneeded "$D/usr/local/bin/droiddeck-clipboard"
|
|
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
|
|
gh release download "${GAMESCOPE_TAG}" -R "${GAMESCOPE_REPO:-${GITHUB_REPOSITORY}}" -p gamescope.tzst -O 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"
|
|
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.preloads.outputs.cache-hit != '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.preloads.outputs.cache-hit != '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
|
|
printf "deb http://archive.debian.org/debian bullseye main\ndeb http://archive.debian.org/debian-security bullseye-security main\n" > /etc/apt/sources.list
|
|
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
|
|
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
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
if [ -f tools/droiddeck-esync/release.env ]; then
|
|
. tools/droiddeck-esync/release.env
|
|
gh release download "$SYNC_BUNDLE_TAG" -R "$SYNC_BUNDLE_REPO" -p "$SYNC_BUNDLE_ASSET" -O sync-bundle.tzst
|
|
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
|
|
gh release download "$SYNC_BUNDLE_TAG" -R "$SYNC_BUNDLE_REPO" -p index.json -O sync-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 -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: |
|
|
set -euo pipefail
|
|
rustup target add aarch64-linux-android
|
|
echo "rustc=$(rustc -V | awk '{print $2}')" >> "$GITHUB_OUTPUT"
|
|
rustc -V; cargo -V
|
|
|
|
- name: Cache cargo registry and the store engines' target
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
~/.cargo/registry/index
|
|
~/.cargo/registry/cache
|
|
~/.cargo/git/db
|
|
~/.cargo/bin/cargo-ndk
|
|
app/src/main/rust/stores/target
|
|
key: stores-${{ steps.rust.outputs.rustc }}-${{ hashFiles('app/src/main/rust/stores/Cargo.lock') }}
|
|
restore-keys: |
|
|
stores-${{ steps.rust.outputs.rustc }}-
|
|
|
|
- name: cargo-ndk
|
|
run: |
|
|
set -euo pipefail
|
|
command -v cargo-ndk >/dev/null || cargo install cargo-ndk --locked
|
|
cargo ndk --version
|
|
|
|
- name: Cache the CMake build tree
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
app/.cxx
|
|
app/build/intermediates/cxx
|
|
key: cxx-${{ env.NDK_VERSION }}-${{ hashFiles('app/src/main/cpp/**', 'tools/vr/src/xr_*.h') }}
|
|
restore-keys: |
|
|
cxx-${{ env.NDK_VERSION }}-
|
|
|
|
- name: Build GOG Galaxy companion
|
|
if: steps.preloads.outputs.cache-hit != 'true'
|
|
run: |
|
|
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
|
|
id: build
|
|
env:
|
|
DROIDDECK_BUILD_LABEL: ${{ steps.build-label.outputs.label }}
|
|
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
|
|
python3 tools/release/runtime_inputs.py apk app/build/outputs/apk/release/app-release.apk
|
|
|
|
- name: Check the store engines' library
|
|
run: |
|
|
set -euo pipefail
|
|
NDK="${ANDROID_NDK_LATEST_HOME:-${ANDROID_NDK_HOME:-$ANDROID_NDK_ROOT}}"
|
|
python3 tools/release/check_android_libraries.py app/build/outputs/apk/release/app-release.apk \
|
|
--readelf "$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-readelf"
|
|
git diff --stat -- app/src/main/rust/stores/Cargo.lock
|
|
git diff --exit-code -- app/src/main/rust/stores/Cargo.lock \
|
|
|| { echo "::error::Cargo.lock changed during the build; commit the regenerated lockfile (uploaded as stores-cargo-lock)"; exit 1; }
|
|
|
|
- name: Store engines host unit tests
|
|
timeout-minutes: 10
|
|
continue-on-error: true
|
|
run: |
|
|
cd app/src/main/rust/stores
|
|
cargo test --locked 2>&1 | tail -60
|
|
|
|
- name: Upload the regenerated Cargo.lock
|
|
if: failure()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: stores-cargo-lock
|
|
path: app/src/main/rust/stores/Cargo.lock
|
|
if-no-files-found: ignore
|
|
|
|
- name: Steam import and game environment tests
|
|
run: python3 -m unittest discover -s tools/tests -p 'test_*.py'
|
|
|
|
- name: Unit tests
|
|
id: unit-tests
|
|
run: |
|
|
set -o pipefail
|
|
mkdir -p app/build
|
|
./gradlew testReleaseUnitTest --console=plain --info --stacktrace -Drobolectric.logging=stderr -PndkVersion="$NDK_VERSION" 2>&1 | tee app/build/unit-tests.log
|
|
|
|
- name: Upload unit test diagnostics
|
|
id: unit-test-diagnostics
|
|
if: always() && steps.unit-tests.outcome != 'skipped'
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: unit-test-results
|
|
path: |
|
|
app/build/unit-tests.log
|
|
app/build/test-results/testReleaseUnitTest/
|
|
app/build/reports/tests/testReleaseUnitTest/
|
|
if-no-files-found: ignore
|
|
retention-days: 14
|
|
|
|
- name: Sign with the testkey (v1 + v2 + v3)
|
|
run: |
|
|
set -euo pipefail
|
|
APK=$(find app/build/outputs/apk -name '*.apk' | head -1)
|
|
TOOLS=$(find "$ANDROID_HOME/build-tools" -maxdepth 1 -type d | sort -V | tail -1)
|
|
"$TOOLS/zipalign" -p -f 4 "$APK" "$APK.aligned"
|
|
"$TOOLS/apksigner" sign \
|
|
--ks keystore/testkey.p12 --ks-type PKCS12 --ks-pass pass:android \
|
|
--ks-key-alias testkey --key-pass pass:android \
|
|
--v1-signing-enabled true --v2-signing-enabled true --v3-signing-enabled true \
|
|
--out "$APK" "$APK.aligned"
|
|
rm -f "$APK.aligned" "$APK.idsig"
|
|
|
|
- name: Check the signature
|
|
run: |
|
|
set -euo pipefail
|
|
APK=$(find app/build/outputs/apk -name '*.apk' | head -1)
|
|
APKSIGNER=$(find "$ANDROID_HOME/build-tools" -name apksigner | sort -V | tail -1)
|
|
"$APKSIGNER" verify --min-sdk-version 21 --verbose --print-certs "$APK" | tee /tmp/sig.txt
|
|
unzip -l "$APK" | grep -qE 'META-INF/.*\.(SF|RSA|DSA)$' \
|
|
|| { echo "::error::apk carries no JAR signature files"; exit 1; }
|
|
for scheme in "v1 scheme (JAR signing): true" "v2 scheme (APK Signature Scheme v2): true" \
|
|
"v3 scheme (APK Signature Scheme v3): true"; do
|
|
grep -qF "$scheme" /tmp/sig.txt || { echo "::error::apk is not signed with $scheme"; exit 1; }
|
|
done
|
|
grep -qi "CN=Android, OU=Android, O=Android" /tmp/sig.txt \
|
|
|| { echo "::error::apk is not signed with the AOSP testkey"; exit 1; }
|
|
|
|
- uses: actions/upload-artifact@v4
|
|
id: apk-upload
|
|
with:
|
|
name: droiddeck-apk
|
|
path: app/build/outputs/apk/release/*.apk
|
|
if-no-files-found: error
|
|
retention-days: 30
|
|
compression-level: 0
|
|
|
|
- name: Report on the pull request
|
|
if: always() && github.event_name == 'pull_request'
|
|
continue-on-error: true
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
PR: ${{ github.event.pull_request.number }}
|
|
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
BUILD_OUTCOME: ${{ steps.build.outcome }}
|
|
UNIT_TEST_OUTCOME: ${{ steps.unit-tests.outcome }}
|
|
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 "A release-signed tester build that installs over Stable or Preview is being published now 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
|
|
|
|
sign:
|
|
needs: build
|
|
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
|
|
permissions:
|
|
contents: read
|
|
actions: read
|
|
uses: ./.github/workflows/sign.yml
|
|
with:
|
|
environment: signing-main
|
|
variants: ${{ needs.build.outputs.variants }}
|
|
secrets: inherit
|
|
|
|
announce:
|
|
needs: [build, sign]
|
|
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 10
|
|
permissions:
|
|
contents: read
|
|
pull-requests: read
|
|
env:
|
|
CI_REPO: Droid-Deck/DroidDeck-CI
|
|
KEEP: 10
|
|
steps:
|
|
- uses: actions/download-artifact@v4
|
|
with:
|
|
pattern: droiddeck-signed-*
|
|
merge-multiple: true
|
|
path: signed
|
|
|
|
- uses: actions/create-github-app-token@v2
|
|
id: ci-token
|
|
with:
|
|
app-id: ${{ vars.DROIDDECK_CI_APP_ID }}
|
|
private-key: ${{ secrets.DROIDDECK_CI_APP_KEY }}
|
|
owner: Droid-Deck
|
|
repositories: DroidDeck-CI
|
|
|
|
- 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
|
|
|
|
- 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"
|
|
|
|
- name: Post the Preview build to Discord
|
|
env:
|
|
WEBHOOK: ${{ secrets.DISCORD_BUILDS_WEBHOOK }}
|
|
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"
|
|
|
|
catalog:
|
|
needs: announce
|
|
if: ${{ always() && github.event_name != 'pull_request' && github.ref == 'refs/heads/main' && needs.announce.result != 'skipped' }}
|
|
uses: ./.github/workflows/update-catalog.yml
|
|
secrets: inherit
|