Repository navigation
Raise Minimum Version to AGP 9.1.1 - #193813
Conversation
AGP 9.2.x fails to apply with 'Minimum supported Gradle version is 9.4.1' when run with Gradle 9.3.1. Update both validateGradleAndAgp and getGradleVersionFor so flutter tool compatibility checks and suggestions match AGP's real requirement.
Error versions: AGP 9.1.1, Gradle 9.3.1, KGP 2.4.10. Warn versions: AGP 9.3.1, Gradle 9.5.0, KGP 2.4.20 (current template versions). FlutterPluginUtilsTest used errorAGPVersion as a stand-in for an AGP version below 9. Now that the error version is AGP 9.1.1, use an explicit AGP 8.13.0 constant for those AGP 8 behavior tests.
- android_dependency_version_checking_test: use AGP 9.1.1 / Gradle 9.3.1 with KGP 2.4.20 (AGP and Gradle warn band) or KGP 2.4.10 (KGP warn band). - android_java17_dependency_smoke_tests: minimum supported is now AGP 9.1.1 / Gradle 9.3.1 / KGP 2.4.10. Drop the 8.12.0 and 9.1.0 tuples, which are below the new error versions.
There was a problem hiding this comment.
Code Review
This pull request updates the warning and error thresholds for Gradle, AGP, and Kotlin versions in DependencyVersionChecker.kt and gradle_utils.dart, and aligns the corresponding unit and integration tests. Additionally, the compatibility mapping for AGP 9.2.0 is updated to require Gradle 9.4.1. Feedback suggests updating one of the integration tests to use AGP 9.2.0 and Gradle 9.4.1 to avoid redundancy, and adding this new version combination to the device lab dependency smoke tests.
The AGP and Gradle warn-band tests built identical projects. Use Gradle 9.5.0 (the warn version) in the AGP test so only the AGP warning is expected, and assert that the Gradle warning is not printed. The Gradle test cannot be isolated the same way because AGP 9.3.x (the AGP warn version) requires Gradle 9.5.0.
reidbaker
left a comment
There was a problem hiding this comment.
I think you also need to modify build.gradle.kts to build against AGP 9.1.1 this was in https://github.com/jesswrd/flutter/blob/5149053cf2211aa46f115a58bebc480bd93affbb/packages/flutter_tools/lib/src/android/README.md but it was under the Android SDK & NDK section not an AGP section.
| @VisibleForTesting internal val warnGradleVersion: Version = Version(9, 5, 0) | ||
|
|
||
| @VisibleForTesting internal val errorGradleVersion: Version = Version(8, 14, 0) | ||
| @VisibleForTesting internal val errorGradleVersion: Version = Version(9, 3, 1) |
There was a problem hiding this comment.
FYI you are bumping the error past the warn version for all of these. I think it is ok but wanted to make sure you knew. Also I think the AGP bump is justified to support api 37 but I think the kgp one might not be required.
There was a problem hiding this comment.
FYI you are bumping the error past the warn version for all of these.
If you mean I am bumping the new Gradle error version past the old Gradle warn version, then yes. But I am also bumping the warn versions up. Do you think I am bumping the error versions too high?
Also I think the AGP bump is justified to support api 37 but I think the kgp one might not be required.
I bumped all versions based on the AGP 9.1.1 requirements. I think the KGP bump is required because I bumped AGP to 9.1.1 here. I tried building apps with the new error and warn versions, and they successfully built.
| ); | ||
| expect(result, const ProcessResultMatcher()); | ||
| expect(result.stderr, contains('Please upgrade your Android Gradle Plugin version')); | ||
| expect(result.stderr, isNot(contains('Please upgrade your Gradle version'))); |
There was a problem hiding this comment.
Is this test that we output or something that one of the tools we depend on outputs.
There was a problem hiding this comment.
The warn message comes from the Flutter Gradle plugin (DependencyVersionChecker.kt
). This line checks that the Gradle version is above the warn range, so only the AGP warning prints.
This test only asserts that the AGP warning prints, so it probably doesn't matter whether or not the Gradle version is in or above the warn range. Let me remove the line.
There was a problem hiding this comment.
If it comes from us I am happy if you want us to keep it. I just wanted to make sure we were testing code in our control.
….4.10 Align the default AGP version and the kotlin-gradle-plugin dependency in packages/flutter_tools/gradle/build.gradle.kts with DependencyVersionChecker.errorAGPVersion and errorKGPVersion.
Minimum supported KGP is now 2.4.10, so the plugin can depend on the latest stable kotlinx-serialization (1.11.0, built with Kotlin 2.3.20).
… variant API (flutter#193717) PR 7 ([flutter#193693](flutter#193693)) merged to `master` as `ecd1ab06865`, and `23ddd6b74e6` merges `master` into this branch, so the [Files changed](https://github.com/flutter/flutter/pull/193717/files) tab shows only PR 8's changes. PR 8 commits: - `8c9a54d8297` Unify add-to-app module wiring on the variant API - `2c28d8f8245` Merge PR 7 head `f668780fd47` (conflicts resolved to PR 7's wording where PR 8 does not change the code) - `a6be16034c8` Trim comments added by PR 8 - `b8cd39bcf81` Address review: clarify the variant gate and task registration comments - `2efbd345505` Address review: check the add-to-app module in module_host_with_custom_build_test - `d8f869ec468` Address review: share task registration stubs in FlutterPluginTest - `798ac295725` Address review: take ApplicationVariant in configureApplicationOutputs - `1366874099e` Address review: drop android.compatibility.enableLegacyApi from the qa section - `eb20bbd43de` Merge PR 7 head `25550b3fc49` (no conflicts) - `23ddd6b74e6` Merge `master` at `ecd1ab06865` (PR 7 squash-merged). `FlutterPlugin.kt`, `FlutterPluginUtils.kt` and `FlutterPluginTest.kt` conflicted only because the squash commit is not an ancestor; `master`'s copies are identical to `25550b3fc49`, already merged by `eb20bbd43de`, so this branch's side was kept. ## Description This is PR 8 of 11 in the AGP 9.1.0 / public `gradle-api` migration stack ([flutter#180137](flutter#180137), [flutter#166550](flutter#166550)). An add-to-app module (an Android library) is wired through the same `androidComponents.onVariants` path as an app. The module adds Flutter's assets and native libraries to its own variants, and the host app consumes them like any Android library. Flutter does not look up or configure the host project. With this PR, a host app with AGP 9's `android.newDsl=true` builds a Flutter module from source; on `33f39876b92` it fails with `Check failed.` while configuring `:app`. **Breaking:** `flutter.hostAppProjectName` has no effect. Setting it logs a warning. **Base:** `master`. PR 7 (flutter#193693) merged as `ecd1ab06865`. <details> <summary>Agent authored details</summary> ### Changes 1. **Module variants on `onVariants`** (`FlutterPlugin.kt` `addFlutterTasks`). For every variant, app or library, `onVariants` calls `registerFlutterCompileTask`, `registerFlutterAssetTasks` and `registerFlutterJniLibsTask`. Only application variants then call the new `configureApplicationOutputs` (per-ABI versionCodes and the flutter-apk copy), which takes an `ApplicationVariant`. There is no mid-lambda `return@onVariants`. 2. **Every module variant is configured.** `shouldCompileFlutterForVariant` returns true for a `LibraryVariant`. The command-line check (`shouldConfigureFlutterTask`, flutter#109560) compares the command line with the project's own `assemble<Variant>` names, and a host task such as `:app:assembleDemoStaging` never matches the module variant it consumes (`debug`, via `matchingFallbacks`). A module has three variants: the template's `debug` and `release`, plus the `profile` build type the plugin creates. It declares no flavors (`templates/module/android/library_new_embedding/Flutter.tmpl/build.gradle.tmpl`). The tasks are registered lazily, so a build runs only the module variant the host consumes. `--dry-run` on a host with flavor `demo` and build types `staging` (falls back to `debug`) and `prod` (falls back to `release`): | Host task | Module Flutter tasks in the task graph | | --- | --- | | `app:assembleDemoDebug`, `app:assembleDemoStaging` | `compileFlutterBuildDebug`, `copyFlutterAssetsDebug`, `copyJniLibsflutterBuildDebug` | | `app:assembleDemoRelease`, `app:assembleDemoProd` | `compileFlutterBuildRelease`, `copyFlutterAssetsRelease`, `copyJniLibsflutterBuildRelease` | 3. **Build mode from the module variant.** `registerFlutterCompileTask` reads the library variant's `buildType` and public `Component.debuggable`, so the mode follows the module variant the host consumes. 4. **jniLibs wired from the compile task provider.** `registerFlutterJniLibsTask` takes `compileTaskProvider` and sets `intermediateDir` from `compileTaskProvider.map { it.outputDirectory }`. The `tasks.matching { it.name == … }` dependency and the `findByName` provider are gone. Both copy tasks are registered only together with their compile task, so `intermediateDir` is required (not `@Optional`) in `CopyFlutterJniLibsTask` and `CopyFlutterAssetsTask`, and their `isPresent` branches are removed. 5. **`flutter.hostAppProjectName`.** `warnIfHostAppProjectNameIsSet` reads `project.providers.gradleProperty(…)` (not `rootProject.hasProperty`) and logs `Warning: The Gradle property 'flutter.hostAppProjectName' has no effect. Remove it from gradle.properties.` The `flutter.hostAppProjectName=SampleApp` line is removed from `dev/integration_tests/pure_android_host_apps/android_custom_host_app/gradle.properties`, so devicelab `module_custom_host_app_name_test` (host project `:SampleApp`) checks that a host not named `app` builds without the property. 6. **Deleted:** - From `FlutterPlugin.kt`: the host-project lookup, the `appProject.afterEvaluate` × `libraryVariants.all` × `applicationVariants.all` loop, the `merge<HostVariant>Assets.dependsOn` edge, `addFlutterDepsForModule` and `addCopyFlutterAssetsDependency`. - From `FlutterPluginUtils.kt`: `shouldConfigureFlutterTask(Project, Task)` and `buildModeFor(com.android.builder.model.BuildType)`. Both lost their last caller. 7. **Docs.** - `Migrating-Flutter-Gradle-Plugin-to-AGP-public-API.md`: decision record 5 and "Features that must break" item 6 are rewritten. - [`website-page-draft.md`](https://github.com/flutter/flutter/blob/master/docs/platforms/android/website-page-draft.md) (publishing tracked in [flutter#193713](flutter#193713)): the add-to-app section is rewritten. - The text these replace said that the warning names a removal milestone (it does not), and that a host `staging` debuggable build type gets debug artifacts (the module variant decides). It also said that `tasks.getByPath(":flutter:copyFlutterAssetsDebug")` fails with "Task with path … not found". That is false: in the scratch host below it returns a `CopyFlutterAssetsTask`. 8. **Tests.** - New `FlutterPluginTest` cases: - jniLibs wiring from the compile task provider, with no name lookups; - a module variant configured for a single unrelated command-line task, with no APK wiring; - the module build mode for `debug`, `profile` and `release`; - the `hostAppProjectName` warning, with no host lookup, and no warning when it is unset. - The skip test also checks that no jniLibs task is registered. - The new tests share stub helpers (`stubTaskRegistration`, `captureTaskConfiguration`, `setCommandLineTasks`). - devicelab `module_host_with_custom_build_test` gets a final section. The host fixture adds a build type `qa` (debuggable, falls back to `release`). The section runs `app:assembleDemoQa` with `android.newDsl=true` and `flutter.hostAppProjectName=app`. It checks that the output has the warning, and that `app-demo-qa.apk` has the Flutter assets and `libapp.so` for arm64-v8a and armeabi-v7a, and no debug assets. The existing sections already check `app:assembleDemoStaging` (debug artifacts). ### Audit resolution [PR 8 draft audit](reidbaker-agent#8 (comment)): | Item | Resolution | | --- | --- | | 1. String-based compile task lookup for jniLibs | Provider wiring, `FlutterPlugin.kt:549-553`. Unit test `onVariants wires the jniLibs copy to the output of the variant's compile task`. | | 2. Mid-block `return@onVariants` | None. `onVariants` (`:329-350`) calls helpers; app-only wiring is in `configureApplicationOutputs` (`:647`). | | 3. Redundant `dependsOn` | None on the assets copy (`:497-530`) or the jniLibs copy (`:536-560`). | | 4. Dead overload; `rootProject` read; fixture property | `shouldConfigureFlutterTask(Project, Task)` deleted. `providers.gradleProperty` at `:665`. Fixture line removed. | [PR 7 draft audit](reidbaker-agent#7 (comment)): its four items were resolved in flutter#193693. The stale "registered in `applicationVariants`" KDoc it named has no counterpart here; the `registerFlutterCompileTask` KDoc names no project type. ### Why removing the deleted code is safe - The deleted module path used `com.android.build.gradle.LibraryExtension.libraryVariants`, `AbstractAppExtension.applicationVariants`, `BaseVariant.mergeAssetsProvider` and `BaseVariantOutput.processResourcesProvider`. AGP does not register those extensions with `android.newDsl=true`, which is why `33f39876b92` fails with `Check failed.` in the host `afterEvaluate` block that looks up `LibraryExtension`. Its replacements (`LibraryVariant`, `Component.debuggable`, `Sources.assets`/`jniLibs.addGeneratedSourceDirectory`) are public `com.android.build.api` APIs. FGP compiles against AGP 9.1.1, the minimum on `master` (flutter#193813), and `Component.getDebuggable` also exists in gradle-api 8.11.1 (checked with `javap`). - Remaining `hostAppProjectName` users: none. `git grep hostAppProjectName` hits only this PR's code, tests and docs, plus an unrelated iOS `XcodeProject.hostAppProjectName` in the tool. The templates, `include_flutter.groovy`, `module_plugin_loader.gradle` and devicelab do not read it. devicelab `module_custom_host_app_name_test` (host project `:SampleApp`) passes without the fixture property. - `com.android.builder.model.BuildType` and `shouldConfigureFlutterTask(Project, Task)` have no remaining references. ### Removed comments, docstrings and tests | Removed | Reason | | --- | --- | | KDoc and two `TODO(gmackall)` comments (flutter#166550) on `addFlutterDepsForModule`; KDoc on `addCopyFlutterAssetsDependency` | Functions deleted. flutter#166550 stays open for the rest of the stack. | | The "host app build variant → Flutter variant" table comment in the module block | The mapping is gone; the module variant decides the mode. | | KDoc of `shouldConfigureFlutterTask(Project, Task)` and `buildModeFor(ModelBuildType)` | Functions deleted. | | `flutterCompileTaskName` KDoc | Nothing references the task by name, and the one-line name builder needs no doc. | | flutter#188785 comment in `registerFlutterJniLibsTask`, and the `@Optional` KDoc and "no Flutter build" comment in `CopyFlutterJniLibsTask` | The jniLibs task is registered only with its compile task. `gradle_libapp_so_packaging_test` "app:assembleAndroidTest builds when no Flutter compile task is configured" still passes. | | `@Optional` KDoc on `CopyFlutterAssetsTask.intermediateDir` (it kept `@Optional` for the module migration) | The migration is this PR. | | `FlutterPluginUtilsTest`: `buildModeFor returns profile if the BuildType has name profile`, `buildModeFor returns debug if the BuildType is debuggable`, `buildModeFor returns release if the BuildType is not debuggable and not named profile` | Overload deleted. `buildModeFor with a name and debuggable flag prefers the profile name over debuggability` covers the same cases. | | `FlutterPluginUtilsTest`: four `shouldConfigureFlutterTask` tests used a mocked `Task` | Kept with the same names and inputs, calling the `String` overload. | | `CopyFlutterAssetsTaskTest`: `clears the destination directory when there is no flutter build for the variant` | The input is required, so that case cannot happen. Replaced by `removes staged assets that the flutter build does not produce`, which keeps the stale-file check. | | Comments trimmed per review (`a6be16034c8`): the `PROP_HOST_APP_PROJECT_NAME` KDoc; the `registerFlutterJniLibsTask` KDoc sentence naming its compile task; the "An application project only has application variants." comment above the `check`; restatements in the KDoc of `shouldCompileFlutterForVariant` (PR 7's paragraph restored verbatim), `registerFlutterCompileTask`, `configureApplicationOutputs`, `warnIfHostAppProjectNameIsSet` and both `intermediateDir`s; test helper KDocs and integration test comments; migration doc item 6 | Each restated the code. What remains is why library variants are ungated, why the inputs are required, and the test intent. Added code-comment lines vs `f668780fd47`: 85 → 39. | | `packages/flutter_tools/test/integration.shard/android_add_to_app_module_test.dart` (added by this PR, then deleted per review, `2efbd345505`) | Its `qa` check moved into devicelab `module_host_with_custom_build_test`, which builds the same fixture. Its `staging` check duplicated the devicelab staging section, except for `android.newDsl=true`. The devicelab task runs presubmit on Linux, Mac and Windows (`.ci.yaml`), like the deleted test. | | Rewritten per review (`b8cd39bcf81`): the library paragraph of the `shouldCompileFlutterForVariant` KDoc, the assets `GradleException` comment, the `registerFlutterCompileTask` and `configureApplicationOutputs` KDocs | They explained too little ("What does this mean?") or restated the code. | | The `check(variant is ApplicationVariant)` that PR 7 added in the app `onVariants` path, moved by this PR into `configureApplicationOutputs` (`798ac295725`) | The call site branches on `variant is ApplicationVariant`, and the function takes an `ApplicationVariant`, so the compiler enforces the type. In an application project every variant is an `ApplicationVariant`, so the APK wiring runs for the same variants. | | `-Pandroid.compatibility.enableLegacyApi=false` in the devicelab `qa` section (`1366874099e`) | AGP 9.1.0, 9.1.1 and 9.3.1 mark the option `Deprecated(VERSION_10_0)`. Its consumers back the variant API that `android.newDsl=true` removes. | ### Behavioral and compatibility notes 1. **`flutter.hostAppProjectName` has no effect (breaking).** It logs a warning and names no removal milestone. Its only use was the host lookup. 2. **The Flutter build mode follows the module variant the host consumes.** A host `debug`/`profile`/`release` consumes the module variant of the same name. A custom host build type gets the module variant its `matchingFallbacks` select. With a debuggable `qa` that falls back to `release`, built as the only task, the APKs contain: | Build | `flutter_assets` | `libapp.so` | Flutter tasks run | | --- | --- | --- | --- | | `33f39876b92` (`newDsl=false`) | no | no | `compileFlutterBuildDebug` (not packaged) | | This PR | yes | yes | `compileFlutterBuildRelease` | 3. **A host `assemble<Variant>AndroidTest` run on its own runs the module's `flutter assemble` for that variant.** `--dry-run` of `:app:assembleDemoDebugAndroidTest` lists `:flutter:compileFlutterBuildDebug` with this PR, and only `copyJniLibsflutterBuildDebug` on `33f39876b92`. This is a cost, accepted because the module cannot map host task names to its own variants. 4. **Module assets are a generated assets source directory.** The deleted code copied them into the module's merged-assets output after `mergeAssets`, and made every build re-run `clean<MergeAssetsTask>`. Collisions resolve by source-set priority, as for apps since PR 6. 5. **`copyFlutterAssets<V>` in a module is a `CopyFlutterAssetsTask`, not a `Copy`.** Lookups by name still work; lookups typed as `Copy` fail. 6. Related open work: [flutter#191703](flutter#191703) reports an NPE in `getLegacyAndroidExtension` with `newDsl=true`. That function was removed earlier in the stack, and this PR removes the next failure on that path. A module with plugins on `newDsl=true` is not tested here. No conflict with [flutter#193682](flutter#193682) or [flutter#193610](flutter#193610) (`gradle_errors.dart`); this PR changes no message that the tool matches. [flutter#184409](flutter#184409) asks for separate library and app logic in `addFlutterTasks`; this PR shares `onVariants` and splits only the app outputs. ### Integration test coverage of the changed paths | Path | Tests | | --- | --- | | Module from source, `newDsl=true`, debuggable custom build type on `release`, `hostAppProjectName` warning | devicelab `module_host_with_custom_build_test`, `app:assembleDemoQa` section | | Module from source, flavor and custom build types, debug and release | devicelab `module_host_with_custom_build_test` (other sections; `app:assembleDemoStaging` needs the ungated `LibraryVariant`), `build_android_host_app_with_module_source` | | Host project not named `app`, without `hostAppProjectName` | devicelab `module_custom_host_app_name_test` | | Module AAR (`flutter build aar`) and AAR host | devicelab `build_aar_module_test`, `build_android_host_app_with_module_aar`; `android_obfuscate_test`, `android_plugin_new_output_dir_test` | | App path | `gradle_libapp_so_packaging_test`, `flutter_build_apk_split_per_abi_test`, `android_gradle_asset_merging_test`, `android_run_flutter_gradle_plugin_tests_test` | ### Tests run locally (macOS, JDK 17) On the head after merging `master` (`23ddd6b74e6`; FGP compiles and tests against AGP 9.1.1): - `./gradlew test --rerun-tasks` in `packages/flutter_tools/gradle`: 33 suites, 277 tests, 0 failures. - ktlint 1.5.0 with the CI `.editorconfig` and baseline: clean. - `dart format` and `dart analyze --fatal-infos` on `module_host_with_custom_build_test.dart` and `gradle_libapp_so_packaging_test.dart`: clean. - `gradle_libapp_so_packaging_test`: 4 tests pass. - devicelab, all `success`: `module_host_with_custom_build_test`, `module_custom_host_app_name_test`, `build_android_host_app_with_module_source`. On `1366874099e`: - `./gradlew test --rerun-tasks` in `packages/flutter_tools/gradle`: 33 suites, 274 tests, 0 failures. - ktlint 1.5.0 with the CI `.editorconfig` and baseline: clean. - `dart format` and `dart analyze --fatal-infos` on `module_host_with_custom_build_test.dart`: clean. - devicelab `module_host_with_custom_build_test`: `success`. The `app:assembleDemoQa` section ran `./gradlew app:assembleDemoQa -Pandroid.newDsl=true -Pflutter.hostAppProjectName=app` and logged the `flutter.hostAppProjectName` warning. On `d8f869ec468`: - `./gradlew test --rerun-tasks` in `packages/flutter_tools/gradle`: 33 suites, 274 tests, 0 failures. - ktlint 1.5.0 with the CI `.editorconfig` and baseline: clean. - `dart format` and `dart analyze --fatal-infos` on `module_host_with_custom_build_test.dart`: clean. - devicelab `module_host_with_custom_build_test`: `success`. The `app:assembleDemoQa` section logged the `flutter.hostAppProjectName` warning. On `a6be16034c8` (the merge and the trim): - `./gradlew test` in `packages/flutter_tools/gradle`: 33 suites, 274 tests, 0 failures. - ktlint 1.5.0 with the CI `.editorconfig` and baseline: clean. - `dart format` and `dart analyze --fatal-infos` on the changed Dart files: clean. - `android_add_to_app_module_test` (deleted in `2efbd345505`): passes. - devicelab `module_custom_host_app_name_test` (fixture without `flutter.hostAppProjectName`): `success`, and the log has no `hostAppProjectName` warning. On `8c9a54d8297` (before the merge of PR 7's review changes and the comment trim): - Integration tests, one at a time, all pass: `android_add_to_app_module_test` (1), `android_obfuscate_test` (2), `android_plugin_new_output_dir_test` (1), `gradle_libapp_so_packaging_test` (4), `flutter_build_apk_split_per_abi_test` (4), `android_gradle_asset_merging_test` (4), `android_run_flutter_gradle_plugin_tests_test` (2). - `android_add_to_app_module_test` on `33f39876b92`: fails with `Check failed.`. With `variant is LibraryVariant ||` removed: fails with "does not contain 'assets/flutter_assets/AssetManifest.bin'". - devicelab via `bin/test_runner.dart test --exit`, all `success`: `module_host_with_custom_build_test`, `module_custom_host_app_name_test`, `build_android_host_app_with_module_source`, `build_aar_module_test`, `build_android_host_app_with_module_aar`. - Scratch module plus the devicelab host fixture (AGP 9.3.1, Gradle 9.5.0): - Defaults: `assembleDemoStaging` has `kernel_blob.bin` and no `libapp.so`; `assembleDemoRelease` has `libapp.so` for arm64-v8a, armeabi-v7a and x86_64. - Same with `android.compatibility.enableLegacyApi=false`, and with `android.newDsl=true` added. </details> --- ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant in-code documentation (doc comments with `///`). - [x] If this PR introduces a new feature or capability, I created and linked a website documentation issue or PR in [flutter/website] (or verified none is needed). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [flutter/website]: https://github.com/flutter/website [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
…Is (flutter#193962) PR 7 (flutter#193693) merged to master as `ecd1ab06865` and PR 8 (flutter#193717) as `4249ccbc18a`. This branch merges master at `4521d8d6634`, so the Files tab shows only PR 9's changes. PR 9 commits: - `86523c549fc` Move aar_init_script to public AGP APIs - `3edc95d1fa0` Check requested AAR variants in the module and each plugin - `da9646aad53` Address review: derive requested variants from components, register AAR tasks lazily - `94a29907065` Clarify aar_init_script comments from the adversarial review - `ea08280d886` Merge commit '23ddd6b74e6' into agp-aar-init-script (PR 8's head with master at `ecd1ab06865`; no conflicts, no change to PR 9's files) - `a17fafcc35e` Merge commit '5ba8961b517' into agp-aar-init-script (master with PR 8 at `4249ccbc18a`; no conflicts, no change to PR 9's files) - `77ba449ea4a` Filter requested AAR components before adding tasks - `adbfb9cd640` Drop the AGP version list from the duplicate-variant comment - `f2b7111c51e` Docs: state the AAR variant requirement in plain words - `cd10f766b5b` Register an AAR task for every variant, publish only requested ones - `b9493299916` Merge commit '4521d8d6634' into agp-aar-init-script (master, including flutter#193649; no conflicts, no change to PR 9's files) ## Description This is PR 9 of 11 in the AGP 9.1.0 / public `gradle-api` migration stack ([flutter#180137](flutter#180137), [flutter#166550](flutter#166550)). `flutter build aar` passes `packages/flutter_tools/gradle/aar_init_script.gradle` to Gradle with `-I`. The script read `android.libraryVariants` and AGP's internal `publishing.singleVariants`. With AGP 9's `android.newDsl=true`, `flutter build aar` on a module fails on `5ba8961b517` with `Can't generate AAR on a non Android library project.` (script line 13), and then at line 178 (`Could not get unknown property 'libraryVariants'`). With this PR the script uses only `pluginManager.hasPlugin`, `Publishing.singleVariant`, software `components` and `tasks.register`, and the build succeeds with `newDsl` on and off, on AGP 9.3.1 and on master's minimum AGP 9.1.1, and on AGP 8.11.1 when FGP's version check is skipped. **Breaking:** no. Builds that succeed on `5ba8961b517` succeed with the same artifacts. Two failing cases change: a module or plugin build file that declares `singleVariant` for only some variants builds, and a variant mismatch between the module and a plugin fails with a Flutter error instead of `Task with name … not found`. **Base:** `master` at `4521d8d6634`. `aar_init_script.gradle`, `build_aar_module_test.dart` and both docs are the same at `4521d8d6634` and `5ba8961b517`, so the comparisons below against `5ba8961b517` hold for `4521d8d6634`. <details> <summary>Agent authored details</summary> ### Changes All in `aar_init_script.gradle` unless noted. Line numbers are at `b9493299916`. 1. **Library check** (`:13`): `project.pluginManager.hasPlugin("com.android.library")` replaces `android.hasProperty("libraryVariants")`. The error text is unchanged. 2. **Publishing declarations** (`afterProject`, `:185-219`): Flutter calls `android.publishing.singleVariant(<variant>) { withSourcesJar(); withJavadocJar() }` for each variant, one variant at a time. If the project's build file already declared that variant, AGP throws, and `isDuplicateSingleVariantError` (`:125-134`) matches AGP's exact message, so the build file's declaration is kept. Its comment links the AGP source of the message ([`AbstractPublishing.kt` at `studio-2025.3.2`, AGP 9.1.0](https://android.googlesource.com/platform/tools/base/+/refs/tags/studio-2025.3.2/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/dsl/AbstractPublishing.kt#38)). Any other exception is rethrown (`:201-203`). The guard that read `publishing.singleVariants` and skipped every Flutter declaration once the build file declared any variant is deleted. 3. **No module-to-plugin `dependsOn`** (`projectsEvaluated`): the loop over `moduleProject.android.libraryVariants` that made each module `assembleAar<Variant>` depend on the plugin's task (with eager `tasks.named(...).get()`) is deleted. The tool requests `assembleAar<Variant>` without a project path (`gradle.dart` `buildGradleAar`, `command.add(aarTask)`), and Gradle runs a task selector that is only a task name in every project that has the task ([Gradle docs](https://docs.gradle.org/current/userguide/command_line_interface.html#executing_tasks_in_multi_project_builds): "You can also run a task for all subprojects using a task selector that consists of only the task name."). Each plugin publishes from its own task. The comment at `:247-248` says this. 4. **Tasks, publications and task name** (`configureProject` `:27-37`; `aarTaskName` `:93-97`; `requestedAarComponents` `:99-103`): AGP adds one software component for each `singleVariant` declaration, with the variant's name. `registerAarTask` (`:105-110`) registers `assembleAar<Variant>` for every component, so a misspelled `--flavor` gets Gradle's candidate list (`Some candidates are: 'assembleAarDebug', ...`). `requestedAarComponents` returns the components whose `assembleAar<Variant>` name is in `startParameter.taskNames`; only those get a publication (`addAarPublication`, `:112-123`), because `publish` publishes every publication in the project. The check uses the same function. `aarTaskName` is the only place that builds `assembleAar<Variant>`; its comment points to the tool's `getAarTaskFor` (`gradle.dart`). Nothing parses a task name back into a variant. The `component.name != "all"` filter from `5ba8961b517` is deleted (see "Why removing the deleted code is safe"). 5. **Lazy task registration** (`registerAarTask`): `project.tasks.register` replaces `project.tasks.create(name:)`. The configuration action only adds `finalizedBy "publish"`. `publications.create` runs in `addAarPublication`, not in the action, because maven-publish registers tasks for each publication and Gradle rejects that while it realizes a task (`DefaultTaskContainer#register(String, Class, Action) on task set cannot be executed in the current context.`, seen when `publications.create` was inside the action). 6. **Requested-variant check** (`checkProjectsHaveRequestedVariants`, `:136-175`, called at `:252`): Gradle skips projects that lack the requested task, so the script checks the module and each Android plugin. The requested variants are the names of the components that `requestedAarComponents` returns for those projects. A project without a requested variant fails the build with a `GradleException` that names the variant and the project: - module: `Flutter can't build the AAR for variant 'debug' because module project ':flutter' has no variant with that name. Its variants are: demoDebug, demoProfile, demoRelease, fullDebug, fullProfile, fullRelease. Pass the module's product flavor with --flavor, or omit --flavor if the module has no product flavors.` - plugin: `Flutter can't build the AAR for variant 'demoDebug' because plugin project ':plugin_a' has no variant with that name, so the plugin AAR that the module depends on can't be published. `flutter build aar` requires the module and each of its Android plugins to declare the same product flavors.` - A requested task that matches no project's variant (for example `--flavor foo`) is left to Gradle: `Task 'assembleAarFooDebug' not found in root project 'android_generated' and its subprojects. Some candidates are: 'assembleAarDebug', 'assembleAarDemoDebug', 'assembleAarFullDebug'.` That is the same error as on `5ba8961b517`. The check's doc says so. (`3edc95d1fa0` parsed the task-name prefix and reported a Flutter module error in this case; `da9646aad53` drops the parsing per review.) 7. **Docs.** Both docs describe behavior only. - `Migrating-Flutter-Gradle-Plugin-to-AGP-public-API.md`: the P8 row of the phase table and "Features that must break" item 8 are rewritten. The "Why" starting-state list (L38-39) is unchanged, as in other PRs of the stack. - [`website-page-draft.md`](https://github.com/flutter/flutter/blob/master/docs/platforms/android/website-page-draft.md) (publishing tracked in [flutter#193713](flutter#193713)): the `flutter build aar` section is rewritten. It said Flutter "detects the overlap and reports it with an actionable error". The code keeps the build file's declaration instead, so the docs were changed to match the code. It says (L234-237) that when a build file declares `singleVariant` for a variant, Flutter keeps that declaration and declares the other variants, and (L238-241) that the module and each Android plugin need a variant with the built name. 8. **Tests.** devicelab `build_aar_module_test` gets two final sections: - With `android.newDsl=true` and `android.builtInKotlin=true` in `.android/gradle.properties`, and `android.publishing.singleVariant("release") { withSourcesJar() }` in `.android/Flutter/build.gradle`, it runs `flutter build aar --no-profile`. It checks that the edits survive, that release has the AAR and sources jar and no javadoc jar, that debug has the javadoc jar, and that the plugin AARs exist for release and debug. - It replaces that declaration with a `demo` product flavor and checks both errors: `flutter build aar --no-profile --no-release` fails with the module error, and the same with `--flavor demo` fails with the plugin error. `da9646aad53`'s commit message says the check's doc "says when it returns and when it throws". That doc sentence was added in `94a29907065`. ### Bug history (moved out of the docs) The docs describe behavior only, so the history that `3edc95d1fa0` put in them is here: - On `5ba8961b517`, the `afterProject` guard skipped all of Flutter's `singleVariant` declarations for a project whose build file declared any variant. For a module with at least one Android plugin that declared only `release`, every `flutter build aar` failed with `Task with name 'assembleAarDebug' not found in project ':flutter'`. With this PR that build succeeds (table 1 below). - On `5ba8961b517`, the `dependsOn` loop failed with `Task with name 'assembleAar<Variant>' not found in project ':<plugin>'` whenever a plugin lacked any module variant, requested or not, and nothing checked that the module had the requested variant. With this PR only requested variants are checked, in the module and in each plugin (table 2 below). ### Audit resolution [PR 9 draft audit](reidbaker-agent#9 (comment)), and stack audit items M4 and L8: | Item | Resolution | | --- | --- | | 1. Eager `.get()` and cross-project task lookup by name | The wiring is deleted, not made lazy: it was redundant (Changes 3). No `.get()` or `tasks.named` in the script. The check reads software components, and `registerAarTask` uses `tasks.register`. | | 2. Broad `catch (Exception)`; overstated `multipleVariants` warning | Only AGP's exact duplicate message is ignored (`:130-134`); everything else is rethrown (`:201-203`). No warning is logged, so there is no `multipleVariants` claim. | | 3. Ext property without `flutter.internal.` | No ext property. `afterProject` fires once per project (instrumented: count 1 for `:`, `:app`, `:flutter`, `:plugin_a` and the included FGP build), and a second call would only hit ignored duplicates. The "fires more than once in composite settings" claim is dropped. | Why the message match and not the type: AGP reports the duplicate from `AbstractPublishing.checkSingleVariantUniqueness` through `SyncIssueReporterImpl`, which throws `com.android.builder.errors.EvalIssueException` in STANDARD mode. That class is in `com.android.tools.build:builder`, not in `gradle-api`, and is not on the init script's classpath. `javap -v` on `AbstractPublishing` shows the same text in AGP 8.11.1, 9.1.0 and 9.3.1. AGP checks before it adds the component (8.11.1 `AbstractPublishing.kt:27-35`), so the build file's declaration stays. If AGP changes the text, the exception is rethrown and the build fails loudly; devicelab `build_aar_module_test` exercises the path on the template AGP. Why not declare first or query: the public `Publishing` DSL in `gradle-api-9.1.0.jar` has only `singleVariant(String)` and `singleVariant(String, Function1)`, and `LibraryPublishing` adds only `multipleVariants(...)` (`javap`), so there is no public way to list declared variants. Declaring Flutter's variants in `beforeProject` + `pluginManager.withPlugin("com.android.library")` makes the build file fail at its own `singleVariant("release")` line with the duplicate error (probe). Why not AGP types in the init script: `import com.android.build.api.dsl.LibraryExtension` fails with `unable to resolve class`. With `gradle-api:9.3.1` on the `initscript` classpath, the init script gets its own copy of the class from a different classloader, so `instanceof` is `false` and `extensions.getByType(LibraryExtension)` throws `Extension of type 'LibraryExtension' does not exist. Currently registered extension types: [..., LibraryExtension, ...]` (probes). `multipleVariants`: `PublishingUtil.ensureComponentNameUniqueness` errors only when a `multipleVariants` component name equals a `singleVariant` name. Flutter declares the same `singleVariant`s on `5ba8961b517` when the build file declares none, so that behavior is unchanged. It is not tested here. ### Why removing the deleted code is safe - `android.libraryVariants` does not exist with `newDsl=true` (reproduced above). AGP 9.1.0 marks its class, `com.android.build.gradle.LibraryExtension`, `@Deprecated` ("This class is not used for the public extensions in AGP when android.newDsl=true, which is the default in AGP 9.0, and will be removed in AGP 10.0."), and the class is not in `gradle-api-9.1.0.jar`. `singleVariants` is an internal property (`getSingleVariants()` on `com.android.build.gradle.internal.dsl.AbstractPublishing`, `javap`). It resolves from Groovy with `newDsl=true`, so it was not a failure, only internal API. - The `dependsOn`: with it removed, the task graph for `assembleAarRelease` on case (a) has `:plugin_a:assembleAarRelease`, `:plugin_a:bundleReleaseAar`, `:plugin_a:publishReleasePublicationToMavenRepository` and `:plugin_a:publish`. `:flutter:generatePomFileForReleasePublication` depends on no `:plugin_a` task, and `:flutter:publishReleasePublicationToMavenRepository` depends only on the plugin's compile-classpath tasks, so the order between the module's and the plugin's publishing doesn't matter. The module POM refers to the plugin by coordinates (`plugin_a_release`). devicelab `build_aar_module_test`, `build_android_host_app_with_module_aar` and `android_java17_build_android_host_app_with_module_aar` pass (they check the plugin AAR/POM and that the module POM depends on `plugin_with_android_release`). - The `all` filter (`component.name != "all"`, added in flutter#101891 `606e91ad63d`): AGP 7.x created an automatic multi-variant component named `all` when a project used no publishing DSL ([`PublishingUtil.kt` at `studio-2021.3.1`](https://android.googlesource.com/platform/tools/base/+/refs/tags/studio-2021.3.1/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/utils/PublishingUtil.kt), `createPublishingInfoForLibrary` and `publishingFeatureOptIn`; same in the AGP 7.3.1 jar). flutter#101891 published it separately with classifiers and skipped it for the `_<variant>` publications; flutter#112149 (`8e30cc9c56b`) removed that publication and kept the skip. AGP 8.0 disabled automatic components by default ([AGP 8.0.0 release notes](https://developer.android.com/build/releases/past-releases/agp-8-0-0-release-notes)), and the AGP 8.11.1, 9.1.1 and 9.3.1 jars list `android.disableAutomaticComponentCreation` as removed, with no `all` component code. This script declares `singleVariant` for every variant, which also turns automatic components off on AGP 7.x. A probe that prints `project.components` after `projectsEvaluated` on all four matrix configurations shows only per-variant names (`[debug, profile, release]` for `:flutter`, `:app` and each plugin, `[demoDebug, …, fullRelease]` for the flavored module, `[freeDebug, freeProfile, freeRelease]` for `plugin_flavored`, `[debug, release]` for the root), never `all`. - `tasks.register`: the publication is created at configuration time only for a requested task name, as with `tasks.create`, whose configuration closure ran immediately. Only the task object is created lazily. Table rows below and the devicelab runs show the same artifacts. ### Removed comments, assertions and code | Removed | Reason | | --- | --- | | `afterProject` comment item 2, "The project has already defined which variants to publish…", and the `singleVariants.size()` guard it described | The guard read internal API and skipped all of Flutter's declarations once a build file declared any. Item 1 of that comment became "Exit early if the project doesn't have the Android Gradle plugin applied." | | The `projectsEvaluated` comment "Configure the `assembleAar<variantName>` task for each plugin's projects and make the module's equivalent task depend on the plugin's task.", with the `libraryVariants.all` loop, both `tasks.named(...).get()` calls, both `assert(... != null)` and the `dependsOn` | The dependency is redundant (see above). `tasks.named(...).get()` throws before the asserts could fail, so they never checked anything. | | `addAarTask` and its `variantName` local | Split into `registerAarTask` (every component) and `addAarPublication` (requested components). The task name comes from `aarTaskName`, which the check also uses. | | `addAarTask` comment "This check is required to be able to configure the archives before `publish` runs.", with the check | The check is `requestedAarComponents`, applied by the caller before `addAarPublication`. Its reason is the comment on `addAarPublication`: "`publish` publishes every publication in the project, so only requested components get one." | | The `component.name != "all"` filter in `configureProject`, and the fragment "(except "all" since that is used to publish artifacts in the new way)" from "Create a default MavenPublication for the variant" | Nothing creates an `all` component where this script runs (see "Why removing the deleted code is safe"). | | From the migration doc item 8 and the website page draft: the sentences about the deleted guard, the `Task with name … not found` failures, and what the deleted code did and did not check | The docs describe behavior only. The history is in "Bug history" above. | ### Behavioral and compatibility notes Matrix projects (local scratch): (a) module + Android plugin `plugin_a`; (b) module with flavors `demo`/`full` + `plugin_a`; (c) module + `plugin_a` + `plugin_flavored` (flavor `free`); (d) module declaring `singleVariant("release") { withSourcesJar() }` + `plugin_a`. 1. **A build file that declares `singleVariant` for some variants builds.** Case (d), `flutter build aar`: | Script | Result | | --- | --- | | `5ba8961b517` (AGP 9.3.1 `newDsl=false`) | exit 1: `Task with name 'assembleAarDebug' not found in project ':flutter'.` | | This PR (all four configurations below) | exit 0. Release has the AAR, POM and sources jar, and no javadoc jar (the build file's declaration). Debug and profile have sources and javadoc jars. Plugin AAR/POM for all three. | 2. **Variant mismatches fail with a Flutter error that names the project.** Only requested variants are checked. The `5ba8961b517` column is AGP 9.3.1 with `newDsl=false`. | Case | `5ba8961b517` | This PR (all four configurations) | | --- | --- | --- | | (b) `--flavor demo` | `Task with name 'assembleAarDemoDebug' not found in project ':plugin_a'.` | plugin error for `demoDebug` and `:plugin_a` | | (c) default | `Task with name 'assembleAarDebug' not found in project ':plugin_flavored'.` | plugin error for `debug` and `:plugin_flavored` | | (b) no `--flavor`, `--no-profile --no-release` | `Task with name 'assembleAarDemoDebug' not found in project ':plugin_a'.` | module error for `debug`, listing the module's variants | | (c) `--flavor free`, `--no-profile --no-release` | `Task with name 'assembleAarDebug' not found in project ':plugin_flavored'.` | module error for `freeDebug` | | (b) `--flavor foo` | Gradle `Task 'assembleAarFooDebug' not found in root project …` | same Gradle error | Without the module check (`86523c549fc`), the module-error cases exit 0 with "✓ Built" and a repo that has only the plugin AAR. 3. **Path-qualified requests.** `./gradlew -I aar_init_script.gradle … :flutter:assembleAarRelease` exits 0 and publishes nothing, on `5ba8961b517` and on this PR: a publication is created only when `startParameter.taskNames` contains the bare name. The tool sends only the bare name (`gradle.dart` `command.add(aarTask)`). Not changed here. 4. **No warnings.** `flutter build aar` hides Gradle output on success unless `-v` (`ProcessUtils.run` buffers it; `gradle.dart` prints it only on failure, and verified with a `logger.error` probe). Keeping a build file's declaration needs no action, so nothing is logged. Errors are shown. 5. **Configuration cache:** with `org.gradle.configuration-cache=true` in case (a)'s `.android/gradle.properties` (AGP 9.3.1 / Gradle 9.5.0), `flutter build aar` reports the same single problem with the `5ba8961b517` script and with this PR: ``Task `:flutter:compileFlutterBuildDebug` of type `com.flutter.gradle.tasks.FlutterTask`: invocation of 'Task.project' at execution time is unsupported with the configuration cache.`` None comes from the init script. 6. `newDsl=true` with `builtInKotlin=false` fails before the script with any Kotlin plugin (`The 'org.jetbrains.kotlin.android' plugin is not compatible with AGP's 9.0 new DSL`). The devicelab section and matrix set `builtInKotlin=true`. 7. Related open work: no overlap with [flutter#191681](flutter#191681) (`gradle.dart`, `gradle_errors.dart` and tests). No open PR touches `aar_init_script.gradle`. ### Follow-up candidates (pre-existing, not changed here) - `allprojects { apply plugin: "maven-publish" }` applies maven-publish to every project, including ones that never publish. - The `afterProject` and `projectsEvaluated` hooks reach across projects, which Gradle Isolated Projects does not allow. - The `-SNAPSHOT` rewrite of `project.version` in `configureProject`. - The `engine.realm` and `engine.stamp` file reads at configuration time in `configureProject`. - The `is-plugin` branch of `projectsEvaluated` looks unreachable from the tool. - The configuration cache problem in FGP `FlutterTask` (note 5). - `newDsl=true` with KGP (`builtInKotlin=false`), for PR 10. - Mapping between flavored module variants and plugin variants (for example, plugin `release` for module `demoRelease`). - `singleVariant` is declared on the root project and `:app`, which never publish. - Gradle output is hidden on success, so nothing the script logs at info or warn level reaches the user without `-v`. ### Integration test coverage of the changed paths Searched `dev/devicelab/bin/tasks` and `packages/flutter_tools/test/integration.shard` for `build aar` and `assembleAar`. | Path | Tests | | --- | --- | | Library check, publishing declarations, lazy task registration, plugin publishing without `dependsOn` (`newDsl=false`) | devicelab `build_aar_module_test`, `build_android_host_app_with_module_aar`, `android_java17_build_android_host_app_with_module_aar`; `android_obfuscate_test` | | `newDsl=true`, a build file's `singleVariant` declaration kept | devicelab `build_aar_module_test`, `newDsl=true` section | | Module and plugin variant mismatch errors | devicelab `build_aar_module_test`, product flavor section | | AGP 9.1.1 and 8.11.1; a variant that no project has | Manual matrix only | `Linux build_aar_module_test` runs in presubmit for this PR: in `.ci.yaml` its `runIf` includes `dev/**` and `packages/flutter_tools/**`, and it sets neither `presubmit: false` nor `bringup`. It also ran locally on the head (below) with `cd dev/devicelab && ../../bin/dart bin/test_runner.dart test -t build_aar_module_test --exit`. ### Tests run locally (macOS, JDK 17) On the head (`b9493299916`; master merged at `4521d8d6634`): - `gradle test --rerun-tasks` in `packages/flutter_tools/gradle`, with the engine's `third_party/gradle` binary that the README names: 33 suites, 277 tests, 0 failures. - `dart format` and `dart analyze --fatal-infos` on `build_aar_module_test.dart`: clean (unchanged since `3edc95d1fa0`). - Tool tests: `test/commands.shard/hermetic/build_aar_test.dart`, `test/commands.shard/permeable/build_aar_test.dart` and `test/general.shard/android/android_gradle_builder_test.dart`: 56 tests, all pass. - `test/integration.shard/android_obfuscate_test.dart`: 2 tests pass. - devicelab via `bin/test_runner.dart test --exit`, one at a time: `build_aar_module_test` (including both added sections), `build_android_host_app_with_module_aar`, `android_java17_build_android_host_app_with_module_aar`, `build_android_host_app_with_module_source`, `module_host_with_custom_build_test`, all `success`. - The configuration cache comparison (note 5). Manual matrix on `b9493299916`, four configurations: - **9.3.1 off:** AGP 9.3.1 / Gradle 9.5.0, `newDsl=false`, `builtInKotlin=false`. - **9.3.1 on:** AGP 9.3.1 / Gradle 9.5.0, `newDsl=true`, `builtInKotlin=true`. - **9.1.1 on:** AGP 9.1.1 / Gradle 9.3.1 (FGP's minimums on master since flutter#193813), `newDsl=true`, `builtInKotlin=true`. - **8.11.1:** AGP 8.11.1 / Gradle 8.14.3, `newDsl=false`, with `skipDependencyChecks=true` in `.android/gradle.properties`. Without it, FGP stops before the init script runs: `Your project's Gradle version (8.14.3) is lower than Flutter's minimum supported version of 9.3.1.` | Case | 9.3.1 off | 9.3.1 on | 9.1.1 on | 8.11.1 | | --- | --- | --- | --- | --- | | (a) | exit 0, AAR, POM, sources and javadoc jars for `flutter_{debug,profile,release}` and `plugin_a_{debug,profile,release}` | same | same | same | | (b) `--flavor demo` | exit 1, plugin error | same | same | same | | (b) no `--flavor` | exit 1, module error | same | same | same | | (b) `--flavor foo` | exit 1, Gradle `Task 'assembleAarFooDebug' not found in root project` | same | same | same | | (c) | exit 1, plugin error | same | same | same | | (c) `--flavor free` | exit 1, module error | same | same | same | | (d) | exit 0, as (a), except module release has the sources jar and no javadoc jar | same | same | same | Case (a)'s `flutter_release-1.0.pom` depends on `dev.pr9.plugin_a:plugin_a_release:1.0` (checked on `b9493299916`, AGP 9.1.1). Negative control, the master script (same at `5ba8961b517` and `4521d8d6634`), on `b9493299916`: with `newDsl=true` (AGP 9.3.1 and AGP 9.1.1), (a)-(d) all fail with `Can't generate AAR on a non Android library project.`; with `newDsl=false` on AGP 9.3.1, (a) passes and (b)-(d) fail with Gradle's `Task with name 'assembleAar<V>' not found in project ':<p>'.` On `a17fafcc35e`, with only line 13 changed to `hasPlugin`, `newDsl=true` fails at line 178 with `Could not get unknown property 'libraryVariants' for object of type com.android.build.gradle.internal.dsl.LibraryExtensionImpl$AgpDecorated.` devicelab `build_aar_module_test` with that script fails at the `newDsl=true` section with the same error. </details> --- ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant in-code documentation (doc comments with `///`). - [x] If this PR introduces a new feature or capability, I created and linked a website documentation issue or PR in [flutter/website] (or verified none is needed). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [flutter/website]: https://github.com/flutter/website [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
Fixes zulip#2490 (see "Behavior changes, iOS, elsewhere", below). Fixes zulip#2495 (see the "Attach images or videos" behavior changes, below). file_picker 11's Android code doesn't get compiled under Android Gradle Plugin 9 with our `android.builtInKotlin=false`, so the build fails with "cannot find symbol: FilePickerPlugin". That blocks upgrading AGP, which the Flutter tool now requires: flutter/flutter#193813 file_picker 13 builds fine. file_picker 13 depends (through windows_file_picker) on version 6 of package:win32, the Dart bindings for Windows APIs. So package_info_plus, share_plus, and device_info_plus move to their win32-6 majors too (and share_plus_platform_interface with share_plus), and wakelock_plus from 1.5.2 to 1.8.0. Those releases require newer versions of Flutter and Dart than before. For example, share_plus 13.3.1 requires Flutter 3.38.1 or later, up from 3.22.0 for share_plus 12.0.2. We're already on new enough versions. Their changelogs list no other breaking changes. Each package goes to its latest version, not just to the first one that works with win32 6. The exceptions are wakelock_plus 1.8.1 and file_picker_linux 2.0.1 and later: they need package:dbus 0.8, and our connectivity_plus 7.3.1 holds dbus at 0.7 (through package:nm). Both matter only on Linux: wakelock_plus 1.8.1 changes only its dbus dependency, and file_picker_linux is file_picker's Linux implementation. Changelogs: file_picker, 11.0.3 to 13.1.0: https://pub.dev/packages/file_picker/changelog#1310 file_picker_darwin, new dependency at 2.3.2: https://pub.dev/packages/file_picker_darwin/changelog android_file_picker, new dependency at 2.1.1: https://pub.dev/packages/android_file_picker/changelog file_picker_platform_interface, new dependency at 4.2.0: https://pub.dev/packages/file_picker_platform_interface/changelog share_plus, 12.0.2 to 13.3.1: https://pub.dev/packages/share_plus/changelog#1331 share_plus_platform_interface, 6.1.0 to 7.2.0: https://pub.dev/packages/share_plus_platform_interface/changelog#720 package_info_plus, 9.0.1 to 10.2.2: https://pub.dev/packages/package_info_plus/changelog#1022 device_info_plus, 12.4.0 to 13.3.0: https://pub.dev/packages/device_info_plus/changelog#1330 wakelock_plus, 1.5.2 to 1.8.0: https://pub.dev/packages/wakelock_plus/changelog#180 The list leaves out the other plugins' platform-interface packages, and packages used only on web, Windows, or Linux: file_picker_web, windows_file_picker, file_picker_linux, and win32 and what it uses. Behavior changes, iOS, "Attach files" button: - When none of the picked files can be loaded, we now show an error dialog, with the message "PlatformException(load_failed, None of the picked items could be loaded., null, null)". file_picker 11 returned an empty list then, so nothing happened. (The next commit gives the dialog a translated message, without the "PlatformException(...)" clutter.) Behavior changes, iOS, "Attach images or videos" button: - When none of the picked photos or videos can be loaded, the error dialog's message changes: - Old: "PlatformException(file_picker_error, Failed to process any images/video, <a line per picked item>, null)" - New: "PlatformException(load_failed, None of the picked items could be loaded., null, null)" (The next commit replaces that with a translated message, without the "PlatformException(...)" clutter.) - When the user picks multiple photos or videos, they're inserted in the order of the photo picker's result list. file_picker 11 inserted them in whatever order they finished loading. - Picked photos and videos keep the file name iOS gives them, such as `IMG_9329.heic`, instead of file_picker 11's `image_<UUID>_<timestamp>.heic` (zulip#2495). - file_picker 11 copied each picked photo or video into the app's Documents folder (`Documents/picked_images`), and never deleted the copies. file_picker 13 copies to the app's temporary folder instead. Copies left by file_picker 11 stay where they are. We should file an issue to get those cleaned up. Behavior changes, iOS, elsewhere: - On an iPad, the message action sheet's "Share" button now opens the share sheet, anchored at the center of the screen. With share_plus 12, it did nothing (zulip#2490). A later commit in this series updates the share_plus iPad TODO. Behavior changes, Android: - With the "Attach files" button, when the user picks several files and none of them can be read, we now show an error dialog, with the message "PlatformException(unknown_path, Failed to retrieve path., null, null)". file_picker 11 returned an empty list then, so nothing happened. (The next commit gives the dialog a translated message, without the "PlatformException(...)" clutter.) - If Android destroys the app's activity while the system file picker is open, android_file_picker now copies the picked files and saves them for `retrieveLostData()`. We don't call that, so the pick is still lost, as with file_picker 11, but the copies stay in the app's cache folder. (From reading the code.) - Otherwise, we found no changes in file_picker on our path (the "Attach files" button), from reading the code and a quick manual test on a device. - wakelock_plus changed what happens in an edge case, when the app has no Android activity attached (as while it's being destroyed): fluttercommunity/wakelock_plus#134 We're fairly sure that makes no noticeable difference for us. Code changes: - Our `pickFiles` binding follows file_picker 13's `pickFiles`: it returns a list, empty if the user cancelled, and always allows several files. Its `allowMultiple` and `withReadStream` parameters are removed, as file_picker 13 removed them. (Single-file selection now goes through a separate method, `pickFile`.) The binding also takes `darwinOptions`, so the compose box can pass the two options below, and tests can check them. - Pass `assetRepresentationMode: current` and `orderedSelection: false` (see code comments), to keep some iOS photo picker settings unchanged from file_picker 11. These are done mainly to avoid changing behavior in this upgrade; they aren't settled product decisions. - List file_picker_darwin and android_file_picker in pubspec.yaml, for their minimum versions; see the comments there. Upstream fixes: while preparing this upgrade, we found these regressions from file_picker 11 and reported them, and the maintainers fixed them. This commit takes the fixed versions, so the upgrade doesn't bring them: vicajilau/flutter_file_picker#2243 vicajilau/flutter_file_picker#2248 vicajilau/flutter_file_picker#2259 vicajilau/flutter_file_picker#2260 vicajilau/flutter_file_picker#2261 We also reported a bug in `AndroidSAFOptions`, an API new since file_picker 11 that we don't use. It's fixed too: vicajilau/flutter_file_picker#2257 And we suggested Pigeon, as well as typed, documented error codes: vicajilau/flutter_file_picker#2258 vicajilau/flutter_file_picker#2262 Related: zulip#855 Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Co-Authored-By: Claude Opus 5.5 <[email protected]>
Fixes zulip#2483. Flutter's recent change flutter/flutter#193813 made Android Gradle Plugin 9.1.1 the minimum. With our AGP 8.12.0, the build crashes while applying Flutter's Gradle plugin: NoSuchMethodError: 'ApplicationDefaultConfig ApplicationExtension.getDefaultConfig()' at com.flutter.gradle.FlutterPlugin$Companion.configureAbiWithoutSplits Move to the versions in Flutter's current app template, except for AGP. The template has AGP 9.3.1, but lint in AGP 9.3.1 crashes when Gradle runs on JDK 17, as it does in our CI: NoSuchMethodError: 'java.lang.Object java.util.List.removeLast()' AGP 9.3.2 fixed that, according to Google's release notes. Take 9.3.3, the latest. Say so in a comment next to the version, so it doesn't get lowered to match the template, and so we know when we can follow the template again: https://issuetracker.google.com/issues/522845800 Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Co-Authored-By: Claude Opus 5.5 <[email protected]>
And update Flutter's supporting libraries to match. This version of Flutter requires AGP 9.1.1 or later: flutter/flutter#193813 We meet that since an earlier commit in this series, which upgraded AGP to 9.3.3. Also update generated files to match: - windows/flutter/: Flutter now registers each plugin after the plugins it depends on, and share_plus depends on url_launcher_windows: flutter/flutter#191591 - ios/Podfile.lock: Flutter's integration_test plugin now declares iOS 15.0 as its minimum, up from 12.0, which changes its podspec checksum: flutter/flutter#194604 Without this, `pod install` rewrites that line on current Flutter, and the iOS CI job fails on the change. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…tensions in FlutterPlugin.apply (flutter#194918) ## Description Part of [flutter#180137](flutter#180137), [flutter#166550](flutter#166550). I broke the check in PR 7/11 ([flutter#193693](flutter#193693)) in the new DSL migration for users that were on AGP 8.11. This fixes the mistake and adds a test. This will likely need to be cherry picked. <details> <summary>Agent authored details</summary> ### Related PR - Follow-up PR: [flutter#195006](flutter#195006). Because `flutter#194918` is a cherry-pick candidate, it only replaces the `OUT_OF_SUPPORT_RANGE_PROPERTY` check in `FlutterPlugin.apply` with a typed `catch (e: DependencyValidationException)`. Removing `OUT_OF_SUPPORT_RANGE_PROPERTY` itself—which touches 7 property setters, a task action, and 14 test assertions across `DependencyVersionChecker.kt` and `DependencyVersionCheckerTest.kt`—is split into [flutter#195006](flutter#195006) so this cherry-pick stays minimal. ### Background When [flutter#193813](flutter#193813) (`ce6e766ad1c`) raised the minimum supported AGP version in `DependencyVersionChecker` to `9.1.1` and bumped the compile-time `agpVersion` in `packages/flutter_tools/gradle/build.gradle.kts` from `8.11.1` to `9.1.1`, the Kotlin compiler began emitting AGP 9 binary method signatures for calls against `ApplicationExtension` (such as `ApplicationExtension.getDefaultConfig()Lcom/android/build/api/dsl/ApplicationDefaultConfig;`, whereas in AGP 8.x `CommonExtension.getDefaultConfig()` returned `DefaultConfig` without a covariant bridge on `ApplicationExtension`). Because `FlutterPlugin.apply` invoked `addFlutterTasks(project)` (which calls `configureAbis` -> `configureAbiWithoutSplits` -> `extension.defaultConfig.ndk` and `DslVersionCodes.from(appExtension)` introduced in [flutter#193693](flutter#193693) / [flutter#192488](flutter#192488)) before running `DependencyVersionChecker.checkDependencyVersions(project)`, building an AGP 8.x project crashed during `addFlutterTasks` with: ```text java.lang.NoSuchMethodError: 'com.android.build.api.dsl.ApplicationDefaultConfig com.android.build.api.dsl.ApplicationExtension.getDefaultConfig()' ``` before reaching `DependencyVersionChecker.checkDependencyVersions(project)`, preventing the user from seeing the actionable minimum AGP version error message and upgrade guidance. ### Changes 1. **`FlutterPlugin.kt`**: - Moves `FlutterPluginUtils.detectApplyingKotlinGradlePlugin(project)` and the `shouldSkipDependencyChecks` / `DependencyVersionChecker.checkDependencyVersions(project)` block to the beginning of `FlutterPlugin.apply(project)` (immediately after `this.project = project`), before `addFlutterTasks(project)` and all `ApplicationExtension` / Android DSL accesses. - Replaces the `OUT_OF_SUPPORT_RANGE_PROPERTY` extra-property check in `FlutterPlugin.apply` with a typed `catch (e: DependencyValidationException)` that rethrows `DependencyValidationException` directly and logs any other `Exception`, removing the comment that described `OUT_OF_SUPPORT_RANGE_PROPERTY`. 2. **`FlutterPluginTest.kt`**: - Adds `FlutterPlugin apply checks dependency versions before configuring project`, verifying via `verifyOrder` that `FlutterPluginUtils.detectApplyingKotlinGradlePlugin(project)` and `DependencyVersionChecker.checkDependencyVersions(project)` run before task registration, `AndroidComponentsExtension` callbacks (`finalizeDsl`, `onVariants`), and `ApplicationExtension` DSL configuration (`defaultConfig`, `buildTypes`). - Adds `FlutterPlugin apply rethrows DependencyValidationException and logs other exceptions`, verifying both exception branches without relying on the extra property, and stubs supported Gradle and AGP versions in `setupTestProjectEnvironment` / `setupMockComponentsExtension`. 3. **`packages/flutter_tools/gradle/AGENTS.md`**: - Updates the `Matrix Verification` bullet on line 40 to reference the supported AGP and Gradle versions defined in `DependencyVersionChecker.kt`. ### Verification - **Unit test negative/positive verification**: - Moving `DependencyVersionChecker` after `addFlutterTasks` or moving `detectApplyingKotlinGradlePlugin` after `DependencyVersionChecker` fails `FlutterPlugin apply checks dependency versions before configuring project`. - Reverting the typed `catch (e: DependencyValidationException)` to the property check fails `FlutterPlugin apply rethrows DependencyValidationException and logs other exceptions`. - `./gradlew test` with the fix applied: all tests pass. - **End-to-end reproduction with AGP 8.11.1 and Gradle 9.3.1**: - Before fix: failed at `FlutterPlugin.configureAbiWithoutSplits` with `java.lang.NoSuchMethodError: 'com.android.build.api.dsl.ApplicationDefaultConfig com.android.build.api.dsl.ApplicationExtension.getDefaultConfig()'`. - After fix: fails cleanly with `Error: Your project's Android Gradle Plugin version (Android Gradle Plugin version 8.11.1) is lower than Flutter's minimum supported version of Android Gradle Plugin version 9.1.1. Please upgrade your Android Gradle Plugin version.` - **Formatting**: - `ktlint 1.5.0` with `--editorconfig=dev/bots/test/analyze-test-input/.editorconfig --baseline=dev/bots/test/analyze-test-input/ktlint-baseline.xml` passed with zero findings. </details> --- ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant in-code documentation (doc comments with `///`). - [x] If this PR introduces a new feature or capability, I created and linked a website documentation issue or PR in [flutter/website] (or verified none is needed). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/CONTRIBUTING.ai.md [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [flutter/website]: https://github.com/flutter/website [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
Raised the minimum AGP version to 9.1.1 and the warn version to 9.3.1. Raised the minimum to AGP 9.1.1 so developers are all on AGP 9+ and it guarantees support for `compileSdk` 37, which we set [here](flutter#193307). ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant in-code documentation (doc comments with `///`). - [x] If this PR introduces a new feature or capability, I created and linked a website documentation issue or PR in [flutter/website] (or verified none is needed). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [flutter/website]: https://github.com/flutter/website [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
… variant API (flutter#193717) PR 7 ([flutter#193693](flutter#193693)) merged to `master` as `ecd1ab06865`, and `23ddd6b74e6` merges `master` into this branch, so the [Files changed](https://github.com/flutter/flutter/pull/193717/files) tab shows only PR 8's changes. PR 8 commits: - `8c9a54d8297` Unify add-to-app module wiring on the variant API - `2c28d8f8245` Merge PR 7 head `f668780fd47` (conflicts resolved to PR 7's wording where PR 8 does not change the code) - `a6be16034c8` Trim comments added by PR 8 - `b8cd39bcf81` Address review: clarify the variant gate and task registration comments - `2efbd345505` Address review: check the add-to-app module in module_host_with_custom_build_test - `d8f869ec468` Address review: share task registration stubs in FlutterPluginTest - `798ac295725` Address review: take ApplicationVariant in configureApplicationOutputs - `1366874099e` Address review: drop android.compatibility.enableLegacyApi from the qa section - `eb20bbd43de` Merge PR 7 head `25550b3fc49` (no conflicts) - `23ddd6b74e6` Merge `master` at `ecd1ab06865` (PR 7 squash-merged). `FlutterPlugin.kt`, `FlutterPluginUtils.kt` and `FlutterPluginTest.kt` conflicted only because the squash commit is not an ancestor; `master`'s copies are identical to `25550b3fc49`, already merged by `eb20bbd43de`, so this branch's side was kept. ## Description This is PR 8 of 11 in the AGP 9.1.0 / public `gradle-api` migration stack ([flutter#180137](flutter#180137), [flutter#166550](flutter#166550)). An add-to-app module (an Android library) is wired through the same `androidComponents.onVariants` path as an app. The module adds Flutter's assets and native libraries to its own variants, and the host app consumes them like any Android library. Flutter does not look up or configure the host project. With this PR, a host app with AGP 9's `android.newDsl=true` builds a Flutter module from source; on `33f39876b92` it fails with `Check failed.` while configuring `:app`. **Breaking:** `flutter.hostAppProjectName` has no effect. Setting it logs a warning. **Base:** `master`. PR 7 (flutter#193693) merged as `ecd1ab06865`. <details> <summary>Agent authored details</summary> ### Changes 1. **Module variants on `onVariants`** (`FlutterPlugin.kt` `addFlutterTasks`). For every variant, app or library, `onVariants` calls `registerFlutterCompileTask`, `registerFlutterAssetTasks` and `registerFlutterJniLibsTask`. Only application variants then call the new `configureApplicationOutputs` (per-ABI versionCodes and the flutter-apk copy), which takes an `ApplicationVariant`. There is no mid-lambda `return@onVariants`. 2. **Every module variant is configured.** `shouldCompileFlutterForVariant` returns true for a `LibraryVariant`. The command-line check (`shouldConfigureFlutterTask`, flutter#109560) compares the command line with the project's own `assemble<Variant>` names, and a host task such as `:app:assembleDemoStaging` never matches the module variant it consumes (`debug`, via `matchingFallbacks`). A module has three variants: the template's `debug` and `release`, plus the `profile` build type the plugin creates. It declares no flavors (`templates/module/android/library_new_embedding/Flutter.tmpl/build.gradle.tmpl`). The tasks are registered lazily, so a build runs only the module variant the host consumes. `--dry-run` on a host with flavor `demo` and build types `staging` (falls back to `debug`) and `prod` (falls back to `release`): | Host task | Module Flutter tasks in the task graph | | --- | --- | | `app:assembleDemoDebug`, `app:assembleDemoStaging` | `compileFlutterBuildDebug`, `copyFlutterAssetsDebug`, `copyJniLibsflutterBuildDebug` | | `app:assembleDemoRelease`, `app:assembleDemoProd` | `compileFlutterBuildRelease`, `copyFlutterAssetsRelease`, `copyJniLibsflutterBuildRelease` | 3. **Build mode from the module variant.** `registerFlutterCompileTask` reads the library variant's `buildType` and public `Component.debuggable`, so the mode follows the module variant the host consumes. 4. **jniLibs wired from the compile task provider.** `registerFlutterJniLibsTask` takes `compileTaskProvider` and sets `intermediateDir` from `compileTaskProvider.map { it.outputDirectory }`. The `tasks.matching { it.name == … }` dependency and the `findByName` provider are gone. Both copy tasks are registered only together with their compile task, so `intermediateDir` is required (not `@Optional`) in `CopyFlutterJniLibsTask` and `CopyFlutterAssetsTask`, and their `isPresent` branches are removed. 5. **`flutter.hostAppProjectName`.** `warnIfHostAppProjectNameIsSet` reads `project.providers.gradleProperty(…)` (not `rootProject.hasProperty`) and logs `Warning: The Gradle property 'flutter.hostAppProjectName' has no effect. Remove it from gradle.properties.` The `flutter.hostAppProjectName=SampleApp` line is removed from `dev/integration_tests/pure_android_host_apps/android_custom_host_app/gradle.properties`, so devicelab `module_custom_host_app_name_test` (host project `:SampleApp`) checks that a host not named `app` builds without the property. 6. **Deleted:** - From `FlutterPlugin.kt`: the host-project lookup, the `appProject.afterEvaluate` × `libraryVariants.all` × `applicationVariants.all` loop, the `merge<HostVariant>Assets.dependsOn` edge, `addFlutterDepsForModule` and `addCopyFlutterAssetsDependency`. - From `FlutterPluginUtils.kt`: `shouldConfigureFlutterTask(Project, Task)` and `buildModeFor(com.android.builder.model.BuildType)`. Both lost their last caller. 7. **Docs.** - `Migrating-Flutter-Gradle-Plugin-to-AGP-public-API.md`: decision record 5 and "Features that must break" item 6 are rewritten. - [`website-page-draft.md`](https://github.com/flutter/flutter/blob/master/docs/platforms/android/website-page-draft.md) (publishing tracked in [flutter#193713](flutter#193713)): the add-to-app section is rewritten. - The text these replace said that the warning names a removal milestone (it does not), and that a host `staging` debuggable build type gets debug artifacts (the module variant decides). It also said that `tasks.getByPath(":flutter:copyFlutterAssetsDebug")` fails with "Task with path … not found". That is false: in the scratch host below it returns a `CopyFlutterAssetsTask`. 8. **Tests.** - New `FlutterPluginTest` cases: - jniLibs wiring from the compile task provider, with no name lookups; - a module variant configured for a single unrelated command-line task, with no APK wiring; - the module build mode for `debug`, `profile` and `release`; - the `hostAppProjectName` warning, with no host lookup, and no warning when it is unset. - The skip test also checks that no jniLibs task is registered. - The new tests share stub helpers (`stubTaskRegistration`, `captureTaskConfiguration`, `setCommandLineTasks`). - devicelab `module_host_with_custom_build_test` gets a final section. The host fixture adds a build type `qa` (debuggable, falls back to `release`). The section runs `app:assembleDemoQa` with `android.newDsl=true` and `flutter.hostAppProjectName=app`. It checks that the output has the warning, and that `app-demo-qa.apk` has the Flutter assets and `libapp.so` for arm64-v8a and armeabi-v7a, and no debug assets. The existing sections already check `app:assembleDemoStaging` (debug artifacts). ### Audit resolution [PR 8 draft audit](reidbaker-agent#8 (comment)): | Item | Resolution | | --- | --- | | 1. String-based compile task lookup for jniLibs | Provider wiring, `FlutterPlugin.kt:549-553`. Unit test `onVariants wires the jniLibs copy to the output of the variant's compile task`. | | 2. Mid-block `return@onVariants` | None. `onVariants` (`:329-350`) calls helpers; app-only wiring is in `configureApplicationOutputs` (`:647`). | | 3. Redundant `dependsOn` | None on the assets copy (`:497-530`) or the jniLibs copy (`:536-560`). | | 4. Dead overload; `rootProject` read; fixture property | `shouldConfigureFlutterTask(Project, Task)` deleted. `providers.gradleProperty` at `:665`. Fixture line removed. | [PR 7 draft audit](reidbaker-agent#7 (comment)): its four items were resolved in flutter#193693. The stale "registered in `applicationVariants`" KDoc it named has no counterpart here; the `registerFlutterCompileTask` KDoc names no project type. ### Why removing the deleted code is safe - The deleted module path used `com.android.build.gradle.LibraryExtension.libraryVariants`, `AbstractAppExtension.applicationVariants`, `BaseVariant.mergeAssetsProvider` and `BaseVariantOutput.processResourcesProvider`. AGP does not register those extensions with `android.newDsl=true`, which is why `33f39876b92` fails with `Check failed.` in the host `afterEvaluate` block that looks up `LibraryExtension`. Its replacements (`LibraryVariant`, `Component.debuggable`, `Sources.assets`/`jniLibs.addGeneratedSourceDirectory`) are public `com.android.build.api` APIs. FGP compiles against AGP 9.1.1, the minimum on `master` (flutter#193813), and `Component.getDebuggable` also exists in gradle-api 8.11.1 (checked with `javap`). - Remaining `hostAppProjectName` users: none. `git grep hostAppProjectName` hits only this PR's code, tests and docs, plus an unrelated iOS `XcodeProject.hostAppProjectName` in the tool. The templates, `include_flutter.groovy`, `module_plugin_loader.gradle` and devicelab do not read it. devicelab `module_custom_host_app_name_test` (host project `:SampleApp`) passes without the fixture property. - `com.android.builder.model.BuildType` and `shouldConfigureFlutterTask(Project, Task)` have no remaining references. ### Removed comments, docstrings and tests | Removed | Reason | | --- | --- | | KDoc and two `TODO(gmackall)` comments (flutter#166550) on `addFlutterDepsForModule`; KDoc on `addCopyFlutterAssetsDependency` | Functions deleted. flutter#166550 stays open for the rest of the stack. | | The "host app build variant → Flutter variant" table comment in the module block | The mapping is gone; the module variant decides the mode. | | KDoc of `shouldConfigureFlutterTask(Project, Task)` and `buildModeFor(ModelBuildType)` | Functions deleted. | | `flutterCompileTaskName` KDoc | Nothing references the task by name, and the one-line name builder needs no doc. | | flutter#188785 comment in `registerFlutterJniLibsTask`, and the `@Optional` KDoc and "no Flutter build" comment in `CopyFlutterJniLibsTask` | The jniLibs task is registered only with its compile task. `gradle_libapp_so_packaging_test` "app:assembleAndroidTest builds when no Flutter compile task is configured" still passes. | | `@Optional` KDoc on `CopyFlutterAssetsTask.intermediateDir` (it kept `@Optional` for the module migration) | The migration is this PR. | | `FlutterPluginUtilsTest`: `buildModeFor returns profile if the BuildType has name profile`, `buildModeFor returns debug if the BuildType is debuggable`, `buildModeFor returns release if the BuildType is not debuggable and not named profile` | Overload deleted. `buildModeFor with a name and debuggable flag prefers the profile name over debuggability` covers the same cases. | | `FlutterPluginUtilsTest`: four `shouldConfigureFlutterTask` tests used a mocked `Task` | Kept with the same names and inputs, calling the `String` overload. | | `CopyFlutterAssetsTaskTest`: `clears the destination directory when there is no flutter build for the variant` | The input is required, so that case cannot happen. Replaced by `removes staged assets that the flutter build does not produce`, which keeps the stale-file check. | | Comments trimmed per review (`a6be16034c8`): the `PROP_HOST_APP_PROJECT_NAME` KDoc; the `registerFlutterJniLibsTask` KDoc sentence naming its compile task; the "An application project only has application variants." comment above the `check`; restatements in the KDoc of `shouldCompileFlutterForVariant` (PR 7's paragraph restored verbatim), `registerFlutterCompileTask`, `configureApplicationOutputs`, `warnIfHostAppProjectNameIsSet` and both `intermediateDir`s; test helper KDocs and integration test comments; migration doc item 6 | Each restated the code. What remains is why library variants are ungated, why the inputs are required, and the test intent. Added code-comment lines vs `f668780fd47`: 85 → 39. | | `packages/flutter_tools/test/integration.shard/android_add_to_app_module_test.dart` (added by this PR, then deleted per review, `2efbd345505`) | Its `qa` check moved into devicelab `module_host_with_custom_build_test`, which builds the same fixture. Its `staging` check duplicated the devicelab staging section, except for `android.newDsl=true`. The devicelab task runs presubmit on Linux, Mac and Windows (`.ci.yaml`), like the deleted test. | | Rewritten per review (`b8cd39bcf81`): the library paragraph of the `shouldCompileFlutterForVariant` KDoc, the assets `GradleException` comment, the `registerFlutterCompileTask` and `configureApplicationOutputs` KDocs | They explained too little ("What does this mean?") or restated the code. | | The `check(variant is ApplicationVariant)` that PR 7 added in the app `onVariants` path, moved by this PR into `configureApplicationOutputs` (`798ac295725`) | The call site branches on `variant is ApplicationVariant`, and the function takes an `ApplicationVariant`, so the compiler enforces the type. In an application project every variant is an `ApplicationVariant`, so the APK wiring runs for the same variants. | | `-Pandroid.compatibility.enableLegacyApi=false` in the devicelab `qa` section (`1366874099e`) | AGP 9.1.0, 9.1.1 and 9.3.1 mark the option `Deprecated(VERSION_10_0)`. Its consumers back the variant API that `android.newDsl=true` removes. | ### Behavioral and compatibility notes 1. **`flutter.hostAppProjectName` has no effect (breaking).** It logs a warning and names no removal milestone. Its only use was the host lookup. 2. **The Flutter build mode follows the module variant the host consumes.** A host `debug`/`profile`/`release` consumes the module variant of the same name. A custom host build type gets the module variant its `matchingFallbacks` select. With a debuggable `qa` that falls back to `release`, built as the only task, the APKs contain: | Build | `flutter_assets` | `libapp.so` | Flutter tasks run | | --- | --- | --- | --- | | `33f39876b92` (`newDsl=false`) | no | no | `compileFlutterBuildDebug` (not packaged) | | This PR | yes | yes | `compileFlutterBuildRelease` | 3. **A host `assemble<Variant>AndroidTest` run on its own runs the module's `flutter assemble` for that variant.** `--dry-run` of `:app:assembleDemoDebugAndroidTest` lists `:flutter:compileFlutterBuildDebug` with this PR, and only `copyJniLibsflutterBuildDebug` on `33f39876b92`. This is a cost, accepted because the module cannot map host task names to its own variants. 4. **Module assets are a generated assets source directory.** The deleted code copied them into the module's merged-assets output after `mergeAssets`, and made every build re-run `clean<MergeAssetsTask>`. Collisions resolve by source-set priority, as for apps since PR 6. 5. **`copyFlutterAssets<V>` in a module is a `CopyFlutterAssetsTask`, not a `Copy`.** Lookups by name still work; lookups typed as `Copy` fail. 6. Related open work: [flutter#191703](flutter#191703) reports an NPE in `getLegacyAndroidExtension` with `newDsl=true`. That function was removed earlier in the stack, and this PR removes the next failure on that path. A module with plugins on `newDsl=true` is not tested here. No conflict with [flutter#193682](flutter#193682) or [flutter#193610](flutter#193610) (`gradle_errors.dart`); this PR changes no message that the tool matches. [flutter#184409](flutter#184409) asks for separate library and app logic in `addFlutterTasks`; this PR shares `onVariants` and splits only the app outputs. ### Integration test coverage of the changed paths | Path | Tests | | --- | --- | | Module from source, `newDsl=true`, debuggable custom build type on `release`, `hostAppProjectName` warning | devicelab `module_host_with_custom_build_test`, `app:assembleDemoQa` section | | Module from source, flavor and custom build types, debug and release | devicelab `module_host_with_custom_build_test` (other sections; `app:assembleDemoStaging` needs the ungated `LibraryVariant`), `build_android_host_app_with_module_source` | | Host project not named `app`, without `hostAppProjectName` | devicelab `module_custom_host_app_name_test` | | Module AAR (`flutter build aar`) and AAR host | devicelab `build_aar_module_test`, `build_android_host_app_with_module_aar`; `android_obfuscate_test`, `android_plugin_new_output_dir_test` | | App path | `gradle_libapp_so_packaging_test`, `flutter_build_apk_split_per_abi_test`, `android_gradle_asset_merging_test`, `android_run_flutter_gradle_plugin_tests_test` | ### Tests run locally (macOS, JDK 17) On the head after merging `master` (`23ddd6b74e6`; FGP compiles and tests against AGP 9.1.1): - `./gradlew test --rerun-tasks` in `packages/flutter_tools/gradle`: 33 suites, 277 tests, 0 failures. - ktlint 1.5.0 with the CI `.editorconfig` and baseline: clean. - `dart format` and `dart analyze --fatal-infos` on `module_host_with_custom_build_test.dart` and `gradle_libapp_so_packaging_test.dart`: clean. - `gradle_libapp_so_packaging_test`: 4 tests pass. - devicelab, all `success`: `module_host_with_custom_build_test`, `module_custom_host_app_name_test`, `build_android_host_app_with_module_source`. On `1366874099e`: - `./gradlew test --rerun-tasks` in `packages/flutter_tools/gradle`: 33 suites, 274 tests, 0 failures. - ktlint 1.5.0 with the CI `.editorconfig` and baseline: clean. - `dart format` and `dart analyze --fatal-infos` on `module_host_with_custom_build_test.dart`: clean. - devicelab `module_host_with_custom_build_test`: `success`. The `app:assembleDemoQa` section ran `./gradlew app:assembleDemoQa -Pandroid.newDsl=true -Pflutter.hostAppProjectName=app` and logged the `flutter.hostAppProjectName` warning. On `d8f869ec468`: - `./gradlew test --rerun-tasks` in `packages/flutter_tools/gradle`: 33 suites, 274 tests, 0 failures. - ktlint 1.5.0 with the CI `.editorconfig` and baseline: clean. - `dart format` and `dart analyze --fatal-infos` on `module_host_with_custom_build_test.dart`: clean. - devicelab `module_host_with_custom_build_test`: `success`. The `app:assembleDemoQa` section logged the `flutter.hostAppProjectName` warning. On `a6be16034c8` (the merge and the trim): - `./gradlew test` in `packages/flutter_tools/gradle`: 33 suites, 274 tests, 0 failures. - ktlint 1.5.0 with the CI `.editorconfig` and baseline: clean. - `dart format` and `dart analyze --fatal-infos` on the changed Dart files: clean. - `android_add_to_app_module_test` (deleted in `2efbd345505`): passes. - devicelab `module_custom_host_app_name_test` (fixture without `flutter.hostAppProjectName`): `success`, and the log has no `hostAppProjectName` warning. On `8c9a54d8297` (before the merge of PR 7's review changes and the comment trim): - Integration tests, one at a time, all pass: `android_add_to_app_module_test` (1), `android_obfuscate_test` (2), `android_plugin_new_output_dir_test` (1), `gradle_libapp_so_packaging_test` (4), `flutter_build_apk_split_per_abi_test` (4), `android_gradle_asset_merging_test` (4), `android_run_flutter_gradle_plugin_tests_test` (2). - `android_add_to_app_module_test` on `33f39876b92`: fails with `Check failed.`. With `variant is LibraryVariant ||` removed: fails with "does not contain 'assets/flutter_assets/AssetManifest.bin'". - devicelab via `bin/test_runner.dart test --exit`, all `success`: `module_host_with_custom_build_test`, `module_custom_host_app_name_test`, `build_android_host_app_with_module_source`, `build_aar_module_test`, `build_android_host_app_with_module_aar`. - Scratch module plus the devicelab host fixture (AGP 9.3.1, Gradle 9.5.0): - Defaults: `assembleDemoStaging` has `kernel_blob.bin` and no `libapp.so`; `assembleDemoRelease` has `libapp.so` for arm64-v8a, armeabi-v7a and x86_64. - Same with `android.compatibility.enableLegacyApi=false`, and with `android.newDsl=true` added. </details> --- ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant in-code documentation (doc comments with `///`). - [x] If this PR introduces a new feature or capability, I created and linked a website documentation issue or PR in [flutter/website] (or verified none is needed). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [flutter/website]: https://github.com/flutter/website [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
…Is (flutter#193962) PR 7 (flutter#193693) merged to master as `ecd1ab06865` and PR 8 (flutter#193717) as `4249ccbc18a`. This branch merges master at `4521d8d6634`, so the Files tab shows only PR 9's changes. PR 9 commits: - `86523c549fc` Move aar_init_script to public AGP APIs - `3edc95d1fa0` Check requested AAR variants in the module and each plugin - `da9646aad53` Address review: derive requested variants from components, register AAR tasks lazily - `94a29907065` Clarify aar_init_script comments from the adversarial review - `ea08280d886` Merge commit '23ddd6b74e6' into agp-aar-init-script (PR 8's head with master at `ecd1ab06865`; no conflicts, no change to PR 9's files) - `a17fafcc35e` Merge commit '5ba8961b517' into agp-aar-init-script (master with PR 8 at `4249ccbc18a`; no conflicts, no change to PR 9's files) - `77ba449ea4a` Filter requested AAR components before adding tasks - `adbfb9cd640` Drop the AGP version list from the duplicate-variant comment - `f2b7111c51e` Docs: state the AAR variant requirement in plain words - `cd10f766b5b` Register an AAR task for every variant, publish only requested ones - `b9493299916` Merge commit '4521d8d6634' into agp-aar-init-script (master, including flutter#193649; no conflicts, no change to PR 9's files) ## Description This is PR 9 of 11 in the AGP 9.1.0 / public `gradle-api` migration stack ([flutter#180137](flutter#180137), [flutter#166550](flutter#166550)). `flutter build aar` passes `packages/flutter_tools/gradle/aar_init_script.gradle` to Gradle with `-I`. The script read `android.libraryVariants` and AGP's internal `publishing.singleVariants`. With AGP 9's `android.newDsl=true`, `flutter build aar` on a module fails on `5ba8961b517` with `Can't generate AAR on a non Android library project.` (script line 13), and then at line 178 (`Could not get unknown property 'libraryVariants'`). With this PR the script uses only `pluginManager.hasPlugin`, `Publishing.singleVariant`, software `components` and `tasks.register`, and the build succeeds with `newDsl` on and off, on AGP 9.3.1 and on master's minimum AGP 9.1.1, and on AGP 8.11.1 when FGP's version check is skipped. **Breaking:** no. Builds that succeed on `5ba8961b517` succeed with the same artifacts. Two failing cases change: a module or plugin build file that declares `singleVariant` for only some variants builds, and a variant mismatch between the module and a plugin fails with a Flutter error instead of `Task with name … not found`. **Base:** `master` at `4521d8d6634`. `aar_init_script.gradle`, `build_aar_module_test.dart` and both docs are the same at `4521d8d6634` and `5ba8961b517`, so the comparisons below against `5ba8961b517` hold for `4521d8d6634`. <details> <summary>Agent authored details</summary> ### Changes All in `aar_init_script.gradle` unless noted. Line numbers are at `b9493299916`. 1. **Library check** (`:13`): `project.pluginManager.hasPlugin("com.android.library")` replaces `android.hasProperty("libraryVariants")`. The error text is unchanged. 2. **Publishing declarations** (`afterProject`, `:185-219`): Flutter calls `android.publishing.singleVariant(<variant>) { withSourcesJar(); withJavadocJar() }` for each variant, one variant at a time. If the project's build file already declared that variant, AGP throws, and `isDuplicateSingleVariantError` (`:125-134`) matches AGP's exact message, so the build file's declaration is kept. Its comment links the AGP source of the message ([`AbstractPublishing.kt` at `studio-2025.3.2`, AGP 9.1.0](https://android.googlesource.com/platform/tools/base/+/refs/tags/studio-2025.3.2/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/dsl/AbstractPublishing.kt#38)). Any other exception is rethrown (`:201-203`). The guard that read `publishing.singleVariants` and skipped every Flutter declaration once the build file declared any variant is deleted. 3. **No module-to-plugin `dependsOn`** (`projectsEvaluated`): the loop over `moduleProject.android.libraryVariants` that made each module `assembleAar<Variant>` depend on the plugin's task (with eager `tasks.named(...).get()`) is deleted. The tool requests `assembleAar<Variant>` without a project path (`gradle.dart` `buildGradleAar`, `command.add(aarTask)`), and Gradle runs a task selector that is only a task name in every project that has the task ([Gradle docs](https://docs.gradle.org/current/userguide/command_line_interface.html#executing_tasks_in_multi_project_builds): "You can also run a task for all subprojects using a task selector that consists of only the task name."). Each plugin publishes from its own task. The comment at `:247-248` says this. 4. **Tasks, publications and task name** (`configureProject` `:27-37`; `aarTaskName` `:93-97`; `requestedAarComponents` `:99-103`): AGP adds one software component for each `singleVariant` declaration, with the variant's name. `registerAarTask` (`:105-110`) registers `assembleAar<Variant>` for every component, so a misspelled `--flavor` gets Gradle's candidate list (`Some candidates are: 'assembleAarDebug', ...`). `requestedAarComponents` returns the components whose `assembleAar<Variant>` name is in `startParameter.taskNames`; only those get a publication (`addAarPublication`, `:112-123`), because `publish` publishes every publication in the project. The check uses the same function. `aarTaskName` is the only place that builds `assembleAar<Variant>`; its comment points to the tool's `getAarTaskFor` (`gradle.dart`). Nothing parses a task name back into a variant. The `component.name != "all"` filter from `5ba8961b517` is deleted (see "Why removing the deleted code is safe"). 5. **Lazy task registration** (`registerAarTask`): `project.tasks.register` replaces `project.tasks.create(name:)`. The configuration action only adds `finalizedBy "publish"`. `publications.create` runs in `addAarPublication`, not in the action, because maven-publish registers tasks for each publication and Gradle rejects that while it realizes a task (`DefaultTaskContainer#register(String, Class, Action) on task set cannot be executed in the current context.`, seen when `publications.create` was inside the action). 6. **Requested-variant check** (`checkProjectsHaveRequestedVariants`, `:136-175`, called at `:252`): Gradle skips projects that lack the requested task, so the script checks the module and each Android plugin. The requested variants are the names of the components that `requestedAarComponents` returns for those projects. A project without a requested variant fails the build with a `GradleException` that names the variant and the project: - module: `Flutter can't build the AAR for variant 'debug' because module project ':flutter' has no variant with that name. Its variants are: demoDebug, demoProfile, demoRelease, fullDebug, fullProfile, fullRelease. Pass the module's product flavor with --flavor, or omit --flavor if the module has no product flavors.` - plugin: `Flutter can't build the AAR for variant 'demoDebug' because plugin project ':plugin_a' has no variant with that name, so the plugin AAR that the module depends on can't be published. `flutter build aar` requires the module and each of its Android plugins to declare the same product flavors.` - A requested task that matches no project's variant (for example `--flavor foo`) is left to Gradle: `Task 'assembleAarFooDebug' not found in root project 'android_generated' and its subprojects. Some candidates are: 'assembleAarDebug', 'assembleAarDemoDebug', 'assembleAarFullDebug'.` That is the same error as on `5ba8961b517`. The check's doc says so. (`3edc95d1fa0` parsed the task-name prefix and reported a Flutter module error in this case; `da9646aad53` drops the parsing per review.) 7. **Docs.** Both docs describe behavior only. - `Migrating-Flutter-Gradle-Plugin-to-AGP-public-API.md`: the P8 row of the phase table and "Features that must break" item 8 are rewritten. The "Why" starting-state list (L38-39) is unchanged, as in other PRs of the stack. - [`website-page-draft.md`](https://github.com/flutter/flutter/blob/master/docs/platforms/android/website-page-draft.md) (publishing tracked in [flutter#193713](flutter#193713)): the `flutter build aar` section is rewritten. It said Flutter "detects the overlap and reports it with an actionable error". The code keeps the build file's declaration instead, so the docs were changed to match the code. It says (L234-237) that when a build file declares `singleVariant` for a variant, Flutter keeps that declaration and declares the other variants, and (L238-241) that the module and each Android plugin need a variant with the built name. 8. **Tests.** devicelab `build_aar_module_test` gets two final sections: - With `android.newDsl=true` and `android.builtInKotlin=true` in `.android/gradle.properties`, and `android.publishing.singleVariant("release") { withSourcesJar() }` in `.android/Flutter/build.gradle`, it runs `flutter build aar --no-profile`. It checks that the edits survive, that release has the AAR and sources jar and no javadoc jar, that debug has the javadoc jar, and that the plugin AARs exist for release and debug. - It replaces that declaration with a `demo` product flavor and checks both errors: `flutter build aar --no-profile --no-release` fails with the module error, and the same with `--flavor demo` fails with the plugin error. `da9646aad53`'s commit message says the check's doc "says when it returns and when it throws". That doc sentence was added in `94a29907065`. ### Bug history (moved out of the docs) The docs describe behavior only, so the history that `3edc95d1fa0` put in them is here: - On `5ba8961b517`, the `afterProject` guard skipped all of Flutter's `singleVariant` declarations for a project whose build file declared any variant. For a module with at least one Android plugin that declared only `release`, every `flutter build aar` failed with `Task with name 'assembleAarDebug' not found in project ':flutter'`. With this PR that build succeeds (table 1 below). - On `5ba8961b517`, the `dependsOn` loop failed with `Task with name 'assembleAar<Variant>' not found in project ':<plugin>'` whenever a plugin lacked any module variant, requested or not, and nothing checked that the module had the requested variant. With this PR only requested variants are checked, in the module and in each plugin (table 2 below). ### Audit resolution [PR 9 draft audit](reidbaker-agent#9 (comment)), and stack audit items M4 and L8: | Item | Resolution | | --- | --- | | 1. Eager `.get()` and cross-project task lookup by name | The wiring is deleted, not made lazy: it was redundant (Changes 3). No `.get()` or `tasks.named` in the script. The check reads software components, and `registerAarTask` uses `tasks.register`. | | 2. Broad `catch (Exception)`; overstated `multipleVariants` warning | Only AGP's exact duplicate message is ignored (`:130-134`); everything else is rethrown (`:201-203`). No warning is logged, so there is no `multipleVariants` claim. | | 3. Ext property without `flutter.internal.` | No ext property. `afterProject` fires once per project (instrumented: count 1 for `:`, `:app`, `:flutter`, `:plugin_a` and the included FGP build), and a second call would only hit ignored duplicates. The "fires more than once in composite settings" claim is dropped. | Why the message match and not the type: AGP reports the duplicate from `AbstractPublishing.checkSingleVariantUniqueness` through `SyncIssueReporterImpl`, which throws `com.android.builder.errors.EvalIssueException` in STANDARD mode. That class is in `com.android.tools.build:builder`, not in `gradle-api`, and is not on the init script's classpath. `javap -v` on `AbstractPublishing` shows the same text in AGP 8.11.1, 9.1.0 and 9.3.1. AGP checks before it adds the component (8.11.1 `AbstractPublishing.kt:27-35`), so the build file's declaration stays. If AGP changes the text, the exception is rethrown and the build fails loudly; devicelab `build_aar_module_test` exercises the path on the template AGP. Why not declare first or query: the public `Publishing` DSL in `gradle-api-9.1.0.jar` has only `singleVariant(String)` and `singleVariant(String, Function1)`, and `LibraryPublishing` adds only `multipleVariants(...)` (`javap`), so there is no public way to list declared variants. Declaring Flutter's variants in `beforeProject` + `pluginManager.withPlugin("com.android.library")` makes the build file fail at its own `singleVariant("release")` line with the duplicate error (probe). Why not AGP types in the init script: `import com.android.build.api.dsl.LibraryExtension` fails with `unable to resolve class`. With `gradle-api:9.3.1` on the `initscript` classpath, the init script gets its own copy of the class from a different classloader, so `instanceof` is `false` and `extensions.getByType(LibraryExtension)` throws `Extension of type 'LibraryExtension' does not exist. Currently registered extension types: [..., LibraryExtension, ...]` (probes). `multipleVariants`: `PublishingUtil.ensureComponentNameUniqueness` errors only when a `multipleVariants` component name equals a `singleVariant` name. Flutter declares the same `singleVariant`s on `5ba8961b517` when the build file declares none, so that behavior is unchanged. It is not tested here. ### Why removing the deleted code is safe - `android.libraryVariants` does not exist with `newDsl=true` (reproduced above). AGP 9.1.0 marks its class, `com.android.build.gradle.LibraryExtension`, `@Deprecated` ("This class is not used for the public extensions in AGP when android.newDsl=true, which is the default in AGP 9.0, and will be removed in AGP 10.0."), and the class is not in `gradle-api-9.1.0.jar`. `singleVariants` is an internal property (`getSingleVariants()` on `com.android.build.gradle.internal.dsl.AbstractPublishing`, `javap`). It resolves from Groovy with `newDsl=true`, so it was not a failure, only internal API. - The `dependsOn`: with it removed, the task graph for `assembleAarRelease` on case (a) has `:plugin_a:assembleAarRelease`, `:plugin_a:bundleReleaseAar`, `:plugin_a:publishReleasePublicationToMavenRepository` and `:plugin_a:publish`. `:flutter:generatePomFileForReleasePublication` depends on no `:plugin_a` task, and `:flutter:publishReleasePublicationToMavenRepository` depends only on the plugin's compile-classpath tasks, so the order between the module's and the plugin's publishing doesn't matter. The module POM refers to the plugin by coordinates (`plugin_a_release`). devicelab `build_aar_module_test`, `build_android_host_app_with_module_aar` and `android_java17_build_android_host_app_with_module_aar` pass (they check the plugin AAR/POM and that the module POM depends on `plugin_with_android_release`). - The `all` filter (`component.name != "all"`, added in flutter#101891 `606e91ad63d`): AGP 7.x created an automatic multi-variant component named `all` when a project used no publishing DSL ([`PublishingUtil.kt` at `studio-2021.3.1`](https://android.googlesource.com/platform/tools/base/+/refs/tags/studio-2021.3.1/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/utils/PublishingUtil.kt), `createPublishingInfoForLibrary` and `publishingFeatureOptIn`; same in the AGP 7.3.1 jar). flutter#101891 published it separately with classifiers and skipped it for the `_<variant>` publications; flutter#112149 (`8e30cc9c56b`) removed that publication and kept the skip. AGP 8.0 disabled automatic components by default ([AGP 8.0.0 release notes](https://developer.android.com/build/releases/past-releases/agp-8-0-0-release-notes)), and the AGP 8.11.1, 9.1.1 and 9.3.1 jars list `android.disableAutomaticComponentCreation` as removed, with no `all` component code. This script declares `singleVariant` for every variant, which also turns automatic components off on AGP 7.x. A probe that prints `project.components` after `projectsEvaluated` on all four matrix configurations shows only per-variant names (`[debug, profile, release]` for `:flutter`, `:app` and each plugin, `[demoDebug, …, fullRelease]` for the flavored module, `[freeDebug, freeProfile, freeRelease]` for `plugin_flavored`, `[debug, release]` for the root), never `all`. - `tasks.register`: the publication is created at configuration time only for a requested task name, as with `tasks.create`, whose configuration closure ran immediately. Only the task object is created lazily. Table rows below and the devicelab runs show the same artifacts. ### Removed comments, assertions and code | Removed | Reason | | --- | --- | | `afterProject` comment item 2, "The project has already defined which variants to publish…", and the `singleVariants.size()` guard it described | The guard read internal API and skipped all of Flutter's declarations once a build file declared any. Item 1 of that comment became "Exit early if the project doesn't have the Android Gradle plugin applied." | | The `projectsEvaluated` comment "Configure the `assembleAar<variantName>` task for each plugin's projects and make the module's equivalent task depend on the plugin's task.", with the `libraryVariants.all` loop, both `tasks.named(...).get()` calls, both `assert(... != null)` and the `dependsOn` | The dependency is redundant (see above). `tasks.named(...).get()` throws before the asserts could fail, so they never checked anything. | | `addAarTask` and its `variantName` local | Split into `registerAarTask` (every component) and `addAarPublication` (requested components). The task name comes from `aarTaskName`, which the check also uses. | | `addAarTask` comment "This check is required to be able to configure the archives before `publish` runs.", with the check | The check is `requestedAarComponents`, applied by the caller before `addAarPublication`. Its reason is the comment on `addAarPublication`: "`publish` publishes every publication in the project, so only requested components get one." | | The `component.name != "all"` filter in `configureProject`, and the fragment "(except "all" since that is used to publish artifacts in the new way)" from "Create a default MavenPublication for the variant" | Nothing creates an `all` component where this script runs (see "Why removing the deleted code is safe"). | | From the migration doc item 8 and the website page draft: the sentences about the deleted guard, the `Task with name … not found` failures, and what the deleted code did and did not check | The docs describe behavior only. The history is in "Bug history" above. | ### Behavioral and compatibility notes Matrix projects (local scratch): (a) module + Android plugin `plugin_a`; (b) module with flavors `demo`/`full` + `plugin_a`; (c) module + `plugin_a` + `plugin_flavored` (flavor `free`); (d) module declaring `singleVariant("release") { withSourcesJar() }` + `plugin_a`. 1. **A build file that declares `singleVariant` for some variants builds.** Case (d), `flutter build aar`: | Script | Result | | --- | --- | | `5ba8961b517` (AGP 9.3.1 `newDsl=false`) | exit 1: `Task with name 'assembleAarDebug' not found in project ':flutter'.` | | This PR (all four configurations below) | exit 0. Release has the AAR, POM and sources jar, and no javadoc jar (the build file's declaration). Debug and profile have sources and javadoc jars. Plugin AAR/POM for all three. | 2. **Variant mismatches fail with a Flutter error that names the project.** Only requested variants are checked. The `5ba8961b517` column is AGP 9.3.1 with `newDsl=false`. | Case | `5ba8961b517` | This PR (all four configurations) | | --- | --- | --- | | (b) `--flavor demo` | `Task with name 'assembleAarDemoDebug' not found in project ':plugin_a'.` | plugin error for `demoDebug` and `:plugin_a` | | (c) default | `Task with name 'assembleAarDebug' not found in project ':plugin_flavored'.` | plugin error for `debug` and `:plugin_flavored` | | (b) no `--flavor`, `--no-profile --no-release` | `Task with name 'assembleAarDemoDebug' not found in project ':plugin_a'.` | module error for `debug`, listing the module's variants | | (c) `--flavor free`, `--no-profile --no-release` | `Task with name 'assembleAarDebug' not found in project ':plugin_flavored'.` | module error for `freeDebug` | | (b) `--flavor foo` | Gradle `Task 'assembleAarFooDebug' not found in root project …` | same Gradle error | Without the module check (`86523c549fc`), the module-error cases exit 0 with "✓ Built" and a repo that has only the plugin AAR. 3. **Path-qualified requests.** `./gradlew -I aar_init_script.gradle … :flutter:assembleAarRelease` exits 0 and publishes nothing, on `5ba8961b517` and on this PR: a publication is created only when `startParameter.taskNames` contains the bare name. The tool sends only the bare name (`gradle.dart` `command.add(aarTask)`). Not changed here. 4. **No warnings.** `flutter build aar` hides Gradle output on success unless `-v` (`ProcessUtils.run` buffers it; `gradle.dart` prints it only on failure, and verified with a `logger.error` probe). Keeping a build file's declaration needs no action, so nothing is logged. Errors are shown. 5. **Configuration cache:** with `org.gradle.configuration-cache=true` in case (a)'s `.android/gradle.properties` (AGP 9.3.1 / Gradle 9.5.0), `flutter build aar` reports the same single problem with the `5ba8961b517` script and with this PR: ``Task `:flutter:compileFlutterBuildDebug` of type `com.flutter.gradle.tasks.FlutterTask`: invocation of 'Task.project' at execution time is unsupported with the configuration cache.`` None comes from the init script. 6. `newDsl=true` with `builtInKotlin=false` fails before the script with any Kotlin plugin (`The 'org.jetbrains.kotlin.android' plugin is not compatible with AGP's 9.0 new DSL`). The devicelab section and matrix set `builtInKotlin=true`. 7. Related open work: no overlap with [flutter#191681](flutter#191681) (`gradle.dart`, `gradle_errors.dart` and tests). No open PR touches `aar_init_script.gradle`. ### Follow-up candidates (pre-existing, not changed here) - `allprojects { apply plugin: "maven-publish" }` applies maven-publish to every project, including ones that never publish. - The `afterProject` and `projectsEvaluated` hooks reach across projects, which Gradle Isolated Projects does not allow. - The `-SNAPSHOT` rewrite of `project.version` in `configureProject`. - The `engine.realm` and `engine.stamp` file reads at configuration time in `configureProject`. - The `is-plugin` branch of `projectsEvaluated` looks unreachable from the tool. - The configuration cache problem in FGP `FlutterTask` (note 5). - `newDsl=true` with KGP (`builtInKotlin=false`), for PR 10. - Mapping between flavored module variants and plugin variants (for example, plugin `release` for module `demoRelease`). - `singleVariant` is declared on the root project and `:app`, which never publish. - Gradle output is hidden on success, so nothing the script logs at info or warn level reaches the user without `-v`. ### Integration test coverage of the changed paths Searched `dev/devicelab/bin/tasks` and `packages/flutter_tools/test/integration.shard` for `build aar` and `assembleAar`. | Path | Tests | | --- | --- | | Library check, publishing declarations, lazy task registration, plugin publishing without `dependsOn` (`newDsl=false`) | devicelab `build_aar_module_test`, `build_android_host_app_with_module_aar`, `android_java17_build_android_host_app_with_module_aar`; `android_obfuscate_test` | | `newDsl=true`, a build file's `singleVariant` declaration kept | devicelab `build_aar_module_test`, `newDsl=true` section | | Module and plugin variant mismatch errors | devicelab `build_aar_module_test`, product flavor section | | AGP 9.1.1 and 8.11.1; a variant that no project has | Manual matrix only | `Linux build_aar_module_test` runs in presubmit for this PR: in `.ci.yaml` its `runIf` includes `dev/**` and `packages/flutter_tools/**`, and it sets neither `presubmit: false` nor `bringup`. It also ran locally on the head (below) with `cd dev/devicelab && ../../bin/dart bin/test_runner.dart test -t build_aar_module_test --exit`. ### Tests run locally (macOS, JDK 17) On the head (`b9493299916`; master merged at `4521d8d6634`): - `gradle test --rerun-tasks` in `packages/flutter_tools/gradle`, with the engine's `third_party/gradle` binary that the README names: 33 suites, 277 tests, 0 failures. - `dart format` and `dart analyze --fatal-infos` on `build_aar_module_test.dart`: clean (unchanged since `3edc95d1fa0`). - Tool tests: `test/commands.shard/hermetic/build_aar_test.dart`, `test/commands.shard/permeable/build_aar_test.dart` and `test/general.shard/android/android_gradle_builder_test.dart`: 56 tests, all pass. - `test/integration.shard/android_obfuscate_test.dart`: 2 tests pass. - devicelab via `bin/test_runner.dart test --exit`, one at a time: `build_aar_module_test` (including both added sections), `build_android_host_app_with_module_aar`, `android_java17_build_android_host_app_with_module_aar`, `build_android_host_app_with_module_source`, `module_host_with_custom_build_test`, all `success`. - The configuration cache comparison (note 5). Manual matrix on `b9493299916`, four configurations: - **9.3.1 off:** AGP 9.3.1 / Gradle 9.5.0, `newDsl=false`, `builtInKotlin=false`. - **9.3.1 on:** AGP 9.3.1 / Gradle 9.5.0, `newDsl=true`, `builtInKotlin=true`. - **9.1.1 on:** AGP 9.1.1 / Gradle 9.3.1 (FGP's minimums on master since flutter#193813), `newDsl=true`, `builtInKotlin=true`. - **8.11.1:** AGP 8.11.1 / Gradle 8.14.3, `newDsl=false`, with `skipDependencyChecks=true` in `.android/gradle.properties`. Without it, FGP stops before the init script runs: `Your project's Gradle version (8.14.3) is lower than Flutter's minimum supported version of 9.3.1.` | Case | 9.3.1 off | 9.3.1 on | 9.1.1 on | 8.11.1 | | --- | --- | --- | --- | --- | | (a) | exit 0, AAR, POM, sources and javadoc jars for `flutter_{debug,profile,release}` and `plugin_a_{debug,profile,release}` | same | same | same | | (b) `--flavor demo` | exit 1, plugin error | same | same | same | | (b) no `--flavor` | exit 1, module error | same | same | same | | (b) `--flavor foo` | exit 1, Gradle `Task 'assembleAarFooDebug' not found in root project` | same | same | same | | (c) | exit 1, plugin error | same | same | same | | (c) `--flavor free` | exit 1, module error | same | same | same | | (d) | exit 0, as (a), except module release has the sources jar and no javadoc jar | same | same | same | Case (a)'s `flutter_release-1.0.pom` depends on `dev.pr9.plugin_a:plugin_a_release:1.0` (checked on `b9493299916`, AGP 9.1.1). Negative control, the master script (same at `5ba8961b517` and `4521d8d6634`), on `b9493299916`: with `newDsl=true` (AGP 9.3.1 and AGP 9.1.1), (a)-(d) all fail with `Can't generate AAR on a non Android library project.`; with `newDsl=false` on AGP 9.3.1, (a) passes and (b)-(d) fail with Gradle's `Task with name 'assembleAar<V>' not found in project ':<p>'.` On `a17fafcc35e`, with only line 13 changed to `hasPlugin`, `newDsl=true` fails at line 178 with `Could not get unknown property 'libraryVariants' for object of type com.android.build.gradle.internal.dsl.LibraryExtensionImpl$AgpDecorated.` devicelab `build_aar_module_test` with that script fails at the `newDsl=true` section with the same error. </details> --- ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant in-code documentation (doc comments with `///`). - [x] If this PR introduces a new feature or capability, I created and linked a website documentation issue or PR in [flutter/website] (or verified none is needed). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [flutter/website]: https://github.com/flutter/website [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
…tensions in FlutterPlugin.apply (flutter#194918) ## Description Part of [flutter#180137](flutter#180137), [flutter#166550](flutter#166550). I broke the check in PR 7/11 ([flutter#193693](flutter#193693)) in the new DSL migration for users that were on AGP 8.11. This fixes the mistake and adds a test. This will likely need to be cherry picked. <details> <summary>Agent authored details</summary> ### Related PR - Follow-up PR: [flutter#195006](flutter#195006). Because `flutter#194918` is a cherry-pick candidate, it only replaces the `OUT_OF_SUPPORT_RANGE_PROPERTY` check in `FlutterPlugin.apply` with a typed `catch (e: DependencyValidationException)`. Removing `OUT_OF_SUPPORT_RANGE_PROPERTY` itself—which touches 7 property setters, a task action, and 14 test assertions across `DependencyVersionChecker.kt` and `DependencyVersionCheckerTest.kt`—is split into [flutter#195006](flutter#195006) so this cherry-pick stays minimal. ### Background When [flutter#193813](flutter#193813) (`ce6e766ad1c`) raised the minimum supported AGP version in `DependencyVersionChecker` to `9.1.1` and bumped the compile-time `agpVersion` in `packages/flutter_tools/gradle/build.gradle.kts` from `8.11.1` to `9.1.1`, the Kotlin compiler began emitting AGP 9 binary method signatures for calls against `ApplicationExtension` (such as `ApplicationExtension.getDefaultConfig()Lcom/android/build/api/dsl/ApplicationDefaultConfig;`, whereas in AGP 8.x `CommonExtension.getDefaultConfig()` returned `DefaultConfig` without a covariant bridge on `ApplicationExtension`). Because `FlutterPlugin.apply` invoked `addFlutterTasks(project)` (which calls `configureAbis` -> `configureAbiWithoutSplits` -> `extension.defaultConfig.ndk` and `DslVersionCodes.from(appExtension)` introduced in [flutter#193693](flutter#193693) / [flutter#192488](flutter#192488)) before running `DependencyVersionChecker.checkDependencyVersions(project)`, building an AGP 8.x project crashed during `addFlutterTasks` with: ```text java.lang.NoSuchMethodError: 'com.android.build.api.dsl.ApplicationDefaultConfig com.android.build.api.dsl.ApplicationExtension.getDefaultConfig()' ``` before reaching `DependencyVersionChecker.checkDependencyVersions(project)`, preventing the user from seeing the actionable minimum AGP version error message and upgrade guidance. ### Changes 1. **`FlutterPlugin.kt`**: - Moves `FlutterPluginUtils.detectApplyingKotlinGradlePlugin(project)` and the `shouldSkipDependencyChecks` / `DependencyVersionChecker.checkDependencyVersions(project)` block to the beginning of `FlutterPlugin.apply(project)` (immediately after `this.project = project`), before `addFlutterTasks(project)` and all `ApplicationExtension` / Android DSL accesses. - Replaces the `OUT_OF_SUPPORT_RANGE_PROPERTY` extra-property check in `FlutterPlugin.apply` with a typed `catch (e: DependencyValidationException)` that rethrows `DependencyValidationException` directly and logs any other `Exception`, removing the comment that described `OUT_OF_SUPPORT_RANGE_PROPERTY`. 2. **`FlutterPluginTest.kt`**: - Adds `FlutterPlugin apply checks dependency versions before configuring project`, verifying via `verifyOrder` that `FlutterPluginUtils.detectApplyingKotlinGradlePlugin(project)` and `DependencyVersionChecker.checkDependencyVersions(project)` run before task registration, `AndroidComponentsExtension` callbacks (`finalizeDsl`, `onVariants`), and `ApplicationExtension` DSL configuration (`defaultConfig`, `buildTypes`). - Adds `FlutterPlugin apply rethrows DependencyValidationException and logs other exceptions`, verifying both exception branches without relying on the extra property, and stubs supported Gradle and AGP versions in `setupTestProjectEnvironment` / `setupMockComponentsExtension`. 3. **`packages/flutter_tools/gradle/AGENTS.md`**: - Updates the `Matrix Verification` bullet on line 40 to reference the supported AGP and Gradle versions defined in `DependencyVersionChecker.kt`. ### Verification - **Unit test negative/positive verification**: - Moving `DependencyVersionChecker` after `addFlutterTasks` or moving `detectApplyingKotlinGradlePlugin` after `DependencyVersionChecker` fails `FlutterPlugin apply checks dependency versions before configuring project`. - Reverting the typed `catch (e: DependencyValidationException)` to the property check fails `FlutterPlugin apply rethrows DependencyValidationException and logs other exceptions`. - `./gradlew test` with the fix applied: all tests pass. - **End-to-end reproduction with AGP 8.11.1 and Gradle 9.3.1**: - Before fix: failed at `FlutterPlugin.configureAbiWithoutSplits` with `java.lang.NoSuchMethodError: 'com.android.build.api.dsl.ApplicationDefaultConfig com.android.build.api.dsl.ApplicationExtension.getDefaultConfig()'`. - After fix: fails cleanly with `Error: Your project's Android Gradle Plugin version (Android Gradle Plugin version 8.11.1) is lower than Flutter's minimum supported version of Android Gradle Plugin version 9.1.1. Please upgrade your Android Gradle Plugin version.` - **Formatting**: - `ktlint 1.5.0` with `--editorconfig=dev/bots/test/analyze-test-input/.editorconfig --baseline=dev/bots/test/analyze-test-input/ktlint-baseline.xml` passed with zero findings. </details> --- ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant in-code documentation (doc comments with `///`). - [x] If this PR introduces a new feature or capability, I created and linked a website documentation issue or PR in [flutter/website] (or verified none is needed). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/CONTRIBUTING.ai.md [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [flutter/website]: https://github.com/flutter/website [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
Raised the minimum AGP version to 9.1.1 and the warn version to 9.3.1. Raised the minimum to AGP 9.1.1 so developers are all on AGP 9+ and it guarantees support for
compileSdk37, which we set here.Pre-launch Checklist
///).If you need help, consider asking for advice on the #hackers-new channel on Discord.
If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance.
Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the
gemini-code-assistbot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.