blob: 25418b247e8e1b116cf40e6360fb7c007f83c6ed [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import '../../src/base/process.dart';
import '../../src/convert.dart' show json;
import '../../src/macos/xcode.dart';
import '../convert.dart';
/// The generator of xcresults.
///
/// Call [generate] after an iOS/MacOS build will generate a [XCResult].
/// This only works when the `-resultBundleVersion` is set to 3.
/// * See also: [XCResult].
class XCResultGenerator {
/// Construct the [XCResultGenerator].
XCResultGenerator({
required this.resultPath,
required this.xcode,
required this.processUtils,
});
/// The file path that used to store the xcrun result.
///
/// There's usually a `resultPath.xcresult` file in the same folder.
final String resultPath;
/// The [ProcessUtils] to run commands.
final ProcessUtils processUtils;
/// [Xcode] object used to run xcode command.
final Xcode xcode;
/// Generates the XCResult.
///
/// Calls `xcrun xcresulttool get --path <resultPath> --format json`,
/// then stores the useful information the json into an [XCResult] object.
Future<XCResult> generate() async {
final RunResult result = await processUtils.run(
<String>[
...xcode.xcrunCommand(),
'xcresulttool',
'get',
'--path',
resultPath,
'--format',
'json',
],
);
if (result.exitCode != 0) {
return XCResult.failed(errorMessage: result.stderr);
}
if (result.stdout.isEmpty) {
return XCResult.failed(
errorMessage: 'xcresult parser: Unrecognized top level json format.');
}
final Object? resultJson = json.decode(result.stdout);
if (resultJson == null || resultJson is! Map<String, Object?>) {
// If json parsing failed, indicate such error.
// This also includes the top level json object is an array, which indicates
// the structure of the json is changed and this parser class possibly needs to update for this change.
return XCResult.failed(
errorMessage: 'xcresult parser: Unrecognized top level json format.');
}
return XCResult(resultJson: resultJson);
}
}
/// The xcresult of an `xcodebuild` command.
///
/// This is the result from an `xcrun xcresulttool get --path <resultPath> --format json` run.
/// The result contains useful information such as build errors and warnings.
class XCResult {
/// Parse the `resultJson` and stores useful informations in the returned `XCResult`.
factory XCResult({required Map<String, Object?> resultJson}) {
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'];
if (issuesMap == null || issuesMap is! Map<String, Object?>) {
return XCResult.failed(
errorMessage: 'xcresult parser: Failed to parse the issues map.');
}
List<XCResultIssue> _parseIssuesFromIssueSummariesJson({
required XCResultIssueType type,
required Map<String, Object?> issueSummariesJson,
}) {
final List<XCResultIssue> issues = <XCResultIssue>[];
final Object? errorsList = issueSummariesJson['_values'];
if (errorsList is List<Object?>) {
for (final Object? issueJson in errorsList) {
if (issueJson == null || issueJson is! Map<String, Object?>) {
continue;
}
final XCResultIssue resultIssue = XCResultIssue(
type: type,
issueJson: issueJson,
);
issues.add(resultIssue);
}
}
return issues;
}
final Object? errorSummaries = issuesMap['errorSummaries'];
if (errorSummaries is Map<String, Object?>) {
issues.addAll(_parseIssuesFromIssueSummariesJson(
type: XCResultIssueType.error,
issueSummariesJson: errorSummaries,
));
}
final Object? warningSummaries = issuesMap['warningSummaries'];
if (warningSummaries is Map<String, Object?>) {
issues.addAll(_parseIssuesFromIssueSummariesJson(
type: XCResultIssueType.warning,
issueSummariesJson: warningSummaries,
));
}
return XCResult._(issues: issues);
}
factory XCResult.failed({required String errorMessage}) {
return XCResult._(
parseSuccess: false,
parsingErrorMessage: errorMessage,
);
}
XCResult._({
this.issues = const <XCResultIssue>[],
this.parseSuccess = true,
this.parsingErrorMessage,
});
/// The issues in the xcresult file.
final List<XCResultIssue> issues;
/// Indicate if the xcresult was successfully parsed.
///
/// See also: [parsingErrorMessage] for the error message if the parsing was unsuccessful.
final bool parseSuccess;
/// The error message describes why the parse if unsuccessful.
///
/// This is `null` if [parseSuccess] is `true`.
final String? parsingErrorMessage;
}
/// An issue object in the XCResult
class XCResultIssue {
/// Construct an `XCResultIssue` object from `issueJson`.
///
/// `issueJson` is the object at xcresultJson[['actions']['_values'][0]['buildResult']['issues']['errorSummaries'/'warningSummaries']['_values'].
factory XCResultIssue({
required XCResultIssueType type,
required Map<String, Object?> issueJson,
}) {
final Object? issueSubTypeMap = issueJson['issueType'];
String subType = '';
if (issueSubTypeMap is Map<String, Object?>) {
final Object? subTypeValue = issueSubTypeMap['_value'];
if (subTypeValue is String) {
subType = subTypeValue;
}
}
String message = '';
final Object? messageMap = issueJson['message'];
if (messageMap is Map<String, Object?>) {
final Object? messageValue = messageMap['_value'];
if (messageValue is String) {
message = messageValue;
}
}
return XCResultIssue._(
type: type,
subType: subType,
message: message,
);
}
XCResultIssue._({
required this.type,
required this.subType,
required this.message,
});
/// The type of the issue.
final XCResultIssueType type;
/// The sub type of the issue.
///
/// This is a more detailed category about the issue.
/// The possible values are `Warning`, `Semantic Issue'` etc.
final String subType;
/// Human readable message for the issue.
///
/// This can be displayed to user for their information.
final String message;
}
/// The type of an `XCResultIssue`.
enum XCResultIssueType {
/// The issue is an warning.
///
/// This is for all the issues under the `warningSummaries` key in the xcresult.
warning,
/// The issue is an warning.
///
/// This is for all the issues under the `errorSummaries` key in the xcresult.
error,
}