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
!xcodebuildOutput.contains('Unable to detect a Flutter engine build directory in bogus') ||
!xcodebuildOutput.contains('Command PhaseScriptExecution failed with a nonzero exit code')) {
!xcodebuildOutput.contains('Unable to detect a Flutter engine build directory in bogus')) {
return TaskResult.failure('Host Objective-C app build succeeded though flutter script failed');
}

View File

@ -97,11 +97,17 @@ class Context {
if (verbose) {
print((result.stdout as String).trim());
}
if ((result.stderr as String).isNotEmpty) {
echoError((result.stderr as String).trim());
final String resultStderr = result.stderr.toString().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) {
stderr.write('${result.stderr}\n');
throw Exception(
'Command "$bin ${args.join(' ')}" exited with code ${result.exitCode}',
);

View File

@ -871,13 +871,11 @@ class _BuildInstance {
ErrorHandlingFileSystem.deleteIfExists(previousFile);
}
} 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);
succeeded = false;
skipped = false;
exceptionMeasurements[node.target.name] = ExceptionMeasurement(
node.target.name, exception, stackTrace);
node.target.name, exception, stackTrace, fatal: true);
} finally {
resource.release();
stopwatch.stop();

View File

@ -347,7 +347,7 @@ class AssembleCommand extends FlutterCommand {
for (final ExceptionMeasurement measurement in result.exceptions.values) {
if (measurement.fatal || globals.logger.isVerbose) {
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) {
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(
success: false,
stdout: buildResult.stdout,
@ -738,7 +730,7 @@ bool _handleIssues(XCResult? xcResult, Logger logger, XcodeBuildExecution? xcode
if (requiresProvisioningProfile) {
logger.printError(noProvisioningProfileInstruction, emphasis: true);
} else if (_missingDevelopmentTeam(xcodeBuildExecution)) {
} else if ((!issueDetected || hasProvisioningProfileIssue) && _missingDevelopmentTeam(xcodeBuildExecution)) {
issueDetected = true;
logger.printError(noDevelopmentTeamInstruction, emphasis: true);
} 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.
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
// 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)) {
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
// found in the LICENSE file.
import 'package:meta/meta.dart';
import '../../src/base/process.dart';
import '../../src/convert.dart' show json;
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._({
this.issues = const <XCResultIssue>[],
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._({
required this.type,
required this.subType,

View File

@ -135,13 +135,13 @@ void main() {
testUsingContext('flutter assemble does not log stack traces during build failure', () async {
final CommandRunner<void> commandRunner = createTestCommandRunner(AssembleCommand(
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']),
throwsToolExit());
expect(testLogger.errorText, isNot(contains('bar')));
expect(testLogger.errorText, contains('Target hello failed: bar'));
expect(testLogger.errorText, isNot(contains(stackTrace.toString())));
}, overrides: <Type, Generator>{
Cache: () => Cache.test(processManager: FakeProcessManager.any()),

View File

@ -372,7 +372,7 @@ void main() {
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();
createMinimalMockProjectFiles();
@ -384,13 +384,16 @@ void main() {
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.statusText, isNot(contains("Xcode's output")));
expect(testLogger.statusText, isNot(contains('Lots of spew from Xcode')));
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
xattrCommand,
setUpFakeXcodeBuildHandler(exitCode: 1, onRun: () {
fileSystem.systemTempDirectory.childDirectory(_xcBundleFilePath).createSync();
}),
}, stdout: 'Lots of spew from Xcode',
),
setUpXCResultCommand(stdout: kSampleResultJsonWithIssues),
setUpRsyncCommand(),
]),
@ -621,7 +624,6 @@ Runner requires a provisioning profile. Select a provisioning profile in the Sig
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(developmentTeam: null),
});
testUsingContext('xcresult did not detect issue but detected by stdout.', () async {
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/iproxy.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/reporting/reporting.dart';
import 'package:test/fake.dart';
@ -265,49 +266,8 @@ Xcode's output:
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
[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.''',
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.'),
);
});
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', () {

View File

@ -64,8 +64,7 @@ int x = 'String';
], workingDirectory: projectRoot.path);
expect(
// iOS shows this as stdout.
targetPlatform == 'ios' ? result.stdout : result.stderr,
result.stderr,
contains("A value of type 'String' can't be assigned to a variable of type 'int'."),
);
expect(result.exitCode, 1);