Unverified Commit 972b4474 authored by Chris Yang's avatar Chris Yang Committed by GitHub

[tool] delete xcresult bundle file before each xcode retry. (#127144)

xcodebuild command generates a xcresult bundle file on each run, however, it doesn't delete the file generated from previous run and will throw an error if the exact file already exists.

In tool, we manually delete the file after each `flutter build` or `flutter run` command. However, there are some internal logic where xcodebuild retries multiple times. 

This PR deletes the xcresult bundle file at the start of each retry if it exists.

Fixes https://github.com/flutter/flutter/issues/127119
parent b9e8b0a8
......@@ -38,6 +38,9 @@ import 'xcode_build_settings.dart';
import 'xcodeproj.dart';
import 'xcresult.dart';
const String kConcurrentRunFailureMessage1 = 'database is locked';
const String kConcurrentRunFailureMessage2 = 'there are two concurrent builds running';
class IMobileDevice {
IMobileDevice({
required Artifacts artifacts,
......@@ -354,9 +357,10 @@ Future<XcodeBuildResult> buildXcodeProject({
buildCommands.add('SCRIPT_OUTPUT_STREAM_FILE=${scriptOutputPipeFile.absolute.path}');
}
final File resultBundleFile = tempDir.childFile(_kResultBundlePath);
buildCommands.addAll(<String>[
'-resultBundlePath',
tempDir.childFile(_kResultBundlePath).absolute.path,
resultBundleFile.absolute.path,
'-resultBundleVersion',
_kResultBundleVersion,
]);
......@@ -378,7 +382,7 @@ Future<XcodeBuildResult> buildXcodeProject({
final Stopwatch sw = Stopwatch()..start();
initialBuildStatus = globals.logger.startProgress('Running Xcode build...');
buildResult = await _runBuildWithRetries(buildCommands, app);
buildResult = await _runBuildWithRetries(buildCommands, app, resultBundleFile);
// Notifies listener that no more output is coming.
scriptOutputPipeFile?.writeAsStringSync('all done');
......@@ -508,12 +512,15 @@ Future<void> removeFinderExtendedAttributes(FileSystemEntity projectDirectory, P
}
}
Future<RunResult?> _runBuildWithRetries(List<String> buildCommands, BuildableIOSApp app) async {
Future<RunResult?> _runBuildWithRetries(List<String> buildCommands, BuildableIOSApp app, File resultBundleFile) async {
int buildRetryDelaySeconds = 1;
int remainingTries = 8;
RunResult? buildResult;
while (remainingTries > 0) {
if (resultBundleFile.existsSync()) {
resultBundleFile.deleteSync(recursive: true);
}
remainingTries--;
buildRetryDelaySeconds *= 2;
......@@ -546,8 +553,8 @@ Future<RunResult?> _runBuildWithRetries(List<String> buildCommands, BuildableIOS
bool _isXcodeConcurrentBuildFailure(RunResult result) {
return result.exitCode != 0 &&
result.stdout.contains('database is locked') &&
result.stdout.contains('there are two concurrent builds running');
result.stdout.contains(kConcurrentRunFailureMessage1) &&
result.stdout.contains(kConcurrentRunFailureMessage2);
}
Future<void> diagnoseXcodeBuildFailure(XcodeBuildResult result, Usage flutterUsage, Logger logger) async {
......
......@@ -5,6 +5,7 @@
import 'package:args/command_runner.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/android/android_sdk.dart';
import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/os.dart';
......@@ -15,6 +16,7 @@ import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/build.dart';
import 'package:flutter_tools/src/commands/build_ios.dart';
import 'package:flutter_tools/src/ios/code_signing.dart';
import 'package:flutter_tools/src/ios/mac.dart';
import 'package:flutter_tools/src/ios/xcodeproj.dart';
import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:test/fake.dart';
......@@ -636,6 +638,50 @@ void main() {
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
});
testUsingContext('Delete xcresult bundle before each xcodebuild command.', () async {
final BuildCommand command = BuildCommand(
androidSdk: FakeAndroidSdk(),
buildSystem: TestBuildSystem.all(BuildResult(success: true)),
fileSystem: MemoryFileSystem.test(),
logger: BufferLogger.test(),
osUtils: FakeOperatingSystemUtils(),
);
createMinimalMockProjectFiles();
await createTestCommandRunner(command).run(const <String>['build', 'ios', '--no-pub']);
expect(testLogger.statusText, contains('Xcode build done.'));
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
xattrCommand,
// Intentionally fail the first xcodebuild command with concurrent run failure message.
setUpFakeXcodeBuildHandler(
exitCode: 1,
stdout: '$kConcurrentRunFailureMessage1 $kConcurrentRunFailureMessage2',
onRun: () {
fileSystem.systemTempDirectory.childFile(_xcBundleFilePath).createSync();
}
),
// The second xcodebuild is triggered due to above concurrent run failure message.
setUpFakeXcodeBuildHandler(
onRun: () {
// If the file is not cleaned, throw an error, test failure.
if (fileSystem.systemTempDirectory.childFile(_xcBundleFilePath).existsSync()) {
throwToolExit('xcresult bundle file existed.', exitCode: 2);
}
fileSystem.systemTempDirectory.childFile(_xcBundleFilePath).createSync();
}
),
setUpXCResultCommand(stdout: kSampleResultJsonNoIssues),
setUpRsyncCommand(),
],
),
Platform: () => macosPlatform,
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
});
testUsingContext('Failed to parse xcresult but display missing provisioning profile issue from stdout.', () async {
final BuildCommand command = BuildCommand(
androidSdk: FakeAndroidSdk(),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment