Sitelet https://github.com/flutter/flutter/pull/193717
Skip to content

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

Open
reidbaker-agent wants to merge 11 commits into
flutter:masterfrom
reidbaker-agent:agp-add-to-app-module
Open

reidbaker-agent wants to merge 11 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

To review only PR 8 while #193693 is open: changes f668780fd47..1366874099e. f668780fd47 is PR 7's head, merged in by 2c28d8f8245, so this range has no PR 7 changes. The link is updated on every push.

Stacked on #193693. Review only the commits after f668780fd47.

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

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: reidbaker-agent:agp-apk-copy-versioncode at f668780fd47 (PR 7, #193693, open). When #193693 merges, this branch will merge master in.

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 at the stack's AGP 8.11.1 floor (Component.getDebuggable checked with javap on gradle-api 8.11.1).
  • 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 (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
Contributor

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
Contributor

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
Contributor

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
Contributor

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
Contributor

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
Contributor

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
Contributor

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
Contributor

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
Contributor

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
Contributor

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
Contributor

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
Contributor

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
Contributor

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 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
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.

2 participants