Skip to content

ci: sign Android releases, and build iOS against the SDK it now needs - #16

Merged
nathanfallet merged 2 commits into
mainfrom
ci/android-release-signing
Aug 1, 2026
Merged

ci: sign Android releases, and build iOS against the SDK it now needs#16
nathanfallet merged 2 commits into
mainfrom
ci/android-release-signing

Conversation

@nathanfallet

@nathanfallet nathanfallet commented Aug 1, 2026

Copy link
Copy Markdown
Member

deploy-android now builds — the Ruby fix got it as far as BUILD SUCCESSFUL and a bundle — but
what it produces is unsigned, so the Play Store will refuse it.

The workflow writes fastlane/keystore.jks from SIGNING_KEY_JKS and passes
SIGNING_STORE_PASSWORD, SIGNING_KEY_ALIAS and SIGNING_KEY_PASSWORD to fastlane. Nothing read
any of it: androidApp/build.gradle.kts had no signingConfigs block at all.

This is latexcards-app's block, which has been deploying that way for a year, with one deliberate
change: the config only applies when the keystore is actually present.

val keystore = file("fastlane/keystore.jks")
signingConfigs {
    create("release") {
        if (keystore.exists()) { … }
    }
}

Taken verbatim, storeFile = file("fastlane/keystore.jks") makes any release built outside CI fail
on a missing file — including assembleProductionRelease, which is worth keeping usable for
checking what ships. With the guard, a build without the keystore comes out unsigned rather than
failing. Say the word if you would rather it match latexcards exactly.

Verified

A release built with the keystore present and the three variables set comes out signed with the
upload certificate (CN=Fallet Nathan, OU=ZabriCraft) — which is the CI's exact situation.

Still needed outside this PR

  • iOS: the distribution certificate in groupeminaste/match has expired
    (V6866DQQ65.cer). The SSH side is fixed — match clones and decrypts fine now — but it stops on
    the expired certificate. Removing the four distribution files from the match repo and re-running
    fastlane match appstore regenerates it without revoking anything.
  • Android: GOOGLE_SERVICE_ACCOUNT_KEY was empty, which is why upload_to_play_store said
    'fastlane/key.json' doesn't seem to be a JSON file. If SIGNING_KEY_JKS and the three signing
    secrets were never set either, this PR will get the build to the same place and stop there — the
    bundle will be unsigned again, because the keystore file will be empty.

Also: iOS builds on macos-latest

Pushed here rather than in its own PR, to save a merge and a run.

With the certificate renewed, deploy-ios finally reached compilation and failed at the link:

Undefined symbols for architecture arm64
  "_OBJC_CLASS_$_UIViewLayoutRegion", referenced from: …

UIViewLayoutRegion is iOS 26. Compose Multiplatform 1.11.1 references it in its UIKit layer, so
the framework needs the iOS 26 SDK. macos-15 ships Xcode 16 and iPhoneOS18.5.sdk; locally the
same code builds against iPhoneOS26.5.sdk, which is why this never showed up before.

It is a consequence of the migration raising Compose Multiplatform from 1.8.2 to 1.11.1 — invisible
until now, because the job kept stopping earlier: first on the SSH key, then on the expired
certificate.

macos-latest currently resolves to macOS 26 arm64. Worth remembering that this repository once
had to pin away from it (ci: using macos-15 (because macos-latest is macos-14)), so if GitHub's
default ever lags the SDK the app needs, macos-26 is the explicit alternative.

The Android deploy writes `fastlane/keystore.jks` from a secret and passes
SIGNING_STORE_PASSWORD, SIGNING_KEY_ALIAS and SIGNING_KEY_PASSWORD to
fastlane — but nothing ever read any of it. There was no `signingConfigs`
block at all, so `bundleDevRelease` produced an unsigned bundle, which the
Play Store will not take.

This is latexcards-app's block, which has been deploying that way for a year,
with one change: the config only applies when the keystore is actually there.
Taken verbatim, a release built anywhere but CI fails on a missing file, and
`assembleProductionRelease` is worth keeping usable locally. Without the
keystore the build now comes out unsigned instead of failing.

Verified by building a release with the keystore and the three variables
present: the APK comes out signed with the upload certificate.
With the certificate renewed, `deploy-ios` finally reached compilation — and
failed at the link:

    Undefined symbols for architecture arm64
      "_OBJC_CLASS_$_UIViewLayoutRegion", referenced from: …

`UIViewLayoutRegion` is iOS 26. Compose Multiplatform 1.11.1 references it in
its UIKit layer, so the framework needs the iOS 26 SDK. `macos-15` ships
Xcode 16 and `iPhoneOS18.5.sdk`, which is why this only showed up on CI:
locally the same code builds against `iPhoneOS26.5.sdk`.

This is a consequence of the migration raising Compose Multiplatform from
1.8.2 to 1.11.1. Nothing could see it while the job was still stopping earlier,
first on the SSH key and then on the expired certificate.

`macos-latest` currently resolves to macOS 26 arm64. Worth knowing that this
repository once had to pin *away* from it — `ci: using macos-15 (because
macos-latest is macos-14)` — so if GitHub's default ever lags the SDK the app
needs again, pinning to `macos-26` is the answer.
@nathanfallet nathanfallet changed the title build: sign release builds with the upload keystore ci: sign Android releases, and build iOS against the SDK it now needs Aug 1, 2026
@nathanfallet
nathanfallet merged commit 338cdd0 into main Aug 1, 2026
1 check passed
@nathanfallet
nathanfallet deleted the ci/android-release-signing branch August 1, 2026 19:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant