Refactor gradle.dart (#43479)
diff --git a/packages/flutter_tools/test/general.shard/android/gradle_test.dart b/packages/flutter_tools/test/general.shard/android/gradle_test.dart
index 21ec43e..0571175 100644
--- a/packages/flutter_tools/test/general.shard/android/gradle_test.dart
+++ b/packages/flutter_tools/test/general.shard/android/gradle_test.dart
@@ -8,79 +8,370 @@
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/base/logger.dart';
+import 'package:flutter_tools/src/android/gradle_utils.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/common.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
-import 'package:flutter_tools/src/base/os.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';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/ios/xcodeproj.dart';
import 'package:flutter_tools/src/project.dart';
+import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:mockito/mockito.dart';
import 'package:platform/platform.dart';
import 'package:process/process.dart';
import '../../src/common.dart';
import '../../src/context.dart';
+import '../../src/mocks.dart';
import '../../src/pubspec_schema.dart';
void main() {
Cache.flutterRoot = getFlutterRoot();
- group('gradle build', () {
- test('do not crash if there is no Android SDK', () async {
- Exception shouldBeToolExit;
- try {
- // We'd like to always set androidSdk to null and test updateLocalProperties. But that's
- // currently impossible as the test is not hermetic. Luckily, our bots don't have Android
- // SDKs yet so androidSdk should be null by default.
- //
- // This test is written to fail if our bots get Android SDKs in the future: shouldBeToolExit
- // will be null and our expectation would fail. That would remind us to make these tests
- // hermetic before adding Android SDKs to the bots.
- updateLocalProperties(project: FlutterProject.current());
- } on Exception catch (e) {
- shouldBeToolExit = e;
- }
- // Ensure that we throw a meaningful ToolExit instead of a general crash.
- expect(shouldBeToolExit, isToolExit);
+
+ group('build artifacts', () {
+ test('getApkDirectory in app projects', () {
+ final FlutterProject project = MockFlutterProject();
+ final AndroidProject androidProject = MockAndroidProject();
+ when(project.android).thenReturn(androidProject);
+ when(project.isModule).thenReturn(false);
+ when(androidProject.buildDirectory).thenReturn(fs.directory('foo'));
+
+ expect(
+ getApkDirectory(project).path,
+ equals(fs.path.join('foo', 'app', 'outputs', 'apk')),
+ );
});
- // Regression test for https://github.com/flutter/flutter/issues/34700
- testUsingContext('Does not return nulls in apk list', () {
- final GradleProject gradleProject = MockGradleProject();
- const AndroidBuildInfo buildInfo = AndroidBuildInfo(BuildInfo.debug);
- when(gradleProject.apkFilesFor(buildInfo)).thenReturn(<String>['not_real']);
- when(gradleProject.apkDirectory).thenReturn(fs.currentDirectory);
+ test('getApkDirectory in module projects', () {
+ final FlutterProject project = MockFlutterProject();
+ final AndroidProject androidProject = MockAndroidProject();
+ when(project.android).thenReturn(androidProject);
+ when(project.isModule).thenReturn(true);
+ when(androidProject.buildDirectory).thenReturn(fs.directory('foo'));
- expect(findApkFiles(gradleProject, buildInfo), <File>[]);
+ expect(
+ getApkDirectory(project).path,
+ equals(fs.path.join('foo', 'host', 'outputs', 'apk')),
+ );
+ });
+
+ test('getBundleDirectory in app projects', () {
+ final FlutterProject project = MockFlutterProject();
+ final AndroidProject androidProject = MockAndroidProject();
+ when(project.android).thenReturn(androidProject);
+ when(project.isModule).thenReturn(false);
+ when(androidProject.buildDirectory).thenReturn(fs.directory('foo'));
+
+ expect(
+ getBundleDirectory(project).path,
+ equals(fs.path.join('foo', 'app', 'outputs', 'bundle')),
+ );
+ });
+
+ test('getBundleDirectory in module projects', () {
+ final FlutterProject project = MockFlutterProject();
+ final AndroidProject androidProject = MockAndroidProject();
+ when(project.android).thenReturn(androidProject);
+ when(project.isModule).thenReturn(true);
+ when(androidProject.buildDirectory).thenReturn(fs.directory('foo'));
+
+ expect(
+ getBundleDirectory(project).path,
+ equals(fs.path.join('foo', 'host', 'outputs', 'bundle')),
+ );
+ });
+
+ test('getRepoDirectory', () {
+ expect(
+ getRepoDirectory(fs.directory('foo')).path,
+ equals(fs.path.join('foo','outputs', 'repo')),
+ );
+ });
+ });
+
+ group('gradle tasks', () {
+ test('assemble release', () {
+ expect(
+ getAssembleTaskFor(const BuildInfo(BuildMode.release, null)),
+ equals('assembleRelease'),
+ );
+ expect(
+ getAssembleTaskFor(const BuildInfo(BuildMode.release, 'flavorFoo')),
+ equals('assembleFlavorFooRelease'),
+ );
+ });
+
+ test('assemble debug', () {
+ expect(
+ getAssembleTaskFor(const BuildInfo(BuildMode.debug, null)),
+ equals('assembleDebug'),
+ );
+ expect(
+ getAssembleTaskFor(const BuildInfo(BuildMode.debug, 'flavorFoo')),
+ equals('assembleFlavorFooDebug'),
+ );
+ });
+
+ test('assemble profile', () {
+ expect(
+ getAssembleTaskFor(const BuildInfo(BuildMode.profile, null)),
+ equals('assembleProfile'),
+ );
+ expect(
+ getAssembleTaskFor(const BuildInfo(BuildMode.profile, 'flavorFoo')),
+ equals('assembleFlavorFooProfile'),
+ );
+ });
+ });
+
+ group('findBundleFile', () {
+ testUsingContext('Finds app bundle when flavor contains underscores in release mode', () {
+ final FlutterProject project = generateFakeAppBundle('foo_barRelease', 'app.aab');
+ final File bundle = findBundleFile(project, const BuildInfo(BuildMode.release, 'foo_bar'));
+ expect(bundle, isNotNull);
+ expect(bundle.path, fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'foo_barRelease', 'app.aab'));
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem(),
ProcessManager: () => FakeProcessManager.any(),
});
- test('androidXFailureRegex should match lines with likely AndroidX errors', () {
- final List<String> nonMatchingLines = <String>[
- ':app:preBuild UP-TO-DATE',
- 'BUILD SUCCESSFUL in 0s',
- '',
- ];
- final List<String> matchingLines = <String>[
- 'AAPT: error: resource android:attr/fontVariationSettings not found.',
- 'AAPT: error: resource android:attr/ttcIndex not found.',
- 'error: package android.support.annotation does not exist',
- 'import android.support.annotation.NonNull;',
- 'import androidx.annotation.NonNull;',
- 'Daemon: AAPT2 aapt2-3.2.1-4818971-linux Daemon #0',
- ];
- for (String m in nonMatchingLines) {
- expect(androidXFailureRegex.hasMatch(m), isFalse);
- }
- for (String m in matchingLines) {
- expect(androidXFailureRegex.hasMatch(m), isTrue);
- }
+ testUsingContext('Finds app bundle when flavor doesn\'t contain underscores in release mode', () {
+ final FlutterProject project = generateFakeAppBundle('fooRelease', 'app.aab');
+ final File bundle = findBundleFile(project, const BuildInfo(BuildMode.release, 'foo'));
+ expect(bundle, isNotNull);
+ expect(bundle.path, fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'fooRelease', 'app.aab'));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => MemoryFileSystem(),
+ ProcessManager: () => FakeProcessManager.any(),
+ });
+
+ testUsingContext('Finds app bundle when no flavor is used in release mode', () {
+ final FlutterProject project = generateFakeAppBundle('release', 'app.aab');
+ final File bundle = findBundleFile(project, const BuildInfo(BuildMode.release, null));
+ expect(bundle, isNotNull);
+ expect(bundle.path, fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'release', 'app.aab'));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => MemoryFileSystem(),
+ ProcessManager: () => FakeProcessManager.any(),
+ });
+
+ testUsingContext('Finds app bundle when flavor contains underscores in debug mode', () {
+ final FlutterProject project = generateFakeAppBundle('foo_barDebug', 'app.aab');
+ final File bundle = findBundleFile(project, const BuildInfo(BuildMode.debug, 'foo_bar'));
+ expect(bundle, isNotNull);
+ expect(bundle.path, fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'foo_barDebug', 'app.aab'));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => MemoryFileSystem(),
+ ProcessManager: () => FakeProcessManager.any(),
+ });
+
+ testUsingContext('Finds app bundle when flavor doesn\'t contain underscores in debug mode', () {
+ final FlutterProject project = generateFakeAppBundle('fooDebug', 'app.aab');
+ final File bundle = findBundleFile(project, const BuildInfo(BuildMode.debug, 'foo'));
+ expect(bundle, isNotNull);
+ expect(bundle.path, fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'fooDebug', 'app.aab'));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => MemoryFileSystem(),
+ ProcessManager: () => FakeProcessManager.any(),
+ });
+
+ testUsingContext('Finds app bundle when no flavor is used in debug mode', () {
+ final FlutterProject project = generateFakeAppBundle('debug', 'app.aab');
+ final File bundle = findBundleFile(project, const BuildInfo(BuildMode.debug, null));
+ expect(bundle, isNotNull);
+ expect(bundle.path, fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'debug', 'app.aab'));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => MemoryFileSystem(),
+ ProcessManager: () => FakeProcessManager.any(),
+ });
+
+ testUsingContext('Finds app bundle when flavor contains underscores in profile mode', () {
+ final FlutterProject project = generateFakeAppBundle('foo_barProfile', 'app.aab');
+ final File bundle = findBundleFile(project, const BuildInfo(BuildMode.profile, 'foo_bar'));
+ expect(bundle, isNotNull);
+ expect(bundle.path, fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'foo_barProfile', 'app.aab'));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => MemoryFileSystem(),
+ ProcessManager: () => FakeProcessManager.any(),
+ });
+
+ testUsingContext('Finds app bundle when flavor doesn\'t contain underscores in profile mode', () {
+ final FlutterProject project = generateFakeAppBundle('fooProfile', 'app.aab');
+ final File bundle = findBundleFile(project, const BuildInfo(BuildMode.profile, 'foo'));
+ expect(bundle, isNotNull);
+ expect(bundle.path, fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'fooProfile', 'app.aab'));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => MemoryFileSystem(),
+ ProcessManager: () => FakeProcessManager.any(),
+ });
+
+ testUsingContext('Finds app bundle when no flavor is used in profile mode', () {
+ final FlutterProject project = generateFakeAppBundle('profile', 'app.aab');
+ final File bundle = findBundleFile(project, const BuildInfo(BuildMode.profile, null));
+ expect(bundle, isNotNull);
+ expect(bundle.path, fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'profile', 'app.aab'));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => MemoryFileSystem(),
+ ProcessManager: () => FakeProcessManager.any(),
+ });
+
+ testUsingContext('Finds app bundle in release mode - Gradle 3.5', () {
+ final FlutterProject project = generateFakeAppBundle('release', 'app-release.aab');
+ final File bundle = findBundleFile(project, const BuildInfo(BuildMode.release, null));
+ expect(bundle, isNotNull);
+ expect(bundle.path, fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'release', 'app-release.aab'));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => MemoryFileSystem(),
+ ProcessManager: () => FakeProcessManager.any(),
+ });
+
+ testUsingContext('Finds app bundle in profile mode - Gradle 3.5', () {
+ final FlutterProject project = generateFakeAppBundle('profile', 'app-profile.aab');
+ final File bundle = findBundleFile(project, const BuildInfo(BuildMode.profile, null));
+ expect(bundle, isNotNull);
+ expect(bundle.path, fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'profile', 'app-profile.aab'));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => MemoryFileSystem(),
+ ProcessManager: () => FakeProcessManager.any(),
+ });
+
+ testUsingContext('Finds app bundle in debug mode - Gradle 3.5', () {
+ final FlutterProject project = generateFakeAppBundle('debug', 'app-debug.aab');
+ final File bundle = findBundleFile(project, const BuildInfo(BuildMode.debug, null));
+ expect(bundle, isNotNull);
+ expect(bundle.path, fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'debug', 'app-debug.aab'));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => MemoryFileSystem(),
+ ProcessManager: () => FakeProcessManager.any(),
+ });
+
+ testUsingContext('Finds app bundle when flavor contains underscores in release mode - Gradle 3.5', () {
+ final FlutterProject project = generateFakeAppBundle('foo_barRelease', 'app-foo_bar-release.aab');
+ final File bundle = findBundleFile(project, const BuildInfo(BuildMode.release, 'foo_bar'));
+ expect(bundle, isNotNull);
+ expect(bundle.path, fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'foo_barRelease', 'app-foo_bar-release.aab'));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => MemoryFileSystem(),
+ ProcessManager: () => FakeProcessManager.any(),
+ });
+
+ testUsingContext('Finds app bundle when flavor contains underscores in profile mode - Gradle 3.5', () {
+ final FlutterProject project = generateFakeAppBundle('foo_barProfile', 'app-foo_bar-profile.aab');
+ final File bundle = findBundleFile(project, const BuildInfo(BuildMode.profile, 'foo_bar'));
+ expect(bundle, isNotNull);
+ expect(bundle.path, fs.path.join('irrelevant', 'app', 'outputs', 'bundle', 'foo_barProfile', 'app-foo_bar-profile.aab'));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => MemoryFileSystem(),
+ ProcessManager: () => FakeProcessManager.any(),
+ });
+
+ testUsingContext('Finds app bundle when flavor contains underscores in debug mode - Gradle 3.5', () {
+ final FlutterProject project = generateFakeAppBundle('foo_barDebug', 'app-foo_bar-debug.aab');
+ final File bundle = findBundleFile(project, const BuildInfo(BuildMode.debug, 'foo_bar'));
+ expect(bundle, isNotNull);
+ expect(bundle.path, fs.path.join('irrelevant','app', 'outputs', 'bundle', 'foo_barDebug', 'app-foo_bar-debug.aab'));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => MemoryFileSystem(),
+ ProcessManager: () => FakeProcessManager.any(),
+ });
+ });
+
+ group('findApkFiles', () {
+ testUsingContext('Finds APK without flavor in release', () {
+ final FlutterProject project = MockFlutterProject();
+ final AndroidProject androidProject = MockAndroidProject();
+
+ when(project.android).thenReturn(androidProject);
+ when(project.isModule).thenReturn(false);
+ when(androidProject.buildDirectory).thenReturn(fs.directory('irrelevant'));
+
+ final Directory apkDirectory = fs.directory(fs.path.join('irrelevant', 'app', 'outputs', 'apk', 'release'));
+ apkDirectory.createSync(recursive: true);
+ apkDirectory.childFile('app-release.apk').createSync();
+
+ final Iterable<File> apks = findApkFiles(
+ project,
+ const AndroidBuildInfo(BuildInfo(BuildMode.release, '')),
+ );
+ expect(apks.isNotEmpty, isTrue);
+ expect(apks.first.path, equals(fs.path.join('irrelevant', 'app', 'outputs', 'apk', 'release', 'app-release.apk')));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => MemoryFileSystem(),
+ ProcessManager: () => FakeProcessManager.any(),
+ });
+
+ testUsingContext('Finds APK with flavor in release mode', () {
+ final FlutterProject project = MockFlutterProject();
+ final AndroidProject androidProject = MockAndroidProject();
+
+ when(project.android).thenReturn(androidProject);
+ when(project.isModule).thenReturn(false);
+ when(androidProject.buildDirectory).thenReturn(fs.directory('irrelevant'));
+
+ final Directory apkDirectory = fs.directory(fs.path.join('irrelevant', 'app', 'outputs', 'apk', 'release'));
+ apkDirectory.createSync(recursive: true);
+ apkDirectory.childFile('app-flavor1-release.apk').createSync();
+
+ final Iterable<File> apks = findApkFiles(
+ project,
+ const AndroidBuildInfo(BuildInfo(BuildMode.release, 'flavor1')),
+ );
+ expect(apks.isNotEmpty, isTrue);
+ expect(apks.first.path, equals(fs.path.join('irrelevant', 'app', 'outputs', 'apk', 'release', 'app-flavor1-release.apk')));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => MemoryFileSystem(),
+ ProcessManager: () => FakeProcessManager.any(),
+ });
+
+ testUsingContext('Finds APK with flavor in release mode - AGP v3', () {
+ final FlutterProject project = MockFlutterProject();
+ final AndroidProject androidProject = MockAndroidProject();
+
+ when(project.android).thenReturn(androidProject);
+ when(project.isModule).thenReturn(false);
+ when(androidProject.buildDirectory).thenReturn(fs.directory('irrelevant'));
+
+ final Directory apkDirectory = fs.directory(fs.path.join('irrelevant', 'app', 'outputs', 'apk', 'flavor1', 'release'));
+ apkDirectory.createSync(recursive: true);
+ apkDirectory.childFile('app-flavor1-release.apk').createSync();
+
+ final Iterable<File> apks = findApkFiles(
+ project,
+ const AndroidBuildInfo(BuildInfo(BuildMode.release, 'flavor1')),
+ );
+ expect(apks.isNotEmpty, isTrue);
+ expect(apks.first.path, equals(fs.path.join('irrelevant', 'app', 'outputs', 'apk', 'flavor1', 'release', 'app-flavor1-release.apk')));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => MemoryFileSystem(),
+ ProcessManager: () => FakeProcessManager.any(),
+ });
+ });
+
+ group('gradle build', () {
+ testUsingContext('do not crash if there is no Android SDK', () async {
+ expect(() {
+ updateLocalProperties(project: FlutterProject.current());
+ }, throwsToolExit(
+ message: '$warningMark No Android SDK found. Try setting the ANDROID_HOME environment variable.',
+ ));
+ }, overrides: <Type, Generator>{
+ AndroidSdk: () => null,
+ });
+
+ // Regression test for https://github.com/flutter/flutter/issues/34700
+ testUsingContext('Does not return nulls in apk list', () {
+ const AndroidBuildInfo buildInfo = AndroidBuildInfo(BuildInfo.debug);
+ expect(findApkFiles(FlutterProject.current(), buildInfo), <File>[]);
+ }, overrides: <Type, Generator>{
+ FileSystem: () => MemoryFileSystem(),
+ ProcessManager: () => FakeProcessManager.any(),
});
test('androidXPluginWarningRegex should match lines with the AndroidX plugin warnings', () {
@@ -103,355 +394,6 @@
expect(androidXPluginWarningRegex.hasMatch(m), isTrue);
}
});
-
- test('ndkMessageFilter should only match lines without the error message', () {
- final List<String> nonMatchingLines = <String>[
- 'NDK is missing a "platforms" directory.',
- 'If you are using NDK, verify the ndk.dir is set to a valid NDK directory. It is currently set to /usr/local/company/home/username/Android/Sdk/ndk-bundle.',
- 'If you are not using NDK, unset the NDK variable from ANDROID_NDK_HOME or local.properties to remove this warning.',
- ];
- final List<String> matchingLines = <String>[
- ':app:preBuild UP-TO-DATE',
- 'BUILD SUCCESSFUL in 0s',
- '',
- 'Something NDK related mentioning ANDROID_NDK_HOME',
- ];
- for (String m in nonMatchingLines) {
- expect(ndkMessageFilter.hasMatch(m), isFalse);
- }
- for (String m in matchingLines) {
- expect(ndkMessageFilter.hasMatch(m), isTrue);
- }
- });
-
- testUsingContext('Finds app bundle when flavor contains underscores in release mode', () {
- final GradleProject gradleProject = generateFakeAppBundle('foo_barRelease', 'app.aab');
- final File bundle = findBundleFile(gradleProject, const BuildInfo(BuildMode.release, 'foo_bar'));
- expect(bundle, isNotNull);
- expect(bundle.path, '/foo_barRelease/app.aab');
- }, overrides: <Type, Generator>{
- FileSystem: () => MemoryFileSystem(),
- ProcessManager: () => FakeProcessManager.any(),
- });
-
- testUsingContext('Finds app bundle when flavor doesn\'t contain underscores in release mode', () {
- final GradleProject gradleProject = generateFakeAppBundle('fooRelease', 'app.aab');
- final File bundle = findBundleFile(gradleProject, const BuildInfo(BuildMode.release, 'foo'));
- expect(bundle, isNotNull);
- expect(bundle.path, '/fooRelease/app.aab');
- }, overrides: <Type, Generator>{
- FileSystem: () => MemoryFileSystem(),
- ProcessManager: () => FakeProcessManager.any(),
- });
-
- testUsingContext('Finds app bundle when no flavor is used in release mode', () {
- final GradleProject gradleProject = generateFakeAppBundle('release', 'app.aab');
- final File bundle = findBundleFile(gradleProject, const BuildInfo(BuildMode.release, null));
- expect(bundle, isNotNull);
- expect(bundle.path, '/release/app.aab');
- }, overrides: <Type, Generator>{
- FileSystem: () => MemoryFileSystem(),
- ProcessManager: () => FakeProcessManager.any(),
- });
-
- testUsingContext('Finds app bundle when flavor contains underscores in debug mode', () {
- final GradleProject gradleProject = generateFakeAppBundle('foo_barDebug', 'app.aab');
- final File bundle = findBundleFile(gradleProject, const BuildInfo(BuildMode.debug, 'foo_bar'));
- expect(bundle, isNotNull);
- expect(bundle.path, '/foo_barDebug/app.aab');
- }, overrides: <Type, Generator>{
- FileSystem: () => MemoryFileSystem(),
- ProcessManager: () => FakeProcessManager.any(),
- });
-
- testUsingContext('Finds app bundle when flavor doesn\'t contain underscores in debug mode', () {
- final GradleProject gradleProject = generateFakeAppBundle('fooDebug', 'app.aab');
- final File bundle = findBundleFile(gradleProject, const BuildInfo(BuildMode.debug, 'foo'));
- expect(bundle, isNotNull);
- expect(bundle.path, '/fooDebug/app.aab');
- }, overrides: <Type, Generator>{
- FileSystem: () => MemoryFileSystem(),
- ProcessManager: () => FakeProcessManager.any(),
- });
-
- testUsingContext('Finds app bundle when no flavor is used in debug mode', () {
- final GradleProject gradleProject = generateFakeAppBundle('debug', 'app.aab');
- final File bundle = findBundleFile(gradleProject, const BuildInfo(BuildMode.debug, null));
- expect(bundle, isNotNull);
- expect(bundle.path, '/debug/app.aab');
- }, overrides: <Type, Generator>{
- FileSystem: () => MemoryFileSystem(),
- ProcessManager: () => FakeProcessManager.any(),
- });
-
- testUsingContext('Finds app bundle when flavor contains underscores in profile mode', () {
- final GradleProject gradleProject = generateFakeAppBundle('foo_barProfile', 'app.aab');
- final File bundle = findBundleFile(gradleProject, const BuildInfo(BuildMode.profile, 'foo_bar'));
- expect(bundle, isNotNull);
- expect(bundle.path, '/foo_barProfile/app.aab');
- }, overrides: <Type, Generator>{
- FileSystem: () => MemoryFileSystem(),
- ProcessManager: () => FakeProcessManager.any(),
- });
-
- testUsingContext('Finds app bundle when flavor doesn\'t contain underscores in profile mode', () {
- final GradleProject gradleProject = generateFakeAppBundle('fooProfile', 'app.aab');
- final File bundle = findBundleFile(gradleProject, const BuildInfo(BuildMode.profile, 'foo'));
- expect(bundle, isNotNull);
- expect(bundle.path, '/fooProfile/app.aab');
- }, overrides: <Type, Generator>{
- FileSystem: () => MemoryFileSystem(),
- ProcessManager: () => FakeProcessManager.any(),
- });
-
- testUsingContext('Finds app bundle when no flavor is used in profile mode', () {
- final GradleProject gradleProject = generateFakeAppBundle('profile', 'app.aab');
- final File bundle = findBundleFile(gradleProject, const BuildInfo(BuildMode.profile, null));
- expect(bundle, isNotNull);
- expect(bundle.path, '/profile/app.aab');
- }, overrides: <Type, Generator>{
- FileSystem: () => MemoryFileSystem(),
- ProcessManager: () => FakeProcessManager.any(),
- });
-
- testUsingContext('Finds app bundle in release mode - Gradle 3.5', () {
- final GradleProject gradleProject = generateFakeAppBundle('release', 'app-release.aab');
- final File bundle = findBundleFile(gradleProject, const BuildInfo(BuildMode.release, null));
- expect(bundle, isNotNull);
- expect(bundle.path, '/release/app-release.aab');
- }, overrides: <Type, Generator>{
- FileSystem: () => MemoryFileSystem(),
- ProcessManager: () => FakeProcessManager.any(),
- });
-
- testUsingContext('Finds app bundle in profile mode - Gradle 3.5', () {
- final GradleProject gradleProject = generateFakeAppBundle('profile', 'app-profile.aab');
- final File bundle = findBundleFile(gradleProject, const BuildInfo(BuildMode.profile, null));
- expect(bundle, isNotNull);
- expect(bundle.path, '/profile/app-profile.aab');
- }, overrides: <Type, Generator>{
- FileSystem: () => MemoryFileSystem(),
- ProcessManager: () => FakeProcessManager.any(),
- });
-
- testUsingContext('Finds app bundle in debug mode - Gradle 3.5', () {
- final GradleProject gradleProject = generateFakeAppBundle('debug', 'app-debug.aab');
- final File bundle = findBundleFile(gradleProject, const BuildInfo(BuildMode.debug, null));
- expect(bundle, isNotNull);
- expect(bundle.path, '/debug/app-debug.aab');
- }, overrides: <Type, Generator>{
- FileSystem: () => MemoryFileSystem(),
- ProcessManager: () => FakeProcessManager.any(),
- });
-
- testUsingContext('Finds app bundle when flavor contains underscores in release mode - Gradle 3.5', () {
- final GradleProject gradleProject = generateFakeAppBundle('foo_barRelease', 'app-foo_bar-release.aab');
- final File bundle = findBundleFile(gradleProject, const BuildInfo(BuildMode.release, 'foo_bar'));
- expect(bundle, isNotNull);
- expect(bundle.path, '/foo_barRelease/app-foo_bar-release.aab');
- }, overrides: <Type, Generator>{
- FileSystem: () => MemoryFileSystem(),
- ProcessManager: () => FakeProcessManager.any(),
- });
-
- testUsingContext('Finds app bundle when flavor contains underscores in profile mode - Gradle 3.5', () {
- final GradleProject gradleProject = generateFakeAppBundle('foo_barProfile', 'app-foo_bar-profile.aab');
- final File bundle = findBundleFile(gradleProject, const BuildInfo(BuildMode.profile, 'foo_bar'));
- expect(bundle, isNotNull);
- expect(bundle.path, '/foo_barProfile/app-foo_bar-profile.aab');
- }, overrides: <Type, Generator>{
- FileSystem: () => MemoryFileSystem(),
- ProcessManager: () => FakeProcessManager.any(),
- });
-
- testUsingContext('Finds app bundle when flavor contains underscores in debug mode - Gradle 3.5', () {
- final GradleProject gradleProject = generateFakeAppBundle('foo_barDebug', 'app-foo_bar-debug.aab');
- final File bundle = findBundleFile(gradleProject, const BuildInfo(BuildMode.debug, 'foo_bar'));
- expect(bundle, isNotNull);
- expect(bundle.path, '/foo_barDebug/app-foo_bar-debug.aab');
- }, overrides: <Type, Generator>{
- FileSystem: () => MemoryFileSystem(),
- ProcessManager: () => FakeProcessManager.any(),
- });
- });
-
- group('gradle project', () {
- GradleProject projectFrom(String properties, String tasks) => GradleProject.fromAppProperties(properties, tasks);
-
- test('should extract build directory from app properties', () {
- final GradleProject project = projectFrom('''
-someProperty: someValue
-buildDir: /Users/some/apps/hello/build/app
-someOtherProperty: someOtherValue
- ''', '');
- expect(
- fs.path.normalize(project.apkDirectory.path),
- fs.path.normalize('/Users/some/apps/hello/build/app/outputs/apk'),
- );
- });
- test('should extract default build variants from app properties', () {
- final GradleProject project = projectFrom('buildDir: /Users/some/apps/hello/build/app', '''
-someTask
-assemble
-assembleAndroidTest
-assembleDebug
-assembleProfile
-assembleRelease
-someOtherTask
- ''');
- expect(project.buildTypes, <String>['debug', 'profile', 'release']);
- expect(project.productFlavors, isEmpty);
- });
- test('should extract custom build variants from app properties', () {
- final GradleProject project = projectFrom('buildDir: /Users/some/apps/hello/build/app', '''
-someTask
-assemble
-assembleAndroidTest
-assembleDebug
-assembleFree
-assembleFreeAndroidTest
-assembleFreeDebug
-assembleFreeProfile
-assembleFreeRelease
-assemblePaid
-assemblePaidAndroidTest
-assemblePaidDebug
-assemblePaidProfile
-assemblePaidRelease
-assembleProfile
-assembleRelease
-someOtherTask
- ''');
- expect(project.buildTypes, <String>['debug', 'profile', 'release']);
- expect(project.productFlavors, <String>['free', 'paid']);
- });
- test('should provide apk file name for default build types', () {
- final GradleProject project = GradleProject(<String>['debug', 'profile', 'release'], <String>[], '/some/dir');
- expect(project.apkFilesFor(const AndroidBuildInfo(BuildInfo.debug)).first, 'app-debug.apk');
- expect(project.apkFilesFor(const AndroidBuildInfo(BuildInfo.profile)).first, 'app-profile.apk');
- expect(project.apkFilesFor(const AndroidBuildInfo(BuildInfo.release)).first, 'app-release.apk');
- expect(project.apkFilesFor(const AndroidBuildInfo(BuildInfo(BuildMode.release, 'unknown'))).isEmpty, isTrue);
- });
- test('should provide apk file name for flavored build types', () {
- final GradleProject project = GradleProject(<String>['debug', 'profile', 'release'], <String>['free', 'paid'], '/some/dir');
- expect(project.apkFilesFor(const AndroidBuildInfo(BuildInfo(BuildMode.debug, 'free'))).first, 'app-free-debug.apk');
- expect(project.apkFilesFor(const AndroidBuildInfo(BuildInfo(BuildMode.release, 'paid'))).first, 'app-paid-release.apk');
- expect(project.apkFilesFor(const AndroidBuildInfo(BuildInfo(BuildMode.release, 'unknown'))).isEmpty, isTrue);
- });
- test('should provide apks for default build types and each ABI', () {
- final GradleProject project = GradleProject(<String>['debug', 'profile', 'release'], <String>[], '/some/dir');
- expect(project.apkFilesFor(
- const AndroidBuildInfo(
- BuildInfo.debug,
- splitPerAbi: true,
- targetArchs: <AndroidArch>[
- AndroidArch.armeabi_v7a,
- AndroidArch.arm64_v8a,
- ],
- )),
- <String>[
- 'app-armeabi-v7a-debug.apk',
- 'app-arm64-v8a-debug.apk',
- ]);
-
- expect(project.apkFilesFor(
- const AndroidBuildInfo(
- BuildInfo.release,
- splitPerAbi: true,
- targetArchs: <AndroidArch>[
- AndroidArch.armeabi_v7a,
- AndroidArch.arm64_v8a,
- ],
- )),
- <String>[
- 'app-armeabi-v7a-release.apk',
- 'app-arm64-v8a-release.apk',
- ]);
-
- expect(project.apkFilesFor(
- const AndroidBuildInfo(
- BuildInfo(BuildMode.release, 'unknown'),
- splitPerAbi: true,
- targetArchs: <AndroidArch>[
- AndroidArch.armeabi_v7a,
- AndroidArch.arm64_v8a,
- ],
- )).isEmpty, isTrue);
- });
- test('should provide apks for each ABI and flavored build types', () {
- final GradleProject project = GradleProject(<String>['debug', 'profile', 'release'], <String>['free', 'paid'], '/some/dir');
- expect(project.apkFilesFor(
- const AndroidBuildInfo(
- BuildInfo(BuildMode.debug, 'free'),
- splitPerAbi: true,
- targetArchs: <AndroidArch>[
- AndroidArch.armeabi_v7a,
- AndroidArch.arm64_v8a,
- ],
- )),
- <String>[
- 'app-free-armeabi-v7a-debug.apk',
- 'app-free-arm64-v8a-debug.apk',
- ]);
-
- expect(project.apkFilesFor(
- const AndroidBuildInfo(
- BuildInfo(BuildMode.release, 'paid'),
- splitPerAbi: true,
- targetArchs: <AndroidArch>[
- AndroidArch.armeabi_v7a,
- AndroidArch.arm64_v8a,
- ],
- )),
- <String>[
- 'app-paid-armeabi-v7a-release.apk',
- 'app-paid-arm64-v8a-release.apk',
- ]);
-
- expect(project.apkFilesFor(
- const AndroidBuildInfo(
- BuildInfo(BuildMode.release, 'unknown'),
- splitPerAbi: true,
- targetArchs: <AndroidArch>[
- AndroidArch.armeabi_v7a,
- AndroidArch.arm64_v8a,
- ],
- )).isEmpty, isTrue);
- });
- test('should provide assemble task name for default build types', () {
- final GradleProject project = GradleProject(<String>['debug', 'profile', 'release'], <String>[], '/some/dir');
- expect(project.assembleTaskFor(BuildInfo.debug), 'assembleDebug');
- expect(project.assembleTaskFor(BuildInfo.profile), 'assembleProfile');
- expect(project.assembleTaskFor(BuildInfo.release), 'assembleRelease');
- expect(project.assembleTaskFor(const BuildInfo(BuildMode.release, 'unknown')), isNull);
- });
- test('should provide assemble task name for flavored build types', () {
- final GradleProject project = GradleProject(<String>['debug', 'profile', 'release'], <String>['free', 'paid'], '/some/dir');
- expect(project.assembleTaskFor(const BuildInfo(BuildMode.debug, 'free')), 'assembleFreeDebug');
- expect(project.assembleTaskFor(const BuildInfo(BuildMode.release, 'paid')), 'assemblePaidRelease');
- expect(project.assembleTaskFor(const BuildInfo(BuildMode.release, 'unknown')), isNull);
- });
- test('should respect format of the flavored build types', () {
- final GradleProject project = GradleProject(<String>['debug'], <String>['randomFlavor'], '/some/dir');
- expect(project.assembleTaskFor(const BuildInfo(BuildMode.debug, 'randomFlavor')), 'assembleRandomFlavorDebug');
- });
- test('bundle should provide assemble task name for default build types', () {
- final GradleProject project = GradleProject(<String>['debug', 'profile', 'release'], <String>[], '/some/dir');
- expect(project.bundleTaskFor(BuildInfo.debug), 'bundleDebug');
- expect(project.bundleTaskFor(BuildInfo.profile), 'bundleProfile');
- expect(project.bundleTaskFor(BuildInfo.release), 'bundleRelease');
- expect(project.bundleTaskFor(const BuildInfo(BuildMode.release, 'unknown')), isNull);
- });
- test('bundle should provide assemble task name for flavored build types', () {
- final GradleProject project = GradleProject(<String>['debug', 'profile', 'release'], <String>['free', 'paid'], '/some/dir');
- expect(project.bundleTaskFor(const BuildInfo(BuildMode.debug, 'free')), 'bundleFreeDebug');
- expect(project.bundleTaskFor(const BuildInfo(BuildMode.release, 'paid')), 'bundlePaidRelease');
- expect(project.bundleTaskFor(const BuildInfo(BuildMode.release, 'unknown')), isNull);
- });
- test('bundle should respect format of the flavored build types', () {
- final GradleProject project = GradleProject(<String>['debug'], <String>['randomFlavor'], '/some/dir');
- expect(project.bundleTaskFor(const BuildInfo(BuildMode.debug, 'randomFlavor')), 'bundleRandomFlavorDebug');
- });
});
group('Config files', () {
@@ -461,7 +403,6 @@
setUp(() {
mockLogger = BufferLogger();
tempDir = fs.systemTempDirectory.createTempSync('flutter_settings_aar_test.');
-
});
testUsingContext('create settings_aar.gradle when current settings.gradle loads plugins', () {
@@ -547,48 +488,6 @@
});
});
- group('Undefined task', () {
- BufferLogger mockLogger;
-
- setUp(() {
- mockLogger = BufferLogger();
- });
-
- testUsingContext('print undefined build type', () {
- final GradleProject project = GradleProject(<String>['debug', 'release'],
- const <String>['free', 'paid'], '/some/dir');
-
- printUndefinedTask(project, const BuildInfo(BuildMode.profile, 'unknown'));
- expect(mockLogger.errorText, contains('The Gradle project does not define a task suitable for the requested build'));
- expect(mockLogger.errorText, contains('Review the android/app/build.gradle file and ensure it defines a profile build type'));
- }, overrides: <Type, Generator>{
- Logger: () => mockLogger,
- });
-
- testUsingContext('print no flavors', () {
- final GradleProject project = GradleProject(<String>['debug', 'release'],
- const <String>[], '/some/dir');
-
- printUndefinedTask(project, const BuildInfo(BuildMode.debug, 'unknown'));
- expect(mockLogger.errorText, contains('The Gradle project does not define a task suitable for the requested build'));
- expect(mockLogger.errorText, contains('The android/app/build.gradle file does not define any custom product flavors'));
- expect(mockLogger.errorText, contains('You cannot use the --flavor option'));
- }, overrides: <Type, Generator>{
- Logger: () => mockLogger,
- });
-
- testUsingContext('print flavors', () {
- final GradleProject project = GradleProject(<String>['debug', 'release'],
- const <String>['free', 'paid'], '/some/dir');
-
- printUndefinedTask(project, const BuildInfo(BuildMode.debug, 'unknown'));
- expect(mockLogger.errorText, contains('The Gradle project does not define a task suitable for the requested build'));
- expect(mockLogger.errorText, contains('The android/app/build.gradle file defines product flavors: free, paid'));
- }, overrides: <Type, Generator>{
- Logger: () => mockLogger,
- });
- });
-
group('Gradle local.properties', () {
MockLocalEngineArtifacts mockArtifacts;
MockProcessManager mockProcessManager;
@@ -861,477 +760,6 @@
});
});
- group('Gradle failures', () {
- MemoryFileSystem fs;
- Directory tempDir;
- Directory gradleWrapperDirectory;
- MockProcessManager mockProcessManager;
- String gradleBinary;
-
- setUp(() {
- fs = MemoryFileSystem();
- tempDir = fs.systemTempDirectory.createTempSync('flutter_artifacts_test.');
- gradleBinary = platform.isWindows ? 'gradlew.bat' : 'gradlew';
- gradleWrapperDirectory = fs.directory(
- fs.path.join(tempDir.path, 'bin', 'cache', 'artifacts', 'gradle_wrapper'));
- gradleWrapperDirectory.createSync(recursive: true);
- gradleWrapperDirectory
- .childFile(gradleBinary)
- .writeAsStringSync('irrelevant');
- fs.currentDirectory
- .childDirectory('android')
- .createSync();
- fs.currentDirectory
- .childDirectory('android')
- .childFile('gradle.properties')
- .writeAsStringSync('irrelevant');
- gradleWrapperDirectory
- .childDirectory('gradle')
- .childDirectory('wrapper')
- .createSync(recursive: true);
- gradleWrapperDirectory
- .childDirectory('gradle')
- .childDirectory('wrapper')
- .childFile('gradle-wrapper.jar')
- .writeAsStringSync('irrelevant');
-
- mockProcessManager = MockProcessManager();
- });
-
- testUsingContext('throws toolExit if gradle fails while downloading', () async {
- final List<String> cmd = <String>[
- fs.path.join(fs.currentDirectory.path, 'android', gradleBinary),
- '-v',
- ];
- const String errorMessage = '''
-Exception in thread "main" java.io.FileNotFoundException: https://downloads.gradle.org/distributions/gradle-4.1.1-all.zip
-at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1872)
-at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1474)
-at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:254)
-at org.gradle.wrapper.Download.downloadInternal(Download.java:58)
-at org.gradle.wrapper.Download.download(Download.java:44)
-at org.gradle.wrapper.Install\$1.call(Install.java:61)
-at org.gradle.wrapper.Install\$1.call(Install.java:48)
-at org.gradle.wrapper.ExclusiveFileAccessManager.access(ExclusiveFileAccessManager.java:65)
-at org.gradle.wrapper.Install.createDist(Install.java:48)
-at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:128)
-at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
- final ProcessException exception = ProcessException(
- gradleBinary,
- <String>['-v'],
- errorMessage,
- 1,
- );
- when(mockProcessManager.run(cmd, workingDirectory: anyNamed('workingDirectory'), environment: anyNamed('environment')))
- .thenThrow(exception);
- await expectLater(() async {
- await checkGradleDependencies();
- }, throwsToolExit(message: errorMessage));
- }, overrides: <Type, Generator>{
- Cache: () => Cache(rootOverride: tempDir),
- FileSystem: () => fs,
- ProcessManager: () => mockProcessManager,
- });
-
- testUsingContext('throw toolExit if gradle fails downloading with proxy error', () async {
- final List<String> cmd = <String>[
- fs.path.join(fs.currentDirectory.path, 'android', gradleBinary),
- '-v',
- ];
- const String errorMessage = '''
-Exception in thread "main" java.io.IOException: Unable to tunnel through proxy. Proxy returns "HTTP/1.1 400 Bad Request"
-at sun.net.www.protocol.http.HttpURLConnection.doTunneling(HttpURLConnection.java:2124)
-at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:183)
-at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1546)
-at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1474)
-at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:254)
-at org.gradle.wrapper.Download.downloadInternal(Download.java:58)
-at org.gradle.wrapper.Download.download(Download.java:44)
-at org.gradle.wrapper.Install\$1.call(Install.java:61)
-at org.gradle.wrapper.Install\$1.call(Install.java:48)
-at org.gradle.wrapper.ExclusiveFileAccessManager.access(ExclusiveFileAccessManager.java:65)
-at org.gradle.wrapper.Install.createDist(Install.java:48)
-at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:128)
-at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
- final ProcessException exception = ProcessException(
- gradleBinary,
- <String>['-v'],
- errorMessage,
- 1,
- );
- when(mockProcessManager.run(cmd, environment: anyNamed('environment'), workingDirectory: null))
- .thenThrow(exception);
- await expectLater(() async {
- await checkGradleDependencies();
- }, throwsToolExit(message: errorMessage));
- }, overrides: <Type, Generator>{
- Cache: () => Cache(rootOverride: tempDir),
- FileSystem: () => fs,
- ProcessManager: () => mockProcessManager,
- });
-
- testUsingContext('throws toolExit if gradle is missing execute permissions. ', () async {
- final List<String> cmd = <String>[
- fs.path.join(fs.currentDirectory.path, 'android', gradleBinary),
- '-v',
- ];
- final ProcessException exception = ProcessException(
- gradleBinary,
- <String>['-v'],
- 'Permission denied\nCommand: /home/android/gradlew -v',
- 1,
- );
- when(mockProcessManager.run(cmd, workingDirectory: anyNamed('workingDirectory'), environment: anyNamed('environment')))
- .thenThrow(exception);
- await expectLater(() async {
- await checkGradleDependencies();
- }, throwsToolExit(message: 'does not have permission to execute by your user'));
- }, overrides: <Type, Generator>{
- Cache: () => Cache(rootOverride: tempDir),
- FileSystem: () => fs,
- ProcessManager: () => mockProcessManager,
- });
-
- testUsingContext('throws toolExit if gradle times out waiting for exclusive access to zip', () async {
- final List<String> cmd = <String>[
- fs.path.join(fs.currentDirectory.path, 'android', gradleBinary),
- '-v',
- ];
- const String errorMessage = '''
-Exception in thread "main" java.lang.RuntimeException: Timeout of 120000 reached waiting for exclusive access to file: /User/documents/gradle-5.6.2-all.zip
- at org.gradle.wrapper.ExclusiveFileAccessManager.access(ExclusiveFileAccessManager.java:61)
- at org.gradle.wrapper.Install.createDist(Install.java:48)
- at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:128)
- at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
- final ProcessException exception = ProcessException(
- gradleBinary,
- <String>['-v'],
- errorMessage,
- 1,
- );
- when(mockProcessManager.run(cmd, workingDirectory: anyNamed('workingDirectory'), environment: anyNamed('environment')))
- .thenThrow(exception);
- await expectLater(() async {
- await checkGradleDependencies();
- }, throwsToolExit(message: errorMessage));
- }, overrides: <Type, Generator>{
- Cache: () => Cache(rootOverride: tempDir),
- FileSystem: () => fs,
- ProcessManager: () => mockProcessManager,
- });
-
- testUsingContext('throws toolExit if gradle fails to unzip file', () async {
- final List<String> cmd = <String>[
- fs.path.join(fs.currentDirectory.path, 'android', gradleBinary),
- '-v',
- ];
- const String errorMessage = '''
-Exception in thread "main" java.util.zip.ZipException: error in opening zip file /User/documents/gradle-5.6.2-all.zip
- at org.gradle.wrapper.ExclusiveFileAccessManager.access(ExclusiveFileAccessManager.java:61)
- at org.gradle.wrapper.Install.createDist(Install.java:48)
- at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:128)
- at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
- final ProcessException exception = ProcessException(
- gradleBinary,
- <String>['-v'],
- errorMessage,
- 1,
- );
- when(mockProcessManager.run(cmd, workingDirectory: anyNamed('workingDirectory'), environment: anyNamed('environment')))
- .thenThrow(exception);
- await expectLater(() async {
- await checkGradleDependencies();
- }, throwsToolExit(message: errorMessage));
- }, overrides: <Type, Generator>{
- Cache: () => Cache(rootOverride: tempDir),
- FileSystem: () => fs,
- ProcessManager: () => mockProcessManager,
- });
-
- testUsingContext('throws toolExit if remote host closes connection', () async {
- final List<String> cmd = <String>[
- fs.path.join(fs.currentDirectory.path, 'android', gradleBinary),
- '-v',
- ];
- const String errorMessage = '''
-Downloading https://services.gradle.org/distributions/gradle-5.6.2-all.zip
-
-
-Exception in thread "main" javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake
- at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:994)
- at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1367)
- at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1395)
- at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1379)
- at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559)
- at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
- at sun.net.www.protocol.http.HttpURLConnection.followRedirect0(HttpURLConnection.java:2729)
- at sun.net.www.protocol.http.HttpURLConnection.followRedirect(HttpURLConnection.java:2641)
- at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1824)
- at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1492)
- at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:263)
- at org.gradle.wrapper.Download.downloadInternal(Download.java:58)
- at org.gradle.wrapper.Download.download(Download.java:44)
- at org.gradle.wrapper.Install\$1.call(Install.java:61)
- at org.gradle.wrapper.Install\$1.call(Install.java:48)
- at org.gradle.wrapper.ExclusiveFileAccessManager.access(ExclusiveFileAccessManager.java:65)
- at org.gradle.wrapper.Install.createDist(Install.java:48)
- at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:128)
- at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
- final ProcessException exception = ProcessException(
- gradleBinary,
- <String>['-v'],
- errorMessage,
- 1,
- );
- when(mockProcessManager.run(cmd, workingDirectory: anyNamed('workingDirectory'), environment: anyNamed('environment')))
- .thenThrow(exception);
- await expectLater(() async {
- await checkGradleDependencies();
- }, throwsToolExit(message: errorMessage));
- }, overrides: <Type, Generator>{
- Cache: () => Cache(rootOverride: tempDir),
- FileSystem: () => fs,
- ProcessManager: () => mockProcessManager,
- });
-
- testUsingContext('throws toolExit if file opening fails', () async {
- final List<String> cmd = <String>[
- fs.path.join(fs.currentDirectory.path, 'android', gradleBinary),
- '-v',
- ];
- const String errorMessage = r'''
-Downloading https://services.gradle.org/distributions/gradle-3.5.0-all.zip
-
-Exception in thread "main" java.io.FileNotFoundException: https://downloads.gradle-dn.com/distributions/gradle-3.5.0-all.zip
- at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1890)
- at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1492)
- at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:263)
- at org.gradle.wrapper.Download.downloadInternal(Download.java:58)
- at org.gradle.wrapper.Download.download(Download.java:44)
- at org.gradle.wrapper.Install$1.call(Install.java:61)
- at org.gradle.wrapper.Install$1.call(Install.java:48)
- at org.gradle.wrapper.ExclusiveFileAccessManager.access(ExclusiveFileAccessManager.java:65)
- at org.gradle.wrapper.Install.createDist(Install.java:48)
- at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:128)
- at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
- final ProcessException exception = ProcessException(
- gradleBinary,
- <String>['-v'],
- errorMessage,
- 1,
- );
- when(mockProcessManager.run(cmd, workingDirectory: anyNamed('workingDirectory'), environment: anyNamed('environment')))
- .thenThrow(exception);
- await expectLater(() async {
- await checkGradleDependencies();
- }, throwsToolExit(message: errorMessage));
- }, overrides: <Type, Generator>{
- Cache: () => Cache(rootOverride: tempDir),
- FileSystem: () => fs,
- ProcessManager: () => mockProcessManager,
- });
-
- testUsingContext('throws toolExit if the connection is reset', () async {
- final List<String> cmd = <String>[
- fs.path.join(fs.currentDirectory.path, 'android', gradleBinary),
- '-v',
- ];
- const String errorMessage = '''
-Downloading https://services.gradle.org/distributions/gradle-5.6.2-all.zip
-
-
-Exception in thread "main" java.net.SocketException: Connection reset
- at java.net.SocketInputStream.read(SocketInputStream.java:210)
- at java.net.SocketInputStream.read(SocketInputStream.java:141)
- at sun.security.ssl.InputRecord.readFully(InputRecord.java:465)
- at sun.security.ssl.InputRecord.readV3Record(InputRecord.java:593)
- at sun.security.ssl.InputRecord.read(InputRecord.java:532)
- at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:975)
- at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1367)
- at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1395)
- at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1379)
- at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559)
- at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
- at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1564)
- at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1492)
- at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:263)
- at org.gradle.wrapper.Download.downloadInternal(Download.java:58)
- at org.gradle.wrapper.Download.download(Download.java:44)
- at org.gradle.wrapper.Install\$1.call(Install.java:61)
- at org.gradle.wrapper.Install\$1.call(Install.java:48)
- at org.gradle.wrapper.ExclusiveFileAccessManager.access(ExclusiveFileAccessManager.java:65)
- at org.gradle.wrapper.Install.createDist(Install.java:48)
- at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:128)
- at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
- final ProcessException exception = ProcessException(
- gradleBinary,
- <String>['-v'],
- errorMessage,
- 1,
- );
- when(mockProcessManager.run(cmd, workingDirectory: anyNamed('workingDirectory'), environment: anyNamed('environment')))
- .thenThrow(exception);
- await expectLater(() async {
- await checkGradleDependencies();
- }, throwsToolExit(message: errorMessage));
- }, overrides: <Type, Generator>{
- Cache: () => Cache(rootOverride: tempDir),
- FileSystem: () => fs,
- ProcessManager: () => mockProcessManager,
- });
-
- testUsingContext('throws toolExit if gradle exits abnormally', () async {
- final List<String> cmd = <String>[
- fs.path.join(fs.currentDirectory.path, 'android', gradleBinary),
- '-v',
- ];
- const String errorMessage = '''
-ProcessException: Process exited abnormally:
-Exception in thread "main" java.lang.NullPointerException
- at org.gradle.wrapper.BootstrapMainStarter.findLauncherJar(BootstrapMainStarter.java:34)
- at org.gradle.wrapper.BootstrapMainStarter.start(BootstrapMainStarter.java:25)
- at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:129)
- at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
- final ProcessException exception = ProcessException(
- gradleBinary,
- <String>['-v'],
- errorMessage,
- 1,
- );
- when(mockProcessManager.run(cmd, workingDirectory: anyNamed('workingDirectory'), environment: anyNamed('environment')))
- .thenThrow(exception);
- await expectLater(() async {
- await checkGradleDependencies();
- }, throwsToolExit(message: errorMessage));
- }, overrides: <Type, Generator>{
- Cache: () => Cache(rootOverride: tempDir),
- FileSystem: () => fs,
- ProcessManager: () => mockProcessManager,
- });
- });
-
- group('injectGradleWrapperIfNeeded', () {
- MemoryFileSystem memoryFileSystem;
- Directory tempDir;
- Directory gradleWrapperDirectory;
-
- setUp(() {
- memoryFileSystem = MemoryFileSystem();
- tempDir = memoryFileSystem.systemTempDirectory.createTempSync('flutter_artifacts_test.');
- gradleWrapperDirectory = memoryFileSystem.directory(
- memoryFileSystem.path.join(tempDir.path, 'bin', 'cache', 'artifacts', '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('Inject the wrapper when all files are missing', () {
- final Directory sampleAppAndroid = fs.directory('/sample-app/android');
- sampleAppAndroid.createSync(recursive: true);
-
- injectGradleWrapperIfNeeded(sampleAppAndroid);
-
- expect(sampleAppAndroid.childFile('gradlew').existsSync(), isTrue);
-
- expect(sampleAppAndroid
- .childDirectory('gradle')
- .childDirectory('wrapper')
- .childFile('gradle-wrapper.jar')
- .existsSync(), isTrue);
-
- expect(sampleAppAndroid
- .childDirectory('gradle')
- .childDirectory('wrapper')
- .childFile('gradle-wrapper.properties')
- .existsSync(), isTrue);
-
- expect(sampleAppAndroid
- .childDirectory('gradle')
- .childDirectory('wrapper')
- .childFile('gradle-wrapper.properties')
- .readAsStringSync(),
- 'distributionBase=GRADLE_USER_HOME\n'
- 'distributionPath=wrapper/dists\n'
- 'zipStoreBase=GRADLE_USER_HOME\n'
- 'zipStorePath=wrapper/dists\n'
- 'distributionUrl=https\\://services.gradle.org/distributions/gradle-5.6.2-all.zip\n');
- }, overrides: <Type, Generator>{
- Cache: () => Cache(rootOverride: tempDir),
- FileSystem: () => memoryFileSystem,
- ProcessManager: () => FakeProcessManager.any(),
- });
-
- testUsingContext('Inject the wrapper when some files are missing', () {
- final Directory sampleAppAndroid = fs.directory('/sample-app/android');
- sampleAppAndroid.createSync(recursive: true);
-
- // There's an existing gradlew
- sampleAppAndroid.childFile('gradlew').writeAsStringSync('existing gradlew');
-
- injectGradleWrapperIfNeeded(sampleAppAndroid);
-
- expect(sampleAppAndroid.childFile('gradlew').existsSync(), isTrue);
- expect(sampleAppAndroid.childFile('gradlew').readAsStringSync(),
- equals('existing gradlew'));
-
- expect(sampleAppAndroid
- .childDirectory('gradle')
- .childDirectory('wrapper')
- .childFile('gradle-wrapper.jar')
- .existsSync(), isTrue);
-
- expect(sampleAppAndroid
- .childDirectory('gradle')
- .childDirectory('wrapper')
- .childFile('gradle-wrapper.properties')
- .existsSync(), isTrue);
-
- expect(sampleAppAndroid
- .childDirectory('gradle')
- .childDirectory('wrapper')
- .childFile('gradle-wrapper.properties')
- .readAsStringSync(),
- 'distributionBase=GRADLE_USER_HOME\n'
- 'distributionPath=wrapper/dists\n'
- 'zipStoreBase=GRADLE_USER_HOME\n'
- 'zipStorePath=wrapper/dists\n'
- 'distributionUrl=https\\://services.gradle.org/distributions/gradle-5.6.2-all.zip\n');
- }, overrides: <Type, Generator>{
- Cache: () => Cache(rootOverride: tempDir),
- FileSystem: () => memoryFileSystem,
- ProcessManager: () => FakeProcessManager.any(),
- });
-
- testUsingContext('Gives executable permission to gradle', () {
- final Directory sampleAppAndroid = fs.directory('/sample-app/android');
- sampleAppAndroid.createSync(recursive: true);
-
- // Make gradlew in the wrapper executable.
- os.makeExecutable(gradleWrapperDirectory.childFile('gradlew'));
-
- injectGradleWrapperIfNeeded(sampleAppAndroid);
-
- final File gradlew = sampleAppAndroid.childFile('gradlew');
- expect(gradlew.existsSync(), isTrue);
- expect(gradlew.statSync().modeString().contains('x'), isTrue);
- }, overrides: <Type, Generator>{
- Cache: () => Cache(rootOverride: tempDir),
- FileSystem: () => memoryFileSystem,
- ProcessManager: () => FakeProcessManager.any(),
- OperatingSystemUtils: () => OperatingSystemUtils(),
- });
- });
-
group('migrateToR8', () {
MemoryFileSystem memoryFileSystem;
@@ -1532,7 +960,8 @@
plugin1=${plugin1.path}
plugin2=${plugin2.path}
''');
- final Directory buildDirectory = androidDirectory.childDirectory('build');
+ final Directory buildDirectory = androidDirectory
+ .childDirectory('build');
buildDirectory
.childDirectory('outputs')
.childDirectory('repo')
@@ -1541,7 +970,7 @@
await buildPluginsAsAar(
FlutterProject.fromPath(androidDirectory.path),
const AndroidBuildInfo(BuildInfo.release),
- buildDirectory: buildDirectory.path,
+ buildDirectory: buildDirectory,
);
final String flutterRoot = fs.path.absolute(Cache.flutterRoot);
@@ -1584,6 +1013,8 @@
});
group('gradle build', () {
+ final Usage mockUsage = MockUsage();
+
MockAndroidSdk mockAndroidSdk;
MockAndroidStudio mockAndroidStudio;
MockLocalEngineArtifacts mockArtifacts;
@@ -1623,6 +1054,399 @@
.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));
+
+ fs.directory('android')
+ .childFile('build.gradle')
+ .createSync(recursive: true);
+
+ fs.directory('android')
+ .childFile('gradle.properties')
+ .createSync(recursive: true);
+
+ fs.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,
+ ),
+ ),
+ 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);
+
+ verify(mockUsage.sendEvent(
+ any,
+ any,
+ label: 'gradle--random-event-label-failure',
+ parameters: anyNamed('parameters'),
+ )).called(1);
+
+ }, overrides: <Type, Generator>{
+ AndroidSdk: () => mockAndroidSdk,
+ Cache: () => cache,
+ Platform: () => android,
+ FileSystem: () => fs,
+ ProcessManager: () => mockProcessManager,
+ Usage: () => mockUsage,
+ });
+
+ 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);
+ });
+
+ fs.directory('android')
+ .childFile('build.gradle')
+ .createSync(recursive: true);
+
+ fs.directory('android')
+ .childFile('gradle.properties')
+ .createSync(recursive: true);
+
+ fs.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,
+ ),
+ ),
+ 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));
+
+ verify(mockUsage.sendEvent(
+ any,
+ any,
+ label: 'gradle--random-event-label-failure',
+ parameters: anyNamed('parameters'),
+ )).called(1);
+
+ }, overrides: <Type, Generator>{
+ AndroidSdk: () => mockAndroidSdk,
+ Cache: () => cache,
+ Platform: () => android,
+ FileSystem: () => fs,
+ ProcessManager: () => mockProcessManager,
+ Usage: () => mockUsage,
+ });
+
+ testUsingContext('logs success event after a sucessful 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);
+ });
+
+ fs.directory('android')
+ .childFile('build.gradle')
+ .createSync(recursive: true);
+
+ fs.directory('android')
+ .childFile('gradle.properties')
+ .createSync(recursive: true);
+
+ fs.directory('android')
+ .childDirectory('app')
+ .childFile('build.gradle')
+ ..createSync(recursive: true)
+ ..writeAsStringSync('apply from: irrelevant/flutter.gradle');
+
+ fs.directory('build')
+ .childDirectory('app')
+ .childDirectory('outputs')
+ .childDirectory('apk')
+ .childDirectory('release')
+ .childFile('app-release.apk')
+ ..createSync(recursive: true);
+
+ await buildGradleApp(
+ project: FlutterProject.current(),
+ androidBuildInfo: const AndroidBuildInfo(
+ BuildInfo(
+ BuildMode.release,
+ null,
+ ),
+ ),
+ 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',
+ ),
+ ],
+ );
+
+ verify(mockUsage.sendEvent(
+ any,
+ any,
+ label: 'gradle--random-event-label-success',
+ parameters: anyNamed('parameters'),
+ )).called(1);
+
+ }, overrides: <Type, Generator>{
+ AndroidSdk: () => mockAndroidSdk,
+ Cache: () => cache,
+ FileSystem: () => fs,
+ Platform: () => android,
+ ProcessManager: () => mockProcessManager,
+ Usage: () => mockUsage,
+ });
+
+ 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);
+ });
+
+ fs.directory('android')
+ .childFile('build.gradle')
+ .createSync(recursive: true);
+
+ fs.directory('android')
+ .childFile('gradle.properties')
+ .createSync(recursive: true);
+
+ fs.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,
+ ),
+ ),
+ 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);
+
+ verify(mockUsage.sendEvent(
+ any,
+ any,
+ label: 'gradle--random-event-label-failure',
+ parameters: anyNamed('parameters'),
+ )).called(1);
+
+ }, overrides: <Type, Generator>{
+ AndroidSdk: () => mockAndroidSdk,
+ Cache: () => cache,
+ Platform: () => android,
+ FileSystem: () => fs,
+ ProcessManager: () => mockProcessManager,
+ Usage: () => mockUsage,
+ });
+
+ testUsingContext('indicates that an APK has been built successfully', () async {
+ when(mockProcessManager.start(any,
+ workingDirectory: anyNamed('workingDirectory'),
+ environment: anyNamed('environment')))
+ .thenAnswer((_) {
+ return Future<Process>.value(
+ createMockProcess(
+ exitCode: 0,
+ stdout: '',
+ ));
+ });
+
+ fs.directory('android')
+ .childFile('build.gradle')
+ .createSync(recursive: true);
+
+ fs.directory('android')
+ .childFile('gradle.properties')
+ .createSync(recursive: true);
+
+ fs.directory('android')
+ .childDirectory('app')
+ .childFile('build.gradle')
+ ..createSync(recursive: true)
+ ..writeAsStringSync('apply from: irrelevant/flutter.gradle');
+
+ fs.directory('build')
+ .childDirectory('app')
+ .childDirectory('outputs')
+ .childDirectory('apk')
+ .childDirectory('release')
+ .childFile('app-release.apk')
+ ..createSync(recursive: true);
+
+ await buildGradleApp(
+ project: FlutterProject.current(),
+ androidBuildInfo: const AndroidBuildInfo(
+ BuildInfo(
+ BuildMode.release,
+ null,
+ ),
+ ),
+ target: 'lib/main.dart',
+ isBuildingBundle: false,
+ localGradleErrors: <GradleHandledError>[],
+ );
+
+ final BufferLogger logger = context.get<Logger>();
+ expect(
+ logger.statusText,
+ contains('Built build/app/outputs/apk/release/app-release.apk (0.0MB)'),
+ );
+
+ }, overrides: <Type, Generator>{
+ AndroidSdk: () => mockAndroidSdk,
+ Cache: () => cache,
+ FileSystem: () => fs,
+ Platform: () => android,
+ ProcessManager: () => mockProcessManager,
+ });
+
testUsingContext('build aar uses selected local engine', () async {
when(mockArtifacts.getArtifactPath(Artifact.flutterFramework,
platform: TargetPlatform.android_arm, mode: anyNamed('mode'))).thenReturn('engine');
@@ -1650,6 +1474,9 @@
fs.file('path/to/project/.android/gradle.properties')
.writeAsStringSync('irrelevant');
+ fs.file('path/to/project/.android/build.gradle')
+ .createSync(recursive: true);
+
when(mockProcessManager.run(
<String> ['/path/to/project/.android/gradlew', '-v'],
workingDirectory: anyNamed('workingDirectory'),
@@ -1675,7 +1502,7 @@
await buildGradleAar(
androidBuildInfo: const AndroidBuildInfo(BuildInfo(BuildMode.release, null)),
project: FlutterProject.current(),
- outputDir: 'build/',
+ outputDir: fs.directory('build/'),
target: '',
);
@@ -1700,14 +1527,24 @@
}
/// Generates a fake app bundle at the location [directoryName]/[fileName].
-GradleProject generateFakeAppBundle(String directoryName, String fileName) {
- final GradleProject gradleProject = MockGradleProject();
- when(gradleProject.bundleDirectory).thenReturn(fs.currentDirectory);
+FlutterProject generateFakeAppBundle(String directoryName, String fileName) {
+ final FlutterProject project = MockFlutterProject();
+ final AndroidProject androidProject = MockAndroidProject();
- final Directory aabDirectory = gradleProject.bundleDirectory.childDirectory(directoryName);
- fs.directory(aabDirectory).createSync(recursive: true);
- fs.file(fs.path.join(aabDirectory.path, fileName)).writeAsStringSync('irrelevant');
- return gradleProject;
+ when(project.isModule).thenReturn(false);
+ when(project.android).thenReturn(androidProject);
+ when(androidProject.buildDirectory).thenReturn(fs.directory('irrelevant'));
+
+ final Directory bundleDirectory = getBundleDirectory(project);
+ bundleDirectory
+ .childDirectory(directoryName)
+ ..createSync(recursive: true);
+
+ bundleDirectory
+ .childDirectory(directoryName)
+ .childFile(fileName)
+ .createSync();
+ return project;
}
Platform fakePlatform(String name) {
@@ -1716,17 +1553,19 @@
class FakeGradleUtils extends GradleUtils {
@override
- Future<String> getExecutable(FlutterProject project) async {
+ String getExecutable(FlutterProject project) {
return 'gradlew';
}
}
class MockAndroidSdk extends Mock implements AndroidSdk {}
+class MockAndroidProject extends Mock implements AndroidProject {}
class MockAndroidStudio extends Mock implements AndroidStudio {}
class MockDirectory extends Mock implements Directory {}
class MockFile extends Mock implements File {}
-class MockGradleProject extends Mock implements GradleProject {}
+class MockFlutterProject extends Mock implements FlutterProject {}
class MockLocalEngineArtifacts extends Mock implements LocalEngineArtifacts {}
class MockProcessManager extends Mock implements ProcessManager {}
class MockXcodeProjectInterpreter extends Mock implements XcodeProjectInterpreter {}
class MockitoAndroidSdk extends Mock implements AndroidSdk {}
+class MockUsage extends Mock implements Usage {}