Skip to content

[AGP 9.1.0 Migration #8] Unify add-to-app module wiring on the variant API - #193717

Merged
auto-submit[bot] merged 14 commits into
flutter:masterfrom
reidbaker-agent:agp-add-to-app-module
Oct 12, 2026
Merged

auto-submit[bot] merged 14 commits into
flutter:masterfrom
reidbaker-agent:agp-add-to-app-module

Conversation

@reidbaker-agent

@reidbaker-agent reidbaker-agent commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

PR 7 (#193693) merged to master as ecd1ab06865, and 23ddd6b74e6 merges master into this branch, so the Files changed 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 (#180137, #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 (#193693) merged as ecd1ab06865.

Agent authored details

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, Remove flutter.gradle shouldConfigureFlutterTask hack #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 (publishing tracked in #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:

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: its four items were resolved in #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 (Raise Minimum Version to AGP 9.1.1 #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 (#166550) on addFlutterDepsForModule; KDoc on addCopyFlutterAssetsDependency Functions deleted. #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.
#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 intermediateDirs; 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: #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 #193682 or #193610 (gradle_errors.dart); this PR changes no message that the tool matches. #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.

Pre-launch Checklist

The application path of the Flutter Gradle Plugin no longer uses the
deprecated BaseVariant API (applicationVariants / ApkVariantOutput):

- CopyFlutterApksTask copies the variant's SingleArtifact.APK outputs,
  read through BuiltArtifactsLoader, into build/outputs/flutter-apk under
  the unchanged names app[-abi][-flavor]-<mode>.apk. assemble<Variant>
  depends on it. The task has no Project or variant fields and uses an
  injected FileSystemOperations.
- A single naming function in the task produces both the declared
  @OutputFiles and the copied file names. Its inputs are the ABIs of the
  variant's outputs (what AGP builds), the flavor, and the build mode.
  The task fails if AGP built an APK for an ABI that was not declared.
- Per-ABI versionCode for --split-per-abi is a read-then-set on
  VariantOutput.versionCode in onVariants. This is a documented
  exception to the no-configuration-time-get rule, because the lazy
  form is circular. The read relies on AGP's compatibility mode
  (android.compatibility.enableLegacyApi, on by default through 9.x).
- The applicationVariants.configureEach block and its
  @Suppress("DEPRECATION") markers are deleted. The add-to-app module
  path keeps using libraryVariants until the next phase.

Behavior change: Flutter's onVariants callback runs before an app's own
androidComponents.onVariants block, while ApkVariantOutput's
versionCodeOverride was applied after it. Apps that transform
output.versionCode there transform Flutter's offset value (for example
arm64-v8a, build 42, x10000: 422000 -> 20420000). The migration doc and
the breaking-change page draft describe this and give a recipe that
keeps the 422000-style numbers. No divergence warning is emitted,
because it would fire on the onVariants pattern that AGP recommends.

Part of flutter#166550.
…ures

- Take the per-ABI versionCode base from a finalizeDsl snapshot of the
  DSL versionCodes (DslVersionCodes) instead of reading
  output.versionCode, which AGP rejects during configuration when
  android.compatibility.enableLegacyApi=false. Warn and skip when the
  DSL declares no versionCode.
- Add a strict-mode (enableLegacyApi=false) case to
  flutter_build_apk_split_per_abi_test.
- CopyFlutterApksTask: use Gradle's Copy/Sync caching annotation and
  reason; explain when the metadata can be missing and when the ABI
  check can fail; point both errors at the plugin that transforms
  SingleArtifact.APK instead of asking for a Flutter issue.
- Document what an APK transform must keep in website-page-draft.md.
- Link the website-page-draft.md references to tracking issue flutter#193713.
- Remove temporal wording from KDoc and docs.
Add-to-app module (library) variants use the same androidComponents.onVariants
path as application variants: the compile, assets and jniLibs tasks are
registered lazily per library variant, and the assets and native libraries are
generated source directories of the variant. The host app consumes them like
any Android library.

Deletes the host-project lookup, the libraryVariants x applicationVariants
cross-wiring, the merge<HostVariant>Assets.dependsOn edge,
addFlutterDepsForModule, addCopyFlutterAssetsDependency,
shouldConfigureFlutterTask(Project, Task) and
buildModeFor(com.android.builder.model.BuildType).

flutter.hostAppProjectName has no effect and logs a warning.

The jniLibs copy is wired from the compile task provider, so its input
directory is required in both copy tasks.
@github-actions github-actions Bot added platform-android Android applications specifically tool Affects the "flutter" command-line tool. See also t: labels. team-android Owned by Android platform team d: docs/ flutter/flutter/docs, for contributors labels Oct 2, 2026
…er comments

- CopyFlutterApksTask: state why caching is disabled (local copy; a cache
  entry would duplicate the APKs) and build both errors through one
  transformedApkError helper.
- Trim comments and KDoc added by this PR to what the code does not
  already say.
…er-agent/flutter into agp-add-to-app-module

# Conflicts:
#	packages/flutter_tools/gradle/src/main/kotlin/FlutterPlugin.kt
#	packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt
Keep only what the code doesn't say: why library variants are not
gated by the command line, why the copy task inputs are required, and
what the tests guard. Restore PR 7's shouldCompileFlutterForVariant
paragraph verbatim.

@reidbaker reidbaker left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please update the top of the pr to include a link for how to review only the changes intended in pr 8 while we wait for pr 7 to land. Something like this https://github.com/flutter/flutter/pull/193717/changes/33f39876b92a9e904a092375c141d6c4de2df62c..a6be16034c8a1a4a0456cd35194b5973cfd4dc75 would give the changes for 2 diffs, or if you can used stacked prs.

Comment on lines 466 to 468
* [FlutterPluginUtils.shouldConfigureFlutterTask] exists to prevent. Removing it is
* tracked by https://github.com/flutter/flutter/issues/109560, which also documents the
* AGP behavior that made it necessary.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this "hack be removed" it seems like the bug was a result of our seeing build breakages and not being able to modify our interactions with agp such that the expected behavior happens. Seee #109560 (comment)

If the "hack" should be removed should it happen as part of this pr, this pr stack or independently? Is the root cause still something that can happen with the migration in progress?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not in this PR. I couldn't reproduce either root cause on this stack.

Test setup: a scratch app on AGP 9.3.1 / Gradle 9.5.0 with two flavors, built with ./gradlew clean :app:assembleFlavoraRelease. With two tasks on the command line, the gate returns true for all 6 variants.

Results:

  • Only the FlavoraRelease Flutter tasks were realized and run.
  • lintVitalAnalyzeFlavoraRelease ran without any debug compile.

That's because task registration is lazy. Laziness removes both the AGP 4.0 lint dependency and the eager configuration described in the linked comment.

I didn't test a CMake/native flavor build (issuetracker 329132239) or the AGP 8.11.1 floor.

I suggest removing the gate in an independent PR after the stack lands, tracked by #109560. It changes app behavior and should be revertable on its own. Removing the gate also removes the LibraryVariant special case.

Comment on lines +470 to +472
* Library (add-to-app module) variants are never gated: the command line names a host
* task, and `matchingFallbacks` can map it to a module variant of any name. The tasks are
* registered lazily, so only the module variant the host consumes runs.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this mean?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rewritten in b8cd39b.

The command line names a host task (:app:assembleDemoStaging), and the host's matchingFallbacks choose the module variant. So the module can't tell from the task name which of its variants is needed.

All library variants are registered. Because registration is lazy, only the consumed one runs.

project,
"assemble${FlutterPluginUtils.capitalize(variant.name)}"
)
variant is LibraryVariant ||

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this comment is related to https://github.com/flutter/flutter/pull/193717/changes/f668780fd47caca13c03828f49d21572074825d6..a6be16034c8a1a4a0456cd35194b5973cfd4dc75#r4185324460 but why are library variants the only one that need this check and why does a pr about add to app have to be the one that modifies this code?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before this PR, module variants were gated through the host loop (FlutterPlugin.kt:404-407 on 33f3987):
applicationVariants.all → shouldConfigureFlutterTask(project, appAssembleTask) → map to a module variant by build mode.

This PR deletes that loop and routes library variants through onVariants, so it has to decide the gate for them.

shouldConfigureFlutterTask (FlutterPluginUtils.kt:352) matches the exact name or a Debug/Release/Profile suffix. So :app:assembleDemoStaging matches no module task. When I removed variant is LibraryVariant ||, the staging APK was missing AssetManifest.bin.

Application variants keep the gate because their task names do match.

}
// The assets source set is expected to exist for application variants; fail loudly
// rather than silently building an APK without Flutter assets.
// The assets source set is expected to exist for application and library variants;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there other types of variants? if not then can we not specific a tautological list?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. gradle-api 9.1.0 has four Variant subtypes: Application, Library, DynamicFeature and Test.

FGP is only applied to app and library projects. The deferred-component template applies com.android.dynamic-feature without FGP.

I removed the list in b8cd39b.

* Registers the [FlutterTask] (the `flutter assemble` invocation) for [variant],
* configured entirely from the public variant API. Application projects only; the
* add-to-app module path registers its own compile task in [addFlutterDepsForModule].
* configured entirely from the public variant API.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does "configured entirely from the public variant API." add any value to future maintainers? I dont think so.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. In b8cd39b the KDoc is: Registers the [FlutterTask] (the flutter assemble invocation) for [variant].


