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';
import 'migrations/xcode_build_system_migration.dart';
import 'xcode_build_settings.dart';
import 'xcodeproj.dart';
import 'xcresult.dart';
class IMobileDevice {
IMobileDevice({
......@@ -315,11 +316,13 @@ Future<XcodeBuildResult> buildXcodeProject({
Status? buildSubStatus;
Status? initialBuildStatus;
Directory? tempDir;
File? scriptOutputPipeFile;
RunResult? buildResult;
XCResult? xcResult;
final Directory tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_ios_build_temp_dir');
try {
if (globals.logger.hasTerminal) {
tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_build_log_pipe.');
scriptOutputPipeFile = tempDir.childFile('pipe_to_stdout');
globals.os.makePipe(scriptOutputPipeFile.path);
......@@ -330,8 +333,6 @@ Future<XcodeBuildResult> buildXcodeProject({
buildSubStatus?.stop();
buildSubStatus = null;
if (line == 'all done') {
// Free pipe file.
tempDir?.deleteSync(recursive: true);
return;
}
} else {
......@@ -352,6 +353,13 @@ Future<XcodeBuildResult> buildXcodeProject({
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.
// e.g. `flutter build bundle`.
buildCommands.add('FLUTTER_SUPPRESS_ANALYTICS=true');
......@@ -369,7 +377,7 @@ Future<XcodeBuildResult> buildXcodeProject({
final Stopwatch sw = Stopwatch()..start();
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.
scriptOutputPipeFile?.writeAsStringSync('all done');
......@@ -383,6 +391,22 @@ Future<XcodeBuildResult> buildXcodeProject({
);
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) {
globals.printStatus('Failed to build iOS app');
if (buildResult.stderr.isNotEmpty) {
......@@ -403,6 +427,7 @@ Future<XcodeBuildResult> buildXcodeProject({
environmentType: environmentType,
buildSettings: buildSettings,
),
xcResult: xcResult,
);
} else {
String? outputDir;
......@@ -466,6 +491,7 @@ Future<XcodeBuildResult> buildXcodeProject({
environmentType: environmentType,
buildSettings: buildSettings,
),
xcResult: xcResult,
);
}
}
......@@ -585,16 +611,19 @@ Future<void> diagnoseXcodeBuildFailure(XcodeBuildResult result, Usage flutterUsa
logger.printError(' open ios/Runner.xcworkspace');
return;
}
if (result.stdout?.contains('Code Sign error') == true) {
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:");
// Handle xcresult errors.
final XCResult? xcResult = result.xcResult;
if (xcResult == null) {
return;
}
if (!xcResult.parseSuccess) {
globals.printTrace('XCResult parsing error: ${xcResult.parsingErrorMessage}');
return;
}
for (final XCResultIssue issue in xcResult.issues) {
_handleXCResultIssue(issue: issue, logger: logger);
}
}
/// xcodebuild <buildaction> parameter (see man xcodebuild for details).
......@@ -618,6 +647,7 @@ class XcodeBuildResult {
this.stdout,
this.stderr,
this.xcodeBuildExecution,
this.xcResult
});
final bool success;
......@@ -626,6 +656,10 @@ class XcodeBuildResult {
final String? stderr;
/// The invocation of the build that resulted in this result instance.
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.
......@@ -686,3 +720,38 @@ bool upgradePbxProjWithFlutterAssets(IosProject project, Logger logger) {
xcodeProjectFile.writeAsStringSync(buffer.toString());
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 {
deviceID: id,
);
if (!buildResult.success) {
await diagnoseXcodeBuildFailure(buildResult, globals.flutterUsage, globals.logger);
throwToolExit('Could not build the application for the simulator.');
}
......
......@@ -78,31 +78,8 @@ class XCResult {
/// Parse the `resultJson` and stores useful informations in the returned `XCResult`.
factory XCResult({required Map<String, Object?> resultJson, List<XCResultIssueDiscarder> issueDiscarders = const <XCResultIssueDiscarder>[]}) {
final List<XCResultIssue> issues = <XCResultIssue>[];
final Object? actionsMap = resultJson['actions'];
if (actionsMap == null || actionsMap is! Map<String, Object?>) {
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'];
final Object? issuesMap = resultJson['issues'];
if (issuesMap == null || issuesMap is! Map<String, Object?>) {
return XCResult.failed(
errorMessage: 'xcresult parser: Failed to parse the issues map.');
......
......@@ -58,6 +58,8 @@ const List<String> kRunReleaseArgs = <String>[
'id=123',
'ONLY_ACTIVE_ARCH=YES',
'ARCHS=arm64',
'-resultBundlePath', '/.tmp_rand0/flutter_ios_build_temp_dirrand0/temporary_xcresult_bundle',
'-resultBundleVersion', '3',
'FLUTTER_SUPPRESS_ANALYTICS=true',
'COMPILER_INDEX_STORE_ENABLE=NO',
];
......
......@@ -240,58 +240,17 @@ void main() {
'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 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: 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.');
'xcresult parser: Failed to parse the issues map.');
});
testWithoutContext('error: empty actions map', () async {
testWithoutContext('error: invalid issue map', () async {
final XCResultGenerator generator = _setupGenerator(resultJson: kSampleResultJsonInvalidIssuesMap);
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