Skip to content

Support Android 17 (target=37, api_level=37) in build + integration tests - #53

Merged
guylis-ironsrc merged 5 commits into
masterfrom
guy/android-17-workflow-only
Jul 29, 2026
Merged

Support Android 17 (target=37, api_level=37) in build + integration tests#53
guylis-ironsrc merged 5 commits into
masterfrom
guy/android-17-workflow-only

Conversation

@guylis-ironsrc

Copy link
Copy Markdown
Contributor

Summary

Unblocks the Android integration-tests workflow for Android 17 / target=37 / api_level=37 across both matrix jobs (Kotlin + Java). Verified end-to-end green on GHA: run 30366238885 — Kotlin ~4:40, Java ~4:38.

Three commits, each isolating a distinct required piece — deltas kept minimal:

  1. a067e20 — Map api_level=37 in the workflow to api-level: "37.0" + target: "google_apis" when calling reactivecircus/android-emulator-runner@v2. Older api-levels pass through unchanged with target: default.
  2. 8414d01 — Bump AGP 8.2.2 → 9.1.1 and Gradle 8.2 → 9.3.1 in both projects, plus the coupled AGP 9 requirements (proguard-optimize, drop kotlin-android plugin, kotlin { compilerOptions {} } block).
  3. 6221e8b — Add a pre-emulator step that upgrades $ANDROID_HOME/cmdline-tools/latest to build 15859902 (cmdline-tools 22.0). Runs only when api_level == '37'.

Why each piece is required (verified empirically)

Google switched Android 17 to a feature-drop release model. Only versioned variants of the SDK packages exist:

  • platforms;android-37not published
  • platforms;android-37.0, platforms;android-37.1 — published
  • system-images;android-37;<tag>;<abi>not published
  • system-images;android-37.0;google_apis;<abi> — published (no default tag exists at API 37)
