Skip to content

[AGP 9.1.0 Migration #6] Deliver Flutter assets as a generated assets source directory on the app path - #192488

Merged
auto-submit[bot] merged 11 commits into
flutter:masterfrom
reidbaker-agent:agp-assets-onvariants
Oct 1, 2026
Merged

auto-submit[bot] merged 11 commits into
flutter:masterfrom
reidbaker-agent:agp-assets-onvariants

Conversation

@reidbaker-agent

@reidbaker-agent reidbaker-agent commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

Description

This is PR 6 of 11 in the AGP 9.1.0 / public gradle-api migration 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 ### Changes
  1. App asset delivery via the modern variant API
    • Registers copyFlutterAssets<Variant> inside the consolidated onVariants block as a lazy TaskProvider<CopyFlutterAssetsTask>.
    • Wires it to AGP with 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.
  2. CopyFlutterAssetsTask
    • Uses injected FileSystemOperations.sync so stale assets are pruned on rebuild.
    • Forces owner read+write on staged assets, avoiding read-only files inherited from the pub cache.
    • @InputDirectory + @PathSensitive(RELATIVE), @OutputDirectory, and @DisableCachingByDefault.
  3. Compile option extraction — adds a FlutterCompileOptions data class to remove duplicated property reads between the app path and addFlutterDepsForModule.
  4. Task graph cleanup — deletes the legacy app-path copy into mergeAssets.outputDir, the processResources / cleanMergeAssets surgery, and the manual compress<Variant>Assets dependsOn wiring. Add-to-app module paths are intentionally untouched until PR 8.
  5. Tests — new unit tests for onVariants wiring and CopyFlutterAssetsTask execution, 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:

  • Bundle-level — does the asset end up in the flutter_assets bundle that flutter assemble produces? Covered by unit tests in general.shard.
  • APK-level — does it end up inside the built .apk / .aab? Covered by integration tests that unzip the artifact.

This distinction matters because CopyFlutterAssetsTask performs a single opaque sync of the whole flutter_assets/** tree with one top-level include and 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 as asset_merging.

Asset type How it's specified Bundle-level coverage APK-level coverage Assessment
Standard assets: entry Flutter: assets and images asset_bundle_test.dart → 'nonempty' asset_merging → 'Flutter assets, directory assets, resolution variants, and native Android assets coexist in APK' Covered at both levels.
Directory / wildcard assets Flutter: asset variants asset_bundle_test.dart → 'wildcard directories do not include subdirectories' asset_merging → same test as above Covered at both levels.
Resolution-aware variants (2.0x/, 3.0x/) Flutter: resolution-aware images asset_bundle_variant_test.dart → group 'AssetBundle asset variants (with Unix-style paths)' asset_merging → same test as above Covered at both levels.
Fonts (fonts:) Flutter: custom fonts asset_bundle_package_fonts_test.dart → 'App includes neither font manifest nor fonts when no defines fonts' None No new test recommended. Fonts are ordinary files inside flutter_assets and receive no special handling from CopyFlutterAssetsTask or AGP. Bundle-level coverage plus this PR's directory-arrival coverage is sufficient.
Package assets (packages/<pkg>/...) Flutter: assets from packages asset_bundle_package_test.dart:530 → 'One asset is bundled when the app depends on a package, ...' None No new test recommended. Same rationale — these are plain files under flutter_assets.
NOTICES / license aggregation Flutter: licenses asset_bundle_test.dart#L131 — NOTICES.Z is asserted in the expected bundle output of most tests in the file None No new test recommended. Same rationale.
Shaders (shaders:) Flutter: fragment shaders asset_bundle_test.dart#L918 → 'Including a shader triggers the shader compiler' None No new test recommended. Compiled shaders land in flutter_assets as opaque files.
Deferred-component assets Flutter: deferred components 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' Covered at both levels. These are the only pre-existing tests that unzip an artifact and assert on asset entries.
Obfuscation / split debug info Flutter: obfuscation n/a android_obfuscate_test.dart → 'Dart identifiers are obfuscated with build apk --obfuscate' Not an asset path. That test asserts on 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/ Android: app resources n/a asset_merging → 'Flutter assets, directory assets, resolution variants, and native Android assets coexist in APK' Covered. Also covered on collision: asset_merging → 'generated Flutter assets take precedence over static src/main/assets on path collision without build failure'.
Per-flavor src/<flavor>/assets/ Android: build variants n/a asset_merging → 'flavor-specific and buildType-specific native assets are packaged into the matching variant APK' Covered, including the negative case that the non-selected flavor does not contribute.
Per-buildType src/<buildType>/assets/ Android: build variants n/a asset_merging → same test as above Covered. Added in response to review; folded into the existing flavor test so it reuses that build and costs no additional CI time.
Library / AAR assets from dependencies Android: create a library n/a None No new test recommended. Likely common in the wild, but merging AAR assets is core AGP behavior that this PR does not touch — we add a generated source directory and otherwise leave the merger alone. Testing it would be testing AGP, not Flutter.
androidResources.noCompress Android: AaptOptions n/a None No new test recommended. Rare, and handled entirely by AGP's packaging step downstream of anything this PR changes.

Gap 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 a flutter create --template=plugin dependency carrying an asset and assert the entry appears in the app's APK.

Behavioral and compatibility notes

  1. copyFlutterAssets<Variant> is no longer a Gradle Copy. Build scripts that reached in and cast it to org.gradle.api.tasks.Copy will now get a ClassCastException and must use CopyFlutterAssetsTask or plain Task.
  2. processResources no longer depends on flutter assemble. Java/Kotlin resource processing is now independent of Flutter compilation.
  3. Stale asset pruning moved. It is handled by FileSystemOperations.sync in the task's own output directory rather than by mutating cleanMergeAssets.
  4. Collision precedence is contractual, not incidental. Per the AGP SourceDirectories API docs, addGeneratedSourceDirectory places the directory in the "Variant" overlay and it "will have the highest priority" during merge. Generated Flutter assets therefore win over src/main/assets. This is asserted by test, and the contract is quoted in a comment above that test.

Pre-launch Checklist

@github-actions github-actions Bot added platform-android Android applications specifically tool Affects the "flutter" command-line tool. See also t: labels. labels Sep 9, 2026
@reidbaker
reidbaker force-pushed the agp-assets-onvariants branch from 1625350 to 2baee0e Compare September 9, 2026 15:30
@reidbaker reidbaker added the CICD Run CI/CD label Sep 9, 2026
@reidbaker
reidbaker force-pushed the agp-assets-onvariants branch from 2baee0e to a441448 Compare September 9, 2026 15:58
@flutter-dashboard flutter-dashboard Bot removed the CICD Run CI/CD label Sep 9, 2026

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

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

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.

Flutter plugin can have lots of different meanings. pick a variable name that better helps describe the object.

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.

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)

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.

Can we define this right before it is first used?

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.

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

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.

banned words, legacy, please fix. If you dont know what banned words are or where they are defined ask your human.

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.

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

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.

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.

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.

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(

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 do we need shouldConfigureFlutterTask?

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.

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.

Comment thread packages/flutter_tools/gradle/src/main/kotlin/FlutterPlugin.kt
}
}

// Add-to-app module (library) path. Still entirely on the legacy variant API; the

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.

banned words violation

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.

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.

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.

Double reference to the same issue?

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.

Fixed! Removed the duplicate inner TODO comments.

}

@Test
fun `shouldConfigureFlutterTask with assembleTaskName string parameter`() {

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 add a test for profile since that is a custom task we add in the flutter gradle plugin.

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.

Added unit tests covering the Profile build mode (both Task and String overloads, plus negative mismatch validation) in FlutterPluginUtilsTest.kt.

@reidbaker
reidbaker force-pushed the agp-assets-onvariants branch from a441448 to 0531208 Compare September 9, 2026 16:50
@reidbaker reidbaker added the CICD Run CI/CD label Sep 10, 2026
… 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.
@reidbaker
reidbaker force-pushed the agp-assets-onvariants branch from 0531208 to e629da3 Compare September 10, 2026 15:30
@flutter-dashboard flutter-dashboard Bot removed the CICD Run CI/CD label Sep 10, 2026
@github-actions github-actions Bot added the team-android Owned by Android platform team label Sep 10, 2026
/**
* Resolves Gradle and project properties for configuring a [FlutterTask].
*/
internal data class FlutterCompileOptions(

@reidbaker reidbaker Sep 10, 2026 •

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 class should be its own file with its own tests.

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.

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 {

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.

Give me a justification for authoring equals and hashcode they seem cumbersome to maintain and easily forgotten.

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.

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")

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.

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>?,
Suggested change
.findProperty("filesystem-roots")
.findProperty(fileSystemRootsProperty)

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.

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.

Comment on lines 339 to 370
@@ -362,7 +370,7 @@ object FlutterPluginUtils {
@JvmName("shouldConfigureFlutterTask")

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.

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/

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.

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.

Comment on lines +153 to +159
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

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.

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.

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.

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`(

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

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.

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) {

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

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'),

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

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.

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');

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.

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.

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.

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',

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

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.

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.
@reidbaker-agent

Copy link
Copy Markdown
Contributor Author

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:

  • I split the matrix into bundle-level and APK-level columns. Only two pre-existing tests in the repo actually unzip a build artifact and assert on asset entries (deferred_components_test.dart and deferred_components_assets_reproduce_test.dart), so claiming the general.shard asset bundle tests as "verified in the build artifact" would have been misleading.
  • For the Flutter-side types with no APK-level test (fonts, package assets, NOTICES, shaders) the assessment column argues why one isn't needed: CopyFlutterAssetsTask does a single opaque sync of flutter_assets/** with one top-level include and no per-type handling, so the real risk is the directory not arriving, which the new tests cover. Happy to add specific ones if you disagree with that reasoning.
  • Every test name in the matrix was verified to exist. Line-number links where useful.

Also pushed f5f63e084b4 with three test changes:

  1. Added per-buildType asset coverage (src/debug / src/release), including the negative case. Folded into the existing flavor test so it reuses that build and adds no CI time. This was the one real gap the matrix turned up.
  2. Renamed the "incremental" test to assets removed from pubspec are pruned from the APK on rebuild. The old name overclaimed — the test never asserted Gradle incrementality, and a full rebuild would also have passed it. Comment explains the deliberate choice not to scrape stdout for UP-TO-DATE.
  3. Documented the AGP contract above the collision test. I'd originally written that test asserting src/main/assets wins; it failed and I flipped it. That was the right answer for the wrong reason, so I went and read the AGP source — SourceDirectories states that addGeneratedSourceDirectory enters the "Variant" overlay and "will have the highest priority" during merge. The assertion pins a documented guarantee, and the quote is now in a comment so nobody has to rediscover it.

All 4 integration tests pass locally (71s total), dart analyze --fatal-infos clean, ktlint 1.5.0 clean.

@reidbaker reidbaker added the CICD Run CI/CD label Sep 11, 2026
…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.
@reidbaker reidbaker added the CICD Run CI/CD label Sep 17, 2026
reidbaker
reidbaker previously approved these changes Sep 17, 2026
@flutter-dashboard flutter-dashboard Bot removed the CICD Run CI/CD label Sep 17, 2026
- 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.
@flutter-dashboard flutter-dashboard Bot removed the CICD Run CI/CD label Sep 17, 2026
@reidbaker reidbaker added the CICD Run CI/CD label Sep 17, 2026
reidbaker
reidbaker previously approved these changes Sep 18, 2026
output as com.android.build.gradle.api.ApkVariantOutput
val versionCodeIfPresent: Int? = if (variant is ApkVariant) variant.versionCode else null
) {
val compileTaskProvider =

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

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.

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.

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.

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.

@reidbaker reidbaker added the CICD Run CI/CD label Sep 29, 2026

@gmackall gmackall left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

lgtm, sorry for delay

@reidbaker reidbaker added the autosubmit Merge PR when tree becomes green via auto submit App label Oct 1, 2026
@auto-submit
auto-submit Bot added this pull request to the merge queue Oct 1, 2026
Merged via the queue into flutter:master with commit 65afe58 Oct 1, 2026
28 of 29 checks passed
@flutter-dashboard flutter-dashboard Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Oct 1, 2026
auto-submit Bot pushed a commit to flutter/packages that referenced this pull request Oct 2, 2026
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants