release(mobile): drop AAB from GitHub Release attachments #53
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build WASM Docker Image | |
| on: | |
| push: | |
| branches: | |
| - main | |
| pull_request: | |
| branches: | |
| - main | |
| jobs: | |
| test: | |
| name: shared :connectedAndroidDeviceTest | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| - name: Enable KVM | |
| run: | | |
| echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \ | |
| | sudo tee /etc/udev/rules.d/99-kvm4all.rules | |
| sudo udevadm control --reload-rules | |
| sudo udevadm trigger --name-match=kvm | |
| - name: Set up JDK | |
| uses: actions/setup-java@v5 | |
| with: | |
| distribution: temurin | |
| java-version: '21' | |
| - name: Set up Gradle | |
| uses: gradle/actions/setup-gradle@v5 | |
| # Smoke-compile the test source set on the runner without an emulator. | |
| # Fails fast on Gradle / source-set wiring issues before paying the | |
| # ~5 min AVD boot. Task name comes from the new | |
| # com.android.kotlin.multiplatform.library plugin (androidDeviceTest). | |
| - name: Compile instrumented tests | |
| run: ./gradlew :shared:compileAndroidDeviceTest --stacktrace | |
| - name: AVD cache | |
| id: avd-cache | |
| uses: actions/cache@v6 | |
| with: | |
| path: | | |
| ~/.android/avd/* | |
| ~/.android/adb* | |
| key: avd-api-34-x86_64-v1 | |
| - name: Create AVD and generate snapshot (cold) | |
| if: steps.avd-cache.outputs.cache-hit != 'true' | |
| uses: reactivecircus/android-emulator-runner@v2 | |
| with: | |
| api-level: 34 | |
| arch: x86_64 | |
| target: default | |
| force-avd-creation: false | |
| emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none | |
| disable-animations: true | |
| script: echo "Generated AVD snapshot for caching." | |
| - name: Run :shared:connectedAndroidDeviceTest | |
| uses: reactivecircus/android-emulator-runner@v2 | |
| with: | |
| api-level: 34 | |
| arch: x86_64 | |
| target: default | |
| force-avd-creation: false | |
| emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none | |
| disable-animations: true | |
| script: ./gradlew :shared:connectedAndroidDeviceTest --stacktrace | |
| - name: Upload test reports | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: shared-android-test-report | |
| path: | | |
| shared/build/reports/androidTests/ | |
| shared/build/outputs/androidTest-results/ | |
| # Fast JVM run of every `commonTest` case (~30s after warm cache). Runs in | |
| # parallel with `test` to give PR authors near-immediate feedback if a common | |
| # test breaks, without paying the Android emulator boot. Now part of the | |
| # `build-wasm-image` deploy gate (see the `needs:` list on that job), so a | |
| # Skia/AWT flake here must be treated as a real blocker that needs a rerun | |
| # rather than silently ignored. | |
| desktop-test: | |
| name: shared :desktopTest | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| - name: Set up JDK | |
| uses: actions/setup-java@v5 | |
| with: | |
| distribution: temurin | |
| java-version: '21' | |
| - name: Set up Gradle | |
| uses: gradle/actions/setup-gradle@v5 | |
| - name: Run :shared:desktopTest | |
| run: ./gradlew :shared:desktopTest --stacktrace | |
| - name: Upload test reports | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: shared-desktop-test-report | |
| path: | | |
| shared/build/reports/tests/desktopTest/ | |
| shared/build/test-results/desktopTest/ | |
| # End-to-end slice against the published Octocon backend image | |
| # (digest-pinned in shared/src/desktopIntegrationTest/.../BackendContainer.kt). | |
| # Boots the image in Testcontainers, drives the OAuth-callback auth flow | |
| # to obtain a real token, then exercises ApiInterfaceImpl through the | |
| # Phoenix WebSocket + endpoint-proxy paths (LoadClient + CreateAlter). | |
| # | |
| # Part of the `build-wasm-image` deploy gate (see the `needs:` list on | |
| # that job). A Testcontainers hiccup or a ghcr.io outage pulling the | |
| # backend image therefore blocks WASM deploy until a rerun succeeds — | |
| # that's the accepted trade-off for guaranteeing every WASM ship has | |
| # green end-to-end signal. | |
| # Approximate cost on a cold runner: ~30 s image pull + ~5 s tests. | |
| # | |
| # Requirements satisfied implicitly on `ubuntu-latest`: | |
| # - Docker daemon pre-installed and reachable on the default Unix socket. | |
| # - Bridge-network container IPs (172.17.x.y) are directly routable from | |
| # the host JVM, which BackendContainer.baseUrl depends on so the | |
| # backend's WebSocket endpoint-proxy loopback resolves correctly. | |
| integration-test: | |
| name: shared :desktopIntegrationTest | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| # Only required if ghcr.io/azyyyyyy/interfold-api ever flips private; | |
| # harmless when it stays public, so granted defensively. | |
| packages: read | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| - name: Set up JDK | |
| uses: actions/setup-java@v5 | |
| with: | |
| distribution: temurin | |
| java-version: '21' | |
| - name: Set up Gradle | |
| uses: gradle/actions/setup-gradle@v5 | |
| - name: Run :shared:desktopIntegrationTest | |
| run: ./gradlew :shared:desktopIntegrationTest --stacktrace | |
| - name: Upload test reports | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: shared-integration-test-report | |
| path: | | |
| shared/build/reports/tests/desktopIntegrationTest/ | |
| shared/build/test-results/desktopIntegrationTest/ | |
| # Quick iOS build + test pass. Deliberately mirrors the iOS half of | |
| # mobile-release.yml but stops at `xcodebuild build` — no archive, no | |
| # IPA pack, no attest, no upload. Goal is a ~10-15 min warm signal that | |
| # a future mobile release will still compile, plus running the same | |
| # `commonTest` corpus on iOS that `test` runs on Android. | |
| # | |
| # macos-26 for iOS 26 SDK parity with mobile-release (Compose | |
| # Multiplatform 1.11.x emits `UIViewLayoutRegion` refs that only link | |
| # against the iOS 26 SDK — see the runner comment on mobile-release). | |
| # Cache keys deliberately match mobile-release.yml so PR runs warm the | |
| # ~/.konan and CocoaPods caches that the release workflow will re-use. | |
| ios-test: | |
| name: shared :iosSimulatorArm64Test + xcodebuild build | |
| runs-on: macos-26 | |
| timeout-minutes: 40 | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| - name: Set up JDK | |
| uses: actions/setup-java@v5 | |
| with: | |
| distribution: temurin | |
| java-version: '21' | |
| - name: Set up Gradle | |
| uses: gradle/actions/setup-gradle@v5 | |
| # Same key as mobile-release.yml so the two workflows share the | |
| # ~/.konan cache slice (~1-2 GB of Kotlin/Native compiler + | |
| # prebuilts). Adding iosSimulatorArm64 here grows the slice by a | |
| # simulator-arch triple; still well under the 10 GB repo quota. | |
| - name: Cache Kotlin/Native (~/.konan) | |
| uses: actions/cache@v6 | |
| with: | |
| path: ~/.konan | |
| key: konan-${{ runner.os }}-${{ hashFiles('gradle.properties') }}-${{ hashFiles('**/*.gradle.kts', 'gradle/libs.versions.toml') }} | |
| restore-keys: | | |
| konan-${{ runner.os }}-${{ hashFiles('gradle.properties') }}- | |
| konan-${{ runner.os }}- | |
| # Same key as mobile-release.yml. The cache-hit output gates | |
| # whether `pod install` needs `--repo-update`. | |
| - name: Cache CocoaPods | |
| id: pods-cache | |
| uses: actions/cache@v6 | |
| with: | |
| path: | | |
| ~/.cocoapods | |
| ~/Library/Caches/CocoaPods | |
| iosApp/Pods | |
| key: cocoapods-${{ runner.os }}-${{ hashFiles('iosApp/Podfile.lock') }} | |
| restore-keys: | | |
| cocoapods-${{ runner.os }}- | |
| # Same prep as mobile-release but without `-PappVersion.*` flags — | |
| # CI doesn't need release-shaped version stamps. writeIosVersionXcconfig | |
| # is still needed so the Podfile's post_install hook can chain | |
| # Version.generated.xcconfig into every Pods xcconfig at pod-install | |
| # time; generateDummyFramework is required or pod install fails with | |
| # "Kotlin framework 'shared' doesn't exist yet". | |
| - name: Prepare KMP iOS artifacts | |
| run: | | |
| set -euo pipefail | |
| ./gradlew :shared:writeIosVersionXcconfig :shared:generateDummyFramework --stacktrace | |
| - name: pod install | |
| working-directory: iosApp | |
| run: | | |
| set -euo pipefail | |
| if [ "${{ steps.pods-cache.outputs.cache-hit }}" = "true" ]; then | |
| echo "CocoaPods cache hit — skipping --repo-update" | |
| pod install | |
| else | |
| echo "CocoaPods cache miss — running pod install --repo-update" | |
| pod install --repo-update | |
| fi | |
| # Fast smoke compile of the iOS test source set. Analog to the | |
| # `Compile instrumented tests` step in the Android job — fails fast | |
| # on source-set / cinterop / pod-drift issues before we pay | |
| # simulator boot cost in the test step below. | |
| # | |
| # `-Pkotlin.incremental.native=false` avoids a known Kotlin/Native | |
| # compiler assertion the linker hits on iosSimulatorArm64 Debug/Test | |
| # klibs: | |
| # Failed to build cache for [...]/multiplatform-markdown-renderer[...] | |
| # java.lang.AssertionError: Lowering ReturnsInsertion: phases | |
| # [Enums] are required, but not satisfied | |
| # The Kotlin compiler itself suggests this flag as the workaround. | |
| # Doesn't affect mobile-release.yml which uses linkReleaseFramework | |
| # (a different phase pipeline). | |
| - name: Compile iOS simulator tests | |
| run: ./gradlew :shared:compileTestKotlinIosSimulatorArm64 -Pkotlin.incremental.native=false --stacktrace | |
| # Build the Xcode target for Simulator arch. Skips archive, IPA | |
| # pack, signing, and attestation — the pieces mobile-release adds | |
| # on top. On macos-26 Apple Silicon the Simulator arch is | |
| # `iosSimulatorArm64`, same LLVM target family as the `iosArm64` | |
| # device build mobile-release uses, so Swift + KMP + linker errors | |
| # surface identically. Difference vs mobile-release is limited to | |
| # signing/entitlements (skipped) and thin-binary packaging (n/a | |
| # for `build`). | |
| # | |
| # `ARCHS=arm64 ONLY_ACTIVE_ARCH=YES` pins the build to the | |
| # iosSimulatorArm64 slice. Without them, `generic/platform=iOS | |
| # Simulator` builds a fat slice on Apple Silicon (arm64 + x86_64) | |
| # and Compose Multiplatform's `syncPodComposeResourcesForIos` | |
| # aborts with "Unknown iOS simulator arch: 'x86_64'" — that arch | |
| # slice isn't in the KMP plugin's whitelist because we don't | |
| # declare an iosX64 target in shared/build.gradle.kts. | |
| - name: xcodebuild build (Simulator) | |
| run: | | |
| set -euo pipefail | |
| xcodebuild build \ | |
| -workspace iosApp/iosApp.xcworkspace \ | |
| -scheme iosApp \ | |
| -configuration Debug \ | |
| -sdk iphonesimulator \ | |
| -destination "generic/platform=iOS Simulator" \ | |
| ARCHS=arm64 \ | |
| ONLY_ACTIVE_ARCH=YES \ | |
| CODE_SIGNING_ALLOWED=NO \ | |
| CODE_SIGNING_REQUIRED=NO \ | |
| CODE_SIGN_IDENTITY="" \ | |
| CODE_SIGN_ENTITLEMENTS="" \ | |
| DEVELOPMENT_TEAM="" | |
| # Direct KMP analog of :shared:connectedAndroidDeviceTest — same | |
| # commonTest source set, compiled for iosSimulatorArm64, launched | |
| # on a Kotlin/Native-managed simulator. Iterates the same test | |
| # corpus across both mobile targets. Same -Pkotlin.incremental.native | |
| # workaround as the smoke step; the linker (linkDebugTestIosSimulatorArm64) | |
| # is what actually trips the [Enums] phase assertion. | |
| - name: Run :shared:iosSimulatorArm64Test | |
| run: ./gradlew :shared:iosSimulatorArm64Test -Pkotlin.incremental.native=false --stacktrace | |
| - name: Upload test reports | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: shared-ios-test-report | |
| path: | | |
| shared/build/reports/tests/iosSimulatorArm64Test/ | |
| shared/build/test-results/iosSimulatorArm64Test/ | |
| build-wasm-image: | |
| needs: [test, desktop-test, integration-test, ios-test] | |
| if: github.event_name == 'push' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| with: | |
| # Need full history so the compute-version step below can walk back | |
| # through commits with `git log --grep=^ReleaseName:` — the marker | |
| # is intentionally sticky across commits until re-declared. | |
| fetch-depth: 0 | |
| # Central AppVersion knobs threaded into Dockerfile.wasm as build-args | |
| # and re-injected as -PappVersion.* flags on the Gradle command inside. | |
| # TAG -> the git tag on HEAD (e.g. "v1.3.2"); empty for | |
| # rolling builds. Presence flips versionName from | |
| # the build-id to the semver. | |
| # RELEASE_NAME -> value of the most recent `ReleaseName: <name>` | |
| # line reachable from HEAD (sticky across commits). | |
| # run_number is passed straight from github.run_number below and | |
| # forms the build-id date-runNumber pair. | |
| - name: Compute app version inputs | |
| id: appversion | |
| run: | | |
| set -euo pipefail | |
| TAG=$(git describe --tags --exact-match HEAD 2>/dev/null || true) | |
| RELEASE_NAME=$(git log --grep='^ReleaseName:' -1 --pretty=%B \ | |
| | sed -n 's/^ReleaseName:[[:space:]]*//p' \ | |
| | head -n1 || true) | |
| echo "tag=$TAG" >> "$GITHUB_OUTPUT" | |
| echo "release_name=$RELEASE_NAME" >> "$GITHUB_OUTPUT" | |
| echo "Resolved tag='$TAG' release_name='$RELEASE_NAME' run_number='${{ github.run_number }}'" | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Extract Docker metadata | |
| id: meta | |
| uses: docker/metadata-action@v5 | |
| with: | |
| images: ghcr.io/${{ github.repository_owner }}/octocon-wasm | |
| tags: | | |
| type=ref,event=branch | |
| type=sha,prefix=sha- | |
| type=raw,value=latest,enable={{is_default_branch}} | |
| - name: Build and push Docker image | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: . | |
| file: ./Dockerfile.wasm | |
| push: true | |
| # Multi-arch fan-out. Builder stage runs once on $BUILDPLATFORM | |
| # (the GHA amd64 runner) — see Dockerfile.wasm header comment. | |
| # Runtime stage is COPY-only, so no QEMU binfmt handler is needed. | |
| platforms: linux/amd64,linux/arm64,linux/arm/v7 | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| build-args: | | |
| APPVERSION_RUN_NUMBER=${{ github.run_number }} | |
| APPVERSION_TAG=${{ steps.appversion.outputs.tag }} | |
| APPVERSION_RELEASE_NAME=${{ steps.appversion.outputs.release_name }} | |
| # Suppress the SBOM/provenance attestation manifests — they otherwise | |
| # show up as `unknown/unknown` entries in the OCI index, which confuses | |
| # the bootstrapper's manifest inspection. | |
| provenance: false | |
| sbom: false | |
| cache-from: type=gha,scope=octocon-wasm | |
| cache-to: type=gha,mode=max,scope=octocon-wasm |