Repository navigation
[AGP 9.1.0 Migration #9] Move aar_init_script to public AGP APIs - #193962
reidbaker-agent wants to merge 19 commits into
Conversation
The application path of the Flutter Gradle Plugin no longer uses the deprecated BaseVariant API (applicationVariants / ApkVariantOutput): - CopyFlutterApksTask copies the variant's SingleArtifact.APK outputs, read through BuiltArtifactsLoader, into build/outputs/flutter-apk under the unchanged names app[-abi][-flavor]-<mode>.apk. assemble<Variant> depends on it. The task has no Project or variant fields and uses an injected FileSystemOperations. - A single naming function in the task produces both the declared @OutputFiles and the copied file names. Its inputs are the ABIs of the variant's outputs (what AGP builds), the flavor, and the build mode. The task fails if AGP built an APK for an ABI that was not declared. - Per-ABI versionCode for --split-per-abi is a read-then-set on VariantOutput.versionCode in onVariants. This is a documented exception to the no-configuration-time-get rule, because the lazy form is circular. The read relies on AGP's compatibility mode (android.compatibility.enableLegacyApi, on by default through 9.x). - The applicationVariants.configureEach block and its @Suppress("DEPRECATION") markers are deleted. The add-to-app module path keeps using libraryVariants until the next phase. Behavior change: Flutter's onVariants callback runs before an app's own androidComponents.onVariants block, while ApkVariantOutput's versionCodeOverride was applied after it. Apps that transform output.versionCode there transform Flutter's offset value (for example arm64-v8a, build 42, x10000: 422000 -> 20420000). The migration doc and the breaking-change page draft describe this and give a recipe that keeps the 422000-style numbers. No divergence warning is emitted, because it would fire on the onVariants pattern that AGP recommends. Part of flutter#166550.
…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.
…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.
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.
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.
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.
flutter build aar failed on a module with android.newDsl=true: the
library check read android.libraryVariants, which AGP does not create
with the new DSL ("Can't generate AAR on a non Android library
project."), and the plugin wiring read it again.
- Detect a library project with pluginManager.hasPlugin(
"com.android.library").
- Declare singleVariant publishing for each variant, and keep a
declaration that the project's build file already made. AGP's
duplicate-declaration error is matched by its message, because its
class is not public AGP API. This replaces the read of AGP's internal
publishing.singleVariants, which skipped Flutter's declarations for
every variant once the build file declared any, so a module that
declared only release failed every flutter build aar.
- Remove the module-to-plugin assembleAar<Variant> dependsOn. The tool
requests the task by name, so Gradle runs it in every project that
has it and each plugin publishes its own AAR.
- When an Android plugin has no variant with the requested name
(different product flavors), fail with an error that names the plugin,
instead of "Task with name 'assembleAar<Variant>' not found".
- devicelab build_aar_module_test builds with android.newDsl=true and a
module singleVariant("release") declaration.
- Update the migration doc and the website page draft.
The tool requests assembleAar<Variant> without a project path, so Gradle skips projects that lack it. Check each requested task against the module and every Android plugin, so a flavor mismatch in either direction fails instead of reporting success with an incomplete repo. Cover both errors in build_aar_module_test and scope the doc claims about the deleted code.
| } | ||
|
|
||
| static String aarTaskName(String variantName) { | ||
| return "assembleAar${variantName.capitalize()}" |
There was a problem hiding this comment.
Is there a more typesafe way to get build this name?
There was a problem hiding this comment.
The name can't come from an AGP type. AGP classes live in the build's classloader, and the init script can't see them.
- With
import com.android.build.api.dsl.LibraryExtension, the init script fails to compile:unable to resolve class com.android.build.api.dsl.LibraryExtension. - With
com.android.tools.build:gradle-api:9.3.1on theinitscriptclasspath, the init script gets its own copy of the class. Theandroidextension comes fromcoreAndPlugins:settings[:], and the init script'sLibraryExtensioncomes fromcoreAndPlugins:init-file:.... Soinstanceofisfalse, andextensions.getByType(LibraryExtension)throwsExtension of type 'LibraryExtension' does not exist. Currently registered extension types: [..., LibraryExtension, ...].
The AGP task names (assembleRelease, bundleReleaseAar) are also plain strings with no typed accessor. The most typesafe option is Gradle's typed SoftwareComponent collection plus one name builder:
aarComponents()(aar_init_script.gradle:88) is the one source of variants.configureProjectregisters a task for each of them (:27), and the check reads them (:143).aarTaskName()(:94) is the only place that builds the name. The check compares requested task names withaarTaskName(component.name)(:147), so nothing parses a task name back into a variant.
One trade-off: because nothing parses the requested name, the check can't report a variant that no project has (for example a misspelled --flavor). Gradle reports that case with its own Task 'assembleAarFooDebug' not found in root project 'android_generated' and its subprojects. Some candidates are: ... error, as it does with the base script. The check's doc says so (:134-136). 3edc95d parsed the prefix and gave a Flutter module error there.
The tool builds the same name in Dart (getAarTaskFor, packages/flutter_tools/lib/src/android/gradle.dart:141-143, _taskFor('assembleAar', …)). The comment on aarTaskName points there. A constant shared between Dart and Groovy would need code generation or a new tool-to-script property, which is outside this PR.
| if (!project.hasProperty("android")) { | ||
| throw new GradleException("Android property not found.") | ||
| } | ||
| if (!project.android.hasProperty("libraryVariants")) { |
There was a problem hiding this comment.
What was property libraryVariants not a good way to check this condition?
There was a problem hiding this comment.
android.libraryVariants doesn't exist when android.newDsl=true, which is the default in AGP 9.
- AGP 9.1.0 marks the class that defines it,
com.android.build.gradle.LibraryExtension,@Deprecated("Replaced by com.android.build.api.dsl.LibraryExtension. 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.")(javap -vongradle-9.1.0.jar). The class isn't in the publicgradle-api-9.1.0.jar. getLibraryVariants()callsrecordOldVariantApiUsage().- Reproduction with the base script, the template AGP 9.3.1 and
android.newDsl=true: every scratch module fails at line 13 withCan't generate AAR on a non Android library project.With only line 13 changed tohasPlugin, the next failure is line 178:Could not get unknown property 'libraryVariants' for object of type com.android.build.gradle.internal.dsl.LibraryExtensionImpl$AgpDecorated.
pluginManager.hasPlugin("com.android.library") is Gradle API. It works on AGP 9.3.1 with newDsl off and on, on master's minimum AGP 9.1.1 with newDsl on, and on AGP 8.11.1 with FGP's version check skipped (manual matrix in the PR body).
|
|
||
| // AGP reports this error when `singleVariant` is declared twice for one variant | ||
| // (`AbstractPublishing.checkSingleVariantUniqueness`; same text in AGP 8.11.1, 9.1.0 and 9.3.1). | ||
| // Its class, com.android.builder.errors.EvalIssueException, is not public AGP API and is not on |
There was a problem hiding this comment.
Done in 94a2990. The comment links AGP's checkSingleVariantUniqueness at the studio-2025.3.2 tag, which is AGP 9.1.0 (common/release_version.bzl at that tag has BUILD_VERSION = "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
The message is at lines 38-45. I opened the link. The file at studio-2025.1.1 (AGP 8.11.0) has the same text, with only formatting differences.
| // (`AbstractPublishing.checkSingleVariantUniqueness`; same text in AGP 8.11.1, 9.1.0 and 9.3.1). | ||
| // Its class, com.android.builder.errors.EvalIssueException, is not public AGP API and is not on | ||
| // this script's classpath, so the error is matched by its message. | ||
| static boolean isDuplicateSingleVariantError(Exception e, String variantName) { |
There was a problem hiding this comment.
Why if android throws this do we need to be able to identify it.
There was a problem hiding this comment.
Without identifying the error there are two choices: Flutter can't add publishing to a variant the build file already declared, or every other failure gets swallowed too.
- No public query. The public DSL can declare variants but not list them. In
gradle-api-9.1.0.jar,Publishinghas onlysingleVariant(String)andsingleVariant(String, Function1), andLibraryPublishingadds onlymultipleVariants(...)(javap). ThesingleVariantslist the base script read isgetSingleVariants()oncom.android.build.gradle.internal.dsl.AbstractPublishing, which is internal. - Declaring first doesn't work. I tried a
beforeProject { pluginManager.withPlugin("com.android.library") { android.publishing.singleVariant("release") {...} } }init script on a module whose build file declaressingleVariant("release") { withSourcesJar() }. The build file then fails at that line withUsing singleVariant publishing DSL multiple times to publish variant "release" to component "release" is not allowed. - So Flutter declares each variant after the build file and ignores only that exact AGP message. Any other exception is rethrown (
aar_init_script.gradle:198-200). If AGP changes the text, the build fails loudly. devicelabbuild_aar_module_testcovers this path (itsnewDsl=truesection declaresreleasein the module).
The comment at :187-190 gives this reason.
| // The tool requests `assembleAar<Variant>` by name, without a project path. Gradle runs that task | ||
| // in every project that has it and skips projects that don't, so a project without the variant | ||
| // would leave the repo without its AAR. The module's POM depends on each plugin's AAR for the same | ||
| // variant name. |
There was a problem hiding this comment.
Doc should describe how callers can know when the check is a success or failure.
There was a problem hiding this comment.
Done in 94a2990 (aar_init_script.gradle:132-136): "Returns normally if they do. Otherwise throws a GradleException that names the variant and the first project without it. A requested task that matches no project's variant (for example a misspelled --flavor) is left to Gradle, which fails with Task '<name>' not found in root project."
| // variant name. | ||
| void checkProjectsHaveRequestedVariants(Project moduleProject, Collection<Project> pluginProjects) { | ||
| List<String> requestedAarTasks = moduleProject.gradle.startParameter.taskNames.findAll { | ||
| it.startsWith("assembleAar") |
There was a problem hiding this comment.
If we are going to use string identification here instead of something more typesafe (and I would prefer the more typesafe solution) then use a constant that is referenced here and in aarTaskName so that they always match and we know where the prefix comes from.
There was a problem hiding this comment.
Done in 94a2990 with no string parsing (see the reply on aarTaskName). The check builds the requested variant set by comparing startParameter.taskNames with aarTaskName(component.name) for each publishing component of the module and the Android plugins. There is no prefix literal, no substring/uncapitalize, and the variant name in the error comes from the component, so flavor names that start with an uppercase letter are reported correctly.
Behavior in the matrix (AGP 9.3.1 newDsl off and on, AGP 9.1.1, AGP 8.11.1 with the version check skipped):
- flavored module without
--flavor→module project ':flutter' has no varianterror; --flavor demowhen a plugin lacks it →plugin project ':plugin_a' has no varianterror.- If no project has the requested variant (
--flavor foo), the build fails with Gradle's ownTask 'assembleAarFooDebug' not found in root project 'android_generated' and its subprojects. Some candidates are: …, as on the base. - A path-qualified request (
:flutter:assembleAarRelease) isn't checked and publishes nothing. The base script does the same (exit 0, no artifacts).addAarTaskcreates publications only for the bare name, and the tool sends only the bare name (gradle.dart:951).
| // in every project that has it, and each plugin publishes its own AAR. | ||
| modulePlugins.each { pluginProject -> | ||
| configurePlugin(pluginProject, moduleProject.property("output-dir")) | ||
| moduleProject.android.libraryVariants.all { variant -> |
There was a problem hiding this comment.
Why is this not needed anymore?
There was a problem hiding this comment.
Gradle already runs each plugin's task, and the module's publishing doesn't need the plugin's AAR.
- The tool requests the task without a project path (the comment at
aar_init_script.gradle:243-244says this).buildGradleAaraddsgetAarTaskFor(buildInfo)(assembleAar<Variant>) to the command (gradle.dart:143,:951). The Gradle docs say "You can also run a task for all subprojects using a task selector that consists of only the task name." (https://docs.gradle.org/current/userguide/command_line_interface.html#executing_tasks_in_multi_project_builds). With the loop deleted, the task graph has:plugin_a:assembleAarRelease,:plugin_a:bundleReleaseAar,:plugin_a:publishReleasePublicationToMavenRepositoryand:plugin_a:publish. - Ordering doesn't matter. In the same graph,
:flutter:generatePomFileForReleasePublicationdepends on no:plugin_atask.:flutter:publishReleasePublicationToMavenRepositorydepends only on the plugin's compile-classpath tasks (compileReleaseKotlin,bundleLibCompileToJarRelease, …), not onbundleReleaseAaror a plugin publish task. The module POM refers to the plugin by coordinates (plugin_a_release). Each project publishes its own files to the shared local repo. - Result. In the manual matrix (case (a), module + one Android plugin) and in devicelab
build_aar_module_test,build_android_host_app_with_module_aarandandroid_java17_build_android_host_app_with_module_aar, the repo has the module and plugin AAR, POM, sources and javadoc for every mode. The devicelab tests also check that the module POM depends onplugin_with_android_release. - Missing variants. The loop's
tasks.named(...).get()threwTask with name 'assembleAar<Variant>' not found in project ':<plugin>'when a plugin lacked any module variant.checkProjectsHaveRequestedVariants(:140) covers that case for the requested variant, with a message that names the project and the fix.
…AR tasks lazily - checkProjectsHaveRequestedVariants compares the requested task names with aarTaskName() of each project's publishing components, so no task name is parsed back into a variant name. Its doc says when it returns and when it throws. - aarComponents() is the one component filter for task registration and the check. - addAarTask registers its task lazily. The publication is created outside the task's configuration action, where maven-publish may register its tasks. - Link AGP's checkSingleVariantUniqueness source (AGP 9.1.0 tag) and say why the duplicate error is identified. - Keep the task-selector explanation once, in projectsEvaluated. - Docs describe only the behavior.
- checkProjectsHaveRequestedVariants: a requested task that matches no project's variant is left to Gradle's task selector error. - aarComponents: describe what the filter returns. - addAarTask: state the effect of the requested-task guard, and drop the "all" note from the publication comment (the filter is in aarComponents).
…add-to-app-module # Conflicts: # packages/flutter_tools/gradle/src/main/kotlin/FlutterPlugin.kt # packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt # packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginTest.kt
To review only PR 9 while #193717 is open: changes
23ddd6b74e6..ea08280d886.23ddd6b74e6is PR 8's head, merged in byea08280d886, so this range has no PR 7 or PR 8 changes. The link is updated on every push.Stacked on #193717. PR 7 (#193693) merged to master as
ecd1ab06865, and PR 8's head23ddd6b74e6merges that master. Review only the commits after23ddd6b74e6.PR 9 commits:
86523c549fcMove aar_init_script to public AGP APIs3edc95d1fa0Check requested AAR variants in the module and each pluginda9646aad53Address review: derive requested variants from components, register AAR tasks lazily94a29907065Clarify aar_init_script comments from the adversarial reviewea08280d886Merge commit '23ddd6b74e6' into agp-aar-init-script (PR 8's head with master atecd1ab06865; no conflicts, no change to PR 9's files)Description
This is PR 9 of 11 in the AGP 9.1.0 / public
gradle-apimigration stack (#180137, #166550).flutter build aarpassespackages/flutter_tools/gradle/aar_init_script.gradleto Gradle with-I. The script readandroid.libraryVariantsand AGP's internalpublishing.singleVariants. With AGP 9'sandroid.newDsl=true,flutter build aaron a module fails on23ddd6b74e6withCan'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 onlypluginManager.hasPlugin,Publishing.singleVariant, softwarecomponentsandtasks.register, and the build succeeds withnewDslon 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
23ddd6b74e6succeed with the same artifacts. Two failing cases change: a module or plugin build file that declaressingleVariantfor only some variants builds, and a variant mismatch between the module and a plugin fails with a Flutter error instead ofTask with name … not found.Base:
reidbaker-agent:agp-add-to-app-moduleat23ddd6b74e6(PR 8, #193717, open).aar_init_script.gradleis the same at23ddd6b74e6and at PR 8's previous headeb20bbd43de. When #193717 merges, this branch will mergemasterin.Agent authored details
Changes
All in
aar_init_script.gradleunless noted. Line numbers are atea08280d886.:13):project.pluginManager.hasPlugin("com.android.library")replacesandroid.hasProperty("libraryVariants"). The error text is unchanged.afterProject,:181-215): Flutter callsandroid.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, andisDuplicateSingleVariantError(:121-130) matches AGP's exact message, so the build file's declaration is kept. Its comment links the AGP source of the message (AbstractPublishing.ktatstudio-2025.3.2, AGP 9.1.0). Any other exception is rethrown (:198-200). The guard that readpublishing.singleVariantsand skipped every Flutter declaration once the build file declared any variant is deleted.dependsOn(projectsEvaluated): the loop overmoduleProject.android.libraryVariantsthat made each moduleassembleAar<Variant>depend on the plugin's task (with eagertasks.named(...).get()) is deleted. The tool requestsassembleAar<Variant>without a project path (gradle.dartbuildGradleAar,command.add(aarTask)), and Gradle runs a task selector that is only a task name in every project that has the task (Gradle docs: "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:243-244says this.aarComponents,:85-90;aarTaskName,:92-96):aarComponentsreturns every software component exceptall.configureProjectregisters one task for each (:27), and the check reads the same components.aarTaskNameis the only place that buildsassembleAar<Variant>; its comment points to the tool'sgetAarTaskFor(gradle.dart). Nothing parses a task name back into a variant.addAarTask,:98-119):project.tasks.registerreplacesproject.tasks.create(name:). The configuration action only addsfinalizedBy "publish". The check for a requested task name andpublications.createrun outside 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 whenpublications.createwas inside the action).checkProjectsHaveRequestedVariants,:132-171, called at:248): Gradle skips projects that lack the requested task, so the script checks the module and each Android plugin. The requested variants are the component namesnof those projects for whichstartParameter.taskNamescontainsaarTaskName(n). A project without a requested variant fails the build with aGradleExceptionthat names the variant and the project: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.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 aarrequires the module and each of its Android plugins to declare the same product flavors.--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 on23ddd6b74e6. The check's doc says so. (3edc95d1fa0parsed the task-name prefix and reported a Flutter module error in this case;da9646aad53drops the parsing per review.)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(publishing tracked in #193713): theflutter build aarsection 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.build_aar_module_testgets two final sections:android.newDsl=trueandandroid.builtInKotlin=truein.android/gradle.properties, andandroid.publishing.singleVariant("release") { withSourcesJar() }in.android/Flutter/build.gradle, it runsflutter 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.demoproduct flavor and checks both errors:flutter build aar --no-profile --no-releasefails with the module error, and the same with--flavor demofails 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 in94a29907065.Bug history (moved out of the docs)
The docs describe behavior only, so the history that
3edc95d1fa0put in them is here:23ddd6b74e6, theafterProjectguard skipped all of Flutter'ssingleVariantdeclarations for a project whose build file declared any variant. For a module with at least one Android plugin that declared onlyrelease, everyflutter build aarfailed withTask with name 'assembleAarDebug' not found in project ':flutter'. With this PR that build succeeds (table 1 below).23ddd6b74e6, thedependsOnloop failed withTask 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, and stack audit items M4 and L8:
.get()and cross-project task lookup by name.get()ortasks.namedin the script. The check reads software components, andaddAarTaskusestasks.register.catch (Exception); overstatedmultipleVariantswarning:126-130); everything else is rethrown (:198-200). No warning is logged, so there is nomultipleVariantsclaim.flutter.internal.afterProjectfires once per project (instrumented: count 1 for:,:app,:flutter,:plugin_aand 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.checkSingleVariantUniquenessthroughSyncIssueReporterImpl, which throwscom.android.builder.errors.EvalIssueExceptionin STANDARD mode. That class is incom.android.tools.build:builder, not ingradle-api, and is not on the init script's classpath.javap -vonAbstractPublishingshows the same text in AGP 8.11.1, 9.1.0 and 9.3.1. AGP checks before it adds the component (8.11.1AbstractPublishing.kt:27-35), so the build file's declaration stays. If AGP changes the text, the exception is rethrown and the build fails loudly; devicelabbuild_aar_module_testexercises the path on the template AGP.Why not declare first or query: the public
PublishingDSL ingradle-api-9.1.0.jarhas onlysingleVariant(String)andsingleVariant(String, Function1), andLibraryPublishingadds onlymultipleVariants(...)(javap), so there is no public way to list declared variants. Declaring Flutter's variants inbeforeProject+pluginManager.withPlugin("com.android.library")makes the build file fail at its ownsingleVariant("release")line with the duplicate error (probe).Why not AGP types in the init script:
import com.android.build.api.dsl.LibraryExtensionfails withunable to resolve class. Withgradle-api:9.3.1on theinitscriptclasspath, the init script gets its own copy of the class from a different classloader, soinstanceofisfalseandextensions.getByType(LibraryExtension)throwsExtension of type 'LibraryExtension' does not exist. Currently registered extension types: [..., LibraryExtension, ...](probes).multipleVariants:PublishingUtil.ensureComponentNameUniquenesserrors only when amultipleVariantscomponent name equals asingleVariantname. Flutter declares the samesingleVariants on23ddd6b74e6when the build file declares none, so that behavior is unchanged. It is not tested here.Why removing the deleted code is safe
android.libraryVariantsdoes not exist withnewDsl=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 ingradle-api-9.1.0.jar.singleVariantsis an internal property (getSingleVariants()oncom.android.build.gradle.internal.dsl.AbstractPublishing,javap). It resolves from Groovy withnewDsl=true, so it was not a failure, only internal API.dependsOn: with it removed, the task graph forassembleAarReleaseon case (a) has:plugin_a:assembleAarRelease,:plugin_a:bundleReleaseAar,:plugin_a:publishReleasePublicationToMavenRepositoryand:plugin_a:publish.:flutter:generatePomFileForReleasePublicationdepends on no:plugin_atask, and:flutter:publishReleasePublicationToMavenRepositorydepends 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). devicelabbuild_aar_module_test,build_android_host_app_with_module_aarandandroid_java17_build_android_host_app_with_module_aarpass (they check the plugin AAR/POM and that the module POM depends onplugin_with_android_release).newDslon and off) the components are[debug, profile, release]for:flutter,:appand each plugin, and[debug, release]for the root. There is noallcomponent; AGP 8.11.1LibraryTaskManager.createComponentcreates components only for publishing declarations. Theallskip is kept, inaarComponents.tasks.register: the publication is created at configuration time only for a requested task name, as withtasks.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
afterProjectcomment item 2, "The project has already defined which variants to publish…", and thesingleVariants.size()guard it describedprojectsEvaluatedcomment "Configure theassembleAar<variantName>task for each plugin's projects and make the module's equivalent task depend on the plugin's task.", with thelibraryVariants.allloop, bothtasks.named(...).get()calls, bothassert(... != null)and thedependsOntasks.named(...).get()throws before the asserts could fail, so they never checked anything.variantNamelocal inaddAarTaskaarTaskName, which the check also uses.addAarTaskcomment "This check is required to be able to configure the archives beforepublishruns."publishfinalizer publishes only those." The check moved out of the task's configuration action.allskip lives inaarComponents, whose comment says it. Noallcomponent exists on AGP 8.11.1 or 9.3.1 (above).Task with name … not foundfailures, and what the deleted code did and did not checkBehavioral and compatibility notes
Matrix projects (scratch, under
/tmp): (a) module + Android pluginplugin_a; (b) module with flavorsdemo/full+plugin_a; (c) module +plugin_a+plugin_flavored(flavorfree); (d) module declaringsingleVariant("release") { withSourcesJar() }+plugin_a.A build file that declares
singleVariantfor some variants builds. Case (d),flutter build aar:23ddd6b74e6(AGP 9.3.1newDsl=false)Task with name 'assembleAarDebug' not found in project ':flutter'.Variant mismatches fail with a Flutter error that names the project. Only requested variants are checked. The
23ddd6b74e6column is AGP 9.3.1 withnewDsl=false.23ddd6b74e6--flavor demoTask with name 'assembleAarDemoDebug' not found in project ':plugin_a'.demoDebugand:plugin_aTask with name 'assembleAarDebug' not found in project ':plugin_flavored'.debugand:plugin_flavored--flavor,--no-profile --no-releaseTask with name 'assembleAarDemoDebug' not found in project ':plugin_a'.debug, listing the module's variants--flavor free,--no-profile --no-releaseTask with name 'assembleAarDebug' not found in project ':plugin_flavored'.freeDebug--flavor fooTask 'assembleAarFooDebug' not found in root project …Without the module check (
86523c549fc), the module-error cases exit 0 with "✓ Built" and a repo that has only the plugin AAR.Path-qualified requests.
./gradlew -I aar_init_script.gradle … :flutter:assembleAarReleaseexits 0 and publishes nothing, on23ddd6b74e6and on this PR:addAarTaskcreates a publication only whenstartParameter.taskNamescontains the bare name. The tool sends only the bare name (gradle.dartcommand.add(aarTask)). Not changed here.No warnings.
flutter build aarhides Gradle output on success unless-v(ProcessUtils.runbuffers it;gradle.dartprints it only on failure, and verified with alogger.errorprobe). Keeping a build file's declaration needs no action, so nothing is logged. Errors are shown.Configuration cache: with
org.gradle.configuration-cache=truein case (a)'s.android/gradle.properties(AGP 9.3.1 / Gradle 9.5.0),flutter build aarreports the same single problem with the23ddd6b74e6script 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.newDsl=truewithbuiltInKotlin=falsefails 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 setbuiltInKotlin=true.Related open work: no overlap with #191681 (
gradle.dart,gradle_errors.dartand tests). No open PR touchesaar_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.afterProjectandprojectsEvaluatedhooks reach across projects, which Gradle Isolated Projects does not allow.-SNAPSHOTrewrite ofproject.versioninconfigureProject.engine.realmandengine.stampfile reads at configuration time inconfigureProject.is-pluginbranch ofprojectsEvaluatedlooks unreachable from the tool.FlutterTask(note 5).newDsl=truewith KGP (builtInKotlin=false), for PR 10.releasefor moduledemoRelease).allcomponent skip may be dead code (noallcomponent on AGP 8.11.1 or 9.3.1).singleVariantis declared on the root project and:app, which never publish.-v.Integration test coverage of the changed paths
Searched
dev/devicelab/bin/tasksandpackages/flutter_tools/test/integration.shardforbuild aarandassembleAar.dependsOn(newDsl=false)build_aar_module_test,build_android_host_app_with_module_aar,android_java17_build_android_host_app_with_module_aar;android_obfuscate_testnewDsl=true, a build file'ssingleVariantdeclaration keptbuild_aar_module_test,newDsl=truesectionbuild_aar_module_test, product flavor sectionTests run locally (macOS, JDK 17)
On the head (
ea08280d886, after merging23ddd6b74e6):gradle test --rerun-tasksinpackages/flutter_tools/gradle, with the engine'sthird_party/gradlebinary that the README names: 33 suites, 277 tests, 0 failures.dart formatanddart analyze --fatal-infosonbuild_aar_module_test.dart: clean (unchanged since3edc95d1fa0).test/commands.shard/hermetic/build_aar_test.dart,test/commands.shard/permeable/build_aar_test.dartandtest/general.shard/android/android_gradle_builder_test.dart: 56 tests, all pass.test/integration.shard/android_obfuscate_test.dart: 2 tests pass.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, allsuccess.Manual matrix on
ea08280d886, four configurations:newDsl=false,builtInKotlin=false.newDsl=true,builtInKotlin=true.newDsl=true,builtInKotlin=true.newDsl=false, withskipDependencyChecks=truein.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.flutter_{debug,profile,release}andplugin_a_{debug,profile,release}--flavor demo--flavor--flavor fooTask 'assembleAarFooDebug' not found in root project--flavor freeCase (a)'s
flutter_release-1.0.pomdepends ondev.pr9.plugin_a:plugin_a_release:1.0(checked on94a29907065).Negative control, the
23ddd6b74e6script, onea08280d886: withnewDsl=true(AGP 9.3.1 and AGP 9.1.1), (a)-(d) all fail withCan't generate AAR on a non Android library project.; withnewDsl=falseon AGP 9.3.1, (a) passes and (b)-(d) fail as in the tables above. With only line 13 changed tohasPlugin,newDsl=truefails at line 178 withCould not get unknown property 'libraryVariants' for object of type com.android.build.gradle.internal.dsl.LibraryExtensionImpl$AgpDecorated.devicelabbuild_aar_module_testwith that script fails at thenewDsl=truesection with the same error.Pre-launch Checklist
///).