Unverified Commit 97a9f2ae authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] move gradle helper methods into AndroidBuilder class body,...

[flutter_tools] move gradle helper methods into AndroidBuilder class body, split unit tests (#75931)
parent fb808b40
......@@ -6,21 +6,15 @@
import 'package:meta/meta.dart';
import '../android/gradle_errors.dart';
import '../base/context.dart';
import '../base/file_system.dart';
import '../build_info.dart';
import '../globals.dart' as globals;
import '../project.dart';
import 'gradle.dart';
/// The builder in the current context.
AndroidBuilder get androidBuilder {
return context.get<AndroidBuilder>() ?? const _AndroidBuilderImpl();
return context.get<AndroidBuilder>();
}
/// Provides the methods to build Android artifacts.
// TODO(egarciad): https://github.com/flutter/flutter/issues/43863
abstract class AndroidBuilder {
const AndroidBuilder();
/// Builds an AAR artifact.
......@@ -46,89 +40,3 @@ abstract class AndroidBuilder {
@required String target,
});
}
/// Default implementation of [AarBuilder].
class _AndroidBuilderImpl extends AndroidBuilder {
const _AndroidBuilderImpl();
/// Builds the AAR and POM files for the current Flutter module or plugin.
@override
Future<void> buildAar({
@required FlutterProject project,
@required Set<AndroidBuildInfo> androidBuildInfo,
@required String target,
@required String outputDirectoryPath,
@required String buildNumber,
}) async {
try {
Directory outputDirectory =
globals.fs.directory(outputDirectoryPath ?? project.android.buildDirectory);
if (project.isModule) {
// Module projects artifacts are located in `build/host`.
outputDirectory = outputDirectory.childDirectory('host');
}
for (final AndroidBuildInfo androidBuildInfo in androidBuildInfo) {
await buildGradleAar(
project: project,
androidBuildInfo: androidBuildInfo,
target: target,
outputDirectory: outputDirectory,
buildNumber: buildNumber,
);
}
printHowToConsumeAar(
buildModes: androidBuildInfo
.map<String>((AndroidBuildInfo androidBuildInfo) {
return androidBuildInfo.buildInfo.modeName;
}).toSet(),
androidPackage: project.manifest.androidPackage,
repoDirectory: getRepoDirectory(outputDirectory),
buildNumber: buildNumber,
logger: globals.logger,
fileSystem: globals.fs,
);
} finally {
globals.androidSdk?.reinitialize();
}
}
/// Builds the APK.
@override
Future<void> buildApk({
@required FlutterProject project,
@required AndroidBuildInfo androidBuildInfo,
@required String target,
}) async {
try {
await buildGradleApp(
project: project,
androidBuildInfo: androidBuildInfo,
target: target,
isBuildingBundle: false,
localGradleErrors: gradleErrors,
);
} finally {
globals.androidSdk?.reinitialize();
}
}
/// Builds the App Bundle.
@override
Future<void> buildAab({
@required FlutterProject project,
@required AndroidBuildInfo androidBuildInfo,
@required String target,
}) async {
try {
await buildGradleApp(
project: project,
androidBuildInfo: androidBuildInfo,
target: target,
isBuildingBundle: true,
localGradleErrors: gradleErrors,
);
} finally {
globals.androidSdk?.reinitialize();
}
}
}
......@@ -24,6 +24,7 @@ import '../flutter_manifest.dart';
import '../globals.dart' as globals;
import '../project.dart';
import '../reporting/reporting.dart';
import 'android_builder.dart';
import 'gradle_errors.dart';
import 'gradle_utils.dart';
......@@ -202,18 +203,101 @@ void createSettingsAarGradle(Directory androidDirectory) {
globals.printStatus('$successMark `$newSettingsRelativeFile` created successfully.');
}
/// Builds an app.
///
/// * [project] is typically [FlutterProject.current()].
/// * [androidBuildInfo] is the build configuration.
/// * [target] is the target dart entry point. Typically, `lib/main.dart`.
/// * If [isBuildingBundle] is `true`, then the output artifact is an `*.aab`,
/// otherwise the output artifact is an `*.apk`.
/// * The plugins are built as AARs if [shouldBuildPluginAsAar] is `true`. This isn't set by default
/// because it makes the build slower proportional to the number of plugins.
/// * [retries] is the max number of build retries in case one of the [GradleHandledError] handler
/// returns [GradleBuildStatus.retry] or [GradleBuildStatus.retryWithAarPlugins].
Future<void> buildGradleApp({
/// An implementation of the [AndroidBuilder] that delegates to gradle.
class AndroidGradleBuilder implements AndroidBuilder {
/// Builds the AAR and POM files for the current Flutter module or plugin.
@override
Future<void> buildAar({
@required FlutterProject project,
@required Set<AndroidBuildInfo> androidBuildInfo,
@required String target,
@required String outputDirectoryPath,
@required String buildNumber,
}) async {
try {
Directory outputDirectory =
globals.fs.directory(outputDirectoryPath ?? project.android.buildDirectory);
if (project.isModule) {
// Module projects artifacts are located in `build/host`.
outputDirectory = outputDirectory.childDirectory('host');
}
for (final AndroidBuildInfo androidBuildInfo in androidBuildInfo) {
await buildGradleAar(
project: project,
androidBuildInfo: androidBuildInfo,
target: target,
outputDirectory: outputDirectory,
buildNumber: buildNumber,
);
}
printHowToConsumeAar(
buildModes: androidBuildInfo
.map<String>((AndroidBuildInfo androidBuildInfo) {
return androidBuildInfo.buildInfo.modeName;
}).toSet(),
androidPackage: project.manifest.androidPackage,
repoDirectory: getRepoDirectory(outputDirectory),
buildNumber: buildNumber,
logger: globals.logger,
fileSystem: globals.fs,
);
} finally {
globals.androidSdk?.reinitialize();
}
}
/// Builds the APK.
@override
Future<void> buildApk({
@required FlutterProject project,
@required AndroidBuildInfo androidBuildInfo,
@required String target,
}) async {
try {
await buildGradleApp(
project: project,
androidBuildInfo: androidBuildInfo,
target: target,
isBuildingBundle: false,
localGradleErrors: gradleErrors,
);
} finally {
globals.androidSdk?.reinitialize();
}
}
/// Builds the App Bundle.
@override
Future<void> buildAab({
@required FlutterProject project,
@required AndroidBuildInfo androidBuildInfo,
@required String target,
}) async {
try {
await buildGradleApp(
project: project,
androidBuildInfo: androidBuildInfo,
target: target,
isBuildingBundle: true,
localGradleErrors: gradleErrors,
);
} finally {
globals.androidSdk?.reinitialize();
}
}
/// Builds an app.
///
/// * [project] is typically [FlutterProject.current()].
/// * [androidBuildInfo] is the build configuration.
/// * [target] is the target dart entry point. Typically, `lib/main.dart`.
/// * If [isBuildingBundle] is `true`, then the output artifact is an `*.aab`,
/// otherwise the output artifact is an `*.apk`.
/// * The plugins are built as AARs if [shouldBuildPluginAsAar] is `true`. This isn't set by default
/// because it makes the build slower proportional to the number of plugins.
/// * [retries] is the max number of build retries in case one of the [GradleHandledError] handler
/// returns [GradleBuildStatus.retry] or [GradleBuildStatus.retryWithAarPlugins].
Future<void> buildGradleApp({
@required FlutterProject project,
@required AndroidBuildInfo androidBuildInfo,
@required String target,
......@@ -221,7 +305,7 @@ Future<void> buildGradleApp({
@required List<GradleHandledError> localGradleErrors,
bool shouldBuildPluginAsAar = false,
int retries = 1,
}) async {
}) async {
assert(project != null);
assert(androidBuildInfo != null);
assert(target != null);
......@@ -356,7 +440,8 @@ Future<void> buildGradleApp({
return line;
}
final Stopwatch sw = Stopwatch()..start();
final Stopwatch sw = Stopwatch()
..start();
int exitCode = 1;
try {
exitCode = await globals.processUtils.stream(
......@@ -464,7 +549,9 @@ Future<void> buildGradleApp({
// Copy the first APK to app.apk, so `flutter run` can find it.
// TODO(egarciad): Handle multiple APKs.
apkFile.copySync(apkDirectory.childFile('app.apk').path);
apkFile.copySync(apkDirectory
.childFile('app.apk')
.path);
globals.printTrace('calculateSha: $apkDirectory/app.apk');
final File apkShaFile = apkDirectory.childFile('app.apk.sha1');
......@@ -481,13 +568,11 @@ Future<void> buildGradleApp({
if (buildInfo.codeSizeDirectory != null) {
await _performCodeSizeAnalysis('apk', apkFile, androidBuildInfo);
}
}
}
Future<void> _performCodeSizeAnalysis(
String kind,
Future<void> _performCodeSizeAnalysis(String kind,
File zipFile,
AndroidBuildInfo androidBuildInfo,
) async {
AndroidBuildInfo androidBuildInfo,) async {
final SizeAnalyzer sizeAnalyzer = SizeAnalyzer(
fileSystem: globals.fs,
logger: globals.logger,
......@@ -509,34 +594,38 @@ Future<void> _performCodeSizeAnalysis(
globals.fs
.directory(globals.fsUtils.homeDirPath)
.childDirectory('.flutter-devtools'), '$kind-code-size-analysis', 'json',
)..writeAsStringSync(jsonEncode(output));
)
..writeAsStringSync(jsonEncode(output));
// This message is used as a sentinel in analyze_apk_size_test.dart
globals.printStatus(
'A summary of your ${kind.toUpperCase()} analysis can be found at: ${outputFile.path}',
);
// DevTools expects a file path relative to the .flutter-devtools/ dir.
final String relativeAppSizePath = outputFile.path.split('.flutter-devtools/').last.trim();
final String relativeAppSizePath = outputFile.path
.split('.flutter-devtools/')
.last
.trim();
globals.printStatus(
'\nTo analyze your app size in Dart DevTools, run the following command:\n'
'flutter pub global activate devtools; flutter pub global run devtools '
'--appSizeBase=$relativeAppSizePath'
);
}
}
/// Builds AAR and POM files.
///
/// * [project] is typically [FlutterProject.current()].
/// * [androidBuildInfo] is the build configuration.
/// * [outputDir] is the destination of the artifacts,
/// * [buildNumber] is the build number of the output aar,
Future<void> buildGradleAar({
/// Builds AAR and POM files.
///
/// * [project] is typically [FlutterProject.current()].
/// * [androidBuildInfo] is the build configuration.
/// * [outputDir] is the destination of the artifacts,
/// * [buildNumber] is the build number of the output aar,
Future<void> buildGradleAar({
@required FlutterProject project,
@required AndroidBuildInfo androidBuildInfo,
@required String target,
@required Directory outputDirectory,
@required String buildNumber,
}) async {
}) async {
assert(project != null);
assert(target != null);
assert(androidBuildInfo != null);
......@@ -611,7 +700,7 @@ Future<void> buildGradleAar({
localEngineRepo,
getRepoDirectory(outputDirectory),
);
} on FileSystemException catch(_) {
} on FileSystemException catch (_) {
throwToolExit(
'Failed to copy the local engine ${localEngineRepo.path} repo '
'in ${outputDirectory.path}'
......@@ -627,7 +716,8 @@ Future<void> buildGradleAar({
command.add(aarTask);
final Stopwatch sw = Stopwatch()..start();
final Stopwatch sw = Stopwatch()
..start();
RunResult result;
try {
result = await globals.processUtils.run(
......@@ -662,6 +752,59 @@ Future<void> buildGradleAar({
'$successMark Built ${globals.fs.path.relative(repoDirectory.path)}.',
color: TerminalColor.green,
);
}
/// Builds the plugins as AARs.
@visibleForTesting
Future<void> buildPluginsAsAar(
FlutterProject flutterProject,
AndroidBuildInfo androidBuildInfo, {
Directory buildDirectory,
}) async {
final File flutterPluginFile = flutterProject.flutterPluginsFile;
if (!flutterPluginFile.existsSync()) {
return;
}
final List<String> plugins = flutterPluginFile.readAsStringSync().split('\n');
for (final String plugin in plugins) {
final List<String> pluginParts = plugin.split('=');
if (pluginParts.length != 2) {
continue;
}
final Directory pluginDirectory = globals.fs.directory(pluginParts.last);
assert(pluginDirectory.existsSync());
final String pluginName = pluginParts.first;
final File buildGradleFile = pluginDirectory.childDirectory('android').childFile('build.gradle');
if (!buildGradleFile.existsSync()) {
globals.printTrace("Skipping plugin $pluginName since it doesn't have a android/build.gradle file");
continue;
}
globals.logger.printStatus('Building plugin $pluginName...');
try {
await buildGradleAar(
project: FlutterProject.fromDirectory(pluginDirectory),
androidBuildInfo: AndroidBuildInfo(
BuildInfo(
BuildMode.release, // Plugins are built as release.
null, // Plugins don't define flavors.
treeShakeIcons: androidBuildInfo.buildInfo.treeShakeIcons,
packagesPath: androidBuildInfo.buildInfo.packagesPath,
),
),
target: '',
outputDirectory: buildDirectory,
buildNumber: '1.0'
);
} on ToolExit {
// Log the entire plugin entry in `.flutter-plugins` since it
// includes the plugin name and the version.
BuildEvent('gradle-plugin-aar-failure', eventError: plugin, flutterUsage: globals.flutterUsage).send();
throwToolExit('The plugin $pluginName could not be built due to the issue above.');
}
}
}
}
/// Prints how to consume the AAR from a host app.
......@@ -774,57 +917,6 @@ bool isAppUsingAndroidX(Directory androidDirectory) {
return properties.readAsStringSync().contains('android.useAndroidX=true');
}
/// Builds the plugins as AARs.
@visibleForTesting
Future<void> buildPluginsAsAar(
FlutterProject flutterProject,
AndroidBuildInfo androidBuildInfo, {
Directory buildDirectory,
}) async {
final File flutterPluginFile = flutterProject.flutterPluginsFile;
if (!flutterPluginFile.existsSync()) {
return;
}
final List<String> plugins = flutterPluginFile.readAsStringSync().split('\n');
for (final String plugin in plugins) {
final List<String> pluginParts = plugin.split('=');
if (pluginParts.length != 2) {
continue;
}
final Directory pluginDirectory = globals.fs.directory(pluginParts.last);
assert(pluginDirectory.existsSync());
final String pluginName = pluginParts.first;
final File buildGradleFile = pluginDirectory.childDirectory('android').childFile('build.gradle');
if (!buildGradleFile.existsSync()) {
globals.printTrace("Skipping plugin $pluginName since it doesn't have a android/build.gradle file");
continue;
}
globals.logger.printStatus('Building plugin $pluginName...');
try {
await buildGradleAar(
project: FlutterProject.fromDirectory(pluginDirectory),
androidBuildInfo: AndroidBuildInfo(
BuildInfo(
BuildMode.release, // Plugins are built as release.
null, // Plugins don't define flavors.
treeShakeIcons: androidBuildInfo.buildInfo.treeShakeIcons,
packagesPath: androidBuildInfo.buildInfo.packagesPath,
),
),
target: '',
outputDirectory: buildDirectory,
buildNumber: '1.0'
);
} on ToolExit {
// Log the entire plugin entry in `.flutter-plugins` since it
// includes the plugin name and the version.
BuildEvent('gradle-plugin-aar-failure', eventError: plugin, flutterUsage: globals.flutterUsage).send();
throwToolExit('The plugin $pluginName could not be built due to the issue above.');
}
}
}
/// Returns the APK files for a given [FlutterProject] and [AndroidBuildInfo].
@visibleForTesting
Iterable<String> findApkFilesModule(
......
......@@ -8,9 +8,11 @@ import 'dart:async';
import 'package:process/process.dart';
import 'android/android_builder.dart';
import 'android/android_sdk.dart';
import 'android/android_studio.dart';
import 'android/android_workflow.dart';
import 'android/gradle.dart';
import 'android/gradle_utils.dart';
import 'application_package.dart';
import 'artifacts.dart';
......@@ -77,6 +79,7 @@ Future<T> runInContext<T>(
body: runnerWrapper,
overrides: overrides,
fallbacks: <Type, Generator>{
AndroidBuilder: () => AndroidGradleBuilder(),
AndroidLicenseValidator: () => AndroidLicenseValidator(
operatingSystemUtils: globals.os,
platform: globals.platform,
......
// 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.
// @dart = 2.8
import 'package:archive/archive.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/android/android_sdk.dart';
import 'package:flutter_tools/src/android/android_studio.dart';
import 'package:flutter_tools/src/android/gradle.dart';
import 'package:flutter_tools/src/android/gradle_errors.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/context.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';
import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/mocks.dart';
void main() {
group('gradle build', () {
TestUsage testUsage;
MockAndroidSdk mockAndroidSdk;
MockAndroidStudio mockAndroidStudio;
MockLocalEngineArtifacts mockArtifacts;
MockProcessManager mockProcessManager;
FakePlatform android;
FileSystem fileSystem;
FileSystemUtils fileSystemUtils;
Cache cache;
AndroidGradleBuilder builder;
setUp(() {
testUsage = TestUsage();
fileSystem = MemoryFileSystem.test();
fileSystemUtils = MockFileSystemUtils();
mockAndroidSdk = MockAndroidSdk();
mockAndroidStudio = MockAndroidStudio();
mockArtifacts = MockLocalEngineArtifacts();
mockProcessManager = MockProcessManager();
android = fakePlatform('android');
builder = AndroidGradleBuilder();
when(mockAndroidSdk.directory).thenReturn('irrelevant');
final Directory rootDirectory = fileSystem.currentDirectory;
cache = Cache.test(
rootOverride: rootDirectory,
fileSystem: fileSystem,
);
final Directory gradleWrapperDirectory = rootDirectory
.childDirectory('bin')
.childDirectory('cache')
.childDirectory('artifacts')
.childDirectory('gradle_wrapper');
gradleWrapperDirectory.createSync(recursive: true);
gradleWrapperDirectory
.childFile('gradlew')
.writeAsStringSync('irrelevant');
gradleWrapperDirectory
.childDirectory('gradle')
.childDirectory('wrapper')
.createSync(recursive: true);
gradleWrapperDirectory
.childDirectory('gradle')
.childDirectory('wrapper')
.childFile('gradle-wrapper.jar')
.writeAsStringSync('irrelevant');
});
testUsingContext('recognizes common errors - tool exit', () async {
final Process process = createMockProcess(
exitCode: 1,
stdout: 'irrelevant\nSome gradle message\nirrelevant',
);
when(mockProcessManager.start(any,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment')))
.thenAnswer((_) => Future<Process>.value(process));
fileSystem.directory('android')
.childFile('build.gradle')
.createSync(recursive: true);
fileSystem.directory('android')
.childFile('gradle.properties')
.createSync(recursive: true);
fileSystem.directory('android')
.childDirectory('app')
.childFile('build.gradle')
..createSync(recursive: true)
..writeAsStringSync('apply from: irrelevant/flutter.gradle');
bool handlerCalled = false;
await expectLater(() async {
await builder.buildGradleApp(
project: FlutterProject.current(),
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(
BuildMode.release,
null,
treeShakeIcons: false,
),
),
target: 'lib/main.dart',
isBuildingBundle: false,
localGradleErrors: <GradleHandledError>[
GradleHandledError(
test: (String line) {
return line.contains('Some gradle message');
},
handler: ({
String line,
FlutterProject project,
bool usesAndroidX,
bool shouldBuildPluginAsAar,
}) async {
handlerCalled = true;
return GradleBuildStatus.exit;
},
eventLabel: 'random-event-label',
),
],
);
},
throwsToolExit(
message: 'Gradle task assembleRelease failed with exit code 1'
));
expect(handlerCalled, isTrue);
expect(testUsage.events, contains(
const TestUsageEvent(
'build',
'unspecified',
label: 'gradle-random-event-label-failure',
parameters: <String, String>{},
),
));
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
Cache: () => cache,
Platform: () => android,
FileSystem: () => fileSystem,
ProcessManager: () => mockProcessManager,
Usage: () => testUsage,
});
testUsingContext('recognizes common errors - retry build', () async {
when(mockProcessManager.start(any,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment')))
.thenAnswer((_) {
final Process process = createMockProcess(
exitCode: 1,
stdout: 'irrelevant\nSome gradle message\nirrelevant',
);
return Future<Process>.value(process);
});
fileSystem.directory('android')
.childFile('build.gradle')
.createSync(recursive: true);
fileSystem.directory('android')
.childFile('gradle.properties')
.createSync(recursive: true);
fileSystem.directory('android')
.childDirectory('app')
.childFile('build.gradle')
..createSync(recursive: true)
..writeAsStringSync('apply from: irrelevant/flutter.gradle');
int testFnCalled = 0;
await expectLater(() async {
await builder.buildGradleApp(
project: FlutterProject.current(),
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(
BuildMode.release,
null,
treeShakeIcons: false,
),
),
target: 'lib/main.dart',
isBuildingBundle: false,
localGradleErrors: <GradleHandledError>[
GradleHandledError(
test: (String line) {
if (line.contains('Some gradle message')) {
testFnCalled++;
return true;
}
return false;
},
handler: ({
String line,
FlutterProject project,
bool usesAndroidX,
bool shouldBuildPluginAsAar,
}) async {
return GradleBuildStatus.retry;
},
eventLabel: 'random-event-label',
),
],
);
}, throwsToolExit(
message: 'Gradle task assembleRelease failed with exit code 1'
));
expect(testFnCalled, equals(2));
expect(testUsage.events, contains(
const TestUsageEvent(
'build',
'unspecified',
label: 'gradle-random-event-label-failure',
parameters: <String, String>{},
),
));
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
Cache: () => cache,
Platform: () => android,
FileSystem: () => fileSystem,
ProcessManager: () => mockProcessManager,
Usage: () => testUsage,
});
testUsingContext('recognizes process exceptions - tool exit', () async {
when(mockProcessManager.start(any,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment')))
.thenThrow(const ProcessException('', <String>[], 'Some gradle message'));
fileSystem.directory('android')
.childFile('build.gradle')
.createSync(recursive: true);
fileSystem.directory('android')
.childFile('gradle.properties')
.createSync(recursive: true);
fileSystem.directory('android')
.childDirectory('app')
.childFile('build.gradle')
..createSync(recursive: true)
..writeAsStringSync('apply from: irrelevant/flutter.gradle');
bool handlerCalled = false;
await expectLater(() async {
await builder.buildGradleApp(
project: FlutterProject.current(),
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(
BuildMode.release,
null,
treeShakeIcons: false,
),
),
target: 'lib/main.dart',
isBuildingBundle: false,
localGradleErrors: <GradleHandledError>[
GradleHandledError(
test: (String line) {
return line.contains('Some gradle message');
},
handler: ({
String line,
FlutterProject project,
bool usesAndroidX,
bool shouldBuildPluginAsAar,
}) async {
handlerCalled = true;
return GradleBuildStatus.exit;
},
eventLabel: 'random-event-label',
),
],
);
},
throwsToolExit(
message: 'Gradle task assembleRelease failed with exit code 1'
));
expect(handlerCalled, isTrue);
expect(testUsage.events, contains(
const TestUsageEvent(
'build',
'unspecified',
label: 'gradle-random-event-label-failure',
parameters: <String, String>{},
),
));
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
Cache: () => cache,
Platform: () => android,
FileSystem: () => fileSystem,
ProcessManager: () => mockProcessManager,
Usage: () => testUsage,
});
testUsingContext('rethrows unrecognized ProcessException', () async {
when(mockProcessManager.start(any,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment')))
.thenThrow(const ProcessException('', <String>[], 'Unrecognized'));
fileSystem.directory('android')
.childFile('build.gradle')
.createSync(recursive: true);
fileSystem.directory('android')
.childFile('gradle.properties')
.createSync(recursive: true);
fileSystem.directory('android')
.childDirectory('app')
.childFile('build.gradle')
..createSync(recursive: true)
..writeAsStringSync('apply from: irrelevant/flutter.gradle');
await expectLater(() async {
await builder.buildGradleApp(
project: FlutterProject.current(),
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(
BuildMode.release,
null,
treeShakeIcons: false,
),
),
target: 'lib/main.dart',
isBuildingBundle: false,
localGradleErrors: const <GradleHandledError>[],
);
},
throwsA(isA<ProcessException>()));
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
Cache: () => cache,
Platform: () => android,
FileSystem: () => fileSystem,
ProcessManager: () => mockProcessManager,
});
testUsingContext('logs success event after a successful retry', () async {
int testFnCalled = 0;
when(mockProcessManager.start(any,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment')))
.thenAnswer((_) {
Process process;
if (testFnCalled == 0) {
process = createMockProcess(
exitCode: 1,
stdout: 'irrelevant\nSome gradle message\nirrelevant',
);
} else {
process = createMockProcess(
exitCode: 0,
stdout: 'irrelevant',
);
}
testFnCalled++;
return Future<Process>.value(process);
});
fileSystem.directory('android')
.childFile('build.gradle')
.createSync(recursive: true);
fileSystem.directory('android')
.childFile('gradle.properties')
.createSync(recursive: true);
fileSystem.directory('android')
.childDirectory('app')
.childFile('build.gradle')
..createSync(recursive: true)
..writeAsStringSync('apply from: irrelevant/flutter.gradle');
fileSystem.directory('build')
.childDirectory('app')
.childDirectory('outputs')
.childDirectory('flutter-apk')
.childFile('app-release.apk')
.createSync(recursive: true);
await builder.buildGradleApp(
project: FlutterProject.current(),
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(
BuildMode.release,
null,
treeShakeIcons: false,
),
),
target: 'lib/main.dart',
isBuildingBundle: false,
localGradleErrors: <GradleHandledError>[
GradleHandledError(
test: (String line) {
return line.contains('Some gradle message');
},
handler: ({
String line,
FlutterProject project,
bool usesAndroidX,
bool shouldBuildPluginAsAar,
}) async {
return GradleBuildStatus.retry;
},
eventLabel: 'random-event-label',
),
],
);
expect(testUsage.events, contains(
const TestUsageEvent(
'build',
'unspecified',
label: 'gradle-random-event-label-success',
parameters: <String, String>{},
),
));
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
Cache: () => cache,
FileSystem: () => fileSystem,
Platform: () => android,
ProcessManager: () => mockProcessManager,
Usage: () => testUsage,
});
testUsingContext('performs code size analysis and sends analytics', () async {
when(mockProcessManager.start(any,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment')))
.thenAnswer((_) {
return Future<Process>.value(createMockProcess(
exitCode: 0,
stdout: 'irrelevant',
));
});
fileSystem.directory('android')
.childFile('build.gradle')
.createSync(recursive: true);
fileSystem.directory('android')
.childFile('gradle.properties')
.createSync(recursive: true);
fileSystem.directory('android')
.childDirectory('app')
.childFile('build.gradle')
..createSync(recursive: true)
..writeAsStringSync('apply from: irrelevant/flutter.gradle');
final Archive archive = Archive()
..addFile(ArchiveFile('AndroidManifest.xml', 100, List<int>.filled(100, 0)))
..addFile(ArchiveFile('META-INF/CERT.RSA', 10, List<int>.filled(10, 0)))
..addFile(ArchiveFile('META-INF/CERT.SF', 10, List<int>.filled(10, 0)))
..addFile(ArchiveFile('lib/arm64-v8a/libapp.so', 50, List<int>.filled(50, 0)))
..addFile(ArchiveFile('lib/arm64-v8a/libflutter.so', 50, List<int>.filled(50, 0)));
fileSystem.directory('build')
.childDirectory('app')
.childDirectory('outputs')
.childDirectory('flutter-apk')
.childFile('app-release.apk')
..createSync(recursive: true)
..writeAsBytesSync(ZipEncoder().encode(archive));
fileSystem.file('foo/snapshot.arm64-v8a.json')
..createSync(recursive: true)
..writeAsStringSync(r'''
[
{
"l": "dart:_internal",
"c": "SubListIterable",
"n": "[Optimized] skip",
"s": 2400
}
]''');
fileSystem.file('foo/trace.arm64-v8a.json')
..createSync(recursive: true)
..writeAsStringSync('{}');
await builder.buildGradleApp(
project: FlutterProject.current(),
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(
BuildMode.release,
null,
treeShakeIcons: false,
codeSizeDirectory: 'foo',
),
targetArchs: <AndroidArch>[AndroidArch.arm64_v8a],
),
target: 'lib/main.dart',
isBuildingBundle: false,
localGradleErrors: <GradleHandledError>[],
);
expect(testUsage.events, contains(
const TestUsageEvent(
'code-size-analysis',
'apk',
),
));
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
Cache: () => cache,
FileSystem: () => fileSystem,
Platform: () => android,
ProcessManager: () => mockProcessManager,
Usage: () => testUsage,
});
testUsingContext('recognizes common errors - retry build with AAR plugins', () async {
when(mockProcessManager.start(any,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment')))
.thenAnswer((_) {
final Process process = createMockProcess(
exitCode: 1,
stdout: 'irrelevant\nSome gradle message\nirrelevant',
);
return Future<Process>.value(process);
});
fileSystem.directory('android')
.childFile('build.gradle')
.createSync(recursive: true);
fileSystem.directory('android')
.childFile('gradle.properties')
.createSync(recursive: true);
fileSystem.directory('android')
.childDirectory('app')
.childFile('build.gradle')
..createSync(recursive: true)
..writeAsStringSync('apply from: irrelevant/flutter.gradle');
int testFnCalled = 0;
bool builtPluginAsAar = false;
await expectLater(() async {
await builder.buildGradleApp(
project: FlutterProject.current(),
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(
BuildMode.release,
null,
treeShakeIcons: false,
),
),
target: 'lib/main.dart',
isBuildingBundle: false,
localGradleErrors: <GradleHandledError>[
GradleHandledError(
test: (String line) {
if (line.contains('Some gradle message')) {
testFnCalled++;
return true;
}
return false;
},
handler: ({
String line,
FlutterProject project,
bool usesAndroidX,
bool shouldBuildPluginAsAar,
}) async {
if (testFnCalled == 2) {
builtPluginAsAar = shouldBuildPluginAsAar;
}
return GradleBuildStatus.retryWithAarPlugins;
},
eventLabel: 'random-event-label',
),
],
);
}, throwsToolExit(
message: 'Gradle task assembleRelease failed with exit code 1'
));
expect(testFnCalled, equals(2));
expect(builtPluginAsAar, isTrue);
expect(testUsage.events, contains(
const TestUsageEvent(
'build',
'unspecified',
label: 'gradle-random-event-label-failure',
parameters: <String, String>{},
),
));
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
Cache: () => cache,
Platform: () => android,
FileSystem: () => fileSystem,
ProcessManager: () => mockProcessManager,
Usage: () => testUsage,
});
testUsingContext('indicates that an APK has been built successfully', () async {
fileSystem.directory('android')
.childFile('build.gradle')
.createSync(recursive: true);
fileSystem.directory('android')
.childFile('gradle.properties')
.createSync(recursive: true);
fileSystem.directory('android')
.childDirectory('app')
.childFile('build.gradle')
..createSync(recursive: true)
..writeAsStringSync('apply from: irrelevant/flutter.gradle');
fileSystem.directory('build')
.childDirectory('app')
.childDirectory('outputs')
.childDirectory('flutter-apk')
.childFile('app-release.apk')
.createSync(recursive: true);
await builder.buildGradleApp(
project: FlutterProject.current(),
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(
BuildMode.release,
null,
treeShakeIcons: false,
),
),
target: 'lib/main.dart',
isBuildingBundle: false,
localGradleErrors: const <GradleHandledError>[],
);
expect(
testLogger.statusText,
contains('Built build/app/outputs/flutter-apk/app-release.apk (0.0MB)'),
);
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
Cache: () => cache,
FileSystem: () => fileSystem,
Platform: () => android,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext("doesn't indicate how to consume an AAR when printHowToConsumeAaar is false", () async {
final File manifestFile = fileSystem.file('pubspec.yaml');
manifestFile.createSync(recursive: true);
manifestFile.writeAsStringSync('''
flutter:
module:
androidPackage: com.example.test
'''
);
fileSystem.file('.android/gradlew').createSync(recursive: true);
fileSystem.file('.android/gradle.properties')
.writeAsStringSync('irrelevant');
fileSystem.file('.android/build.gradle')
.createSync(recursive: true);
// Let any process start. Assert after.
when(mockProcessManager.run(
any,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'),
)).thenAnswer((_) async => ProcessResult(1, 0, '', ''));
fileSystem.directory('build/outputs/repo').createSync(recursive: true);
await builder.buildGradleAar(
androidBuildInfo: const AndroidBuildInfo(BuildInfo(BuildMode.release, null, treeShakeIcons: false)),
project: FlutterProject.current(),
outputDirectory: fileSystem.directory('build/'),
target: '',
buildNumber: '1.0',
);
expect(
testLogger.statusText,
contains('Built build/outputs/repo'),
);
expect(
testLogger.statusText.contains('Consuming the Module'),
isFalse,
);
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
AndroidStudio: () => mockAndroidStudio,
Cache: () => cache,
Platform: () => android,
FileSystem: () => fileSystem,
ProcessManager: () => mockProcessManager,
});
testUsingContext('gradle exit code is properly passed on', () async {
final File manifestFile = fileSystem.file('pubspec.yaml');
manifestFile.createSync(recursive: true);
manifestFile.writeAsStringSync('''
flutter:
module:
androidPackage: com.example.test
'''
);
fileSystem.file('.android/gradlew').createSync(recursive: true);
fileSystem.file('.android/gradle.properties')
.writeAsStringSync('irrelevant');
fileSystem.file('.android/build.gradle')
.createSync(recursive: true);
// Let any process start. Assert after.
when(mockProcessManager.run(
any,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'),
)).thenAnswer((_) async => ProcessResult(1, 108, '', ''));
fileSystem.directory('build/outputs/repo').createSync(recursive: true);
await expectLater(() async =>
await builder.buildGradleAar(
androidBuildInfo: const AndroidBuildInfo(BuildInfo(BuildMode.release, null, treeShakeIcons: false)),
project: FlutterProject.current(),
outputDirectory: fileSystem.directory('build/'),
target: '',
buildNumber: '1.0',
)
, throwsToolExit(exitCode: 108, message: 'Gradle task assembleAarRelease failed with exit code 108.'));
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
AndroidStudio: () => mockAndroidStudio,
Cache: () => cache,
Platform: () => android,
FileSystem: () => fileSystem,
ProcessManager: () => mockProcessManager,
});
testUsingContext('build apk uses selected local engine,the engine abi is arm', () async {
when(mockArtifacts.getArtifactPath(
Artifact.flutterFramework,
platform: TargetPlatform.android_arm,
mode: anyNamed('mode'),
environmentType: anyNamed('environmentType'),
)).thenReturn('engine');
when(mockArtifacts.engineOutPath).thenReturn(fileSystem.path.join('out', 'android_arm'));
fileSystem.file('out/android_arm/flutter_embedding_release.pom')
..createSync(recursive: true)
..writeAsStringSync('''
<?xml version="1.0" encoding="UTF-8"?>
<project>
<version>1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b</version>
<dependencies>
</dependencies>
</project>
''');
fileSystem.file('out/android_arm/armeabi_v7a_release.pom').createSync(recursive: true);
fileSystem.file('out/android_arm/armeabi_v7a_release.jar').createSync(recursive: true);
fileSystem.file('out/android_arm/armeabi_v7a_release.maven-metadata.xml').createSync(recursive: true);
fileSystem.file('out/android_arm/flutter_embedding_release.jar').createSync(recursive: true);
fileSystem.file('out/android_arm/flutter_embedding_release.pom').createSync(recursive: true);
fileSystem.file('out/android_arm/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true);
fileSystem.file('android/gradlew').createSync(recursive: true);
fileSystem.directory('android')
.childFile('gradle.properties')
.createSync(recursive: true);
fileSystem.file('android/build.gradle')
.createSync(recursive: true);
fileSystem.directory('android')
.childDirectory('app')
.childFile('build.gradle')
..createSync(recursive: true)
..writeAsStringSync('apply from: irrelevant/flutter.gradle');
// Let any process start. Assert after.
when(mockProcessManager.run(
any,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'),
)).thenAnswer((_) async => ProcessResult(1, 0, '', ''));
when(mockProcessManager.start(any,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment')))
.thenAnswer((_) {
return Future<Process>.value(
createMockProcess(
exitCode: 1,
)
);
});
await expectLater(() async {
await builder.buildGradleApp(
project: FlutterProject.current(),
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(
BuildMode.release,
null,
treeShakeIcons: false,
),
),
target: 'lib/main.dart',
isBuildingBundle: false,
localGradleErrors: const <GradleHandledError>[],
);
}, throwsToolExit());
final List<String> actualGradlewCall = verify(
mockProcessManager.start(
captureAny,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory')
),
).captured.last as List<String>;
expect(actualGradlewCall, contains('/android/gradlew'));
expect(actualGradlewCall, contains('-Plocal-engine-out=out/android_arm'));
expect(actualGradlewCall, contains('-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0'));
expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release'));
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
AndroidStudio: () => mockAndroidStudio,
Artifacts: () => mockArtifacts,
Cache: () => cache,
Platform: () => android,
FileSystem: () => fileSystem,
ProcessManager: () => mockProcessManager,
});
testUsingContext(
'build apk uses selected local engine,the engine abi is arm64', () async {
when(mockArtifacts.getArtifactPath(
Artifact.flutterFramework,
platform: anyNamed('platform'),
mode: anyNamed('mode'),
environmentType: anyNamed('environmentType'),
)).thenReturn('engine');
when(mockArtifacts.engineOutPath).thenReturn(fileSystem.path.join('out', 'android_arm64'));
fileSystem.file('out/android_arm64/flutter_embedding_release.pom')
..createSync(recursive: true)
..writeAsStringSync('''
<?xml version="1.0" encoding="UTF-8"?>
<project>
<version>1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b</version>
<dependencies>
</dependencies>
</project>
''');
fileSystem.file('out/android_arm64/arm64_v8a_release.pom').createSync(recursive: true);
fileSystem.file('out/android_arm64/arm64_v8a_release.jar').createSync(recursive: true);
fileSystem.file('out/android_arm64/arm64_v8a_release.maven-metadata.xml').createSync(recursive: true);
fileSystem.file('out/android_arm64/flutter_embedding_release.jar').createSync(recursive: true);
fileSystem.file('out/android_arm64/flutter_embedding_release.pom').createSync(recursive: true);
fileSystem.file('out/android_arm64/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true);
fileSystem.file('android/gradlew').createSync(recursive: true);
fileSystem.directory('android')
.childFile('gradle.properties')
.createSync(recursive: true);
fileSystem.file('android/build.gradle')
.createSync(recursive: true);
fileSystem.directory('android')
.childDirectory('app')
.childFile('build.gradle')
..createSync(recursive: true)
..writeAsStringSync('apply from: irrelevant/flutter.gradle');
// Let any process start. Assert after.
when(mockProcessManager.run(
any,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'),
)).thenAnswer((_) async => ProcessResult(1, 0, '', ''));
when(mockProcessManager.start(any,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment')))
.thenAnswer((_) {
return Future<Process>.value(
createMockProcess(
exitCode: 1,
)
);
});
await expectLater(() async {
await builder.buildGradleApp(
project: FlutterProject.current(),
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(
BuildMode.release,
null,
treeShakeIcons: false,
),
),
target: 'lib/main.dart',
isBuildingBundle: false,
localGradleErrors: const <GradleHandledError>[],
);
}, throwsToolExit());
final List<String> actualGradlewCall = verify(
mockProcessManager.start(
captureAny,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory')
),
).captured.last as List<String>;
expect(actualGradlewCall, contains('/android/gradlew'));
expect(actualGradlewCall, contains('-Plocal-engine-out=out/android_arm64'));
expect(actualGradlewCall, contains('-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0'));
expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release'));
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
AndroidStudio: () => mockAndroidStudio,
Artifacts: () => mockArtifacts,
Cache: () => cache,
Platform: () => android,
FileSystem: () => fileSystem,
ProcessManager: () => mockProcessManager,
});
testUsingContext(
'build apk uses selected local engine,the engine abi is x86', () async {
when(mockArtifacts.getArtifactPath(
Artifact.flutterFramework,
platform: anyNamed('platform'),
mode: anyNamed('mode'),
environmentType: anyNamed('environmentType'),
)).thenReturn('engine');
when(mockArtifacts.engineOutPath).thenReturn(fileSystem.path.join('out', 'android_x86'));
fileSystem.file('out/android_x86/flutter_embedding_release.pom')
..createSync(recursive: true)
..writeAsStringSync('''
<?xml version="1.0" encoding="UTF-8"?>
<project>
<version>1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b</version>
<dependencies>
</dependencies>
</project>
''');
fileSystem.file('out/android_x86/x86_release.pom').createSync(recursive: true);
fileSystem.file('out/android_x86/x86_release.jar').createSync(recursive: true);
fileSystem.file('out/android_x86/x86_release.maven-metadata.xml').createSync(recursive: true);
fileSystem.file('out/android_x86/flutter_embedding_release.jar').createSync(recursive: true);
fileSystem.file('out/android_x86/flutter_embedding_release.pom').createSync(recursive: true);
fileSystem.file('out/android_x86/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true);
fileSystem.file('android/gradlew').createSync(recursive: true);
fileSystem.directory('android')
.childFile('gradle.properties')
.createSync(recursive: true);
fileSystem.file('android/build.gradle')
.createSync(recursive: true);
fileSystem.directory('android')
.childDirectory('app')
.childFile('build.gradle')
..createSync(recursive: true)
..writeAsStringSync('apply from: irrelevant/flutter.gradle');
// Let any process start. Assert after.
when(mockProcessManager.run(
any,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'),
)).thenAnswer((_) async => ProcessResult(1, 0, '', ''));
when(mockProcessManager.start(any,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment')))
.thenAnswer((_) {
return Future<Process>.value(
createMockProcess(
exitCode: 1,
)
);
});
await expectLater(() async {
await builder.buildGradleApp(
project: FlutterProject.current(),
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(
BuildMode.release,
null,
treeShakeIcons: false,
),
),
target: 'lib/main.dart',
isBuildingBundle: false,
localGradleErrors: const <GradleHandledError>[],
);
}, throwsToolExit());
final List<String> actualGradlewCall = verify(
mockProcessManager.start(
captureAny,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory')
),
).captured.last as List<String>;
expect(actualGradlewCall, contains('/android/gradlew'));
expect(actualGradlewCall, contains('-Plocal-engine-out=out/android_x86'));
expect(actualGradlewCall, contains('-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0'));
expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release'));
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
AndroidStudio: () => mockAndroidStudio,
Artifacts: () => mockArtifacts,
Cache: () => cache,
Platform: () => android,
FileSystem: () => fileSystem,
ProcessManager: () => mockProcessManager,
});
testUsingContext(
'build apk uses selected local engine,the engine abi is x64', () async {
when(mockArtifacts.getArtifactPath(
Artifact.flutterFramework,
platform: anyNamed('platform'),
mode: anyNamed('mode'),
environmentType: anyNamed('environmentType'),
)).thenReturn('engine');
when(mockArtifacts.engineOutPath).thenReturn(fileSystem.path.join('out', 'android_x64'));
fileSystem.file('out/android_x64/flutter_embedding_release.pom')
..createSync(recursive: true)
..writeAsStringSync('''
<?xml version="1.0" encoding="UTF-8"?>
<project>
<version>1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b</version>
<dependencies>
</dependencies>
</project>
''');
fileSystem.file('out/android_x64/x86_64_release.pom').createSync(recursive: true);
fileSystem.file('out/android_x64/x86_64_release.jar').createSync(recursive: true);
fileSystem.file('out/android_x64/x86_64_release.maven-metadata.xml').createSync(recursive: true);
fileSystem.file('out/android_x64/flutter_embedding_release.jar').createSync(recursive: true);
fileSystem.file('out/android_x64/flutter_embedding_release.pom').createSync(recursive: true);
fileSystem.file('out/android_x64/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true);
fileSystem.file('android/gradlew').createSync(recursive: true);
fileSystem.directory('android')
.childFile('gradle.properties')
.createSync(recursive: true);
fileSystem.file('android/build.gradle')
.createSync(recursive: true);
fileSystem.directory('android')
.childDirectory('app')
.childFile('build.gradle')
..createSync(recursive: true)
..writeAsStringSync('apply from: irrelevant/flutter.gradle');
// Let any process start. Assert after.
when(mockProcessManager.run(
any,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'),
)).thenAnswer((_) async => ProcessResult(1, 0, '', ''));
when(mockProcessManager.start(any,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment')))
.thenAnswer((_) {
return Future<Process>.value(
createMockProcess(
exitCode: 1,
)
);
});
await expectLater(() async {
await builder.buildGradleApp(
project: FlutterProject.current(),
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(
BuildMode.release,
null,
treeShakeIcons: false,
),
),
target: 'lib/main.dart',
isBuildingBundle: false,
localGradleErrors: const <GradleHandledError>[],
);
}, throwsToolExit());
final List<String> actualGradlewCall = verify(
mockProcessManager.start(
captureAny,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory')
),
).captured.last as List<String>;
expect(actualGradlewCall, contains('/android/gradlew'));
expect(actualGradlewCall, contains('-Plocal-engine-out=out/android_x64'));
expect(actualGradlewCall, contains('-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0'));
expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release'));
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
AndroidStudio: () => mockAndroidStudio,
Artifacts: () => mockArtifacts,
Cache: () => cache,
Platform: () => android,
FileSystem: () => fileSystem,
ProcessManager: () => mockProcessManager,
});
testUsingContext('honors --no-android-gradle-daemon setting', () async {
(globals.processManager as FakeProcessManager).addCommand(
const FakeCommand(command: <String>[
'/android/gradlew',
'-q',
'--no-daemon',
'-Ptarget-platform=android-arm,android-arm64,android-x64',
'-Ptarget=lib/main.dart',
'-Pdart-obfuscation=false',
'-Ptrack-widget-creation=false',
'-Ptree-shake-icons=false',
'assembleRelease'
],
));
fileSystem.file('android/gradlew').createSync(recursive: true);
fileSystem.directory('android')
.childFile('gradle.properties')
.createSync(recursive: true);
fileSystem.file('android/build.gradle')
.createSync(recursive: true);
fileSystem.directory('android')
.childDirectory('app')
.childFile('build.gradle')
..createSync(recursive: true)
..writeAsStringSync('apply from: irrelevant/flutter.gradle');
await expectLater(() async {
await builder.buildGradleApp(
project: FlutterProject.current(),
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(
BuildMode.release,
null,
treeShakeIcons: false,
androidGradleDaemon: false,
),
),
target: 'lib/main.dart',
isBuildingBundle: false,
localGradleErrors: const <GradleHandledError>[],
);
}, throwsToolExit());
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
AndroidStudio: () => mockAndroidStudio,
Artifacts: () => Artifacts.test(),
Cache: () => cache,
Platform: () => android,
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.list(<FakeCommand>[]),
});
testUsingContext('build aar uses selected local engine,the engine abi is arm', () async {
when(mockArtifacts.getArtifactPath(
Artifact.flutterFramework,
platform: TargetPlatform.android_arm,
mode: anyNamed('mode'),
environmentType: anyNamed('environmentType'),
)).thenReturn('engine');
when(mockArtifacts.engineOutPath).thenReturn(fileSystem.path.join('out', 'android_arm'));
fileSystem.file('out/android_arm/flutter_embedding_release.pom')
..createSync(recursive: true)
..writeAsStringSync('''
<?xml version="1.0" encoding="UTF-8"?>
<project>
<version>1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b</version>
<dependencies>
</dependencies>
</project>
''');
fileSystem.file('out/android_arm/armeabi_v7a_release.pom').createSync(recursive: true);
fileSystem.file('out/android_arm/armeabi_v7a_release.jar').createSync(recursive: true);
fileSystem.file('out/android_arm/armeabi_v7a_release.maven-metadata.xml').createSync(recursive: true);
fileSystem.file('out/android_arm/flutter_embedding_release.jar').createSync(recursive: true);
fileSystem.file('out/android_arm/flutter_embedding_release.pom').createSync(recursive: true);
fileSystem.file('out/android_arm/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true);
final File manifestFile = fileSystem.file('pubspec.yaml');
manifestFile.createSync(recursive: true);
manifestFile.writeAsStringSync('''
flutter:
module:
androidPackage: com.example.test
'''
);
fileSystem.directory('.android/gradle')
.createSync(recursive: true);
fileSystem.directory('.android/gradle/wrapper')
.createSync(recursive: true);
fileSystem.file('.android/gradlew').createSync(recursive: true);
fileSystem.file('.android/gradle.properties')
.writeAsStringSync('irrelevant');
fileSystem.file('.android/build.gradle')
.createSync(recursive: true);
// Let any process start. Assert after.
when(mockProcessManager.run(
any,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'),
)).thenAnswer((_) async => ProcessResult(1, 0, '', ''));
fileSystem.directory('build/outputs/repo').createSync(recursive: true);
when(fileSystemUtils.copyDirectorySync(any, any)).thenReturn(null);
await builder.buildGradleAar(
androidBuildInfo: const AndroidBuildInfo(BuildInfo(BuildMode.release, null, treeShakeIcons: false)),
project: FlutterProject.current(),
outputDirectory: fileSystem.directory('build/'),
target: '',
buildNumber: '2.0',
);
final List<String> actualGradlewCall = verify(
mockProcessManager.run(
captureAny,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'),
),
).captured.last as List<String>;
expect(actualGradlewCall, contains('/.android/gradlew'));
expect(actualGradlewCall, contains('-Plocal-engine-out=out/android_arm'));
expect(actualGradlewCall, contains('-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0'));
expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release'));
expect(actualGradlewCall, contains('-PbuildNumber=2.0'));
// Verify the local engine repo is copied into the generated Maven repo.
final List<dynamic> copyDirectoryArguments = verify(
fileSystemUtils.copyDirectorySync(captureAny, captureAny)
).captured;
expect(copyDirectoryArguments.length, 2);
expect((copyDirectoryArguments.first as Directory).path, '/.tmp_rand0/flutter_tool_local_engine_repo.rand0');
expect((copyDirectoryArguments.last as Directory).path, 'build/outputs/repo');
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
AndroidStudio: () => mockAndroidStudio,
Artifacts: () => mockArtifacts,
Cache: () => cache,
Platform: () => android,
FileSystem: () => fileSystem,
FileSystemUtils: () => fileSystemUtils,
ProcessManager: () => mockProcessManager,
});
testUsingContext(
'build aar uses selected local engine,the engine abi is arm64', () async {
when(mockArtifacts.getArtifactPath(
Artifact.flutterFramework,
platform: anyNamed('platform'),
mode: anyNamed('mode'),
environmentType: anyNamed('environmentType'),
)).thenReturn('engine');
when(mockArtifacts.engineOutPath).thenReturn(fileSystem.path.join('out', 'android_arm64'));
fileSystem.file('out/android_arm64/flutter_embedding_release.pom')
..createSync(recursive: true)
..writeAsStringSync('''
<?xml version="1.0" encoding="UTF-8"?>
<project>
<version>1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b</version>
<dependencies>
</dependencies>
</project>
''');
fileSystem.file('out/android_arm64/arm64_v8a_release.pom').createSync(recursive: true);
fileSystem.file('out/android_arm64/arm64_v8a_release.jar').createSync(recursive: true);
fileSystem.file('out/android_arm64/arm64_v8a_release.maven-metadata.xml').createSync(recursive: true);
fileSystem.file('out/android_arm64/flutter_embedding_release.jar').createSync(recursive: true);
fileSystem.file('out/android_arm64/flutter_embedding_release.pom').createSync(recursive: true);
fileSystem.file('out/android_arm64/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true);
final File manifestFile = fileSystem.file('pubspec.yaml');
manifestFile.createSync(recursive: true);
manifestFile.writeAsStringSync('''
flutter:
module:
androidPackage: com.example.test
'''
);
fileSystem.directory('.android/gradle')
.createSync(recursive: true);
fileSystem.directory('.android/gradle/wrapper')
.createSync(recursive: true);
fileSystem.file('.android/gradlew').createSync(recursive: true);
fileSystem.file('.android/gradle.properties')
.writeAsStringSync('irrelevant');
fileSystem.file('.android/build.gradle')
.createSync(recursive: true);
// Let any process start. Assert after.
when(mockProcessManager.run(
any,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'),
)).thenAnswer((_) async => ProcessResult(1, 0, '', ''));
fileSystem.directory('build/outputs/repo').createSync(recursive: true);
when(fileSystemUtils.copyDirectorySync(any, any)).thenReturn(null);
await builder.buildGradleAar(
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(BuildMode.release, null, treeShakeIcons: false)),
project: FlutterProject.current(),
outputDirectory: fileSystem.directory('build/'),
target: '',
buildNumber: '2.0',
);
final List<String> actualGradlewCall = verify(
mockProcessManager.run(
captureAny,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'),
),
).captured.last as List<String>;
expect(actualGradlewCall, contains('/.android/gradlew'));
expect(actualGradlewCall, contains('-Plocal-engine-out=out/android_arm64'));
expect(actualGradlewCall, contains('-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0'));
expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release'));
expect(actualGradlewCall, contains('-PbuildNumber=2.0'));
// Verify the local engine repo is copied into the generated Maven repo.
final List<dynamic> copyDirectoryArguments = verify(
fileSystemUtils.copyDirectorySync(captureAny, captureAny)
).captured;
expect(copyDirectoryArguments.length, 2);
expect((copyDirectoryArguments.first as Directory).path, '/.tmp_rand0/flutter_tool_local_engine_repo.rand0');
expect((copyDirectoryArguments.last as Directory).path, 'build/outputs/repo');
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
AndroidStudio: () => mockAndroidStudio,
Artifacts: () => mockArtifacts,
Cache: () => cache,
Platform: () => android,
FileSystem: () => fileSystem,
FileSystemUtils: () => fileSystemUtils,
ProcessManager: () => mockProcessManager,
});
testUsingContext(
'build aar uses selected local engine,the engine abi is x86', () async {
when(mockArtifacts.getArtifactPath(
Artifact.flutterFramework,
platform: anyNamed('platform'),
mode: anyNamed('mode'),
environmentType: anyNamed('environmentType'),
)).thenReturn('engine');
when(mockArtifacts.engineOutPath).thenReturn(fileSystem.path.join('out', 'android_x86'));
fileSystem.file('out/android_x86/flutter_embedding_release.pom')
..createSync(recursive: true)
..writeAsStringSync('''
<?xml version="1.0" encoding="UTF-8"?>
<project>
<version>1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b</version>
<dependencies>
</dependencies>
</project>
''');
fileSystem.file('out/android_x86/x86_release.pom').createSync(recursive: true);
fileSystem.file('out/android_x86/x86_release.jar').createSync(recursive: true);
fileSystem.file('out/android_x86/x86_release.maven-metadata.xml').createSync(recursive: true);
fileSystem.file('out/android_x86/flutter_embedding_release.jar').createSync(recursive: true);
fileSystem.file('out/android_x86/flutter_embedding_release.pom').createSync(recursive: true);
fileSystem.file('out/android_x86/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true);
final File manifestFile = fileSystem.file('pubspec.yaml');
manifestFile.createSync(recursive: true);
manifestFile.writeAsStringSync('''
flutter:
module:
androidPackage: com.example.test
'''
);
fileSystem.directory('.android/gradle')
.createSync(recursive: true);
fileSystem.directory('.android/gradle/wrapper')
.createSync(recursive: true);
fileSystem.file('.android/gradlew').createSync(recursive: true);
fileSystem.file('.android/gradle.properties')
.writeAsStringSync('irrelevant');
fileSystem.file('.android/build.gradle')
.createSync(recursive: true);
// Let any process start. Assert after.
when(mockProcessManager.run(
any,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'),
)).thenAnswer((_) async => ProcessResult(1, 0, '', ''));
fileSystem.directory('build/outputs/repo').createSync(recursive: true);
when(fileSystemUtils.copyDirectorySync(any, any)).thenReturn(null);
await builder.buildGradleAar(
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(BuildMode.release, null, treeShakeIcons: false)),
project: FlutterProject.current(),
outputDirectory: fileSystem.directory('build/'),
target: '',
buildNumber: '2.0',
);
final List<String> actualGradlewCall = verify(
mockProcessManager.run(
captureAny,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'),
),
).captured.last as List<String>;
expect(actualGradlewCall, contains('/.android/gradlew'));
expect(actualGradlewCall, contains('-Plocal-engine-out=out/android_x86'));
expect(actualGradlewCall, contains('-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0'));
expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release'));
expect(actualGradlewCall, contains('-PbuildNumber=2.0'));
// Verify the local engine repo is copied into the generated Maven repo.
final List<dynamic> copyDirectoryArguments = verify(
fileSystemUtils.copyDirectorySync(captureAny, captureAny)
).captured;
expect(copyDirectoryArguments.length, 2);
expect((copyDirectoryArguments.first as Directory).path, '/.tmp_rand0/flutter_tool_local_engine_repo.rand0');
expect((copyDirectoryArguments.last as Directory).path, 'build/outputs/repo');
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
AndroidStudio: () => mockAndroidStudio,
Artifacts: () => mockArtifacts,
Cache: () => cache,
Platform: () => android,
FileSystem: () => fileSystem,
FileSystemUtils: () => fileSystemUtils,
ProcessManager: () => mockProcessManager,
});
testUsingContext(
'build aar uses selected local engine,the engine abi is x64', () async {
when(mockArtifacts.getArtifactPath(
Artifact.flutterFramework,
platform: anyNamed('platform'),
mode: anyNamed('mode'),
environmentType: anyNamed('environmentType'),
)).thenReturn('engine');
when(mockArtifacts.engineOutPath).thenReturn(fileSystem.path.join('out', 'android_x64'));
fileSystem.file('out/android_x64/flutter_embedding_release.pom')
..createSync(recursive: true)
..writeAsStringSync('''
<?xml version="1.0" encoding="UTF-8"?>
<project>
<version>1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b</version>
<dependencies>
</dependencies>
</project>
''');
fileSystem.file('out/android_x64/x86_64_release.pom').createSync(recursive: true);
fileSystem.file('out/android_x64/x86_64_release.jar').createSync(recursive: true);
fileSystem.file('out/android_x64/x86_64_release.maven-metadata.xml').createSync(recursive: true);
fileSystem.file('out/android_x64/flutter_embedding_release.jar').createSync(recursive: true);
fileSystem.file('out/android_x64/flutter_embedding_release.pom').createSync(recursive: true);
fileSystem.file('out/android_x64/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true);
final File manifestFile = fileSystem.file('pubspec.yaml');
manifestFile.createSync(recursive: true);
manifestFile.writeAsStringSync('''
flutter:
module:
androidPackage: com.example.test
'''
);
fileSystem.directory('.android/gradle')
.createSync(recursive: true);
fileSystem.directory('.android/gradle/wrapper')
.createSync(recursive: true);
fileSystem.file('.android/gradlew').createSync(recursive: true);
fileSystem.file('.android/gradle.properties')
.writeAsStringSync('irrelevant');
fileSystem.file('.android/build.gradle')
.createSync(recursive: true);
// Let any process start. Assert after.
when(mockProcessManager.run(
any,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'),
)).thenAnswer((_) async => ProcessResult(1, 0, '', ''));
fileSystem.directory('build/outputs/repo').createSync(recursive: true);
when(fileSystemUtils.copyDirectorySync(any, any)).thenReturn(null);
await builder.buildGradleAar(
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(BuildMode.release, null, treeShakeIcons: false)),
project: FlutterProject.current(),
outputDirectory: fileSystem.directory('build/'),
target: '',
buildNumber: '2.0',
);
final List<String> actualGradlewCall = verify(
mockProcessManager.run(
captureAny,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'),
),
).captured.last as List<String>;
expect(actualGradlewCall, contains('/.android/gradlew'));
expect(actualGradlewCall, contains('-Plocal-engine-out=out/android_x64'));
expect(actualGradlewCall, contains('-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0'));
expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release'));
expect(actualGradlewCall, contains('-PbuildNumber=2.0'));
// Verify the local engine repo is copied into the generated Maven repo.
final List<dynamic> copyDirectoryArguments = verify(
fileSystemUtils.copyDirectorySync(captureAny, captureAny)
).captured;
expect(copyDirectoryArguments.length, 2);
expect((copyDirectoryArguments.first as Directory).path, '/.tmp_rand0/flutter_tool_local_engine_repo.rand0');
expect((copyDirectoryArguments.last as Directory).path, 'build/outputs/repo');
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
AndroidStudio: () => mockAndroidStudio,
Artifacts: () => mockArtifacts,
Cache: () => cache,
Platform: () => android,
FileSystem: () => fileSystem,
FileSystemUtils: () => fileSystemUtils,
ProcessManager: () => mockProcessManager,
});
});
}
FakePlatform fakePlatform(String name) {
return FakePlatform(
environment: <String, String>{'HOME': '/path/to/home'},
operatingSystem: name,
stdoutSupportsAnsi: false,
);
}
class MockProcessManager extends Mock implements ProcessManager {}
class MockAndroidSdk extends Mock implements AndroidSdk {}
class MockAndroidStudio extends Mock implements AndroidStudio {}
class MockFileSystemUtils extends Mock implements FileSystemUtils {}
class MockLocalEngineArtifacts extends Mock implements LocalEngineArtifacts {}
......@@ -4,10 +4,8 @@
// @dart = 2.8
import 'package:archive/archive.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/android/android_sdk.dart';
import 'package:flutter_tools/src/android/android_studio.dart';
import 'package:flutter_tools/src/android/gradle.dart';
import 'package:flutter_tools/src/android/gradle_errors.dart';
import 'package:flutter_tools/src/android/gradle_utils.dart';
......@@ -15,7 +13,6 @@ import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/context.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/terminal.dart';
......@@ -29,7 +26,6 @@ import 'package:process/process.dart';
import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/mocks.dart';
void main() {
Cache.flutterRoot = getFlutterRoot();
......@@ -837,12 +833,14 @@ flutter:
FileSystem fs;
FakeProcessManager fakeProcessManager;
MockAndroidSdk mockAndroidSdk;
AndroidGradleBuilder builder;
setUp(() {
fs = MemoryFileSystem.test();
fakeProcessManager = FakeProcessManager.list(<FakeCommand>[]);
mockAndroidSdk = MockAndroidSdk();
when(mockAndroidSdk.directory).thenReturn('irrelevant');
builder = AndroidGradleBuilder();
});
testUsingContext('calls gradle', () async {
......@@ -942,7 +940,7 @@ plugin2=${plugin2.path}
workingDirectory: plugin2.childDirectory('android').path,
));
await buildPluginsAsAar(
await builder.buildPluginsAsAar(
FlutterProject.fromPath(androidDirectory.path),
const AndroidBuildInfo(BuildInfo(
BuildMode.release,
......@@ -995,7 +993,7 @@ plugin1=${plugin1.path}
.childDirectory('repo')
.createSync(recursive: true);
await buildPluginsAsAar(
await builder.buildPluginsAsAar(
FlutterProject.fromPath(androidDirectory.path),
const AndroidBuildInfo(BuildInfo.release),
buildDirectory: buildDirectory,
......@@ -1009,1612 +1007,6 @@ plugin1=${plugin1.path}
});
});
group('gradle build', () {
TestUsage testUsage;
MockAndroidSdk mockAndroidSdk;
MockAndroidStudio mockAndroidStudio;
MockLocalEngineArtifacts mockArtifacts;
MockProcessManager mockProcessManager;
FakePlatform android;
FileSystem fileSystem;
FileSystemUtils fileSystemUtils;
Cache cache;
setUp(() {
testUsage = TestUsage();
fileSystem = MemoryFileSystem.test();
fileSystemUtils = MockFileSystemUtils();
mockAndroidSdk = MockAndroidSdk();
mockAndroidStudio = MockAndroidStudio();
mockArtifacts = MockLocalEngineArtifacts();
mockProcessManager = MockProcessManager();
android = fakePlatform('android');
when(mockAndroidSdk.directory).thenReturn('irrelevant');
final Directory rootDirectory = fileSystem.currentDirectory;
cache = Cache.test(
rootOverride: rootDirectory,
fileSystem: fileSystem,
);
final Directory gradleWrapperDirectory = rootDirectory
.childDirectory('bin')
.childDirectory('cache')
.childDirectory('artifacts')
.childDirectory('gradle_wrapper');
gradleWrapperDirectory.createSync(recursive: true);
gradleWrapperDirectory
.childFile('gradlew')
.writeAsStringSync('irrelevant');
gradleWrapperDirectory
.childDirectory('gradle')
.childDirectory('wrapper')
.createSync(recursive: true);
gradleWrapperDirectory
.childDirectory('gradle')
.childDirectory('wrapper')
.childFile('gradle-wrapper.jar')
.writeAsStringSync('irrelevant');
});
testUsingContext('recognizes common errors - tool exit', () async {
final Process process = createMockProcess(
exitCode: 1,
stdout: 'irrelevant\nSome gradle message\nirrelevant',
);
when(mockProcessManager.start(any,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment')))
.thenAnswer((_) => Future<Process>.value(process));
fileSystem.directory('android')
.childFile('build.gradle')
.createSync(recursive: true);
fileSystem.directory('android')
.childFile('gradle.properties')
.createSync(recursive: true);
fileSystem.directory('android')
.childDirectory('app')
.childFile('build.gradle')
..createSync(recursive: true)
..writeAsStringSync('apply from: irrelevant/flutter.gradle');
bool handlerCalled = false;
await expectLater(() async {
await buildGradleApp(
project: FlutterProject.current(),
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(
BuildMode.release,
null,
treeShakeIcons: false,
),
),
target: 'lib/main.dart',
isBuildingBundle: false,
localGradleErrors: <GradleHandledError>[
GradleHandledError(
test: (String line) {
return line.contains('Some gradle message');
},
handler: ({
String line,
FlutterProject project,
bool usesAndroidX,
bool shouldBuildPluginAsAar,
}) async {
handlerCalled = true;
return GradleBuildStatus.exit;
},
eventLabel: 'random-event-label',
),
],
);
},
throwsToolExit(
message: 'Gradle task assembleRelease failed with exit code 1'
));
expect(handlerCalled, isTrue);
expect(testUsage.events, contains(
const TestUsageEvent(
'build',
'unspecified',
label: 'gradle-random-event-label-failure',
parameters: <String, String>{},
),
));
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
Cache: () => cache,
Platform: () => android,
FileSystem: () => fileSystem,
ProcessManager: () => mockProcessManager,
Usage: () => testUsage,
});
testUsingContext('recognizes common errors - retry build', () async {
when(mockProcessManager.start(any,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment')))
.thenAnswer((_) {
final Process process = createMockProcess(
exitCode: 1,
stdout: 'irrelevant\nSome gradle message\nirrelevant',
);
return Future<Process>.value(process);
});
fileSystem.directory('android')
.childFile('build.gradle')
.createSync(recursive: true);
fileSystem.directory('android')
.childFile('gradle.properties')
.createSync(recursive: true);
fileSystem.directory('android')
.childDirectory('app')
.childFile('build.gradle')
..createSync(recursive: true)
..writeAsStringSync('apply from: irrelevant/flutter.gradle');
int testFnCalled = 0;
await expectLater(() async {
await buildGradleApp(
project: FlutterProject.current(),
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(
BuildMode.release,
null,
treeShakeIcons: false,
),
),
target: 'lib/main.dart',
isBuildingBundle: false,
localGradleErrors: <GradleHandledError>[
GradleHandledError(
test: (String line) {
if (line.contains('Some gradle message')) {
testFnCalled++;
return true;
}
return false;
},
handler: ({
String line,
FlutterProject project,
bool usesAndroidX,
bool shouldBuildPluginAsAar,
}) async {
return GradleBuildStatus.retry;
},
eventLabel: 'random-event-label',
),
],
);
}, throwsToolExit(
message: 'Gradle task assembleRelease failed with exit code 1'
));
expect(testFnCalled, equals(2));
expect(testUsage.events, contains(
const TestUsageEvent(
'build',
'unspecified',
label: 'gradle-random-event-label-failure',
parameters: <String, String>{},
),
));
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
Cache: () => cache,
Platform: () => android,
FileSystem: () => fileSystem,
ProcessManager: () => mockProcessManager,
Usage: () => testUsage,
});
testUsingContext('recognizes process exceptions - tool exit', () async {
when(mockProcessManager.start(any,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment')))
.thenThrow(const ProcessException('', <String>[], 'Some gradle message'));
fileSystem.directory('android')
.childFile('build.gradle')
.createSync(recursive: true);
fileSystem.directory('android')
.childFile('gradle.properties')
.createSync(recursive: true);
fileSystem.directory('android')
.childDirectory('app')
.childFile('build.gradle')
..createSync(recursive: true)
..writeAsStringSync('apply from: irrelevant/flutter.gradle');
bool handlerCalled = false;
await expectLater(() async {
await buildGradleApp(
project: FlutterProject.current(),
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(
BuildMode.release,
null,
treeShakeIcons: false,
),
),
target: 'lib/main.dart',
isBuildingBundle: false,
localGradleErrors: <GradleHandledError>[
GradleHandledError(
test: (String line) {
return line.contains('Some gradle message');
},
handler: ({
String line,
FlutterProject project,
bool usesAndroidX,
bool shouldBuildPluginAsAar,
}) async {
handlerCalled = true;
return GradleBuildStatus.exit;
},
eventLabel: 'random-event-label',
),
],
);
},
throwsToolExit(
message: 'Gradle task assembleRelease failed with exit code 1'
));
expect(handlerCalled, isTrue);
expect(testUsage.events, contains(
const TestUsageEvent(
'build',
'unspecified',
label: 'gradle-random-event-label-failure',
parameters: <String, String>{},
),
));
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
Cache: () => cache,
Platform: () => android,
FileSystem: () => fileSystem,
ProcessManager: () => mockProcessManager,
Usage: () => testUsage,
});
testUsingContext('rethrows unrecognized ProcessException', () async {
when(mockProcessManager.start(any,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment')))
.thenThrow(const ProcessException('', <String>[], 'Unrecognized'));
fileSystem.directory('android')
.childFile('build.gradle')
.createSync(recursive: true);
fileSystem.directory('android')
.childFile('gradle.properties')
.createSync(recursive: true);
fileSystem.directory('android')
.childDirectory('app')
.childFile('build.gradle')
..createSync(recursive: true)
..writeAsStringSync('apply from: irrelevant/flutter.gradle');
await expectLater(() async {
await buildGradleApp(
project: FlutterProject.current(),
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(
BuildMode.release,
null,
treeShakeIcons: false,
),
),
target: 'lib/main.dart',
isBuildingBundle: false,
localGradleErrors: const <GradleHandledError>[],
);
},
throwsA(isA<ProcessException>()));
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
Cache: () => cache,
Platform: () => android,
FileSystem: () => fileSystem,
ProcessManager: () => mockProcessManager,
});
testUsingContext('logs success event after a successful retry', () async {
int testFnCalled = 0;
when(mockProcessManager.start(any,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment')))
.thenAnswer((_) {
Process process;
if (testFnCalled == 0) {
process = createMockProcess(
exitCode: 1,
stdout: 'irrelevant\nSome gradle message\nirrelevant',
);
} else {
process = createMockProcess(
exitCode: 0,
stdout: 'irrelevant',
);
}
testFnCalled++;
return Future<Process>.value(process);
});
fileSystem.directory('android')
.childFile('build.gradle')
.createSync(recursive: true);
fileSystem.directory('android')
.childFile('gradle.properties')
.createSync(recursive: true);
fileSystem.directory('android')
.childDirectory('app')
.childFile('build.gradle')
..createSync(recursive: true)
..writeAsStringSync('apply from: irrelevant/flutter.gradle');
fileSystem.directory('build')
.childDirectory('app')
.childDirectory('outputs')
.childDirectory('flutter-apk')
.childFile('app-release.apk')
.createSync(recursive: true);
await buildGradleApp(
project: FlutterProject.current(),
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(
BuildMode.release,
null,
treeShakeIcons: false,
),
),
target: 'lib/main.dart',
isBuildingBundle: false,
localGradleErrors: <GradleHandledError>[
GradleHandledError(
test: (String line) {
return line.contains('Some gradle message');
},
handler: ({
String line,
FlutterProject project,
bool usesAndroidX,
bool shouldBuildPluginAsAar,
}) async {
return GradleBuildStatus.retry;
},
eventLabel: 'random-event-label',
),
],
);
expect(testUsage.events, contains(
const TestUsageEvent(
'build',
'unspecified',
label: 'gradle-random-event-label-success',
parameters: <String, String>{},
),
));
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
Cache: () => cache,
FileSystem: () => fileSystem,
Platform: () => android,
ProcessManager: () => mockProcessManager,
Usage: () => testUsage,
});
testUsingContext('performs code size analysis and sends analytics', () async {
when(mockProcessManager.start(any,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment')))
.thenAnswer((_) {
return Future<Process>.value(createMockProcess(
exitCode: 0,
stdout: 'irrelevant',
));
});
fileSystem.directory('android')
.childFile('build.gradle')
.createSync(recursive: true);
fileSystem.directory('android')
.childFile('gradle.properties')
.createSync(recursive: true);
fileSystem.directory('android')
.childDirectory('app')
.childFile('build.gradle')
..createSync(recursive: true)
..writeAsStringSync('apply from: irrelevant/flutter.gradle');
final Archive archive = Archive()
..addFile(ArchiveFile('AndroidManifest.xml', 100, List<int>.filled(100, 0)))
..addFile(ArchiveFile('META-INF/CERT.RSA', 10, List<int>.filled(10, 0)))
..addFile(ArchiveFile('META-INF/CERT.SF', 10, List<int>.filled(10, 0)))
..addFile(ArchiveFile('lib/arm64-v8a/libapp.so', 50, List<int>.filled(50, 0)))
..addFile(ArchiveFile('lib/arm64-v8a/libflutter.so', 50, List<int>.filled(50, 0)));
fileSystem.directory('build')
.childDirectory('app')
.childDirectory('outputs')
.childDirectory('flutter-apk')
.childFile('app-release.apk')
..createSync(recursive: true)
..writeAsBytesSync(ZipEncoder().encode(archive));
fileSystem.file('foo/snapshot.arm64-v8a.json')
..createSync(recursive: true)
..writeAsStringSync(r'''
[
{
"l": "dart:_internal",
"c": "SubListIterable",
"n": "[Optimized] skip",
"s": 2400
}
]''');
fileSystem.file('foo/trace.arm64-v8a.json')
..createSync(recursive: true)
..writeAsStringSync('{}');
await buildGradleApp(
project: FlutterProject.current(),
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(
BuildMode.release,
null,
treeShakeIcons: false,
codeSizeDirectory: 'foo',
),
targetArchs: <AndroidArch>[AndroidArch.arm64_v8a],
),
target: 'lib/main.dart',
isBuildingBundle: false,
localGradleErrors: <GradleHandledError>[],
);
expect(testUsage.events, contains(
const TestUsageEvent(
'code-size-analysis',
'apk',
),
));
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
Cache: () => cache,
FileSystem: () => fileSystem,
Platform: () => android,
ProcessManager: () => mockProcessManager,
Usage: () => testUsage,
});
testUsingContext('recognizes common errors - retry build with AAR plugins', () async {
when(mockProcessManager.start(any,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment')))
.thenAnswer((_) {
final Process process = createMockProcess(
exitCode: 1,
stdout: 'irrelevant\nSome gradle message\nirrelevant',
);
return Future<Process>.value(process);
});
fileSystem.directory('android')
.childFile('build.gradle')
.createSync(recursive: true);
fileSystem.directory('android')
.childFile('gradle.properties')
.createSync(recursive: true);
fileSystem.directory('android')
.childDirectory('app')
.childFile('build.gradle')
..createSync(recursive: true)
..writeAsStringSync('apply from: irrelevant/flutter.gradle');
int testFnCalled = 0;
bool builtPluginAsAar = false;
await expectLater(() async {
await buildGradleApp(
project: FlutterProject.current(),
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(
BuildMode.release,
null,
treeShakeIcons: false,
),
),
target: 'lib/main.dart',
isBuildingBundle: false,
localGradleErrors: <GradleHandledError>[
GradleHandledError(
test: (String line) {
if (line.contains('Some gradle message')) {
testFnCalled++;
return true;
}
return false;
},
handler: ({
String line,
FlutterProject project,
bool usesAndroidX,
bool shouldBuildPluginAsAar,
}) async {
if (testFnCalled == 2) {
builtPluginAsAar = shouldBuildPluginAsAar;
}
return GradleBuildStatus.retryWithAarPlugins;
},
eventLabel: 'random-event-label',
),
],
);
}, throwsToolExit(
message: 'Gradle task assembleRelease failed with exit code 1'
));
expect(testFnCalled, equals(2));
expect(builtPluginAsAar, isTrue);
expect(testUsage.events, contains(
const TestUsageEvent(
'build',
'unspecified',
label: 'gradle-random-event-label-failure',
parameters: <String, String>{},
),
));
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
Cache: () => cache,
Platform: () => android,
FileSystem: () => fileSystem,
ProcessManager: () => mockProcessManager,
Usage: () => testUsage,
});
testUsingContext('indicates that an APK has been built successfully', () async {
fileSystem.directory('android')
.childFile('build.gradle')
.createSync(recursive: true);
fileSystem.directory('android')
.childFile('gradle.properties')
.createSync(recursive: true);
fileSystem.directory('android')
.childDirectory('app')
.childFile('build.gradle')
..createSync(recursive: true)
..writeAsStringSync('apply from: irrelevant/flutter.gradle');
fileSystem.directory('build')
.childDirectory('app')
.childDirectory('outputs')
.childDirectory('flutter-apk')
.childFile('app-release.apk')
.createSync(recursive: true);
await buildGradleApp(
project: FlutterProject.current(),
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(
BuildMode.release,
null,
treeShakeIcons: false,
),
),
target: 'lib/main.dart',
isBuildingBundle: false,
localGradleErrors: const <GradleHandledError>[],
);
expect(
testLogger.statusText,
contains('Built build/app/outputs/flutter-apk/app-release.apk (0.0MB)'),
);
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
Cache: () => cache,
FileSystem: () => fileSystem,
Platform: () => android,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext("doesn't indicate how to consume an AAR when printHowToConsumeAaar is false", () async {
final File manifestFile = fileSystem.file('pubspec.yaml');
manifestFile.createSync(recursive: true);
manifestFile.writeAsStringSync('''
flutter:
module:
androidPackage: com.example.test
'''
);
fileSystem.file('.android/gradlew').createSync(recursive: true);
fileSystem.file('.android/gradle.properties')
.writeAsStringSync('irrelevant');
fileSystem.file('.android/build.gradle')
.createSync(recursive: true);
// Let any process start. Assert after.
when(mockProcessManager.run(
any,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'),
)).thenAnswer((_) async => ProcessResult(1, 0, '', ''));
fileSystem.directory('build/outputs/repo').createSync(recursive: true);
await buildGradleAar(
androidBuildInfo: const AndroidBuildInfo(BuildInfo(BuildMode.release, null, treeShakeIcons: false)),
project: FlutterProject.current(),
outputDirectory: fileSystem.directory('build/'),
target: '',
buildNumber: '1.0',
);
expect(
testLogger.statusText,
contains('Built build/outputs/repo'),
);
expect(
testLogger.statusText.contains('Consuming the Module'),
isFalse,
);
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
AndroidStudio: () => mockAndroidStudio,
Cache: () => cache,
Platform: () => android,
FileSystem: () => fileSystem,
ProcessManager: () => mockProcessManager,
});
testUsingContext('gradle exit code is properly passed on', () async {
final File manifestFile = fileSystem.file('pubspec.yaml');
manifestFile.createSync(recursive: true);
manifestFile.writeAsStringSync('''
flutter:
module:
androidPackage: com.example.test
'''
);
fileSystem.file('.android/gradlew').createSync(recursive: true);
fileSystem.file('.android/gradle.properties')
.writeAsStringSync('irrelevant');
fileSystem.file('.android/build.gradle')
.createSync(recursive: true);
// Let any process start. Assert after.
when(mockProcessManager.run(
any,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'),
)).thenAnswer((_) async => ProcessResult(1, 108, '', ''));
fileSystem.directory('build/outputs/repo').createSync(recursive: true);
await expectLater(() async =>
await buildGradleAar(
androidBuildInfo: const AndroidBuildInfo(BuildInfo(BuildMode.release, null, treeShakeIcons: false)),
project: FlutterProject.current(),
outputDirectory: fileSystem.directory('build/'),
target: '',
buildNumber: '1.0',
)
, throwsToolExit(exitCode: 108, message: 'Gradle task assembleAarRelease failed with exit code 108.'));
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
AndroidStudio: () => mockAndroidStudio,
Cache: () => cache,
Platform: () => android,
FileSystem: () => fileSystem,
ProcessManager: () => mockProcessManager,
});
testUsingContext('build apk uses selected local engine,the engine abi is arm', () async {
when(mockArtifacts.getArtifactPath(
Artifact.flutterFramework,
platform: TargetPlatform.android_arm,
mode: anyNamed('mode'),
environmentType: anyNamed('environmentType'),
)).thenReturn('engine');
when(mockArtifacts.engineOutPath).thenReturn(fileSystem.path.join('out', 'android_arm'));
fileSystem.file('out/android_arm/flutter_embedding_release.pom')
..createSync(recursive: true)
..writeAsStringSync('''
<?xml version="1.0" encoding="UTF-8"?>
<project>
<version>1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b</version>
<dependencies>
</dependencies>
</project>
''');
fileSystem.file('out/android_arm/armeabi_v7a_release.pom').createSync(recursive: true);
fileSystem.file('out/android_arm/armeabi_v7a_release.jar').createSync(recursive: true);
fileSystem.file('out/android_arm/armeabi_v7a_release.maven-metadata.xml').createSync(recursive: true);
fileSystem.file('out/android_arm/flutter_embedding_release.jar').createSync(recursive: true);
fileSystem.file('out/android_arm/flutter_embedding_release.pom').createSync(recursive: true);
fileSystem.file('out/android_arm/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true);
fileSystem.file('android/gradlew').createSync(recursive: true);
fileSystem.directory('android')
.childFile('gradle.properties')
.createSync(recursive: true);
fileSystem.file('android/build.gradle')
.createSync(recursive: true);
fileSystem.directory('android')
.childDirectory('app')
.childFile('build.gradle')
..createSync(recursive: true)
..writeAsStringSync('apply from: irrelevant/flutter.gradle');
// Let any process start. Assert after.
when(mockProcessManager.run(
any,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'),
)).thenAnswer((_) async => ProcessResult(1, 0, '', ''));
when(mockProcessManager.start(any,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment')))
.thenAnswer((_) {
return Future<Process>.value(
createMockProcess(
exitCode: 1,
)
);
});
await expectLater(() async {
await buildGradleApp(
project: FlutterProject.current(),
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(
BuildMode.release,
null,
treeShakeIcons: false,
),
),
target: 'lib/main.dart',
isBuildingBundle: false,
localGradleErrors: const <GradleHandledError>[],
);
}, throwsToolExit());
final List<String> actualGradlewCall = verify(
mockProcessManager.start(
captureAny,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory')
),
).captured.last as List<String>;
expect(actualGradlewCall, contains('/android/gradlew'));
expect(actualGradlewCall, contains('-Plocal-engine-out=out/android_arm'));
expect(actualGradlewCall, contains('-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0'));
expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release'));
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
AndroidStudio: () => mockAndroidStudio,
Artifacts: () => mockArtifacts,
Cache: () => cache,
Platform: () => android,
FileSystem: () => fileSystem,
ProcessManager: () => mockProcessManager,
});
testUsingContext(
'build apk uses selected local engine,the engine abi is arm64', () async {
when(mockArtifacts.getArtifactPath(
Artifact.flutterFramework,
platform: anyNamed('platform'),
mode: anyNamed('mode'),
environmentType: anyNamed('environmentType'),
)).thenReturn('engine');
when(mockArtifacts.engineOutPath).thenReturn(fileSystem.path.join('out', 'android_arm64'));
fileSystem.file('out/android_arm64/flutter_embedding_release.pom')
..createSync(recursive: true)
..writeAsStringSync('''
<?xml version="1.0" encoding="UTF-8"?>
<project>
<version>1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b</version>
<dependencies>
</dependencies>
</project>
''');
fileSystem.file('out/android_arm64/arm64_v8a_release.pom').createSync(recursive: true);
fileSystem.file('out/android_arm64/arm64_v8a_release.jar').createSync(recursive: true);
fileSystem.file('out/android_arm64/arm64_v8a_release.maven-metadata.xml').createSync(recursive: true);
fileSystem.file('out/android_arm64/flutter_embedding_release.jar').createSync(recursive: true);
fileSystem.file('out/android_arm64/flutter_embedding_release.pom').createSync(recursive: true);
fileSystem.file('out/android_arm64/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true);
fileSystem.file('android/gradlew').createSync(recursive: true);
fileSystem.directory('android')
.childFile('gradle.properties')
.createSync(recursive: true);
fileSystem.file('android/build.gradle')
.createSync(recursive: true);
fileSystem.directory('android')
.childDirectory('app')
.childFile('build.gradle')
..createSync(recursive: true)
..writeAsStringSync('apply from: irrelevant/flutter.gradle');
// Let any process start. Assert after.
when(mockProcessManager.run(
any,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'),
)).thenAnswer((_) async => ProcessResult(1, 0, '', ''));
when(mockProcessManager.start(any,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment')))
.thenAnswer((_) {
return Future<Process>.value(
createMockProcess(
exitCode: 1,
)
);
});
await expectLater(() async {
await buildGradleApp(
project: FlutterProject.current(),
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(
BuildMode.release,
null,
treeShakeIcons: false,
),
),
target: 'lib/main.dart',
isBuildingBundle: false,
localGradleErrors: const <GradleHandledError>[],
);
}, throwsToolExit());
final List<String> actualGradlewCall = verify(
mockProcessManager.start(
captureAny,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory')
),
).captured.last as List<String>;
expect(actualGradlewCall, contains('/android/gradlew'));
expect(actualGradlewCall, contains('-Plocal-engine-out=out/android_arm64'));
expect(actualGradlewCall, contains('-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0'));
expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release'));
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
AndroidStudio: () => mockAndroidStudio,
Artifacts: () => mockArtifacts,
Cache: () => cache,
Platform: () => android,
FileSystem: () => fileSystem,
ProcessManager: () => mockProcessManager,
});
testUsingContext(
'build apk uses selected local engine,the engine abi is x86', () async {
when(mockArtifacts.getArtifactPath(
Artifact.flutterFramework,
platform: anyNamed('platform'),
mode: anyNamed('mode'),
environmentType: anyNamed('environmentType'),
)).thenReturn('engine');
when(mockArtifacts.engineOutPath).thenReturn(fileSystem.path.join('out', 'android_x86'));
fileSystem.file('out/android_x86/flutter_embedding_release.pom')
..createSync(recursive: true)
..writeAsStringSync('''
<?xml version="1.0" encoding="UTF-8"?>
<project>
<version>1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b</version>
<dependencies>
</dependencies>
</project>
''');
fileSystem.file('out/android_x86/x86_release.pom').createSync(recursive: true);
fileSystem.file('out/android_x86/x86_release.jar').createSync(recursive: true);
fileSystem.file('out/android_x86/x86_release.maven-metadata.xml').createSync(recursive: true);
fileSystem.file('out/android_x86/flutter_embedding_release.jar').createSync(recursive: true);
fileSystem.file('out/android_x86/flutter_embedding_release.pom').createSync(recursive: true);
fileSystem.file('out/android_x86/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true);
fileSystem.file('android/gradlew').createSync(recursive: true);
fileSystem.directory('android')
.childFile('gradle.properties')
.createSync(recursive: true);
fileSystem.file('android/build.gradle')
.createSync(recursive: true);
fileSystem.directory('android')
.childDirectory('app')
.childFile('build.gradle')
..createSync(recursive: true)
..writeAsStringSync('apply from: irrelevant/flutter.gradle');
// Let any process start. Assert after.
when(mockProcessManager.run(
any,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'),
)).thenAnswer((_) async => ProcessResult(1, 0, '', ''));
when(mockProcessManager.start(any,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment')))
.thenAnswer((_) {
return Future<Process>.value(
createMockProcess(
exitCode: 1,
)
);
});
await expectLater(() async {
await buildGradleApp(
project: FlutterProject.current(),
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(
BuildMode.release,
null,
treeShakeIcons: false,
),
),
target: 'lib/main.dart',
isBuildingBundle: false,
localGradleErrors: const <GradleHandledError>[],
);
}, throwsToolExit());
final List<String> actualGradlewCall = verify(
mockProcessManager.start(
captureAny,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory')
),
).captured.last as List<String>;
expect(actualGradlewCall, contains('/android/gradlew'));
expect(actualGradlewCall, contains('-Plocal-engine-out=out/android_x86'));
expect(actualGradlewCall, contains('-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0'));
expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release'));
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
AndroidStudio: () => mockAndroidStudio,
Artifacts: () => mockArtifacts,
Cache: () => cache,
Platform: () => android,
FileSystem: () => fileSystem,
ProcessManager: () => mockProcessManager,
});
testUsingContext(
'build apk uses selected local engine,the engine abi is x64', () async {
when(mockArtifacts.getArtifactPath(
Artifact.flutterFramework,
platform: anyNamed('platform'),
mode: anyNamed('mode'),
environmentType: anyNamed('environmentType'),
)).thenReturn('engine');
when(mockArtifacts.engineOutPath).thenReturn(fileSystem.path.join('out', 'android_x64'));
fileSystem.file('out/android_x64/flutter_embedding_release.pom')
..createSync(recursive: true)
..writeAsStringSync('''
<?xml version="1.0" encoding="UTF-8"?>
<project>
<version>1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b</version>
<dependencies>
</dependencies>
</project>
''');
fileSystem.file('out/android_x64/x86_64_release.pom').createSync(recursive: true);
fileSystem.file('out/android_x64/x86_64_release.jar').createSync(recursive: true);
fileSystem.file('out/android_x64/x86_64_release.maven-metadata.xml').createSync(recursive: true);
fileSystem.file('out/android_x64/flutter_embedding_release.jar').createSync(recursive: true);
fileSystem.file('out/android_x64/flutter_embedding_release.pom').createSync(recursive: true);
fileSystem.file('out/android_x64/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true);
fileSystem.file('android/gradlew').createSync(recursive: true);
fileSystem.directory('android')
.childFile('gradle.properties')
.createSync(recursive: true);
fileSystem.file('android/build.gradle')
.createSync(recursive: true);
fileSystem.directory('android')
.childDirectory('app')
.childFile('build.gradle')
..createSync(recursive: true)
..writeAsStringSync('apply from: irrelevant/flutter.gradle');
// Let any process start. Assert after.
when(mockProcessManager.run(
any,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'),
)).thenAnswer((_) async => ProcessResult(1, 0, '', ''));
when(mockProcessManager.start(any,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment')))
.thenAnswer((_) {
return Future<Process>.value(
createMockProcess(
exitCode: 1,
)
);
});
await expectLater(() async {
await buildGradleApp(
project: FlutterProject.current(),
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(
BuildMode.release,
null,
treeShakeIcons: false,
),
),
target: 'lib/main.dart',
isBuildingBundle: false,
localGradleErrors: const <GradleHandledError>[],
);
}, throwsToolExit());
final List<String> actualGradlewCall = verify(
mockProcessManager.start(
captureAny,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory')
),
).captured.last as List<String>;
expect(actualGradlewCall, contains('/android/gradlew'));
expect(actualGradlewCall, contains('-Plocal-engine-out=out/android_x64'));
expect(actualGradlewCall, contains('-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0'));
expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release'));
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
AndroidStudio: () => mockAndroidStudio,
Artifacts: () => mockArtifacts,
Cache: () => cache,
Platform: () => android,
FileSystem: () => fileSystem,
ProcessManager: () => mockProcessManager,
});
testUsingContext('honors --no-android-gradle-daemon setting', () async {
(globals.processManager as FakeProcessManager).addCommand(
const FakeCommand(command: <String>[
'/android/gradlew',
'-q',
'--no-daemon',
'-Ptarget-platform=android-arm,android-arm64,android-x64',
'-Ptarget=lib/main.dart',
'-Pdart-obfuscation=false',
'-Ptrack-widget-creation=false',
'-Ptree-shake-icons=false',
'assembleRelease'
],
));
fileSystem.file('android/gradlew').createSync(recursive: true);
fileSystem.directory('android')
.childFile('gradle.properties')
.createSync(recursive: true);
fileSystem.file('android/build.gradle')
.createSync(recursive: true);
fileSystem.directory('android')
.childDirectory('app')
.childFile('build.gradle')
..createSync(recursive: true)
..writeAsStringSync('apply from: irrelevant/flutter.gradle');
await expectLater(() async {
await buildGradleApp(
project: FlutterProject.current(),
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(
BuildMode.release,
null,
treeShakeIcons: false,
androidGradleDaemon: false,
),
),
target: 'lib/main.dart',
isBuildingBundle: false,
localGradleErrors: const <GradleHandledError>[],
);
}, throwsToolExit());
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
AndroidStudio: () => mockAndroidStudio,
Artifacts: () => Artifacts.test(),
Cache: () => cache,
Platform: () => android,
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.list(<FakeCommand>[]),
});
testUsingContext('build aar uses selected local engine,the engine abi is arm', () async {
when(mockArtifacts.getArtifactPath(
Artifact.flutterFramework,
platform: TargetPlatform.android_arm,
mode: anyNamed('mode'),
environmentType: anyNamed('environmentType'),
)).thenReturn('engine');
when(mockArtifacts.engineOutPath).thenReturn(fileSystem.path.join('out', 'android_arm'));
fileSystem.file('out/android_arm/flutter_embedding_release.pom')
..createSync(recursive: true)
..writeAsStringSync('''
<?xml version="1.0" encoding="UTF-8"?>
<project>
<version>1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b</version>
<dependencies>
</dependencies>
</project>
''');
fileSystem.file('out/android_arm/armeabi_v7a_release.pom').createSync(recursive: true);
fileSystem.file('out/android_arm/armeabi_v7a_release.jar').createSync(recursive: true);
fileSystem.file('out/android_arm/armeabi_v7a_release.maven-metadata.xml').createSync(recursive: true);
fileSystem.file('out/android_arm/flutter_embedding_release.jar').createSync(recursive: true);
fileSystem.file('out/android_arm/flutter_embedding_release.pom').createSync(recursive: true);
fileSystem.file('out/android_arm/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true);
final File manifestFile = fileSystem.file('pubspec.yaml');
manifestFile.createSync(recursive: true);
manifestFile.writeAsStringSync('''
flutter:
module:
androidPackage: com.example.test
'''
);
fileSystem.directory('.android/gradle')
.createSync(recursive: true);
fileSystem.directory('.android/gradle/wrapper')
.createSync(recursive: true);
fileSystem.file('.android/gradlew').createSync(recursive: true);
fileSystem.file('.android/gradle.properties')
.writeAsStringSync('irrelevant');
fileSystem.file('.android/build.gradle')
.createSync(recursive: true);
// Let any process start. Assert after.
when(mockProcessManager.run(
any,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'),
)).thenAnswer((_) async => ProcessResult(1, 0, '', ''));
fileSystem.directory('build/outputs/repo').createSync(recursive: true);
when(fileSystemUtils.copyDirectorySync(any, any)).thenReturn(null);
await buildGradleAar(
androidBuildInfo: const AndroidBuildInfo(BuildInfo(BuildMode.release, null, treeShakeIcons: false)),
project: FlutterProject.current(),
outputDirectory: fileSystem.directory('build/'),
target: '',
buildNumber: '2.0',
);
final List<String> actualGradlewCall = verify(
mockProcessManager.run(
captureAny,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'),
),
).captured.last as List<String>;
expect(actualGradlewCall, contains('/.android/gradlew'));
expect(actualGradlewCall, contains('-Plocal-engine-out=out/android_arm'));
expect(actualGradlewCall, contains('-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0'));
expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release'));
expect(actualGradlewCall, contains('-PbuildNumber=2.0'));
// Verify the local engine repo is copied into the generated Maven repo.
final List<dynamic> copyDirectoryArguments = verify(
fileSystemUtils.copyDirectorySync(captureAny, captureAny)
).captured;
expect(copyDirectoryArguments.length, 2);
expect((copyDirectoryArguments.first as Directory).path, '/.tmp_rand0/flutter_tool_local_engine_repo.rand0');
expect((copyDirectoryArguments.last as Directory).path, 'build/outputs/repo');
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
AndroidStudio: () => mockAndroidStudio,
Artifacts: () => mockArtifacts,
Cache: () => cache,
Platform: () => android,
FileSystem: () => fileSystem,
FileSystemUtils: () => fileSystemUtils,
ProcessManager: () => mockProcessManager,
});
testUsingContext(
'build aar uses selected local engine,the engine abi is arm64', () async {
when(mockArtifacts.getArtifactPath(
Artifact.flutterFramework,
platform: anyNamed('platform'),
mode: anyNamed('mode'),
environmentType: anyNamed('environmentType'),
)).thenReturn('engine');
when(mockArtifacts.engineOutPath).thenReturn(fileSystem.path.join('out', 'android_arm64'));
fileSystem.file('out/android_arm64/flutter_embedding_release.pom')
..createSync(recursive: true)
..writeAsStringSync('''
<?xml version="1.0" encoding="UTF-8"?>
<project>
<version>1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b</version>
<dependencies>
</dependencies>
</project>
''');
fileSystem.file('out/android_arm64/arm64_v8a_release.pom').createSync(recursive: true);
fileSystem.file('out/android_arm64/arm64_v8a_release.jar').createSync(recursive: true);
fileSystem.file('out/android_arm64/arm64_v8a_release.maven-metadata.xml').createSync(recursive: true);
fileSystem.file('out/android_arm64/flutter_embedding_release.jar').createSync(recursive: true);
fileSystem.file('out/android_arm64/flutter_embedding_release.pom').createSync(recursive: true);
fileSystem.file('out/android_arm64/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true);
final File manifestFile = fileSystem.file('pubspec.yaml');
manifestFile.createSync(recursive: true);
manifestFile.writeAsStringSync('''
flutter:
module:
androidPackage: com.example.test
'''
);
fileSystem.directory('.android/gradle')
.createSync(recursive: true);
fileSystem.directory('.android/gradle/wrapper')
.createSync(recursive: true);
fileSystem.file('.android/gradlew').createSync(recursive: true);
fileSystem.file('.android/gradle.properties')
.writeAsStringSync('irrelevant');
fileSystem.file('.android/build.gradle')
.createSync(recursive: true);
// Let any process start. Assert after.
when(mockProcessManager.run(
any,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'),
)).thenAnswer((_) async => ProcessResult(1, 0, '', ''));
fileSystem.directory('build/outputs/repo').createSync(recursive: true);
when(fileSystemUtils.copyDirectorySync(any, any)).thenReturn(null);
await buildGradleAar(
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(BuildMode.release, null, treeShakeIcons: false)),
project: FlutterProject.current(),
outputDirectory: fileSystem.directory('build/'),
target: '',
buildNumber: '2.0',
);
final List<String> actualGradlewCall = verify(
mockProcessManager.run(
captureAny,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'),
),
).captured.last as List<String>;
expect(actualGradlewCall, contains('/.android/gradlew'));
expect(actualGradlewCall, contains('-Plocal-engine-out=out/android_arm64'));
expect(actualGradlewCall, contains('-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0'));
expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release'));
expect(actualGradlewCall, contains('-PbuildNumber=2.0'));
// Verify the local engine repo is copied into the generated Maven repo.
final List<dynamic> copyDirectoryArguments = verify(
fileSystemUtils.copyDirectorySync(captureAny, captureAny)
).captured;
expect(copyDirectoryArguments.length, 2);
expect((copyDirectoryArguments.first as Directory).path, '/.tmp_rand0/flutter_tool_local_engine_repo.rand0');
expect((copyDirectoryArguments.last as Directory).path, 'build/outputs/repo');
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
AndroidStudio: () => mockAndroidStudio,
Artifacts: () => mockArtifacts,
Cache: () => cache,
Platform: () => android,
FileSystem: () => fileSystem,
FileSystemUtils: () => fileSystemUtils,
ProcessManager: () => mockProcessManager,
});
testUsingContext(
'build aar uses selected local engine,the engine abi is x86', () async {
when(mockArtifacts.getArtifactPath(
Artifact.flutterFramework,
platform: anyNamed('platform'),
mode: anyNamed('mode'),
environmentType: anyNamed('environmentType'),
)).thenReturn('engine');
when(mockArtifacts.engineOutPath).thenReturn(fileSystem.path.join('out', 'android_x86'));
fileSystem.file('out/android_x86/flutter_embedding_release.pom')
..createSync(recursive: true)
..writeAsStringSync('''
<?xml version="1.0" encoding="UTF-8"?>
<project>
<version>1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b</version>
<dependencies>
</dependencies>
</project>
''');
fileSystem.file('out/android_x86/x86_release.pom').createSync(recursive: true);
fileSystem.file('out/android_x86/x86_release.jar').createSync(recursive: true);
fileSystem.file('out/android_x86/x86_release.maven-metadata.xml').createSync(recursive: true);
fileSystem.file('out/android_x86/flutter_embedding_release.jar').createSync(recursive: true);
fileSystem.file('out/android_x86/flutter_embedding_release.pom').createSync(recursive: true);
fileSystem.file('out/android_x86/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true);
final File manifestFile = fileSystem.file('pubspec.yaml');
manifestFile.createSync(recursive: true);
manifestFile.writeAsStringSync('''
flutter:
module:
androidPackage: com.example.test
'''
);
fileSystem.directory('.android/gradle')
.createSync(recursive: true);
fileSystem.directory('.android/gradle/wrapper')
.createSync(recursive: true);
fileSystem.file('.android/gradlew').createSync(recursive: true);
fileSystem.file('.android/gradle.properties')
.writeAsStringSync('irrelevant');
fileSystem.file('.android/build.gradle')
.createSync(recursive: true);
// Let any process start. Assert after.
when(mockProcessManager.run(
any,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'),
)).thenAnswer((_) async => ProcessResult(1, 0, '', ''));
fileSystem.directory('build/outputs/repo').createSync(recursive: true);
when(fileSystemUtils.copyDirectorySync(any, any)).thenReturn(null);
await buildGradleAar(
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(BuildMode.release, null, treeShakeIcons: false)),
project: FlutterProject.current(),
outputDirectory: fileSystem.directory('build/'),
target: '',
buildNumber: '2.0',
);
final List<String> actualGradlewCall = verify(
mockProcessManager.run(
captureAny,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'),
),
).captured.last as List<String>;
expect(actualGradlewCall, contains('/.android/gradlew'));
expect(actualGradlewCall, contains('-Plocal-engine-out=out/android_x86'));
expect(actualGradlewCall, contains('-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0'));
expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release'));
expect(actualGradlewCall, contains('-PbuildNumber=2.0'));
// Verify the local engine repo is copied into the generated Maven repo.
final List<dynamic> copyDirectoryArguments = verify(
fileSystemUtils.copyDirectorySync(captureAny, captureAny)
).captured;
expect(copyDirectoryArguments.length, 2);
expect((copyDirectoryArguments.first as Directory).path, '/.tmp_rand0/flutter_tool_local_engine_repo.rand0');
expect((copyDirectoryArguments.last as Directory).path, 'build/outputs/repo');
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
AndroidStudio: () => mockAndroidStudio,
Artifacts: () => mockArtifacts,
Cache: () => cache,
Platform: () => android,
FileSystem: () => fileSystem,
FileSystemUtils: () => fileSystemUtils,
ProcessManager: () => mockProcessManager,
});
testUsingContext(
'build aar uses selected local engine,the engine abi is x64', () async {
when(mockArtifacts.getArtifactPath(
Artifact.flutterFramework,
platform: anyNamed('platform'),
mode: anyNamed('mode'),
environmentType: anyNamed('environmentType'),
)).thenReturn('engine');
when(mockArtifacts.engineOutPath).thenReturn(fileSystem.path.join('out', 'android_x64'));
fileSystem.file('out/android_x64/flutter_embedding_release.pom')
..createSync(recursive: true)
..writeAsStringSync('''
<?xml version="1.0" encoding="UTF-8"?>
<project>
<version>1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b</version>
<dependencies>
</dependencies>
</project>
''');
fileSystem.file('out/android_x64/x86_64_release.pom').createSync(recursive: true);
fileSystem.file('out/android_x64/x86_64_release.jar').createSync(recursive: true);
fileSystem.file('out/android_x64/x86_64_release.maven-metadata.xml').createSync(recursive: true);
fileSystem.file('out/android_x64/flutter_embedding_release.jar').createSync(recursive: true);
fileSystem.file('out/android_x64/flutter_embedding_release.pom').createSync(recursive: true);
fileSystem.file('out/android_x64/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true);
final File manifestFile = fileSystem.file('pubspec.yaml');
manifestFile.createSync(recursive: true);
manifestFile.writeAsStringSync('''
flutter:
module:
androidPackage: com.example.test
'''
);
fileSystem.directory('.android/gradle')
.createSync(recursive: true);
fileSystem.directory('.android/gradle/wrapper')
.createSync(recursive: true);
fileSystem.file('.android/gradlew').createSync(recursive: true);
fileSystem.file('.android/gradle.properties')
.writeAsStringSync('irrelevant');
fileSystem.file('.android/build.gradle')
.createSync(recursive: true);
// Let any process start. Assert after.
when(mockProcessManager.run(
any,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'),
)).thenAnswer((_) async => ProcessResult(1, 0, '', ''));
fileSystem.directory('build/outputs/repo').createSync(recursive: true);
when(fileSystemUtils.copyDirectorySync(any, any)).thenReturn(null);
await buildGradleAar(
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(BuildMode.release, null, treeShakeIcons: false)),
project: FlutterProject.current(),
outputDirectory: fileSystem.directory('build/'),
target: '',
buildNumber: '2.0',
);
final List<String> actualGradlewCall = verify(
mockProcessManager.run(
captureAny,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'),
),
).captured.last as List<String>;
expect(actualGradlewCall, contains('/.android/gradlew'));
expect(actualGradlewCall, contains('-Plocal-engine-out=out/android_x64'));
expect(actualGradlewCall, contains('-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0'));
expect(actualGradlewCall, contains('-Plocal-engine-build-mode=release'));
expect(actualGradlewCall, contains('-PbuildNumber=2.0'));
// Verify the local engine repo is copied into the generated Maven repo.
final List<dynamic> copyDirectoryArguments = verify(
fileSystemUtils.copyDirectorySync(captureAny, captureAny)
).captured;
expect(copyDirectoryArguments.length, 2);
expect((copyDirectoryArguments.first as Directory).path, '/.tmp_rand0/flutter_tool_local_engine_repo.rand0');
expect((copyDirectoryArguments.last as Directory).path, 'build/outputs/repo');
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
AndroidStudio: () => mockAndroidStudio,
Artifacts: () => mockArtifacts,
Cache: () => cache,
Platform: () => android,
FileSystem: () => fileSystem,
FileSystemUtils: () => fileSystemUtils,
ProcessManager: () => mockProcessManager,
});
});
group('printHowToConsumeAar', () {
BufferLogger logger;
FileSystem fileSystem;
......@@ -2854,8 +1246,6 @@ class FakeGradleUtils extends GradleUtils {
class MockAndroidSdk extends Mock implements AndroidSdk {}
class MockAndroidProject extends Mock implements AndroidProject {}
class MockAndroidStudio extends Mock implements AndroidStudio {}
class MockFileSystemUtils extends Mock implements FileSystemUtils {}
class MockFlutterProject extends Mock implements FlutterProject {}
class MockLocalEngineArtifacts extends Mock implements LocalEngineArtifacts {}
class MockProcessManager extends Mock implements ProcessManager {}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment