[AGP 9.1.0 Migration #7] Migrate the flutter-apk copy and per-ABI versionCode to the variant API - #193693
Draft
reidbaker-agent wants to merge 1 commit into
Draft
[AGP 9.1.0 Migration #7] Migrate the flutter-apk copy and per-ABI versionCode to the variant API#193693reidbaker-agent wants to merge 1 commit into
reidbaker-agent wants to merge 1 commit into
Conversation
The application path of the Flutter Gradle Plugin no longer uses the deprecated BaseVariant API (applicationVariants / ApkVariantOutput): - CopyFlutterApksTask copies the variant's SingleArtifact.APK outputs, read through BuiltArtifactsLoader, into build/outputs/flutter-apk under the unchanged names app[-abi][-flavor]-<mode>.apk. assemble<Variant> depends on it. The task has no Project or variant fields and uses an injected FileSystemOperations. - A single naming function in the task produces both the declared @OutputFiles and the copied file names. Its inputs are the ABIs of the variant's outputs (what AGP builds), the flavor, and the build mode. The task fails if AGP built an APK for an ABI that was not declared. - Per-ABI versionCode for --split-per-abi is a read-then-set on VariantOutput.versionCode in onVariants. This is a documented exception to the no-configuration-time-get rule, because the lazy form is circular. The read relies on AGP's compatibility mode (android.compatibility.enableLegacyApi, on by default through 9.x). - The applicationVariants.configureEach block and its @Suppress("DEPRECATION") markers are deleted. The add-to-app module path keeps using libraryVariants until the next phase. Behavior change: Flutter's onVariants callback runs before an app's own androidComponents.onVariants block, while ApkVariantOutput's versionCodeOverride was applied after it. Apps that transform output.versionCode there transform Flutter's offset value (for example arm64-v8a, build 42, x10000: 422000 -> 20420000). The migration doc and the breaking-change page draft describe this and give a recipe that keeps the 422000-style numbers. No divergence warning is emitted, because it would fire on the onVariants pattern that AGP recommends. Part of flutter#166550.
reidbaker
force-pushed
the
agp-apk-copy-versioncode
branch
from
October 2, 2026 01:38
79ef973 to
e15544b
Compare
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.
Description
This is PR 7 of 11 in the AGP 9.1.0 / public
gradle-apimigration stack (#180137, #166550).Base:
masterat6564794ab15, which contains PR 6 (#192488) as merge commit65afe589f29c9941c2473229aba964495782feaf. This PR is a single commit on top of it.Agent authored details
Changes
CopyFlutterApksTask(copyFlutterApks<Variant>) copies the variant'sSingleArtifact.APKoutputs, read throughBuiltArtifactsLoader, intobuild/app/outputs/flutter-apk/under the same names as before (app[-abi][-flavor]-<mode>.apk, matchinglistApkPathsinlib/src/android/gradle.dart).assemble<Variant>depends on it.Projector variant fields; injectedFileSystemOperations;@InputDirectory @PathSensitive(RELATIVE);@DisableCachingByDefault.@OutputFiles), not the sharedflutter-apkdirectory, which would overlap between variants.CopyFlutterApksTask.apkFileName) produces both the declared outputs and the copied files, from explicit inputs: the ABIs of the variant's outputs, the flavor, and the build mode. The task fails if AGP built an APK for an ABI it did not declare, rather than writing an untracked file.--split-per-abi,onVariantssetsoutput.versionCode = ABI_VERSION[abi] * 1000 + versionCodeon eachApplicationVariantoutput with an ABI filter. This is a read-then-set at configuration time: a documented exception to the "no.get()at configuration time" rule inpackages/flutter_tools/gradle/AGENTS.md, because the lazy.mapform is circular.applicationVariants.configureEachblock,configureAbiVersionCodeOverride, and their two@Suppress("DEPRECATION")markers and unsafeas ApkVariantOutputcast. The add-to-app module path (addFlutterDepsForModule,libraryVariants) is intentionally untouched until PR 8.registerFlutterAssetTasksshould not register the compile task):onVariantscallsconfigureSplitPerAbiVersionCodesandregisterCopyFlutterApksTaskafterregisterFlutterAssetTasks, under the sameisApplicationProject && shouldCompileFlutterForVariantgate. Each helper does one job, and no parameters are only forwarded.Migrating-Flutter-Gradle-Plugin-to-AGP-public-API.md(decision record 4 spike result; "Features that must break" items 2 and 3) andwebsite-page-draft.md(versionCode note and recipe).CopyFlutterApksTaskTest(5 tests); 6 newFlutterPluginTestcases (versionCode offsets, no split, force flag, copy wiring, profile/custom build type naming, assemble wiring);flutter_build_apk_split_per_abi_test.dartupdated for the formula change and now also asserts each per-ABI APK is influtter-apk.Changes from the earlier draft of this PR
An earlier draft (cut from a pre-review version of PR 6) was audited in reidbaker-agent/flutter#7 (comment). This version is rewritten on landed PR 6 and resolves each item:
task.outputs.filesequals the files written.Projectpassed to a helper only to re-read propertiesPLATFORM_ARCH_MAPlookup producedapp-null-*.apkonVariantspattern (this repo's own fixture)addFlutterDepsshouldCompileFlutterForVariant,shouldConfigureFlutterTaskKDoc).Note: the audit's example numbers (
423000 -> 30420000) used offset 3 forarm64-v8a;ABI_VERSION["arm64-v8a"]is 2, so the docs use422000 -> 20420000.Why removing the deleted code is safe
The deleted code used
AbstractAppExtension.applicationVariants,ApkVariantOutput.versionCodeOverride/getFilter, andpackageApplicationProvider, which AGP deprecated and which are not available with AGP 9'sandroid.newDsl=true. The replacements (ApplicationVariant.outputs,VariantOutput.versionCode/filters,SingleArtifact.APK,BuiltArtifactsLoader) are publiccom.android.build.apiAPIs available at the stack's AGP 8.11.1 floor. The Gradle unit tests pass on both the default (AGP 8.11.1) and AGP 9 lines.Behavioral and compatibility notes
versionCode for apps that transform it in their own
onVariants(breaking). Flutter'sonVariantscallback is registered when the plugin is applied, so AGP runs it before an app'sandroidComponents.onVariantsblock.versionCodeOverridewas applied after that block. Apps that setversionCodeonly in theandroid {}DSL see no change. An app that multiplies by 10000, built with--build-number 42:armeabi-v7a(1)arm64-v8a(2)x86_64(4)Values stay distinct, keep ABI order, and only go up, so Play upgrades keep working. The docs give a recipe to keep the "Before" numbers. An untested alternative that could keep the "Before" ordering is registering Flutter's
onVariantsfrom insidefinalizeDsl; it was not pursued in favor of documenting the change.The versionCode read depends on AGP's compatibility mode. With
android.compatibility.enableLegacyApi=false(deprecated opt-out, defaulttruethrough AGP 9.x), AGP disallows readingoutput.versionCodeduring configuration and the build fails. The app-sideoutput.versionCode.get()pattern fails the same way. Recorded in decision record 4 with a note to re-test on AGP 10 previews.copyFlutterApks<Variant>tasks appear ingradlew tasksand can be UP-TO-DATE.Two build types that map to the same Flutter mode share a file name (e.g.
debugand a debuggablestagingboth writeapp-debug.apk). The deleted code had the same clash. Running both assembles in one./gradlewinvocation makes the two copy tasks alternate; thefluttertool builds one variant per invocation, so it is unaffected.No configuration-time assertion that
assemble<Variant>exists. The dependency is attached by name withtasks.configureEach(the variant API has no assemble provider, andtasks.named(Spec)is@Incubatingin the Gradle API the plugin compiles against). If the task were missing, the Flutter tool already fails with a clear "APK not found" error.Integration test coverage of the changed paths
onVariantstransformflutter_build_apk_split_per_abi_test.dart(3 tests; asserts versionCodes viaapkanalyzerand the per-ABI files influtter-apk)android_gradle_asset_merging_test.dart,gradle_libapp_so_packaging_test.dart(incl. flavors),android_plugin_example_app_build_test.dart,isolated/native_assets_flutter_build_test.dart; devicelabflavors_test,gradle_desugar_classes_testandroid_run_flutter_gradle_plugin_tests_test.dartTests run locally (on the rebased branch)
./gradlew testinpackages/flutter_tools/gradle(JDK 17): pass (32 suites, 0 failures).editorconfigand baseline: cleandart format/dart analyze --fatal-infoson the changed Dart test: cleanflutter_build_apk_split_per_abi_test,android_gradle_asset_merging_test,gradle_libapp_so_packaging_test,android_run_flutter_gradle_plugin_tests_test: all pass (3, 4, 4 and 2 tests)Pre-launch Checklist
///).