Don't log stack traces to console on build failures (#44966)


diff --git a/packages/flutter_tools/lib/src/aot.dart b/packages/flutter_tools/lib/src/aot.dart
index 50be745..b76d227 100644
--- a/packages/flutter_tools/lib/src/aot.dart
+++ b/packages/flutter_tools/lib/src/aot.dart
@@ -226,8 +226,11 @@
     status?.stop();
     if (!result.success) {
       for (ExceptionMeasurement measurement in result.exceptions.values) {
-        printError(measurement.exception.toString());
-        printError(measurement.stackTrace.toString());
+        printError('Target ${measurement.target} failed: ${measurement.exception}',
+          stackTrace: measurement.fatal
+            ? measurement.stackTrace
+            : null,
+        );
       }
       throwToolExit('Failed to build aot.');
     }
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 3c303c6..c81a43c 100644
--- a/packages/flutter_tools/lib/src/build_system/build_system.dart
+++ b/packages/flutter_tools/lib/src/build_system/build_system.dart
@@ -556,6 +556,8 @@
         }
       }
     } catch (exception, stackTrace) {
+      // TODO(jonahwilliams): throw specific exception for expected errors to mark
+      // as non-fatal. All others should be fatal.
       node.target.clearStamp(environment);
       passed = false;
       skipped = false;
@@ -573,12 +575,15 @@
 
 /// Helper class to collect exceptions.
 class ExceptionMeasurement {
-  ExceptionMeasurement(this.target, this.exception, this.stackTrace);
+  ExceptionMeasurement(this.target, this.exception, this.stackTrace, {this.fatal = false});
 
   final String target;
   final dynamic exception;
   final StackTrace stackTrace;
 
+  /// Whether this exception was a fatal build system error.
+  final bool fatal;
+
   @override
   String toString() => 'target: $target\nexception:$exception\n$stackTrace';
 }
diff --git a/packages/flutter_tools/lib/src/bundle.dart b/packages/flutter_tools/lib/src/bundle.dart
index 0193354..8d157ea9 100644
--- a/packages/flutter_tools/lib/src/bundle.dart
+++ b/packages/flutter_tools/lib/src/bundle.dart
@@ -126,8 +126,11 @@
 
   if (!result.success) {
     for (ExceptionMeasurement measurement in result.exceptions.values) {
-      printError(measurement.exception.toString());
-      printError(measurement.stackTrace.toString());
+        printError('Target ${measurement.target} failed: ${measurement.exception}',
+          stackTrace: measurement.fatal
+            ? measurement.stackTrace
+            : null,
+        );
     }
     throwToolExit('Failed to build bundle.');
   }
diff --git a/packages/flutter_tools/lib/src/commands/assemble.dart b/packages/flutter_tools/lib/src/commands/assemble.dart
index b475880..8b83775 100644
--- a/packages/flutter_tools/lib/src/commands/assemble.dart
+++ b/packages/flutter_tools/lib/src/commands/assemble.dart
@@ -155,8 +155,12 @@
       resourcePoolSize: argResults['resource-pool-size'],
     ));
     if (!result.success) {
-      for (MapEntry<String, ExceptionMeasurement> data in result.exceptions.entries) {
-        printError('Target ${data.key} failed: ${data.value.exception}', stackTrace: data.value.stackTrace);
+      for (ExceptionMeasurement measurement in result.exceptions.values) {
+        printError('Target ${measurement.target} failed: ${measurement.exception}',
+          stackTrace: measurement.fatal
+            ? measurement.stackTrace
+            : null,
+        );
       }
       throwToolExit('build failed.');
     }
diff --git a/packages/flutter_tools/lib/src/web/compile.dart b/packages/flutter_tools/lib/src/web/compile.dart
index f0d9524..4f40ccc 100644
--- a/packages/flutter_tools/lib/src/web/compile.dart
+++ b/packages/flutter_tools/lib/src/web/compile.dart
@@ -54,8 +54,11 @@
     ));
     if (!result.success) {
       for (ExceptionMeasurement measurement in result.exceptions.values) {
-        printError(measurement.stackTrace.toString());
-        printError(measurement.exception.toString());
+        printError('Target ${measurement.target} failed: ${measurement.exception}',
+          stackTrace: measurement.fatal
+            ? measurement.stackTrace
+            : null,
+        );
       }
       throwToolExit('Failed to compile application for the Web.');
     }
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 176cb51..1b1a8ed 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/assemble_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/assemble_test.dart
@@ -41,7 +41,8 @@
       });
     final CommandRunner<void> commandRunner = createTestCommandRunner(AssembleCommand());
 
