diff --git a/dev/devicelab/bin/tasks/module_test_ios.dart b/dev/devicelab/bin/tasks/module_test_ios.dart index 82d4f81437..f2322c3c0a 100644 --- a/dev/devicelab/bin/tasks/module_test_ios.dart +++ b/dev/devicelab/bin/tasks/module_test_ios.dart @@ -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'); } diff --git a/packages/flutter_tools/bin/xcode_backend.dart b/packages/flutter_tools/bin/xcode_backend.dart index 0d81840df9..ec85e9cba4 100644 --- a/packages/flutter_tools/bin/xcode_backend.dart +++ b/packages/flutter_tools/bin/xcode_backend.dart @@ -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}', ); diff --git a/packages/flutter_tools/lib/src/build_system/build_system.dart b/packages/flutter_tools/lib/src/build_system/build_system.dart index 55569859c9..0f5acd33b0 100644 --- a/packages/flutter_tools/lib/src/build_system/build_system.dart +++ b/packages/flutter_tools/lib/src/build_system/build_system.dart @@ -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(); diff --git a/packages/flutter_tools/lib/src/commands/assemble.dart b/packages/flutter_tools/lib/src/commands/assemble.dart index 41c3054598..c32f89a3f9 100644 --- a/packages/flutter_tools/lib/src/commands/assemble.dart +++ b/packages/flutter_tools/lib/src/commands/assemble.dart @@ -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, ); } } diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart index cd9b9efbae..c023f1809f 100644 --- a/packages/flutter_tools/lib/src/ios/mac.dart +++ b/packages/flutter_tools/lib/src/ios/mac.dart @@ -414,14 +414,6 @@ Future 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; } } diff --git a/packages/flutter_tools/lib/src/ios/xcresult.dart b/packages/flutter_tools/lib/src/ios/xcresult.dart index 4f505735f9..5bb520e0a5 100644 --- a/packages/flutter_tools/lib/src/ios/xcresult.dart +++ b/packages/flutter_tools/lib/src/ios/xcresult.dart @@ -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? issues, + bool? parseSuccess, + String? parsingErrorMessage, + }) { + return XCResult._( + issues: issues ?? const [], + parseSuccess: parseSuccess ?? true, + parsingErrorMessage: parsingErrorMessage, + ); + } + XCResult._({ this.issues = const [], 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 warnings = const [], + }) { + return XCResultIssue._( + type: type, + subType: subType, + message: message, + location: location, + warnings: warnings, + ); + } + XCResultIssue._({ required this.type, required this.subType, diff --git a/packages/flutter_tools/test/commands.shard/hermetic/assemble_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/assemble_test.dart index 0137d7fffc..22c30a6f78 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/assemble_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/assemble_test.dart @@ -135,13 +135,13 @@ void main() { testUsingContext('flutter assemble does not log stack traces during build failure', () async { final CommandRunner commandRunner = createTestCommandRunner(AssembleCommand( buildSystem: TestBuildSystem.all(BuildResult(success: false, exceptions: { - 'hello': ExceptionMeasurement('hello', 'bar', stackTrace), + 'hello': ExceptionMeasurement('hello', 'bar', stackTrace, fatal: true), })) )); await expectLater(commandRunner.run(['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: { Cache: () => Cache.test(processManager: FakeProcessManager.any()), diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_ios_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_ios_test.dart index a9dafa324d..57957c8ca2 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/build_ios_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/build_ios_test.dart @@ -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: { FileSystem: () => fileSystem, ProcessManager: () => FakeProcessManager.list([ 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(); diff --git a/packages/flutter_tools/test/general.shard/ios/mac_test.dart b/packages/flutter_tools/test/general.shard/ios/mac_test.dart index 4b9e33c491..acf2138437 100644 --- a/packages/flutter_tools/test/general.shard/ios/mac_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/mac_test.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/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: ['xcrun', 'xcodebuild', 'blah'], + appDirectory: '/blah/blah', + environmentType: EnvironmentType.physical, + buildSettings: buildSettings, + ), + xcResult: XCResult.test(issues: [ + 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', () { diff --git a/packages/flutter_tools/test/integration.shard/flutter_build_with_compilation_error_test.dart b/packages/flutter_tools/test/integration.shard/flutter_build_with_compilation_error_test.dart index 031201f110..d487f89988 100644 --- a/packages/flutter_tools/test/integration.shard/flutter_build_with_compilation_error_test.dart +++ b/packages/flutter_tools/test/integration.shard/flutter_build_with_compilation_error_test.dart @@ -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);