mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Improve Gradle retry logic (#96554)
This commit is contained in:
parent
f9921ebcda
commit
c8266d34f4
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:process/process.dart';
|
||||
@ -106,6 +108,9 @@ Iterable<String> _apkFilesFor(AndroidBuildInfo androidBuildInfo) {
|
||||
return <String>['app$flavorString-$buildType.apk'];
|
||||
}
|
||||
|
||||
// The maximum time to wait before the tool retries a Gradle build.
|
||||
const Duration kMaxRetryTime = Duration(seconds: 10);
|
||||
|
||||
/// An implementation of the [AndroidBuilder] that delegates to gradle.
|
||||
class AndroidGradleBuilder implements AndroidBuilder {
|
||||
AndroidGradleBuilder({
|
||||
@ -212,7 +217,7 @@ class AndroidGradleBuilder implements AndroidBuilder {
|
||||
/// * [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`.
|
||||
/// * [retries] is the max number of build retries in case one of the [GradleHandledError] handler
|
||||
/// * [maxRetries] If not `null`, this is the max number of build retries in case a retry is triggered.
|
||||
Future<void> buildGradleApp({
|
||||
required FlutterProject project,
|
||||
required AndroidBuildInfo androidBuildInfo,
|
||||
@ -221,7 +226,8 @@ class AndroidGradleBuilder implements AndroidBuilder {
|
||||
required List<GradleHandledError> localGradleErrors,
|
||||
bool validateDeferredComponents = true,
|
||||
bool deferredComponentsEnabled = false,
|
||||
int retries = 1,
|
||||
int retry = 0,
|
||||
@visibleForTesting int? maxRetries,
|
||||
}) async {
|
||||
assert(project != null);
|
||||
assert(androidBuildInfo != null);
|
||||
@ -401,7 +407,7 @@ class AndroidGradleBuilder implements AndroidBuilder {
|
||||
'Gradle task $assembleTask failed with exit code $exitCode',
|
||||
exitCode: exitCode,
|
||||
);
|
||||
} else {
|
||||
}
|
||||
final GradleBuildStatus status = await detectedGradleError!.handler(
|
||||
line: detectedGradleErrorLine!,
|
||||
project: project,
|
||||
@ -409,22 +415,29 @@ class AndroidGradleBuilder implements AndroidBuilder {
|
||||
multidexEnabled: androidBuildInfo.multidexEnabled,
|
||||
);
|
||||
|
||||
if (retries >= 1) {
|
||||
final String successEventLabel = 'gradle-${detectedGradleError!.eventLabel}-success';
|
||||
if (maxRetries == null || retry < maxRetries) {
|
||||
switch (status) {
|
||||
case GradleBuildStatus.retry:
|
||||
// Use binary exponential backoff before retriggering the build.
|
||||
// The expected wait times are: 100ms, 200ms, 400ms, and so on...
|
||||
final int waitTime = min(pow(2, retry).toInt() * 100, kMaxRetryTime.inMicroseconds);
|
||||
retry += 1;
|
||||
_logger.printStatus('Retrying Gradle Build: #$retry, wait time: ${waitTime}ms');
|
||||
await Future<void>.delayed(Duration(milliseconds: waitTime));
|
||||
await buildGradleApp(
|
||||
project: project,
|
||||
androidBuildInfo: androidBuildInfo,
|
||||
target: target,
|
||||
isBuildingBundle: isBuildingBundle,
|
||||
localGradleErrors: localGradleErrors,
|
||||
retries: retries - 1,
|
||||
retry: retry,
|
||||
maxRetries: maxRetries,
|
||||
);
|
||||
final String successEventLabel = 'gradle-${detectedGradleError!.eventLabel}-success';
|
||||
BuildEvent(successEventLabel, type: 'gradle', flutterUsage: _usage).send();
|
||||
return;
|
||||
case GradleBuildStatus.exit:
|
||||
// noop.
|
||||
// Continue and throw tool exit.
|
||||
}
|
||||
}
|
||||
BuildEvent('gradle-${detectedGradleError?.eventLabel}-failure', type: 'gradle', flutterUsage: _usage).send();
|
||||
@ -433,7 +446,6 @@ class AndroidGradleBuilder implements AndroidBuilder {
|
||||
exitCode: exitCode,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (isBuildingBundle) {
|
||||
final File bundleFile = findBundleFile(project, buildInfo, _logger, _usage);
|
||||
|
@ -226,8 +226,8 @@ final GradleHandledError networkErrorHandler = GradleHandledError(
|
||||
required bool multidexEnabled,
|
||||
}) async {
|
||||
globals.printError(
|
||||
'${globals.logger.terminal.warningMark} Gradle threw an error while downloading artifacts from the network. '
|
||||
'Retrying to download...'
|
||||
'${globals.logger.terminal.warningMark} '
|
||||
'Gradle threw an error while downloading artifacts from the network.'
|
||||
);
|
||||
try {
|
||||
final String? homeDir = globals.platform.environment['HOME'];
|
||||
|
@ -138,7 +138,8 @@ void main() {
|
||||
gradleUtils: FakeGradleUtils(),
|
||||
platform: FakePlatform(),
|
||||
);
|
||||
processManager.addCommand(const FakeCommand(
|
||||
|
||||
const FakeCommand fakeCmd = FakeCommand(
|
||||
command: <String>[
|
||||
'gradlew',
|
||||
'-q',
|
||||
@ -151,23 +152,15 @@ void main() {
|
||||
'assembleRelease',
|
||||
],
|
||||
exitCode: 1,
|
||||
stderr: '\nSome gradle message\n'
|
||||
));
|
||||
processManager.addCommand(const FakeCommand(
|
||||
command: <String>[
|
||||
'gradlew',
|
||||
'-q',
|
||||
'-Ptarget-platform=android-arm,android-arm64,android-x64',
|
||||
'-Ptarget=lib/main.dart',
|
||||
'-Pbase-application-name=io.flutter.app.FlutterApplication',
|
||||
'-Pdart-obfuscation=false',
|
||||
'-Ptrack-widget-creation=false',
|
||||
'-Ptree-shake-icons=false',
|
||||
'assembleRelease',
|
||||
],
|
||||
exitCode: 1,
|
||||
stderr: '\nSome gradle message\n'
|
||||
));
|
||||
stderr: '\nSome gradle message\n',
|
||||
);
|
||||
|
||||
processManager.addCommand(fakeCmd);
|
||||
|
||||
const int maxRetries = 2;
|
||||
for (int i = 0; i < maxRetries; i++) {
|
||||
processManager.addCommand(fakeCmd);
|
||||
}
|
||||
|
||||
fileSystem.directory('android')
|
||||
.childFile('build.gradle')
|
||||
@ -186,6 +179,7 @@ void main() {
|
||||
int testFnCalled = 0;
|
||||
await expectLater(() async {
|
||||
await builder.buildGradleApp(
|
||||
maxRetries: maxRetries,
|
||||
project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
|
||||
androidBuildInfo: const AndroidBuildInfo(
|
||||
BuildInfo(
|
||||
@ -221,7 +215,10 @@ void main() {
|
||||
message: 'Gradle task assembleRelease failed with exit code 1'
|
||||
));
|
||||
|
||||
expect(testFnCalled, equals(2));
|
||||
expect(logger.statusText, contains('Retrying Gradle Build: #1, wait time: 100ms'));
|
||||
expect(logger.statusText, contains('Retrying Gradle Build: #2, wait time: 200ms'));
|
||||
|
||||
expect(testFnCalled, equals(maxRetries + 1));
|
||||
expect(testUsage.events, contains(
|
||||
const TestUsageEvent(
|
||||
'build',
|
||||
|
@ -103,7 +103,6 @@ at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
|
||||
expect(testLogger.errorText,
|
||||
contains(
|
||||
'Gradle threw an error while downloading artifacts from the network.'
|
||||
'Retrying to download...'
|
||||
)
|
||||
);
|
||||
}, overrides: <Type, Generator>{
|
||||
@ -134,7 +133,6 @@ at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
|
||||
expect(testLogger.errorText,
|
||||
contains(
|
||||
'Gradle threw an error while downloading artifacts from the network.'
|
||||
'Retrying to download...'
|
||||
)
|
||||
);
|
||||
}, overrides: <Type, Generator>{
|
||||
@ -156,7 +154,6 @@ Exception in thread "main" java.lang.RuntimeException: Timeout of 120000 reached
|
||||
expect(testLogger.errorText,
|
||||
contains(
|
||||
'Gradle threw an error while downloading artifacts from the network.'
|
||||
'Retrying to download...'
|
||||
)
|
||||
);
|
||||
}, overrides: <Type, Generator>{
|
||||
@ -194,7 +191,6 @@ Exception in thread "main" javax.net.ssl.SSLHandshakeException: Remote host clos
|
||||
expect(testLogger.errorText,
|
||||
contains(
|
||||
'Gradle threw an error while downloading artifacts from the network.'
|
||||
'Retrying to download...'
|
||||
)
|
||||
);
|
||||
}, overrides: <Type, Generator>{
|
||||
@ -224,7 +220,6 @@ Exception in thread "main" java.io.FileNotFoundException: https://downloads.grad
|
||||
expect(testLogger.errorText,
|
||||
contains(
|
||||
'Gradle threw an error while downloading artifacts from the network.'
|
||||
'Retrying to download...'
|
||||
)
|
||||
);
|
||||
}, overrides: <Type, Generator>{
|
||||
@ -265,7 +260,6 @@ Exception in thread "main" java.net.SocketException: Connection reset
|
||||
expect(testLogger.errorText,
|
||||
contains(
|
||||
'Gradle threw an error while downloading artifacts from the network.'
|
||||
'Retrying to download...'
|
||||
)
|
||||
);
|
||||
}, overrides: <Type, Generator>{
|
||||
@ -293,7 +287,6 @@ A problem occurred configuring root project 'android'.
|
||||
expect(testLogger.errorText,
|
||||
contains(
|
||||
'Gradle threw an error while downloading artifacts from the network.'
|
||||
'Retrying to download...'
|
||||
)
|
||||
);
|
||||
}, overrides: <Type, Generator>{
|
||||
@ -325,7 +318,6 @@ A problem occurred configuring root project 'android'.
|
||||
expect(testLogger.errorText,
|
||||
contains(
|
||||
'Gradle threw an error while downloading artifacts from the network.'
|
||||
'Retrying to download...'
|
||||
)
|
||||
);
|
||||
}, overrides: <Type, Generator>{
|
||||
|
Loading…
Reference in New Issue
Block a user