// 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 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: ['--android', '--no-ios']); final Directory tempDir = Directory.systemTemp.createTempSync('flutter_module_test.'); final projectDir = Directory(path.join(tempDir.path, 'hello')); try { await inDirectory(tempDir, () async { await flutter( 'create', options: ['--org', 'io.flutter.devicelab', '--template=module', 'hello'], ); }); section('Run flutter pub get'); await inDirectory(projectDir, () async { await flutter('pub', options: ['get']); }); section('Add to existing Android app'); final 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 clean() async { section('Clean'); await inDirectory(hostAppDir, () async { await exec( gradlewExecutable, ['clean'], environment: {'JAVA_HOME': javaHome}, ); }); } if (!Platform.isWindows) { section('Make $gradlewExecutable executable'); await inDirectory(hostAppDir, () async { await exec('chmod', ['+x', gradlewExecutable]); }); } section('Build debug APKs'); section('Run app:assembleDemoDebug'); await inDirectory(hostAppDir, () async { await exec( gradlewExecutable, ['app:assembleDemoDebug'], environment: {'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([ ...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, [ // Normally, `app:processDemoDebugManifest` runs before `app:mergeDemoDebugAssets`. // In this case, we run `app:mergeDemoDebugAssets` first. 'app:mergeDemoDebugAssets', 'app:processDemoDebugManifest', 'app:assembleDemoDebug', ], environment: {'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([ ...flutterAssets, ...debugAssets, ], await getFilesInApk(demoDebugApk2)); await clean(); section('Run app:assembleDemoStaging'); await inDirectory(hostAppDir, () async { await exec( gradlewExecutable, ['app:assembleDemoStaging'], environment: {'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([ ...flutterAssets, ...debugAssets, ], await getFilesInApk(demoStagingApk)); await clean(); section('Build release APKs'); section('Run app:assembleDemoRelease'); await inDirectory(hostAppDir, () async { await exec( gradlewExecutable, ['app:assembleDemoRelease'], environment: {'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([ ...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, ['app:assembleDemoProd'], environment: {'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([ ...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, stackTrace) { print('Task exception stack trace:\n$stackTrace'); return TaskResult.failure(e.toString()); } finally { rmTree(tempDir); } }); }