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
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:crypto/crypto.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
import 'package:process/process.dart';
|
import 'package:process/process.dart';
|
||||||
@ -106,6 +108,9 @@ Iterable<String> _apkFilesFor(AndroidBuildInfo androidBuildInfo) {
|
|||||||
return <String>['app$flavorString-$buildType.apk'];
|
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.
|
/// An implementation of the [AndroidBuilder] that delegates to gradle.
|
||||||
class AndroidGradleBuilder implements AndroidBuilder {
|
class AndroidGradleBuilder implements AndroidBuilder {
|
||||||
AndroidGradleBuilder({
|
AndroidGradleBuilder({
|
||||||
@ -212,7 +217,7 @@ class AndroidGradleBuilder implements AndroidBuilder {
|
|||||||
/// * [target] is the target dart entry point. Typically, `lib/main.dart`.
|
/// * [target] is the target dart entry point. Typically, `lib/main.dart`.
|
||||||
/// * If [isBuildingBundle] is `true`, then the output artifact is an `*.aab`,
|
/// * If [isBuildingBundle] is `true`, then the output artifact is an `*.aab`,
|
||||||
/// otherwise the output artifact is an `*.apk`.
|
/// 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({
|
Future<void> buildGradleApp({
|
||||||
required FlutterProject project,
|
required FlutterProject project,
|
||||||
required AndroidBuildInfo androidBuildInfo,
|
required AndroidBuildInfo androidBuildInfo,
|
||||||
@ -221,7 +226,8 @@ class AndroidGradleBuilder implements AndroidBuilder {
|
|||||||
required List<GradleHandledError> localGradleErrors,
|
required List<GradleHandledError> localGradleErrors,
|
||||||
bool validateDeferredComponents = true,
|
bool validateDeferredComponents = true,
|
||||||
bool deferredComponentsEnabled = false,
|
bool deferredComponentsEnabled = false,
|
||||||
int retries = 1,
|
int retry = 0,
|
||||||
|
@visibleForTesting int? maxRetries,
|
||||||
}) async {
|
}) async {
|
||||||
assert(project != null);
|
assert(project != null);
|
||||||
assert(androidBuildInfo != null);
|
assert(androidBuildInfo != null);
|
||||||
@ -401,7 +407,7 @@ class AndroidGradleBuilder implements AndroidBuilder {
|
|||||||
'Gradle task $assembleTask failed with exit code $exitCode',
|
'Gradle task $assembleTask failed with exit code $exitCode',
|
||||||
exitCode: exitCode,
|
exitCode: exitCode,
|
||||||
);
|
);
|
||||||
} else {
|
}
|
||||||
final GradleBuildStatus status = await detectedGradleError!.handler(
|
final GradleBuildStatus status = await detectedGradleError!.handler(
|
||||||
line: detectedGradleErrorLine!,
|
line: detectedGradleErrorLine!,
|
||||||
project: project,
|
project: project,
|
||||||
@ -409,22 +415,29 @@ class AndroidGradleBuilder implements AndroidBuilder {
|
|||||||
multidexEnabled: androidBuildInfo.multidexEnabled,
|
multidexEnabled: androidBuildInfo.multidexEnabled,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (retries >= 1) {
|
if (maxRetries == null || retry < maxRetries) {
|
||||||
final String successEventLabel = 'gradle-${detectedGradleError!.eventLabel}-success';
|
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case GradleBuildStatus.retry:
|
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(
|
await buildGradleApp(
|
||||||
project: project,
|
project: project,
|
||||||
androidBuildInfo: androidBuildInfo,
|
androidBuildInfo: androidBuildInfo,
|
||||||
target: target,
|
target: target,
|
||||||
isBuildingBundle: isBuildingBundle,
|
isBuildingBundle: isBuildingBundle,
|
||||||
localGradleErrors: localGradleErrors,
|
localGradleErrors: localGradleErrors,
|
||||||
retries: retries - 1,
|
retry: retry,
|
||||||
|
maxRetries: maxRetries,
|
||||||
);
|
);
|
||||||
|
final String successEventLabel = 'gradle-${detectedGradleError!.eventLabel}-success';
|
||||||
BuildEvent(successEventLabel, type: 'gradle', flutterUsage: _usage).send();
|
BuildEvent(successEventLabel, type: 'gradle', flutterUsage: _usage).send();
|
||||||
return;
|
return;
|
||||||
case GradleBuildStatus.exit:
|
case GradleBuildStatus.exit:
|
||||||
// noop.
|
// Continue and throw tool exit.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BuildEvent('gradle-${detectedGradleError?.eventLabel}-failure', type: 'gradle', flutterUsage: _usage).send();
|
BuildEvent('gradle-${detectedGradleError?.eventLabel}-failure', type: 'gradle', flutterUsage: _usage).send();
|
||||||
@ -433,7 +446,6 @@ class AndroidGradleBuilder implements AndroidBuilder {
|
|||||||
exitCode: exitCode,
|
exitCode: exitCode,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (isBuildingBundle) {
|
if (isBuildingBundle) {
|
||||||
final File bundleFile = findBundleFile(project, buildInfo, _logger, _usage);
|
final File bundleFile = findBundleFile(project, buildInfo, _logger, _usage);
|
||||||
|
@ -226,8 +226,8 @@ final GradleHandledError networkErrorHandler = GradleHandledError(
|
|||||||
required bool multidexEnabled,
|
required bool multidexEnabled,
|
||||||
}) async {
|
}) async {
|
||||||
globals.printError(
|
globals.printError(
|
||||||
'${globals.logger.terminal.warningMark} Gradle threw an error while downloading artifacts from the network. '
|
'${globals.logger.terminal.warningMark} '
|
||||||
'Retrying to download...'
|
'Gradle threw an error while downloading artifacts from the network.'
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
final String? homeDir = globals.platform.environment['HOME'];
|
final String? homeDir = globals.platform.environment['HOME'];
|
||||||
|
@ -138,7 +138,8 @@ void main() {
|
|||||||
gradleUtils: FakeGradleUtils(),
|
gradleUtils: FakeGradleUtils(),
|
||||||
platform: FakePlatform(),
|
platform: FakePlatform(),
|
||||||
);
|
);
|
||||||
processManager.addCommand(const FakeCommand(
|
|
||||||
|
const FakeCommand fakeCmd = FakeCommand(
|
||||||
command: <String>[
|
command: <String>[
|
||||||
'gradlew',
|
'gradlew',
|
||||||
'-q',
|
'-q',
|
||||||
@ -151,23 +152,15 @@ void main() {
|
|||||||
'assembleRelease',
|
'assembleRelease',
|
||||||
],
|
],
|
||||||
exitCode: 1,
|
exitCode: 1,
|
||||||
stderr: '\nSome gradle message\n'
|
stderr: '\nSome gradle message\n',
|
||||||
));
|
);
|
||||||
processManager.addCommand(const FakeCommand(
|
|
||||||
command: <String>[
|
processManager.addCommand(fakeCmd);
|
||||||
'gradlew',
|
|
||||||
'-q',
|
const int maxRetries = 2;
|
||||||
'-Ptarget-platform=android-arm,android-arm64,android-x64',
|
for (int i = 0; i < maxRetries; i++) {
|
||||||
'-Ptarget=lib/main.dart',
|
processManager.addCommand(fakeCmd);
|
||||||
'-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'
|
|
||||||
));
|
|
||||||
|
|
||||||
fileSystem.directory('android')
|
fileSystem.directory('android')
|
||||||
.childFile('build.gradle')
|
.childFile('build.gradle')
|
||||||
@ -186,6 +179,7 @@ void main() {
|
|||||||
int testFnCalled = 0;
|
int testFnCalled = 0;
|
||||||
await expectLater(() async {
|
await expectLater(() async {
|
||||||
await builder.buildGradleApp(
|
await builder.buildGradleApp(
|
||||||
|
maxRetries: maxRetries,
|
||||||
project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
|
project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
|
||||||
androidBuildInfo: const AndroidBuildInfo(
|
androidBuildInfo: const AndroidBuildInfo(
|
||||||
BuildInfo(
|
BuildInfo(
|
||||||
@ -221,7 +215,10 @@ void main() {
|
|||||||
message: 'Gradle task assembleRelease failed with exit code 1'
|
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(
|
expect(testUsage.events, contains(
|
||||||
const TestUsageEvent(
|
const TestUsageEvent(
|
||||||
'build',
|
'build',
|
||||||
|
@ -103,7 +103,6 @@ at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
|
|||||||
expect(testLogger.errorText,
|
expect(testLogger.errorText,
|
||||||
contains(
|
contains(
|
||||||
'Gradle threw an error while downloading artifacts from the network.'
|
'Gradle threw an error while downloading artifacts from the network.'
|
||||||
'Retrying to download...'
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
@ -134,7 +133,6 @@ at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
|
|||||||
expect(testLogger.errorText,
|
expect(testLogger.errorText,
|
||||||
contains(
|
contains(
|
||||||
'Gradle threw an error while downloading artifacts from the network.'
|
'Gradle threw an error while downloading artifacts from the network.'
|
||||||
'Retrying to download...'
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
@ -156,7 +154,6 @@ Exception in thread "main" java.lang.RuntimeException: Timeout of 120000 reached
|
|||||||
expect(testLogger.errorText,
|
expect(testLogger.errorText,
|
||||||
contains(
|
contains(
|
||||||
'Gradle threw an error while downloading artifacts from the network.'
|
'Gradle threw an error while downloading artifacts from the network.'
|
||||||
'Retrying to download...'
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
@ -194,7 +191,6 @@ Exception in thread "main" javax.net.ssl.SSLHandshakeException: Remote host clos
|
|||||||
expect(testLogger.errorText,
|
expect(testLogger.errorText,
|
||||||
contains(
|
contains(
|
||||||
'Gradle threw an error while downloading artifacts from the network.'
|
'Gradle threw an error while downloading artifacts from the network.'
|
||||||
'Retrying to download...'
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
@ -224,7 +220,6 @@ Exception in thread "main" java.io.FileNotFoundException: https://downloads.grad
|
|||||||
expect(testLogger.errorText,
|
expect(testLogger.errorText,
|
||||||
contains(
|
contains(
|
||||||
'Gradle threw an error while downloading artifacts from the network.'
|
'Gradle threw an error while downloading artifacts from the network.'
|
||||||
'Retrying to download...'
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
@ -265,7 +260,6 @@ Exception in thread "main" java.net.SocketException: Connection reset
|
|||||||
expect(testLogger.errorText,
|
expect(testLogger.errorText,
|
||||||
contains(
|
contains(
|
||||||
'Gradle threw an error while downloading artifacts from the network.'
|
'Gradle threw an error while downloading artifacts from the network.'
|
||||||
'Retrying to download...'
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
@ -293,7 +287,6 @@ A problem occurred configuring root project 'android'.
|
|||||||
expect(testLogger.errorText,
|
expect(testLogger.errorText,
|
||||||
contains(
|
contains(
|
||||||
'Gradle threw an error while downloading artifacts from the network.'
|
'Gradle threw an error while downloading artifacts from the network.'
|
||||||
'Retrying to download...'
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
@ -325,7 +318,6 @@ A problem occurred configuring root project 'android'.
|
|||||||
expect(testLogger.errorText,
|
expect(testLogger.errorText,
|
||||||
contains(
|
contains(
|
||||||
'Gradle threw an error while downloading artifacts from the network.'
|
'Gradle threw an error while downloading artifacts from the network.'
|
||||||
'Retrying to download...'
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
|
Loading…
Reference in New Issue
Block a user