/**
* Per-ABI versionCodes and the copy into `build/outputs/flutter-apk/`. Library variants
* produce an AAR, so they need neither.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So they need neither what?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworded in b8cd39b: "Configures the per-ABI versionCodes and the copy into build/outputs/flutter-apk/. Both act on APK outputs, which library variants don't have."

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file has a lot of mocking per test. Is there a way to make individual tests easier to understand and reduce code duplication?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

d8f869e adds stubTaskRegistration, captureTaskConfiguration and setCommandLineTasks, and PR 8's three tests use them.

The tests from PR 6/7 repeat the same stubs at about 7 sites. I'll convert those in a follow-up after the stack lands, so this PR doesn't rewrite tests it didn't add.

// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

@Timeout(Duration(minutes: 15))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where in our stack is this value used? Why not rely on the default?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It only repeated the default. packages/flutter_tools/dart_test.yaml sets timeout: 15m and says "we never set the timeouts in the tests themselves".

The file is now deleted (see the next reply).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am surprised this is not already an existing integration test. Are you sure there was not something close that could be modified to check the new integration behavior for warning about hostAppProjectName.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. devicelab module_host_with_custom_build_test already builds this fixture one task at a time, including assembleDemoStaging.

2efbd34 deletes the integration test and instead:

  • adds a qa build type to the fixture;
  • adds a final devicelab section that runs app:assembleDemoQa with -Pandroid.newDsl=true -Pandroid.compatibility.enableLegacyApi=false -Pflutter.hostAppProjectName=app.

The section checks for the warning, the Flutter assets and libapp.so (arm64, armeabi-v7a), and that no debug assets are present.

It runs presubmit on Linux, Mac and Windows (.ci.yaml:1104, 4486, 6630). Local run: success.

This also corrects my earlier reply (r4184409663): android_add_to_app_module_test no longer exists. The new devicelab qa section sets flutter.hostAppProjectName and checks the warning.

Explain why library variants skip the command-line gate, drop the
application/library list from the assets comment, drop the 'public
variant API' clause, and say what library variants lack in
configureApplicationOutputs.
…m_build_test

The devicelab test already builds the same host fixture one task at a
time. Add a debuggable qa build type that falls back to the module's
release variant, and a final section that builds it with
android.newDsl=true and flutter.hostAppProjectName set. It checks the
warning and that the APK has release Flutter artifacts.

Delete android_add_to_app_module_test.dart, which duplicated that
setup.
Add stubTaskRegistration, captureTaskConfiguration and
setCommandLineTasks, and use them in the add-to-app onVariants tests.
<String>[
'app:assembleDemoQa',
'-Pandroid.newDsl=true',
'-Pandroid.compatibility.enableLegacyApi=false',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://gitgud.io/aosp/platform/tools/base/-/commit/2fa01f332fdf8c258e7822173aaeaaee93477575

I think this value has been replaced with a more specific one. Maybe it does not matter because you are setting the values towards using the new api.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. That commit ("Deprecate android.compatibility.enableLegacyApi") moves ENABLE_LEGACY_API from FeatureStage.Supported to FeatureStage.Deprecated(VERSION_10_0) with /** This flag is subsumed by android.enableLegacyVariantApi. */.

