From b8902e6a174656a06ab82f4a22e749d96e90dea5 Mon Sep 17 00:00:00 2001
From: Mikhail Novoseltsev <51940183+Sameri11@users.noreply.github.com>
Date: Fri, 28 Feb 2025 19:57:20 +0500
Subject: [PATCH] [tool] Allow using archiveName in android bundle build
(#162390)
fixes #126971
Also, for some reason, fixes #147261 , which shouldn't have been ever
broken since this case tested here:
https://github.com/flutter/flutter/blob/b007899d3a7a072e880e3984856c777cc9c5082b/packages/flutter_tools/test/general.shard/android/gradle_find_bundle_test.dart#L153-L173
Key change here is replacing the optimistic approach of guessing the
filename based on buildInfo parameters with the actual discovery of
built artifacts on the filesystem. This change is needed because there
is no way the build can determine the archiveName from Gradle, which
differentiates this part from other parameters such as buildType and
flavor. Flutter build contains information about them, but not about the
base name.
## Pre-launch Checklist
- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.
If you need help, consider asking for advice on the #hackers-new channel
on [Discord].
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
---------
Co-authored-by: Reid Baker
Co-authored-by: Gray Mackall <34871572+gmackall@users.noreply.github.com>
Co-authored-by: Reid Baker
---
.../flutter_tools/lib/src/android/gradle.dart | 89 ++++++++-----------
.../android/gradle_find_bundle_test.dart | 21 +++++
2 files changed, 60 insertions(+), 50 deletions(-)
diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart
index 6cca0e60c7..d49fd74796 100644
--- a/packages/flutter_tools/lib/src/android/gradle.dart
+++ b/packages/flutter_tools/lib/src/android/gradle.dart
@@ -1149,58 +1149,47 @@ File findBundleFile(
Logger logger,
Analytics analytics,
) {
- final List fileCandidates = [
- getBundleDirectory(project).childDirectory(camelCase(buildInfo.modeName)).childFile('app.aab'),
- getBundleDirectory(
- project,
- ).childDirectory(camelCase(buildInfo.modeName)).childFile('app-${buildInfo.modeName}.aab'),
- ];
- if (buildInfo.flavor != null) {
- // The Android Gradle plugin 3.0.0 adds the flavor name to the path.
- // For example: In release mode, if the flavor name is `foo_bar`, then
- // the directory name is `foo_barRelease`.
- fileCandidates.add(
- getBundleDirectory(project)
- .childDirectory('${buildInfo.lowerCasedFlavor}${camelCase('_${buildInfo.modeName}')}')
- .childFile('app.aab'),
- );
-
- // The Android Gradle plugin 3.5.0 adds the flavor name to file name.
- // For example: In release mode, if the flavor name is `foo_bar`, then
- // the file name is `app-foo_bar-release.aab`.
- fileCandidates.add(
- getBundleDirectory(project)
- .childDirectory('${buildInfo.lowerCasedFlavor}${camelCase('_${buildInfo.modeName}')}')
- .childFile('app-${buildInfo.lowerCasedFlavor}-${buildInfo.modeName}.aab'),
- );
-
- // The Android Gradle plugin 4.1.0 does only lowercase the first character of flavor name.
- fileCandidates.add(
- getBundleDirectory(project)
- .childDirectory('${buildInfo.uncapitalizedFlavor}${camelCase('_${buildInfo.modeName}')}')
- .childFile('app-${buildInfo.uncapitalizedFlavor}-${buildInfo.modeName}.aab'),
- );
-
- // The Android Gradle plugin uses kebab-case and lowercases the first character of the flavor name
- // when multiple flavor dimensions are used:
- // e.g.
- // flavorDimensions "dimension1","dimension2"
- // productFlavors {
- // foo {
- // dimension "dimension1"
- // }
- // bar {
- // dimension "dimension2"
- // }
- // }
- fileCandidates.add(
- getBundleDirectory(project)
- .childDirectory('${buildInfo.uncapitalizedFlavor}${camelCase('_${buildInfo.modeName}')}')
- .childFile('app-${kebabCase(buildInfo.uncapitalizedFlavor!)}-${buildInfo.modeName}.aab'),
+ final Directory bundleDir = getBundleDirectory(project);
+ if (!bundleDir.existsSync()) {
+ _exitWithExpectedFileNotFound(
+ project: project,
+ fileExtension: '.aab',
+ logger: logger,
+ analytics: analytics,
);
}
- for (final File bundleFile in fileCandidates) {
- if (bundleFile.existsSync()) {
+ final Iterable allBundleFiles = bundleDir.listSync(recursive: true).whereType().where(
+ (File file) {
+ return file.path.endsWith('.aab');
+ },
+ );
+
+ for (final File bundleFile in allBundleFiles) {
+ // Use lowercase bundle parent directory name to handle varying cases from Android Gradle Plugin
+ final String bundleParentDir = bundleFile.parent.basename.toLowerCase();
+
+ if (buildInfo.flavor != null) {
+ // Handle flavor builds (e.g., 'build/app/outputs/bundle/foo_barRelease/app-foo_bar-release.aab')
+ if (bundleParentDir.contains(
+ '${buildInfo.lowerCasedFlavor}${buildInfo.modeName.toLowerCase()}',
+ ) &&
+ bundleFile.basename.endsWith('${buildInfo.modeName}.aab')) {
+ return bundleFile;
+ }
+
+ // Support legacy Android Gradle Plugin versions that don't include flavor in AAB filename
+ if (bundleParentDir.contains('${buildInfo.lowerCasedFlavor}${buildInfo.modeName}')) {
+ return bundleFile;
+ }
+ }
+
+ // Handle non-flavor builds (e.g., 'build/app/outputs/bundle/release/app-release.aab')
+ if (bundleParentDir == buildInfo.modeName &&
+ bundleFile.basename.endsWith('${buildInfo.modeName}.aab')) {
+ return bundleFile;
+ }
+ // Support legacy Android Gradle Plugin versions without build mode in AAB filename
+ if (bundleParentDir == buildInfo.modeName && bundleFile.basename.endsWith('aab')) {
return bundleFile;
}
}
diff --git a/packages/flutter_tools/test/general.shard/android/gradle_find_bundle_test.dart b/packages/flutter_tools/test/general.shard/android/gradle_find_bundle_test.dart
index e3eb2c6829..437644ff7e 100644
--- a/packages/flutter_tools/test/general.shard/android/gradle_find_bundle_test.dart
+++ b/packages/flutter_tools/test/general.shard/android/gradle_find_bundle_test.dart
@@ -621,6 +621,27 @@ void main() {
),
);
});
+
+ testWithoutContext(
+ 'Finds app bundle when archiveName / archiveBaseName is not standard "app"',
+ () {
+ final FlutterProject project = generateFakeAppBundle('debug', 'foo-debug.aab', fileSystem);
+ final File bundle = findBundleFile(
+ project,
+ const BuildInfo(
+ BuildMode.debug,
+ null,
+ treeShakeIcons: false,
+ packageConfigPath: '.dart_tool/package_config.json',
+ ),
+ BufferLogger.test(),
+ fakeAnalytics,
+ );
+
+ expect(bundle, isNotNull);
+ expect(bundle.path, '/build/app/outputs/bundle/debug/foo-debug.aab');
+ },
+ );
}
/// Generates a fake app bundle at the location [directoryName]/[fileName].