Unverified Commit 2892b575 authored by Victoria Ashworth's avatar Victoria Ashworth Committed by GitHub

Reland "Print pretty error when xcodebuild fails due to missing simulator #130286" (#130506)

Reland https://github.com/flutter/flutter/pull/130286 with fix.

It failed the first time because of a discrepancy between the master branch and my branch (see https://github.com/flutter/flutter/pull/130504#issue-1803449182 for more info).
parent 315ebafe
......@@ -4,6 +4,7 @@
import 'dart:async';
import 'package:meta/meta.dart';
import 'package:process/process.dart';
import '../artifacts.dart';
......@@ -41,6 +42,21 @@ import 'xcresult.dart';
const String kConcurrentRunFailureMessage1 = 'database is locked';
const String kConcurrentRunFailureMessage2 = 'there are two concurrent builds running';
/// User message when missing platform required to use Xcode.
///
/// Starting with Xcode 15, the simulator is no longer downloaded with Xcode
/// and must be downloaded and installed separately.
@visibleForTesting
String missingPlatformInstructions(String simulatorVersion) => '''
════════════════════════════════════════════════════════════════════════════════
$simulatorVersion is not installed. To download and install the platform, open
Xcode, select Xcode > Settings > Platforms, and click the GET button for the
required platform.
For more information, please visit:
https://developer.apple.com/documentation/xcode/installing-additional-simulator-runtimes
════════════════════════════════════════════════════════════════════════════════''';
class IMobileDevice {
IMobileDevice({
required Artifacts artifacts,
......@@ -700,6 +716,11 @@ _XCResultIssueHandlingResult _handleXCResultIssue({required XCResultIssue issue,
return _XCResultIssueHandlingResult(requiresProvisioningProfile: true, hasProvisioningProfileIssue: true);
} else if (message.toLowerCase().contains('provisioning profile')) {
return _XCResultIssueHandlingResult(requiresProvisioningProfile: false, hasProvisioningProfileIssue: true);
} else if (message.toLowerCase().contains('ineligible destinations')) {
final String? missingPlatform = _parseMissingPlatform(message);
if (missingPlatform != null) {
return _XCResultIssueHandlingResult(requiresProvisioningProfile: false, hasProvisioningProfileIssue: false, missingPlatform: missingPlatform);
}
}
return _XCResultIssueHandlingResult(requiresProvisioningProfile: false, hasProvisioningProfileIssue: false);
}
......@@ -709,6 +730,7 @@ bool _handleIssues(XCResult? xcResult, Logger logger, XcodeBuildExecution? xcode
bool requiresProvisioningProfile = false;
bool hasProvisioningProfileIssue = false;
bool issueDetected = false;
String? missingPlatform;
if (xcResult != null && xcResult.parseSuccess) {
for (final XCResultIssue issue in xcResult.issues) {
......@@ -719,6 +741,7 @@ bool _handleIssues(XCResult? xcResult, Logger logger, XcodeBuildExecution? xcode
if (handlingResult.requiresProvisioningProfile) {
requiresProvisioningProfile = true;
}
missingPlatform = handlingResult.missingPlatform;
issueDetected = true;
}
} else if (xcResult != null) {
......@@ -738,6 +761,8 @@ bool _handleIssues(XCResult? xcResult, Logger logger, XcodeBuildExecution? xcode
logger.printError(' open ios/Runner.xcworkspace');
logger.printError('');
logger.printError("Also try selecting 'Product > Build' to fix the problem.");
} else if (missingPlatform != null) {
logger.printError(missingPlatformInstructions(missingPlatform), emphasis: true);
}
return issueDetected;
......@@ -773,18 +798,41 @@ void _parseIssueInStdout(XcodeBuildExecution xcodeBuildExecution, Logger logger,
&& (result.stdout?.contains('requires a provisioning profile. Select a provisioning profile in the Signing & Capabilities editor') ?? false)) {
logger.printError(noProvisioningProfileInstruction, emphasis: true);
}
if (stderr != null && stderr.contains('Ineligible destinations')) {
final String? version = _parseMissingPlatform(stderr);
if (version != null) {
logger.printError(missingPlatformInstructions(version), emphasis: true);
}
}
}
String? _parseMissingPlatform(String message) {
final RegExp pattern = RegExp(r'error:(.*?) is not installed\. To use with Xcode, first download and install the platform');
final RegExpMatch? match = pattern.firstMatch(message);
if (match != null) {
final String? version = match.group(1);
return version;
}
return null;
}
// The result of [_handleXCResultIssue].
class _XCResultIssueHandlingResult {
_XCResultIssueHandlingResult({required this.requiresProvisioningProfile, required this.hasProvisioningProfileIssue});
_XCResultIssueHandlingResult({
required this.requiresProvisioningProfile,
required this.hasProvisioningProfileIssue,
this.missingPlatform,
});
// An issue indicates that user didn't provide the provisioning profile.
final bool requiresProvisioningProfile;
// An issue indicates that there is a provisioning profile issue.
final bool hasProvisioningProfileIssue;
final String? missingPlatform;
}
const String _kResultBundlePath = 'temporary_xcresult_bundle';
......
......@@ -104,6 +104,13 @@ class XCResult {
issueDiscarder: issueDiscarders,
));
}
final Object? actionsMap = resultJson['actions'];
if (actionsMap is Map<String, Object?>) {
final List<XCResultIssue> actionIssues = _parseActionIssues(actionsMap, issueDiscarders: issueDiscarders);
issues.addAll(actionIssues);
}
return XCResult._(issues: issues);
}
......@@ -383,3 +390,84 @@ List<XCResultIssue> _parseIssuesFromIssueSummariesJson({
}
return issues;
}
List<XCResultIssue> _parseActionIssues(
Map<String, Object?> actionsMap, {
required List<XCResultIssueDiscarder> issueDiscarders,
}) {
// Example of json:
// {
// "actions" : {
// "_values" : [
// {
// "actionResult" : {
// "_type" : {
// "_name" : "ActionResult"
// },
// "issues" : {
// "_type" : {
// "_name" : "ResultIssueSummaries"
// },
// "testFailureSummaries" : {
// "_type" : {
// "_name" : "Array"
// },
// "_values" : [
// {
// "_type" : {
// "_name" : "TestFailureIssueSummary",
// "_supertype" : {
// "_name" : "IssueSummary"
// }
// },
// "issueType" : {
// "_type" : {
// "_name" : "String"
// },
// "_value" : "Uncategorized"
// },
// "message" : {
// "_type" : {
// "_name" : "String"
// },
// "_value" : "Unable to find a destination matching the provided destination specifier:\n\t\t{ id:1234D567-890C-1DA2-34E5-F6789A0123C4 }\n\n\tIneligible destinations for the \"Runner\" scheme:\n\t\t{ platform:iOS, id:dvtdevice-DVTiPhonePlaceholder-iphoneos:placeholder, name:Any iOS Device, error:iOS 17.0 is not installed. To use with Xcode, first download and install the platform }"
// }
// }
// ]
// }
// }
// }
// }
// ]
// }
// }
final List<XCResultIssue> issues = <XCResultIssue>[];
final Object? actionsValues = actionsMap['_values'];
if (actionsValues is! List<Object?>) {
return issues;
}
for (final Object? actionValue in actionsValues) {
if (actionValue is!Map<String, Object?>) {
continue;
}
final Object? actionResult = actionValue['actionResult'];
if (actionResult is! Map<String, Object?>) {
continue;
}
final Object? actionResultIssues = actionResult['issues'];
if (actionResultIssues is! Map<String, Object?>) {
continue;
}
final Object? testFailureSummaries = actionResultIssues['testFailureSummaries'];
if (testFailureSummaries is Map<String, Object?>) {
issues.addAll(_parseIssuesFromIssueSummariesJson(
type: XCResultIssueType.error,
issueSummariesJson: testFailureSummaries,
issueDiscarder: issueDiscarders,
));
}
}
return issues;
}
......@@ -638,6 +638,37 @@ void main() {
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
});
testUsingContext('Extra error message for missing simulator platform in xcresult bundle.', () async {
final BuildCommand command = BuildCommand(
androidSdk: FakeAndroidSdk(),
buildSystem: TestBuildSystem.all(BuildResult(success: true)),
fileSystem: MemoryFileSystem.test(),
logger: BufferLogger.test(),
osUtils: FakeOperatingSystemUtils(),
);
createMinimalMockProjectFiles();
await expectLater(
createTestCommandRunner(command).run(const <String>['build', 'ios', '--no-pub']),
throwsToolExit(),
);
expect(testLogger.errorText, contains(missingPlatformInstructions('iOS 17.0')));
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
xattrCommand,
setUpFakeXcodeBuildHandler(exitCode: 1, onRun: () {
fileSystem.systemTempDirectory.childDirectory(_xcBundleDirectoryPath).createSync();
}),
setUpXCResultCommand(stdout: kSampleResultJsonWithActionIssues),
setUpRsyncCommand(),
]),
Platform: () => macosPlatform,
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
});
testUsingContext('Delete xcresult bundle before each xcodebuild command.', () async {
final BuildCommand command = BuildCommand(
androidSdk: FakeAndroidSdk(),
......
......@@ -245,6 +245,44 @@ Error launching application on iPhone.''',
);
});
testWithoutContext('fallback to stdout: Ineligible destinations', () async {
final Map<String, String> buildSettingsWithDevTeam = <String, String>{
'PRODUCT_BUNDLE_IDENTIFIER': 'test.app',
'DEVELOPMENT_TEAM': 'a team',
};
final XcodeBuildResult buildResult = XcodeBuildResult(
success: false,
stderr: '''
Launching lib/main.dart on iPhone in debug mode...
Signing iOS app for device deployment using developer identity: "iPhone Developer: test@flutter.io (1122334455)"
Running Xcode build... 1.3s
Failed to build iOS app
Error output from Xcode build:
xcodebuild: error: Unable to find a destination matching the provided destination specifier:
{ id:1234D567-890C-1DA2-34E5-F6789A0123C4 }
Ineligible destinations for the "Runner" scheme:
{ platform:iOS, id:dvtdevice-DVTiPhonePlaceholder-iphoneos:placeholder, name:Any iOS Device, error:iOS 17.0 is not installed. To use with Xcode, first download and install the platform }
Could not build the precompiled application for the device.
Error launching application on iPhone.''',
xcodeBuildExecution: XcodeBuildExecution(
buildCommands: <String>['xcrun', 'xcodebuild', 'blah'],
appDirectory: '/blah/blah',
environmentType: EnvironmentType.physical,
buildSettings: buildSettingsWithDevTeam,
),
);
await diagnoseXcodeBuildFailure(buildResult, testUsage, logger);
expect(
logger.errorText,
contains(missingPlatformInstructions('iOS 17.0')),
);
});
testWithoutContext('No development team shows message', () async {
final XcodeBuildResult buildResult = XcodeBuildResult(
success: false,
......
......@@ -204,6 +204,19 @@ void main() {
expect(result.parsingErrorMessage, isNull);
});
testWithoutContext(
'correctly parse sample result json with action issues.', () async {
final XCResultGenerator generator = setupGenerator(resultJson: kSampleResultJsonWithActionIssues);
final XCResultIssueDiscarder discarder = XCResultIssueDiscarder(typeMatcher: XCResultIssueType.warning);
final XCResult result = await generator.generate(issueDiscarders: <XCResultIssueDiscarder>[discarder]);
expect(result.issues.length, 1);
expect(result.issues.first.type, XCResultIssueType.error);
expect(result.issues.first.subType, 'Uncategorized');
expect(result.issues.first.message, contains('Unable to find a destination matching the provided destination specifier'));
expect(result.parseSuccess, isTrue);
expect(result.parsingErrorMessage, isNull);
});
testWithoutContext(
'error: `xcresulttool get` process fail should return an `XCResult` with stderr as `parsingErrorMessage`.',
() async {
......
......@@ -378,3 +378,252 @@ const String kSampleResultJsonWithProvisionIssue = r'''
}
}
''';
/// An example xcresult bundle json that contains action issues.
const String kSampleResultJsonWithActionIssues = r'''
{
"_type" : {
"_name" : "ActionsInvocationRecord"
},
"actions" : {
"_type" : {
"_name" : "Array"
},
"_values" : [
{
"_type" : {
"_name" : "ActionRecord"
},
"actionResult" : {
"_type" : {
"_name" : "ActionResult"
},
"coverage" : {
"_type" : {
"_name" : "CodeCoverageInfo"
}
},
"issues" : {
"_type" : {
"_name" : "ResultIssueSummaries"
},
"testFailureSummaries" : {
"_type" : {
"_name" : "Array"
},
"_values" : [
{
"_type" : {
"_name" : "TestFailureIssueSummary",
"_supertype" : {
"_name" : "IssueSummary"
}
},
"issueType" : {
"_type" : {
"_name" : "String"
},
"_value" : "Uncategorized"
},
"message" : {
"_type" : {
"_name" : "String"
},
"_value" : "Unable to find a destination matching the provided destination specifier:\n\t\t{ id:1234D567-890C-1DA2-34E5-F6789A0123C4 }\n\n\tIneligible destinations for the \"Runner\" scheme:\n\t\t{ platform:iOS, id:dvtdevice-DVTiPhonePlaceholder-iphoneos:placeholder, name:Any iOS Device, error:iOS 17.0 is not installed. To use with Xcode, first download and install the platform }"
}
}
]
}
},
"logRef" : {
"_type" : {
"_name" : "Reference"
},
"id" : {
"_type" : {
"_name" : "String"
},
"_value" : "0~5X-qvql8_ppq0bj9taBMeZd4L2lXQagy1twsFRWwc06r42obpBZfP87uKnGO98mp5CUz1Ppr1knHiTMH9tOuwQ=="
},
"targetType" : {
"_type" : {
"_name" : "TypeDefinition"
},
"name" : {
"_type" : {
"_name" : "String"
},
"_value" : "ActivityLogSection"
}
}
},
"metrics" : {
"_type" : {
"_name" : "ResultMetrics"
}
},
"resultName" : {
"_type" : {
"_name" : "String"
},
"_value" : "All Tests"
},
"status" : {
"_type" : {
"_name" : "String"
},
"_value" : "failedToStart"
},
"testsRef" : {
"_type" : {
"_name" : "Reference"
},
"id" : {
"_type" : {
"_name" : "String"
},
"_value" : "0~Dmuz8-g6YRb8HPVbTUXJD21oy3r5jxIGi-njd2Lc43yR5JlJf7D78HtNn2BsrF5iw1uYMnsuJ9xFDV7ZAmwhGg=="
},
"targetType" : {
"_type" : {
"_name" : "TypeDefinition"
},
"name" : {
"_type" : {
"_name" : "String"
},
"_value" : "ActionTestPlanRunSummaries"
}
}
}
},
"buildResult" : {
"_type" : {
"_name" : "ActionResult"
},
"coverage" : {
"_type" : {
"_name" : "CodeCoverageInfo"
}
},
"issues" : {
"_type" : {
"_name" : "ResultIssueSummaries"
}
},
"metrics" : {
"_type" : {
"_name" : "ResultMetrics"
}
},
"resultName" : {
"_type" : {
"_name" : "String"
},
"_value" : "Build Succeeded"
},
"status" : {
"_type" : {
"_name" : "String"
},
"_value" : "succeeded"
}
},
"endedTime" : {
"_type" : {
"_name" : "Date"
},
"_value" : "2023-07-10T12:52:22.592-0500"
},
"runDestination" : {
"_type" : {
"_name" : "ActionRunDestinationRecord"
},
"localComputerRecord" : {
"_type" : {
"_name" : "ActionDeviceRecord"
},
"platformRecord" : {
"_type" : {
"_name" : "ActionPlatformRecord"
}
}
},
"targetDeviceRecord" : {
"_type" : {
"_name" : "ActionDeviceRecord"
},
"platformRecord" : {
"_type" : {
"_name" : "ActionPlatformRecord"
}
}
},
"targetSDKRecord" : {
"_type" : {
"_name" : "ActionSDKRecord"
}
}
},
"schemeCommandName" : {
"_type" : {
"_name" : "String"
},
"_value" : "Test"
},
"schemeTaskName" : {
"_type" : {
"_name" : "String"
},
"_value" : "BuildAndAction"
},
"startedTime" : {
"_type" : {
"_name" : "Date"
},
"_value" : "2023-07-10T12:52:22.592-0500"
},
"title" : {
"_type" : {
"_name" : "String"
},
"_value" : "RunnerTests.xctest"
}
}
]
},
"issues" : {
"_type" : {
"_name" : "ResultIssueSummaries"
}
},
"metadataRef" : {
"_type" : {
"_name" : "Reference"
},
"id" : {
"_type" : {
"_name" : "String"
},
"_value" : "0~pY0GqmiVE6Q3qlWdLJDp_PnrsUKsJ7KKM1zKGnvEZOWGdBeGNArjjU62kgF2UBFdQLdRmf5SGpImQfJB6e7vDQ=="
},
"targetType" : {
"_type" : {
"_name" : "TypeDefinition"
},
"name" : {
"_type" : {
"_name" : "String"
},
"_value" : "ActionsInvocationMetadata"
}
}
},
"metrics" : {
"_type" : {
"_name" : "ResultMetrics"
}
}
}
''';
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