Migrate to AGP's androidComponents/Artifacts API and support AGP 9.x#283
Migrate to AGP's androidComponents/Artifacts API and support AGP 9.x#283tnj wants to merge 13 commits into
Conversation
There was a problem hiding this comment.
Code Review
This pull request updates the Gradle DeployGate Plugin to support AGP 9.x and Gradle 9.x by migrating from internal AGP APIs to the public androidComponents and Artifacts API. It also updates test suites, build configurations, and dependencies to align with Gradle 9 requirements. The review feedback highlights a syntax issue in build.gradle where a trailing comma in jvmArgs could break compatibility with older Gradle versions running Groovy 2.5. Additionally, improvements are suggested in VariantArtifacts.groovy to ensure robust file matching for .aab artifacts and to use .getClass() instead of .class to prevent dynamic property resolution issues in Groovy.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
Pull request overview
This PR migrates the plugin’s Android Gradle Plugin integration to the public androidComponents + Artifacts API in order to support AGP 9.x, while updating tests/fixtures, CI matrix, and documentation to reflect the new minimum supported AGP/Gradle versions.
Changes:
- Replace deprecated Variant API/internal task reflection with
androidComponents.onVariants+SingleArtifactresolution (newVariantArtifactshelper). - Modernize acceptance/unit test fixtures for AGP 7.0+ / Gradle 9 compatibility (DSL updates, exported activity, configuration-cache spec parameterization).
- Update build logic, CI matrix, and README/README_JP to document and exercise AGP 9.x support and the new minimum versions.
Reviewed changes
Copilot reviewed 15 out of 16 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
src/test/resources/project/src/main/AndroidManifest.xml |
Adds android:exported to keep the fixture compatible with newer Android tooling expectations. |
src/test/resources/project/build.gradle |
Updates fixture DSL to AGP 7+ style and adjusts SDK/proguard defaults. |
src/test/resources/acceptance/android_block.gradle |
Updates acceptance fixture compile/target/proguard defaults for newer AGP. |
src/test/groovy/com/deploygate/gradle/plugins/internal/agp/AndroidGradlePluginSpec.groovy |
Removes unit tests tied to deleted AGP-version-gating helpers. |
src/test/groovy/com/deploygate/gradle/plugins/DeployGatePluginSpec.groovy |
Makes the Gradle <7.1 ProjectBuilder workaround conditional and reflection-based for Gradle 9 compatibility. |
src/test/acceptance/com/deploygate/gradle/plugins/ConfigurationCacheSpec.groovy |
Parameterizes AGP/Gradle versions and refactors configuration-cache acceptance coverage. |
src/main/groovy/com/deploygate/gradle/plugins/internal/agp/IApplicationVariantImpl.groovy |
Removes old Variant API proxy implementation. |
src/main/groovy/com/deploygate/gradle/plugins/internal/agp/IApplicationVariant.groovy |
Removes old Variant API proxy interface. |
src/main/groovy/com/deploygate/gradle/plugins/internal/agp/AndroidGradlePlugin.groovy |
Deletes AGP-version-gate helpers no longer needed with the public Artifacts API. |
src/main/groovy/com/deploygate/gradle/plugins/DeployGatePlugin.groovy |
Switches variant iteration to androidComponents.onVariants and wires upload tasks via VariantArtifacts. |
src/main/groovy/com/deploygate/gradle/plugins/artifacts/VariantArtifacts.groovy |
Introduces new artifact resolution implementation for APK/AAB (Artifacts API + conventional outputs for skipAssemble). |
src/main/groovy/com/deploygate/gradle/plugins/artifacts/PackageAppTaskCompat.groovy |
Removes legacy reflection-based package task compatibility layer. |
README.md |
Documents AGP 9.x support and the new minimum supported AGP/Gradle versions. |
README_JP.md |
Same as README.md, in Japanese. |
build.gradle |
Updates build/test dependencies and JVM args for Gradle 9 / Groovy 4 / JUnit Platform launcher behavior. |
.github/workflows/build-and-test.yml |
Updates the CI matrix to drop AGP 4.2 and add AGP 9.0–9.2 with appropriate Gradle/JDK versions. |
Comments suppressed due to low confidence (2)
src/test/acceptance/com/deploygate/gradle/plugins/ConfigurationCacheSpec.groovy:116
GradleRunner.withGradleVersion(...)is called withTEST_GRADLE_VERSIONdirectly. If that env var is unset (e.g., when running the spec locally),withGradleVersion(null)will fail. Provide a safe fallback to the current Gradle version so the spec can run without requiring env setup.
src/test/acceptance/com/deploygate/gradle/plugins/ConfigurationCacheSpec.groovy:277configurationCacheRunneralso callswithGradleVersion(System.getenv("TEST_GRADLE_VERSION"))without a null-safe fallback, which can break local runs when the env var isn’t set.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Gradle 9 removed/changed several build-script and test-runtime behaviors. Apply
the minimal, backward-compatible fixes so the build configures, compiles, and
runs unit tests on both Gradle 6.7.1 (current wrapper) and Gradle 9.4.1:
- Move sourceCompatibility/targetCompatibility into the `java {}` extension
(the project-level convention was removed in Gradle 9).
- Select the Spock `groovy-4.0` variant when running on Gradle 9.0+ (which
bundles Groovy 4); keep groovy-3.0 / groovy-2.5 for older Gradle.
- Add the JUnit Platform launcher to the test runtime (Gradle 9 no longer
provides it automatically).
- Add `--add-opens` JVM args to Test tasks so ProjectBuilder can inject
synthetic classes under the JDK module system.
- Guard the gradle-tooling-api-builders workaround to Gradle < 7.5; the
internal ModuleRegistry#classpath it relies on was removed in Gradle 9.
- In DeployGatePluginSpec, call the pre-7.1 GradlePropertiesController
workaround reflectively and only on Gradle < 7.1 (the type moved in Gradle 9).
Verified: unit tests green on Gradle 6.7.1 (155/155). On Gradle 9.4.1 only
ConfigurationCacheSpec fails because it hardcodes AGP 4.2.0; that is addressed
by the Variant API migration.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…cts API Replace the deprecated/removed AGP Variant API and the reflection into internal package tasks with the public androidComponents API, per AGP's official guidance. This is the core of AGP 9.x support and removes all AGP-version-specific branching. - DeployGatePlugin: iterate variants via androidComponents.onVariants(...) instead of project.android.applicationVariants. Accessed dynamically (getByName + Action) because the plugin runs on a classloader separate from AGP; a typed reference throws ClassNotFoundException at runtime. - New artifacts/VariantArtifacts: resolves the final APK/AAB via variant.artifacts.get(SingleArtifact.APK/BUNDLE) + BuiltArtifactsLoader, looking up the artifact constants through the variant's classloader. - Delete PackageAppTaskCompat, IApplicationVariant(Impl), and the AGP version gates (isInternalSigningConfigData / hasOutputsHandlerApiOnPackageApplication) with their spec. Signing readiness is no longer pre-checked (server rejects unsigned uploads), per product decision. This raises the effective minimum to AGP 7.0 (androidComponents/Artifacts API). Verified locally (mock server): 12/14 AcceptanceTestSpec pass on AGP 8.13/Gradle 8.13 (all happy paths, customApk, negative cases). Known remaining work: - skipAssemble + pre-built artifact: the strict Artifacts API provider cannot be queried before its producing task completes; needs a dependency-free read path (2 acceptance cases). - ConfigurationCacheSpec hardcodes AGP 4.2.0 (incompatible with the new API); must be parameterized to AGP 7.0+/new DSL, along with the shared acceptance fixture, the version floor, CI matrix, and README (Step 3). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The strict Artifacts API provider cannot be queried unless its producing task runs in the same build, which conflicts with skipAssemble deployments (upload a separately-built artifact without depending on assemble/bundle). For skipAssemble, read the artifact from AGP's conventional output directory (build/outputs/apk/<flavorName>/<buildType>, build/outputs/bundle/<variant>) using the variant's flavorName/buildType and BuiltArtifactsLoader — no dependency on, and no triggering of, the build. The assembled path keeps using the strict provider plus a dependsOn the assemble/bundle task. All 14 AcceptanceTestSpec cases pass on AGP 8.13/Gradle 8.13 (mock server), including the skipAssemble "require assembling/bundling" cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Update the shared acceptance fixture so it builds on AGP 9.x while remaining compatible with AGP 7.0+ (the new minimum): - Use the new DSL property assignment form: compileSdk/minSdk/targetSdk, flavorDimensions += [...], dimension =, applicationIdSuffix = (the legacy compileSdkVersion(int)/dimension(...) methods were removed in AGP 9.0). - Raise compileSdk/targetSdk to 36 and minSdk to 21; drop the pinned buildToolsVersion (AGP defaults to 36.0.0). - Use proguard-android-optimize.txt; proguard-android.txt is rejected in AGP 9 because it implies -dontoptimize. - Declare android:exported on MainActivity (required since Android 12 / targetSdk 31+). Verified: all 14 AcceptanceTestSpec cases pass on both AGP 9.2.0/Gradle 9.4.1 and AGP 8.13.0/Gradle 8.13 via the mock server (real APK/AAB build + upload). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ConfigurationCacheSpec hardcoded AGP 4.2.0 and ran on the ambient Gradle, which is incompatible with the new androidComponents/Artifacts API (AGP 7.0+). Move it into the pluginAcceptanceTest source set so it runs across the AGP/Gradle/JDK matrix, drive the AGP classpath from TEST_AGP_VERSION, pin the inner build to TEST_GRADLE_VERSION via withGradleVersion, and switch the inline projects to the new DSL (extracted into an androidProject() helper). Also migrate the shared project/build.gradle fixture (used by AndroidGradlePluginAcceptanceSpec) to the new DSL + proguard-android-optimize.txt. Verified on the mock server: full testPluginAcceptanceTest green on AGP 9.2.0/ Gradle 9.4.1 and AGP 8.13.0/Gradle 8.13; unit :test green on Gradle 9.4.1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The androidComponents/Artifacts API migration raises the minimum to AGP 7.0, so remove the AGP 4.2.0 rows and add AGP 9.0.0/9.1.0/9.2.0 with their minimum Gradle versions (9.1.0 / 9.3.1 / 9.4.1) on JDK 17. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…pport Reflect the androidComponents/Artifacts API migration: 2.10.0 raises the minimum to AGP 7.0 and Gradle 7.0, and AGP 9.0/9.1/9.2 are now verified in CI. Cap AGP 4.2.x at <2.10.0 and add the 9.x compatibility rows. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
compileSdk 36 (introduced when modernizing the fixtures) is rejected by
AGP 7.0–8.0 — the runtime-env CI matrix failed for those versions. AGP 9.x in
turn requires compileSdk >= 30 ("In order to compile Java 9+ source"). Use
compileSdk/targetSdk 31, which AGP 7.0 supports (its max) and AGP 9.2 accepts.
Verified locally via the mock server: full testPluginAcceptanceTest passes on
AGP 8.0.0/Gradle 8.0 (previously failing) and AGP 9.2.0/Gradle 9.4.1.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Match the AAB only against files (`it.isFile() && ...`), not a directory that happens to end with `.aab`. - Use `variant.getClass()` instead of `variant.class` so dynamic property interception on decorated AGP domain objects cannot shadow the lookup. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CI showed AGP 7.0.0 still failing with compileSdk 31 — AGP 7.0.0 only supports up to compileSdk 30. AGP 9.x requires compileSdk >= 30, so 30 is the single value compatible across the whole AGP 7.0–9.2 matrix. Verified locally (mock server) that full testPluginAcceptanceTest passes on AGP 8.0.0/Gradle 8.0 and AGP 9.2.0/Gradle 9.4.1 with android-30 installed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
….0.0
AGP 7.0.0 exposes `flavorDimensions` as a read-only property, so
`flavorDimensions += [...]` fails ("Cannot set the value of read-only property").
The vararg method forms (`flavorDimensions "a","b"`, `dimension "x"`,
`applicationIdSuffix`, `minSdkVersion`/`targetSdkVersion`, `minifyEnabled`) still
work on AGP 9.2, so use them — only `compileSdk` needs the property form
(`compileSdkVersion` is removed in AGP 9). compileSdk stays at 30 (AGP 7.0.0's max,
and AGP 9.x's minimum).
Verified locally (mock server): full testPluginAcceptanceTest passes on
AGP 7.0.0/Gradle 7.0.2 and AGP 9.2.0/Gradle 9.4.1.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lution - Guard `artifactFileProvider` against a null `artifactFilePath` so an unbuilt skipAssemble deployment (no sourceFile) yields the existing "artifact not found" error instead of an NPE from `new File(null)`. - Resolve `BuiltArtifact.outputFile` against the APK artifact directory when it is relative (it is absolute on supported AGP versions, but resolve defensively). Verified on AGP 9.2.0/Gradle 9.4.1: full AcceptanceTestSpec happy paths, the skipAssemble "should fail" / "require assembling" cases, and unit :test all pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
satsukies
left a comment
There was a problem hiding this comment.
Nice work, thank you!
I added some comments and a question, please check them.
| """ | ||
|
|
||
| when: "Running the task with configuration cache enabled" | ||
| def gradleVersion = System.getenv("TEST_GRADLE_VERSION") |
There was a problem hiding this comment.
[nits]
At line 54, the value "8.7.0" is set as a fallback if System.getenv() returns null.
Would you also consider to handle null at here? (e.g., set fallback value, etc)
There was a problem hiding this comment.
Good catch. I'll add Gradle version fallbacks.
| def elements = builtArtifacts?.elements ?: [] | ||
| File apkFile = elements.isEmpty() ? null : resolveOutputFile(apkDirectory, elements.iterator().next().outputFile) | ||
| // DeployGate only accepts a single universal APK; multiple elements imply split APKs. | ||
| boolean universal = elements.size() == 1 |
There was a problem hiding this comment.
[ask]
loader.load() method read the metadata from buildDir.
elements.size() equals zero before build a project: no artifact exists in buildDir.
So it may be misunderstand as a split apk.
Is this behavior what you expect?
There was a problem hiding this comment.
Nope, 0 shouldn't mean a split apk. I'll fix it, too
| boolean universal = elements.size() == 1 | ||
| // Signing readiness is no longer pre-checked here: the public API does not expose it, | ||
| // and DeployGate rejects unsigned uploads server-side. | ||
| return new DirectApkInfo(variantName, apkFile, true, universal) |
There was a problem hiding this comment.
[note]
3rd argument isSigningReady is always true, it becomes meaningless.
In the future, we can maybe consider refactoring this code.
There was a problem hiding this comment.
Let's remove them. I thought we'll restore it if we find a way to retrieve that status again in the future, but we ain't gonna need it.
… dead isSigningReady - ConfigurationCacheSpec: fall back to Gradle 8.9 when TEST_GRADLE_VERSION is unset so the spec runs locally without env setup. - VariantArtifacts: treat an empty Artifacts result (APK not built yet) as non-split so the null artifact surfaces the clear "artifact was not found" error instead of a misleading "non-universal apk" one. - Remove the now-meaningless isSigningReady flag end to end: the public Artifacts API does not expose signing state and DeployGate rejects unsigned uploads server-side, so the local check was always-true dead code. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
satsukies
left a comment
There was a problem hiding this comment.
Thank you for your great work!
LGTM
Summary
Adds support for the current Android Gradle Plugin (9.x) by migrating the plugin off the deprecated/removed Variant API and the reflection into AGP's internal package tasks, onto the public
androidComponents+ Artifacts API — exactly as AGP's official migration guidance instructs. This removes all AGP-version-specific branching and was validated end-to-end (real APK/AAB build + upload) against AGP 9.2.0 / Gradle 9.4.1 and AGP 8.13.0 / Gradle 8.13 locally via the DeployGate mock server.The minimum supported AGP is raised to 7.0 (Gradle 7.0+); AGP 4.2.x–6.x are no longer compatible because they predate
androidComponents.What changed
Core migration
androidComponents.onVariants(...)instead ofproject.android.applicationVariants. Accessed dynamically (getByName+org.gradle.api.Action) because the plugin runs on a classloader separate from AGP — a typed reference throwsClassNotFoundExceptionat runtime.artifacts/VariantArtifacts: resolves the final APK/AAB throughvariant.artifacts.get(SingleArtifact.APK/BUNDLE)+BuiltArtifactsLoader, looking up the artifact constants via the variant's classloader (getClass().classLoader).PackageAppTaskCompat,IApplicationVariant(Impl), and the AGP version gates (isInternalSigningConfigData/hasOutputsHandlerApiOnPackageApplication) — no longer needed with the stable public API.skipAssembledeployments read from AGP's conventional output directory (build/outputs/apk/<flavorName>/<buildType>,build/outputs/bundle/<variant>) so a separately-built artifact can still be uploaded without depending on (or triggering) the build. The strict Artifacts API provider cannot be queried before its producing task runs, so this path intentionally avoids it.SigningConfigexposes no such getter); DeployGate rejects unsigned uploads server-side.Gradle 9 build/test compatibility
java {}extension for source/target compatibility; Spockgroovy-4.0on Gradle 9; JUnit Platform launcher on the test runtime;--add-opensforProjectBuilder; guarded the pre-7.5ModuleRegistryworkaround and the pre-7.1GradlePropertiesControllertest workaround.Tests & fixtures
ConfigurationCacheSpecmoved into the acceptance source set and parameterized byTEST_AGP_VERSION/TEST_GRADLE_VERSION(it previously hardcoded AGP 4.2.0).compileSdkVersion→compileSdk(property) is required for AGP 9 (the method was removed); set to 30, which is AGP 7.0.0's maximum and AGP 9.x's minimum compileSdk. The flavor DSL keeps the vararg method form (flavorDimensions "a","b",dimension "x") because AGP 7.0.0 exposesflavorDimensionsas a read-only property (so+= [...]fails there), while the method forms still work on AGP 9.2. Also:proguard-android-optimize.txt(AGP 9 rejectsproguard-android.txt) andandroid:exportedon the launcher activity.CI & docs
Verification
The full CI matrix is green —
acceptance-test-runtime-envpasses for all 22 AGP versions (7.0.0 → 9.2.0), plus lint, unit, and unroll-acceptance jobs.Additionally run locally against the DeployGate mock server (real build + upload):
testPluginAcceptanceTest(full)ConfigurationCacheSpec:testChangelog (targeting 2.10.0)
Proposed CHANGELOG entry for this PR (to be combined with the configuration-cache work in #272):
androidComponents+ Artifacts API (no reflection into internal AGP tasks), preserving configuration-cache compatibility.skipAssembledeployments without an explicitsourceFilenow resolve the artifact from AGP's conventional output directory (build/outputs/apk/<flavor>/<buildType>,build/outputs/bundle/<variant>) instead of reading the package task's configured output location. Behavior is unchanged for standard projects; projects that relocate the APK/AAB output should setsourceFileexplicitly.Breaking Changes
androidComponents/Artifacts API, which does not exist before AGP 7.0).Notes for review
feat/support-configuration-cache(not yet merged), so this PR shows only the AGP 9.x migration on top of it.🤖 Generated with Claude Code