diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart index 853d81e463..940c655cf5 100644 --- a/packages/flutter_tools/lib/src/android/gradle.dart +++ b/packages/flutter_tools/lib/src/android/gradle.dart @@ -205,6 +205,7 @@ class AndroidGradleBuilder implements AndroidBuilder { isBuildingBundle: false, localGradleErrors: gradleErrors, configOnly: configOnly, + maxRetries: 1, ); } @@ -227,6 +228,7 @@ class AndroidGradleBuilder implements AndroidBuilder { validateDeferredComponents: validateDeferredComponents, deferredComponentsEnabled: deferredComponentsEnabled, configOnly: configOnly, + maxRetries: 1, ); } diff --git a/packages/flutter_tools/lib/src/android/gradle_errors.dart b/packages/flutter_tools/lib/src/android/gradle_errors.dart index cfbb6c88c5..4d48be20f3 100644 --- a/packages/flutter_tools/lib/src/android/gradle_errors.dart +++ b/packages/flutter_tools/lib/src/android/gradle_errors.dart @@ -84,6 +84,7 @@ final List gradleErrors = [ sslExceptionHandler, zipExceptionHandler, incompatibleJavaAndGradleVersionsHandler, + remoteTerminatedHandshakeHandler, ]; const String _boxTitle = 'Flutter Fix'; @@ -700,3 +701,22 @@ final GradleHandledError incompatibleJavaAndGradleVersionsHandler = GradleHandle }, eventLabel: 'incompatible-java-gradle-version', ); + +@visibleForTesting +final GradleHandledError remoteTerminatedHandshakeHandler = GradleHandledError( + test: (String line) => line.contains('Remote host terminated the handshake'), + handler: ({ + required String line, + required FlutterProject project, + required bool usesAndroidX, + required bool multidexEnabled, + }) async { + globals.printError( + '${globals.logger.terminal.warningMark} ' + 'Gradle threw an error while downloading artifacts from the network.' + ); + + return GradleBuildStatus.retry; + }, + eventLabel: 'remote-terminated-handshake', +); 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 419de4d8f3..015897e18f 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 @@ -49,6 +49,7 @@ void main() { sslExceptionHandler, zipExceptionHandler, incompatibleJavaAndGradleVersionsHandler, + remoteTerminatedHandshakeHandler, ]) ); }); @@ -88,6 +89,50 @@ at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)'''; ProcessManager: () => processManager, }); + testUsingContext('retries if remote host terminated ssl handshake', () async { + const String errorMessage = r''' +Exception in thread "main" javax.net.ssl.SSLHandshakeException: Remote host terminated the handshake + at java.base/sun.security.ssl.SSLSocketImpl.handleEOF(SSLSocketImpl.java:1696) + at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1514) + at java.base/sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1416) + at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:456) + at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:427) + at java.base/sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:572) + at java.base/sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:197) + at java.base/sun.net.www.protocol.http.HttpURLConnection.followRedirect0(HttpURLConnection.java:2783) + at java.base/sun.net.www.protocol.http.HttpURLConnection.followRedirect(HttpURLConnection.java:2695) + at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1854) + at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1520) + at java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:250) + 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) +Caused by: java.io.EOFException: SSL peer shut down incorrectly + at java.base/sun.security.ssl.SSLSocketInputRecord.read(SSLSocketInputRecord.java:483) + at java.base/sun.security.ssl.SSLSocketInputRecord.readHeader(SSLSocketInputRecord.java:472) + at java.base/sun.security.ssl.SSLSocketInputRecord.decode(SSLSocketInputRecord.java:160) + at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:111) + at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1506)'''; + + expect(formatTestErrorMessage(errorMessage, remoteTerminatedHandshakeHandler), isTrue); + expect(await remoteTerminatedHandshakeHandler.handler( + line: '', + multidexEnabled: true, + project: FakeFlutterProject(), + usesAndroidX: true, + ), equals(GradleBuildStatus.retry)); + + expect(testLogger.errorText, + contains( + 'Gradle threw an error while downloading artifacts from the network.' + ) + ); + }); testUsingContext('retries if gradle fails downloading with proxy error', () async { const String errorMessage = r''' Exception in thread "main" java.io.IOException: Unable to tunnel through proxy. Proxy returns "HTTP/1.1 400 Bad Request"