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

This commit is contained in:
Jenn Magder 2022-10-17 10:47:25 -07:00 committed by GitHub
parent 3034a4ef39
commit f059dd40ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 108 additions and 67 deletions

View File

@ -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');
} }

View File

@ -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}',
); );

View File

@ -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();

View File

@ -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,
); );
} }
} }

View File

@ -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;
} }
} }

View File

@ -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,

View File

@ -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()),

View File

@ -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();

View File

@ -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', () {

View File

@ -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);