diff --git a/dev/devicelab/bin/tasks/gradle_plugin_bundle_test.dart b/dev/devicelab/bin/tasks/gradle_plugin_bundle_test.dart index 69c1854275..f08e96fcd5 100644 --- a/dev/devicelab/bin/tasks/gradle_plugin_bundle_test.dart +++ b/dev/devicelab/bin/tasks/gradle_plugin_bundle_test.dart @@ -7,19 +7,26 @@ import 'dart:async'; import 'package:flutter_devicelab/framework/apk_utils.dart'; import 'package:flutter_devicelab/framework/framework.dart'; import 'package:flutter_devicelab/framework/utils.dart'; +import 'package:path/path.dart' as path; Future main() async { await task(() async { try { - await runPluginProjectTest((FlutterPluginProject pluginProject) async { + await runProjectTest((FlutterProject project) async { section('App bundle content for task bundleRelease without explicit target platform'); - await pluginProject.runGradleTask('bundleRelease'); + await project.runGradleTask('bundleRelease'); - if (!pluginProject.hasReleaseBundle) - throw TaskResult.failure( - 'Gradle did not produce a release aab file at: ${pluginProject.releaseBundlePath}'); + final String releaseBundle = path.join( + project.rootPath, + 'build', + 'app', + 'outputs', + 'bundle', + 'release', + 'app.aab', + ); - final Iterable bundleFiles = await pluginProject.getFilesInAppBundle(pluginProject.releaseBundlePath); + final Iterable bundleFiles = await getFilesInAppBundle(releaseBundle); checkItContains([ 'base/manifest/AndroidManifest.xml', @@ -31,16 +38,51 @@ Future main() async { ], bundleFiles); }); - await runPluginProjectTest((FlutterPluginProject pluginProject) async { + await runProjectTest((FlutterProject project) async { + section('App bundle content using flavors without explicit target platform'); + // Add a few flavors. + await project.addProductFlavors( ['production', 'staging', 'development']); + // Build the production flavor in release mode. + await project.runGradleTask('bundleProductionRelease'); + + final String bundlePath = path.join( + project.rootPath, + 'build', + 'app', + 'outputs', + 'bundle', + 'productionRelease', + 'app.aab', + ); + + final Iterable bundleFiles = await getFilesInAppBundle(bundlePath); + + checkItContains([ + 'base/manifest/AndroidManifest.xml', + 'base/dex/classes.dex', + 'base/lib/arm64-v8a/libapp.so', + 'base/lib/arm64-v8a/libflutter.so', + 'base/lib/armeabi-v7a/libapp.so', + 'base/lib/armeabi-v7a/libflutter.so', + ], bundleFiles); + }); + + await runProjectTest((FlutterProject project) async { section('App bundle content for task bundleRelease with target platform = android-arm'); - await pluginProject.runGradleTask('bundleRelease', + await project.runGradleTask('bundleRelease', options: ['-Ptarget-platform=android-arm']); - if (!pluginProject.hasReleaseBundle) - throw TaskResult.failure( - 'Gradle did not produce a release aab file at: ${pluginProject.releaseBundlePath}'); + final String releaseBundle = path.join( + project.rootPath, + 'build', + 'app', + 'outputs', + 'bundle', + 'release', + 'app.aab', + ); - final Iterable bundleFiles = await pluginProject.getFilesInAppBundle(pluginProject.releaseBundlePath); + final Iterable bundleFiles = await getFilesInAppBundle(releaseBundle); checkItContains([ 'base/manifest/AndroidManifest.xml', diff --git a/dev/devicelab/bin/tasks/gradle_plugin_fat_apk_test.dart b/dev/devicelab/bin/tasks/gradle_plugin_fat_apk_test.dart index a876528d40..cc3f149418 100644 --- a/dev/devicelab/bin/tasks/gradle_plugin_fat_apk_test.dart +++ b/dev/devicelab/bin/tasks/gradle_plugin_fat_apk_test.dart @@ -17,11 +17,7 @@ Future main() async { section('APK content for task assembleDebug without explicit target platform'); await pluginProject.runGradleTask('assembleDebug'); - if (!pluginProject.hasDebugApk) - throw TaskResult.failure( - 'Gradle did not produce a debug apk file at: ${pluginProject.debugApkPath}'); - - final Iterable apkFiles = await pluginProject.getFilesInApk(pluginProject.debugApkPath); + final Iterable apkFiles = await getFilesInApk(pluginProject.debugApkPath); checkItContains([ 'AndroidManifest.xml', @@ -48,11 +44,7 @@ Future main() async { section('APK content for task assembleRelease without explicit target platform'); await pluginProject.runGradleTask('assembleRelease'); - if (!pluginProject.hasReleaseApk) - throw TaskResult.failure( - 'Gradle did not produce a release apk file at: ${pluginProject.releaseApkPath}'); - - final Iterable apkFiles = await pluginProject.getFilesInApk(pluginProject.releaseApkPath); + final Iterable apkFiles = await getFilesInApk(pluginProject.releaseApkPath); checkItContains([ 'AndroidManifest.xml', @@ -75,11 +67,7 @@ Future main() async { await pluginProject.runGradleTask('assembleRelease', options: ['-Ptarget-platform=android-arm,android-arm64']); - if (!pluginProject.hasReleaseApk) - throw TaskResult.failure( - 'Gradle did not produce a release apk at: ${pluginProject.releaseApkPath}'); - - final Iterable apkFiles = await pluginProject.getFilesInApk(pluginProject.releaseApkPath); + final Iterable apkFiles = await getFilesInApk(pluginProject.releaseApkPath); checkItContains([ 'AndroidManifest.xml', @@ -103,11 +91,7 @@ Future main() async { await pluginProject.runGradleTask('assembleRelease', options: ['-Ptarget-platform=android-arm,android-arm64', '-Psplit-per-abi=true']); - if (!pluginProject.hasReleaseArmApk) - throw TaskResult.failure( - 'Gradle did not produce a release apk at: ${pluginProject.releaseArmApkPath}'); - - final Iterable armApkFiles = await pluginProject.getFilesInApk(pluginProject.releaseArmApkPath); + final Iterable armApkFiles = await getFilesInApk(pluginProject.releaseArmApkPath); checkItContains([ 'AndroidManifest.xml', @@ -122,11 +106,7 @@ Future main() async { 'assets/flutter_assets/vm_snapshot_data', ], armApkFiles); - if (!pluginProject.hasReleaseArm64Apk) - throw TaskResult.failure( - 'Gradle did not produce a release apk at: ${pluginProject.releaseArm64ApkPath}'); - - final Iterable arm64ApkFiles = await pluginProject.getFilesInApk(pluginProject.releaseArm64ApkPath); + final Iterable arm64ApkFiles = await getFilesInApk(pluginProject.releaseArm64ApkPath); checkItContains([ 'AndroidManifest.xml', diff --git a/dev/devicelab/bin/tasks/gradle_plugin_light_apk_test.dart b/dev/devicelab/bin/tasks/gradle_plugin_light_apk_test.dart index 800a5075c8..6e44c50569 100644 --- a/dev/devicelab/bin/tasks/gradle_plugin_light_apk_test.dart +++ b/dev/devicelab/bin/tasks/gradle_plugin_light_apk_test.dart @@ -17,11 +17,7 @@ Future main() async { await pluginProject.runGradleTask('assembleDebug', options: ['-Ptarget-platform=android-arm']); - if (!pluginProject.hasDebugApk) - throw TaskResult.failure( - 'Gradle did not produce a debug apk file at: ${pluginProject.debugApkPath}'); - - final Iterable apkFiles = await pluginProject.getFilesInApk(pluginProject.debugApkPath); + final Iterable apkFiles = await getFilesInApk(pluginProject.debugApkPath); checkItContains([ 'AndroidManifest.xml', @@ -47,11 +43,7 @@ Future main() async { await pluginProject.runGradleTask('assembleRelease', options: ['-Ptarget-platform=android-arm']); - if (!pluginProject.hasReleaseApk) - throw TaskResult.failure( - 'Gradle did not produce a release apk file at: ${pluginProject.releaseApkPath}'); - - final Iterable apkFiles = await pluginProject.getFilesInApk(pluginProject.releaseApkPath); + final Iterable apkFiles = await getFilesInApk(pluginProject.releaseApkPath); checkItContains([ 'AndroidManifest.xml', @@ -74,11 +66,7 @@ Future main() async { await pluginProject.runGradleTask('assembleRelease', options: ['-Ptarget-platform=android-arm64']); - if (!pluginProject.hasReleaseApk) - throw TaskResult.failure( - 'Gradle did not produce a release apk file at: ${pluginProject.releaseApkPath}'); - - final Iterable apkFiles = await pluginProject.getFilesInApk(pluginProject.releaseApkPath); + final Iterable apkFiles = await getFilesInApk(pluginProject.releaseApkPath); checkItContains([ 'AndroidManifest.xml', @@ -124,7 +112,7 @@ Future main() async { await runProjectTest((FlutterProject project) async { section('gradlew assembleFreeDebug (product flavor)'); - await project.addProductFlavor('free'); + await project.addProductFlavors(['free']); await project.runGradleTask('assembleFreeDebug'); }); @@ -169,7 +157,7 @@ Future main() async { await runPluginProjectTest((FlutterPluginProject pluginProject) async { section('gradlew assembleDebug on plugin example'); await pluginProject.runGradleTask('assembleDebug'); - if (!pluginProject.hasDebugApk) + if (!File(pluginProject.debugApkPath).existsSync()) throw TaskResult.failure( 'Gradle did not produce an apk file at the expected place'); }); diff --git a/dev/devicelab/lib/framework/apk_utils.dart b/dev/devicelab/lib/framework/apk_utils.dart index 28efcbc54e..3b17872318 100644 --- a/dev/devicelab/lib/framework/apk_utils.dart +++ b/dev/devicelab/lib/framework/apk_utils.dart @@ -34,6 +34,27 @@ Future runPluginProjectTest(Future testFunction(FlutterPluginProject } } +Future> getFilesInApk(String apk) async { + if (!File(apk).existsSync()) + throw TaskResult.failure( + 'Gradle did not produce an output artifact file at: $apk'); + + final Process unzip = await startProcess( + 'unzip', + ['-v', apk], + isBot: false, // we just want to test the output, not have any debugging info + ); + return unzip.stdout + .transform(utf8.decoder) + .transform(const LineSplitter()) + .map((String line) => line.split(' ').last) + .toList(); +} + +Future> getFilesInAppBundle(String bundle) { + return getFilesInApk(bundle); +} + void checkItContains(Iterable values, Iterable collection) { for (T value in values) { if (!collection.contains(value)) { @@ -95,20 +116,25 @@ android { '''); } - Future addProductFlavor(String name) async { + Future addProductFlavors(Iterable flavors) async { final File buildScript = File( path.join(androidPath, 'app', 'build.gradle'), ); - buildScript.openWrite(mode: FileMode.append).write(''' + final String flavorConfig = flavors.map((String name) { + return ''' +$name { + applicationIdSuffix ".$name" + versionNameSuffix "-$name" +} + '''; + }).join('\n'); + buildScript.openWrite(mode: FileMode.append).write(''' android { flavorDimensions "mode" productFlavors { - $name { - applicationIdSuffix ".$name" - versionNameSuffix "-$name" - } + $flavorConfig } } '''); @@ -160,32 +186,9 @@ class FlutterPluginProject { String get releaseArm64ApkPath => path.join(examplePath, 'build', 'app', 'outputs', 'apk', 'release', 'app-arm64-v8a-release.apk'); String get releaseBundlePath => path.join(examplePath, 'build', 'app', 'outputs', 'bundle', 'release', 'app.aab'); - bool get hasDebugApk => File(debugApkPath).existsSync(); - bool get hasReleaseApk => File(releaseApkPath).existsSync(); - bool get hasReleaseArmApk => File(releaseArmApkPath).existsSync(); - bool get hasReleaseArm64Apk => File(releaseArm64ApkPath).existsSync(); - bool get hasReleaseBundle => File(releaseBundlePath).existsSync(); - Future runGradleTask(String task, {List options}) async { return _runGradleTask(workingDirectory: exampleAndroidPath, task: task, options: options); } - - Future> getFilesInApk(String apk) async { - final Process unzip = await startProcess( - 'unzip', - ['-v', apk], - isBot: false, // we just want to test the output, not have any debugging info - ); - return unzip.stdout - .transform(utf8.decoder) - .transform(const LineSplitter()) - .map((String line) => line.split(' ').last) - .toList(); - } - - Future> getFilesInAppBundle(String bundle) { - return getFilesInApk(bundle); - } } Future _runGradleTask({String workingDirectory, String task, List options}) async { diff --git a/packages/flutter_tools/gradle/flutter.gradle b/packages/flutter_tools/gradle/flutter.gradle index a69a19c673..84b8bb193c 100644 --- a/packages/flutter_tools/gradle/flutter.gradle +++ b/packages/flutter_tools/gradle/flutter.gradle @@ -114,7 +114,6 @@ class FlutterPlugin implements Plugin { String abiValue = PLATFORM_ARCH_MAP[targetArch] project.android { packagingOptions { - pickFirst "lib/${abiValue}/libflutter.so" // Prevent the ELF library from getting corrupted. doNotStrip "*/${abiValue}/libapp.so" } @@ -178,7 +177,7 @@ class FlutterPlugin implements Plugin { // The local engine is built for one of the build type. // However, we use the same engine for each of the build types. project.android.buildTypes.each { - addApiDependencies(project, it, project.files { + addApiDependencies(project, it.name, project.files { flutterJar }) } @@ -219,13 +218,13 @@ class FlutterPlugin implements Plugin { // added after applying the Flutter plugin. project.android.buildTypes.each { def buildMode = buildModeFor(it) - addApiDependencies(project, it, project.files { + addApiDependencies(project, it.name, project.files { baseJar[buildMode] }) } project.android.buildTypes.whenObjectAdded { def buildMode = buildModeFor(it) - addApiDependencies(project, it, project.files { + addApiDependencies(project, it.name, project.files { baseJar[buildMode] }) } @@ -356,14 +355,14 @@ class FlutterPlugin implements Plugin { } } - private static void addApiDependencies(Project project, buildType, FileCollection files) { + private static void addApiDependencies(Project project, String variantName, FileCollection files) { project.dependencies { String configuration; // `compile` dependencies are now `api` dependencies. if (project.getConfigurations().findByName("api")) { - configuration = buildType.name + "Api"; + configuration = "${variantName}Api"; } else { - configuration = buildType.name + "Compile"; + configuration = "${variantName}Compile"; } add(configuration, files) } @@ -549,7 +548,7 @@ class FlutterPlugin implements Plugin { } } // Include the snapshots and libflutter.so in `lib/`. - addApiDependencies(project, buildType, project.files { + addApiDependencies(project, variant.name, project.files { packFlutterSnapshotsAndLibsTask }) diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart index 139118738d..e9045435ab 100644 --- a/packages/flutter_tools/lib/src/android/gradle.dart +++ b/packages/flutter_tools/lib/src/android/gradle.dart @@ -587,8 +587,12 @@ File _findBundleFile(GradleProject project, BuildInfo buildInfo) { if (bundleFile.existsSync()) return bundleFile; if (buildInfo.flavor != null) { + // Android Studio Gradle plugin v3 adds the flavor to the path. For the bundle the folder name is the flavor plus the mode name. - bundleFile = project.bundleDirectory.childDirectory(buildInfo.flavor + modeName).childFile(bundleFileName); + // On linux, filenames are case sensitive. + bundleFile = project.bundleDirectory + .childDirectory(camelCase('${buildInfo.flavor}_$modeName')) + .childFile(bundleFileName); if (bundleFile.existsSync()) return bundleFile; }