In AGP 9.1.0, 9.1.1 and 9.3.1 (javap on BooleanOption), android.enableLegacyVariantApi is itself ApiStage.Removed(VERSION_9_0, "The android.enableLegacyVariantApi property has no effect, use android.newDsl instead"). Setting it on 9.3.1 only prints that warning. So the flag that matters is android.newDsl, which the test already sets.

In 9.3.1, ENABLE_LEGACY_API is read only by the old variant API classes (BaseVariantImpl, ApkVariantOutputImpl, MergedFlavor, OldVariantApiLegacySupportImpl, and VariantServicesImpl.newPropertyBackingDeprecatedApi/newProviderBackingDeprecatedApi). newDsl=true doesn't create those, so the flag was redundant.

I dropped it in 1366874. devicelab still passes, and the qa section still builds with newDsl=true overriding the fixture's newDsl=false.

```kotlin
create("staging") {
initWith(getByName("debug"))
isDebuggable = true // staging gets debug Flutter artifacts

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is isDebuggable deleted here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was deleted on purpose, in 8c9a54d.

The text it illustrated said Flutter maps host build types to build modes using the debuggable flag. That mapping (buildModeFor(com.android.builder.model.BuildType), which read isDebuggable) is deleted in this PR. The mode comes from the module variant that matchingFallbacks selects, so the flag doesn't affect the Flutter artifacts.

It was also redundant in the snippet: initWith(getByName("debug")) already makes staging debuggable (BuildType.initWith: "Copies all properties from the given build type.").

The PR body shows the effect: a debuggable qa that falls back to release gets release Flutter artifacts (libapp.so, compileFlutterBuildRelease), and the devicelab qa section checks that.

variant: Variant,
dslVersionCodes: DslVersionCodes
) {
check(variant is ApplicationVariant) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"which library variants don't have." and " check(variant is ApplicationVariant) {" then what looks like an error code seems backwards unless I misunderstand how check works.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You read check correctly: it throws when the condition is false. The message described what it expected, not what went wrong, and sat next to a KDoc about library variants.

In 798ac29 the call site branches on variant is ApplicationVariant and the function takes an ApplicationVariant. The compiler enforces the type, so there is no runtime check or message left.

Behavior is unchanged. In an application project, onVariants comes from ApplicationAndroidComponentsExtension : AndroidComponentsExtension<ApplicationExtension, ApplicationVariantBuilder, ApplicationVariant>, so every variant there is an ApplicationVariant.

Branch on the variant type at the call site so the function receives an
ApplicationVariant and needs no check().
…a section

AGP 9.1 and 9.3 deprecate the option. Its consumers back the variant API
that android.newDsl=true already removes.
reidbaker
reidbaker previously approved these changes Oct 5, 2026
@reidbaker reidbaker added the CICD Run CI/CD label Oct 5, 2026
@reidbaker-agent
reidbaker-agent marked this pull request as ready for review October 5, 2026 20:45
@reidbaker-agent
reidbaker-agent requested a review from a team as a code owner October 5, 2026 20:45
@reidbaker-agent
reidbaker-agent requested review from gmackall and removed request for a team October 5, 2026 20:45

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request migrates the Flutter Gradle Plugin's APK copying and per-ABI versionCode configuration to the modern Android Gradle Plugin (AGP) Variant API, removing deprecated legacy API dependencies. It introduces the CopyFlutterApksTask to copy and rename APKs using SingleArtifact.APK and BuiltArtifactsLoader, and the DslVersionCodes class to capture version codes during finalizeDsl, enabling compatibility with AGP's legacy API mode disabled. Additionally, add-to-app module configuration is simplified by removing host-project lookups and registering tasks lazily in onVariants. Unit tests, integration tests, and documentation are updated to align with these changes. No review comments were provided, so there is no feedback to address.

@reidbaker
reidbaker requested a review from mboetger October 5, 2026 21:17
The flutter tool runs Gradle with -q, which hides logger.warn. Log the
warning with logger.error, as the plugin's other user-facing warnings
do, and state the consequence (Google Play needs a distinct versionCode
per APK) and the fix.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewers, Ignore this file I dont think it is worth your time.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewers, Ignore this file I dont think it is worth your time. It is more valuable than the migrateing md file but still probalby not worth it.

@reidbaker reidbaker added the autosubmit Merge PR when tree becomes green via auto submit App label Oct 12, 2026
@auto-submit
auto-submit Bot added this pull request to the merge queue Oct 12, 2026
Merged via the queue into flutter:master with commit 4249ccb Oct 12, 2026
31 checks passed
@flutter-dashboard flutter-dashboard Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Oct 12, 2026
auto-submit Bot pushed a commit to flutter/packages that referenced this pull request Oct 14, 2026
…#13220)

Manual roll Flutter from d03768eef055 to 4249ccbc18a3 (158 revisions)

Manual roll requested by [email protected]

flutter/flutter@d03768e...4249ccb

2026-10-12 [email protected] [AGP 9.1.0 Migration #8] Unify add-to-app module wiring on the variant API (flutter/flutter#193717)
2026-10-12 [email protected] [master] Update Flutter DEPS to Dart 5933cb486b4aef2f8cf6b9f89a5651c0d5b0955e (flutter/flutter#193982)
2026-10-12 [email protected] [Android] Fix use-after-free of SurfaceControl.Transaction in HCPP (flutter/flutter#192969)
2026-10-12 [email protected] [windows] Add OpenGL compositor tests against ANGLE at D3D11 feature level 10_0 (flutter/flutter#194114)
2026-10-12 [email protected] Fix HDR image packing for ios (flutter/flutter#192526)
2026-10-12 [email protected] Bump customer tests to pick up material_ui and cupertino_ui (flutter/flutter#194236)
2026-10-12 [email protected] Remove no-shuffle from text_field_test.dart (flutter/flutter#193550)
2026-10-12 [email protected] [web] Clip semantics DOM text labels to stop stealing taps (flutter/flutter#194011)
2026-10-12 [email protected] Roll Skia from 9b6ceba4728d to 17f6986c226c (6 revisions) (flutter/flutter#194232)
2026-10-12 [email protected] [web] Skip non-focusable containers in Focusable.focusAsRouteDefault (flutter/flutter#194138)
2026-10-12 [email protected] [web_paragraph] Honor kTextHeightNone in TextStyle and ParagraphStyle (flutter/flutter#193733)
2026-10-12 [email protected] Roll Skia from c88b49d1d6c7 to 9b6ceba4728d (4 revisions) (flutter/flutter#194226)
2026-10-12 [email protected] Migrate remaining test to macOS 26 (flutter/flutter#193825)
2026-10-12 [email protected] Roll Dart SDK from 047ca322aa9c to 221363557163 (1 revision) (flutter/flutter#194219)
2026-10-12 [email protected] [flutter_tools] Include dev_dependencies hooks for release integration tests (flutter/flutter#193436)
2026-10-12 [email protected] Roll Skia from d8afad1b1829 to c88b49d1d6c7 (1 revision) (flutter/flutter#194198)
2026-10-12 [email protected] Roll Skia from b23ddbff1265 to d8afad1b1829 (4 revisions) (flutter/flutter#194195)
2026-10-11 [email protected] Roll Skia from a168863e44ec to b23ddbff1265 (1 revision) (flutter/flutter#194185)
2026-10-11 [email protected] Roll Skia from 0c1d93abfa04 to a168863e44ec (1 revision) (flutter/flutter#194181)
2026-10-11 [email protected] iOS: Re-enable rendering on willEnterForeground (flutter/flutter#191350)
2026-10-11 [email protected] Roll Skia from 93c9d3ffda25 to 0c1d93abfa04 (1 revision) (flutter/flutter#194179)
2026-10-11 [email protected] Remove more instances of deprecated `withOpacity` (flutter/flutter#193559)
2026-10-11 [email protected] Fix another `parameter_assignments` warning (flutter/flutter#193669)
2026-10-11 [email protected] Roll Skia from 3136abef8f58 to 93c9d3ffda25 (1 revision) (flutter/flutter#194169)
2026-10-11 [email protected] [Impeller] Guard the sampler library caches with a mutex (flutter/flutter#193738)
2026-10-11 [email protected] Roll Dart SDK from e9c89888bcc1 to 047ca322aa9c (1 revision) (flutter/flutter#194157)
2026-10-10 [email protected] Roll Dart SDK from 787baa8aae78 to e9c89888bcc1 (2 revisions) (flutter/flutter#194141)
2026-10-10 [email protected] Fix OverlayPortal layout in shrink-wrapped overlays (flutter/flutter#193079)
2026-10-10 [email protected] Roll Skia from 353076e27b1c to 3136abef8f58 (1 revision) (flutter/flutter#194130)
2026-10-10 [email protected] Roll Dart SDK from 88a07589b1ff to 787baa8aae78 (1 revision) (flutter/flutter#194124)
2026-10-10 [email protected] Route windowing view lookups through WidgetsBinding (flutter/flutter#192666)
2026-10-10 [email protected] Roll Dart SDK from 2a06cab34715 to 88a07589b1ff (1 revision) (flutter/flutter#194117)
2026-10-10 [email protected] Roll Skia from f9043b7aa798 to 353076e27b1c (2 revisions) (flutter/flutter#194115)
2026-10-10 [email protected] Refactored PipelineLibraryGLES (flutter/flutter#193985)
2026-10-09 [email protected] add shorthands for scroll physics subclasses in framework (flutter/flutter#193082)
2026-10-09 [email protected] Roll Dart SDK from ba889e3cee8a to 2a06cab34715 (1 revision) (flutter/flutter#194105)
2026-10-09 [email protected] Roll Skia from 5ea03d8a53a6 to f9043b7aa798 (2 revisions) (flutter/flutter#194099)
2026-10-09 [email protected] Add docs for using bb and led with LUCI (flutter/flutter#193859)
2026-10-09 [email protected] Roll Skia from e329bb617820 to 5ea03d8a53a6 (5 revisions) (flutter/flutter#194087)
2026-10-09 [email protected] Roll Packages from 54706bd to d8d9e97 (5 revisions) (flutter/flutter#194080)
2026-10-09 [email protected] Move the Vulkan wrapped-texture helper out of the GPU surface (flutter/flutter#193001)
2026-10-09 [email protected] Roll Skia from 3e54bcf4c963 to e329bb617820 (1 revision) (flutter/flutter#194078)
2026-10-09 [email protected] Roll Dart SDK from 23f0308f9af8 to ba889e3cee8a (2 revisions) (flutter/flutter#194077)
2026-10-09 [email protected] Bump dartdoc to 9.0.11 (flutter/flutter#194076)
...
gmackall pushed a commit to gmackall/flutter that referenced this pull request Oct 20, 2026
…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
suryaKommana2662 pushed a commit to suryaKommana2662/flutter that referenced this pull request Oct 29, 2026
… 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
suryaKommana2662 pushed a commit to suryaKommana2662/flutter that referenced this pull request Oct 29, 2026
…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CICD Run CI/CD d: docs/ flutter/flutter/docs, for contributors platform-android Android applications specifically team-android Owned by Android platform team tool Affects the "flutter" command-line tool. See also t: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants