Unverified Commit f059dd40 authored by Jenn Magder's avatar Jenn Magder Committed by GitHub

Show Xcode compilation errors at end of build, suppress stdout and stderr from Xcode (#113302)

parent 3034a4ef
...@@ -548,8 +548,7 @@ end ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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({ ...@@ -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 ...@@ -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) { ...@@ -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 @@ ...@@ -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 { ...@@ -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 { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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 ...@@ -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'; ...@@ -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,48 @@ Xcode's output: ...@@ -265,49 +266,48 @@ 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 Could not build the precompiled application for the device.''',
xcodeBuildExecution: XcodeBuildExecution(
buildCommands: <String>['xcrun', 'xcodebuild', 'blah'],
appDirectory: '/blah/blah',
environmentType: EnvironmentType.physical,
buildSettings: buildSettings,
),
);
blah await diagnoseXcodeBuildFailure(buildResult, testUsage, logger);
expect(
logger.errorText,
contains('Building a deployable iOS app requires a selected Development Team with a \nProvisioning Profile.'),
);
});
=== BUILD TARGET Pods-Runner OF PROJECT Pods WITH CONFIGURATION Release === 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 **
Check dependencies
The following build commands failed:
Check dependencies
(1 failure)
Xcode's output:
blah blah
=== BUILD TARGET Runner OF PROJECT Runner WITH CONFIGURATION Release ===
Check dependencies Check dependencies
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.
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(
...@@ -316,13 +316,14 @@ Could not build the precompiled application for the device.''', ...@@ -316,13 +316,14 @@ Could not build the precompiled application for the device.''',
environmentType: EnvironmentType.physical, environmentType: EnvironmentType.physical,
buildSettings: buildSettings, buildSettings: buildSettings,
), ),
xcResult: XCResult.test(issues: <XCResultIssue>[
XCResultIssue.test(message: 'Target aot_assembly_release failed', subType: 'Error'),
])
); );
await diagnoseXcodeBuildFailure(buildResult, testUsage, logger); await diagnoseXcodeBuildFailure(buildResult, testUsage, logger);
expect( expect(logger.errorText, contains('Error (Xcode): Target aot_assembly_release failed'));
logger.errorText, expect(logger.errorText, isNot(contains('Building a deployable iOS app requires a selected Development Team')));
contains('Building a deployable iOS app requires a selected Development Team with a \nProvisioning Profile.'),
);
}); });
}); });
......
...@@ -64,8 +64,7 @@ int x = 'String'; ...@@ -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);
......
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