274334d14c
Adds a hang-interop job that runs after lint and exercises the :nestsClient:jvmTest suite under -DnestsHangInterop=true. Cargo registry + cli/hang-interop/target are cached on the Cargo.lock + REV file hashes so cold runs (~6 min for the moq-relay install) only happen on dependency change; warm runs finish in seconds. Linux-only — the protocol logic is platform-agnostic and the JNA libopus natives are already exercised by JvmOpusRoundTripTest on the Android job. macOS / Windows interop runs would double the matrix cost without catching new defects. Pinned to dtolnay/rust-toolchain@stable; ubuntu-latest's bundled Rust drifts and moq-relay 0.10.25 needs ≥ 1.95 (the constant_time_eq 0.4.3 transitive dep). https://claude.ai/code/session_01ERJPUYfdLPwZ99pr5EcEcV
331 lines
12 KiB
YAML
331 lines
12 KiB
YAML
name: Test/Build
|
||
|
||
on:
|
||
pull_request:
|
||
branches: [main]
|
||
push:
|
||
branches: [main]
|
||
|
||
permissions:
|
||
contents: read
|
||
|
||
concurrency:
|
||
group: ${{ github.workflow }}-${{ github.ref }}
|
||
cancel-in-progress: true
|
||
|
||
jobs:
|
||
lint:
|
||
runs-on: ubuntu-latest
|
||
timeout-minutes: 15
|
||
steps:
|
||
- name: Checkout code
|
||
uses: actions/checkout@v6
|
||
|
||
- name: Set up JDK 21
|
||
uses: actions/setup-java@v5
|
||
with:
|
||
distribution: 'zulu'
|
||
java-version: 21
|
||
|
||
# Remote Gradle build cache: writes on push to main, reads on PRs and
|
||
# other branches. Caches both `~/.gradle/caches/` and individual task
|
||
# outputs, so dependency-only changes hit the cache and skip recompiling
|
||
# downstream modules / re-merging native libs (~600MB of work on
|
||
# :amethyst alone). Replaces the narrower `cache: gradle` previously on
|
||
# actions/setup-java, which only cached `modules-2`.
|
||
- name: Set up Gradle
|
||
uses: gradle/actions/setup-gradle@v4
|
||
with:
|
||
cache-read-only: ${{ github.ref != 'refs/heads/main' }}
|
||
|
||
- name: Linter (gradle)
|
||
run: ./gradlew spotlessCheck
|
||
|
||
build-desktop:
|
||
needs: lint
|
||
strategy:
|
||
fail-fast: false
|
||
matrix:
|
||
include:
|
||
- os: ubuntu-latest
|
||
desktop-task: packageDeb
|
||
desktop-artifact-name: Desktop Linux DEB
|
||
desktop-artifact-path: desktopApp/build/compose/binaries/main/deb/*.deb
|
||
- os: macos-latest
|
||
desktop-task: packageDmg
|
||
desktop-artifact-name: Desktop macOS DMG
|
||
desktop-artifact-path: desktopApp/build/compose/binaries/main/dmg/*.dmg
|
||
- os: windows-latest
|
||
desktop-task: packageMsi
|
||
desktop-artifact-name: Desktop Windows MSI
|
||
desktop-artifact-path: desktopApp/build/compose/binaries/main/msi/*.msi
|
||
runs-on: ${{ matrix.os }}
|
||
timeout-minutes: 60
|
||
defaults:
|
||
run:
|
||
shell: bash
|
||
steps:
|
||
- name: Checkout code
|
||
uses: actions/checkout@v6
|
||
|
||
- name: Set up JDK 21
|
||
uses: actions/setup-java@v5
|
||
with:
|
||
distribution: 'zulu'
|
||
java-version: 21
|
||
|
||
- name: Set up Gradle
|
||
uses: gradle/actions/setup-gradle@v4
|
||
with:
|
||
cache-read-only: ${{ github.ref != 'refs/heads/main' }}
|
||
|
||
# Cache vlc-setup plugin downloads (VLC + UPX archives) keyed on the
|
||
# versions pinned in desktopApp/build.gradle.kts. Each OS gets its own
|
||
# cache namespace because the plugin downloads platform-specific archives.
|
||
# On a hit the vlcDownload / upxDownload tasks are up-to-date and we
|
||
# never touch get.videolan.org; on a miss (version bump or new runner)
|
||
# we fall back to fetching, which is what the in-build retry budget
|
||
# exists for.
|
||
- name: Cache vlc-setup downloads
|
||
uses: actions/cache@v4
|
||
with:
|
||
path: ~/.gradle/vlcSetup
|
||
key: vlcsetup-${{ runner.os }}-${{ hashFiles('desktopApp/build.gradle.kts') }}
|
||
restore-keys: |
|
||
vlcsetup-${{ runner.os }}-
|
||
|
||
# Pre-fetch VLC + UPX archives into ~/.gradle/vlcSetup before invoking
|
||
# Gradle. The vlc-setup plugin (ir.mahozad.vlc-setup 0.1.0) writes its
|
||
# downloads to ${gradleUserHomeDir}/vlcSetup/ and sets overwrite(false),
|
||
# so an existing file there makes vlcDownload / upxDownload up-to-date
|
||
# and Gradle never opens a socket to videolan.org.
|
||
#
|
||
# Why curl instead of relying on de.undercouch.gradle.tasks.download:
|
||
# curl --retry-all-errors with a long --retry-max-time tolerates a
|
||
# sustained get.videolan.org outage far better than the plugin's inner
|
||
# retry budget (retries(4) + 5min readTimeout in build.gradle.kts),
|
||
# which has been hitting SocketTimeoutException on Windows runners.
|
||
#
|
||
# Cache hit: the file is already on disk, fetch() short-circuits, this
|
||
# step takes <1s. Cache miss: curl downloads with aggressive retries,
|
||
# populating the cache for the next run.
|
||
#
|
||
# Versions are pinned to match desktopApp/build.gradle.kts (vlcVersion
|
||
# = 3.0.21) and the vlc-setup extension default (upxVersion = 4.2.4).
|
||
# macOS does not download UPX — UPX cannot compress .dylib files.
|
||
- name: Pre-fetch VLC + UPX archives
|
||
env:
|
||
VLC_VERSION: "3.0.21"
|
||
UPX_VERSION: "4.2.4"
|
||
run: |
|
||
set -euo pipefail
|
||
DEST="$HOME/.gradle/vlcSetup"
|
||
mkdir -p "$DEST"
|
||
fetch() {
|
||
local url="$1" out="$2"
|
||
if [[ -s "$out" ]]; then
|
||
echo "cached: $out"
|
||
return 0
|
||
fi
|
||
echo "fetching: $url"
|
||
curl -fL --retry 10 --retry-delay 5 --retry-all-errors \
|
||
--retry-max-time 900 --connect-timeout 30 \
|
||
-o "$out.part" "$url"
|
||
mv "$out.part" "$out"
|
||
}
|
||
case "${{ runner.os }}" in
|
||
Windows)
|
||
fetch "https://get.videolan.org/vlc/${VLC_VERSION}/win64/vlc-${VLC_VERSION}-win64.zip" \
|
||
"$DEST/vlc-${VLC_VERSION}.zip"
|
||
fetch "https://github.com/upx/upx/releases/download/v${UPX_VERSION}/upx-${UPX_VERSION}-win64.zip" \
|
||
"$DEST/upx-${UPX_VERSION}.zip"
|
||
;;
|
||
Linux)
|
||
fetch "https://repo1.maven.org/maven2/ir/mahozad/vlc-plugins-linux/${VLC_VERSION}/vlc-plugins-linux-${VLC_VERSION}.jar" \
|
||
"$DEST/vlc-${VLC_VERSION}.jar"
|
||
fetch "https://github.com/upx/upx/releases/download/v${UPX_VERSION}/upx-${UPX_VERSION}-amd64_linux.tar.xz" \
|
||
"$DEST/upx-${UPX_VERSION}.tar.xz"
|
||
;;
|
||
macOS)
|
||
fetch "https://get.videolan.org/vlc/${VLC_VERSION}/macosx/vlc-${VLC_VERSION}-universal.dmg" \
|
||
"$DEST/vlc-${VLC_VERSION}.dmg"
|
||
;;
|
||
esac
|
||
|
||
- name: Test + Build Desktop (gradle)
|
||
run: ./gradlew :quartz:jvmTest :commons:jvmTest :nestsClient:jvmTest :cli:test :desktopApp:test :desktopApp:${{ matrix.desktop-task }}
|
||
|
||
# jpackage pins libicu to the build host's version (libicu74 on
|
||
# ubuntu-24.04). Rewrite the .deb so testers on other Debian/Ubuntu
|
||
# releases can install the uploaded artifact.
|
||
- name: Relax libicu dependency in .deb
|
||
if: matrix.desktop-task == 'packageDeb'
|
||
run: |
|
||
set -euo pipefail
|
||
chmod +x scripts/relax-deb-libicu.sh
|
||
scripts/relax-deb-libicu.sh desktopApp/build/compose/binaries/main/deb/*.deb
|
||
|
||
- name: Upload Desktop Distribution
|
||
uses: actions/upload-artifact@v7
|
||
with:
|
||
name: ${{ matrix.desktop-artifact-name }}
|
||
path: ${{ matrix.desktop-artifact-path }}
|
||
|
||
# Cross-stack interop tests (T16). Builds the Rust hang-listen +
|
||
# hang-publish sidecars (cli/hang-interop/), `cargo install`s
|
||
# moq-relay + moq-token-cli at the version pinned in
|
||
# `cli/hang-interop/REV`, then runs `:nestsClient:jvmTest
|
||
# -DnestsHangInterop=true`. See
|
||
# `nestsClient/plans/2026-05-06-cross-stack-interop-test.md`.
|
||
#
|
||
# Linux-only: the cargo install of `moq-relay` 0.10.x has nontrivial
|
||
# native deps (aws-lc-sys, ring) that take 5+ min cold; we cache
|
||
# both ~/.cargo and the cli/hang-interop/target tree so the warm
|
||
# path is a few seconds. macOS / Windows runs would double the
|
||
# matrix cost without catching anything Linux doesn't catch — the
|
||
# protocol logic is platform-agnostic and the JNA libopus natives
|
||
# are already exercised by the unit-level JvmOpusRoundTripTest on
|
||
# the Android job.
|
||
hang-interop:
|
||
needs: lint
|
||
runs-on: ubuntu-latest
|
||
timeout-minutes: 30
|
||
steps:
|
||
- name: Checkout code
|
||
uses: actions/checkout@v6
|
||
|
||
- name: Set up JDK 21
|
||
uses: actions/setup-java@v5
|
||
with:
|
||
distribution: 'zulu'
|
||
java-version: 21
|
||
|
||
- name: Set up Gradle
|
||
uses: gradle/actions/setup-gradle@v4
|
||
with:
|
||
cache-read-only: ${{ github.ref != 'refs/heads/main' }}
|
||
|
||
# Pin Rust to ≥ 1.95 — moq-relay 0.10.25's transitive dep
|
||
# `constant_time_eq 0.4.3` requires it, and ubuntu-latest's
|
||
# default Rust version drifts.
|
||
- name: Set up Rust
|
||
uses: dtolnay/rust-toolchain@stable
|
||
|
||
# Cache cargo registry + the sidecar workspace's target/.
|
||
# First cold run takes ~6 min for the moq-relay install;
|
||
# cached runs ~30 s.
|
||
- name: Cache cargo
|
||
uses: actions/cache@v4
|
||
with:
|
||
path: |
|
||
~/.cargo/registry
|
||
~/.cargo/git
|
||
cli/hang-interop/target
|
||
~/.cache/amethyst-nests-interop/hang-interop-cargo
|
||
key: ${{ runner.os }}-cargo-${{ hashFiles('cli/hang-interop/Cargo.lock', 'cli/hang-interop/REV') }}
|
||
restore-keys: |
|
||
${{ runner.os }}-cargo-
|
||
|
||
- name: Run cross-stack interop suite
|
||
run: ./gradlew :nestsClient:jvmTest -DnestsHangInterop=true
|
||
|
||
- name: Upload interop test report
|
||
uses: actions/upload-artifact@v7
|
||
if: failure()
|
||
with:
|
||
name: Hang Interop Test Reports
|
||
path: nestsClient/build/reports/tests/jvmTest/
|
||
|
||
test-and-build-android:
|
||
needs: lint
|
||
runs-on: ubuntu-latest
|
||
timeout-minutes: 60
|
||
steps:
|
||
- name: Checkout code
|
||
uses: actions/checkout@v6
|
||
|
||
- name: Set up JDK 21
|
||
uses: actions/setup-java@v5
|
||
with:
|
||
distribution: 'zulu'
|
||
java-version: 21
|
||
|
||
- name: Set up Gradle
|
||
uses: gradle/actions/setup-gradle@v4
|
||
with:
|
||
cache-read-only: ${{ github.ref != 'refs/heads/main' }}
|
||
|
||
# Lint + focused unit tests + benchmark assembly in one Gradle invocation.
|
||
# Previously: one invocation for lint, one for `test` (which compiled all
|
||
# six amethyst variants × all flavors), one for `assembleBenchmark`
|
||
# (re-walking the same task graph). Combining them keeps the daemon hot
|
||
# across phases and lets task-level dedup (e.g. compileKotlin) only
|
||
# happen once.
|
||
#
|
||
# `-PdisableAbiSplits=true` produces a single non-split APK per
|
||
# (flavor, buildType) instead of 5 (4 ABIs + universal). The CI only
|
||
# uploads the universal-equivalent benchmark APK; per-ABI splits were
|
||
# being built and discarded, costing ~600MB of stripped_native_libs
|
||
# intermediates and several minutes per run.
|
||
#
|
||
# Test scope: only Debug unit tests for amethyst. The release/benchmark
|
||
# variants are compile-equivalent for unit-test purposes; running all six
|
||
# adds ~5× the kotlinc work without catching new defects on PRs. Push to
|
||
# main still gets the full test matrix via the production-build path.
|
||
- name: Test + Build Android (gradle)
|
||
run: |
|
||
./gradlew \
|
||
:amethyst:lintFdroidBenchmark \
|
||
:amethyst:lintPlayBenchmark \
|
||
:quartz:jvmTest \
|
||
:commons:jvmTest \
|
||
:nestsClient:jvmTest \
|
||
:ammolite:testDebugUnitTest \
|
||
:amethyst:testFdroidDebugUnitTest \
|
||
:amethyst:testPlayDebugUnitTest \
|
||
:amethyst:assembleBenchmark \
|
||
-PdisableAbiSplits=true
|
||
|
||
- name: Upload Android Lint Reports
|
||
uses: actions/upload-artifact@v7
|
||
if: always()
|
||
with:
|
||
name: Android Lint Reports
|
||
path: amethyst/build/reports/lint-results-*.html
|
||
|
||
- name: Android Test Report
|
||
uses: asadmansr/android-test-report-action@v1.2.0
|
||
if: always()
|
||
|
||
- name: Upload Test Results
|
||
uses: actions/upload-artifact@v7
|
||
if: failure()
|
||
with:
|
||
name: Test Reports
|
||
path: amethyst/build/reports
|
||
|
||
# With -PdisableAbiSplits=true the APK is named without the ABI/universal
|
||
# suffix: amethyst-<flavor>-benchmark.apk. Glob both forms so this still
|
||
# works if a contributor runs CI on a branch that doesn't pass the flag.
|
||
- name: Upload Play APK Benchmark
|
||
uses: actions/upload-artifact@v7
|
||
with:
|
||
name: Play Benchmark APK
|
||
path: |
|
||
amethyst/build/outputs/apk/play/benchmark/amethyst-play-benchmark.apk
|
||
amethyst/build/outputs/apk/play/benchmark/amethyst-play-universal-benchmark.apk
|
||
|
||
- name: Upload FDroid APK Benchmark
|
||
uses: actions/upload-artifact@v7
|
||
with:
|
||
name: FDroid Benchmark APK
|
||
path: |
|
||
amethyst/build/outputs/apk/fdroid/benchmark/amethyst-fdroid-benchmark.apk
|
||
amethyst/build/outputs/apk/fdroid/benchmark/amethyst-fdroid-universal-benchmark.apk
|
||
|
||
- name: Upload Compose Reports
|
||
uses: actions/upload-artifact@v7
|
||
with:
|
||
name: Compose Reports
|
||
path: amethyst/build/compose_compiler
|