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