build: platform cluster bump - AGP 9.4, Kotlin 2.4.10, compileSdk 37, version catalog - #40
Merged
anasvhora284 merged 12 commits intoSep 17, 2026
Merged
Conversation
Contributor
Reviewer's GuideMigrates the build to a catalog-driven AGP 9.4/Gradle 9.6 platform with Kotlin 2.4.10, compileSdk 37, and updated AndroidX dependencies, while adapting built-in Kotlin and variant APIs, preserving targetSdk 34, modernizing APK staging, and aligning CI and documentation. Sequence diagram for AGP 9 APK stagingsequenceDiagram
participant Gradle
participant VariantAPI as AndroidComponents
participant CopyTask as Copy task
participant Dist as Dist staging
Gradle->>VariantAPI: onVariants
VariantAPI->>CopyTask: register copy<Variant>ApksToDist
Gradle->>CopyTask: assemble<Variant>
CopyTask->>Dist: Copy APK artifacts
CopyTask->>Dist: rename APKs with ABI-aware pattern
Sequence diagram for the updated Android CI validationsequenceDiagram
participant CI
participant SDK as Android SDK
participant Gradle
participant Staging as APK dist staging
participant Emulator
CI->>SDK: Install platforms;android-37.0 and build-tools;36.0.0
CI->>Gradle: assembleDebug and assembleDebugAndroidTest
Gradle->>Staging: Stage renamed ABI APKs
CI->>Staging: Assert expected renamed APK files
CI->>Emulator: Run connected smoke tests on API 36
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
guaje
marked this pull request as ready for review
September 4, 2026 17:17
Contributor
There was a problem hiding this comment.
Hey - I've found 4 issues
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="app/build.gradle.kts" line_range="109" />
<code_context>
+ ) {
+ from(variant.artifacts.get(SingleArtifact.APK))
+ into(layout.buildDirectory.dir("dist/${variant.name}"))
+ rename { fileName ->
+ Regex("app-([A-Za-z0-9_]+)-(debug|release)\\.apk").find(fileName)
+ ?.let { "input-leaf_${versionName}_${it.groupValues[1]}.apk" }
+ ?: fileName
+ }
}
</code_context>
<issue_to_address>
**issue (bug_risk):** The APK rename pattern does not match ABI names containing hyphens, including `arm64-v8a` and `armeabi-v7a`, so those copied APKs retain AGP's `app-...apk` names instead of the historical `input-leaf_<version>_<abi>.apk` names.
**Triggers:** When the arm64-v8a or armeabi-v7a split APKs are staged.
**Suggested fix:** Allow hyphens in the ABI capture group, for example with `[A-Za-z0-9_-]+`.
```suggestion
Regex("app-([A-Za-z0-9_-]+)-(debug|release)\\.apk").find(fileName)
```
</issue_to_address>
### Comment 2
<location path="app/build.gradle.kts" line_range="103-106" />
<code_context>
+ onVariants { variant ->
+ val versionName = android.defaultConfig.versionName
+ ?: error("versionName is required for the APK naming scheme")
+ tasks.register<Copy>(
+ "copy${variant.name.replaceFirstChar { it.uppercase() }}ApksToDist"
+ ) {
+ from(variant.artifacts.get(SingleArtifact.APK))
+ into(layout.buildDirectory.dir("dist/${variant.name}"))
+ rename { fileName ->
</code_context>
<issue_to_address>
**issue (bug_risk):** The new `Copy` tasks are registered but are not wired to any assemble or package task, so running the normal `assembleDebug` or `assembleRelease` commands does not create `build/dist/<variant>` or produce the replacement filenames.
**Triggers:** When callers expect the normal APK build to provide the staged, historically named APKs.
**Suggested fix:** Make each copy task a dependency of the corresponding assemble/package task, or explicitly invoke the copy task in every build workflow that requires the staged outputs.
</issue_to_address>
### Comment 3
<location path="uhid-server/build.gradle.kts" line_range="56-65" />
<code_context>
+ val d8Path = if (sdkRoot.isNotBlank()) "$sdkRoot/build-tools/36.0.0/d8" else ""
</code_context>
<issue_to_address>
**issue:** The UHID build now requires Android platform 36 and build-tools 36.0.0, while `docs/TESTING.md` still instructs developers that platform 34 and build-tools 34.0.0 are required; following the documented setup with only those packages causes `buildDex` to fail its existence checks.
**Triggers:** When a developer follows the generated-asset setup instructions on a machine that has only the documented API 34 SDK packages.
**Suggested fix:** Update `docs/TESTING.md` to require platform 36 and build-tools 36.0.0.
</issue_to_address>
### Comment 4
<location path="build.gradle.kts" line_range="13" />
<code_context>
+ mavenCentral()
+ }
+ dependencies {
+ classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.4.10")
+ }
+}
</code_context>
<issue_to_address>
**nitpick:** The actual Kotlin Gradle plugin version is hardcoded as `2.4.10` outside the version catalog, so changing `versions.kotlin` does not update the compiler override and the catalog is not the single source of truth claimed by this migration.
**Triggers:** When the Kotlin version is changed through `gradle/libs.versions.toml` without also editing the root build script.
**Suggested fix:** Expose the Kotlin version to the buildscript through a shared property or otherwise keep the compiler classpath dependency mechanically tied to the catalog value.
</issue_to_address>
guaje
marked this pull request as draft
September 4, 2026 20:57
4 tasks done
Introduce gradle/libs.versions.toml as the single source of truth for plugin and library versions across the root, app, and uhid-server build files. Versions are carried over verbatim - this commit is a pure migration with no behavior change, so version bumps can be reviewed on their own.
… Gradle 9.6 - AGP 8.7.0 -> 9.4.0 (new DSL interfaces; legacy variant API removed): migrate to built-in Kotlin (kotlin-android plugin dropped) and override the built-in compiler to Kotlin 2.4.10 via the documented KGP classpath mechanism; compose-compiler plugin kept (it overrides AGP's supplied Compose compiler, per the JetBrains migration guide). - compileSdk 34 -> 36; targetSdk deliberately stays 34 (SDK 35+ enforces edge-to-edge - a product decision, not a dependency bump). - enableAndroidTestCoverage -> isTestCoverageEnabled (AGP 9 DSL). - kotlinOptions deleted: built-in Kotlin derives jvmTarget from compileOptions.targetCompatibility (17). - APK renaming: applicationVariants/BaseVariantOutputImpl is gone; the input-leaf_<version>_<abi>.apk scheme is reproduced with the public variant API via a Copy task staging APKs under build/dist/<variant>/ (public VariantOutput has no outputFileName). - Generated uhid assets: pass a resolved File to sourceSets (AGP 9 disallows providers there via android.sourceset.disallowProvider). - Version bumps carried by the catalog: compose-bom 2026.08.00, core-ktx 1.19.0, lifecycle 2.11.0, activity-compose 1.13.0, datastore 1.2.1, coroutines 1.11.0 (requires the Kotlin 2.4 compiler for its 2.2 metadata), mockito 5.23.0, androidx.test quartet 1.7.0. - Gradle wrapper 8.13 -> 9.6.0 (AGP 9.4 minimum) and uhid-server d8 switched to build-tools 36.0.0 / android-36.
CI failed CheckAarMetadata: compose 1.12.0 (compose-bom 2026.08.00), core/core-ktx 1.19.0, and lifecycle 2.11.0 all require compileSdk 37, which AGP 9.4 does not support yet. AAR metadata inspection pins the newest API-36-compatible generation instead: - compose-bom 2026.08.00 -> 2026.06.01 (ui 1.11.4: minCompileSdk 35, material3 1.4.0: minCompileSdk 35) - core-ktx 1.19.0 -> 1.18.0 (minCompileSdk 36; also what activity-compose 1.13.0 itself targets) - lifecycle 2.11.0 -> 2.10.0 (minCompileSdk 34/35) activity-compose 1.13.0 and datastore 1.2.1 stay: neither appeared in the CheckAarMetadata violations.
…h minor-version ids)
…the buildscript override
guaje
force-pushed
the
maintenance/platform-deps
branch
from
September 16, 2026 13:01
ff5329c to
7a152e6
Compare
guaje
marked this pull request as ready for review
September 16, 2026 14:16
Contributor
There was a problem hiding this comment.
Hey - I've found 1 issue
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="app/build.gradle.kts" line_range="19" />
<code_context>
android {
namespace = "com.inputleaf.android"
- compileSdk = 34
+ compileSdk = 37
defaultConfig {
</code_context>
<issue_to_address>
**nitpick:** The Android SDK platform and build-tools versions remain duplicated as hardcoded literals across the app, CI, and `uhid-server`; changing the platform cluster in one place does not update the other declarations, so the UHID DEX build and app/CI can compile against different SDK installations or fail because the separately hardcoded package is absent.
**Triggers:** When a future platform bump updates `compileSdk` or the CI SDK packages without manually updating `uhid-server/build.gradle.kts`.
**Suggested fix:** Define the compile SDK, platform minor version, and build-tools version in shared Gradle properties or the version catalog, and have CI and `buildDex` consume those values.
</issue_to_address>
Collaborator
Author
|
Hi @anasvhora284! This PR should be ready for review. PTAL. |
Owner
|
Thanks for your efforts @guaje |
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Platform dependency cluster: AGP 9.4, Kotlin 2.4.10, compileSdk 37, Gradle 9.6
Draft / maintenance PR — review whenever convenient; independent of #32 in content.
What this does
Bumps the platform cluster that can only move together, and centralizes all version declarations in a Gradle version catalog. Twelve commits, reviewable independently:
build: centralize dependency declarations in a version catalog— pure migration, zero version changes. Newgradle/libs.versions.tomlbecomes the single source of truth for plugin and library versions across the root,app, anduhid-serverbuild files.build: platform cluster bump— the actual version/behavior changes.ci: android-36— CI SDK/emulator images to match.fix: pin androidx generation that compiles against API 36— first-roundCheckAarMetadatafallout (see ceiling history below).build: compile against api 37 and restore the latest androidx generation— raises compileSdk to 37 (supported by AGP 9.4) and restores the true-latest androidx pins.fix: correct split-APK rename regex and wire dist staging to assemble— hyphenated ABIs now rename, andassemble<Variant>triggers the dist staging (verified by a new CI assertion step).docs: sync testing guide with platform 37 and api-36 emulator—docs/TESTING.mdmatches the build platform and the emulator.fix: install the android-37.0 platform package— API 37 ships only with minor-version package ids (see below).build: tie the built-in Kotlin version to the version catalog— the buildscript override reads its version from the catalog itself.build: pin the built-in Kotlin via the compose compiler plugin, drop the buildscript override— the buildscript block is gone entirely: the compose-compiler plugin's POM depends on the same-version KGP, so the catalog alone pins the built-in compiler.build: drop explanatory comments from build files— the migration rationale lives in this description instead of the build scripts.build: replace deprecated isTestCoverageEnabled with enableAndroidTestCoverage— AGP 9 splits the old unified coverage flag; the androidTest half's replacement isenableAndroidTestCoverage(flagged by @anasvhora284 in review).Rebased onto master after #41–#43 landed; master's new JVM-test dependencies (
org.json, Robolectric,androidx.test:core) were ported into the catalog during the rebase.Version changes (all in
gradle/libs.versions.toml)targetSdk deliberately stays on 34. SDK 35+ enforces edge-to-edge, which is a product/design decision, not a dependency bump. It is set explicitly so AGP 9's new
android.sdk.defaultTargetSdkToCompileSdkIfUnsetdefault never silently moves it.Coroutines 1.11.0 ships Kotlin 2.2 metadata, which is why #32 pins 1.10.2 (consumable by the 2.0 compiler); here the compiler is 2.4.10, so 1.11.0 is consumable again.
Ceiling history: the road to compileSdk 37 (CI rounds taught this)
Round 1 failed
CheckAarMetadata: the then-latest androidx generation — compose 1.12.0 (compose-bom 2026.08.00), core-ktx 1.19.0, and lifecycle 2.11.0 — all require minCompileSdk 37. That round responded by pinning compileSdk 36 and dropping to the newest API-36-compatible generation instead (verified per artifact via each AAR'saar-metadata.properties: compose ui 1.11.4 / material3 1.4.0 → 35, core-ktx 1.18.0 → 36, lifecycle 2.10.0 → 34/35). That ceiling was self-imposed, not an AGP limit: AGP 9.4 supports API level 37 (its release notes' compatibility table; theCheckAarMetadataerror itself recommended "compileSdk of at least 37, for example 37.2").Later rounds restored the true-latest generation on compileSdk 37. Two findings worth recording for future bumps:
platforms;android-37.0/37.1/37.2—sdkmanager "platforms;android-37"fails with "Failed to find package". The correct install isplatforms;android-37.0.compileSdk = 37(minor 0 default) maps to thatandroid-37.0package: the 37-generation AARs carryminCompileSdk=37, minCompileMinorSdk=0, and the green head run compiles against it.The final pins, AAR-metadata-verified: compose ui 1.12.0 / material3 1.4.0 (BOM 2026.08.00) and core-ktx 1.19.0 and lifecycle 2.11.0 — all
minCompileSdk 37, minCompileMinorSdk 0. activity-compose 1.13.0 and datastore 1.2.1 stay — neither appeared in the violations. Build-tools stay 36.0.0 (AGP 9.4 default) and the emulator stays on API 36.The AGP 9 migration details
Built-in Kotlin. AGP 9 removes compatibility with the external
org.jetbrains.kotlin.androidplugin — it compiles Kotlin itself, running with whatever Kotlin Gradle plugin version wins on the plugin classpath. The catalog pins that to Kotlin 2.4.10 with no extra machinery:versions.kotlinversions the compose-compiler plugin, whose POM depends on the same-version KGP, soalias(libs.plugins.compose.compiler)transitively places KGP 2.4.10 above AGP's bundled version — the built-in compiler follows. (The Google-documented buildscript-classpath recipe remains the fallback if the compose plugin is ever removed.) Consequences in the build files:kotlin-androidplugin declarations are gone (root + app), and the root file has nobuildscriptblock at all.kotlinOptions { jvmTarget = "17" }block is deleted; built-in Kotlin derivesjvmTargetfromcompileOptions.targetCompatibility(17).org.jetbrains.kotlin.plugin.composestays: under AGP 9 the Compose compiler is supplied by AGP itself, and applying the JetBrains plugin simply overrides its coordinates with the Kotlin-matched 2.4.10 compiler (per the JetBrains compose-compiler migration guide).versions.kotlinis the only Kotlin declaration in the repository — it versions the compose-compiler plugin, which transitively pins the built-in compiler's KGP. No buildscript block, no duplicate literal, no regex parsing.Legacy variant API removal. AGP 9 deletes
applicationVariants/BaseVariantOutputImpl, which the app used to rename APKs in place. The publicVariantOutputAPI does not exposeoutputFileName, so the historicalinput-leaf_<version>_<abi>.apkscheme is reproduced with the public variant API: aCopytask per variant that stages renamed APKs underbuild/dist/<variant>/(copyDebugApksToDist/copyReleaseApksToDist), now wired into the matchingassemble<Variant>task so every assemble run also produces the staged copies. The rename pattern covers hyphenated ABIs (armeabi-v7a,arm64-v8a), and theandroid-coveragejob gained anAssert renamed APK stagingstep that fails if unrenamed APKs are staged or any of the five expected per-ABI files is missing. Default AGP output names inapp/build/outputs/apk/…are unchanged.Other AGP 9 adjustments.
isTestCoverageEnabled(AGP 9 splits the old unified flag) is replaced byenableAndroidTestCoverage = trueon the debug build type — same JaCoCo offline instrumentation of the androidTest APK feeding theandroid-coverageCodecov session. AGP's unit-test half (enableUnitTestCoverage) is deliberately not enabled: JVM unit-test coverage is owned by the KoverdebugJvmpipeline below.sourceSetsas a resolvedFileinstead of aProvider(AGP 9 setsandroid.sourceset.disallowProvider=true).testInstrumentationRunnerwas already explicit (AGP 9 changes the default); ABIsplitsare unaffected (only density splits were removed).CI outcome (head
6a53e8739b1b2d6c19540e35108672dcda606238)fast-jvm✓ — catalog + built-in Kotlin 2.4.10 (catalog-tied, no buildscript block) + compose plugin + Kover on Gradle 9.6 all compile and run against compileSdk 37 / BOM 2026.08.00; Codecov jvm upload ✓; no coverage-flag deprecation warnings.android-coverage✓ — api-36 emulator, debug+test APKs assembled (including the newAssert renamed APK stagingstep ✓), connected suite green, JaCoCo report ✓.codecov/patch✓ — the PR touches build files only, so the patch gate reports no misses.35236278448).CheckAarMetadata; the API-36 pin round went green; round 3 failed installingplatforms;android-37(no flat id); intermediate rounds validated compileSdk 37 againstandroid-37.0, the restored latest androidx generation, and the catalog-tied compiler override; the head run adds the switch from the deprecatedisTestCoverageEnabledtoenableAndroidTestCoverage(caught by @anasvhora284 in review — the AGP 9.4 compiler itself deprecates the old unified flag in favor of the split pair) on top of the docs: optimize repository for search discovery #41–ci(codecov): fix master coverage report and UpdateService gaps #43 rebase.Risks and fallbacks (CI is the validator)
android.enableAppCompileTimeRClass=true(new AGP 9 default): fails compilation if app code requires constantRfields (e.g.whenonR.id). The app has no such usages. Fallback: set the flag tofalseingradle.properties.kotlin-android, the fallback is to drop the plugin entirely (AGP then supplies its bundled Compose compiler); if the transitive KGP pin ever needs replacing, the Google-documented buildscript-classpath recipe is the fallback.fast-jvmfailures.android-37.0+); future bumps must follow the same scheme (theCompileSdkReleaseSpec.minorApiLevelDSL exists if a minor > 0 is ever needed).google_apis/x86_64images are published for API 36; if the emulator job gets flaky, pinning it back to api 34 is acceptable (instrumented tests only require ≥ minSdk 26).kotlin-stdlib:2.4.10dependency would resolve it.Summary by Sourcery
Modernize the Android build platform and centralize dependency management while preserving the existing target SDK and test coverage workflows.
Enhancements:
Build:
CI:
Docs: