Skip to content

[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
flutter:masterfrom
reidbaker-agent:agp-apk-copy-versioncode
Draft

reidbaker-agent wants to merge 1 commit into
flutter:masterfrom
reidbaker-agent:agp-apk-copy-versioncode

Conversation

@reidbaker-agent

Copy link
Copy Markdown
Contributor

Description

This is PR 7 of 11 in the AGP 9.1.0 / public gradle-api migration stack (#180137, #166550).

Base: master at 6564794ab15, which contains PR 6 (#192488) as merge commit 65afe589f29c9941c2473229aba964495782feaf. This PR is a single commit on top of it.

Agent authored details

Changes

  1. flutter-apk copy on the variant API. CopyFlutterApksTask (copyFlutterApks<Variant>) copies the variant's SingleArtifact.APK outputs, read through BuiltArtifactsLoader, into build/app/outputs/flutter-apk/ under the same names as before (app[-abi][-flavor]-<mode>.apk, matching listApkPaths in lib/src/android/gradle.dart). assemble<Variant> depends on it.
    • No Project or variant fields; injected FileSystemOperations; @InputDirectory @PathSensitive(RELATIVE); @DisableCachingByDefault.
    • Declared outputs are the individual APK files (@OutputFiles), not the shared flutter-apk directory, which would overlap between variants.
    • One naming function (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.
  2. Per-ABI versionCode on the variant API. For --split-per-abi, onVariants sets output.versionCode = ABI_VERSION[abi] * 1000 + versionCode on each ApplicationVariant output with an ABI filter. This is a read-then-set at configuration time: a documented exception to the "no .get() at configuration time" rule in packages/flutter_tools/gradle/AGENTS.md, because the lazy .map form is circular.
  3. Deleted the app-path applicationVariants.configureEach block, configureAbiVersionCodeOverride, and their two @Suppress("DEPRECATION") markers and unsafe as ApkVariantOutput cast. The add-to-app module path (addFlutterDepsForModule, libraryVariants) is intentionally untouched until PR 8.
  4. Structure follows the PR 6 review (@mboetger's point that registerFlutterAssetTasks should not register the compile task): onVariants calls configureSplitPerAbiVersionCodes and registerCopyFlutterApksTask after registerFlutterAssetTasks, under the same isApplicationProject && shouldCompileFlutterForVariant gate. Each helper does one job, and no parameters are only forwarded.
  5. Docs: Migrating-Flutter-Gradle-Plugin-to-AGP-public-API.md (decision record 4 spike result; "Features that must break" items 2 and 3) and website-page-draft.md (versionCode note and recipe).
  6. Tests: new CopyFlutterApksTaskTest (5 tests); 6 new FlutterPluginTest cases (versionCode offsets, no split, force flag, copy wiring, profile/custom build type naming, assemble wiring); flutter_build_apk_split_per_abi_test.dart updated for the formula change and now also asserts each per-ABI APK is in flutter-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:

Audit item Resolution
APK names computed twice (plugin for declared outputs, task for real names), so they could drift One naming function in the task, used for both. A unit test asserts task.outputs.files equals the files written.
Project passed to a helper only to re-read properties Removed. Names come from task inputs.
Null PLATFORM_ARCH_MAP lookup produced app-null-*.apk ABIs come from the variant outputs' ABI filters; an output without one maps to the non-ABI name.
versionCode divergence warning fires on AGP's recommended onVariants pattern (this repo's own fixture) No warning. The docs explain why.
Formula change is user-visible Test name and comments updated; breaking-change notes and a migration recipe added (see below).
Stale comments referring to the deleted callback / addFlutterDeps Updated (shouldCompileFlutterForVariant, shouldConfigureFlutterTask KDoc).

Note: the audit's example numbers (423000 -> 30420000) used offset 3 for arm64-v8a; ABI_VERSION["arm64-v8a"] is 2, so the docs use 422000 -> 20420000.

Why removing the deleted code is safe

The deleted code used AbstractAppExtension.applicationVariants, ApkVariantOutput.versionCodeOverride/getFilter, and packageApplicationProvider, which AGP deprecated and which are not available with AGP 9's android.newDsl=true. The replacements (ApplicationVariant.outputs, VariantOutput.versionCode/filters, SingleArtifact.APK, BuiltArtifactsLoader) are public com.android.build.api APIs 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

  1. versionCode for apps that transform it in their own onVariants (breaking). Flutter's onVariants callback is registered when the plugin is applied, so AGP runs it before an app's androidComponents.onVariants block. versionCodeOverride was applied after that block. Apps that set versionCode only in the android {} DSL see no change. An app that multiplies by 10000, built with --build-number 42:

    ABI (offset) Before After
    armeabi-v7a (1) 421000 10420000
    arm64-v8a (2) 422000 20420000
    x86_64 (4) 424000 40420000

    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 onVariants from inside finalizeDsl; it was not pursued in favor of documenting the change.

  2. The versionCode read depends on AGP's compatibility mode. With android.compatibility.enableLegacyApi=false (deprecated opt-out, default true through AGP 9.x), AGP disallows reading output.versionCode during configuration and the build fails. The app-side output.versionCode.get() pattern fails the same way. Recorded in decision record 4 with a note to re-test on AGP 10 previews.

  3. copyFlutterApks<Variant> tasks appear in gradlew tasks and can be UP-TO-DATE.

  4. Two build types that map to the same Flutter mode share a file name (e.g. debug and a debuggable staging both write app-debug.apk). The deleted code had the same clash. Running both assembles in one ./gradlew invocation makes the two copy tasks alternate; the flutter tool builds one variant per invocation, so it is unaffected.

  5. No configuration-time assertion that assemble<Variant> exists. The dependency is attached by name with tasks.configureEach (the variant API has no assemble provider, and tasks.named(Spec) is @Incubating in 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

Path Tests
Per-ABI versionCode, with and without an app onVariants transform flutter_build_apk_split_per_abi_test.dart (3 tests; asserts versionCodes via apkanalyzer and the per-ABI files in flutter-apk)
flutter-apk copy, default and flavored 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; devicelab flavors_test, gradle_desugar_classes_test
Plugin unit tests on AGP 8.11.1 and AGP 9 android_run_flutter_gradle_plugin_tests_test.dart

Tests run locally (on the rebased branch)

  • ./gradlew test in packages/flutter_tools/gradle (JDK 17): pass (32 suites, 0 failures)
  • ktlint 1.5.0 with the CI .editorconfig and baseline: clean
  • dart format / dart analyze --fatal-infos on the changed Dart test: clean
  • Integration, run one file at a time: flutter_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

@github-actions github-actions Bot added platform-android Android applications specifically tool Affects the "flutter" command-line tool. See also t: labels. d: docs/ flutter/flutter/docs, for contributors labels Oct 2, 2026
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
reidbaker force-pushed the agp-apk-copy-versioncode branch from 79ef973 to e15544b Compare October 2, 2026 01:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

d: docs/ flutter/flutter/docs, for contributors platform-android Android applications specifically tool Affects the "flutter" command-line tool. See also t: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant