Unverified Commit 1c4128c7 authored by Chris Yang's avatar Chris Yang Committed by GitHub

Xcode error message (#94747)

parent 710502c4
...@@ -32,6 +32,7 @@ import 'migrations/remove_framework_link_and_embedding_migration.dart'; ...@@ -32,6 +32,7 @@ import 'migrations/remove_framework_link_and_embedding_migration.dart';
import 'migrations/xcode_build_system_migration.dart'; import 'migrations/xcode_build_system_migration.dart';
import 'xcode_build_settings.dart'; import 'xcode_build_settings.dart';
import 'xcodeproj.dart'; import 'xcodeproj.dart';
import 'xcresult.dart';
class IMobileDevice { class IMobileDevice {
IMobileDevice({ IMobileDevice({
...@@ -315,11 +316,13 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -315,11 +316,13 @@ Future<XcodeBuildResult> buildXcodeProject({
Status? buildSubStatus; Status? buildSubStatus;
Status? initialBuildStatus; Status? initialBuildStatus;
Directory? tempDir;
File? scriptOutputPipeFile; File? scriptOutputPipeFile;
RunResult? buildResult;
XCResult? xcResult;
final Directory tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_ios_build_temp_dir');
try {
if (globals.logger.hasTerminal) { if (globals.logger.hasTerminal) {
tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_build_log_pipe.');
scriptOutputPipeFile = tempDir.childFile('pipe_to_stdout'); scriptOutputPipeFile = tempDir.childFile('pipe_to_stdout');
globals.os.makePipe(scriptOutputPipeFile.path); globals.os.makePipe(scriptOutputPipeFile.path);
...@@ -330,8 +333,6 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -330,8 +333,6 @@ Future<XcodeBuildResult> buildXcodeProject({
buildSubStatus?.stop(); buildSubStatus?.stop();
buildSubStatus = null; buildSubStatus = null;
if (line == 'all done') { if (line == 'all done') {
// Free pipe file.
tempDir?.deleteSync(recursive: true);
return; return;
} }
} else { } else {
...@@ -352,6 +353,13 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -352,6 +353,13 @@ Future<XcodeBuildResult> buildXcodeProject({
buildCommands.add('SCRIPT_OUTPUT_STREAM_FILE=${scriptOutputPipeFile.absolute.path}'); buildCommands.add('SCRIPT_OUTPUT_STREAM_FILE=${scriptOutputPipeFile.absolute.path}');
} }
buildCommands.addAll(<String>[
'-resultBundlePath',
tempDir.childFile(_kResultBundlePath).absolute.path,
'-resultBundleVersion',
_kResultBundleVersion
]);
// Don't log analytics for downstream Flutter commands. // Don't log analytics for downstream Flutter commands.
// e.g. `flutter build bundle`. // e.g. `flutter build bundle`.
buildCommands.add('FLUTTER_SUPPRESS_ANALYTICS=true'); buildCommands.add('FLUTTER_SUPPRESS_ANALYTICS=true');
...@@ -369,7 +377,7 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -369,7 +377,7 @@ Future<XcodeBuildResult> buildXcodeProject({
final Stopwatch sw = Stopwatch()..start(); final Stopwatch sw = Stopwatch()..start();
initialBuildStatus = globals.logger.startProgress('Running Xcode build...'); initialBuildStatus = globals.logger.startProgress('Running Xcode build...');
final RunResult? buildResult = await _runBuildWithRetries(buildCommands, app); buildResult = await _runBuildWithRetries(buildCommands, app);
// Notifies listener that no more output is coming. // Notifies listener that no more output is coming.
scriptOutputPipeFile?.writeAsStringSync('all done'); scriptOutputPipeFile?.writeAsStringSync('all done');
...@@ -383,6 +391,22 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -383,6 +391,22 @@ Future<XcodeBuildResult> buildXcodeProject({
); );
globals.flutterUsage.sendTiming(xcodeBuildActionToString(buildAction), 'xcode-ios', Duration(milliseconds: sw.elapsedMilliseconds)); globals.flutterUsage.sendTiming(xcodeBuildActionToString(buildAction), 'xcode-ios', Duration(milliseconds: sw.elapsedMilliseconds));
if (tempDir.existsSync()) {
// Display additional warning and error message from xcresult bundle.
final Directory resultBundle = tempDir.childDirectory(_kResultBundlePath);
if (!resultBundle.existsSync()) {
globals.printTrace('The xcresult bundle are not generated. Displaying xcresult is disabled.');
} else {
// Discard unwanted errors. See: https://github.com/flutter/flutter/issues/95354
final XCResultIssueDiscarder warningDiscarder = XCResultIssueDiscarder(typeMatcher: XCResultIssueType.warning);
final XCResultIssueDiscarder dartBuildErrorDiscarder = XCResultIssueDiscarder(messageMatcher: RegExp(r'Command PhaseScriptExecution failed with a nonzero exit code'));
final XCResultGenerator xcResultGenerator = XCResultGenerator(resultPath: resultBundle.absolute.path, xcode: globals.xcode!, processUtils: globals.processUtils);
xcResult = await xcResultGenerator.generate(issueDiscarders: <XCResultIssueDiscarder>[warningDiscarder, dartBuildErrorDiscarder]);
}
}
} finally {
tempDir.deleteSync(recursive: true);
}
if (buildResult != null && buildResult.exitCode != 0) { if (buildResult != null && buildResult.exitCode != 0) {
globals.printStatus('Failed to build iOS app'); globals.printStatus('Failed to build iOS app');
if (buildResult.stderr.isNotEmpty) { if (buildResult.stderr.isNotEmpty) {
...@@ -403,6 +427,7 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -403,6 +427,7 @@ Future<XcodeBuildResult> buildXcodeProject({
environmentType: environmentType, environmentType: environmentType,
buildSettings: buildSettings, buildSettings: buildSettings,
), ),
xcResult: xcResult,
); );
} else { } else {
String? outputDir; String? outputDir;
...@@ -466,6 +491,7 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -466,6 +491,7 @@ Future<XcodeBuildResult> buildXcodeProject({
environmentType: environmentType, environmentType: environmentType,
buildSettings: buildSettings, buildSettings: buildSettings,
), ),
xcResult: xcResult,
); );
} }
} }
...@@ -585,16 +611,19 @@ Future<void> diagnoseXcodeBuildFailure(XcodeBuildResult result, Usage flutterUsa ...@@ -585,16 +611,19 @@ Future<void> diagnoseXcodeBuildFailure(XcodeBuildResult result, Usage flutterUsa
logger.printError(' open ios/Runner.xcworkspace'); logger.printError(' open ios/Runner.xcworkspace');
return; return;
} }
if (result.stdout?.contains('Code Sign error') == true) {
logger.printError(''); // Handle xcresult errors.
logger.printError('It appears that there was a problem signing your application prior to installation on the device.'); final XCResult? xcResult = result.xcResult;
logger.printError(''); if (xcResult == null) {
logger.printError('Verify that the Bundle Identifier in your project is your signing id in Xcode'); return;
logger.printError(' open ios/Runner.xcworkspace'); }
logger.printError(''); if (!xcResult.parseSuccess) {
logger.printError("Also try selecting 'Product > Build' to fix the problem:"); globals.printTrace('XCResult parsing error: ${xcResult.parsingErrorMessage}');
return; return;
} }
for (final XCResultIssue issue in xcResult.issues) {
_handleXCResultIssue(issue: issue, logger: logger);
}
} }
/// xcodebuild <buildaction> parameter (see man xcodebuild for details). /// xcodebuild <buildaction> parameter (see man xcodebuild for details).
...@@ -618,6 +647,7 @@ class XcodeBuildResult { ...@@ -618,6 +647,7 @@ class XcodeBuildResult {
this.stdout, this.stdout,
this.stderr, this.stderr,
this.xcodeBuildExecution, this.xcodeBuildExecution,
this.xcResult
}); });
final bool success; final bool success;
...@@ -626,6 +656,10 @@ class XcodeBuildResult { ...@@ -626,6 +656,10 @@ class XcodeBuildResult {
final String? stderr; final String? stderr;
/// The invocation of the build that resulted in this result instance. /// The invocation of the build that resulted in this result instance.
final XcodeBuildExecution? xcodeBuildExecution; final XcodeBuildExecution? xcodeBuildExecution;
/// Parsed information in xcresult bundle.
///
/// Can be null if the bundle is not created during build.
final XCResult? xcResult;
} }
/// Describes an invocation of a Xcode build command. /// Describes an invocation of a Xcode build command.
...@@ -686,3 +720,38 @@ bool upgradePbxProjWithFlutterAssets(IosProject project, Logger logger) { ...@@ -686,3 +720,38 @@ bool upgradePbxProjWithFlutterAssets(IosProject project, Logger logger) {
xcodeProjectFile.writeAsStringSync(buffer.toString()); xcodeProjectFile.writeAsStringSync(buffer.toString());
return true; return true;
} }
void _handleXCResultIssue({required XCResultIssue issue, required Logger logger}) {
// Issue summary from xcresult.
final StringBuffer issueSummaryBuffer = StringBuffer();
issueSummaryBuffer.write(issue.subType ?? 'Unknown');
issueSummaryBuffer.write(' (Xcode): ');
issueSummaryBuffer.writeln(issue.message ?? '');
if (issue.location != null ) {
issueSummaryBuffer.writeln(issue.location);
}
final String issueSummary = issueSummaryBuffer.toString();
switch (issue.type) {
case XCResultIssueType.error:
logger.printError(issueSummary);
break;
case XCResultIssueType.warning:
logger.printWarning(issueSummary);
break;
}
// Add more custom output for flutter users.
if (issue.message != null && issue.message!.toLowerCase().contains('provisioning profile')) {
logger.printError('');
logger.printError('It appears that there was a problem signing your application prior to installation on the device.');
logger.printError('');
logger.printError('Verify that the Bundle Identifier in your project is your signing id in Xcode');
logger.printError(' open ios/Runner.xcworkspace');
logger.printError('');
logger.printError("Also try selecting 'Product > Build' to fix the problem:");
}
}
const String _kResultBundlePath = 'temporary_xcresult_bundle';
const String _kResultBundleVersion = '3';
...@@ -545,6 +545,7 @@ class IOSSimulator extends Device { ...@@ -545,6 +545,7 @@ class IOSSimulator extends Device {
deviceID: id, deviceID: id,
); );
if (!buildResult.success) { if (!buildResult.success) {
await diagnoseXcodeBuildFailure(buildResult, globals.flutterUsage, globals.logger);
throwToolExit('Could not build the application for the simulator.'); throwToolExit('Could not build the application for the simulator.');
} }
......
...@@ -78,31 +78,8 @@ class XCResult { ...@@ -78,31 +78,8 @@ class XCResult {
/// Parse the `resultJson` and stores useful informations in the returned `XCResult`. /// Parse the `resultJson` and stores useful informations in the returned `XCResult`.
factory XCResult({required Map<String, Object?> resultJson, List<XCResultIssueDiscarder> issueDiscarders = const <XCResultIssueDiscarder>[]}) { factory XCResult({required Map<String, Object?> resultJson, List<XCResultIssueDiscarder> issueDiscarders = const <XCResultIssueDiscarder>[]}) {
final List<XCResultIssue> issues = <XCResultIssue>[]; final List<XCResultIssue> issues = <XCResultIssue>[];
final Object? actionsMap = resultJson['actions'];
if (actionsMap == null || actionsMap is! Map<String, Object?>) { final Object? issuesMap = resultJson['issues'];
return XCResult.failed(
errorMessage: 'xcresult parser: Failed to parse the actions map.');
}
final Object? actionValueList = actionsMap['_values'];
if (actionValueList == null ||
actionValueList is! List<Object?> ||
actionValueList.isEmpty) {
return XCResult.failed(
errorMessage: 'xcresult parser: Failed to parse the actions map.');
}
final Object? actionMap = actionValueList.first;
if (actionMap == null || actionMap is! Map<String, Object?>) {
return XCResult.failed(
errorMessage:
'xcresult parser: Failed to parse the first action map.');
}
final Object? buildResultMap = actionMap['buildResult'];
if (buildResultMap == null || buildResultMap is! Map<String, Object?>) {
return XCResult.failed(
errorMessage:
'xcresult parser: Failed to parse the buildResult map.');
}
final Object? issuesMap = buildResultMap['issues'];
if (issuesMap == null || issuesMap is! Map<String, Object?>) { if (issuesMap == null || issuesMap is! Map<String, Object?>) {
return XCResult.failed( return XCResult.failed(
errorMessage: 'xcresult parser: Failed to parse the issues map.'); errorMessage: 'xcresult parser: Failed to parse the issues map.');
......
...@@ -58,6 +58,8 @@ const List<String> kRunReleaseArgs = <String>[ ...@@ -58,6 +58,8 @@ const List<String> kRunReleaseArgs = <String>[
'id=123', 'id=123',
'ONLY_ACTIVE_ARCH=YES', 'ONLY_ACTIVE_ARCH=YES',
'ARCHS=arm64', 'ARCHS=arm64',
'-resultBundlePath', '/.tmp_rand0/flutter_ios_build_temp_dirrand0/temporary_xcresult_bundle',
'-resultBundleVersion', '3',
'FLUTTER_SUPPRESS_ANALYTICS=true', 'FLUTTER_SUPPRESS_ANALYTICS=true',
'COMPILER_INDEX_STORE_ENABLE=NO', 'COMPILER_INDEX_STORE_ENABLE=NO',
]; ];
......
...@@ -240,58 +240,17 @@ void main() { ...@@ -240,58 +240,17 @@ void main() {
'xcresult parser: Unrecognized top level json format.'); 'xcresult parser: Unrecognized top level json format.');
}); });
testWithoutContext('error: fail to parse actions map', () async { testWithoutContext('error: fail to parse issue map', () async {
final XCResultGenerator generator = _setupGenerator(resultJson: '{}'); final XCResultGenerator generator = _setupGenerator(resultJson: '{}');
final XCResult result = await generator.generate(); final XCResult result = await generator.generate();
expect(result.issues.length, 0); expect(result.issues.length, 0);
expect(result.parseSuccess, false); expect(result.parseSuccess, false);
expect(result.parsingErrorMessage, expect(result.parsingErrorMessage,
'xcresult parser: Failed to parse the actions map.'); 'xcresult parser: Failed to parse the issues map.');
});
testWithoutContext('error: empty actions map', () async {
final XCResultGenerator generator =
_setupGenerator(resultJson: kSampleResultJsonEmptyActionsMap);
final XCResult result = await generator.generate();
expect(result.issues.length, 0);
expect(result.parseSuccess, false);
expect(result.parsingErrorMessage,
'xcresult parser: Failed to parse the actions map.');
});
testWithoutContext('error: empty actions map', () async {
final XCResultGenerator generator = _setupGenerator(resultJson: kSampleResultJsonEmptyActionsMap);
final XCResult result = await generator.generate();
expect(result.issues.length, 0);
expect(result.parseSuccess, false);
expect(result.parsingErrorMessage,
'xcresult parser: Failed to parse the actions map.');
});
testWithoutContext('error: empty actions map', () async {
final XCResultGenerator generator = _setupGenerator(resultJson: kSampleResultJsonInvalidActionMap);
final XCResult result = await generator.generate();
expect(result.issues.length, 0);
expect(result.parseSuccess, false);
expect(result.parsingErrorMessage,
'xcresult parser: Failed to parse the first action map.');
});
testWithoutContext('error: empty actions map', () async {
final XCResultGenerator generator = _setupGenerator(resultJson: kSampleResultJsonInvalidBuildResultMap);
final XCResult result = await generator.generate();
expect(result.issues.length, 0);
expect(result.parseSuccess, false);
expect(result.parsingErrorMessage,
'xcresult parser: Failed to parse the buildResult map.');
}); });
testWithoutContext('error: empty actions map', () async { testWithoutContext('error: invalid issue map', () async {
final XCResultGenerator generator = _setupGenerator(resultJson: kSampleResultJsonInvalidIssuesMap); final XCResultGenerator generator = _setupGenerator(resultJson: kSampleResultJsonInvalidIssuesMap);
final XCResult result = await generator.generate(); final XCResult result = await generator.generate();
......
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