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
|
||||
!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');
|
||||
}
|
||||
|
||||
|
@ -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}',
|
||||
);
|
||||
|
@ -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();
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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()),
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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', () {
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user