Exit as failure if any transformer had an error (#1531)

Fixes #1336

Record when we log and error and mark the entire build as failed. This
catches the cases where there are errors on assets that did not
transitively impact dart2js output - like transformers on non-Dart
files.
diff --git a/lib/src/command/build.dart b/lib/src/command/build.dart
index 93537b6..f8ce382 100644
--- a/lib/src/command/build.dart
+++ b/lib/src/command/build.dart
@@ -69,10 +69,12 @@
       var environment = await AssetEnvironment.create(entrypoint, mode,
           environmentConstants: environmentConstants, useDart2JS: true);
 
+      var hasError = false;
       // Show in-progress errors, but not results. Those get handled
       // implicitly by getAllAssets().
       environment.barback.errors.listen((error) {
         log.error(log.red("Build error:\n$error"));
+        hasError = true;
 
         if (log.json.enabled) {
           // Wrap the error in a map in case we end up decorating it with
@@ -111,12 +113,19 @@
       log.message('Built $builtFiles ${pluralize('file', builtFiles)} '
           'to "$outputDirectory".');
 
-      log.json.message({
-        "buildResult": "success",
-        "outputDirectory": outputDirectory,
-        "numFiles": builtFiles,
-        "log": logJson
-      });
+      if (hasError) {
+        log.error(log.red("Build failed."));
+        log.json.message(
+            {"buildResult": "failure", "errors": errorsJson, "log": logJson});
+        return flushThenExit(exit_codes.DATA);
+      } else {
+        log.json.message({
+          "buildResult": "success",
+          "outputDirectory": outputDirectory,
+          "numFiles": builtFiles,
+          "log": logJson
+        });
+      }
     } on BarbackException catch (_) {
       // If [getAllAssets()] throws a BarbackException, the error has already
       // been reported.
diff --git a/test/build/reports_failures_test.dart b/test/build/reports_failures_test.dart
new file mode 100644
index 0000000..b50e010
--- /dev/null
+++ b/test/build/reports_failures_test.dart
@@ -0,0 +1,45 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS d.file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+
+const _failingTransformer = """
+import 'dart:async';
+
+import 'package:barback/barback.dart';
+
+class FailingTransformer extends Transformer {
+  FailingTransformer.asPlugin();
+
+  String get allowedExtensions => '.txt';
+
+  Future apply(Transform transform) {
+    throw 'FAIL!';
+  }
+}
+""";
+
+main() {
+  integration("reports failures in transformers which don't output dart", () {
+    // Test for https://github.com/dart-lang/pub/issues/1336
+    serveBarback();
+
+    d.dir(appPath, [
+      d.pubspec({
+        "name": "myapp",
+        "transformers": ["myapp/src/transformer"],
+        "dependencies": {"barback": "any"}
+      }),
+      d.dir("lib", [
+        d.dir("src", [d.file("transformer.dart", _failingTransformer)])
+      ]),
+      d.dir("web",
+          [d.file("foo.txt", "foo"), d.file("main.dart", "void main() {}")])
+    ]).create();
+
+    pubGet();
+    schedulePub(args: ["build"], exitCode: 65);
+  });
+}