Repository navigation
[AGP 9.1.0 Migration #6] Deliver Flutter assets as a generated assets source directory on the app path - #192488
Conversation
1625350 to
2baee0e
Compare
2baee0e to
a441448
Compare
reidbaker
left a comment
There was a problem hiding this comment.
In general I am worried about flutters integration test coverage for this pr. Starting from the docs look at the different assets types that can be specified then find an test in this repo that already exists that uses each of the asset types with a confirmation that the assets are present in the build artifact. After going through the flutter documentation look at android documentation for asset types.
Update the pr description to have a new section that is a table of the asset type, the documentation from flutter (or android_ on how to use that type and the link to the code that verifies that the asset type is present post build in this pr.
For any asset types that do not have a integration test instead include a short description of the likley popularity of that asset type and a recommendation on if we should add a test specifically for that asset type. if so where in the codebase.
| FlutterPluginUtils.addTasksForEnableHcppManifest(projectToAddTasksTo) | ||
|
|
||
| val isAppProject = FlutterPluginUtils.isFlutterAppProject(projectToAddTasksTo) | ||
| val flutterPlugin = this |
There was a problem hiding this comment.
Flutter plugin can have lots of different meanings. pick a variable name that better helps describe the object.
There was a problem hiding this comment.
Renamed to flutterGradlePlugin. The line above it now says why the reference is captured at all: this is shadowed inside the task configuration blocks reached from here.
| // into the library manifest would break host builds that explicitly opt out. | ||
| FlutterPluginUtils.addTasksForEnableHcppManifest(projectToAddTasksTo) | ||
|
|
||
| val isAppProject = FlutterPluginUtils.isFlutterAppProject(projectToAddTasksTo) |
There was a problem hiding this comment.
Can we define this right before it is first used?
There was a problem hiding this comment.
Moved. It is now isApplicationProject, declared immediately above the onVariants call that reads it.
|
|
||
| // For application projects, the Flutter compile task is registered here, lazily, | ||
| // from the public variant API. For add-to-app module (library) projects it is still | ||
| // registered by the legacy variant callback in addFlutterDepsForModule until that |
There was a problem hiding this comment.
banned words, legacy, please fix. If you dont know what banned words are or where they are defined ask your human.
There was a problem hiding this comment.
Removed. The comment names the API instead: add-to-app module projects register from the libraryVariants callback in addFlutterDepsForModule. I removed the word from the two other places in this file as well, and renamed configureLegacyAbiVersionCodeOverride to configureAbiVersionCodeOverride.
| // path migrates to the variant API | ||
| // (https://github.com/flutter/flutter/issues/166550). The gating mirrors the | ||
| // legacy callback's shouldConfigureFlutterTask check on the assemble task name. | ||
| if (isAppProject && |
There was a problem hiding this comment.
is app project used here because the code only works on apps or because the app is the first thing to be migrated? If it is because the code is the first thing migrated the lets use a different variable name that better indicates the intent.
There was a problem hiding this comment.
Because the code only applies to application projects, not because they are first in line. Add-to-app module projects register their compile task from addFlutterDepsForModule and keep doing so until #166550. Renamed to isApplicationProject, and the comment on the branch names the path that handles the other case.
| // (https://github.com/flutter/flutter/issues/166550). The gating mirrors the | ||
| // legacy callback's shouldConfigureFlutterTask check on the assemble task name. | ||
| if (isAppProject && | ||
| FlutterPluginUtils.shouldConfigureFlutterTask( |
There was a problem hiding this comment.
Why do we need shouldConfigureFlutterTask?
There was a problem hiding this comment.
To keep the variant API path behaving like the path it replaces: with a single assemble<Variant> on the command line, Flutter is compiled only for the variant that task builds, so flutter build apk --release does not also configure the debug compile task.
I gave it a named wrapper, shouldCompileFlutterForVariant, whose doc states that reason and links #109560, which tracks removing the hack. I did not remove it here because that issue records that removal was tried on AGP/Gradle 7.2.0/7.5 and still caused build failures. That belongs in its own change.
| } | ||
| } | ||
|
|
||
| // Add-to-app module (library) path. Still entirely on the legacy variant API; the |
There was a problem hiding this comment.
Removed, same as the other one. The comment now says the function reads BaseVariant, which AGP deprecated.
| // Add-to-app module (library) path. Still entirely on the legacy variant API; the | ||
| // whole path is rewired to the variant API when add-to-app migrates | ||
| // (https://github.com/flutter/flutter/issues/166550). | ||
| // TODO(gmackall): Migrate to AGPs variant api. |
There was a problem hiding this comment.
Double reference to the same issue?
There was a problem hiding this comment.
Fixed! Removed the duplicate inner TODO comments.
| } | ||
|
|
||
| @Test | ||
| fun `shouldConfigureFlutterTask with assembleTaskName string parameter`() { |
There was a problem hiding this comment.
please add a test for profile since that is a custom task we add in the flutter gradle plugin.
There was a problem hiding this comment.
Added unit tests covering the Profile build mode (both Task and String overloads, plus negative mismatch validation) in FlutterPluginUtilsTest.kt.
a441448 to
0531208
Compare
… source directory on the app path This is PR 6 of 11 in the AGP 9.1.0 / public `gradle-api` migration stack (flutter#180137, flutter#166550). - Implements `CopyFlutterAssetsTask.kt` to stage `flutter_assets/**` from Flutter build output into a dedicated output directory with user read+write permissions. - For application projects, registers `copyFlutterAssets<Variant>` in the consolidated `onVariants` block as a lazy `TaskProvider` and wires it via `variant.sources.assets.addGeneratedSourceDirectory`. AGP now merges Flutter's assets as a generated asset source directory. - Refactors compile option extraction into `FlutterCompileOptions` data class, deduplicating property reads and eliminating redundant `dependsOn` declarations. - Deletes legacy app-path asset copying (`mergeAssets.outputDir`), `processResources`/`cleanMergeAssets` task-graph surgery, and manual `compress<Variant>Assets` `dependsOn` wiring for app projects (leaving add-to-app module paths unchanged until PR 8). - Adds unit tests in `FlutterPluginTest.kt` for `onVariants` lazy registration, asset source directory wiring, and skipping when `shouldConfigureFlutterTask` is false. - Adds unit tests in `CopyFlutterAssetsTaskTest.kt` verifying task execution against real files (staging layout, POSIX permission bits, non-asset exclusions, and stale output cleanup). - Adds integration test `android_gradle_asset_merging_test.dart` verifying standard assets, directory assets, resolution variants, native Android assets, incremental add/remove sync, collision precedence, and flavor assets.
0531208 to
e629da3
Compare
| /** | ||
| * Resolves Gradle and project properties for configuring a [FlutterTask]. | ||
| */ | ||
| internal data class FlutterCompileOptions( |
There was a problem hiding this comment.
This class should be its own file with its own tests.
There was a problem hiding this comment.
Moved to FlutterCompileOptions.kt, with tests in FlutterCompileOptionsTest.kt covering the property reads, the defaults when nothing is set, and equality.
| val deferredComponents: Boolean, | ||
| val validateDeferredComponents: Boolean | ||
| ) { | ||
| override fun equals(other: Any?): Boolean { |
There was a problem hiding this comment.
Give me a justification for authoring equals and hashcode they seem cumbersome to maintain and easily forgotten.
There was a problem hiding this comment.
There is no justification, so they are gone. They existed only because fileSystemRoots was an Array, which compares by identity and would have made the generated equals wrong. The field is a List now, the generated implementations are correct, and both overrides are deleted. BaseFlutterTask.fileSystemRoots still takes an Array, so the conversion happens at the one assignment site.
FlutterCompileOptionsTest has a case asserting that options resolved from two equivalent projects are equal, so a future Array field fails a test rather than silently breaking equality.
| FlutterCompileOptions( | ||
| fileSystemRoots = | ||
| project | ||
| .findProperty("filesystem-roots") |
There was a problem hiding this comment.
Pull the strings out into constants and define each constant next to the parameter related to it.
val fileSystemRootsProperty = "filesystem-roots"
val fileSystemRoots: Array<String>?,
| .findProperty("filesystem-roots") | |
| .findProperty(fileSystemRootsProperty) |
There was a problem hiding this comment.
Done, every property name is a constant and from reads them by name. One deviation from your snippet: Kotlin does not allow declarations between primary constructor parameters, so the constants are a single block in the companion object, in the same order as the parameters and named after them. If you would rather have locals inside from exactly as you wrote it, say so and I will switch.
| @@ -362,7 +370,7 @@ object FlutterPluginUtils { | |||
| @JvmName("shouldConfigureFlutterTask") | |||
There was a problem hiding this comment.
There is a lot of prose in this kt doc with linked issues. Is all of this documentation still true? does this documentation needs to be updated? Does this pr change anything in this documentation/
There was a problem hiding this comment.
Audited it.
Still true: the workaround itself, and the recorded finding that removing it on AGP/Gradle 7.2.0/7.5 still caused build failures.
Stale: "The AGP team said that this issue is fixed in Gradle 7.0, which isn't released at the time of adding this code."
Changed by this PR: the function has a second caller, the variant API registration, so the doc now leads with what the function does and where it is used rather than with the JAR task story.
The tested Gradle invocations and the two AGP bug links are quoted verbatim in #109560, so the doc points there instead of repeating them.
| every { mockVariant.name } returns "debug" | ||
| every { mockVariant.buildType } returns "debug" | ||
| every { mockVariant.debuggable } returns true | ||
| every { mockVariant.flavorName } returns null | ||
| every { mockVariant.minSdk.apiLevel } returns 21 | ||
| every { mockVariant.sources } returns mockSources | ||
| every { mockSources.assets } returns mockAssetsSource |
There was a problem hiding this comment.
Here and in other places in this code i see lots of duplicate code that does not appear to be related to the code under test. Consider authoring a helper. Code that is specifically being tested, as opposed to required mocks for the code to execute, can not use helpers for clarity for the reader understanding the test.
There was a problem hiding this comment.
Added two helpers: applyPluginCapturingVariantCallback, which does the mock setup, applies the plugin and returns the captured onVariants callback, and mockApplicationVariant, which stubs the variant the plugin reads.
Anything a test is actually about is still passed explicitly at the call site, for example assetsSource = null in the GradleException test and name = "androidTest" in the gating test.
|
|
||
| @Test | ||
| fun `apply adds task for generating manifest with engine shell arguments`( | ||
| fun `registerFlutterCompileTask sets compile properties from project and variant`( |
There was a problem hiding this comment.
This test will probably be moved when the data class is moved to its own file. This test can be in a new test file that has a similar name.
There was a problem hiding this comment.
The data class and the tests for it are in FlutterCompileOptions.kt and FlutterCompileOptionsTest.kt, which is where property parsing and defaults now live.
I kept one test here, renamed to registerFlutterCompileTask applies compile options and variant values to the task, because what it checks is FlutterPlugin behavior: that each option and each variant value is mapped onto the right FlutterTask property. That is the mapping most likely to acquire a copy paste error, and it is not testable from the data class. Happy to move it too if you disagree.
|
|
||
| val stagedAsset = destinationDir.resolve("flutter_assets/sub/asset.txt") | ||
| assertTrue(stagedAsset.isFile, "expected $stagedAsset to be staged") | ||
| if (!OperatingSystem.current().isWindows) { |
There was a problem hiding this comment.
why exclude windows here?
There was a problem hiding this comment.
Because Files.getPosixFilePermissions throws UnsupportedOperationException on a file system with no POSIX view, which is the usual case on Windows.
The operating system was the wrong thing to branch on, so it now checks FileSystems.getDefault().supportedFileAttributeViews(), and the readable and writable assertions, which are the actual contract, run on every platform.
| void addAssetsToPubspec(File pubspecFile, List<String> assets) { | ||
| final String content = pubspecFile.readAsStringSync(); | ||
| expect( | ||
| content.contains('uses-material-design: true'), |
There was a problem hiding this comment.
Why do we care about uses-material-design? That package is under migration and if we can avoid referencing it then we should. I dont think we need material anything to test the code we care about.
There was a problem hiding this comment.
Dropped. addAssetsToPubspec anchors on the top-level flutter: section with RegExp(r'^flutter:$', multiLine: true) and fails with a reason if that section is missing. No part of the test refers to material now.
| 'assets/flutter_assets/assets/image.png', | ||
| ); | ||
| expect(baseImageEntry, isNotNull); | ||
| expect(readArchiveFileString(baseImageEntry!), 'flutter_image_1x_content'); |
There was a problem hiding this comment.
For the asset types lets not use 1x and 2x lets use whatever is default and something that is a common split for apps to do. Maybe something ultra high resolution.
There was a problem hiding this comment.
Changed to the default asset plus 3.0x and 4.0x.
The pubspec declares only assets/image.png, which is how an app declares a resolution-aware image. The tool discovers the density variants from the sibling directories, and the test asserts that all three files land in the APK.
| } | ||
|
|
||
| testWithoutContext( | ||
| 'Flutter assets, directory assets, resolution variants, and native Android assets coexist in APK', |
There was a problem hiding this comment.
This test is extremely long with few helper methods. Help organize this test for readability and make it clear to future maintainers who want to add a new asset type what sections need modification.
There was a problem hiding this comment.
Reorganized around one table per asset source, flutterAssets and nativeAndroidAssets. Each row carries the source path, the pubspec entry, the expected APK path and the contents, and the same table drives fixture creation, the pubspec, and the assertions, so covering a new asset type is one row in one place. A comment above the table says so.
The repeated childDirectory chains became writeFlutterAsset, writeAndroidSourceSetAsset, expectApkEntry and expectNoApkEntry, which the other three tests in the file use as well.
…et coverage - Rename the incremental test to describe what it actually asserts (stale asset pruning on rebuild) and document why it does not assert UP-TO-DATE. - Document the AGP SourceDirectories contract above the collision test, so it is clear the assertion pins a documented guarantee rather than incidental merge ordering, and strengthen the failure reason accordingly. - Extend the flavor test to also cover per-buildType source-set assets (src/debug, src/release), including the negative case. Reuses the existing freeDebug build, so this adds no extra CI time.
|
Addressed the asset-type coverage request in the PR description — see the new Asset type coverage section. A few notes on how I approached it:
Also pushed
All 4 integration tests pass locally (71s total), |
…n tests Main code: * Extract FlutterCompileOptions into its own file. fileSystemRoots is a List instead of an Array, so the generated equals and hashCode are correct and the hand written ones are deleted. Every Gradle property name is a named constant. * Split the onVariants body into shouldCompileFlutterForVariant, registerFlutterAssetTasks and registerFlutterJniLibsTask, and share one configureCompileTask between the application and add-to-app paths. * Rename the captured plugin reference to flutterGradlePlugin, rename configureLegacyAbiVersionCodeOverride to configureAbiVersionCodeOverride, and remove "legacy" from the comments in favor of the API names. * Audit the shouldConfigureFlutterTask doc: keep the workaround rationale, drop the stale claim about Gradle 7.0, and point at issue 109560 for the history. Tests: * Add FlutterCompileOptionsTest for property reads, defaults and equality. * Add applyPluginCapturingVariantCallback and mockApplicationVariant helpers to FlutterPluginTest. * Assert staged asset permissions by POSIX view support instead of by operating system. * Rework the asset merging integration test around one table per asset source that drives the fixture, the pubspec and the assertions, use the base image plus 3.0x and 4.0x variants, and anchor the pubspec edit on the flutter: section instead of uses-material-design.
- CopyFlutterAssetsTask.intermediateDir docs claimed the value could be absent by analogy to CopyFlutterJniLibsTask. That analogy is wrong: the assets task is only registered for variants Flutter compiles for and is wired to that variant's compile task, which always sets an output directory. Document why @optional is kept anyway (add-to-app, flutter#166550) and why the registration site asserts instead of tolerating absence. - Add a comment at the registration site explaining the requireNotNull. - buildApk() took a mode parameter but always produced a debug APK name, and every caller used the default. Remove the parameter and document the debug-only choice.
| output as com.android.build.gradle.api.ApkVariantOutput | ||
| val versionCodeIfPresent: Int? = if (variant is ApkVariant) variant.versionCode else null | ||
| ) { | ||
| val compileTaskProvider = |
There was a problem hiding this comment.
Why does registerFlutterAssetTasks call registerFlutterCompileTask? Are we considering the compile task an asset task? Can we move it outside of this task and just call it before registerFlutterAssetTasks?
There was a problem hiding this comment.
Looking at the stack I think this was because the original copy task tied everything together. With this new code it should be possible to extract. Will ask for rereview once I either get this extracted or figure out if there is still a reason to keep them combined.
There was a problem hiding this comment.
Implemented in commit cfa443e: the compile task is now registered directly in onVariants and its compileTaskProvider is passed to registerFlutterAssetTasks(project, variant, compileTaskProvider). This also allowed dropping flutterGradlePlugin and targetPlatforms from registerFlutterAssetTasks.
flutter/flutter@e89fd0a...d03768e 2026-10-02 engine-flutter-autoroll@skia.org Roll Skia from 7b7326917e77 to 9e88bf828078 (8 revisions) (flutter/flutter#193694) 2026-10-02 bkonyi@google.com [Widget Preview] Provide descriptive error when widget preview is unconstrained (flutter/flutter#193005) 2026-10-02 engine-flutter-autoroll@skia.org Roll Dart SDK from 0e7642b85457 to ac1a97aae47d (26 revisions) (flutter/flutter#193692) 2026-10-02 mbrase@google.com Remove unused Fuchsia sysmem header files (flutter/flutter#193244) 2026-10-02 57864509+tjcGoogle@users.noreply.github.com [Windows] Preserve composing extent in setEditingState (flutter/flutter#189968) 2026-10-02 brackenavaron@gmail.com [docs] Make Border.symmetric docs more explicit about their arguments (flutter/flutter#193222) 2026-10-02 60122246+xiaowei-guan@users.noreply.github.com [Embedder] Support render texture for vulkan (flutter/flutter#188855) 2026-10-02 jesswon@google.com Updated Remaining Engine Defaults to SDK 37 (flutter/flutter#190429) 2026-10-02 105214765+HibaChamkhi@users.noreply.github.com Document that enableSuggestions: false can disable keyboard languages on Android (flutter/flutter#192714) 2026-10-02 bkonyi@google.com [flutter_tools] Explicitly track host CPU architecture in command result analytics (flutter/flutter#191836) 2026-10-02 kevmoo@users.noreply.github.com [web] Preserve DOM focus on role update and honor isAccessibilityFocusBlocked (flutter/flutter#192963) 2026-10-02 mdebbar@google.com [flutter_tools] Include base href in web hot reload script paths (flutter/flutter#193678) 2026-10-02 kevmoo@users.noreply.github.com [Impeller] Deduplicate GLES render pass state (flutter/flutter#193427) 2026-10-02 engine-flutter-autoroll@skia.org Roll Skia from f2d68e0b8863 to 7b7326917e77 (16 revisions) (flutter/flutter#193676) 2026-10-01 bkonyi@google.com Fix analysis failures due to missing `const` (flutter/flutter#193685) 2026-10-01 47866232+chunhtai@users.noreply.github.com Removes a11y_assessment app (flutter/flutter#193671) 2026-10-01 codefu@google.com test: configure Xvfb and openbox for windowing_test (flutter/flutter#193529) 2026-10-01 154381524+flutteractionsbot@users.noreply.github.com Sync CHANGELOG.md from stable (flutter/flutter#193666) 2026-10-01 alexmarkov@google.com Avoid using relative path in Process.start (flutter/flutter#193664) 2026-10-01 269567208+reidbaker-agent@users.noreply.github.com [AGP 9.1.0 Migration #6] Deliver Flutter assets as a generated assets source directory on the app path (flutter/flutter#192488) 2026-10-01 codefu@google.com ci(bringup): android_java17_build_android_host_app_with_module_aar is green (flutter/flutter#193580) 2026-10-01 bkonyi@google.com [flutter_tools] Fix Use dependency graph to determine plugin initialization order (flutter/flutter#191591) 2026-10-01 bkonyi@google.com [flutter_tools] Migrate DaemonCommand and Daemon domains to constructor DI (flutter/flutter#193542) If this roll has caused a breakage, revert this CL and set the roller to dry run mode using the controls here: https://autoroll.skia.org/r/flutter-packages Please CC quncheng@google.com,stuartmorgan@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
Description
This is PR 6 of 11 in the AGP 9.1.0 / public
gradle-apimigration stack (#180137, #166550).Before turning an agent to this stack of prs this migration was the one I was the most worried about getting right. I added a test we were missing to help prevent regressions. Add to app is a future pr (I think the next one).
Agent authored details
### ChangescopyFlutterAssets<Variant>inside the consolidatedonVariantsblock as a lazyTaskProvider<CopyFlutterAssetsTask>.variant.sources.assets.addGeneratedSourceDirectory(copyFlutterAssetsTaskProvider, CopyFlutterAssetsTask::destinationDir), so AGP merges Flutter's assets as a generated asset source directory instead of us mutating the task graph.CopyFlutterAssetsTaskFileSystemOperations.syncso stale assets are pruned on rebuild.@InputDirectory+@PathSensitive(RELATIVE),@OutputDirectory, and@DisableCachingByDefault.FlutterCompileOptionsdata class to remove duplicated property reads between the app path andaddFlutterDepsForModule.mergeAssets.outputDir, theprocessResources/cleanMergeAssetssurgery, and the manualcompress<Variant>AssetsdependsOnwiring. Add-to-app module paths are intentionally untouched until PR 8.onVariantswiring andCopyFlutterAssetsTaskexecution, plus a new Android integration test that inspects the built APK.Asset type coverage
Two different levels of verification are relevant here, and it is worth separating them:
flutter_assetsbundle thatflutter assembleproduces? Covered by unit tests ingeneral.shard..apk/.aab? Covered by integration tests that unzip the artifact.This distinction matters because
CopyFlutterAssetsTaskperforms a single opaquesyncof the wholeflutter_assets/**tree with one top-levelincludeand no per-type filtering, renaming, or special-casing. Nothing inside that directory is treated differently by the Android build. So for Flutter-side asset types, the APK-level risk is not "does this asset type survive" but "does the directory arrive at all" — which the new tests in this PR cover directly.Tests added in this PR are in
packages/flutter_tools/test/integration.shard/android_gradle_asset_merging_test.dart, abbreviated below asasset_merging.assets:entryasset_bundle_test.dart→'nonempty'asset_merging→'Flutter assets, directory assets, resolution variants, and native Android assets coexist in APK'asset_bundle_test.dart→'wildcard directories do not include subdirectories'asset_merging→ same test as above2.0x/,3.0x/)asset_bundle_variant_test.dart→ group'AssetBundle asset variants (with Unix-style paths)'asset_merging→ same test as abovefonts:)asset_bundle_package_fonts_test.dart→'App includes neither font manifest nor fonts when no defines fonts'flutter_assetsand receive no special handling fromCopyFlutterAssetsTaskor AGP. Bundle-level coverage plus this PR's directory-arrival coverage is sufficient.packages/<pkg>/...)asset_bundle_package_test.dart:530→'One asset is bundled when the app depends on a package, ...'flutter_assets.NOTICES/ license aggregationasset_bundle_test.dart#L131—NOTICES.Zis asserted in the expected bundle output of most tests in the fileshaders:)asset_bundle_test.dart#L918→'Including a shader triggers the shader compiler'flutter_assetsas opaque files.asset_bundle_test.dart→'deferred assets are parsed'deferred_components_assets_reproduce_test.dart→'deferred components assets are not missing on clean build';deferred_components_test.dart→'simple build appbundle android-arm64 target succeeds'android_obfuscate_test.dart→'Dart identifiers are obfuscated with build apk --obfuscate'libapp.so, and the symbol file is emitted out-of-band to the host disk rather than packaged. Listed only to preempt the question.src/main/assets/asset_merging→'Flutter assets, directory assets, resolution variants, and native Android assets coexist in APK'asset_merging→'generated Flutter assets take precedence over static src/main/assets on path collision without build failure'.src/<flavor>/assets/asset_merging→'flavor-specific and buildType-specific native assets are packaged into the matching variant APK'src/<buildType>/assets/asset_merging→ same test as aboveandroidResources.noCompressGap summary. The only Android-side gap this PR chose not to close is library/AAR asset merging, on the grounds that it is AGP behavior we do not modify. If we later want it, the natural home is
android_gradle_asset_merging_test.dart— add aflutter create --template=plugindependency carrying an asset and assert the entry appears in the app's APK.Behavioral and compatibility notes
copyFlutterAssets<Variant>is no longer a GradleCopy. Build scripts that reached in and cast it toorg.gradle.api.tasks.Copywill now get aClassCastExceptionand must useCopyFlutterAssetsTaskor plainTask.processResourcesno longer depends onflutter assemble. Java/Kotlin resource processing is now independent of Flutter compilation.FileSystemOperations.syncin the task's own output directory rather than by mutatingcleanMergeAssets.SourceDirectoriesAPI docs,addGeneratedSourceDirectoryplaces the directory in the "Variant" overlay and it "will have the highest priority" during merge. Generated Flutter assets therefore win oversrc/main/assets. This is asserted by test, and the contract is quoted in a comment above that test.Pre-launch Checklist
///).