Skip to content

Commit

Permalink
Parse xcode error message
Browse files Browse the repository at this point in the history
  • Loading branch information
Chris Yang committed Dec 21, 2021
1 parent c746be6 commit fb53cb9
Show file tree
Hide file tree
Showing 8 changed files with 664 additions and 1,502 deletions.
195 changes: 132 additions & 63 deletions packages/flutter_tools/lib/src/ios/mac.dart
Expand Up @@ -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({
Expand Down Expand Up @@ -315,74 +316,97 @@ Future<XcodeBuildResult> buildXcodeProject({

Status? buildSubStatus;
Status? initialBuildStatus;
Directory? tempDir;

File? scriptOutputPipeFile;
if (globals.logger.hasTerminal) {
tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_build_log_pipe.');
scriptOutputPipeFile = tempDir.childFile('pipe_to_stdout');
globals.os.makePipe(scriptOutputPipeFile.path);

Future<void> listenToScriptOutputLine() async {
final List<String> lines = await scriptOutputPipeFile!.readAsLines();
for (final String line in lines) {
if (line == 'done' || line == 'all done') {
buildSubStatus?.stop();
buildSubStatus = null;
if (line == 'all done') {
// Free pipe file.
tempDir?.deleteSync(recursive: true);
return;
RunResult? buildResult;
XCResult? xcResult;

final Directory tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_ios_build_temp_dir');
try {
if (globals.logger.hasTerminal) {
scriptOutputPipeFile = tempDir.childFile('pipe_to_stdout');
globals.os.makePipe(scriptOutputPipeFile.path);

Future<void> listenToScriptOutputLine() async {
final List<String> lines = await scriptOutputPipeFile!.readAsLines();
for (final String line in lines) {
if (line == 'done' || line == 'all done') {
buildSubStatus?.stop();
buildSubStatus = null;
if (line == 'all done') {
return;
}
} else {
initialBuildStatus?.cancel();
initialBuildStatus = null;
buildSubStatus = globals.logger.startProgress(
line,
progressIndicatorPadding: kDefaultStatusPadding - 7,
);
}
} else {
initialBuildStatus?.cancel();
initialBuildStatus = null;
buildSubStatus = globals.logger.startProgress(
line,
progressIndicatorPadding: kDefaultStatusPadding - 7,
);
}
await listenToScriptOutputLine();
}
await listenToScriptOutputLine();
}

// Trigger the start of the pipe -> stdout loop. Ignore exceptions.
unawaited(listenToScriptOutputLine());
// Trigger the start of the pipe -> stdout loop. Ignore exceptions.
unawaited(listenToScriptOutputLine());

buildCommands.add('SCRIPT_OUTPUT_STREAM_FILE=${scriptOutputPipeFile.absolute.path}');
}

// Don't log analytics for downstream Flutter commands.
// e.g. `flutter build bundle`.
buildCommands.add('FLUTTER_SUPPRESS_ANALYTICS=true');
buildCommands.add('COMPILER_INDEX_STORE_ENABLE=NO');
buildCommands.addAll(environmentVariablesAsXcodeBuildSettings(globals.platform));
buildCommands.add('SCRIPT_OUTPUT_STREAM_FILE=${scriptOutputPipeFile.absolute.path}');
}

if (buildAction == XcodeBuildAction.archive) {
buildCommands.addAll(<String>[
'-archivePath',
globals.fs.path.absolute(app.archiveBundlePath),
'archive',
'-resultBundlePath',
tempDir.childFile(_kResultBundlePath).absolute.path,
'-resultBundleVersion',
_kResultBundleVersion
]);
}

final Stopwatch sw = Stopwatch()..start();
initialBuildStatus = globals.logger.startProgress('Running Xcode build...');
// Don't log analytics for downstream Flutter commands.
// e.g. `flutter build bundle`.
buildCommands.add('FLUTTER_SUPPRESS_ANALYTICS=true');
buildCommands.add('COMPILER_INDEX_STORE_ENABLE=NO');
buildCommands.addAll(environmentVariablesAsXcodeBuildSettings(globals.platform));

if (buildAction == XcodeBuildAction.archive) {
buildCommands.addAll(<String>[
'-archivePath',
globals.fs.path.absolute(app.archiveBundlePath),
'archive',
]);
}

final RunResult? buildResult = await _runBuildWithRetries(buildCommands, app);
final Stopwatch sw = Stopwatch()..start();
initialBuildStatus = globals.logger.startProgress('Running Xcode build...');

// Notifies listener that no more output is coming.
scriptOutputPipeFile?.writeAsStringSync('all done');
buildSubStatus?.stop();
buildSubStatus = null;
initialBuildStatus?.cancel();
initialBuildStatus = null;
globals.printStatus(
'Xcode ${xcodeBuildActionToString(buildAction)} done.'.padRight(kDefaultStatusPadding + 1)
+ getElapsedAsSeconds(sw.elapsed).padLeft(5),
);
globals.flutterUsage.sendTiming(xcodeBuildActionToString(buildAction), 'xcode-ios', Duration(milliseconds: sw.elapsedMilliseconds));
buildResult = await _runBuildWithRetries(buildCommands, app);

// Notifies listener that no more output is coming.
scriptOutputPipeFile?.writeAsStringSync('all done');
buildSubStatus?.stop();
buildSubStatus = null;
initialBuildStatus?.cancel();
initialBuildStatus = null;
globals.printStatus(
'Xcode ${xcodeBuildActionToString(buildAction)} done.'.padRight(kDefaultStatusPadding + 1)
+ getElapsedAsSeconds(sw.elapsed).padLeft(5),
);
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) {
Expand All @@ -403,6 +427,7 @@ Future<XcodeBuildResult> buildXcodeProject({
environmentType: environmentType,
buildSettings: buildSettings,
),
xcResult: xcResult,
);
} else {
String? outputDir;
Expand Down Expand Up @@ -466,6 +491,7 @@ Future<XcodeBuildResult> buildXcodeProject({
environmentType: environmentType,
buildSettings: buildSettings,
),
xcResult: xcResult,
);
}
}
Expand Down Expand Up @@ -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).
Expand All @@ -618,6 +647,7 @@ class XcodeBuildResult {
this.stdout,
this.stderr,
this.xcodeBuildExecution,
this.xcResult
});

final bool success;
Expand All @@ -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.
Expand Down Expand Up @@ -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';
1 change: 1 addition & 0 deletions packages/flutter_tools/lib/src/ios/simulators.dart
Expand Up @@ -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.');
}

Expand Down
27 changes: 2 additions & 25 deletions packages/flutter_tools/lib/src/ios/xcresult.dart
Expand Up @@ -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.');
Expand Down

0 comments on commit fb53cb9

Please sign in to comment.