| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:io'; |
| |
| import 'package:flutter_devicelab/framework/apk_utils.dart'; |
| import 'package:flutter_devicelab/framework/framework.dart'; |
| import 'package:flutter_devicelab/framework/task_result.dart'; |
| import 'package:flutter_devicelab/framework/utils.dart'; |
| import 'package:path/path.dart' as path; |
| |
| final String gradlew = Platform.isWindows ? 'gradlew.bat' : 'gradlew'; |
| final String gradlewExecutable = Platform.isWindows ? '.\\$gradlew' : './$gradlew'; |
| |
| /// Tests that the Android app containing a Flutter module can be built when |
| /// it has custom build types and flavors. |
| Future<void> main() async { |
| await task(() async { |
| |
| section('Find Java'); |
| |
| final String? javaHome = await findJavaHome(); |
| if (javaHome == null) { |
| return TaskResult.failure('Could not find Java'); |
| } |
| |
| print('\nUsing JAVA_HOME=$javaHome'); |
| |
| section('Create Flutter module project'); |
| |
| await flutter( |
| 'precache', |
| options: <String>['--android', '--no-ios'], |
| ); |
| |
| final Directory tempDir = Directory.systemTemp.createTempSync('flutter_module_test.'); |
| final Directory projectDir = Directory(path.join(tempDir.path, 'hello')); |
| try { |
| await inDirectory(tempDir, () async { |
| await flutter( |
| 'create', |
| options: <String>['--org', 'io.flutter.devicelab', '--template=module', 'hello'], |
| ); |
| }); |
| |
| section('Run flutter pub get'); |
| |
| await inDirectory(projectDir, () async { |
| await flutter( |
| 'pub', |
| options: <String>['get'], |
| ); |
| }); |
| |
| section('Add to existing Android app'); |
| |
| final Directory hostAppDir = Directory(path.join(tempDir.path, 'hello_host_app_with_custom_build')); |
| mkdir(hostAppDir); |
| recursiveCopy( |
| Directory( |
| path.join( |
| flutterDirectory.path, |
| 'dev', |
| 'integration_tests', |
| 'module_host_with_custom_build_v2_embedding', |
| ), |
| ), |
| hostAppDir, |
| ); |
| copy( |
| File(path.join(projectDir.path, '.android', gradlew)), |
| hostAppDir, |
| ); |
| copy( |
| File(path.join(projectDir.path, '.android', 'gradle', 'wrapper', 'gradle-wrapper.jar')), |
| Directory(path.join(hostAppDir.path, 'gradle', 'wrapper')), |
| ); |
| |
| Future<void> clean() async { |
| section('Clean'); |
| await inDirectory(hostAppDir, () async { |
| await exec(gradlewExecutable, |
| <String>['clean'], |
| environment: <String, String>{ |
| 'JAVA_HOME': javaHome, |
| }, |
| ); |
| }); |
| } |
| |
| if (!Platform.isWindows) { |
| section('Make $gradlewExecutable executable'); |
| await inDirectory(hostAppDir, () async { |
| await exec('chmod', <String>['+x', gradlewExecutable]); |
| }); |
| } |
| |
| section('Build debug APKs'); |
| |
| section('Run app:assembleDemoDebug'); |
| |
| await inDirectory(hostAppDir, () async { |
| await exec(gradlewExecutable, |
| <String>['app:assembleDemoDebug'], |
| environment: <String, String>{ |
| 'JAVA_HOME': javaHome, |
| }, |
| ); |
| }); |
| |
| final String demoDebugApk = path.join( |
| hostAppDir.path, |
| 'app', |
| 'build', |
| 'outputs', |
| 'apk', |
| 'demo', |
| 'debug', |
| 'app-demo-debug.apk', |
| ); |
| |
| if (!exists(File(demoDebugApk))) { |
| return TaskResult.failure('Failed to build app-demo-debug.apk'); |
| } |
| |
| section('Verify snapshots in app-demo-debug.apk'); |
| |
| checkCollectionContains<String>(<String>[ |
| ...flutterAssets, |
| ...debugAssets, |
| ], await getFilesInApk(demoDebugApk)); |
| |
| await clean(); |
| |
| // Change the order of the task and ensure that flutter_assets are in the APK. |
| // https://github.com/flutter/flutter/pull/41333 |
| section('Run app:assembleDemoDebug - Merge assets before processing manifest'); |
| |
| await inDirectory(hostAppDir, () async { |
| await exec(gradlewExecutable, |
| <String>[ |
| // Normally, `app:processDemoDebugManifest` runs before `app:mergeDemoDebugAssets`. |
| // In this case, we run `app:mergeDemoDebugAssets` first. |
| 'app:mergeDemoDebugAssets', |
| 'app:processDemoDebugManifest', |
| 'app:assembleDemoDebug', |
| ], |
| environment: <String, String>{ |
| 'JAVA_HOME': javaHome, |
| }, |
| ); |
| }); |
| |
| final String demoDebugApk2 = path.join( |
| hostAppDir.path, |
| 'app', |
| 'build', |
| 'outputs', |
| 'apk', |
| 'demo', |
| 'debug', |
| 'app-demo-debug.apk', |
| ); |
| |
| if (!exists(File(demoDebugApk2))) { |
| return TaskResult.failure('Failed to build app-demo-debug.apk'); |
| } |
| |
| section('Verify snapshots in app-demo-debug.apk'); |
| |
| checkCollectionContains<String>(<String>[ |
| ...flutterAssets, |
| ...debugAssets, |
| ], await getFilesInApk(demoDebugApk2)); |
| |
| await clean(); |
| |
| section('Run app:assembleDemoStaging'); |
| |
| await inDirectory(hostAppDir, () async { |
| await exec(gradlewExecutable, |
| <String>['app:assembleDemoStaging'], |
| environment: <String, String>{ |
| 'JAVA_HOME': javaHome, |
| }, |
| ); |
| }); |
| |
| final String demoStagingApk = path.join( |
| hostAppDir.path, |
| 'app', |
| 'build', |
| 'outputs', |
| 'apk', |
| 'demo', |
| 'staging', |
| 'app-demo-staging.apk', |
| ); |
| |
| if (!exists(File(demoStagingApk))) { |
| return TaskResult.failure('Failed to build app-demo-staging.apk'); |
| } |
| |
| section('Verify snapshots in app-demo-staging.apk'); |
| |
| checkCollectionContains<String>(<String>[ |
| ...flutterAssets, |
| ...debugAssets, |
| ], await getFilesInApk(demoStagingApk)); |
| |
| await clean(); |
| |
| section('Build release APKs'); |
| |
| section('Run app:assembleDemoRelease'); |
| |
| await inDirectory(hostAppDir, () async { |
| await exec(gradlewExecutable, |
| <String>['app:assembleDemoRelease'], |
| environment: <String, String>{ |
| 'JAVA_HOME': javaHome, |
| }, |
| ); |
| }); |
| |
| final String demoReleaseApk = path.join( |
| hostAppDir.path, |
| 'app', |
| 'build', |
| 'outputs', |
| 'apk', |
| 'demo', |
| 'release', |
| 'app-demo-release-unsigned.apk', |
| ); |
| |
| if (!exists(File(demoReleaseApk))) { |
| return TaskResult.failure('Failed to build app-demo-release-unsigned.apk'); |
| } |
| |
| section('Verify AOT ELF in app-demo-release-unsigned.apk'); |
| |
| checkCollectionContains<String>(<String>[ |
| ...flutterAssets, |
| 'lib/arm64-v8a/libflutter.so', |
| 'lib/arm64-v8a/libapp.so', |
| 'lib/armeabi-v7a/libflutter.so', |
| 'lib/armeabi-v7a/libapp.so', |
| ], await getFilesInApk(demoReleaseApk)); |
| |
| await clean(); |
| |
| section('Run app:assembleDemoProd'); |
| |
| await inDirectory(hostAppDir, () async { |
| await exec(gradlewExecutable, |
| <String>['app:assembleDemoProd'], |
| environment: <String, String>{ |
| 'JAVA_HOME': javaHome, |
| }, |
| ); |
| }); |
| |
| final String demoProdApk = path.join( |
| hostAppDir.path, |
| 'app', |
| 'build', |
| 'outputs', |
| 'apk', |
| 'demo', |
| 'prod', |
| 'app-demo-prod-unsigned.apk', |
| ); |
| |
| if (!exists(File(demoProdApk))) { |
| return TaskResult.failure('Failed to build app-demo-prod-unsigned.apk'); |
| } |
| |
| section('Verify AOT ELF in app-demo-prod-unsigned.apk'); |
| |
| checkCollectionContains<String>(<String>[ |
| ...flutterAssets, |
| 'lib/arm64-v8a/libapp.so', |
| 'lib/arm64-v8a/libflutter.so', |
| 'lib/armeabi-v7a/libapp.so', |
| 'lib/armeabi-v7a/libflutter.so', |
| ], await getFilesInApk(demoProdApk)); |
| |
| return TaskResult.success(null); |
| } on TaskResult catch (taskResult) { |
| return taskResult; |
| } catch (e) { |
| return TaskResult.failure(e.toString()); |
| } finally { |
| rmTree(tempDir); |
| } |
| }); |
| } |