-    expect(commandRunner.run(<String>['assemble', 'debug_macos_bundle_flutter_assets']), throwsA(isInstanceOf<ToolExit>()));
+    expect(commandRunner.run(<String>['assemble', 'debug_macos_bundle_flutter_assets']),
+      throwsA(isInstanceOf<ToolExit>()));
   });
 
   testbed.test('Throws ToolExit if called with non-existent rule', () async {
@@ -51,7 +52,25 @@
       });
     final CommandRunner<void> commandRunner = createTestCommandRunner(AssembleCommand());
 
-    expect(commandRunner.run(<String>['assemble', '-o Output', 'undefined']), throwsA(isInstanceOf<ToolExit>()));
+    expect(commandRunner.run(<String>['assemble', '-o Output', 'undefined']),
+      throwsA(isInstanceOf<ToolExit>()));
+  });
+
+  testbed.test('Does not log stack traces during build failure', () async {
+    final BufferLogger bufferLogger = logger;
+    final StackTrace testStackTrace = StackTrace.current;
+    when(buildSystem.build(any, any, buildSystemConfig: anyNamed('buildSystemConfig')))
+      .thenAnswer((Invocation invocation) async {
+        return BuildResult(success: false, exceptions: <String, ExceptionMeasurement>{
+          'hello': ExceptionMeasurement('hello', 'bar', testStackTrace),
+        });
+      });
+    final CommandRunner<void> commandRunner = createTestCommandRunner(AssembleCommand());
+
+    await expectLater(commandRunner.run(<String>['assemble', '-o Output', 'debug_macos_bundle_flutter_assets']),
+      throwsA(isInstanceOf<ToolExit>()));
+    expect(bufferLogger.errorText, contains('bar'));
+    expect(bufferLogger.errorText, isNot(contains(testStackTrace.toString())));
   });
 
   testbed.test('Only writes input and output files when the values change', () async {
@@ -65,7 +84,13 @@
       });
 
     final CommandRunner<void> commandRunner = createTestCommandRunner(AssembleCommand());
-    await commandRunner.run(<String>['assemble', '-o Output', '--build-outputs=outputs', '--build-inputs=inputs', 'debug_macos_bundle_flutter_assets']);
+    await commandRunner.run(<String>[
+      'assemble',
+      '-o Output',
+      '--build-outputs=outputs',
+      '--build-inputs=inputs',
+      'debug_macos_bundle_flutter_assets',
+    ]);
 
     final File inputs = fs.file('inputs');
     final File outputs = fs.file('outputs');
@@ -75,7 +100,13 @@
     final DateTime theDistantPast = DateTime(1991, 8, 23);
     inputs.setLastModifiedSync(theDistantPast);
     outputs.setLastModifiedSync(theDistantPast);
-    await commandRunner.run(<String>['assemble', '-o Output', '--build-outputs=outputs', '--build-inputs=inputs', 'debug_macos_bundle_flutter_assets']);
+    await commandRunner.run(<String>[
+      'assemble',
+      '-o Output',
+      '--build-outputs=outputs',
+      '--build-inputs=inputs',
+      'debug_macos_bundle_flutter_assets',
+    ]);
 
     expect(inputs.lastModifiedSync(), theDistantPast);
     expect(outputs.lastModifiedSync(), theDistantPast);
@@ -87,7 +118,13 @@
           inputFiles: <File>[fs.file('foo'), fs.file('fizz')..createSync()],
           outputFiles: <File>[fs.file('bar'), fs.file(fs.path.join('.dart_tool', 'fizz2'))..createSync(recursive: true)]);
       });
-    await commandRunner.run(<String>['assemble', '-o Output', '--build-outputs=outputs', '--build-inputs=inputs', 'debug_macos_bundle_flutter_assets']);
+    await commandRunner.run(<String>[
+      'assemble',
+      '-o Output',
+      '--build-outputs=outputs',
+      '--build-inputs=inputs',
+      'debug_macos_bundle_flutter_assets',
+    ]);
 
     expect(inputs.readAsStringSync(), contains('foo'));
     expect(inputs.readAsStringSync(), contains('fizz'));