name: Android # The native Kotlin/Compose client over the shared Rust core (M12). # # Replaces the Tauri-mobile lane deleted in step 2. What changed is what this # builds, not that Android has a lane: the UI is Compose, and the store and sync # engine are `thoughtsync-core` cross-compiled by cargo-ndk and loaded through # uniffi. # # CI can only prove this BUILDS. A Linux runner cannot execute an APK, so anything # about feel, touch or on-device correctness is an operator pass on an emulator or # phone. on: push: branches: [dev, main] paths: - "android/**" # The Rust the .so is built from. A core change reaches the phone exactly # as it reaches the desktop, so this lane has to rebuild on it. - "core/**" - "Cargo.toml" - "Cargo.lock" - ".forgejo/workflows/android.yml" workflow_dispatch: concurrency: group: android-${{ github.ref }} cancel-in-progress: true env: # Silences the JDK 22+ "restricted method in java.lang.System has been called" # warning that Gradle 9.1's bundled native-platform jar trips at launch. This # targets the LAUNCHER JVM, which is why org.gradle.jvmargs in # gradle.properties is not enough on its own (Minstrel hit the same thing). JAVA_TOOL_OPTIONS: "--enable-native-access=ALL-UNNAMED" jobs: build: name: Kotlin + Rust (debug APK) # runs-on is only a scheduling label (Label Model B). flutter-ci is the # proven-working label that can pull our container images. runs-on: flutter-ci container: # The image repurposed from ci-tauri-android in M12 step 3: Rust + the four # Android ABIs + cargo-ndk + SDK/NDK + JDK 25 + ktlint + detekt. image: git.fabledsword.com/bvandeusen/ci-rust-android:1.97 defaults: run: working-directory: android steps: - uses: actions/checkout@v4 - name: Cache Gradle and Cargo uses: actions/cache@v4 with: path: | ~/.gradle/caches ~/.gradle/wrapper ~/.kotlin target key: android-${{ hashFiles('android/gradle/wrapper/gradle-wrapper.properties', 'android/gradle/libs.versions.toml', 'android/**/*.gradle.kts', 'Cargo.lock') }} restore-keys: | android- - name: Make gradlew executable run: chmod +x ./gradlew # Fails loudly here if the wrapper and the image's JDK disagree, rather # than thirty seconds into a compile with an opaque version message. - name: Gradle wrapper check run: ./gradlew --version # Cross-compiles the core for four ABIs and generates the Kotlin bindings # from the built .so. Run as its own step so a Rust failure is legible as a # Rust failure instead of arriving inside a Gradle stack trace. - name: Build the native library and bindings run: ./gradlew generateUniffiBindings # The image's PINNED CLIs, not Gradle plugins. ci-rust-android carries both # (M12 step 3) precisely so this lane needs no second image, and going # through Gradle plugins would mean a second version of each tool resolved # at build time and kept in lockstep with the image's by hand. # # Scoped to src/main: the generated uniffi bindings live under build/ and # are not ours to style. - name: ktlint run: ktlint "app/src/main/**/*.kt" - name: detekt run: detekt --build-upon-default-config --config config/detekt.yml --input app/src/main/java - name: Unit tests # Host-JVM tests only. Anything touching the core needs an Android # runtime to load the .so, so those are instrumented tests and belong on # an emulator, not here — the Rust side is covered by the workspace # tests in the desktop lane. run: ./gradlew testDebugUnitTest - name: Assemble debug APK run: ./gradlew assembleDebug - name: Upload debug APK # Mirrored action, never actions/upload-artifact. @v4+ throws # GHESNotSupportedError client-side on this hostname, and @v3 is worse — # it reports success while Gitea serves artifacts back only through the # v4 API, so the upload is stored and invisible. Pinned by SHA because # the mirror auto-syncs; full URL because DEFAULT_ACTIONS_URL sends bare # owner/repo to github.com. See Scribe issues 2255 / 2270. uses: https://git.fabledsword.com/bvandeusen/upload-artifact@cb8afe72b42edc798abfb8fcb556cf660d894245 with: name: thoughtsync-android-debug-${{ github.sha }} path: android/app/build/outputs/apk/debug/app-debug.apk if-no-files-found: error