Piece What fails without it Verified failing run
AGP 9.1.1 + Gradle 9.3.1 Build fails: Failed to find Platform SDK with path: platforms;android-37 (AGP 8.2.2 can't map compileSdk = 37 to the on-disk platforms/android-37.0/) run 30365216456
api-level: "37.0" Build succeeds, then reactivecircus fails: Warning: Failed to find package 'platforms;android-37' run 30362466493
target: "google_apis" reactivecircus fails at next step: Warning: Failed to find package 'system-images;android-37.0;default;x86_64' run 30364151515
cmdline-tools 22.0 upgrade reactivecircus's install steps warn SDK XML file of version 4 was encountered; avdmanager writes target=android-0 into AVD config.ini; emulator boot never completes and reactivecircus times out at 600s run 30365450517 (cancelled at ~5 min stuck in adb-poll loop)

All 4 pieces present ⇒ end-to-end green: run 30366238885.

Behavior for older API levels

Unchanged. The workflow-input mapping only rewrites api_level=37; anything else passes through as before. The cmdline-tools upgrade step has if: ${{ steps.args.outputs.api_level == '37' }} and is a no-op otherwise. Runs at default api_level=29 are byte-identical to before this PR.

Removes when

Both upstream PRs land and we bump the reactivecircus action pin:

Once those release in a fresh @v2 or @v3, the workflow mappings and cmdline-tools install step can be removed and the reactivecircus block goes back to the original 4-line form. The AGP + Gradle bump stays (it's an inherent AGP requirement for compileSdk = 37, unrelated to the action).

Supersedes

Replaces PR #52, which had the same fix mixed with two dead-end experiment commits (manual emulator setup) that aren't needed.

Test plan

  • Local ./gradlew assembleDebug (default target 34) passes on both projects.
  • Local ./gradlew assembleDebug -PtargetSdkVersion=37 -PcompileSdkVersion=37 passes on both projects, aapt2 dump badging confirms targetSdkVersion:'37'.
  • Local ./gradlew connectedDebugAndroidTest -PtargetSdkVersion=37 -PcompileSdkVersion=37 — 6/6 tests pass on an API 37 arm64-v8a emulator (Kotlin + Java).
  • GHA workflow_dispatch sdk_version=9.5.0 api_level=37 target_sdk_version=37run 30366238885, both matrix jobs green.
  • Verify a default-args PR run (api_level=29, target_sdk_version=34) still passes on this branch, to confirm no regression for older api-levels.

🤖 Generated with Claude Code

guylis-ironsrc and others added 3 commits July 28, 2026 16:48
reactivecircus/android-emulator-runner@v2 interpolates its `api-level`
and `target` inputs directly into the sdkmanager package names it
installs. For Android 17 Google only publishes feature-drop variants
(`platforms;android-37.0` / `-37.1`) under `google_apis` etc. — no
bare `platforms;android-37` and no `default` tag exist at API 37 — so
passing "37.0" and "google_apis" through to the action makes it request
packages that actually exist.

Minimal reproduction of the Android-17 CI workaround: this branch
carries only the two workflow-input mappings, without the AGP 9.1.1 /
Gradle 9.3.1 bump or cmdline-tools 22 upgrade. Purpose is to confirm
which pieces are strictly required.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
AGP 8.2.2 cannot resolve `compileSdk = 37` — it looks for
`platforms;android-37` on disk, which Google no longer publishes for
Android 17 (feature-drop naming: only `platforms;android-37.0` /
`-37.1` exist). AGP 9.1.1 is Google's first line that maps
`compileSdk = 37` to the installed feature-drop platform.

Both projects (Kotlin + Java):
  * AGP `com.android.tools.build:gradle` 8.2.2 -> 9.1.1
  * Gradle wrapper 8.2 -> 9.3.1 (AGP 9.1.1 minimum)
  * `proguard-android.txt` -> `proguard-android-optimize.txt` — AGP 9
    rejects the non-optimized default because it embeds `-dontoptimize`,
    which prevents R8 optimizations.

Kotlin project only:
  * Drop `org.jetbrains.kotlin:kotlin-gradle-plugin` classpath and
    `ext.kotlin_version` — AGP 9 ships built-in Kotlin support.
  * Drop `id 'kotlin-android'` from `app/build.gradle` plugins block.
  * Move `android.kotlinOptions { jvmTarget = "1.8" }` to top-level
    `kotlin { compilerOptions { jvmTarget = JVM_1_8 } }`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Without this, avdmanager v12 (pre-installed on ubuntu-latest) doesn't
understand the SDK XML v4 metadata that ships with feature-drop
system-images and writes `target=android-0` into the AVD's config.ini.
The emulator then can't finish booting — sits in "device offline" until
reactivecircus/android-emulator-runner@v2 hits the 600s boot timeout.

Empirically confirmed on run 30365450517 (this branch, cancelled at
~5 min stuck in adb-poll loop): "Warning: This version only understands
SDK XML versions up to 3 but an SDK XML file of version 4 was
encountered" fires during `avdmanager create avd`, and boot never
completes.

Runs only for `api_level == '37'` so older api-levels are untouched.
Pending upstream: ReactiveCircus/android-emulator-runner#483.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@Gal-Yedidovich-iSrc

Copy link
Copy Markdown
Contributor

Review

CI + build config only, so nothing to check against a ticket. The diff is small (+43/-12) and the reasoning is unusually well evidenced.

I verified the two claims the description presents as "coupled AGP 9 requirements" — both are genuinely forced, not discretionary:

Also checked the one unchecked test-plan box: it's satisfied. This PR touches Android/**, so the pull_request trigger ran the workflow with default args (api_level=29, target_sdk_version=34) — integration-tests (Java) and (Kotlin) both pass in run 30366855506 (~3m24s each). The no-regression claim for older API levels is empirically confirmed; that box can be ticked.

Overall: approve with comments. Nothing blocks merge. One security hardening item and one workflow-robustness item are worth fixing now; the publisher-facing impact of the AGP bump deserves an explicit decision.

Strengths

  • Per-piece failing-run links proving each of the four changes is individually necessary — this is the standard CI changes should be held to and rarely are.
  • Workflow comments record the exact error strings (SDK XML file of version 4, target=android-0) rather than restating the code. Correct call for a temporary workaround.
  • Guarded so older API levels are untouched: the mapping only rewrites 37, and the new step is if-gated.
  • Documented removal criteria with links to both upstream PRs, plus a note on which part (AGP/Gradle) is permanent. Makes the workaround reversible instead of load-bearing forever.
  • Supersedes Bump AGP 8.2.2 → 9.1.1 + Gradle 9.3.1 for compileSdk=37 support #52 with the dead-end experiment commits dropped.

🟡 1. cmdline-tools zip is downloaded and executed with no integrity check

.github/workflows/android-integration-tests.yml — "Upgrade cmdline-tools to 22.0" step

The job has PRIVATE_MAVEN_USERNAME / PRIVATE_MAVEN_PASSWORD in env, and this step fetches a 180 MB archive over the network and then runs a binary out of it. -f catches HTTP errors but nothing verifies content. Google publishes a SHA-1 for this exact build in repository2-3.xml:

curl -sfLO https://dl.google.com/android/repository/commandlinetools-linux-15859902_latest.zip
echo "040d3996a65543d22ec4bf73e4c37aa37a8d4af4  commandlinetools-linux-15859902_latest.zip" | sha1sum -c -

Cheap, and it also turns a silently corrupt/truncated download into a clear failure instead of a confusing avdmanager error later.

Consider working-directory: /tmp for this step too — right now the zip lands in the checkout root and is cleaned up by a trailing rm; using /tmp removes the need for that line and can't dirty the workspace if the step aborts midway.

🟡 2. The "is this API 37" decision is encoded twice and can drift

The shell block computes EMU_API_LEVEL/EMU_TARGET from [ "$API_LEVEL" = "37" ], and the step gate separately re-tests steps.args.outputs.api_level == '37'. Two sources of truth for one condition. Emit it once:

if [ "$API_LEVEL" = "37" ]; then
  EMU_API_LEVEL="37.0"
  EMU_TARGET="google_apis"
  NEEDS_CMDLINE_UPGRADE="true"
fi
...
echo "needs_cmdline_upgrade=${NEEDS_CMDLINE_UPGRADE:-false}" >> $GITHUB_OUTPUT

Related, and the reason I'd fix this now rather than later: api_level=37.0 silently takes the non-37 path. Given this entire PR is about 37.0 being the real package name, someone will plausibly type 37.0 into the dispatch form. They'd get target=default and no cmdline-tools upgrade — i.e. exactly the 600s emulator-boot hang this PR fixes, with nothing in the log explaining why. Either normalize 37.0/37.1 into the same branch, or fail fast on an unsupported value.

🔵 3. buildToolsVersion '34.0.0' is now inconsistent with a compileSdk that can be 37

Android/{Java,Kotlin}/app/build.gradle

Untouched by this PR and the build is green, so not blocking. But an explicit 34.0.0 pin next to a parameterized compileSdk that can be 37 is misleading in a reference app. AGP picks a correct default if the line is simply deleted.

🔵 4. Cosmetic leftovers from the kotlinOptions move

Android/Kotlin/app/build.gradle — the android { } block now ends with a stray blank line, and org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_1_8 is fully qualified inline. Works, but in a file publishers read as a template, an import reads better.


The one thing worth an explicit decision

These are demo apps publishers copy verbatim, and the description frames the AGP/Gradle bump purely as a CI necessity without noting its publisher-facing consequence. Concretely, after this PR:

  • The Kotlin demo has 11 .kt files (DemoActivity.kt, 3 androidTest classes, …) and no Kotlin plugin applied anywhere — Kotlin compilation now depends entirely on AGP 9's built-in support. A publisher still on AGP 8.x who copies Android/Kotlin gets a project whose Kotlin sources are never compiled. The demo has gone from "works on AGP 8" to "requires AGP 9".
  • The demo now requires Gradle 9.3.1 and JDK 17+.
  • The Kotlin language version is no longer pinned — ext.kotlin_version = '2.1.0' is gone, so it's whatever AGP 9.1.1 bundles.

Note the committed default is still compileSdk = 34, which AGP 8.2.2 handled fine; compileSdk = 37 only appears on manual workflow_dispatch. So the demo's published baseline moved to satisfy a capability exercised only in dispatch runs.

Not asking for a reversal — pinning AGP per-job is genuinely awkward, and AGP 9 is where the ecosystem is heading. But it should be a stated choice rather than a side effect, and the README (which currently documents no version requirements at all) is the natural place for a one-line "requires AGP 9.x / Gradle 9.x / JDK 17+" note, since publishers hitting the AGP 8 incompatibility have no other signal.

guylis-ironsrc and others added 2 commits July 29, 2026 13:54
Two issues surfaced in the PR #53 review:

1. `api_level=37.0` on workflow_dispatch silently took the non-37 path
   (target=default, cmdline-tools upgrade skipped) — reproduces the 600s
   emulator-boot hang the PR was written to fix. Fix: compute a single
   `is_android_17` step output for the "is this A17?" decision from a
   case matching `37`, `37.0`, `37.1`, and gate both the emulator-runner
   inputs and the cmdline-tools upgrade step on it. One source of truth.

2. cmdline-tools zip was downloaded and unzipped without an integrity
   check. Add sha1sum verification against Google's published digest
   for build 15859902 (040d3996a65543d22ec4bf73e4c37aa37a8d4af4).
   Also move `working-directory` to `/tmp` so the archive doesn't
   land in the checkout root and can't dirty the workspace on abort.

Plus a README note: the AGP 8 -> 9 bump raises the floor for any
publisher who copies these demo apps. Document the AGP 9.1+ / Gradle
9.3+ / JDK 17+ requirement and point at JetBrains' migration guide so
publishers on AGP 8 know what to expect.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds a small markdown table to $GITHUB_STEP_SUMMARY summarizing which
LevelPlay SDK version, emulator API level, target tag, and compile/
target SDK the job actually ran against. Renders as a header at the top
of the run's Summary page so it's obvious per matrix job without
digging into step logs or artifact names.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@guylis-ironsrc
guylis-ironsrc merged commit 80742fe into master Jul 29, 2026
9 checks passed
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.

2 participants