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 }} 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--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