Show Xcode compilation errors at end of build, suppress stdout and stderr from Xcode (#113302)
This commit is contained in:
parent
3034a4ef39
commit
f059dd40ea
@ -548,8 +548,7 @@ end
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!xcodebuildOutput.contains('flutter --verbose --local-engine-src-path=bogus assemble') || // Verbose output
|
if (!xcodebuildOutput.contains('flutter --verbose --local-engine-src-path=bogus assemble') || // Verbose output
|
||||||
!xcodebuildOutput.contains('Unable to detect a Flutter engine build directory in bogus') ||
|
!xcodebuildOutput.contains('Unable to detect a Flutter engine build directory in bogus')) {
|
||||||
!xcodebuildOutput.contains('Command PhaseScriptExecution failed with a nonzero exit code')) {
|
|
||||||
return TaskResult.failure('Host Objective-C app build succeeded though flutter script failed');
|
return TaskResult.failure('Host Objective-C app build succeeded though flutter script failed');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,11 +97,17 @@ class Context {
|
|||||||
if (verbose) {
|
if (verbose) {
|
||||||
print((result.stdout as String).trim());
|
print((result.stdout as String).trim());
|
||||||
}
|
}
|
||||||
if ((result.stderr as String).isNotEmpty) {
|
final String resultStderr = result.stderr.toString().trim();
|
||||||
echoError((result.stderr as String).trim());
|
if (resultStderr.isNotEmpty) {
|
||||||
|
final StringBuffer errorOutput = StringBuffer();
|
||||||
|
if (result.exitCode != 0) {
|
||||||
|
// "error:" prefix makes this show up as an Xcode compilation error.
|
||||||
|
errorOutput.write('error: ');
|
||||||
|
}
|
||||||
|
errorOutput.write(resultStderr);
|
||||||
|
echoError(errorOutput.toString());
|
||||||
}
|
}
|
||||||
if (!allowFail && result.exitCode != 0) {
|
if (!allowFail && result.exitCode != 0) {
|
||||||
stderr.write('${result.stderr}\n');
|
|
||||||
throw Exception(
|
throw Exception(
|
||||||
'Command "$bin ${args.join(' ')}" exited with code ${result.exitCode}',
|
'Command "$bin ${args.join(' ')}" exited with code ${result.exitCode}',
|
||||||
);
|
);
|
||||||
|
@ -871,13 +871,11 @@ class _BuildInstance {
|
|||||||
ErrorHandlingFileSystem.deleteIfExists(previousFile);
|
ErrorHandlingFileSystem.deleteIfExists(previousFile);
|
||||||
}
|
}
|
||||||
} on Exception catch (exception, stackTrace) {
|
} on Exception catch (exception, stackTrace) {
|
||||||
// TODO(zanderso): throw specific exception for expected errors to mark
|
|
||||||
// as non-fatal. All others should be fatal.
|
|
||||||
node.target.clearStamp(environment);
|
node.target.clearStamp(environment);
|
||||||
succeeded = false;
|
succeeded = false;
|
||||||
skipped = false;
|
skipped = false;
|
||||||
exceptionMeasurements[node.target.name] = ExceptionMeasurement(
|
exceptionMeasurements[node.target.name] = ExceptionMeasurement(
|
||||||
node.target.name, exception, stackTrace);
|
node.target.name, exception, stackTrace, fatal: true);
|
||||||
} finally {
|
} finally {
|
||||||
resource.release();
|
resource.release();
|
||||||
stopwatch.stop();
|
stopwatch.stop();
|
||||||
|
@ -347,7 +347,7 @@ class AssembleCommand extends FlutterCommand {
|
|||||||
for (final ExceptionMeasurement measurement in result.exceptions.values) {
|
for (final ExceptionMeasurement measurement in result.exceptions.values) {
|
||||||
if (measurement.fatal || globals.logger.isVerbose) {
|
if (measurement.fatal || globals.logger.isVerbose) {
|
||||||
globals.printError('Target ${measurement.target} failed: ${measurement.exception}',
|
globals.printError('Target ${measurement.target} failed: ${measurement.exception}',
|
||||||
stackTrace: measurement.stackTrace
|
stackTrace: globals.logger.isVerbose ? measurement.stackTrace : null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -414,14 +414,6 @@ Future<XcodeBuildResult> buildXcodeProject({
|
|||||||
}
|
}
|
||||||
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) {
|
|
||||||
globals.printStatus('Error output from Xcode build:\n↳');
|
|
||||||
globals.printStatus(buildResult.stderr, indent: 4);
|
|
||||||
}
|
|
||||||
if (buildResult.stdout.isNotEmpty) {
|
|
||||||
globals.printStatus("Xcode's output:\n↳");
|
|
||||||
globals.printStatus(buildResult.stdout, indent: 4);
|
|
||||||
}
|
|
||||||
return XcodeBuildResult(
|
return XcodeBuildResult(
|
||||||
success: false,
|
success: false,
|
||||||
stdout: buildResult.stdout,
|
stdout: buildResult.stdout,
|
||||||
@ -738,7 +730,7 @@ bool _handleIssues(XCResult? xcResult, Logger logger, XcodeBuildExecution? xcode
|
|||||||
|
|
||||||
if (requiresProvisioningProfile) {
|
if (requiresProvisioningProfile) {
|
||||||
logger.printError(noProvisioningProfileInstruction, emphasis: true);
|
logger.printError(noProvisioningProfileInstruction, emphasis: true);
|
||||||
} else if (_missingDevelopmentTeam(xcodeBuildExecution)) {
|
} else if ((!issueDetected || hasProvisioningProfileIssue) && _missingDevelopmentTeam(xcodeBuildExecution)) {
|
||||||
issueDetected = true;
|
issueDetected = true;
|
||||||
logger.printError(noDevelopmentTeamInstruction, emphasis: true);
|
logger.printError(noDevelopmentTeamInstruction, emphasis: true);
|
||||||
} else if (hasProvisioningProfileIssue) {
|
} else if (hasProvisioningProfileIssue) {
|
||||||
@ -782,11 +774,21 @@ bool _needUpdateSigningIdentifier(XcodeBuildExecution? xcodeBuildExecution) {
|
|||||||
//
|
//
|
||||||
// As detecting issues in stdout is not usually accurate, this should be used as a fallback when other issue detecting methods failed.
|
// As detecting issues in stdout is not usually accurate, this should be used as a fallback when other issue detecting methods failed.
|
||||||
void _parseIssueInStdout(XcodeBuildExecution xcodeBuildExecution, Logger logger, XcodeBuildResult result) {
|
void _parseIssueInStdout(XcodeBuildExecution xcodeBuildExecution, Logger logger, XcodeBuildResult result) {
|
||||||
|
final String? stderr = result.stderr;
|
||||||
|
if (stderr != null && stderr.isNotEmpty) {
|
||||||
|
logger.printStatus('Error output from Xcode build:\n↳');
|
||||||
|
logger.printStatus(stderr, indent: 4);
|
||||||
|
}
|
||||||
|
final String? stdout = result.stdout;
|
||||||
|
if (stdout != null && stdout.isNotEmpty) {
|
||||||
|
logger.printStatus("Xcode's output:\n↳");
|
||||||
|
logger.printStatus(stdout, indent: 4);
|
||||||
|
}
|
||||||
|
|
||||||
if (xcodeBuildExecution.environmentType == EnvironmentType.physical
|
if (xcodeBuildExecution.environmentType == EnvironmentType.physical
|
||||||
// May need updating if Xcode changes its outputs.
|
// May need updating if Xcode changes its outputs.
|
||||||
&& (result.stdout?.contains('requires a provisioning profile. Select a provisioning profile in the Signing & Capabilities editor') ?? false)) {
|
&& (result.stdout?.contains('requires a provisioning profile. Select a provisioning profile in the Signing & Capabilities editor') ?? false)) {
|
||||||
logger.printError(noProvisioningProfileInstruction, emphasis: true);
|
logger.printError(noProvisioningProfileInstruction, emphasis: true);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
import '../../src/base/process.dart';
|
import '../../src/base/process.dart';
|
||||||
import '../../src/convert.dart' show json;
|
import '../../src/convert.dart' show json;
|
||||||
import '../../src/macos/xcode.dart';
|
import '../../src/macos/xcode.dart';
|
||||||
@ -112,6 +114,20 @@ class XCResult {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a [XCResult] with constructed [XCResultIssue]s for testing.
|
||||||
|
@visibleForTesting
|
||||||
|
factory XCResult.test({
|
||||||
|
List<XCResultIssue>? issues,
|
||||||
|
bool? parseSuccess,
|
||||||
|
String? parsingErrorMessage,
|
||||||
|
}) {
|
||||||
|
return XCResult._(
|
||||||
|
issues: issues ?? const <XCResultIssue>[],
|
||||||
|
parseSuccess: parseSuccess ?? true,
|
||||||
|
parsingErrorMessage: parsingErrorMessage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
XCResult._({
|
XCResult._({
|
||||||
this.issues = const <XCResultIssue>[],
|
this.issues = const <XCResultIssue>[],
|
||||||
this.parseSuccess = true,
|
this.parseSuccess = true,
|
||||||
@ -189,6 +205,24 @@ class XCResultIssue {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a [XCResultIssue] without JSON parsing for testing.
|
||||||
|
@visibleForTesting
|
||||||
|
factory XCResultIssue.test({
|
||||||
|
XCResultIssueType type = XCResultIssueType.error,
|
||||||
|
String? subType,
|
||||||
|
String? message,
|
||||||
|
String? location,
|
||||||
|
List<String> warnings = const <String>[],
|
||||||
|
}) {
|
||||||
|
return XCResultIssue._(
|
||||||
|
type: type,
|
||||||
|
subType: subType,
|
||||||
|
message: message,
|
||||||
|
location: location,
|
||||||
|
warnings: warnings,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
XCResultIssue._({
|
XCResultIssue._({
|
||||||
required this.type,
|
required this.type,
|
||||||
required this.subType,
|
required this.subType,
|
||||||
|
@ -135,13 +135,13 @@ void main() {
|
|||||||
testUsingContext('flutter assemble does not log stack traces during build failure', () async {
|
testUsingContext('flutter assemble does not log stack traces during build failure', () async {
|
||||||
final CommandRunner<void> commandRunner = createTestCommandRunner(AssembleCommand(
|
final CommandRunner<void> commandRunner = createTestCommandRunner(AssembleCommand(
|
||||||
buildSystem: TestBuildSystem.all(BuildResult(success: false, exceptions: <String, ExceptionMeasurement>{
|
buildSystem: TestBuildSystem.all(BuildResult(success: false, exceptions: <String, ExceptionMeasurement>{
|
||||||
'hello': ExceptionMeasurement('hello', 'bar', stackTrace),
|
'hello': ExceptionMeasurement('hello', 'bar', stackTrace, fatal: true),
|
||||||
}))
|
}))
|
||||||
));
|
));
|
||||||
|
|
||||||
await expectLater(commandRunner.run(<String>['assemble', '-o Output', 'debug_macos_bundle_flutter_assets']),
|
await expectLater(commandRunner.run(<String>['assemble', '-o Output', 'debug_macos_bundle_flutter_assets']),
|
||||||
throwsToolExit());
|
throwsToolExit());
|
||||||
expect(testLogger.errorText, isNot(contains('bar')));
|
expect(testLogger.errorText, contains('Target hello failed: bar'));
|
||||||
expect(testLogger.errorText, isNot(contains(stackTrace.toString())));
|
expect(testLogger.errorText, isNot(contains(stackTrace.toString())));
|
||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
Cache: () => Cache.test(processManager: FakeProcessManager.any()),
|
Cache: () => Cache.test(processManager: FakeProcessManager.any()),
|
||||||
|
@ -372,7 +372,7 @@ void main() {
|
|||||||
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
|
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
|
||||||
});
|
});
|
||||||
|
|
||||||
testUsingContext('Display xcresult issues on console if parsed.', () async {
|
testUsingContext('Display xcresult issues on console if parsed, suppress Xcode output', () async {
|
||||||
final BuildCommand command = BuildCommand();
|
final BuildCommand command = BuildCommand();
|
||||||
|
|
||||||
createMinimalMockProjectFiles();
|
createMinimalMockProjectFiles();
|
||||||
@ -384,13 +384,16 @@ void main() {
|
|||||||
|
|
||||||
expect(testLogger.errorText, contains("Use of undeclared identifier 'asdas'"));
|
expect(testLogger.errorText, contains("Use of undeclared identifier 'asdas'"));
|
||||||
expect(testLogger.errorText, contains('/Users/m/Projects/test_create/ios/Runner/AppDelegate.m:7:56'));
|
expect(testLogger.errorText, contains('/Users/m/Projects/test_create/ios/Runner/AppDelegate.m:7:56'));
|
||||||
|
expect(testLogger.statusText, isNot(contains("Xcode's output")));
|
||||||
|
expect(testLogger.statusText, isNot(contains('Lots of spew from Xcode')));
|
||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
FileSystem: () => fileSystem,
|
FileSystem: () => fileSystem,
|
||||||
ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
|
ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
|
||||||
xattrCommand,
|
xattrCommand,
|
||||||
setUpFakeXcodeBuildHandler(exitCode: 1, onRun: () {
|
setUpFakeXcodeBuildHandler(exitCode: 1, onRun: () {
|
||||||
fileSystem.systemTempDirectory.childDirectory(_xcBundleFilePath).createSync();
|
fileSystem.systemTempDirectory.childDirectory(_xcBundleFilePath).createSync();
|
||||||
}),
|
}, stdout: 'Lots of spew from Xcode',
|
||||||
|
),
|
||||||
setUpXCResultCommand(stdout: kSampleResultJsonWithIssues),
|
setUpXCResultCommand(stdout: kSampleResultJsonWithIssues),
|
||||||
setUpRsyncCommand(),
|
setUpRsyncCommand(),
|
||||||
]),
|
]),
|
||||||
@ -621,7 +624,6 @@ Runner requires a provisioning profile. Select a provisioning profile in the Sig
|
|||||||
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(developmentTeam: null),
|
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(developmentTeam: null),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
testUsingContext('xcresult did not detect issue but detected by stdout.', () async {
|
testUsingContext('xcresult did not detect issue but detected by stdout.', () async {
|
||||||
final BuildCommand command = BuildCommand();
|
final BuildCommand command = BuildCommand();
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ import 'package:flutter_tools/src/cache.dart';
|
|||||||
import 'package:flutter_tools/src/ios/code_signing.dart';
|
import 'package:flutter_tools/src/ios/code_signing.dart';
|
||||||
import 'package:flutter_tools/src/ios/iproxy.dart';
|
import 'package:flutter_tools/src/ios/iproxy.dart';
|
||||||
import 'package:flutter_tools/src/ios/mac.dart';
|
import 'package:flutter_tools/src/ios/mac.dart';
|
||||||
|
import 'package:flutter_tools/src/ios/xcresult.dart';
|
||||||
import 'package:flutter_tools/src/project.dart';
|
import 'package:flutter_tools/src/project.dart';
|
||||||
import 'package:flutter_tools/src/reporting/reporting.dart';
|
import 'package:flutter_tools/src/reporting/reporting.dart';
|
||||||
import 'package:test/fake.dart';
|
import 'package:test/fake.dart';
|
||||||
@ -265,49 +266,8 @@ Xcode's output:
|
|||||||
↳
|
↳
|
||||||
blah
|
blah
|
||||||
|
|
||||||
=== CLEAN TARGET url_launcher OF PROJECT Pods WITH CONFIGURATION Release ===
|
|
||||||
|
|
||||||
Check dependencies
|
|
||||||
|
|
||||||
blah
|
|
||||||
|
|
||||||
=== CLEAN TARGET Pods-Runner OF PROJECT Pods WITH CONFIGURATION Release ===
|
|
||||||
|
|
||||||
Check dependencies
|
|
||||||
|
|
||||||
blah
|
|
||||||
|
|
||||||
=== CLEAN TARGET Runner OF PROJECT Runner WITH CONFIGURATION Release ===
|
|
||||||
|
|
||||||
Check dependencies
|
Check dependencies
|
||||||
[BCEROR]Signing for "Runner" requires a development team. Select a development team in the project editor.
|
[BCEROR]Signing for "Runner" requires a development team. Select a development team in the project editor.
|
||||||
[BCEROR]Code signing is required for product type 'Application' in SDK 'iOS 10.3'
|
|
||||||
[BCEROR]Code signing is required for product type 'Application' in SDK 'iOS 10.3'
|
|
||||||
[BCEROR]Code signing is required for product type 'Application' in SDK 'iOS 10.3'
|
|
||||||
|
|
||||||
blah
|
|
||||||
|
|
||||||
** CLEAN SUCCEEDED **
|
|
||||||
|
|
||||||
=== BUILD TARGET url_launcher OF PROJECT Pods WITH CONFIGURATION Release ===
|
|
||||||
|
|
||||||
Check dependencies
|
|
||||||
|
|
||||||
blah
|
|
||||||
|
|
||||||
=== BUILD TARGET Pods-Runner OF PROJECT Pods WITH CONFIGURATION Release ===
|
|
||||||
|
|
||||||
Check dependencies
|
|
||||||
|
|
||||||
blah
|
|
||||||
|
|
||||||
=== BUILD TARGET Runner OF PROJECT Runner WITH CONFIGURATION Release ===
|
|
||||||
|
|
||||||
Check dependencies
|
|
||||||
Signing for "Runner" requires a development team. Select a development team in the project editor.
|
|
||||||
Code signing is required for product type 'Application' in SDK 'iOS 10.3'
|
|
||||||
Code signing is required for product type 'Application' in SDK 'iOS 10.3'
|
|
||||||
Code signing is required for product type 'Application' in SDK 'iOS 10.3'
|
|
||||||
|
|
||||||
Could not build the precompiled application for the device.''',
|
Could not build the precompiled application for the device.''',
|
||||||
xcodeBuildExecution: XcodeBuildExecution(
|
xcodeBuildExecution: XcodeBuildExecution(
|
||||||
@ -324,6 +284,47 @@ Could not build the precompiled application for the device.''',
|
|||||||
contains('Building a deployable iOS app requires a selected Development Team with a \nProvisioning Profile.'),
|
contains('Building a deployable iOS app requires a selected Development Team with a \nProvisioning Profile.'),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWithoutContext('does not show no development team message when other Xcode issues detected', () async {
|
||||||
|
final XcodeBuildResult buildResult = XcodeBuildResult(
|
||||||
|
success: false,
|
||||||
|
stdout: '''
|
||||||
|
Running "flutter pub get" in flutter_gallery... 0.6s
|
||||||
|
Launching lib/main.dart on x in release mode...
|
||||||
|
Running pod install... 1.2s
|
||||||
|
Running Xcode build... 1.4s
|
||||||
|
Failed to build iOS app
|
||||||
|
Error output from Xcode build:
|
||||||
|
↳
|
||||||
|
** BUILD FAILED **
|
||||||
|
|
||||||
|
|
||||||
|
The following build commands failed:
|
||||||
|
Check dependencies
|
||||||
|
(1 failure)
|
||||||
|
Xcode's output:
|
||||||
|
↳
|
||||||
|
blah
|
||||||
|
|
||||||
|
Check dependencies
|
||||||
|
[BCEROR]Signing for "Runner" requires a development team. Select a development team in the project editor.
|
||||||
|
|
||||||
|
Could not build the precompiled application for the device.''',
|
||||||
|
xcodeBuildExecution: XcodeBuildExecution(
|
||||||
|
buildCommands: <String>['xcrun', 'xcodebuild', 'blah'],
|
||||||
|
appDirectory: '/blah/blah',
|
||||||
|
environmentType: EnvironmentType.physical,
|
||||||
|
buildSettings: buildSettings,
|
||||||
|
),
|
||||||
|
xcResult: XCResult.test(issues: <XCResultIssue>[
|
||||||
|
XCResultIssue.test(message: 'Target aot_assembly_release failed', subType: 'Error'),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
await diagnoseXcodeBuildFailure(buildResult, testUsage, logger);
|
||||||
|
expect(logger.errorText, contains('Error (Xcode): Target aot_assembly_release failed'));
|
||||||
|
expect(logger.errorText, isNot(contains('Building a deployable iOS app requires a selected Development Team')));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
group('Upgrades project.pbxproj for old asset usage', () {
|
group('Upgrades project.pbxproj for old asset usage', () {
|
||||||
|
@ -64,8 +64,7 @@ int x = 'String';
|
|||||||
], workingDirectory: projectRoot.path);
|
], workingDirectory: projectRoot.path);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
// iOS shows this as stdout.
|
result.stderr,
|
||||||
targetPlatform == 'ios' ? result.stdout : result.stderr,
|
|
||||||
contains("A value of type 'String' can't be assigned to a variable of type 'int'."),
|
contains("A value of type 'String' can't be assigned to a variable of type 'int'."),
|
||||||
);
|
);
|
||||||
expect(result.exitCode, 1);
|
expect(result.exitCode, 1);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user