Forward ProcessException to error handlers (#44783)
diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart
index 3c509e8..a4b5594 100644
--- a/packages/flutter_tools/lib/src/android/gradle.dart
+++ b/packages/flutter_tools/lib/src/android/gradle.dart
@@ -334,39 +334,48 @@
}
command.add(assembleTask);
- final Stopwatch sw = Stopwatch()..start();
- int exitCode = 1;
GradleHandledError detectedGradleError;
String detectedGradleErrorLine;
+ String consumeLog(String line) {
+ // This message was removed from first-party plugins,
+ // but older plugin versions still display this message.
+ if (androidXPluginWarningRegex.hasMatch(line)) {
+ // Don't pipe.
+ return null;
+ }
+ if (detectedGradleError != null) {
+ // Pipe stdout/stderr from Gradle.
+ return line;
+ }
+ for (final GradleHandledError gradleError in localGradleErrors) {
+ if (gradleError.test(line)) {
+ detectedGradleErrorLine = line;
+ detectedGradleError = gradleError;
+ // The first error match wins.
+ break;
+ }
+ }
+ // Pipe stdout/stderr from Gradle.
+ return line;
+ }
+
+ final Stopwatch sw = Stopwatch()..start();
+ int exitCode = 1;
try {
exitCode = await processUtils.stream(
command,
workingDirectory: project.android.hostAppGradleRoot.path,
allowReentrantFlutter: true,
environment: gradleEnvironment,
- mapFunction: (String line) {
- // This message was removed from first-party plugins,
- // but older plugin versions still display this message.
- if (androidXPluginWarningRegex.hasMatch(line)) {
- // Don't pipe.
- return null;
- }
- if (detectedGradleError != null) {
- // Pipe stdout/stderr from Gradle.
- return line;
- }
- for (final GradleHandledError gradleError in localGradleErrors) {
- if (gradleError.test(line)) {
- detectedGradleErrorLine = line;
- detectedGradleError = gradleError;
- // The first error match wins.
- break;
- }
- }
- // Pipe stdout/stderr from Gradle.
- return line;
- },
+ mapFunction: consumeLog,
);
+ } on ProcessException catch(exception) {
+ consumeLog(exception.toString());
+ // Rethrow the exception if the error isn't handled by any of the
+ // `localGradleErrors`.
+ if (detectedGradleError == null) {
+ rethrow;
+ }
} finally {
status.stop();
}
diff --git a/packages/flutter_tools/lib/src/android/gradle_errors.dart b/packages/flutter_tools/lib/src/android/gradle_errors.dart
index ac94bd1..7aa55c8 100644
--- a/packages/flutter_tools/lib/src/android/gradle_errors.dart
+++ b/packages/flutter_tools/lib/src/android/gradle_errors.dart
@@ -85,7 +85,7 @@
bool usesAndroidX,
bool shouldBuildPluginAsAar,
}) async {
- printStatus('$warningMark Gradle does not have permission to execute by your user.', emphasis: true);
+ printStatus('$warningMark Gradle does not have execution permission.', emphasis: true);
printStatus(
'You should change the ownership of the project directory to your user, '
'or move the project to a directory with execute permissions.',
diff --git a/packages/flutter_tools/test/general.shard/android/gradle_errors_test.dart b/packages/flutter_tools/test/general.shard/android/gradle_errors_test.dart
index 8e05d75..19670e0 100644
--- a/packages/flutter_tools/test/general.shard/android/gradle_errors_test.dart
+++ b/packages/flutter_tools/test/general.shard/android/gradle_errors_test.dart
@@ -231,7 +231,7 @@
final BufferLogger logger = context.get<Logger>();
expect(
logger.statusText,
- contains('Gradle does not have permission to execute by your user.'),
+ contains('Gradle does not have execution permission.'),
);
expect(
logger.statusText,
@@ -399,7 +399,7 @@
final BufferLogger logger = context.get<Logger>();
expect(
logger.statusText,
- contains('Gradle does not have permission to execute by your user.'),
+ contains('Gradle does not have execution permission.'),
);
expect(
logger.statusText,
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 294716e..8f13098 100644
--- a/packages/flutter_tools/test/general.shard/android/gradle_test.dart
+++ b/packages/flutter_tools/test/general.shard/android/gradle_test.dart
@@ -1266,6 +1266,123 @@
Usage: () => mockUsage,
});
+ testUsingContext('recognizes process exceptions - tool exit', () async {
+ when(mockProcessManager.start(any,
+ workingDirectory: anyNamed('workingDirectory'),
+ environment: anyNamed('environment')))
+ .thenThrow(const ProcessException('', <String>[], 'Some gradle message'));
+
+ 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('rethrows unrecognized ProcessException', () async {
+ when(mockProcessManager.start(any,
+ workingDirectory: anyNamed('workingDirectory'),
+ environment: anyNamed('environment')))
+ .thenThrow(const ProcessException('', <String>[], 'Unrecognized'));
+
+ 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');
+
+ await expectLater(() async {
+ await buildGradleApp(
+ project: FlutterProject.current(),
+ androidBuildInfo: const AndroidBuildInfo(
+ BuildInfo(
+ BuildMode.release,
+ null,
+ ),
+ ),
+ target: 'lib/main.dart',
+ isBuildingBundle: false,
+ localGradleErrors: const <GradleHandledError>[],
+ );
+ },
+ throwsA(isInstanceOf<ProcessException>()));
+
+ }, overrides: <Type, Generator>{
+ AndroidSdk: () => mockAndroidSdk,
+ Cache: () => cache,
+ Platform: () => android,
+ FileSystem: () => fs,
+ ProcessManager: () => mockProcessManager,
+ });
+
testUsingContext('logs success event after a sucessful retry', () async {
int testFnCalled = 0;
when(mockProcessManager.start(any,