[roll] Update third-party dart packages

Change-Id: I1378d82bc909788bbfd7eff34fa8f695bdb63b84
diff --git a/bazel_worker/.gitignore b/bazel_worker/.gitignore
new file mode 100644
index 0000000..00035d7
--- /dev/null
+++ b/bazel_worker/.gitignore
@@ -0,0 +1,10 @@
+.buildlog
+.DS_Store
+.idea
+.pub/
+.settings/
+build/
+packages
+.packages
+pubspec.lock
+.dart_tool
diff --git a/bazel_worker/.travis.yml b/bazel_worker/.travis.yml
new file mode 100644
index 0000000..f21e9b1
--- /dev/null
+++ b/bazel_worker/.travis.yml
@@ -0,0 +1,18 @@
+language: dart
+
+script: ./tool/travis.sh
+
+dart:
+  - dev
+
+# Speed up builds by using containerization. Disable this if you need to use
+# sudo in your scripts.
+sudo: false
+
+branches:
+  only:
+    - master
+
+cache:
+  directories:
+  - $HOME/.pub-cache
diff --git a/bazel_worker/AUTHORS b/bazel_worker/AUTHORS
new file mode 100644
index 0000000..e8063a8
--- /dev/null
+++ b/bazel_worker/AUTHORS
@@ -0,0 +1,6 @@
+# Below is a list of people and organizations that have contributed
+# to the project. Names should be added to the list like so:
+#
+#   Name/Organization <email address>
+
+Google Inc.
diff --git a/bazel_worker/BUILD.gn b/bazel_worker/BUILD.gn
new file mode 100644
index 0000000..f34a3f5
--- /dev/null
+++ b/bazel_worker/BUILD.gn
@@ -0,0 +1,19 @@
+# This file is generated by importer.py for bazel_worker-0.1.23+1
+
+import("//build/dart/dart_library.gni")
+
+dart_library("bazel_worker") {
+  package_name = "bazel_worker"
+
+  # This parameter is left empty as we don't care about analysis or exporting
+  # these sources outside of the tree.
+  sources = []
+
+  disable_analysis = true
+
+  deps = [
+    "//third_party/dart-pkg/pub/async",
+    "//third_party/dart-pkg/pub/pedantic",
+    "//third_party/dart-pkg/pub/protobuf",
+  ]
+}
diff --git a/bazel_worker/CHANGELOG.md b/bazel_worker/CHANGELOG.md
new file mode 100644
index 0000000..6a6fed5
--- /dev/null
+++ b/bazel_worker/CHANGELOG.md
@@ -0,0 +1,129 @@
+## 0.1.23+1
+
+* Don't rely on `exitCode` to know when a worker terminates, instead wait for
+  the input stream to close.
+  * The SDK may also start throwing instead of returning a `null` here, so this
+    pre-emptively guards against that.
+
+## 0.1.23
+
+* Support protobuf `1.x`.
+* Added a tool for updating generated proto files and updated them
+  using the latest version of the protoc_plugin package.
+  * This required a lower bound bump of the `protobuf` package to `0.14.4`.
+
+## 0.1.22
+
+* Require protobuf 0.14.0.
+
+## 0.1.21
+
+* Make `TestStdinAsync` behave like a `Stream<Uint8List>`
+
+## 0.1.20
+
+* Close worker `outputStream` on `cancel`.
+
+## 0.1.19
+
+* Work around https://github.com/dart-lang/sdk/issues/35874.
+
+## 0.1.18
+
+* Add a `trackWork` optional named argument to `BazelDriver.doWork`. This allows
+  the caller to know when a work request is actually sent to a worker.
+
+## 0.1.17
+
+* Allow protobuf 0.13.0.
+
+## 0.1.16
+
+* Update the worker_protocol.pb.dart file with the latest proto generator.
+* Require protobuf 0.11.0.
+
+## 0.1.15
+
+* Update the worker_protocol.pb.dart file with the latest proto generator.
+* Require protobuf 0.10.4.
+
+## 0.1.14
+
+* Allow workers to support running in isolates. To support running in isolates,
+  workers must modify their `main` method to accept a `SendPort` then use it
+  when creating the `AsyncWorkerConnection`. See `async_worker` in `e2e_test`.
+
+## 0.1.13
+
+* Support protobuf 0.10.0.
+
+## 0.1.12
+
+* Set max SDK version to `<3.0.0`.
+
+## 0.1.11
+
+* Added support for protobuf 0.9.0.
+
+## 0.1.10
+
+* Update the SDK dependency to 2.0.0-dev.17.0.
+* Update to protobuf version 0.8.0
+* Remove usages of deprecated upper-case constants from the SDK.
+
+## 0.1.9
+
+* Update the worker_protocol.pb.dart file with the latest proto generator.
+
+## 0.1.8
+
+* Add `Future cancel()` method to `DriverConnection`, which in the case of a
+  `StdDriverConnection` closes the input stream.
+  * The `terminateWorkers` method on `BazelWorkerDriver` now calls `cancel` on
+    all worker connections to ensure the vm can exit correctly.
+
+## 0.1.7
+
+* Update the `BazelWorkerDriver` class to handle worker crashes, and retry work
+  requests. The number of retries is configurable with the new `int maxRetries`
+  optional arg to the `BazelWorkerDriver` constructor.
+
+## 0.1.6
+
+* Update the worker_protocol.pb.dart file with the latest proto generator.
+* Add support for package:async 2.x and package:protobuf 6.x.
+
+## 0.1.5
+
+* Change TestStdinAsync.controller to StreamController<List<int>> (instead of
+  using dynamic as the type argument).
+
+## 0.1.4
+
+* Added `BazelWorkerDriver` class, which can be used to implement the bazel side
+  of the protocol. This allows you to speak to any process which knows the bazel
+  protocol from your own process.
+* Changed `WorkerConnection#readRequest` to return a `FutureOr<WorkRequest>`
+  instead of dynamic.
+
+## 0.1.3
+
+* Add automatic intercepting of print calls and append them to
+  `response.output`. This makes more libraries work out of the box, as printing
+  would previously cause an error due to communication over stdin/stdout.
+  * Note that using stdin/stdout directly will still cause an error, but that is
+    less common.
+
+## 0.1.2
+
+* Add better handling for the case where stdin gives an error instead of an EOF.
+
+## 0.1.1
+
+* Export `AsyncMessageGrouper` and `SyncMessageGrouper` as part of the testing
+  library. These can assist when writing e2e tests and communicating with a
+  worker process.
+
+## 0.1.0
+
+* Initial version.
diff --git a/bazel_worker/CONTRIBUTING.md b/bazel_worker/CONTRIBUTING.md
new file mode 100644
index 0000000..6f5e0ea
--- /dev/null
+++ b/bazel_worker/CONTRIBUTING.md
@@ -0,0 +1,33 @@
+Want to contribute? Great! First, read this page (including the small print at
+the end).
+
+### Before you contribute
+Before we can use your code, you must sign the
+[Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual)
+(CLA), which you can do online. The CLA is necessary mainly because you own the
+copyright to your changes, even after your contribution becomes part of our
+codebase, so we need your permission to use and distribute your code. We also
+need to be sure of various other things—for instance that you'll tell us if you
+know that your code infringes on other people's patents. You don't have to sign
+the CLA until after you've submitted your code for review and a member has
+approved it, but you must do it before we can put your code into our codebase.
+
+Before you start working on a larger contribution, you should get in touch with
+us first through the issue tracker with your idea so that we can help out and
+possibly guide you. Coordinating up front makes it much easier to avoid
+frustration later on.
+
+### Code reviews
+All submissions, including submissions by project members, require review.
+
+### File headers
+All files in the project must start with the following header.
+
+    // Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS 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.
+
+### The small print
+Contributions made by corporations are covered by a different agreement than the
+one above, the
+[Software Grant and Corporate Contributor License Agreement](https://developers.google.com/open-source/cla/corporate).
diff --git a/bazel_worker/LICENSE b/bazel_worker/LICENSE
new file mode 100644
index 0000000..82e9b52
--- /dev/null
+++ b/bazel_worker/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2016, the Dart project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of Google Inc. nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/bazel_worker/README.md b/bazel_worker/README.md
new file mode 100644
index 0000000..41a6784
--- /dev/null
+++ b/bazel_worker/README.md
@@ -0,0 +1,66 @@
+Tools for creating a persistent worker loop for [bazel](http://bazel.io/).
+
+## Usage
+
+There are two abstract classes provided by this package, `AsyncWorkerLoop` and
+`SyncWorkerLoop`. These each have a `performRequest` method which you must
+implement.
+
+Lets look at a simple example of a `SyncWorkerLoop` implementation:
+
+```dart
+import 'dart:io';
+import 'package:bazel_worker/bazel_worker.dart';
+
+void main() {
+  // Blocks until it gets an EOF from stdin.
+  new SyncSimpleWorker().run();
+}
+
+class SyncSimpleWorker extends SyncWorkerLoop {
+  /// Must synchronously return a [WorkResponse], since this is a
+  /// [SyncWorkerLoop].
+  WorkResponse performRequest(WorkRequest request) {
+    new File('hello.txt').writeAsStringSync('hello world!');
+    return new WorkResponse()..exitCode = EXIT_CODE_OK;
+  }
+}
+```
+
+And now the same thing, implemented as an `AsyncWorkerLoop`:
+
+```dart
+import 'dart:io';
+import 'package:bazel_worker/bazel_worker.dart';
+
+void main() {
+  // Doesn't block, runs tasks async as they are received on stdin.
+  new AsyncSimpleWorker().run();
+}
+
+class AsyncSimpleWorker extends AsyncWorkerLoop {
+  /// Must return a [Future<WorkResponse>], since this is an
+  /// [AsyncWorkerLoop].
+  Future<WorkResponse> performRequest(WorkRequest request) async {
+    await new File('hello.txt').writeAsString('hello world!');
+    return new WorkResponse()..exitCode = EXIT_CODE_OK;
+  }
+}
+```
+
+As you can see, these are nearly identical, it mostly comes down to the
+constraints on your package and personal preference which one you choose to
+implement.
+
+## Testing
+
+A `package:bazel_worker/testing.dart` file is also provided, which can greatly
+assist with writing unit tests for your worker. See the
+`test/worker_loop_test.dart` test included in this package for an example of how
+the helpers can be used.
+
+## Features and bugs
+
+Please file feature requests and bugs at the [issue tracker][tracker].
+
+[tracker]: https://github.com/dart-lang/bazel_worker/issues
diff --git a/bazel_worker/analysis_options.yaml b/bazel_worker/analysis_options.yaml
new file mode 100644
index 0000000..9781e73
--- /dev/null
+++ b/bazel_worker/analysis_options.yaml
@@ -0,0 +1,27 @@
+include: package:pedantic/analysis_options.yaml
+
+analyzer:
+
+linter:
+  rules:
+    - always_declare_return_types
+    # - annotate_overrides
+    - avoid_empty_else
+    - avoid_init_to_null
+    # - avoid_return_types_on_setters
+    - camel_case_types
+    # - constant_identifier_names
+    - empty_constructor_bodies
+    - hash_and_equals
+    - library_names
+    - library_prefixes
+    # - non_constant_identifier_names
+    - package_api_docs
+    - package_names
+    - package_prefixed_library_names
+    - prefer_is_not_empty
+    - slash_for_doc_comments
+    # - type_annotate_public_apis
+    - type_init_formals
+    - unnecessary_brace_in_string_interps
+    - unnecessary_getters_setters
diff --git a/bazel_worker/e2e_test/bin/async_worker.dart b/bazel_worker/e2e_test/bin/async_worker.dart
new file mode 100644
index 0000000..ddd530c
--- /dev/null
+++ b/bazel_worker/e2e_test/bin/async_worker.dart
@@ -0,0 +1,14 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:isolate';
+
+import 'package:e2e_test/async_worker.dart';
+
+/// This worker can run in one of two ways: normally, using stdin/stdout, or
+/// in an isolate, communicating over a [SendPort].
+Future main(List<String> args, [SendPort sendPort]) async {
+  await ExampleAsyncWorker(sendPort).run();
+}
diff --git a/bazel_worker/e2e_test/bin/async_worker_in_isolate.dart b/bazel_worker/e2e_test/bin/async_worker_in_isolate.dart
new file mode 100644
index 0000000..fce1eff
--- /dev/null
+++ b/bazel_worker/e2e_test/bin/async_worker_in_isolate.dart
@@ -0,0 +1,24 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:isolate';
+
+import 'package:e2e_test/forwards_to_isolate_async_worker.dart';
+
+/// Wraps the worker provided by `async_worker.dart`, launching it in an
+/// isolate. Requests are forwarded to the isolate and responses are returned
+/// directly from the isolate.
+///
+/// Anyone actually using the facility to wrap a worker in an isolate will want
+/// to use this code to do additional work, for example post processing one of
+/// the output files.
+Future main(List<String> args, SendPort message) async {
+  var receivePort = ReceivePort();
+  await Isolate.spawnUri(
+      Uri.file('async_worker.dart'), [], receivePort.sendPort);
+
+  var worker = await ForwardsToIsolateAsyncWorker.create(receivePort);
+  await worker.run();
+}
diff --git a/bazel_worker/e2e_test/bin/sync_worker.dart b/bazel_worker/e2e_test/bin/sync_worker.dart
new file mode 100644
index 0000000..9bdcc77
--- /dev/null
+++ b/bazel_worker/e2e_test/bin/sync_worker.dart
@@ -0,0 +1,9 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'package:e2e_test/sync_worker.dart';
+
+void main() {
+  ExampleSyncWorker().run();
+}
diff --git a/bazel_worker/e2e_test/lib/async_worker.dart b/bazel_worker/e2e_test/lib/async_worker.dart
new file mode 100644
index 0000000..075a5e6
--- /dev/null
+++ b/bazel_worker/e2e_test/lib/async_worker.dart
@@ -0,0 +1,23 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:isolate';
+
+import 'package:bazel_worker/bazel_worker.dart';
+
+/// Example worker that just returns in its response all the arguments passed
+/// separated by newlines.
+class ExampleAsyncWorker extends AsyncWorkerLoop {
+  /// Set [sendPort] to run in an isolate.
+  ExampleAsyncWorker([SendPort sendPort])
+      : super(connection: AsyncWorkerConnection(sendPort: sendPort));
+
+  @override
+  Future<WorkResponse> performRequest(WorkRequest request) async {
+    return WorkResponse()
+      ..exitCode = 0
+      ..output = request.arguments.join('\n');
+  }
+}
diff --git a/bazel_worker/e2e_test/lib/forwards_to_isolate_async_worker.dart b/bazel_worker/e2e_test/lib/forwards_to_isolate_async_worker.dart
new file mode 100644
index 0000000..bb937b2
--- /dev/null
+++ b/bazel_worker/e2e_test/lib/forwards_to_isolate_async_worker.dart
@@ -0,0 +1,28 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:isolate';
+
+import 'package:bazel_worker/bazel_worker.dart';
+import 'package:bazel_worker/driver.dart';
+
+/// Example worker that just forwards requests to an isolate.
+class ForwardsToIsolateAsyncWorker extends AsyncWorkerLoop {
+  final IsolateDriverConnection _isolateDriverConnection;
+
+  static Future<ForwardsToIsolateAsyncWorker> create(
+      ReceivePort receivePort) async {
+    return ForwardsToIsolateAsyncWorker(
+        await IsolateDriverConnection.create(receivePort));
+  }
+
+  ForwardsToIsolateAsyncWorker(this._isolateDriverConnection);
+
+  @override
+  Future<WorkResponse> performRequest(WorkRequest request) {
+    _isolateDriverConnection.writeRequest(request);
+    return _isolateDriverConnection.readResponse();
+  }
+}
diff --git a/bazel_worker/e2e_test/lib/sync_worker.dart b/bazel_worker/e2e_test/lib/sync_worker.dart
new file mode 100644
index 0000000..789f780
--- /dev/null
+++ b/bazel_worker/e2e_test/lib/sync_worker.dart
@@ -0,0 +1,16 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'package:bazel_worker/bazel_worker.dart';
+
+/// Example worker that just returns in its response all the arguments passed
+/// separated by newlines.
+class ExampleSyncWorker extends SyncWorkerLoop {
+  @override
+  WorkResponse performRequest(WorkRequest request) {
+    return WorkResponse()
+      ..exitCode = 0
+      ..output = request.arguments.join('\n');
+  }
+}
diff --git a/bazel_worker/e2e_test/pubspec.yaml b/bazel_worker/e2e_test/pubspec.yaml
new file mode 100644
index 0000000..d9c3577
--- /dev/null
+++ b/bazel_worker/e2e_test/pubspec.yaml
@@ -0,0 +1,10 @@
+name: e2e_test
+dependencies:
+  bazel_worker:
+    path: ../
+dev_dependencies:
+  cli_util: ^0.1.0
+  path: ^1.4.1
+  test: ^1.0.0
+environment:
+  sdk: '>=2.0.0 <3.0.0'
diff --git a/bazel_worker/e2e_test/test/e2e_test.dart b/bazel_worker/e2e_test/test/e2e_test.dart
new file mode 100644
index 0000000..faae0d3
--- /dev/null
+++ b/bazel_worker/e2e_test/test/e2e_test.dart
@@ -0,0 +1,63 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:io';
+
+import 'package:bazel_worker/driver.dart';
+import 'package:cli_util/cli_util.dart';
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+
+void main() {
+  var sdkPath = getSdkPath();
+  var dart = p.join(sdkPath, 'bin', 'dart');
+  runE2eTestForWorker('sync worker',
+      () => Process.start(dart, [p.join('bin', 'sync_worker.dart')]));
+  runE2eTestForWorker('async worker',
+      () => Process.start(dart, [p.join('bin', 'async_worker.dart')]));
+  runE2eTestForWorker(
+      'async worker in isolate',
+      () =>
+          Process.start(dart, [p.join('bin', 'async_worker_in_isolate.dart')]));
+}
+
+void runE2eTestForWorker(String groupName, SpawnWorker spawnWorker) {
+  BazelWorkerDriver driver;
+  group(groupName, () {
+    setUp(() {
+      driver = BazelWorkerDriver(spawnWorker);
+    });
+
+    tearDown(() async {
+      await driver.terminateWorkers();
+    });
+
+    test('single work request', () async {
+      await _doRequests(driver, count: 1);
+    });
+
+    test('lots of requests', () async {
+      await _doRequests(driver, count: 1000);
+    });
+  });
+}
+
+/// Runs [count] work requests through [driver], and asserts that they all
+/// completed with the correct response.
+Future _doRequests(BazelWorkerDriver driver, {int count}) async {
+  count ??= 100;
+  var requests = List.generate(count, (requestNum) {
+    var request = WorkRequest();
+    request.arguments.addAll(List.generate(requestNum, (argNum) => '$argNum'));
+    return request;
+  });
+  var responses = await Future.wait(requests.map(driver.doWork));
+  for (var i = 0; i < responses.length; i++) {
+    var request = requests[i];
+    var response = responses[i];
+    expect(response.exitCode, EXIT_CODE_OK);
+    expect(response.output, request.arguments.join('\n'));
+  }
+}
diff --git a/bazel_worker/lib/bazel_worker.dart b/bazel_worker/lib/bazel_worker.dart
new file mode 100644
index 0000000..0b00408
--- /dev/null
+++ b/bazel_worker/lib/bazel_worker.dart
@@ -0,0 +1,11 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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.
+
+export 'src/worker/async_worker_loop.dart';
+export 'src/constants.dart';
+export 'src/message_grouper.dart';
+export 'src/worker/sync_worker_loop.dart';
+export 'src/worker/worker_connection.dart';
+export 'src/worker/worker_loop.dart';
+export 'src/worker_protocol.pb.dart';
diff --git a/bazel_worker/lib/driver.dart b/bazel_worker/lib/driver.dart
new file mode 100644
index 0000000..7453491
--- /dev/null
+++ b/bazel_worker/lib/driver.dart
@@ -0,0 +1,9 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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.
+
+export 'src/driver/driver.dart';
+export 'src/driver/driver_connection.dart';
+export 'src/constants.dart';
+export 'src/message_grouper.dart';
+export 'src/worker_protocol.pb.dart';
diff --git a/bazel_worker/lib/src/async_message_grouper.dart b/bazel_worker/lib/src/async_message_grouper.dart
new file mode 100644
index 0000000..310a7f9
--- /dev/null
+++ b/bazel_worker/lib/src/async_message_grouper.dart
@@ -0,0 +1,66 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:collection';
+
+import 'package:async/async.dart';
+import 'package:pedantic/pedantic.dart';
+
+import 'message_grouper.dart';
+import 'message_grouper_state.dart';
+
+/// Collects stream data into messages by interpreting it as
+/// base-128 encoded lengths interleaved with raw data.
+class AsyncMessageGrouper implements MessageGrouper {
+  /// Current state for reading in messages;
+  final _state = MessageGrouperState();
+
+  /// The input stream.
+  final StreamQueue<List<int>> _inputQueue;
+
+  /// The current buffer.
+  final Queue<int> _buffer = Queue<int>();
+
+  /// Completes after [cancel] is called or [inputStream] is closed.
+  Future<void> get done => _done.future;
+  final _done = Completer<void>();
+
+  AsyncMessageGrouper(Stream<List<int>> inputStream)
+      : _inputQueue = StreamQueue(inputStream);
+
+  /// Returns the next full message that is received, or null if none are left.
+  @override
+  Future<List<int>> get next async {
+    try {
+      List<int> message;
+      while (message == null &&
+          (_buffer.isNotEmpty || await _inputQueue.hasNext)) {
+        if (_buffer.isEmpty) _buffer.addAll(await _inputQueue.next);
+        var nextByte = _buffer.removeFirst();
+        if (nextByte == -1) return null;
+        message = _state.handleInput(nextByte);
+      }
+
+      // If there is nothing left in the queue then cancel the subscription.
+      if (message == null) unawaited(cancel());
+
+      return message;
+    } catch (e) {
+      // It appears we sometimes get an exception instead of -1 as expected when
+      // stdin closes, this handles that in the same way (returning a null
+      // message)
+      return null;
+    }
+  }
+
+  /// Stop listening to the stream for further updates.
+  Future cancel() {
+    if (!_done.isCompleted) {
+      _done.complete(null);
+      return _inputQueue.cancel();
+    }
+    return done;
+  }
+}
diff --git a/bazel_worker/lib/src/constants.dart b/bazel_worker/lib/src/constants.dart
new file mode 100644
index 0000000..30113d3
--- /dev/null
+++ b/bazel_worker/lib/src/constants.dart
@@ -0,0 +1,6 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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.
+
+const int EXIT_CODE_OK = 0;
+const int EXIT_CODE_ERROR = 15;
diff --git a/bazel_worker/lib/src/driver/driver.dart b/bazel_worker/lib/src/driver/driver.dart
new file mode 100644
index 0000000..16cbc96
--- /dev/null
+++ b/bazel_worker/lib/src/driver/driver.dart
@@ -0,0 +1,234 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:collection';
+import 'dart:io';
+
+import '../constants.dart';
+import '../worker_protocol.pb.dart';
+import 'driver_connection.dart';
+
+typedef SpawnWorker = Future<Process> Function();
+
+/// A driver for talking to a bazel worker.
+///
+/// This allows you to use any binary that supports the bazel worker protocol in
+/// the same way that bazel would, but from another dart process instead.
+class BazelWorkerDriver {
+  /// Idle worker processes.
+  final _idleWorkers = <Process>[];
+
+  /// The maximum number of idle workers at any given time.
+  final int _maxIdleWorkers;
+
+  /// The maximum number of times to retry a [WorkAttempt] if there is an error.
+  final int _maxRetries;
+
+  /// The maximum number of concurrent workers to run at any given time.
+  final int _maxWorkers;
+
+  /// The number of currently active workers.
+  int get _numWorkers => _readyWorkers.length + _spawningWorkers.length;
+
+  /// All workers that are fully spawned and ready to handle work.
+  final _readyWorkers = <Process>[];
+
+  /// All workers that are in the process of being spawned.
+  final _spawningWorkers = <Future<Process>>[];
+
+  /// Work requests that haven't been started yet.
+  final _workQueue = Queue<_WorkAttempt>();
+
+  /// Factory method that spawns a worker process.
+  final SpawnWorker _spawnWorker;
+
+  BazelWorkerDriver(this._spawnWorker,
+      {int maxIdleWorkers, int maxWorkers, int maxRetries})
+      : _maxIdleWorkers = maxIdleWorkers ?? 4,
+        _maxWorkers = maxWorkers ?? 4,
+        _maxRetries = maxRetries ?? 4;
+
+  /// Waits for an available worker, and then sends [WorkRequest] to it.
+  ///
+  /// If [trackWork] is provided it will be invoked with a [Future] once the
+  /// [request] has been actually sent to the worker. This allows the caller
+  /// to determine when actual work is being done versus just waiting for an
+  /// available worker.
+  Future<WorkResponse> doWork(WorkRequest request,
+      {Function(Future<WorkResponse>) trackWork}) {
+    var attempt = _WorkAttempt(request, trackWork: trackWork);
+    _workQueue.add(attempt);
+    _runWorkQueue();
+    return attempt.response;
+  }
+
+  /// Calls `kill` on all worker processes.
+  Future terminateWorkers() async {
+    for (var worker in _readyWorkers.toList()) {
+      _killWorker(worker);
+    }
+    await Future.wait(_spawningWorkers.map((worker) async {
+      _killWorker(await worker);
+    }));
+  }
+
+  /// Runs as many items in [_workQueue] as possible given the number of
+  /// available workers.
+  ///
+  /// Will spawn additional workers until [_maxWorkers] has been reached.
+  ///
+  /// This method synchronously drains the [_workQueue] and [_idleWorkers], but
+  /// some tasks may not actually start right away if they need to wait for a
+  /// worker to spin up.
+  void _runWorkQueue() {
+    // Bail out conditions, we will continue to call ourselves indefinitely
+    // until one of these is met.
+    if (_workQueue.isEmpty) return;
+    if (_numWorkers == _maxWorkers && _idleWorkers.isEmpty) return;
+    if (_numWorkers > _maxWorkers) {
+      throw StateError('Internal error, created to many workers. Please '
+          'file a bug at https://github.com/dart-lang/bazel_worker/issues/new');
+    }
+
+    // At this point we definitely want to run a task, we just need to decide
+    // whether or not we need to start up a new worker.
+    var attempt = _workQueue.removeFirst();
+    if (_idleWorkers.isNotEmpty) {
+      _runWorker(_idleWorkers.removeLast(), attempt);
+    } else {
+      // No need to block here, we want to continue to synchronously drain the
+      // work queue.
+      var futureWorker = _spawnWorker();
+      _spawningWorkers.add(futureWorker);
+      futureWorker.then((worker) {
+        _spawningWorkers.remove(futureWorker);
+        _readyWorkers.add(worker);
+
+        var connection = StdDriverConnection.forWorker(worker);
+        _workerConnections[worker] = connection;
+        _runWorker(worker, attempt);
+
+        // When the worker exits we should retry running the work queue in case
+        // there is more work to be done. This is primarily just a defensive
+        // thing but is cheap to do.
+        //
+        // We don't use `exitCode` because it is null for detached processes (
+        // which is common for workers).
+        connection.done.then((_) {
+          _idleWorkers.remove(worker);
+          _readyWorkers.remove(worker);
+          _runWorkQueue();
+        });
+      });
+    }
+    // Recursively calls itself until one of the bail out conditions are met.
+    _runWorkQueue();
+  }
+
+  /// Sends [request] to [worker].
+  ///
+  /// Once the worker responds then it will be added back to the pool of idle
+  /// workers.
+  void _runWorker(Process worker, _WorkAttempt attempt) {
+    var rescheduled = false;
+
+    runZoned(() async {
+      var connection = _workerConnections[worker];
+
+      connection.writeRequest(attempt.request);
+      var responseFuture = connection.readResponse();
+      if (attempt.trackWork != null) {
+        attempt.trackWork(responseFuture);
+      }
+      var response = await responseFuture;
+
+      // It is possible for us to complete with an error response due to an
+      // unhandled async error before we get here.
+      if (!attempt.responseCompleter.isCompleted) {
+        if (response == null) {
+          rescheduled = _tryReschedule(attempt);
+          if (rescheduled) return;
+          stderr.writeln('Failed to run request ${attempt.request}');
+          response = WorkResponse()
+            ..exitCode = EXIT_CODE_ERROR
+            ..output =
+                'Invalid response from worker, this probably means it wrote '
+                    'invalid output or died.';
+        }
+        attempt.responseCompleter.complete(response);
+        _cleanUp(worker);
+      }
+    }, onError: (e, s) {
+      // Note that we don't need to do additional cleanup here on failures. If
+      // the worker dies that is already handled in a generic fashion, we just
+      // need to make sure we complete with a valid response.
+      if (!attempt.responseCompleter.isCompleted) {
+        rescheduled = _tryReschedule(attempt);
+        if (rescheduled) return;
+        var response = WorkResponse()
+          ..exitCode = EXIT_CODE_ERROR
+          ..output = 'Error running worker:\n$e\n$s';
+        attempt.responseCompleter.complete(response);
+        _cleanUp(worker);
+      }
+    });
+  }
+
+  /// Performs post-work cleanup for [worker].
+  void _cleanUp(Process worker) {
+    // If the worker crashes, it won't be in `_readyWorkers` any more, and
+    // we don't want to add it to _idleWorkers.
+    if (_readyWorkers.contains(worker)) {
+      _idleWorkers.add(worker);
+    }
+
+    // Do additional work if available.
+    _runWorkQueue();
+
+    // If the worker wasn't immediately used we might have to many idle
+    // workers now, kill one if necessary.
+    if (_idleWorkers.length > _maxIdleWorkers) {
+      // Note that whenever we spawn a worker we listen for its exit code
+      // and clean it up so we don't need to do that here.
+      var worker = _idleWorkers.removeLast();
+      _killWorker(worker);
+    }
+  }
+
+  /// Attempts to reschedule a failed [attempt].
+  ///
+  /// Returns whether or not the job was successfully rescheduled.
+  bool _tryReschedule(_WorkAttempt attempt) {
+    if (attempt.timesRetried >= _maxRetries) return false;
+    stderr.writeln('Rescheduling failed request...');
+    attempt.timesRetried++;
+    _workQueue.add(attempt);
+    _runWorkQueue();
+    return true;
+  }
+
+  void _killWorker(Process worker) {
+    _workerConnections[worker].cancel();
+    _readyWorkers.remove(worker);
+    _idleWorkers.remove(worker);
+    worker.kill();
+  }
+}
+
+/// Encapsulates an attempt to fulfill a [WorkRequest], a completer for the
+/// [WorkResponse], and the number of times it has been retried.
+class _WorkAttempt {
+  final WorkRequest request;
+  final responseCompleter = Completer<WorkResponse>();
+  final Function(Future<WorkResponse>) trackWork;
+
+  Future<WorkResponse> get response => responseCompleter.future;
+
+  int timesRetried = 0;
+
+  _WorkAttempt(this.request, {this.trackWork});
+}
+
+final _workerConnections = Expando<DriverConnection>('connection');
diff --git a/bazel_worker/lib/src/driver/driver_connection.dart b/bazel_worker/lib/src/driver/driver_connection.dart
new file mode 100644
index 0000000..b282aec
--- /dev/null
+++ b/bazel_worker/lib/src/driver/driver_connection.dart
@@ -0,0 +1,117 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+import 'dart:isolate';
+
+import '../async_message_grouper.dart';
+import '../worker_protocol.pb.dart';
+import '../constants.dart';
+import '../utils.dart';
+
+/// A connection from a `BazelWorkerDriver` to a worker.
+///
+/// Unlike `WorkerConnection` there is no synchronous version of this class.
+/// This is because drivers talk to multiple workers, so they should never block
+/// when waiting for the response of any individual worker.
+abstract class DriverConnection {
+  Future<WorkResponse> readResponse();
+
+  void writeRequest(WorkRequest request);
+
+  Future cancel();
+}
+
+/// Default implementation of [DriverConnection] that works with [Stdin]
+/// and [Stdout].
+class StdDriverConnection implements DriverConnection {
+  final AsyncMessageGrouper _messageGrouper;
+  final StreamSink<List<int>> _outputStream;
+
+  Future<void> get done => _messageGrouper.done;
+
+  StdDriverConnection(
+      {Stream<List<int>> inputStream, StreamSink<List<int>> outputStream})
+      : _messageGrouper = AsyncMessageGrouper(inputStream ?? stdin),
+        _outputStream = outputStream ?? stdout;
+
+  factory StdDriverConnection.forWorker(Process worker) => StdDriverConnection(
+      inputStream: worker.stdout, outputStream: worker.stdin);
+
+  /// Note: This will attempts to recover from invalid proto messages by parsing
+  /// them as strings. This is a common error case for workers (they print a
+  /// message to stdout on accident). This isn't perfect however as it only
+  /// happens if the parsing throws, you can still hang indefinitely if the
+  /// [MessageGrouper] doesn't find what it thinks is the end of a proto
+  /// message.
+  @override
+  Future<WorkResponse> readResponse() async {
+    var buffer = await _messageGrouper.next;
+    if (buffer == null) return null;
+
+    WorkResponse response;
+    try {
+      response = WorkResponse.fromBuffer(buffer);
+    } catch (_) {
+      try {
+        // Try parsing the message as a string and set that as the output.
+        var output = utf8.decode(buffer);
+        var response = WorkResponse()
+          ..exitCode = EXIT_CODE_ERROR
+          ..output = 'Worker sent an invalid response:\n$output';
+        return response;
+      } catch (_) {
+        // Fall back to original exception and rethrow if we fail to parse as
+        // a string.
+      }
+      rethrow;
+    }
+    return response;
+  }
+
+  @override
+  void writeRequest(WorkRequest request) {
+    _outputStream.add(protoToDelimitedBuffer(request));
+  }
+
+  @override
+  Future cancel() async {
+    await _outputStream.close();
+    await _messageGrouper.cancel();
+  }
+}
+
+/// [DriverConnection] that works with an isolate via a [SendPort].
+class IsolateDriverConnection implements DriverConnection {
+  final StreamIterator _receivePortIterator;
+  final SendPort _sendPort;
+
+  IsolateDriverConnection._(this._receivePortIterator, this._sendPort);
+
+  /// Creates a driver connection for a worker in an isolate. Provide the
+  /// [receivePort] attached to the [SendPort] that the isolate was created
+  /// with.
+  static Future<IsolateDriverConnection> create(ReceivePort receivePort) async {
+    var receivePortIterator = StreamIterator(receivePort);
+    await receivePortIterator.moveNext();
+    var sendPort = receivePortIterator.current as SendPort;
+    return IsolateDriverConnection._(receivePortIterator, sendPort);
+  }
+
+  @override
+  Future<WorkResponse> readResponse() async {
+    await _receivePortIterator.moveNext();
+    return WorkResponse.fromBuffer(_receivePortIterator.current as List<int>);
+  }
+
+  @override
+  void writeRequest(WorkRequest request) {
+    _sendPort.send(request.writeToBuffer());
+  }
+
+  @override
+  Future cancel() async {}
+}
diff --git a/bazel_worker/lib/src/message_grouper.dart b/bazel_worker/lib/src/message_grouper.dart
new file mode 100644
index 0000000..635983e
--- /dev/null
+++ b/bazel_worker/lib/src/message_grouper.dart
@@ -0,0 +1,13 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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.
+
+/// Interface for a [MessageGrouper], which groups bytes in  delimited proto
+/// format into the bytes for each message.
+///
+/// This interface should not generally be implemented directly, instead use
+/// the [SyncMessageGrouper] or [AsyncMessageGrouper] implementations.
+abstract class MessageGrouper {
+  /// Returns either a [List<int>] or a [Future<List<int>>].
+  dynamic get next;
+}
diff --git a/bazel_worker/lib/src/message_grouper_state.dart b/bazel_worker/lib/src/message_grouper_state.dart
new file mode 100644
index 0000000..5cbf933
--- /dev/null
+++ b/bazel_worker/lib/src/message_grouper_state.dart
@@ -0,0 +1,126 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 'dart:typed_data';
+
+import 'package:protobuf/protobuf.dart';
+
+/// State held by the [MessageGrouper] while waiting for additional data to
+/// arrive.
+class MessageGrouperState {
+  /// Reads the initial length.
+  _LengthReader _lengthReader;
+
+  /// Reads messages from a stream of bytes.
+  _MessageReader _messageReader;
+
+  MessageGrouperState() {
+    reset();
+  }
+
+  /// Handle one byte at a time.
+  ///
+  /// Returns a [List<int>] of message bytes if [byte] was the last byte in a
+  /// message, otherwise returns [null].
+  List<int> handleInput(int byte) {
+    if (!_lengthReader.done) {
+      _lengthReader.readByte(byte);
+      if (_lengthReader.done) {
+        _messageReader = _MessageReader(_lengthReader.length);
+      }
+    } else {
+      assert(_messageReader != null);
+      _messageReader.readByte(byte);
+    }
+
+    if (_lengthReader.done && _messageReader.done) {
+      var message = _messageReader.message;
+      reset();
+      return message;
+    }
+
+    return null;
+  }
+
+  /// Reset the state so that we are ready to receive the next message.
+  void reset() {
+    _lengthReader = _LengthReader();
+    _messageReader = null;
+  }
+}
+
+/// Reads a length one byte at a time.
+///
+/// The base-128 encoding is in little-endian order, with the high bit set on
+/// all bytes but the last.  This was chosen since it's the same as the
+/// base-128 encoding used by protobufs, so it allows a modest amount of code
+/// reuse at the other end of the protocol.
+class _LengthReader {
+  /// Whether or not we are done reading the length.
+  bool get done => _done;
+  bool _done = false;
+
+  /// If [_done] is `true`, the decoded value of the length bytes received so
+  /// far (if any).  If [_done] is `false`, the decoded length that was most
+  /// recently received.
+  int _length;
+
+  /// The length read in.  You are only allowed to read this if [_done] is
+  /// `true`.
+  int get length {
+    assert(_done);
+    return _length;
+  }
+
+  final List<int> _buffer = <int>[];
+
+  /// Read a single byte into [_length].
+  void readByte(int byte) {
+    assert(!_done);
+    _buffer.add(byte);
+
+    // Check for the last byte in the length, and then read it.
+    if ((byte & 0x80) == 0) {
+      _done = true;
+      var reader = CodedBufferReader(_buffer);
+      _length = reader.readInt32();
+    }
+  }
+}
+
+/// Reads some number of bytes from a stream, one byte at a time.
+class _MessageReader {
+  /// Whether or not we are done reading bytes from the stream.
+  bool get done => _done;
+  bool _done;
+
+  /// The total length of the message to be read.
+  final int _length;
+
+  /// A [Uint8List] which holds the message data. You are only allowed to read
+  /// this if [_done] is `true`.
+  Uint8List get message {
+    assert(_done);
+    return _message;
+  }
+
+  final Uint8List _message;
+
+  /// If [_done] is `false`, the number of message bytes that have been received
+  /// so far.  Otherwise zero.
+  int _numMessageBytesReceived = 0;
+
+  _MessageReader(int length)
+      : _message = Uint8List(length),
+        _length = length,
+        _done = length == 0;
+
+  /// Reads [byte] into [_message].
+  void readByte(int byte) {
+    assert(!done);
+
+    _message[_numMessageBytesReceived++] = byte;
+    if (_numMessageBytesReceived == _length) _done = true;
+  }
+}
diff --git a/bazel_worker/lib/src/sync_message_grouper.dart b/bazel_worker/lib/src/sync_message_grouper.dart
new file mode 100644
index 0000000..c0d11f0
--- /dev/null
+++ b/bazel_worker/lib/src/sync_message_grouper.dart
@@ -0,0 +1,37 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 'dart:io';
+
+import 'message_grouper.dart';
+import 'message_grouper_state.dart';
+
+/// Groups bytes in delimited proto format into the bytes for each message.
+class SyncMessageGrouper implements MessageGrouper {
+  final _state = MessageGrouperState();
+  final Stdin _stdin;
+
+  SyncMessageGrouper(this._stdin);
+
+  /// Blocks until the next full message is received, and then returns it.
+  ///
+  /// Returns null at end of file.
+  @override
+  List<int> get next {
+    try {
+      List<int> message;
+      while (message == null) {
+        var nextByte = _stdin.readByteSync();
+        if (nextByte == -1) return null;
+        message = _state.handleInput(nextByte);
+      }
+      return message;
+    } catch (e) {
+      // It appears we sometimes get an exception instead of -1 as expected when
+      // stdin closes, this handles that in the same way (returning a null
+      // message)
+      return null;
+    }
+  }
+}
diff --git a/bazel_worker/lib/src/utils.dart b/bazel_worker/lib/src/utils.dart
new file mode 100644
index 0000000..609b435
--- /dev/null
+++ b/bazel_worker/lib/src/utils.dart
@@ -0,0 +1,23 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 'dart:typed_data';
+
+import 'package:protobuf/protobuf.dart';
+
+List<int> protoToDelimitedBuffer(GeneratedMessage message) {
+  var messageBuffer = CodedBufferWriter();
+  message.writeToCodedBufferWriter(messageBuffer);
+
+  var delimiterBuffer = CodedBufferWriter();
+  delimiterBuffer.writeInt32NoTag(messageBuffer.lengthInBytes);
+
+  var result =
+      Uint8List(messageBuffer.lengthInBytes + delimiterBuffer.lengthInBytes);
+
+  delimiterBuffer.writeTo(result);
+  messageBuffer.writeTo(result, delimiterBuffer.lengthInBytes);
+
+  return result;
+}
diff --git a/bazel_worker/lib/src/worker/async_worker_loop.dart b/bazel_worker/lib/src/worker/async_worker_loop.dart
new file mode 100644
index 0000000..9405c4f
--- /dev/null
+++ b/bazel_worker/lib/src/worker/async_worker_loop.dart
@@ -0,0 +1,55 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import '../constants.dart';
+import '../worker_protocol.pb.dart';
+import 'worker_connection.dart';
+import 'worker_loop.dart';
+
+/// Persistent Bazel worker loop.
+///
+/// Extend this class and implement the `performRequest` method.
+abstract class AsyncWorkerLoop implements WorkerLoop {
+  final AsyncWorkerConnection connection;
+
+  AsyncWorkerLoop({AsyncWorkerConnection connection})
+      : connection = connection ?? StdAsyncWorkerConnection();
+
+  /// Perform a single [WorkRequest], and return a [WorkResponse].
+  @override
+  Future<WorkResponse> performRequest(WorkRequest request);
+
+  /// Run the worker loop. The returned [Future] doesn't complete until
+  /// [connection#readRequest] returns `null`.
+  @override
+  Future run() async {
+    while (true) {
+      WorkResponse response;
+      try {
+        var request = await connection.readRequest();
+        if (request == null) break;
+        var printMessages = StringBuffer();
+        response = await runZoned(() => performRequest(request),
+            zoneSpecification:
+                ZoneSpecification(print: (self, parent, zone, message) {
+          printMessages.writeln();
+          printMessages.write(message);
+        }));
+        if (printMessages.isNotEmpty) {
+          response.output = '${response.output}$printMessages';
+        }
+        // In case they forget to set this.
+        response.exitCode ??= EXIT_CODE_OK;
+      } catch (e, s) {
+        response = WorkResponse()
+          ..exitCode = EXIT_CODE_ERROR
+          ..output = '$e\n$s';
+      }
+
+      connection.writeResponse(response);
+    }
+  }
+}
diff --git a/bazel_worker/lib/src/worker/sync_worker_loop.dart b/bazel_worker/lib/src/worker/sync_worker_loop.dart
new file mode 100644
index 0000000..2d287b0
--- /dev/null
+++ b/bazel_worker/lib/src/worker/sync_worker_loop.dart
@@ -0,0 +1,52 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import '../constants.dart';
+import '../worker_protocol.pb.dart';
+import 'worker_connection.dart';
+import 'worker_loop.dart';
+
+/// Persistent Bazel worker loop.
+///
+/// Extend this class and implement the `performRequest` method.
+abstract class SyncWorkerLoop implements WorkerLoop {
+  final SyncWorkerConnection connection;
+
+  SyncWorkerLoop({SyncWorkerConnection connection})
+      : connection = connection ?? StdSyncWorkerConnection();
+
+  /// Perform a single [WorkRequest], and return a [WorkResponse].
+  @override
+  WorkResponse performRequest(WorkRequest request);
+
+  /// Run the worker loop. Blocks until [connection#readRequest] returns `null`.
+  @override
+  void run() {
+    while (true) {
+      WorkResponse response;
+      try {
+        var request = connection.readRequest();
+        if (request == null) break;
+        var printMessages = StringBuffer();
+        response = runZoned(() => performRequest(request), zoneSpecification:
+            ZoneSpecification(print: (self, parent, zone, message) {
+          printMessages.writeln();
+          printMessages.write(message);
+        }));
+        if (printMessages.isNotEmpty) {
+          response.output = '${response.output}$printMessages';
+        }
+        // In case they forget to set this.
+        response.exitCode ??= EXIT_CODE_OK;
+      } catch (e, s) {
+        response = WorkResponse()
+          ..exitCode = EXIT_CODE_ERROR
+          ..output = '$e\n$s';
+      }
+
+      connection.writeResponse(response);
+    }
+  }
+}
diff --git a/bazel_worker/lib/src/worker/worker_connection.dart b/bazel_worker/lib/src/worker/worker_connection.dart
new file mode 100644
index 0000000..a429cfe
--- /dev/null
+++ b/bazel_worker/lib/src/worker/worker_connection.dart
@@ -0,0 +1,124 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:io';
+import 'dart:isolate';
+import 'dart:typed_data';
+
+import '../async_message_grouper.dart';
+import '../sync_message_grouper.dart';
+import '../utils.dart';
+import '../worker_protocol.pb.dart';
+
+/// A connection from a worker to a driver (driver could be bazel, a dart
+/// program using `BazelWorkerDriver`, or any other process that speaks the
+/// protocol).
+abstract class WorkerConnection {
+  /// Reads a [WorkRequest] or returns [null] if there are none left.
+  ///
+  /// See [AsyncWorkerConnection] and [SyncWorkerConnection] for more narrow
+  /// interfaces.
+  FutureOr<WorkRequest> readRequest();
+
+  void writeResponse(WorkResponse response);
+}
+
+abstract class AsyncWorkerConnection implements WorkerConnection {
+  /// Creates a [StdAsyncWorkerConnection] with the specified [inputStream]
+  /// and [outputStream], unless [sendPort] is specified, in which case
+  /// creates a [SendPortAsyncWorkerConnection].
+  factory AsyncWorkerConnection(
+          {Stream<List<int>> inputStream,
+          StreamSink<List<int>> outputStream,
+          SendPort sendPort}) =>
+      sendPort == null
+          ? StdAsyncWorkerConnection(
+              inputStream: inputStream, outputStream: outputStream)
+          : SendPortAsyncWorkerConnection(sendPort);
+
+  @override
+  Future<WorkRequest> readRequest();
+}
+
+abstract class SyncWorkerConnection implements WorkerConnection {
+  @override
+  WorkRequest readRequest();
+}
+
+/// Default implementation of [AsyncWorkerConnection] that works with [Stdin]
+/// and [Stdout].
+class StdAsyncWorkerConnection implements AsyncWorkerConnection {
+  final AsyncMessageGrouper _messageGrouper;
+  final StreamSink<List<int>> _outputStream;
+
+  StdAsyncWorkerConnection(
+      {Stream<List<int>> inputStream, StreamSink<List<int>> outputStream})
+      : _messageGrouper = AsyncMessageGrouper(inputStream ?? stdin),
+        _outputStream = outputStream ?? stdout;
+
+  @override
+  Future<WorkRequest> readRequest() async {
+    var buffer = await _messageGrouper.next;
+    if (buffer == null) return null;
+
+    return WorkRequest.fromBuffer(buffer);
+  }
+
+  @override
+  void writeResponse(WorkResponse response) {
+    _outputStream.add(protoToDelimitedBuffer(response));
+  }
+}
+
+/// Implementation of [AsyncWorkerConnection] for running in an isolate.
+class SendPortAsyncWorkerConnection implements AsyncWorkerConnection {
+  final ReceivePort receivePort;
+  final StreamIterator<Uint8List> receivePortIterator;
+  final SendPort sendPort;
+
+  factory SendPortAsyncWorkerConnection(SendPort sendPort) {
+    var receivePort = ReceivePort();
+    sendPort.send(receivePort.sendPort);
+    return SendPortAsyncWorkerConnection._(receivePort, sendPort);
+  }
+
+  SendPortAsyncWorkerConnection._(this.receivePort, this.sendPort)
+      : receivePortIterator = StreamIterator(receivePort.cast());
+
+  @override
+  Future<WorkRequest> readRequest() async {
+    if (!await receivePortIterator.moveNext()) return null;
+    return WorkRequest.fromBuffer(receivePortIterator.current);
+  }
+
+  @override
+  void writeResponse(WorkResponse response) {
+    sendPort.send(response.writeToBuffer());
+  }
+}
+
+/// Default implementation of [SyncWorkerConnection] that works with [Stdin] and
+/// [Stdout].
+class StdSyncWorkerConnection implements SyncWorkerConnection {
+  final SyncMessageGrouper _messageGrouper;
+  final Stdout _stdoutStream;
+
+  StdSyncWorkerConnection({Stdin stdinStream, Stdout stdoutStream})
+      : _messageGrouper = SyncMessageGrouper(stdinStream ?? stdin),
+        _stdoutStream = stdoutStream ?? stdout;
+
+  @override
+  WorkRequest readRequest() {
+    var buffer = _messageGrouper.next;
+    if (buffer == null) return null;
+
+    return WorkRequest.fromBuffer(buffer);
+  }
+
+  @override
+  void writeResponse(WorkResponse response) {
+    _stdoutStream.add(protoToDelimitedBuffer(response));
+  }
+}
diff --git a/bazel_worker/lib/src/worker/worker_loop.dart b/bazel_worker/lib/src/worker/worker_loop.dart
new file mode 100644
index 0000000..2e4a023
--- /dev/null
+++ b/bazel_worker/lib/src/worker/worker_loop.dart
@@ -0,0 +1,18 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 '../worker_protocol.pb.dart';
+
+/// Interface for a [WorkerLoop].
+///
+/// This interface should not generally be implemented directly, instead use
+/// the [SyncWorkerLoop] or [AsyncWorkerLoop] implementations.
+abstract class WorkerLoop {
+  /// Perform a single [WorkRequest], and return either a [WorkResponse] or
+  /// a [Future<WorkResponse>].
+  dynamic performRequest(WorkRequest request);
+
+  /// Run the worker loop. Should return either a [Future] or [null].
+  dynamic run();
+}
diff --git a/bazel_worker/lib/src/worker_protocol.pb.dart b/bazel_worker/lib/src/worker_protocol.pb.dart
new file mode 100644
index 0000000..90e4381
--- /dev/null
+++ b/bazel_worker/lib/src/worker_protocol.pb.dart
@@ -0,0 +1,182 @@
+///
+//  Generated code. Do not modify.
+//  source: worker_protocol.proto
+//
+// @dart = 2.3
+// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type
+// ignore_for_file: annotate_overrides
+
+import 'dart:core' as $core;
+
+import 'package:protobuf/protobuf.dart' as $pb;
+
+class Input extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo('Input',
+      package: const $pb.PackageName('blaze.worker'),
+      createEmptyInstance: create)
+    ..aOS(1, 'path')
+    ..a<$core.List<$core.int>>(2, 'digest', $pb.PbFieldType.OY)
+    ..hasRequiredFields = false;
+
+  Input._() : super();
+  factory Input() => create();
+  factory Input.fromBuffer($core.List<$core.int> i,
+          [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+      create()..mergeFromBuffer(i, r);
+  factory Input.fromJson($core.String i,
+          [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+      create()..mergeFromJson(i, r);
+  Input clone() => Input()..mergeFromMessage(this);
+  Input copyWith(void Function(Input) updates) =>
+      super.copyWith((message) => updates(message as Input));
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static Input create() => Input._();
+  Input createEmptyInstance() => create();
+  static $pb.PbList<Input> createRepeated() => $pb.PbList<Input>();
+  @$core.pragma('dart2js:noInline')
+  static Input getDefault() =>
+      _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Input>(create);
+  static Input _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.String get path => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set path($core.String v) {
+    $_setString(0, v);
+  }
+
+  @$pb.TagNumber(1)
+  $core.bool hasPath() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearPath() => clearField(1);
+
+  @$pb.TagNumber(2)
+  $core.List<$core.int> get digest => $_getN(1);
+  @$pb.TagNumber(2)
+  set digest($core.List<$core.int> v) {
+    $_setBytes(1, v);
+  }
+
+  @$pb.TagNumber(2)
+  $core.bool hasDigest() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearDigest() => clearField(2);
+}
+
+class WorkRequest extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo('WorkRequest',
+      package: const $pb.PackageName('blaze.worker'),
+      createEmptyInstance: create)
+    ..pPS(1, 'arguments')
+    ..pc<Input>(2, 'inputs', $pb.PbFieldType.PM, subBuilder: Input.create)
+    ..a<$core.int>(3, 'requestId', $pb.PbFieldType.O3)
+    ..hasRequiredFields = false;
+
+  WorkRequest._() : super();
+  factory WorkRequest() => create();
+  factory WorkRequest.fromBuffer($core.List<$core.int> i,
+          [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+      create()..mergeFromBuffer(i, r);
+  factory WorkRequest.fromJson($core.String i,
+          [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+      create()..mergeFromJson(i, r);
+  WorkRequest clone() => WorkRequest()..mergeFromMessage(this);
+  WorkRequest copyWith(void Function(WorkRequest) updates) =>
+      super.copyWith((message) => updates(message as WorkRequest));
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static WorkRequest create() => WorkRequest._();
+  WorkRequest createEmptyInstance() => create();
+  static $pb.PbList<WorkRequest> createRepeated() => $pb.PbList<WorkRequest>();
+  @$core.pragma('dart2js:noInline')
+  static WorkRequest getDefault() => _defaultInstance ??=
+      $pb.GeneratedMessage.$_defaultFor<WorkRequest>(create);
+  static WorkRequest _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.List<$core.String> get arguments => $_getList(0);
+
+  @$pb.TagNumber(2)
+  $core.List<Input> get inputs => $_getList(1);
+
+  @$pb.TagNumber(3)
+  $core.int get requestId => $_getIZ(2);
+  @$pb.TagNumber(3)
+  set requestId($core.int v) {
+    $_setSignedInt32(2, v);
+  }
+
+  @$pb.TagNumber(3)
+  $core.bool hasRequestId() => $_has(2);
+  @$pb.TagNumber(3)
+  void clearRequestId() => clearField(3);
+}
+
+class WorkResponse extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo('WorkResponse',
+      package: const $pb.PackageName('blaze.worker'),
+      createEmptyInstance: create)
+    ..a<$core.int>(1, 'exitCode', $pb.PbFieldType.O3)
+    ..aOS(2, 'output')
+    ..a<$core.int>(3, 'requestId', $pb.PbFieldType.O3)
+    ..hasRequiredFields = false;
+
+  WorkResponse._() : super();
+  factory WorkResponse() => create();
+  factory WorkResponse.fromBuffer($core.List<$core.int> i,
+          [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+      create()..mergeFromBuffer(i, r);
+  factory WorkResponse.fromJson($core.String i,
+          [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+      create()..mergeFromJson(i, r);
+  WorkResponse clone() => WorkResponse()..mergeFromMessage(this);
+  WorkResponse copyWith(void Function(WorkResponse) updates) =>
+      super.copyWith((message) => updates(message as WorkResponse));
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static WorkResponse create() => WorkResponse._();
+  WorkResponse createEmptyInstance() => create();
+  static $pb.PbList<WorkResponse> createRepeated() =>
+      $pb.PbList<WorkResponse>();
+  @$core.pragma('dart2js:noInline')
+  static WorkResponse getDefault() => _defaultInstance ??=
+      $pb.GeneratedMessage.$_defaultFor<WorkResponse>(create);
+  static WorkResponse _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.int get exitCode => $_getIZ(0);
+  @$pb.TagNumber(1)
+  set exitCode($core.int v) {
+    $_setSignedInt32(0, v);
+  }
+
+  @$pb.TagNumber(1)
+  $core.bool hasExitCode() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearExitCode() => clearField(1);
+
+  @$pb.TagNumber(2)
+  $core.String get output => $_getSZ(1);
+  @$pb.TagNumber(2)
+  set output($core.String v) {
+    $_setString(1, v);
+  }
+
+  @$pb.TagNumber(2)
+  $core.bool hasOutput() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearOutput() => clearField(2);
+
+  @$pb.TagNumber(3)
+  $core.int get requestId => $_getIZ(2);
+  @$pb.TagNumber(3)
+  set requestId($core.int v) {
+    $_setSignedInt32(2, v);
+  }
+
+  @$pb.TagNumber(3)
+  $core.bool hasRequestId() => $_has(2);
+  @$pb.TagNumber(3)
+  void clearRequestId() => clearField(3);
+}
diff --git a/bazel_worker/lib/testing.dart b/bazel_worker/lib/testing.dart
new file mode 100644
index 0000000..037aa1e
--- /dev/null
+++ b/bazel_worker/lib/testing.dart
@@ -0,0 +1,197 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:collection';
+import 'dart:io';
+import 'dart:typed_data';
+
+import 'package:bazel_worker/bazel_worker.dart';
+
+export 'src/async_message_grouper.dart';
+export 'src/sync_message_grouper.dart';
+export 'src/utils.dart' show protoToDelimitedBuffer;
+
+/// Interface for a mock [Stdin] object that allows you to add bytes manually.
+abstract class TestStdin implements Stdin {
+  void addInputBytes(List<int> bytes);
+
+  void close();
+}
+
+/// A [Stdin] mock object which only implements `readByteSync`.
+class TestStdinSync implements TestStdin {
+  /// Pending bytes to be delivered synchronously.
+  final Queue<int> pendingBytes = Queue<int>();
+
+  /// Adds all the [bytes] to this stream.
+  @override
+  void addInputBytes(List<int> bytes) {
+    pendingBytes.addAll(bytes);
+  }
+
+  /// Add a -1 to signal EOF.
+  @override
+  void close() {
+    pendingBytes.add(-1);
+  }
+
+  @override
+  int readByteSync() {
+    return pendingBytes.removeFirst();
+  }
+
+  @override
+  void noSuchMethod(Invocation invocation) {
+    throw StateError('Unexpected invocation ${invocation.memberName}.');
+  }
+}
+
+/// A mock [Stdin] object which only implements `listen`.
+///
+/// Note: You must call [close] in order for the loop to exit properly.
+class TestStdinAsync implements TestStdin {
+  /// Controls the stream for async delivery of bytes.
+  final StreamController<Uint8List> _controller = StreamController();
+  StreamController<Uint8List> get controller => _controller;
+
+  /// Adds all the [bytes] to this stream.
+  @override
+  void addInputBytes(List<int> bytes) {
+    _controller.add(Uint8List.fromList(bytes));
+  }
+
+  /// Closes this stream. This is necessary for the [AsyncWorkerLoop] to exit.
+  @override
+  void close() {
+    _controller.close();
+  }
+
+  @override
+  StreamSubscription<Uint8List> listen(void Function(Uint8List bytes) onData,
+      {Function onError, void Function() onDone, bool cancelOnError}) {
+    return _controller.stream.listen(onData,
+        onError: onError, onDone: onDone, cancelOnError: cancelOnError);
+  }
+
+  @override
+  void noSuchMethod(Invocation invocation) {
+    throw StateError('Unexpected invocation ${invocation.memberName}.');
+  }
+}
+
+/// A [Stdout] mock object.
+class TestStdoutStream implements Stdout {
+  final List<List<int>> writes = <List<int>>[];
+
+  @override
+  void add(List<int> bytes) {
+    writes.add(bytes);
+  }
+
+  @override
+  void noSuchMethod(Invocation invocation) {
+    throw StateError('Unexpected invocation ${invocation.memberName}.');
+  }
+}
+
+/// Interface for a [TestWorkerConnection] which records its responses
+abstract class TestWorkerConnection implements WorkerConnection {
+  List<WorkResponse> get responses;
+}
+
+/// Interface for a [TestWorkerLoop] which allows you to enqueue responses.
+abstract class TestWorkerLoop implements WorkerLoop {
+  void enqueueResponse(WorkResponse response);
+
+  /// If set, this message will be printed during the call to `performRequest`.
+  String get printMessage;
+}
+
+/// A [StdSyncWorkerConnection] which records its responses.
+class TestSyncWorkerConnection extends StdSyncWorkerConnection
+    implements TestWorkerConnection {
+  @override
+  final List<WorkResponse> responses = <WorkResponse>[];
+
+  TestSyncWorkerConnection(Stdin stdinStream, Stdout stdoutStream)
+      : super(stdinStream: stdinStream, stdoutStream: stdoutStream);
+
+  @override
+  void writeResponse(WorkResponse response) {
+    super.writeResponse(response);
+    responses.add(response);
+  }
+}
+
+/// A [SyncWorkerLoop] for testing.
+class TestSyncWorkerLoop extends SyncWorkerLoop implements TestWorkerLoop {
+  final List<WorkRequest> requests = <WorkRequest>[];
+  final Queue<WorkResponse> _responses = Queue<WorkResponse>();
+
+  @override
+  final String printMessage;
+
+  TestSyncWorkerLoop(SyncWorkerConnection connection, {this.printMessage})
+      : super(connection: connection);
+
+  @override
+  WorkResponse performRequest(WorkRequest request) {
+    requests.add(request);
+    if (printMessage != null) print(printMessage);
+    return _responses.removeFirst();
+  }
+
+  /// Adds [response] to the queue. These will be returned from
+  /// [performResponse] in the order they are added, otherwise it will throw
+  /// if the queue is empty.
+  @override
+  void enqueueResponse(WorkResponse response) {
+    _responses.addLast(response);
+  }
+}
+
+/// A [StdAsyncWorkerConnection] which records its responses.
+class TestAsyncWorkerConnection extends StdAsyncWorkerConnection
+    implements TestWorkerConnection {
+  @override
+  final List<WorkResponse> responses = <WorkResponse>[];
+
+  TestAsyncWorkerConnection(
+      Stream<List<int>> inputStream, StreamSink<List<int>> outputStream)
+      : super(inputStream: inputStream, outputStream: outputStream);
+
+  @override
+  void writeResponse(WorkResponse response) {
+    super.writeResponse(response);
+    responses.add(response);
+  }
+}
+
+/// A [AsyncWorkerLoop] for testing.
+class TestAsyncWorkerLoop extends AsyncWorkerLoop implements TestWorkerLoop {
+  final List<WorkRequest> requests = <WorkRequest>[];
+  final Queue<WorkResponse> _responses = Queue<WorkResponse>();
+
+  @override
+  final String printMessage;
+
+  TestAsyncWorkerLoop(AsyncWorkerConnection connection, {this.printMessage})
+      : super(connection: connection);
+
+  @override
+  Future<WorkResponse> performRequest(WorkRequest request) async {
+    requests.add(request);
+    if (printMessage != null) print(printMessage);
+    return _responses.removeFirst();
+  }
+
+  /// Adds [response] to the queue. These will be returned from
+  /// [performResponse] in the order they are added, otherwise it will throw
+  /// if the queue is empty.
+  @override
+  void enqueueResponse(WorkResponse response) {
+    _responses.addLast(response);
+  }
+}
diff --git a/bazel_worker/pubspec.yaml b/bazel_worker/pubspec.yaml
new file mode 100644
index 0000000..dbae4cd
--- /dev/null
+++ b/bazel_worker/pubspec.yaml
@@ -0,0 +1,16 @@
+name: bazel_worker
+version: 0.1.23+1
+
+description: Tools for creating a bazel persistent worker.
+homepage: https://github.com/dart-lang/bazel_worker
+
+environment:
+  sdk: '>=2.3.0 <3.0.0'
+
+dependencies:
+  async: '>1.9.0 <3.0.0'
+  pedantic: ^1.8.0
+  protobuf: '>=0.14.4 <2.0.0'
+
+dev_dependencies:
+  test: ^1.2.0
diff --git a/bazel_worker/tool/travis.sh b/bazel_worker/tool/travis.sh
new file mode 100755
index 0000000..bccd424
--- /dev/null
+++ b/bazel_worker/tool/travis.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+# Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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.
+
+# Fast fail the script on failures.
+set -e
+
+# Verify that the libraries are error free.
+dartanalyzer --fatal-infos --fatal-warnings \
+  lib/bazel_worker.dart \
+  lib/driver.dart \
+  lib/testing.dart \
+  test/test_all.dart
+
+# Run the tests.
+pub run test
+
+pushd e2e_test
+pub get
+dartanalyzer --fatal-infos --fatal-warnings test/e2e_test.dart
+pub run test
+popd
diff --git a/bazel_worker/tool/update_proto.sh b/bazel_worker/tool/update_proto.sh
new file mode 100755
index 0000000..b224694
--- /dev/null
+++ b/bazel_worker/tool/update_proto.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+set -e
+
+if [ -z "$1" ]; then
+    echo "Expected exactly one argument which is the protoc_plugin version to use"
+else
+    echo "Using protoc_plugin version $1"
+    pub global activate protoc_plugin "$1"
+fi
+
+BAZEL_REPO=.dart_tool/bazel_worker/bazel.git/
+# Bash away old versions if they exist
+rm -rf "$BAZEL_REPO"
+git clone https://github.com/bazelbuild/bazel.git "$BAZEL_REPO"
+
+protoc --proto_path="${BAZEL_REPO}/src/main/protobuf" --dart_out=lib/src worker_protocol.proto
+dartfmt -w lib/src/worker_protocol.pb.dart
+
+# We only care about the *.pb.dart file, not the extra files
+rm lib/src/worker_protocol.pbenum.dart
+rm lib/src/worker_protocol.pbjson.dart
+rm lib/src/worker_protocol.pbserver.dart
+
+rm -rf "$BAZEL_REPO"
diff --git a/build/BUILD.gn b/build/BUILD.gn
new file mode 100644
index 0000000..1cb5195
--- /dev/null
+++ b/build/BUILD.gn
@@ -0,0 +1,24 @@
+# This file is generated by importer.py for build-1.2.2
+
+import("//build/dart/dart_library.gni")
+
+dart_library("build") {
+  package_name = "build"
+
+  # This parameter is left empty as we don't care about analysis or exporting
+  # these sources outside of the tree.
+  sources = []
+
+  disable_analysis = true
+
+  deps = [
+    "//third_party/dart-pkg/pub/convert",
+    "//third_party/dart-pkg/pub/logging",
+    "//third_party/dart-pkg/pub/glob",
+    "//third_party/dart-pkg/pub/crypto",
+    "//third_party/dart-pkg/pub/analyzer",
+    "//third_party/dart-pkg/pub/meta",
+    "//third_party/dart-pkg/pub/async",
+    "//third_party/dart-pkg/pub/path",
+  ]
+}
diff --git a/build/CHANGELOG.md b/build/CHANGELOG.md
new file mode 100644
index 0000000..1926bf6
--- /dev/null
+++ b/build/CHANGELOG.md
@@ -0,0 +1,471 @@
+## 1.2.2
+
+### Updated docs for some minor behavior changes in build_resolvers 1.3.0
+
+- `Resolver.libraries` will now return any library that has been resolved with
+  this resolver. This means calls to `libraryFor` or `isLibrary` on files not
+  already resolved will make subsequent `libraries` streams return more
+  libraries than previous calls did.
+- The same holds for `findLibraryByName` since it searches through the
+  `libraries` stream. You may get a different result at different points in
+  your build method if you resolve additional libraries.
+
+## 1.2.1
+
+- Allow analyzer `0.39.x`.
+
+## 1.2.0
+
+- Add the `void reportUnusedAssets(Iterable<AssetId> ids)` method to the
+  `BuildStep` class.
+  - **WARNING**: Using this introduces serious risk of non-hermetic builds.
+  - Indicates to the build system that `ids` were read but their content has
+    no impact on the outputs of the build.
+  - Build system implementations can choose to support this feature or not,
+    and it should be assumed to be a no-op by default.
+
+## 1.1.6
+
+- Allow analyzer version 0.38.0.
+
+## 1.1.5
+
+- Allow analyzer version 0.37.0.
+
+## 1.1.4
+
+- Internal cleanup: use "strict raw types".
+- Some return types for interfaces to implement changed from `Future<dynamic>`
+  to `Future<void>`.
+
+## 1.1.3
+
+- Update the minimum sdk constraint to 2.1.0.
+- Increased the upper bound for `package:analyzer` to `<0.37.0`.
+
+## 1.1.2
+
+- Internal fixes: Remove default value for optional argument.
+
+## 1.1.1
+
+- Requires analyzer version `0.35.0`
+  - `Resolver` implementations in other packages are now backed by an
+    `AnalysisDriver`. There are behavior changes which may be breaking. The
+    `LibraryElement` instances returned by the resolver will now:
+    - Have non-working `context` fields.
+    - Have no source offsets for annotations or their errors.
+    - Have working `session` fields.
+    - Have `Source` instances with different URIs than before.
+
+## 1.1.0
+
+- Add `Resolver.assetIdForElement` API. This allows finding the Dart source
+  asset which contains the definition of an element found through the analyzer.
+- Include the AssetId hash code in the default digest implementation.
+  - Any custom implementations of the `AssetReader.digest` method should do the
+    same, and a comment has been added to that effect.
+
+## 1.0.2
+
+- Increased the upper bound for `package:analyzer` to `<0.35.0`.
+
+## 1.0.1
+
+- Increased the upper bound for `package:analyzer` to `<0.34.0`.
+
+## 1.0.0
+
+### Breaking Changes
+
+- Changed the return type of `Builder.build` from `Future<dynamic>` to
+  `FutureOr<void>`. This should not be breaking for most `Builder` authors, it
+  is only breaking for build system implementations that run `Builder`s.
+
+## 0.12.8
+
+- Added the `T trackStage<T>(String label, T Function() action);` method to
+  `BuildStep`. Actions that are tracked in this way will show up in the
+  performance timeline at `/$perf`.
+
+## 0.12.7+4
+
+- `print` calls inside a Builder will now log at warning instead of info.
+
+## 0.12.7+3
+
+- Throw when attempting to use a `BuildStep` after it has been completed.
+
+## 0.12.7+2
+
+- Increased the upper bound for the sdk to `<3.0.0`.
+
+## 0.12.7+1
+
+- `AssetId`s can no longer be constructed with paths that reach outside their
+  package.
+
+## 0.12.7
+
+- Added `Resolvers.reset` method.
+
+## 0.12.6
+
+- Added `List<String> get pathSegments` to `AssetId`.
+- `log` will now always return a `Logger` instance.
+- Support `package:analyzer` `0.32.0`.
+
+## 0.12.5
+
+- Add exclude support to `FileDeletingBuilder`.
+
+## 0.12.4
+
+- Add `FileDeletingBuilder`.
+- Add `PostProcesBuilderFactory` typedef.
+
+## 0.12.3
+
+- Added an `isRoot` boolean to `BuilderOptions`, which allows builders to have
+  different behavior for the root package, if desired.
+- Add `PostProcessBuilder`. This is only supported by `build_runner`.
+
+## 0.12.2
+
+- Include stack trace in log for exceptions throw by builders.
+
+## 0.12.1
+
+- Add `BuilderOptions.empty` and `BuilderOptions.overrideWith`.
+
+## 0.12.0+2
+
+- **Bug Fix** Correctly handle encoding `AssetId`s as `URI`s when they contain
+  characters which are not valid for a path segment.
+
+## 0.12.0+1
+
+- Support the latest `analyzer` package.
+
+## 0.12.0
+
+### Breaking Changes
+
+- Added the `Future<Digest> digest(AssetId id)` method to the `AssetReader`
+  interface. There is a default implementation which uses `readAsBytes` and
+  `md5`.
+
+## 0.11.2
+
+- Bug Fix: `MultiplexingBuilder` now filters inputs rather than calling _every_
+  builder on any input that matched _any_ builder.
+
+## 0.11.1
+
+- Add `BuilderOptions` and `BuilderFactory` interfaces. Along with
+  `package:build_config` this will offer a consistent way to describe and create
+  the Builder instances exposed by a package.
+
+## 0.11.0
+
+- **Breaking**: `AssetReader.findAssets` now returns a `Stream<AssetId>`
+  instead of an `Iterable<AssetId>`. This also impacts `BuildStep` since that
+  implements `AssetReader`.
+
+## 0.10.2+1
+
+- Fix an issue where multiple `ResourceManager`s would share `Resource`
+  instances if running at the same time.
+
+## 0.10.2
+
+- Added the `MultiPackageAssetReader` interface which allows globbing within
+  any package.
+  - This is not exposed to typical users, only build system implementations
+    need it. The `BuildStep` class does not implement this interface.
+- The docs for `AssetReader#findAssets` have changed such that it globs within
+  the current package instead of the root package (typically defined as
+  `buildStep.inputId.package`).
+  - Before you could run builders only on the root package, but now that you
+    can run them on any package the old functionality no longer makes sense.
+
+## 0.10.1
+
+- Remove restrictions around the root package when Builders are running. It is
+  the responsibility of the build system to ensure that builders are only run on
+  inputs that will produce outputs that can be written.
+- Added the `Resource` class, and `BuildStep#fetchResource` method.
+
+## 0.10.0+1
+
+- Bug Fix: Capture asynchronous errors during asset writing.
+
+## 0.10.0
+
+- **Breaking**: Removed deprecated method `BuildStep.hasInput` - all uses should
+  be going through `BuildStep.canRead`.
+- **Breaking**: `Resolver` has asynchronous APIs for resolution and is retrieved
+  synchronously from the `BuildStep`.
+- **Breaking**:  Replaced `Resolver.getLibrary` with `libraryFor` and
+  `getLibraryByName` with `findLibraryByName`.
+- Change `AssetReader.canRead` to always return a Future.
+
+## 0.9.3
+
+- Add support for resolving `asset:` URIs into AssetIds
+- Add `uri` property on AssetId
+
+## 0.9.1
+
+- Skip Builders which would produce no outputs.
+
+## 0.9.0
+
+- Deprecate `BuildStep.hasInput` in favor of `BuildStep.canRead`.
+- Rename `AssetReader.hasInput` as `canRead`. This is breaking for implementers
+  of `AssetReader` and for any clients passing a `BuildStep` as an `AssetReader`
+- Make `canRead` return a `FutureOr` for more flexibility
+- Drop `ManagedBuildStep` class.
+- Drop `BuildStep.logger`. All logging should go through the top level `log`.
+- `BuildStep.writeAs*` methods now take a `FutureOr` for content. Builders which
+  produce content asynchronously can now set up the write without waiting for it
+  to resolve.
+- **Breaking** `declareOutputs` is replaced with `buildExtensions`. All `Builder`
+  implementations must now have outputs that vary only based on the extensions
+  of inputs, rather than based on any part of the `AssetId`.
+
+## 0.8.0
+
+- Add `AssetReader.findAssets` to allow listing assets by glob.
+
+## 0.7.3
+
+- Add `BuildStep.inputLibrary` as a convenience.
+- Fix a bug in `AssetId.resolve` to prepend `lib/` when resolving packages.
+
+## 0.7.2
+
+- Add an `AssetId.resolve` constructor to easily construct AssetIds from import
+  uris.
+- Capture 'print' calls inside running builds and automatically turn them in to
+  `log.info`. Depending on the environment a print can be hazardous so this
+  makes all builders safe and consistent by default.
+
+## 0.7.1+1
+
+- Use comment syntax for generic method - not everyone is on an SDK version that
+  supports the real syntax.
+
+## 0.7.1
+
+- Add a top-level `log` getter which is scoped to running builds and can be used
+  anywhere within a build rather than passing around a logger. This replaces the
+  `BuildStep.logger` field.
+- Deprecate `BuildStep.logger` - it is replaced by `log`
+- Deprecate `ManagedBuildStep`, all build runs should go through `runBuilders`.
+
+## 0.7.0
+
+A number of changes to the apis, primarily to support reading/writing as bytes,
+as this is going to inevitably be a required feature. This will hopefully be the
+last breaking change before the `1.0` release, but it is a fairly large one.
+
+### New Features
+
+- The `AssetWriter` class now has a
+  `Future writeAsBytes(AssetId id, List<int> bytes)` method.
+- The `AssetReader` class now has a `Future<List<int>> readAsBytes(AssetId id)`
+  method.
+- You no longer need to call `Resolver#release` on any resolvers you get from
+  a `BuildStep` (in fact, the `Resolver` interface no longer has this method).
+- There is now a `BuildStep#resolver` getter, which resolves the primary input,
+  and returns a `Future<Resolver>`. This replaces the `BuildStep#resolve`
+  method.
+- `Resolver` has a new `isLibrary` method to check whether an asset is a Dart
+  library source file before trying to resolve it's LibraryElement
+
+### Breaking Changes
+
+- The `Asset` class has been removed entirely.
+- The `AssetWriter#writeAsString` signature has changed to
+  `Future writeAsString(AssetId id, String contents, {Encoding encoding})`.
+- The type of the `AssetWriterSpy#assetsWritten` getter has changed from an
+  `Iterable<Asset>` to an `Iterable<AssetId>`.
+- `BuildStep#input` has been changed to `BuildStep#inputId`, and its type has
+  changed from `Asset` to `AssetId`. This means you must now use
+  `BuildStep#readAsString` or `BuildStep#readAsBytes` to read the primary input,
+  instead of it already being read in for you.
+- `Resolver` no longer has a `release` method (they are released for you).
+- `BuildStep#resolve` no longer exists, and has been replaced with the
+  `BuildStep#resolver` getter.
+- `Resolver.getLibrary` will now throw a `NonLibraryAssetException` instead of
+  return null if it is asked to resolve an impossible library.
+
+**Note**: The changes to `AssetReader` and `AssetWriter` also affect `BuildStep`
+and other classes that implement those interfaces.
+
+## 0.6.3
+
+- Add hook for `build_barback` to write assets from a Future
+
+## 0.6.2
+
+- Remove unused dependencies
+
+## 0.6.1
+
+- `BuildStep` now implements `AssetReader` and `AssetWriter` so it's easier to
+  share with other code paths using a more limited interface.
+
+## 0.6.0
+
+- **BREAKING** Move some classes and methods out of this package. If you are
+  using `build`, `watch`, or `serve`, along with `PhaseGroup` and related
+  classes add a dependency on `build_runner`. If you are using
+  `BuilderTransformer` or `TansformerBuilder` add a dependency on
+  `build_barback`.
+- **BREAKING** Resolvers is now an abstract class. If you were using the
+  constructor `const Resolvers()` as a default instance import `build_barback`
+  and used `const BarbackResolvers()` instead.
+
+## 0.5.0
+
+- **BREAKING** BuilderTransformer must be constructed with a single Builder. Use
+  the MultiplexingBuilder to cover cases with a list of builders
+- When using a MultiplexingBuilder if multiple Builders have overlapping outputs
+  the entire step will not run rather than running builders up to the point
+  where there is an overlap
+
+## 0.4.1+3
+
+- With the default logger, print exceptions with a terse stack trace.
+- Provide a better error when an inputSet package cannot be found.
+- Fix `dev_dependencies` so tests run.
+
+## 0.4.1+2
+
+- Stop using removed argument `useSharedSources` when constructing Resolvers
+- Support code_transformers 0.5.x
+
+## 0.4.1+1
+
+- Support analyzer 0.29.x
+
+## 0.4.1
+
+- Support analyzer 0.28.x
+
+## 0.4.0
+
+- **BREAKING** BuilderTransformer must be constructed with a List<Builder>
+  rather than inherit and override.
+- Simplifies Resolver interface so it is possible to add implementations which
+  are less complex than the one from code_transformers.
+- Adds a Resolvers class and support for overriding the concrete Resolver that
+  is used by build steps.
+- Updates some test expectations to match the new behavior of analyzer.
+
+## 0.3.0+6
+
+- Convert `packages` paths in the file watcher to their absolute paths. This
+  fixes [#109](https://github.com/dart-lang/build/issues/109).
+
+## 0.3.0+5
+
+- Fix duplicate logs issue when running as a BuilderTransformer.
+
+- Support `crypto` 2.0.0.
+
+## 0.3.0+4
+
+- Add error and stack trace to log messages from the BuilderTransformer.
+
+## 0.3.0+3
+
+- Fixed BuilderTransformer so that logs are passed on to the TransformLogger.
+
+## 0.3.0+2
+
+- Enable serving files outside the server root by default (enables serving
+  files from other packages).
+
+## 0.3.0+1
+
+- Fix an AssetGraph bug where generated nodes might be created as non-generated
+  nodes if they are attempted to be read from previous build steps.
+
+## 0.3.0
+
+- **BREAKING** Renamed values of three enums to be lower-case:
+  `BuildType`, `BuildStatus`, and `PackageDependencyType`.
+- Updated to crypto ^1.0.0.
+- Added option to resolve additional entry points in `buildStep.resolve`.
+- Added option to pass in a custom `Resolvers` instance.
+
+## 0.2.1
+
+- Added the `deleteFilesByDefault` option to all top level methods. This will
+  skip the prompt to delete files, and instead act as if you responded `y`.
+  - Also by default in a non-console environment the prompt no longer exists and
+    it will instead just exit with an error.
+- Added support for multiple build scripts. Each script now has its own asset
+  graph based on a hash of the script uri.
+  - You need to be careful here, as you can get in an infinite loop if two
+    separate build scripts keep triggering updates for each other.
+  - There is no explicit link between multiple scripts, so they operate as if
+    all changes from other scripts were user edits. This will usually just do
+    the "right thing", but may result in undesired behavior in some
+    circumstances.
+- Improved logging for non-posix consoles.
+
+## 0.2.0
+
+- Updated the top level classes to take a `PhaseGroup` instead of a
+  `List<List<Phase>>`.
+- Added logic to handle nested package directories.
+- Basic windows support added, although it may still be unstable.
+- Significantly increased the resolving speed by using the same sources cache.
+- Added a basic README.
+- Moved the `.build` folder to `.dart_tool/build`. Other packages in the future
+  may also use this folder.
+
+## 0.1.4
+
+- Added top level `serve` function.
+  - Just like `watch`, but it provides a server which blocks on any ongoing
+    builds before responding to requests.
+- Minor bug fixes.
+
+## 0.1.3
+
+- Builds are now fully incremental, even on startup.
+  - Builds will be invalidated if the build script or any of its dependencies
+    are updated since there is no way of knowing how that would affect things.
+- Added `lastModified` to `AssetReader` (only matters if you implement it).
+
+## 0.1.2
+
+- Exposed the top level `watch` function. This can be used to watch the file
+  system and run incremental rebuilds on changes.
+  - Initial build is still non-incremental.
+
+## 0.1.1
+
+- Exposed the top level `build` function. This can be used to run builds.
+  - For this release all builds are non-incremental, and delete all previous
+    build outputs when they start up.
+  - Creates a `.build` directory which should be added to your `.gitignore`.
+- Added `resolve` method to `BuildStep` which can give you a `Resolver` for an
+  `AssetId`.
+  - This is experimental and may get moved out to a separate package.
+  - Resolves the full dart sdk so this is slow, first call will take multiple
+    seconds. Subsequent calls are much faster though.
+  - Will end up marking all transitive deps as dependencies, so your files may
+    end up being recompiled often when not entirely necessary (once we have
+    incremental builds).
+- Added `listAssetIds` to `AssetReader` (only matters if you implement it).
+- Added `delete` to `AssetWriter` (also only matters if you implement it).
+
+## 0.1.0
+
+- Initial version
diff --git a/build/LICENSE b/build/LICENSE
new file mode 100644
index 0000000..82e9b52
--- /dev/null
+++ b/build/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2016, the Dart project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of Google Inc. nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/build/README.md b/build/README.md
new file mode 100644
index 0000000..48c3545
--- /dev/null
+++ b/build/README.md
@@ -0,0 +1,157 @@
+# [![Build Status](https://travis-ci.org/dart-lang/build.svg?branch=master)](https://travis-ci.org/dart-lang/build)
+
+# `build`
+
+Defines the basic pieces of how a build happens and how they interact.
+
+## [`Builder`][dartdoc:Builder]
+
+The business logic for code generation. Most consumers of the `build` package
+will create custom implementations of `Builder`.
+
+## [`BuildStep`][dartdoc:BuildStep]
+
+The way a `Builder` interacts with the outside world. Defines the unit of work
+and allows reading/writing files and resolving Dart source code.
+
+## [`Resolver`][dartdoc:Resolver] class
+
+An interface into the dart [analyzer][pub:analyzer] to allow resolution of code
+that needs static analysis and/or code generation.
+
+## Implementing your own Builders
+
+A `Builder` gets invoked one by one on it's inputs, and may read other files and
+output new files based on those inputs.
+
+The basic API looks like this:
+
+```dart
+abstract class Builder {
+  /// You can only output files that are configured here by suffix substitution.
+  /// You are not required to output all of these files, but no other builder
+  /// may declare the same outputs.
+  Map<String, List<String>> get buildExtensions;
+
+  /// This is where you build and output files.
+  FutureOr<void> build(BuildStep buildStep);
+}
+```
+
+Here is an implementation of a `Builder` which just copies files to other files
+with the same name, but an additional extension:
+
+```dart
+import 'package:build/build.dart';
+
+/// A really simple [Builder], it just makes copies of .txt files!
+class CopyBuilder implements Builder {
+  @override
+  final buildExtensions = const {
+    '.txt': ['.txt.copy']
+  };
+
+  @override
+  Future<void> build(BuildStep buildStep) async {
+    // Each `buildStep` has a single input.
+    var inputId = buildStep.inputId;
+
+    // Create a new target `AssetId` based on the old one.
+    var copy = inputId.addExtension('.copy');
+    var contents = await buildStep.readAsString(inputId);
+
+    // Write out the new asset.
+    await buildStep.writeAsString(copy, contents);
+  }
+}
+```
+
+It should be noted that you should _never_ touch the file system directly. Go
+through the `buildStep#readAsString` and `buildStep#writeAsString` methods in
+order to read and write assets. This is what enables the package to track all of
+your dependencies and do incremental rebuilds. It is also what enables your
+[`Builder`][dartdoc:Builder] to run on different environments.
+
+### Using the analyzer
+
+If you need to do analyzer resolution, you can use the `BuildStep#resolver`
+object. This makes sure that all `Builder`s in the system share the same
+analysis context, which greatly speeds up the overall system when multiple
+`Builder`s are doing resolution.
+
+Here is an example of a `Builder` which uses the `resolve` method:
+
+```dart
+import 'package:build/build.dart';
+
+class ResolvingCopyBuilder implements Builder {
+  // Take a `.dart` file as input so that the Resolver has code to resolve
+  @override
+  final buildExtensions = const {
+    '.dart': ['.dart.copy']
+  };
+
+  @override
+  Future<void> build(BuildStep buildStep) async {
+    // Get the `LibraryElement` for the primary input.
+    var entryLib = await buildStep.inputLibrary;
+    // Resolves all libraries reachable from the primary input.
+    var resolver = buildStep.resolver;
+    // Get a `LibraryElement` for another asset.
+    var libFromAsset = await resolver.libraryFor(
+        AssetId.resolve('some_import.dart', from: buildStep.inputId));
+    // Or get a `LibraryElement` by name.
+    var libByName = await resolver.findLibraryByName('my.library');
+  }
+}
+```
+
+Once you have gotten a `LibraryElement` using one of the methods on `Resolver`,
+you are now just using the regular `analyzer` package to explore your app.
+
+### Sharing expensive objects across build steps
+
+The build package includes a `Resource` class, which can give you an instance
+of an expensive object that is guaranteed to be unique across builds, but may
+be re-used by multiple build steps within a single build (to the extent that
+the implementation allows). It also gives you a way of disposing of your
+resource at the end of its lifecycle.
+
+The `Resource<T>` constructor takes a single required argument which is a
+factory function that returns a `FutureOr<T>`. There is also a named argument
+`dispose` which is called at the end of life for the resource, with the
+instance that should be disposed. This returns a `FutureOr<dynamic>`.
+
+So a simple example `Resource` would look like this:
+
+```dart
+final resource = Resource(
+  () => createMyExpensiveResource(),
+  dispose: (instance) async {
+    await instance.doSomeCleanup();
+  });
+```
+
+You can get an instance of the underlying resource by using the
+`BuildStep#fetchResource` method, whose type signature looks like
+`Future<T> fetchResource<T>(Resource<T>)`.
+
+**Important Note**: It may be tempting to try and use a `Resource` instance to
+cache information from previous build steps (or even assets), but this should
+be avoided because it can break the soundness of the build, and may introduce
+subtle bugs for incremental builds (remember the whole build doesn't run every
+time!). The `build` package relies on the `BuildStep#canRead` and
+`BuildStep#readAs*` methods to track build step dependencies, so sidestepping
+those can and will break the dependency tracking, resulting in inconsistent and
+stale assets.
+
+## Features and bugs
+
+Please file feature requests and bugs at the [issue tracker][tracker].
+
+[tracker]: https://github.com/dart-lang/build/issues
+
+[dartdoc:Builder]: https://pub.dev/documentation/build/latest/build/Builder-class.html
+[dartdoc:BuildStep]: https://pub.dev/documentation/build/latest/build/BuildStep-class.html
+[dartdoc:Resolver]: https://pub.dev/documentation/build/latest/build/Resolver-class.html
+[pub:analyzer]: https://pub.dev/packages/analyzer
diff --git a/build/lib/build.dart b/build/lib/build.dart
new file mode 100644
index 0000000..b5f3574
--- /dev/null
+++ b/build/lib/build.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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.
+
+export 'src/analyzer/resolver.dart';
+export 'src/asset/exceptions.dart';
+export 'src/asset/id.dart';
+export 'src/asset/reader.dart';
+export 'src/asset/writer.dart';
+export 'src/builder/build_step.dart' hide NoOpStageTracker;
+export 'src/builder/builder.dart';
+export 'src/builder/exceptions.dart';
+export 'src/builder/file_deleting_builder.dart' show FileDeletingBuilder;
+export 'src/builder/logging.dart' show log;
+export 'src/builder/multiplexing_builder.dart';
+export 'src/builder/post_process_build_step.dart' show PostProcessBuildStep;
+export 'src/builder/post_process_builder.dart'
+    show PostProcessBuilder, PostProcessBuilderFactory;
+export 'src/generate/expected_outputs.dart';
+export 'src/generate/run_builder.dart';
+export 'src/generate/run_post_process_builder.dart' show runPostProcessBuilder;
+export 'src/resource/resource.dart';
diff --git a/build/lib/src/analyzer/resolver.dart b/build/lib/src/analyzer/resolver.dart
new file mode 100644
index 0000000..be73cfc
--- /dev/null
+++ b/build/lib/src/analyzer/resolver.dart
@@ -0,0 +1,82 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'package:analyzer/dart/element/element.dart';
+
+import '../asset/id.dart';
+import '../builder/build_step.dart';
+
+/// Standard interface for resolving Dart source code as part of a build.
+abstract class Resolver {
+  /// Returns whether [assetId] represents an Dart library file.
+  ///
+  /// This will be `false` in the case where the file is not Dart source code,
+  /// or is a `part of` file (not a standalone Dart library).
+  Future<bool> isLibrary(AssetId assetId);
+
+  /// All libraries recursively accessible from the entry point or subsequent
+  /// calls to [libraryFor] and [isLibrary].
+  ///
+  /// **NOTE**: This includes all Dart SDK libraries as well.
+  Stream<LibraryElement> get libraries;
+
+  /// Returns a resolved library representing the file defined in [assetId].
+  ///
+  /// * Throws [NonLibraryAssetException] if [assetId] is not a Dart library.
+  Future<LibraryElement> libraryFor(AssetId assetId);
+
+  /// Returns the first resolved library identified by [libraryName].
+  ///
+  /// A library is resolved if it's recursively accessible from the entry point
+  /// or subsequent calls to [libraryFor] and [isLibrary]. If no library can be
+  /// found, returns `null`.
+  ///
+  /// **NOTE**: In general, its recommended to use [libraryFor] with an absolute
+  /// asset id instead of a named identifier that has the possibility of not
+  /// being unique.
+  Future<LibraryElement> findLibraryByName(String libraryName);
+
+  /// Returns the [AssetId] of the Dart library or part declaring [element].
+  ///
+  /// If [element] is defined in the SDK or in a summary throws
+  /// `UnresolvableAssetException`, although a non-throwing return here does not
+  /// guarantee that the asset is readable.
+  ///
+  /// The returned asset is not necessarily the asset that should be imported to
+  /// use the element, it may be a part file instead of the library.
+  Future<AssetId> assetIdForElement(Element element);
+}
+
+/// A resolver that should be manually released at the end of a build step.
+abstract class ReleasableResolver implements Resolver {
+  /// Release this resolver so it can be updated by following build steps.
+  void release();
+}
+
+/// A factory that returns a resolver for a given [BuildStep].
+abstract class Resolvers {
+  const Resolvers();
+
+  Future<ReleasableResolver> get(BuildStep buildStep);
+
+  /// Reset the state of any caches within [Resolver] instances produced by
+  /// this [Resolvers].
+  ///
+  /// In between calls to [reset] no Assets should change, so every call to
+  /// `BuildStep.readAsString` for a given AssetId should return identical
+  /// contents. Any time an Asset's contents may change [reset] must be called.
+  void reset() {}
+}
+
+/// Thrown when attempting to read a non-Dart library in a [Resolver].
+class NonLibraryAssetException implements Exception {
+  final AssetId assetId;
+
+  const NonLibraryAssetException(this.assetId);
+
+  @override
+  String toString() => 'Asset [$assetId] is not a Dart library. '
+      'It may be a part file or a file without Dart source code.';
+}
diff --git a/build/lib/src/asset/exceptions.dart b/build/lib/src/asset/exceptions.dart
new file mode 100644
index 0000000..141c071
--- /dev/null
+++ b/build/lib/src/asset/exceptions.dart
@@ -0,0 +1,57 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 'id.dart';
+
+class AssetNotFoundException implements Exception {
+  final AssetId assetId;
+
+  AssetNotFoundException(this.assetId);
+
+  @override
+  String toString() => 'AssetNotFoundException: $assetId';
+}
+
+class PackageNotFoundException implements Exception {
+  final String name;
+
+  PackageNotFoundException(this.name);
+
+  @override
+  String toString() => 'PackageNotFoundException: $name';
+}
+
+class InvalidOutputException implements Exception {
+  final AssetId assetId;
+  final String message;
+
+  InvalidOutputException(this.assetId, this.message);
+
+  @override
+  String toString() => 'InvalidOutputException: $assetId\n$message';
+}
+
+class InvalidInputException implements Exception {
+  final AssetId assetId;
+
+  InvalidInputException(this.assetId);
+
+  @override
+  String toString() => 'InvalidInputException: $assetId\n'
+      'For package dependencies, only files under `lib` may be used as inputs.';
+}
+
+class BuildStepCompletedException implements Exception {
+  @override
+  String toString() => 'BuildStepCompletedException: '
+      'Attempt to use a BuildStep after is has completed';
+}
+
+class UnresolvableAssetException implements Exception {
+  final String description;
+
+  const UnresolvableAssetException(this.description);
+
+  @override
+  String toString() => 'Unresolvable Asset from $description.';
+}
diff --git a/build/lib/src/asset/id.dart b/build/lib/src/asset/id.dart
new file mode 100644
index 0000000..a743cf7
--- /dev/null
+++ b/build/lib/src/asset/id.dart
@@ -0,0 +1,169 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 'package:path/path.dart' as p;
+
+/// Identifies an asset within a package.
+class AssetId implements Comparable<AssetId> {
+  /// The name of the package containing this asset.
+  final String package;
+
+  /// The path to the asset relative to the root directory of [package].
+  ///
+  /// Source (i.e. read from disk) and generated (i.e. the output of a
+  /// `Builder`) assets all have paths. Even intermediate assets that are
+  /// generated and then consumed by later transformations will still have a
+  /// path used to identify it.
+  ///
+  /// Asset paths always use forward slashes as path separators, regardless of
+  /// the host platform. Asset paths will always be within their package, that
+  /// is they will never contain "../".
+  final String path;
+
+  /// Splits [path] into its components.
+  List<String> get pathSegments => p.url.split(path);
+
+  /// The file extension of the asset, if it has one, including the ".".
+  String get extension => p.extension(path);
+
+  /// Creates a new [AssetId] at [path] within [package].
+  ///
+  /// The [path] will be normalized: any backslashes will be replaced with
+  /// forward slashes (regardless of host OS) and "." and ".." will be removed
+  /// where possible.
+  AssetId(this.package, String path) : path = _normalizePath(path);
+
+  /// Creates a new [AssetId] from an [uri] String.
+  ///
+  /// This gracefully handles `package:` or `asset:` URIs.
+  ///
+  /// Resolve a `package:` URI when creating an [AssetId] from an `import` or
+  /// `export` directive pointing to a package's _lib_ directory:
+  /// ```dart
+  /// AssetId assetOfDirective(UriReferencedElement element) {
+  ///   return new AssetId.resolve(element.uri);
+  /// }
+  /// ```
+  ///
+  /// When resolving a relative URI with no scheme, specifyg the origin asset
+  /// ([from]) - otherwise an [ArgumentError] will be thrown.
+  /// ```dart
+  /// AssetId assetOfDirective(AssetId origin, UriReferencedElement element) {
+  ///   return new AssetId.resolve(element.uri, from: origin);
+  /// }
+  /// ```
+  ///
+  /// `asset:` uris have the format '$package/$path', including the top level
+  /// directory.
+  factory AssetId.resolve(String uri, {AssetId from}) {
+    final parsedUri = Uri.parse(uri);
+    if (parsedUri.hasScheme) {
+      if (parsedUri.scheme == 'package') {
+        return AssetId(parsedUri.pathSegments.first,
+            p.url.join('lib', p.url.joinAll(parsedUri.pathSegments.skip(1))));
+      } else if (parsedUri.scheme == 'asset') {
+        return AssetId(parsedUri.pathSegments.first,
+            p.url.joinAll(parsedUri.pathSegments.skip(1)));
+      }
+      throw UnsupportedError(
+          'Cannot resolve $uri; only "package" and "asset" schemes supported');
+    }
+    if (from == null) {
+      throw ArgumentError.value(from, 'from',
+          'An AssetId "from" must be specified to resolve a relative URI');
+    }
+    return AssetId(p.url.normalize(from.package),
+        p.url.join(p.url.dirname(from.path), uri));
+  }
+
+  /// Parses an [AssetId] string of the form "package|path/to/asset.txt".
+  ///
+  /// The [path] will be normalized: any backslashes will be replaced with
+  /// forward slashes (regardless of host OS) and "." and ".." will be removed
+  /// where possible.
+  factory AssetId.parse(String description) {
+    var parts = description.split('|');
+    if (parts.length != 2) {
+      throw FormatException('Could not parse "$description".');
+    }
+
+    if (parts[0].isEmpty) {
+      throw FormatException(
+          'Cannot have empty package name in "$description".');
+    }
+
+    if (parts[1].isEmpty) {
+      throw FormatException('Cannot have empty path in "$description".');
+    }
+
+    return AssetId(parts[0], parts[1]);
+  }
+
+  /// A `package:` URI suitable for use directly with other systems if this
+  /// asset is under it's package's `lib/` directory, else an `asset:` URI
+  /// suitable for use within build tools.
+  Uri get uri => _uri ??= _constructUri(this);
+  Uri _uri;
+
+  /// Deserializes an [AssetId] from [data], which must be the result of
+  /// calling [serialize] on an existing [AssetId].
+  AssetId.deserialize(List<dynamic> data)
+      : package = data[0] as String,
+        path = data[1] as String;
+
+  /// Returns `true` if [other] is an [AssetId] with the same package and path.
+  @override
+  bool operator ==(Object other) =>
+      other is AssetId && package == other.package && path == other.path;
+
+  @override
+  int get hashCode => package.hashCode ^ path.hashCode;
+
+  @override
+  int compareTo(AssetId other) {
+    var packageComp = package.compareTo(other.package);
+    if (packageComp != 0) return packageComp;
+    return path.compareTo(other.path);
+  }
+
+  /// Returns a new [AssetId] with the same [package] as this one and with the
+  /// [path] extended to include [extension].
+  AssetId addExtension(String extension) => AssetId(package, '$path$extension');
+
+  /// Returns a new [AssetId] with the same [package] and [path] as this one
+  /// but with file extension [newExtension].
+  AssetId changeExtension(String newExtension) =>
+      AssetId(package, p.withoutExtension(path) + newExtension);
+
+  @override
+  String toString() => '$package|$path';
+
+  /// Serializes this [AssetId] to an object that can be sent across isolates
+  /// and passed to [AssetId.deserialize].
+  Object serialize() => [package, path];
+}
+
+String _normalizePath(String path) {
+  if (p.isAbsolute(path)) {
+    throw ArgumentError.value(path, 'Asset paths must be relative.');
+  }
+
+  // Normalize path separators so that they are always "/" in the AssetID.
+  path = path.replaceAll(r'\', '/');
+
+  // Collapse "." and "..".
+  final collapsed = p.posix.normalize(path);
+  if (collapsed.startsWith('../')) {
+    throw ArgumentError.value(
+        path, 'Asset paths may not reach outside the package.');
+  }
+  return collapsed;
+}
+
+Uri _constructUri(AssetId id) {
+  final originalSegments = id.pathSegments;
+  final isLib = originalSegments.first == 'lib';
+  final scheme = isLib ? 'package' : 'asset';
+  final pathSegments = isLib ? originalSegments.skip(1) : originalSegments;
+  return Uri(scheme: scheme, pathSegments: [id.package]..addAll(pathSegments));
+}
diff --git a/build/lib/src/asset/reader.dart b/build/lib/src/asset/reader.dart
new file mode 100644
index 0000000..fdee30b
--- /dev/null
+++ b/build/lib/src/asset/reader.dart
@@ -0,0 +1,68 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:convert';
+
+import 'package:crypto/crypto.dart';
+import 'package:convert/convert.dart';
+import 'package:glob/glob.dart';
+
+import 'id.dart';
+
+/// Standard interface for reading an asset within in a package.
+///
+/// An [AssetReader] is required when calling the `runBuilder` method.
+abstract class AssetReader {
+  /// Returns a [Future] that completes with the bytes of a binary asset.
+  ///
+  /// * Throws a `PackageNotFoundException` if `id.package` is not found.
+  /// * Throws a `AssetNotFoundException` if `id.path` is not found.
+  Future<List<int>> readAsBytes(AssetId id);
+
+  /// Returns a [Future] that completes with the contents of a text asset.
+  ///
+  /// When decoding as text uses [encoding], or [utf8] is not specified.
+  ///
+  /// * Throws a `PackageNotFoundException` if `id.package` is not found.
+  /// * Throws a `AssetNotFoundException` if `id.path` is not found.
+  Future<String> readAsString(AssetId id, {Encoding encoding});
+
+  /// Indicates whether asset at [id] is readable.
+  Future<bool> canRead(AssetId id);
+
+  /// Returns all readable assets matching [glob] under the current package.
+  Stream<AssetId> findAssets(Glob glob);
+
+  /// Returns a [Digest] representing a hash of the contents of [id].
+  ///
+  /// The digests should include the asset ID as well as the content of the
+  /// file, as some build systems may rely on the digests for two files being
+  /// different, even if their content is the same.
+  ///
+  /// This should be treated as a transparent [Digest] and the implementation
+  /// may differ based on the current build system being used.
+  Future<Digest> digest(AssetId id) async {
+    var digestSink = AccumulatorSink<Digest>();
+    md5.startChunkedConversion(digestSink)
+      ..add(await readAsBytes(id))
+      ..add(id.toString().codeUnits)
+      ..close();
+    return digestSink.events.first;
+  }
+}
+
+/// The same as an `AssetReader`, except that `findAssets` takes an optional
+/// argument `package` which allows you to glob any package.
+///
+/// This should not be exposed to end users generally, but can be used by
+/// different build system implementations.
+abstract class MultiPackageAssetReader extends AssetReader {
+  /// Returns all readable assets matching [glob] under [package].
+  ///
+  /// Some implementations may require the [package] argument, while others
+  /// may have a sane default.
+  @override
+  Stream<AssetId> findAssets(Glob glob, {String package});
+}
diff --git a/build/lib/src/asset/writer.dart b/build/lib/src/asset/writer.dart
new file mode 100644
index 0000000..68ba81f
--- /dev/null
+++ b/build/lib/src/asset/writer.dart
@@ -0,0 +1,50 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:convert';
+
+import 'id.dart';
+
+/// Standard interface for writing an asset into a package's outputs.
+abstract class AssetWriter {
+  /// Writes [bytes] to a binary file located at [id].
+  ///
+  /// Returns a [Future] that completes after writing the asset out.
+  ///
+  /// * Throws a `PackageNotFoundException` if `id.package` is not found.
+  /// * Throws an `InvalidOutputException` if the output was not valid.
+  Future<void> writeAsBytes(AssetId id, List<int> bytes);
+
+  /// Writes [contents] to a text file located at [id] with [encoding].
+  ///
+  /// Returns a [Future] that completes after writing the asset out.
+  ///
+  /// * Throws a `PackageNotFoundException` if `id.package` is not found.
+  /// * Throws an `InvalidOutputException` if the output was not valid.
+  Future<void> writeAsString(AssetId id, String contents,
+      {Encoding encoding = utf8});
+}
+
+/// An [AssetWriter] which tracks all [assetsWritten] during its lifetime.
+class AssetWriterSpy implements AssetWriter {
+  final AssetWriter _delegate;
+  final _assetsWritten = Set<AssetId>();
+
+  AssetWriterSpy(this._delegate);
+
+  Iterable<AssetId> get assetsWritten => _assetsWritten;
+
+  @override
+  Future<void> writeAsBytes(AssetId id, List<int> bytes) {
+    _assetsWritten.add(id);
+    return _delegate.writeAsBytes(id, bytes);
+  }
+
+  @override
+  Future<void> writeAsString(AssetId id, String contents,
+      {Encoding encoding = utf8}) {
+    _assetsWritten.add(id);
+    return _delegate.writeAsString(id, contents, encoding: encoding);
+  }
+}
diff --git a/build/lib/src/builder/build_step.dart b/build/lib/src/builder/build_step.dart
new file mode 100644
index 0000000..fb7a159
--- /dev/null
+++ b/build/lib/src/builder/build_step.dart
@@ -0,0 +1,111 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:convert';
+
+import 'package:analyzer/dart/element/element.dart';
+
+import '../analyzer/resolver.dart';
+import '../asset/id.dart';
+import '../asset/reader.dart';
+import '../asset/writer.dart';
+import '../resource/resource.dart';
+
+/// A single step in a build process.
+///
+/// This represents a single [inputId], logic around resolving as a library,
+/// and the ability to read and write assets as allowed by the underlying build
+/// system.
+abstract class BuildStep implements AssetReader, AssetWriter {
+  /// The primary for this build step.
+  AssetId get inputId;
+
+  /// Resolved library defined by [inputId].
+  ///
+  /// Throws [NonLibraryAssetException] if [inputId] is not a Dart library file.
+  Future<LibraryElement> get inputLibrary;
+
+  /// Gets an instance provided by [resource] which is guaranteed to be unique
+  /// within a single build, and may be reused across build steps within a
+  /// build if the implementation allows.
+  ///
+  /// It is also guaranteed that [resource] will be disposed before the next
+  /// build starts (and the dispose callback will be invoked if provided).
+  Future<T> fetchResource<T>(Resource<T> resource);
+
+  /// Writes [bytes] to a binary file located at [id].
+  ///
+  /// Returns a [Future] that completes after writing the asset out.
+  ///
+  /// * Throws a `PackageNotFoundException` if `id.package` is not found.
+  /// * Throws an `InvalidOutputException` if the output was not valid.
+  ///
+  /// **NOTE**: Most `Builder` implementations should not need to `await` this
+  /// Future since the runner will be responsible for waiting until all outputs
+  /// are written.
+  @override
+  Future<void> writeAsBytes(AssetId id, FutureOr<List<int>> bytes);
+
+  /// Writes [contents] to a text file located at [id] with [encoding].
+  ///
+  /// Returns a [Future] that completes after writing the asset out.
+  ///
+  /// * Throws a `PackageNotFoundException` if `id.package` is not found.
+  /// * Throws an `InvalidOutputException` if the output was not valid.
+  ///
+  /// **NOTE**: Most `Builder` implementations should not need to `await` this
+  /// Future since the runner will be responsible for waiting until all outputs
+  /// are written.
+  @override
+  Future<void> writeAsString(AssetId id, FutureOr<String> contents,
+      {Encoding encoding = utf8});
+
+  /// A [Resolver] for [inputId].
+  Resolver get resolver;
+
+  /// Tracks performance of [action] separately.
+  ///
+  /// If performance tracking is enabled, tracks [action] as separate stage
+  /// identified by [label]. Otherwise just runs [action].
+  ///
+  /// You can specify [action] as [isExternal] (waiting for some external
+  /// resource like network, process or file IO). In that case [action] will
+  /// be tracked as single time slice from the beginning of the stage till
+  /// completion of Future returned by [action].
+  ///
+  /// Otherwise all separate time slices of asynchronous execution will be
+  /// tracked, but waiting for external resources will be a gap.
+  ///
+  /// Returns value returned by [action].
+  /// [action] can be async function returning [Future].
+  T trackStage<T>(String label, T Function() action, {bool isExternal = false});
+
+  /// Indicates that [ids] were read but their content has no impact on the
+  /// outputs of this step.
+  ///
+  /// **WARNING**: Using this introduces serious risk of non-hermetic builds.
+  ///
+  /// If these files change or become unreadable on the next build this build
+  /// step may not run.
+  ///
+  /// **Note**: This is not guaranteed to have any effect and it should be
+  /// assumed to be a no-op by default. Implementations must explicitly
+  /// choose to support this feature.
+  void reportUnusedAssets(Iterable<AssetId> ids);
+}
+
+abstract class StageTracker {
+  T trackStage<T>(String label, T Function() action, {bool isExternal = false});
+}
+
+class NoOpStageTracker implements StageTracker {
+  static const StageTracker instance = NoOpStageTracker._();
+
+  @override
+  T trackStage<T>(String label, T Function() action,
+          {bool isExternal = false}) =>
+      action();
+
+  const NoOpStageTracker._();
+}
diff --git a/build/lib/src/builder/build_step_impl.dart b/build/lib/src/builder/build_step_impl.dart
new file mode 100644
index 0000000..65e0aaa
--- /dev/null
+++ b/build/lib/src/builder/build_step_impl.dart
@@ -0,0 +1,213 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:convert';
+
+import 'package:analyzer/dart/element/element.dart';
+import 'package:async/async.dart';
+import 'package:build/src/builder/build_step.dart';
+import 'package:crypto/crypto.dart';
+import 'package:glob/glob.dart';
+
+import '../analyzer/resolver.dart';
+import '../asset/exceptions.dart';
+import '../asset/id.dart';
+import '../asset/reader.dart';
+import '../asset/writer.dart';
+import '../resource/resource.dart';
+import 'build_step.dart';
+import 'exceptions.dart';
+
+/// A single step in the build processes.
+///
+/// This represents a single input and its expected and real outputs. It also
+/// handles tracking of dependencies.
+class BuildStepImpl implements BuildStep {
+  final Resolvers _resolvers;
+  final StageTracker _stageTracker;
+
+  /// The primary input id for this build step.
+  @override
+  final AssetId inputId;
+
+  @override
+  Future<LibraryElement> get inputLibrary async {
+    if (_isComplete) throw BuildStepCompletedException();
+    return resolver.libraryFor(inputId);
+  }
+
+  /// The list of all outputs which are expected/allowed to be output from this
+  /// step.
+  final Set<AssetId> _expectedOutputs;
+
+  /// The result of any writes which are starting during this step.
+  final _writeResults = <Future<Result<void>>>[];
+
+  /// Used internally for reading files.
+  final AssetReader _reader;
+
+  /// Used internally for writing files.
+  final AssetWriter _writer;
+
+  /// The current root package, used for input/output validation.
+  final String _rootPackage;
+
+  final ResourceManager _resourceManager;
+
+  bool _isComplete = false;
+
+  final void Function(Iterable<AssetId>) _reportUnusedAssets;
+
+  BuildStepImpl(this.inputId, Iterable<AssetId> expectedOutputs, this._reader,
+      this._writer, this._rootPackage, this._resolvers, this._resourceManager,
+      {StageTracker stageTracker,
+      void Function(Iterable<AssetId>) reportUnusedAssets})
+      : _expectedOutputs = expectedOutputs.toSet(),
+        _stageTracker = stageTracker ?? NoOpStageTracker.instance,
+        _reportUnusedAssets = reportUnusedAssets;
+
+  @override
+  Resolver get resolver {
+    if (_isComplete) throw BuildStepCompletedException();
+    return _DelayedResolver(_resolver ??= _resolvers.get(this));
+  }
+
+  Future<ReleasableResolver> _resolver;
+
+  @override
+  Future<bool> canRead(AssetId id) {
+    if (_isComplete) throw BuildStepCompletedException();
+    _checkInput(id);
+    return _reader.canRead(id);
+  }
+
+  @override
+  Future<T> fetchResource<T>(Resource<T> resource) {
+    if (_isComplete) throw BuildStepCompletedException();
+    return _resourceManager.fetch(resource);
+  }
+
+  @override
+  Future<List<int>> readAsBytes(AssetId id) {
+    if (_isComplete) throw BuildStepCompletedException();
+    _checkInput(id);
+    return _reader.readAsBytes(id);
+  }
+
+  @override
+  Future<String> readAsString(AssetId id, {Encoding encoding = utf8}) {
+    if (_isComplete) throw BuildStepCompletedException();
+    _checkInput(id);
+    return _reader.readAsString(id, encoding: encoding);
+  }
+
+  @override
+  Stream<AssetId> findAssets(Glob glob) {
+    if (_isComplete) throw BuildStepCompletedException();
+    if (_reader is MultiPackageAssetReader) {
+      return (_reader as MultiPackageAssetReader)
+          .findAssets(glob, package: inputId?.package ?? _rootPackage);
+    } else {
+      return _reader.findAssets(glob);
+    }
+  }
+
+  @override
+  Future<void> writeAsBytes(AssetId id, FutureOr<List<int>> bytes) {
+    if (_isComplete) throw BuildStepCompletedException();
+    _checkOutput(id);
+    var done =
+        _futureOrWrite(bytes, (List<int> b) => _writer.writeAsBytes(id, b));
+    _writeResults.add(Result.capture(done));
+    return done;
+  }
+
+  @override
+  Future<void> writeAsString(AssetId id, FutureOr<String> content,
+      {Encoding encoding = utf8}) {
+    if (_isComplete) throw BuildStepCompletedException();
+    _checkOutput(id);
+    var done = _futureOrWrite(content,
+        (String c) => _writer.writeAsString(id, c, encoding: encoding));
+    _writeResults.add(Result.capture(done));
+    return done;
+  }
+
+  @override
+  Future<Digest> digest(AssetId id) {
+    if (_isComplete) throw BuildStepCompletedException();
+    _checkInput(id);
+    return _reader.digest(id);
+  }
+
+  @override
+  T trackStage<T>(String label, T Function() action,
+          {bool isExternal = false}) =>
+      _stageTracker.trackStage(label, action, isExternal: isExternal);
+
+  Future<void> _futureOrWrite<T>(
+          FutureOr<T> content, Future<void> write(T content)) =>
+      (content is Future<T>) ? content.then(write) : write(content as T);
+
+  /// Waits for work to finish and cleans up resources.
+  ///
+  /// This method should be called after a build has completed. After the
+  /// returned [Future] completes then all outputs have been written and the
+  /// [Resolver] for this build step - if any - has been released.
+  Future<void> complete() async {
+    _isComplete = true;
+    await Future.wait(_writeResults.map(Result.release));
+    (await _resolver)?.release();
+  }
+
+  /// Checks that [id] is a valid input, and throws an [InvalidInputException]
+  /// if its not.
+  void _checkInput(AssetId id) {
+    if (id.package != _rootPackage && !id.path.startsWith('lib/')) {
+      throw InvalidInputException(id);
+    }
+  }
+
+  /// Checks that [id] is an expected output, and throws an
+  /// [InvalidOutputException] or [UnexpectedOutputException] if it's not.
+  void _checkOutput(AssetId id) {
+    if (!_expectedOutputs.contains(id)) {
+      throw UnexpectedOutputException(id, expected: _expectedOutputs);
+    }
+  }
+
+  @override
+  void reportUnusedAssets(Iterable<AssetId> assets) {
+    if (_reportUnusedAssets != null) _reportUnusedAssets(assets);
+  }
+}
+
+class _DelayedResolver implements Resolver {
+  final Future<Resolver> _delegate;
+
+  _DelayedResolver(this._delegate);
+
+  @override
+  Future<bool> isLibrary(AssetId assetId) async =>
+      (await _delegate).isLibrary(assetId);
+
+  @override
+  Stream<LibraryElement> get libraries {
+    var completer = StreamCompleter<LibraryElement>();
+    _delegate.then((r) => completer.setSourceStream(r.libraries));
+    return completer.stream;
+  }
+
+  @override
+  Future<LibraryElement> libraryFor(AssetId assetId) async =>
+      (await _delegate).libraryFor(assetId);
+
+  @override
+  Future<LibraryElement> findLibraryByName(String libraryName) async =>
+      (await _delegate).findLibraryByName(libraryName);
+
+  @override
+  Future<AssetId> assetIdForElement(Element element) async =>
+      (await _delegate).assetIdForElement(element);
+}
diff --git a/build/lib/src/builder/builder.dart b/build/lib/src/builder/builder.dart
new file mode 100644
index 0000000..1c97f40
--- /dev/null
+++ b/build/lib/src/builder/builder.dart
@@ -0,0 +1,61 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'build_step.dart';
+
+/// The basic builder class, used to build new files from existing ones.
+abstract class Builder {
+  /// Generates the outputs for a given [BuildStep].
+  FutureOr<void> build(BuildStep buildStep);
+
+  /// Mapping from input file extension to output file extensions.
+  ///
+  /// All input sources matching any key in this map will be passed as build
+  /// step to this builder. Only files with the same basename and an extension
+  /// from the values in this map are expected as outputs.
+  ///
+  /// - If an empty key exists, all inputs are considered matching.
+  /// - A builder must always return the same configuration. Typically this will
+  /// be `const` but may vary based on build arguments.
+  /// - Most builders will use a single input extension and one or more output
+  /// extensions.
+  Map<String, List<String>> get buildExtensions;
+}
+
+class BuilderOptions {
+  /// A configuration with no options set.
+  static const empty = BuilderOptions({});
+
+  /// A configuration with [isRoot] set to `true`, and no options set.
+  static const forRoot = BuilderOptions({}, isRoot: true);
+
+  /// The configuration to apply to a given usage of a [Builder].
+  ///
+  /// A `Map` parsed from json or yaml. The value types will be `String`, `num`,
+  /// `bool` or `List` or `Map` of these types.
+  final Map<String, dynamic> config;
+
+  /// Whether or not this builder is running on the root package.
+  final bool isRoot;
+
+  const BuilderOptions(this.config, {bool isRoot}) : isRoot = isRoot ?? false;
+
+  /// Returns a new set of options with keys from [other] overriding options in
+  /// this instance.
+  ///
+  /// Config values are overridden at a per-key granularity. There is no value
+  /// level merging. [other] may be null, in which case this instance is
+  /// returned directly.
+  ///
+  /// The `isRoot` value will also be overridden to value from [other].
+  BuilderOptions overrideWith(BuilderOptions other) {
+    if (other == null) return this;
+    return BuilderOptions({}..addAll(config)..addAll(other.config),
+        isRoot: other.isRoot);
+  }
+}
+
+/// Creates a [Builder] honoring the configuation in [options].
+typedef BuilderFactory = Builder Function(BuilderOptions options);
diff --git a/build/lib/src/builder/exceptions.dart b/build/lib/src/builder/exceptions.dart
new file mode 100644
index 0000000..8458b05
--- /dev/null
+++ b/build/lib/src/builder/exceptions.dart
@@ -0,0 +1,15 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 '../asset/id.dart';
+
+class UnexpectedOutputException implements Exception {
+  final AssetId assetId;
+  final Iterable<AssetId> expected;
+
+  UnexpectedOutputException(this.assetId, {this.expected});
+
+  @override
+  String toString() => 'UnexpectedOutputException: $assetId'
+      '${expected == null ? '' : '\nExpected only: $expected'}';
+}
diff --git a/build/lib/src/builder/file_deleting_builder.dart b/build/lib/src/builder/file_deleting_builder.dart
new file mode 100644
index 0000000..4621039
--- /dev/null
+++ b/build/lib/src/builder/file_deleting_builder.dart
@@ -0,0 +1,36 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'package:glob/glob.dart';
+
+import 'post_process_build_step.dart';
+import 'post_process_builder.dart';
+
+/// A [PostProcessBuilder] which can be configured to consume any input
+/// extensions and always deletes it primary input.
+class FileDeletingBuilder implements PostProcessBuilder {
+  @override
+  final List<String> inputExtensions;
+
+  final bool isEnabled;
+  final List<Glob> exclude;
+
+  const FileDeletingBuilder(this.inputExtensions, {this.isEnabled = true})
+      : exclude = const [];
+
+  FileDeletingBuilder.withExcludes(
+      this.inputExtensions, Iterable<String> exclude,
+      {this.isEnabled = true})
+      : exclude = exclude?.map((s) => Glob(s))?.toList() ?? const [];
+
+  @override
+  FutureOr<Null> build(PostProcessBuildStep buildStep) {
+    if (!isEnabled) return null;
+    if (exclude.any((g) => g.matches(buildStep.inputId.path))) return null;
+    buildStep.deletePrimaryInput();
+    return null;
+  }
+}
diff --git a/build/lib/src/builder/logging.dart b/build/lib/src/builder/logging.dart
new file mode 100644
index 0000000..a00e67e
--- /dev/null
+++ b/build/lib/src/builder/logging.dart
@@ -0,0 +1,37 @@
+import 'dart:async';
+
+import 'package:logging/logging.dart';
+
+const Symbol logKey = #buildLog;
+
+final _default = Logger('build.fallback');
+
+/// The log instance for the currently running BuildStep.
+///
+/// Will be `null` when not running within a build.
+Logger get log => Zone.current[logKey] as Logger ?? _default;
+
+/// Runs [fn] in an error handling [Zone].
+///
+/// Any calls to [print] will be logged with `log.warning`, and any errors will
+/// be logged with `log.severe`.
+///
+/// Completes with the first error or result of `fn`, whichever comes first.
+Future<T> scopeLogAsync<T>(Future<T> fn(), Logger log) {
+  var done = Completer<T>();
+  runZoned(fn,
+      zoneSpecification:
+          ZoneSpecification(print: (self, parent, zone, message) {
+        log.warning(message);
+      }),
+      zoneValues: {logKey: log},
+      onError: (Object e, StackTrace s) {
+        log.severe('', e, s);
+        if (done.isCompleted) return;
+        done.completeError(e, s);
+      }).then((result) {
+    if (done.isCompleted) return;
+    done.complete(result);
+  });
+  return done.future;
+}
diff --git a/build/lib/src/builder/multiplexing_builder.dart b/build/lib/src/builder/multiplexing_builder.dart
new file mode 100644
index 0000000..e91ee15
--- /dev/null
+++ b/build/lib/src/builder/multiplexing_builder.dart
@@ -0,0 +1,49 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'build_step.dart';
+import 'builder.dart';
+
+/// A [Builder] that runs multiple delegate builders asynchronously.
+///
+/// **Note**: All builders are ran without ordering guarantees. Thus, none of
+/// the builders can use the outputs of other builders in this group. All
+/// builders must also have distinct outputs.
+class MultiplexingBuilder implements Builder {
+  final Iterable<Builder> _builders;
+
+  MultiplexingBuilder(this._builders);
+
+  @override
+  FutureOr<void> build(BuildStep buildStep) {
+    return Future.wait(_builders
+        .where((builder) =>
+            builder.buildExtensions.keys.any(buildStep.inputId.path.endsWith))
+        .map((builder) => builder.build(buildStep))
+        .whereType<Future<void>>());
+  }
+
+  /// Merges output extensions from all builders.
+  ///
+  /// If multiple builders declare the same output it will appear in this List
+  /// more than once. This should be considered an error.
+  @override
+  Map<String, List<String>> get buildExtensions =>
+      _mergeMaps(_builders.map((b) => b.buildExtensions));
+
+  @override
+  String toString() => '$_builders';
+}
+
+Map<String, List<String>> _mergeMaps(Iterable<Map<String, List<String>>> maps) {
+  var result = <String, List<String>>{};
+  for (var map in maps) {
+    for (var key in map.keys) {
+      result.putIfAbsent(key, () => []);
+      result[key].addAll(map[key]);
+    }
+  }
+  return result;
+}
diff --git a/build/lib/src/builder/post_process_build_step.dart b/build/lib/src/builder/post_process_build_step.dart
new file mode 100644
index 0000000..5fef55f
--- /dev/null
+++ b/build/lib/src/builder/post_process_build_step.dart
@@ -0,0 +1,79 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:convert';
+
+import 'package:async/async.dart';
+import 'package:build/build.dart';
+import 'package:crypto/src/digest.dart';
+
+// This is not exported to hack around a package-private constructor.
+PostProcessBuildStep postProcessBuildStep(
+        AssetId inputId,
+        AssetReader reader,
+        AssetWriter writer,
+        void Function(AssetId) addAsset,
+        void Function(AssetId) deleteAsset) =>
+    PostProcessBuildStep._(inputId, reader, writer, addAsset, deleteAsset);
+
+/// A simplified [BuildStep] which can only read its primary input, and can't
+/// get a [Resolver] or any [Resource]s, at least for now.
+class PostProcessBuildStep {
+  final AssetId inputId;
+
+  final AssetReader _reader;
+  final AssetWriter _writer;
+  final void Function(AssetId) _addAsset;
+  final void Function(AssetId) _deleteAsset;
+
+  /// The result of any writes which are starting during this step.
+  final _writeResults = <Future<Result<void>>>[];
+
+  PostProcessBuildStep._(this.inputId, this._reader, this._writer,
+      this._addAsset, this._deleteAsset);
+
+  Future<Digest> digest(AssetId id) => inputId == id
+      ? _reader.digest(id)
+      : Future.error(InvalidInputException(id));
+
+  Future<List<int>> readInputAsBytes() => _reader.readAsBytes(inputId);
+
+  Future<String> readInputAsString({Encoding encoding = utf8}) =>
+      _reader.readAsString(inputId, encoding: encoding);
+
+  Future<void> writeAsBytes(AssetId id, FutureOr<List<int>> bytes) {
+    _addAsset(id);
+    var done =
+        _futureOrWrite(bytes, (List<int> b) => _writer.writeAsBytes(id, b));
+    _writeResults.add(Result.capture(done));
+    return done;
+  }
+
+  Future<void> writeAsString(AssetId id, FutureOr<String> content,
+      {Encoding encoding = utf8}) {
+    _addAsset(id);
+    var done = _futureOrWrite(content,
+        (String c) => _writer.writeAsString(id, c, encoding: encoding));
+    _writeResults.add(Result.capture(done));
+    return done;
+  }
+
+  /// Marks an asset for deletion in the post process step.
+  void deletePrimaryInput() {
+    _deleteAsset(inputId);
+  }
+
+  /// Waits for work to finish and cleans up resources.
+  ///
+  /// This method should be called after a build has completed. After the
+  /// returned [Future] completes then all outputs have been written.
+  Future<void> complete() async {
+    await Future.wait(_writeResults.map(Result.release));
+  }
+}
+
+Future<void> _futureOrWrite<T>(
+        FutureOr<T> content, Future<void> write(T content)) =>
+    (content is Future<T>) ? content.then(write) : write(content as T);
diff --git a/build/lib/src/builder/post_process_builder.dart b/build/lib/src/builder/post_process_builder.dart
new file mode 100644
index 0000000..074f1d1
--- /dev/null
+++ b/build/lib/src/builder/post_process_builder.dart
@@ -0,0 +1,35 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'builder.dart';
+import 'post_process_build_step.dart';
+
+/// A builder which runes in a special phase at the end of the build.
+///
+/// They are different from a normal [Builder] in several ways:
+///
+/// - They don't have to declare output extensions, and can output any file as
+///   long as it doesn't conflict with an existing one.
+/// - They can only read their primary input.
+/// - They will not cause optional actions to run - they will only run on assets
+///   that were built as a part of the normal build.
+/// - They all run in a single phase, and thus can not see the outputs of any
+///   other [PostProcessBuilder]s.
+/// - Because they run in a separate phase, after other builders, none of thier
+///   outputs can be consumed by [Builder]s.
+///
+/// Because of these restrictions, these builders should never be used to output
+/// Dart files, or any other file which would should be processed by normal
+/// [Builder]s.
+abstract class PostProcessBuilder {
+  /// The extensions this builder expects for its inputs.
+  Iterable<String> get inputExtensions;
+
+  /// Generates the outputs and deletes for [buildStep].
+  FutureOr<void> build(PostProcessBuildStep buildStep);
+}
+
+typedef PostProcessBuilderFactory = PostProcessBuilder Function(BuilderOptions);
diff --git a/build/lib/src/generate/expected_outputs.dart b/build/lib/src/generate/expected_outputs.dart
new file mode 100644
index 0000000..9d3425a
--- /dev/null
+++ b/build/lib/src/generate/expected_outputs.dart
@@ -0,0 +1,28 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 '../asset/id.dart';
+import '../builder/builder.dart';
+
+/// Collects the expected AssetIds created by [builder] when given [input] based
+/// on the extension configuration.
+Iterable<AssetId> expectedOutputs(Builder builder, AssetId input) {
+  var matchingExtensions =
+      builder.buildExtensions.keys.where((e) => input.path.endsWith(e));
+  return matchingExtensions
+      .expand((e) => _replaceExtensions(input, e, builder.buildExtensions[e]));
+}
+
+Iterable<AssetId> _replaceExtensions(
+        AssetId assetId, String oldExtension, List<String> newExtensions) =>
+    newExtensions.map((n) => _replaceExtension(assetId, oldExtension, n));
+
+AssetId _replaceExtension(
+    AssetId assetId, String oldExtension, String newExtension) {
+  var path = assetId.path;
+  assert(path.endsWith(oldExtension));
+  return AssetId(
+      assetId.package,
+      path.replaceRange(
+          path.length - oldExtension.length, path.length, newExtension));
+}
diff --git a/build/lib/src/generate/run_builder.dart b/build/lib/src/generate/run_builder.dart
new file mode 100644
index 0000000..fbceb53
--- /dev/null
+++ b/build/lib/src/generate/run_builder.dart
@@ -0,0 +1,67 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'package:logging/logging.dart';
+
+import '../analyzer/resolver.dart';
+import '../asset/id.dart';
+import '../asset/reader.dart';
+import '../asset/writer.dart';
+import '../builder/build_step.dart';
+import '../builder/build_step_impl.dart';
+import '../builder/builder.dart';
+import '../builder/logging.dart';
+import '../resource/resource.dart';
+import 'expected_outputs.dart';
+
+/// Run [builder] with each asset in [inputs] as the primary input.
+///
+/// Builds for all inputs are run asynchronously and ordering is not guaranteed.
+/// The [log] instance inside the builds will be scoped to [logger] which is
+/// defaulted to a [Logger] name 'runBuilder'.
+///
+/// If a [resourceManager] is provided it will be used and it will not be
+/// automatically disposed of (its up to the caller to dispose of it later). If
+/// one is not provided then one will be created and disposed at the end of
+/// this function call.
+///
+/// If [reportUnusedAssetsForInput] is provided then all calls to
+/// `BuildStep.reportUnusedAssets` in [builder] will be forwarded to this
+/// function with the associated primary input.
+Future<void> runBuilder(Builder builder, Iterable<AssetId> inputs,
+    AssetReader reader, AssetWriter writer, Resolvers resolvers,
+    {Logger logger,
+    ResourceManager resourceManager,
+    String rootPackage,
+    StageTracker stageTracker = NoOpStageTracker.instance,
+    void Function(AssetId input, Iterable<AssetId> assets)
+        reportUnusedAssetsForInput}) async {
+  var shouldDisposeResourceManager = resourceManager == null;
+  resourceManager ??= ResourceManager();
+  logger ??= Logger('runBuilder');
+  //TODO(nbosch) check overlapping outputs?
+  Future<void> buildForInput(AssetId input) async {
+    var outputs = expectedOutputs(builder, input);
+    if (outputs.isEmpty) return;
+    var buildStep = BuildStepImpl(input, outputs, reader, writer,
+        rootPackage ?? input.package, resolvers, resourceManager,
+        stageTracker: stageTracker,
+        reportUnusedAssets: reportUnusedAssetsForInput == null
+            ? null
+            : (assets) => reportUnusedAssetsForInput(input, assets));
+    try {
+      await builder.build(buildStep);
+    } finally {
+      await buildStep.complete();
+    }
+  }
+
+  await scopeLogAsync(() => Future.wait(inputs.map(buildForInput)), logger);
+
+  if (shouldDisposeResourceManager) {
+    await resourceManager.disposeAll();
+    await resourceManager.beforeExit();
+  }
+}
diff --git a/build/lib/src/generate/run_post_process_builder.dart b/build/lib/src/generate/run_post_process_builder.dart
new file mode 100644
index 0000000..a4945fb
--- /dev/null
+++ b/build/lib/src/generate/run_post_process_builder.dart
@@ -0,0 +1,36 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'package:logging/logging.dart';
+import 'package:meta/meta.dart';
+
+import '../asset/id.dart';
+import '../asset/reader.dart';
+import '../asset/writer.dart';
+import '../builder/logging.dart';
+import '../builder/post_process_build_step.dart';
+import '../builder/post_process_builder.dart';
+
+/// Run [builder] with [inputId] as the primary input.
+///
+/// [addAsset] should update the build systems knowledge of what assets exist.
+/// If an asset should not be written this function should throw.
+/// [deleteAsset] should remove the asset from the build system, it will not be
+/// deleted on disk since the `writer` has no mechanism for delete.
+Future<void> runPostProcessBuilder(PostProcessBuilder builder, AssetId inputId,
+    AssetReader reader, AssetWriter writer, Logger logger,
+    {@required void Function(AssetId) addAsset,
+    @required void Function(AssetId) deleteAsset}) async {
+  await scopeLogAsync(() async {
+    var buildStep =
+        postProcessBuildStep(inputId, reader, writer, addAsset, deleteAsset);
+    try {
+      await builder.build(buildStep);
+    } finally {
+      await buildStep.complete();
+    }
+  }, logger);
+}
diff --git a/build/lib/src/resource/resource.dart b/build/lib/src/resource/resource.dart
new file mode 100644
index 0000000..8e786de
--- /dev/null
+++ b/build/lib/src/resource/resource.dart
@@ -0,0 +1,95 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+typedef CreateInstance<T> = FutureOr<T> Function();
+typedef DisposeInstance<T> = FutureOr<void> Function(T instance);
+typedef BeforeExit = FutureOr<void> Function();
+
+/// A [Resource] encapsulates the logic for creating and disposing of some
+/// expensive object which has a lifecycle.
+///
+/// Actual [Resource]s should be retrieved using `BuildStep#fetchResource`.
+///
+/// Build system implementations should be the only users that directly
+/// instantiate a [ResourceManager] since they can handle the lifecycle
+/// guarantees in a sane way.
+class Resource<T> {
+  /// Factory method which creates an instance of this resource.
+  final CreateInstance<T> _create;
+
+  /// Optional method which is given an existing instance that is ready to be
+  /// disposed.
+  final DisposeInstance<T> _userDispose;
+
+  /// Optional method which is called before the process is going to exit.
+  ///
+  /// This allows resources to do any final cleanup, and is not given an
+  /// instance.
+  final BeforeExit _userBeforeExit;
+
+  /// A Future instance of this resource if one has ever been requested.
+  final _instanceByManager = <ResourceManager, Future<T>>{};
+
+  Resource(this._create, {DisposeInstance<T> dispose, BeforeExit beforeExit})
+      : _userDispose = dispose,
+        _userBeforeExit = beforeExit;
+
+  /// Fetches an actual instance of this resource for [manager].
+  Future<T> _fetch(ResourceManager manager) =>
+      _instanceByManager.putIfAbsent(manager, () async => await _create());
+
+  /// Disposes the actual instance of this resource for [manager] if present.
+  Future<void> _dispose(ResourceManager manager) {
+    if (!_instanceByManager.containsKey(manager)) return Future.value(null);
+    var oldInstance = _fetch(manager);
+    _instanceByManager.remove(manager);
+    if (_userDispose != null) {
+      return oldInstance.then(_userDispose);
+    } else {
+      return Future.value(null);
+    }
+  }
+}
+
+/// Manages fetching and disposing of a group of [Resource]s.
+///
+/// This is an internal only API which should only be used by build system
+/// implementations and not general end users. Instead end users should use
+/// the `buildStep#fetchResource` method to get [Resource]s.
+class ResourceManager {
+  final _resources = Set<Resource<void>>();
+
+  /// The [Resource]s that we need to call `beforeExit` on.
+  ///
+  /// We have to hang on to these forever, but they should be small in number,
+  /// and we don't hold on to the actual created instances, just the [Resource]
+  /// instances.
+  final _resourcesWithBeforeExit = Set<Resource<void>>();
+
+  /// Fetches an instance of [resource].
+  Future<T> fetch<T>(Resource<T> resource) async {
+    if (resource._userBeforeExit != null) {
+      _resourcesWithBeforeExit.add(resource);
+    }
+    _resources.add(resource);
+    return resource._fetch(this);
+  }
+
+  /// Disposes of all [Resource]s fetched since the last call to [disposeAll].
+  Future<Null> disposeAll() {
+    var done = Future.wait(_resources.map((r) => r._dispose(this)));
+    _resources.clear();
+    return done.then((_) => null);
+  }
+
+  /// Invokes the `beforeExit` callbacks of all [Resource]s that had one.
+  Future<Null> beforeExit() async {
+    await Future.wait(_resourcesWithBeforeExit.map((r) async {
+      if (r._userBeforeExit != null) return r._userBeforeExit();
+    }));
+    _resourcesWithBeforeExit.clear();
+  }
+}
diff --git a/build/mono_pkg.yaml b/build/mono_pkg.yaml
new file mode 100644
index 0000000..dc1b387
--- /dev/null
+++ b/build/mono_pkg.yaml
@@ -0,0 +1,20 @@
+dart:
+  - dev
+
+stages:
+  - analyze_and_format:
+    - group:
+      - dartfmt: sdk
+      - dartanalyzer: --fatal-infos --fatal-warnings .
+    - dartanalyzer: --fatal-warnings .
+      dart:
+        - 2.3.0
+  - unit_test:
+    - command: pub run build_runner test
+      os:
+        - linux
+        - windows
+
+cache:
+  directories:
+  - .dart_tool/build
diff --git a/build/pubspec.yaml b/build/pubspec.yaml
new file mode 100644
index 0000000..dbbe0c0
--- /dev/null
+++ b/build/pubspec.yaml
@@ -0,0 +1,26 @@
+name: build
+version: 1.2.2
+description: A build system for Dart.
+author: Dart Team <misc@dartlang.org>
+homepage: https://github.com/dart-lang/build/tree/master/build
+
+environment:
+  sdk: ">=2.3.0 <3.0.0"
+
+dependencies:
+  analyzer: '>=0.35.0 <0.40.0'
+  async: ">=1.13.3 <3.0.0"
+  convert: ^2.0.0
+  crypto: ">=0.9.2 <3.0.0"
+  logging: ^0.11.2
+  meta: ^1.1.0
+  path: ^1.1.0
+  glob: ^1.1.0
+
+dev_dependencies:
+  build_resolvers: ^1.0.0
+  build_runner: ^1.0.0
+  build_test: ^0.10.0
+  build_vm_compilers: ">=0.1.0 <2.0.0"
+  pedantic: ^1.0.0
+  test: ^1.2.0
diff --git a/build_config/BUILD.gn b/build_config/BUILD.gn
new file mode 100644
index 0000000..2b434b9
--- /dev/null
+++ b/build_config/BUILD.gn
@@ -0,0 +1,22 @@
+# This file is generated by importer.py for build_config-0.4.2
+
+import("//build/dart/dart_library.gni")
+
+dart_library("build_config") {
+  package_name = "build_config"
+
+  # This parameter is left empty as we don't care about analysis or exporting
+  # these sources outside of the tree.
+  sources = []
+
+  disable_analysis = true
+
+  deps = [
+    "//third_party/dart-pkg/pub/yaml",
+    "//third_party/dart-pkg/pub/meta",
+    "//third_party/dart-pkg/pub/json_annotation",
+    "//third_party/dart-pkg/pub/pubspec_parse",
+    "//third_party/dart-pkg/pub/path",
+    "//third_party/dart-pkg/pub/checked_yaml",
+  ]
+}
diff --git a/build_config/CHANGELOG.md b/build_config/CHANGELOG.md
new file mode 100644
index 0000000..80d5976
--- /dev/null
+++ b/build_config/CHANGELOG.md
@@ -0,0 +1,174 @@
+## 0.4.2
+
+- Add support for an `auto_apply_builders` option to the `target` config.
+  - Defaults to `true` (the previous behavior), setting it to `false`
+    means all builders have to be explicitly enabled. 
+
+## 0.4.1+1
+
+- Support the latest release of `package:json_annotation`.
+- Increased the lower bound for the Dart SDK to `>=2.3.0`.
+
+## 0.4.1
+
+- Added optional `configYamlPath` parameter to `BuildConfig.parse`. When
+  provided, errors reported when parsing build configuration will include
+  the file path.
+
+## 0.4.0
+
+- Breaking for build systems - change type of `BuilderOptions` fields to
+  `Map<String, dynamic>` to drop dependency on `build`. Does not impact packages
+  only depending on `build.yaml` parsing.
+- Breaking for build systems - versioning scheme is changing to match
+  `package:build`. Changes which are breaking to _users_ - those with
+  `build.yaml` files will be indicated with a breaking major version bump.
+  Changed which are breaking to build system _implementors_ - those who use the
+  Dart API for this package, will be indicated with a minor version bump.
+
+## 0.3.2
+
+- Add an explicit error when `buildExtensions` is configured to overwrite it's
+  input.
+- Add an explicit error when an `InputSet` has an empty or null value in a glob
+  list.
+- Increase lower bound SDK constraint to 2.0.0.
+- Normalize builder keys with the legacy `|` separator to use `:` instead.
+
+## 0.3.1+4
+
+- Support the latest `package:json_annotation`.
+
+## 0.3.1+3
+
+- Support `package:build` version `1.x.x`.
+
+## 0.3.1+2
+
+- Support `package:json_annotation` v1.
+
+## 0.3.1+1
+
+- Increased the upper bound for the sdk to `<3.0.0`.
+
+## 0.3.1
+
+- Improve validation and errors when parsing `build.yaml`.
+- Add `BuildConfig.globalOptions` support.
+
+## 0.3.0
+
+- Parsing of `build.yaml` files is now done with the `json_serializable` package
+  and is Dart 2 compatible.
+
+  - The error reporting will be a bit different, but generally should be better,
+    and will include the yaml spans of the problem sections.
+
+### Breaking Changes
+
+There are no changes to the `build.yaml` format, the following changes only
+affect the imperative apis of this package.
+
+- The Constructors for most of the build config classes other than `BuildConfig`
+  itself now have to be ran inside a build config zone, which can be done using
+  the `runInBuildConfigZone` function. This gives the context about what package
+  is currently being parsed, as well as what the default dependencies should be
+  for targets.
+- Many constructor signatures have changed, for the most part removing the
+  `package` parameter (it is now read off the zone).
+
+## 0.2.6+2
+
+- Restore error for missing default target.
+
+## 0.2.6+1
+
+- Restore error for missing build extensions.
+
+## 0.2.6
+
+- The `target` and `build_extensions` keys for builder definitions are now
+  optional and should be omitted in most cases since they are currently unused.
+- Support options based on mode, add `devOptions` and `releaseOptions` on
+  `TargetBuilderConfig`.
+- Support applying default options based on builder definitions, add `option`,
+  `devOptions`, and `releaseOptions` to `TargetBuilderConfigDefaults`.
+- Ensure that `defaults` and `generateFor` fields are never null.
+- Add `InputSet.anything` to name the input sets that don't filter out any
+  assets.
+
+## 0.2.5
+
+- Added `post_process_builders` section to `build.yaml`. See README.md for more
+  information.-dev
+- Adds support for `$default` as a _dependency_, i.e.:
+
+```yaml
+targets:
+  $default:
+    ...
+  foo:
+    dependencies:
+      - $default
+```
+
+## 0.2.4
+
+- Add support for `runs_before` in `BuilderDefinition`.
+
+## 0.2.3
+
+- Expose key normalization methods publicly, these include:
+  - `normalizeBuilderKeyUsage`
+  - `normalizeTargetKeyUsage`
+
+## 0.2.2+1
+
+- Expand support for `package:build` to include version `0.12.0`.
+
+## 0.2.2
+
+- **Bug fix**: Empty build.yaml files no longer fail to parse.
+- Allow `$default` as a target name to get he package name automatically filled
+  in.
+
+## 0.2.1
+
+- Change the default for `BuilderDefinition.buildTo` to `BuildTo.cache`.
+  Builders which want to operate on the source tree will need to explicitly opt
+  in. Allow this regardless of the value of `autoApply` and the build system
+  will need to filter out the builders that can't run.
+- By default including any configuration for a Builder within a BuildTarget will
+  enabled that builder.
+
+## 0.2.0
+
+- Add `build_to` option to Builder configuration.
+- Add `BuildConfig.fromBuildConfigDir` for cases where the package name and
+  dependencies are already known.
+- Add `TargetBuilderConfig` class to configure builders applied to specific
+  targets.
+- Add `TargetBuilderConfigDefaults` class for Builder authors to provide default
+  configuration.
+- Add `InputSet` and change `sources` and `generate_for` to use it.
+- Remove `BuildTarget.isDefault` and related config parsing. The default will be
+  determined by the target which matches the package name.
+- Normalize Target and Builder names so they are scoped to the package they are
+  defined in.
+
+### Breaking
+
+- Remove `BuildConfigSet` class. This was unused.
+- Hide `Pubspec` class. Construct `BuildConfig` instances with a package path
+  rather than an already created `Pubspec` instance.
+
+## 0.1.1
+
+- Add `auto_apply` option to Builder configuration.
+- Add `required_inputs` option to Builder configuration.
+- Add `is_optional` option to Builder configuration.
+
+## 0.1.0
+
+- Initial release - pulled from `package:dazel`. Updated to support
+  `build_extensions` instead of `input_extension` and `output_extensions`.
diff --git a/build_config/LICENSE b/build_config/LICENSE
new file mode 100644
index 0000000..389ce98
--- /dev/null
+++ b/build_config/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2017, the Dart project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of Google Inc. nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/build_config/README.md b/build_config/README.md
new file mode 100644
index 0000000..e759cf4
--- /dev/null
+++ b/build_config/README.md
@@ -0,0 +1,239 @@
+# Customizing builds
+
+Customizing the build behavior of a package is done  by creating a `build.yaml`
+file, which describes your configuration.
+
+The full format is described in the
+[docs/build_yaml_format.md](https://github.com/dart-lang/build/blob/master/docs/build_yaml_format.md)
+file, while this documentation is more focused on specific usage scenarios of
+the file.
+
+## Dividing a package into Build targets
+
+When a `Builder` should be applied to a subset of files in a package the package
+can be broken up into multiple 'targets'. Targets are configured in the
+`targets` section of the `build.yaml`. The key for each target makes up the name
+for that target. Targets can be referred to in
+`'$definingPackageName:$targetname'`. When the target name matches the package
+name it can also be referred to as just the package name. One target in every
+package _must_ use the package name so that consumers will use it by default.
+In the `build.yaml` file this target can be defined with the key `$default` or
+with the name of the package.
+
+Each target may also contain the following keys:
+
+- **sources**: List of Strings or Map, Optional. The set of files within the
+  package which make up this target. Files are specified using glob syntax. If a
+  List of Strings is used they are considered the 'include' globs. If a Map is
+  used can only have the keys `include` and `exclude`. Any file which matches
+  any glob in `include` and no globs in `exclude` is considered a source of the
+  target. When `include` is omitted every file is considered a match.
+- **dependencies**: List of Strings, Optional. The targets that this target
+  depends on. Strings in the format `'$packageName:$targetName'` to depend on a
+  target within a package or `$packageName` to depend on a package's default
+  target. By default this is all of the package names this package depends on
+  (from the `pubspec.yaml`).
+- **builders**: Map, Optional. See "configuring builders" below.
+
+## Configuring `Builder`s applied to your package
+Each target can specify a `builders` key which configures the builders which are
+applied to that target. The value is a Map from builder to configuration for
+that builder. The key is in the format `'$packageName:$builderName'`. The
+configuration may have the following keys:
+
+- **enabled**: Boolean, Optional: Whether to apply the builder to this target.
+  Omit this key if you want the default behavior based on the builder's
+  `auto_apply` configuration. Builders which are manually applied
+  (`auto_apply: none`) are only ever used when there is a target specifying the
+  builder with `enabled: True`.
+- **generate_for**: List of String or Map, Optional:. The subset of files within
+  the target's `sources` which should have this Builder applied. See `sources`
+  configuration above for how to configure this.
+- **options**: Map, Optional: A free-form map which will be passed to the
+  `Builder` as a `BuilderOptions` when it is constructed. Usage varies depending
+  on the particular builder. Values in this map will override the default
+  provided by builder authors. Values may also be overridden based on the build
+  mode with `dev_options` or `release_options`.
+- **dev_options**: Map, Optional: A free-form map which will be passed to the
+  `Builder` as a `BuilderOptions` when it is constructed. Usage varies depending
+  on the particular builder. The values in this map override Builder defaults or
+  non mode-specific options per-key when the build is done in dev mode.
+- **release_options**: Map, Optional: A free-form map which will be passed to
+  the `Builder` as a `BuilderOptions` when it is constructed. Usage varies
+  depending on the particular builder. The values in this map override Builder
+  defaults or non mode-specific options when the build is done in release mode.
+
+## Configuring `Builder`s globally
+Target level builder options can be overridden globally across all packages with
+the `global_options` section. These options are applied _after_ all Builder
+defaults and target level configuration, and _before_ `--define` command line
+arguments.
+
+- **options**: Map, Optional: A free-form map which will be passed to the
+  `Builder` as a `BuilderOptions` when it is constructed. Usage varies depending
+  on the particular builder. Values in this map will override the default
+  provided by builder authors or at the target level. Values may also be
+  overridden based on the build mode with `dev_options` or `release_options`.
+- **dev_options**: Map, Optional: A free-form map which will be passed to the
+  `Builder` as a `BuilderOptions` when it is constructed. Usage varies depending
+  on the particular builder. The values in this map override all other values
+  per-key when the build is done in dev mode.
+- **release_options**: Map, Optional: A free-form map which will be passed to
+  the `Builder` as a `BuilderOptions` when it is constructed. Usage varies
+  depending on the particular builder. The values in this map override all other
+  values per-key when the build is done in release mode.
+
+## Defining `Builder`s to apply to dependents
+
+If users of your package need to apply some code generation to their package,
+then you can define `Builder`s and have those applied to packages with a
+dependency on yours.
+
+The key for a Builder will be normalized so that consumers of the builder can
+refer to it in `'$definingPackageName:$builderName'` format. If the builder name
+matches the package name it can also be referred to with just the package name.
+
+Exposed `Builder`s are configured in the `builders` section of the `build.yaml`.
+This is a map of builder names to configuration. Each builder config may contain
+the following keys:
+
+- **import**: Required. The import uri that should be used to import the library
+  containing the `Builder` class. This should always be a `package:` uri.
+- **builder_factories**: A `List<String>` which contains the names of the
+  top-level methods in the imported library which are a function fitting the
+  typedef `Builder factoryName(BuilderOptions options)`.
+- **build_extensions**: Required. A map from input extension to the list of
+  output extensions that may be created for that input. This must match the
+  merged `buildExtensions` maps from each `Builder` in `builder_factories`.
+- **auto_apply**: Optional. The packages which should have this builder
+  automatically to applied. Defaults to `'none'` The possibilities are:
+  - `"none"`: Never apply this Builder unless it is manually configured
+  - `"dependents"`: Apply this Builder to the package with a direct dependency
+    on the package exposing the builder.
+  - `"all_packages"`: Apply this Builder to all packages in the transitive
+    dependency graph.
+  - `"root_package"`: Apply this Builder only to the top-level package.
+- **required_inputs**: Optional, see [adjusting builder ordering][]
+- **runs_before**: Optional, see [adjusting builder ordering][]
+- **applies_builders**: Optional, list of Builder keys. Specifies that other
+  builders should be run on any target which will run this Builder.
+- **is_optional**: Optional, boolean. Specifies whether a Builder can be run
+  lazily, such that it won't execute until one of it's outputs is requested by a
+  later Builder. This option should be rare. Defaults to `False`.
+- **build_to**: Optional. The location that generated assets should be output
+  to. The possibilities are:
+  - `"source"`: Outputs go to the source tree next to their primary inputs.
+  - `"cache"`: Outputs go to a hidden build cache and won't be published.
+  The default is "cache". If a Builder specifies that it outputs to "source" it
+  will never run on any package other than the root - but does not necessarily
+  need to use the "root_package" value for "auto_apply". If it would otherwise
+  run on a non-root package it will be filtered out.
+- **defaults**: Optional: Default values to apply when a user does not specify
+  the corresponding key in their `builders` section. May contain the following
+  keys:
+  - **generate_for**: A list of globs that this Builder should run on as a
+    subset of the corresponding target, or a map with `include` and `exclude`
+    lists of globs.
+  - **options**: Arbitrary yaml map, provided as the `config` map in
+    `BuilderOptions` to the `BuilderFactory` for this builder. Individual keys
+    will be overridden by configuration provided in either `dev_options` or
+    `release_options` based on the build mode, and then overridden by any user
+    specified configuration.
+  - **dev_options**: Arbitrary yaml map. Values will replace the defaults from
+    `options` when the build is done in dev mode (the default mode).
+  - **release_options**: Arbitrary yaml map. Values will replace the defaults
+    from `options` when the build is done in release mode (with `--release`).
+
+Example `builders` config:
+
+```yaml
+builders:
+  my_builder:
+    import: "package:my_package/builder.dart"
+    builder_factories: ["myBuilder"]
+    build_extensions: {".dart": [".my_package.dart"]}
+    auto_apply: dependents
+    defaults:
+      release_options:
+        some_key: "Some value the users will want in release mode"
+```
+
+## Defining `PostProcessBuilder`s
+
+`PostProcessBuilder`s are configured similarly to normal `Builder`s, but they
+have some different/missing options.
+
+These builders can not be auto-applied on their own, and must always build to
+cache because their outputs are not declared ahead of time. To apply them a
+user will need to explicitly enable them on a target, or a `Builder` definition
+can add them to `apply_builders`.
+
+Exposed `PostProcessBuilder`s are configured in the `post_process_builders`
+section of the  `build.yaml`. This is a map of builder names to configuration.
+Each post process builder config may contain the following keys:
+
+- **import**: Required. The import uri that should be used to import the library
+  containing the `Builder` class. This should always be a `package:` uri.
+- **builder_factory**: A `String` which contains the name of the top-level
+  method in the imported library which is a function fitting the
+  typedef `PostProcessBuilder factoryName(BuilderOptions options)`.
+- **input_extensions**: Required. A list of input extensions that will be
+  processed. This must match the `inputExtensions` from the `PostProcessBuilder`
+  returned by the `builder_factory`.
+- **defaults**: Optional: Default values to apply when a user does not specify
+  the corresponding key in their `builders` section. May contain the following
+  keys:
+  - **generate_for**: A list of globs that this Builder should run on as a
+    subset of the corresponding target, or a map with `include` and `exclude`
+    lists of globs.
+
+Example config with a normal `builder` which auto-applies a
+`post_process_builder`:
+
+```yaml
+builders:
+  # The regular builder config, creates `.tar.gz` files.
+  regular_builder:
+    import: "package:my_package/builder.dart"
+    builder_factories: ["myBuilder"]
+    build_extensions: {".dart": [".tar.gz"]}
+    auto_apply: dependents
+    apply_builders: [":archive_extract_builder"]
+post_process_builders:
+  # The post process builder config, extracts `.tar.gz` files.
+  extract_archive_builder:
+    import: "package:my_package/extract_archive_builder.dart"
+    builder_factory: "myExtractArchiveBuilder"
+    input_extensions: [".tar.gz"]
+```
+
+[adjusting builder ordering]: #adjusting-builder-ordering
+
+### Adjusting Builder Ordering
+
+Both `required_inputs` and `runs_before` can be used to tweak the order that
+Builders run in on a given target. These work by indicating a given builder is a
+_dependency_ of another. The resulting dependency graph must not have cycles and
+these options should be used rarely.
+
+- **required_inputs**: Optional, list of extensions, defaults to empty list. If
+  a Builder must see every input with one or more file extensions they can be
+  specified here and it will be guaranteed to run after any Builder which might
+  produce an output of that type. For instance a compiler must run after any
+  Builder which can produce `.dart` outputs or those libraries can't be
+  compiled. A Builder may not specify that it requires an output that it also
+  produces since this would be a self-cycle.
+- **runs_before**: Optional, list of Builder keys. If a Builder is producing
+  outputs which are intended to be inputs to other Builders they may be
+  specified here. This guarantees that the specified Builders will be ordered
+  later than this one. This will not cause Builders to be applied if they would
+  not otherwise run, it only affects ordering.
+
+# Publishing `build.yaml` files
+
+`build.yaml` configuration should be published to pub with the package and
+checked in to source control. Whenever a package is published with a
+`build.yaml` it should mark a `dependency` on `build_config` to ensure that
+the package consuming the config has a compatible version. Breaking version
+changes which do not impact the configuration file format will be clearly marked
+in the changelog.
diff --git a/build_config/build.yaml b/build_config/build.yaml
new file mode 100644
index 0000000..2f30635
--- /dev/null
+++ b/build_config/build.yaml
@@ -0,0 +1,7 @@
+targets:
+  $default:
+    builders:
+      json_serializable:
+        options:
+          any_map: true
+          checked: true
diff --git a/build_config/lib/build_config.dart b/build_config/lib/build_config.dart
new file mode 100644
index 0000000..c2a6553
--- /dev/null
+++ b/build_config/lib/build_config.dart
@@ -0,0 +1,18 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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.
+
+export 'src/build_config.dart' show BuildConfig;
+export 'src/build_target.dart'
+    show BuildTarget, TargetBuilderConfig, GlobalBuilderConfig;
+export 'src/builder_definition.dart'
+    show
+        BuilderDefinition,
+        AutoApply,
+        BuildTo,
+        PostProcessBuilderDefinition,
+        TargetBuilderConfigDefaults;
+export 'src/common.dart' show runInBuildConfigZone;
+export 'src/input_set.dart' show InputSet;
+export 'src/key_normalization.dart'
+    show normalizeBuilderKeyUsage, normalizeTargetKeyUsage;
diff --git a/build_config/lib/src/build_config.dart b/build_config/lib/src/build_config.dart
new file mode 100644
index 0000000..ac3f903
--- /dev/null
+++ b/build_config/lib/src/build_config.dart
@@ -0,0 +1,193 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:io';
+
+import 'package:checked_yaml/checked_yaml.dart';
+import 'package:json_annotation/json_annotation.dart';
+import 'package:meta/meta.dart';
+import 'package:path/path.dart' as p;
+import 'package:pubspec_parse/pubspec_parse.dart';
+
+import 'build_target.dart';
+import 'builder_definition.dart';
+import 'common.dart';
+import 'expandos.dart';
+import 'input_set.dart';
+import 'key_normalization.dart';
+
+part 'build_config.g.dart';
+
+/// The parsed values from a `build.yaml` file.
+@JsonSerializable(createToJson: false, disallowUnrecognizedKeys: true)
+class BuildConfig {
+  /// Returns a parsed [BuildConfig] file in [path], if one exist, otherwise a
+  /// default config.
+  ///
+  /// [path] must be a directory which contains a `pubspec.yaml` file and
+  /// optionally a `build.yaml`.
+  static Future<BuildConfig> fromPackageDir(String path) async {
+    final pubspec = await _fromPackageDir(path);
+    return fromBuildConfigDir(pubspec.name, pubspec.dependencies.keys, path);
+  }
+
+  /// Returns a parsed [BuildConfig] file in [path], if one exists, otherwise a
+  /// default config.
+  ///
+  /// [path] should the path to a directory which may contain a `build.yaml`.
+  static Future<BuildConfig> fromBuildConfigDir(
+      String packageName, Iterable<String> dependencies, String path) async {
+    final configPath = p.join(path, 'build.yaml');
+    final file = File(configPath);
+    if (await file.exists()) {
+      return BuildConfig.parse(
+        packageName,
+        dependencies,
+        await file.readAsString(),
+        configYamlPath: file.path,
+      );
+    } else {
+      return BuildConfig.useDefault(packageName, dependencies);
+    }
+  }
+
+  @JsonKey(ignore: true)
+  final String packageName;
+
+  /// All the `builders` defined in a `build.yaml` file.
+  @JsonKey(name: 'builders')
+  final Map<String, BuilderDefinition> builderDefinitions;
+
+  /// All the `post_process_builders` defined in a `build.yaml` file.
+  @JsonKey(name: 'post_process_builders')
+  final Map<String, PostProcessBuilderDefinition> postProcessBuilderDefinitions;
+
+  /// All the `targets` defined in a `build.yaml` file.
+  @JsonKey(name: 'targets', fromJson: _buildTargetsFromJson)
+  final Map<String, BuildTarget> buildTargets;
+
+  @JsonKey(name: 'global_options')
+  final Map<String, GlobalBuilderConfig> globalOptions;
+
+  /// The default config if you have no `build.yaml` file.
+  factory BuildConfig.useDefault(
+      String packageName, Iterable<String> dependencies) {
+    return runInBuildConfigZone(() {
+      final key = '$packageName:$packageName';
+      final target = BuildTarget(
+        dependencies: dependencies
+            .map((dep) => normalizeTargetKeyUsage(dep, packageName))
+            .toList(),
+        sources: InputSet.anything,
+      );
+      return BuildConfig(
+        packageName: packageName,
+        buildTargets: {key: target},
+        globalOptions: {},
+      );
+    }, packageName, dependencies.toList());
+  }
+
+  /// Create a [BuildConfig] by parsing [configYaml].
+  ///
+  /// If [configYamlPath] is passed, it's used as the URL from which
+  /// [configYaml] for error reporting.
+  factory BuildConfig.parse(
+    String packageName,
+    Iterable<String> dependencies,
+    String configYaml, {
+    String configYamlPath,
+  }) {
+    try {
+      return checkedYamlDecode(
+        configYaml,
+        (map) =>
+            BuildConfig.fromMap(packageName, dependencies, map ?? const {}),
+        allowNull: true,
+        sourceUrl: configYamlPath,
+      );
+    } on ParsedYamlException catch (e) {
+      throw ArgumentError(e.formattedMessage);
+    }
+  }
+
+  /// Create a [BuildConfig] read a map which was already parsed.
+  factory BuildConfig.fromMap(
+      String packageName, Iterable<String> dependencies, Map config) {
+    return runInBuildConfigZone(() => BuildConfig._fromJson(config),
+        packageName, dependencies.toList());
+  }
+
+  BuildConfig({
+    String packageName,
+    @required Map<String, BuildTarget> buildTargets,
+    Map<String, GlobalBuilderConfig> globalOptions,
+    Map<String, BuilderDefinition> builderDefinitions,
+    Map<String, PostProcessBuilderDefinition> postProcessBuilderDefinitions =
+        const {},
+  })  : buildTargets = buildTargets ??
+            {
+              _defaultTarget(packageName ?? currentPackage): BuildTarget(
+                dependencies: currentPackageDefaultDependencies,
+              )
+            },
+        globalOptions = (globalOptions ?? const {}).map((key, config) =>
+            MapEntry(normalizeBuilderKeyUsage(key, currentPackage), config)),
+        builderDefinitions = _normalizeBuilderDefinitions(
+            builderDefinitions ?? const {}, packageName ?? currentPackage),
+        postProcessBuilderDefinitions = _normalizeBuilderDefinitions(
+            postProcessBuilderDefinitions ?? const {},
+            packageName ?? currentPackage),
+        packageName = packageName ?? currentPackage {
+    // Set up the expandos for all our build targets and definitions so they
+    // can know which package and builder key they refer to.
+    this.buildTargets.forEach((key, target) {
+      packageExpando[target] = this.packageName;
+      builderKeyExpando[target] = key;
+    });
+    this.builderDefinitions.forEach((key, definition) {
+      packageExpando[definition] = this.packageName;
+      builderKeyExpando[definition] = key;
+    });
+    this.postProcessBuilderDefinitions.forEach((key, definition) {
+      packageExpando[definition] = this.packageName;
+      builderKeyExpando[definition] = key;
+    });
+  }
+
+  factory BuildConfig._fromJson(Map json) => _$BuildConfigFromJson(json);
+}
+
+String _defaultTarget(String package) => '$package:$package';
+
+Map<String, T> _normalizeBuilderDefinitions<T>(
+        Map<String, T> builderDefinitions, String packageName) =>
+    builderDefinitions.map((key, definition) =>
+        MapEntry(normalizeBuilderKeyDefinition(key, packageName), definition));
+
+Map<String, BuildTarget> _buildTargetsFromJson(Map json) {
+  if (json == null) {
+    return null;
+  }
+  var targets = json.map((key, target) => MapEntry(
+      normalizeTargetKeyDefinition(key as String, currentPackage),
+      BuildTarget.fromJson(target as Map)));
+
+  if (!targets.containsKey(_defaultTarget(currentPackage))) {
+    throw ArgumentError('Must specify a target with the name '
+        '`$currentPackage` or `\$default`.');
+  }
+
+  return targets;
+}
+
+Future<Pubspec> _fromPackageDir(String path) async {
+  final pubspec = p.join(path, 'pubspec.yaml');
+  final file = File(pubspec);
+  if (await file.exists()) {
+    return Pubspec.parse(await file.readAsString());
+  }
+  throw FileSystemException('No file found', p.absolute(pubspec));
+}
diff --git a/build_config/lib/src/build_config.g.dart b/build_config/lib/src/build_config.g.dart
new file mode 100644
index 0000000..dbbe12b
--- /dev/null
+++ b/build_config/lib/src/build_config.g.dart
@@ -0,0 +1,52 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'build_config.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+BuildConfig _$BuildConfigFromJson(Map json) {
+  return $checkedNew('BuildConfig', json, () {
+    $checkKeys(json, allowedKeys: const [
+      'builders',
+      'post_process_builders',
+      'targets',
+      'global_options'
+    ]);
+    final val = BuildConfig(
+      buildTargets: $checkedConvert(
+          json, 'targets', (v) => _buildTargetsFromJson(v as Map)),
+      globalOptions: $checkedConvert(
+          json,
+          'global_options',
+          (v) => (v as Map)?.map(
+                (k, e) => MapEntry(k as String,
+                    e == null ? null : GlobalBuilderConfig.fromJson(e as Map)),
+              )),
+      builderDefinitions: $checkedConvert(
+          json,
+          'builders',
+          (v) => (v as Map)?.map(
+                (k, e) => MapEntry(k as String,
+                    e == null ? null : BuilderDefinition.fromJson(e as Map)),
+              )),
+      postProcessBuilderDefinitions: $checkedConvert(
+          json,
+          'post_process_builders',
+          (v) => (v as Map)?.map(
+                (k, e) => MapEntry(
+                    k as String,
+                    e == null
+                        ? null
+                        : PostProcessBuilderDefinition.fromJson(e as Map)),
+              )),
+    );
+    return val;
+  }, fieldKeyMap: const {
+    'buildTargets': 'targets',
+    'globalOptions': 'global_options',
+    'builderDefinitions': 'builders',
+    'postProcessBuilderDefinitions': 'post_process_builders'
+  });
+}
diff --git a/build_config/lib/src/build_target.dart b/build_config/lib/src/build_target.dart
new file mode 100644
index 0000000..740a048
--- /dev/null
+++ b/build_config/lib/src/build_target.dart
@@ -0,0 +1,163 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'package:json_annotation/json_annotation.dart';
+
+import 'builder_definition.dart';
+import 'common.dart';
+import 'expandos.dart';
+import 'input_set.dart';
+import 'key_normalization.dart';
+
+part 'build_target.g.dart';
+
+@JsonSerializable(createToJson: false, disallowUnrecognizedKeys: true)
+class BuildTarget {
+  @JsonKey(name: 'auto_apply_builders')
+  final bool autoApplyBuilders;
+
+  /// A map from builder key to the configuration used for this target.
+  ///
+  /// Builder keys are in the format `"$package|$builder"`. This does not
+  /// represent the full set of builders that are applied to the target, only
+  /// those which have configuration customized against the default.
+  final Map<String, TargetBuilderConfig> builders;
+
+  final List<String> dependencies;
+
+  final InputSet sources;
+
+  /// A unique key for this target in `'$package:$target'` format.
+  String get key => builderKeyExpando[this];
+
+  String get package => packageExpando[this];
+
+  BuildTarget({
+    bool autoApplyBuilders,
+    InputSet sources,
+    Iterable<String> dependencies,
+    Map<String, TargetBuilderConfig> builders,
+  })  : autoApplyBuilders = autoApplyBuilders ?? true,
+        dependencies = (dependencies ?? currentPackageDefaultDependencies)
+            .map((d) => normalizeTargetKeyUsage(d, currentPackage))
+            .toList(),
+        builders = (builders ?? const {}).map((key, config) =>
+            MapEntry(normalizeBuilderKeyUsage(key, currentPackage), config)),
+        sources = sources ?? InputSet.anything;
+
+  factory BuildTarget.fromJson(Map json) => _$BuildTargetFromJson(json);
+
+  @override
+  String toString() => {
+        'package': package,
+        'sources': sources,
+        'dependencies': dependencies,
+        'builders': builders,
+        'autoApplyBuilders': autoApplyBuilders,
+      }.toString();
+}
+
+/// The configuration a particular [BuildTarget] applies to a Builder.
+///
+/// Build targets may have builders applied automatically based on
+/// [BuilderDefinition.autoApply] and may override with more specific
+/// configuration.
+@JsonSerializable(createToJson: false, disallowUnrecognizedKeys: true)
+class TargetBuilderConfig {
+  /// Overrides the setting of whether the Builder would run on this target.
+  ///
+  /// Builders may run on this target by default based on the `apply_to`
+  /// argument, set to `false` to disable a Builder which would otherwise run.
+  ///
+  /// By default including a config for a Builder enables that builder.
+  @JsonKey(name: 'enabled')
+  final bool isEnabled;
+
+  /// Sources to use as inputs for this Builder in glob format.
+  ///
+  /// This is always a subset of the `include` argument in the containing
+  /// [BuildTarget]. May be `null` in which cases it will be all the sources in
+  /// the target.
+  @JsonKey(name: 'generate_for')
+  final InputSet generateFor;
+
+  /// The options to pass to the `BuilderFactory` when constructing this
+  /// builder.
+  ///
+  /// The `options` key in the configuration.
+  ///
+  /// Individual keys may be overridden by either [devOptions] or
+  /// [releaseOptions].
+  final Map<String, dynamic> options;
+
+  /// Overrides for [options] in dev mode.
+  @JsonKey(name: 'dev_options')
+  final Map<String, dynamic> devOptions;
+
+  /// Overrides for [options] in release mode.
+  @JsonKey(name: 'release_options')
+  final Map<String, dynamic> releaseOptions;
+
+  TargetBuilderConfig({
+    bool isEnabled,
+    this.generateFor,
+    Map<String, dynamic> options,
+    Map<String, dynamic> devOptions,
+    Map<String, dynamic> releaseOptions,
+  })  : isEnabled = isEnabled ?? true,
+        options = options ?? const {},
+        devOptions = devOptions ?? const {},
+        releaseOptions = releaseOptions ?? const {};
+
+  factory TargetBuilderConfig.fromJson(Map json) =>
+      _$TargetBuilderConfigFromJson(json);
+
+  @override
+  String toString() => {
+        'isEnabled': isEnabled,
+        'generateFor': generateFor,
+        'options': options,
+        'devOptions': devOptions,
+        'releaseOptions': releaseOptions,
+      }.toString();
+}
+
+/// The configuration for a Builder applied globally.
+@JsonSerializable(createToJson: false, disallowUnrecognizedKeys: true)
+class GlobalBuilderConfig {
+  /// The options to pass to the `BuilderFactory` when constructing this
+  /// builder.
+  ///
+  /// The `options` key in the configuration.
+  ///
+  /// Individual keys may be overridden by either [devOptions] or
+  /// [releaseOptions].
+  final Map<String, dynamic> options;
+
+  /// Overrides for [options] in dev mode.
+  @JsonKey(name: 'dev_options')
+  final Map<String, dynamic> devOptions;
+
+  /// Overrides for [options] in release mode.
+  @JsonKey(name: 'release_options')
+  final Map<String, dynamic> releaseOptions;
+
+  GlobalBuilderConfig({
+    Map<String, dynamic> options,
+    Map<String, dynamic> devOptions,
+    Map<String, dynamic> releaseOptions,
+  })  : options = options ?? const {},
+        devOptions = devOptions ?? const {},
+        releaseOptions = releaseOptions ?? const {};
+
+  factory GlobalBuilderConfig.fromJson(Map json) =>
+      _$GlobalBuilderConfigFromJson(json);
+
+  @override
+  String toString() => {
+        'options': options,
+        'devOptions': devOptions,
+        'releaseOptions': releaseOptions,
+      }.toString();
+}
diff --git a/build_config/lib/src/build_target.g.dart b/build_config/lib/src/build_target.g.dart
new file mode 100644
index 0000000..44143e2
--- /dev/null
+++ b/build_config/lib/src/build_target.g.dart
@@ -0,0 +1,106 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'build_target.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+BuildTarget _$BuildTargetFromJson(Map json) {
+  return $checkedNew('BuildTarget', json, () {
+    $checkKeys(json, allowedKeys: const [
+      'auto_apply_builders',
+      'builders',
+      'dependencies',
+      'sources'
+    ]);
+    final val = BuildTarget(
+      autoApplyBuilders:
+          $checkedConvert(json, 'auto_apply_builders', (v) => v as bool),
+      sources: $checkedConvert(
+          json, 'sources', (v) => v == null ? null : InputSet.fromJson(v)),
+      dependencies: $checkedConvert(
+          json, 'dependencies', (v) => (v as List)?.map((e) => e as String)),
+      builders: $checkedConvert(
+          json,
+          'builders',
+          (v) => (v as Map)?.map(
+                (k, e) => MapEntry(k as String,
+                    e == null ? null : TargetBuilderConfig.fromJson(e as Map)),
+              )),
+    );
+    return val;
+  }, fieldKeyMap: const {'autoApplyBuilders': 'auto_apply_builders'});
+}
+
+TargetBuilderConfig _$TargetBuilderConfigFromJson(Map json) {
+  return $checkedNew('TargetBuilderConfig', json, () {
+    $checkKeys(json, allowedKeys: const [
+      'enabled',
+      'generate_for',
+      'options',
+      'dev_options',
+      'release_options'
+    ]);
+    final val = TargetBuilderConfig(
+      isEnabled: $checkedConvert(json, 'enabled', (v) => v as bool),
+      generateFor: $checkedConvert(
+          json, 'generate_for', (v) => v == null ? null : InputSet.fromJson(v)),
+      options: $checkedConvert(
+          json,
+          'options',
+          (v) => (v as Map)?.map(
+                (k, e) => MapEntry(k as String, e),
+              )),
+      devOptions: $checkedConvert(
+          json,
+          'dev_options',
+          (v) => (v as Map)?.map(
+                (k, e) => MapEntry(k as String, e),
+              )),
+      releaseOptions: $checkedConvert(
+          json,
+          'release_options',
+          (v) => (v as Map)?.map(
+                (k, e) => MapEntry(k as String, e),
+              )),
+    );
+    return val;
+  }, fieldKeyMap: const {
+    'isEnabled': 'enabled',
+    'generateFor': 'generate_for',
+    'devOptions': 'dev_options',
+    'releaseOptions': 'release_options'
+  });
+}
+
+GlobalBuilderConfig _$GlobalBuilderConfigFromJson(Map json) {
+  return $checkedNew('GlobalBuilderConfig', json, () {
+    $checkKeys(json,
+        allowedKeys: const ['options', 'dev_options', 'release_options']);
+    final val = GlobalBuilderConfig(
+      options: $checkedConvert(
+          json,
+          'options',
+          (v) => (v as Map)?.map(
+                (k, e) => MapEntry(k as String, e),
+              )),
+      devOptions: $checkedConvert(
+          json,
+          'dev_options',
+          (v) => (v as Map)?.map(
+                (k, e) => MapEntry(k as String, e),
+              )),
+      releaseOptions: $checkedConvert(
+          json,
+          'release_options',
+          (v) => (v as Map)?.map(
+                (k, e) => MapEntry(k as String, e),
+              )),
+    );
+    return val;
+  }, fieldKeyMap: const {
+    'devOptions': 'dev_options',
+    'releaseOptions': 'release_options'
+  });
+}
diff --git a/build_config/lib/src/builder_definition.dart b/build_config/lib/src/builder_definition.dart
new file mode 100644
index 0000000..0abdfc1
--- /dev/null
+++ b/build_config/lib/src/builder_definition.dart
@@ -0,0 +1,251 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'package:json_annotation/json_annotation.dart';
+import 'package:meta/meta.dart';
+
+import 'build_target.dart';
+import 'common.dart';
+import 'expandos.dart';
+import 'input_set.dart';
+import 'key_normalization.dart';
+
+part 'builder_definition.g.dart';
+
+enum AutoApply {
+  none,
+  dependents,
+  @JsonValue('all_packages')
+  allPackages,
+  @JsonValue('root_package')
+  rootPackage
+}
+
+enum BuildTo {
+  /// Generated files are written to the source directory next to their primary
+  /// inputs.
+  source,
+
+  /// Generated files are written to the hidden 'generated' directory.
+  cache
+}
+
+/// Definition of a builder parsed from the `builders` section of `build.yaml`.
+@JsonSerializable(createToJson: false, disallowUnrecognizedKeys: true)
+class BuilderDefinition {
+  /// The package which provides this Builder.
+  String get package => packageExpando[this];
+
+  /// A unique key for this Builder in `'$package:$builder'` format.
+  String get key => builderKeyExpando[this];
+
+  /// The names of the top-level methods in [import] from args -> Builder.
+  @JsonKey(
+      name: 'builder_factories',
+      nullable: false,
+      required: true,
+      disallowNullValue: true)
+  final List<String> builderFactories;
+
+  /// The import to be used to load `clazz`.
+  @JsonKey(nullable: false, required: true, disallowNullValue: true)
+  final String import;
+
+  /// A map from input extension to the output extensions created for matching
+  /// inputs.
+  @JsonKey(
+      name: 'build_extensions',
+      nullable: false,
+      required: true,
+      disallowNullValue: true)
+  final Map<String, List<String>> buildExtensions;
+
+  /// The name of the dart_library target that contains `import`.
+  ///
+  /// May be null or unreliable and should not be used.
+  @deprecated
+  final String target;
+
+  /// Which packages should have this builder applied automatically.
+  @JsonKey(name: 'auto_apply')
+  final AutoApply autoApply;
+
+  /// A list of file extensions which are required to run this builder.
+  ///
+  /// No builder which outputs any extension in this list is allowed to run
+  /// after this builder.
+  @JsonKey(name: 'required_inputs')
+  final List<String> requiredInputs;
+
+  /// Builder keys in `$package:$builder` format which should only be run after
+  /// this Builder.
+  @JsonKey(name: 'runs_before')
+  final List<String> runsBefore;
+
+  /// Builder keys in `$package:$builder` format which should be run on any
+  /// target which also runs this Builder.
+  @JsonKey(name: 'applies_builders')
+  final List<String> appliesBuilders;
+
+  /// Whether this Builder should be deferred until it's output is requested.
+  ///
+  /// Optional builders are lazy and will not run unless some later builder
+  /// requests one of it's possible outputs through either `readAs*` or
+  /// `canRead`.
+  @JsonKey(name: 'is_optional')
+  final bool isOptional;
+
+  /// Where the outputs of this builder should be written.
+  @JsonKey(name: 'build_to')
+  final BuildTo buildTo;
+
+  final TargetBuilderConfigDefaults defaults;
+
+  BuilderDefinition({
+    @required this.builderFactories,
+    @required this.buildExtensions,
+    @required this.import,
+    String target,
+    AutoApply autoApply,
+    Iterable<String> requiredInputs,
+    Iterable<String> runsBefore,
+    Iterable<String> appliesBuilders,
+    bool isOptional,
+    BuildTo buildTo,
+    TargetBuilderConfigDefaults defaults,
+  })  :
+        // ignore: deprecated_member_use
+        target = target != null
+            ? normalizeTargetKeyUsage(target, currentPackage)
+            : null,
+        autoApply = autoApply ?? AutoApply.none,
+        requiredInputs = requiredInputs?.toList() ?? const [],
+        runsBefore = runsBefore
+                ?.map((builder) =>
+                    normalizeBuilderKeyUsage(builder, currentPackage))
+                ?.toList() ??
+            const [],
+        appliesBuilders = appliesBuilders
+                ?.map((builder) =>
+                    normalizeBuilderKeyUsage(builder, currentPackage))
+                ?.toList() ??
+            const [],
+        isOptional = isOptional ?? false,
+        buildTo = buildTo ?? BuildTo.cache,
+        defaults = defaults ?? const TargetBuilderConfigDefaults() {
+    if (builderFactories.isEmpty) {
+      throw ArgumentError.value(builderFactories, 'builderFactories',
+          'Must have at least one value.');
+    }
+    if (buildExtensions.entries.any((e) => e.value.contains(e.key))) {
+      throw ArgumentError.value(
+          buildExtensions,
+          'buildExtensions',
+          'May not overwrite an input, '
+              'the output extensions must not contain the input extension');
+    }
+  }
+
+  factory BuilderDefinition.fromJson(Map json) =>
+      _$BuilderDefinitionFromJson(json);
+
+  @override
+  String toString() => {
+        'autoApply': autoApply,
+        'import': import,
+        'builderFactories': builderFactories,
+        'buildExtensions': buildExtensions,
+        'requiredInputs': requiredInputs,
+        'runsBefore': runsBefore,
+        'isOptional': isOptional,
+        'buildTo': buildTo,
+        'defaults': defaults,
+      }.toString();
+}
+
+/// The definition of a `PostProcessBuilder` in the `post_process_builders`
+/// section of a `build.yaml`.
+@JsonSerializable(createToJson: false, disallowUnrecognizedKeys: true)
+class PostProcessBuilderDefinition {
+  /// The package which provides this Builder.
+  String get package => packageExpando[this];
+
+  /// A unique key for this Builder in `'$package:$builder'` format.
+  String get key => builderKeyExpando[this];
+
+  /// The name of the top-level method in [import] from
+  /// Map<String, dynamic> -> Builder.
+  @JsonKey(
+      name: 'builder_factory',
+      nullable: false,
+      required: true,
+      disallowNullValue: true)
+  final String builderFactory;
+
+  /// The import to be used to load `clazz`.
+  @JsonKey(nullable: false, required: true, disallowNullValue: true)
+  final String import;
+
+  /// A list of input extensions for this builder.
+  ///
+  /// May be null or unreliable and should not be used.
+  @deprecated
+  @JsonKey(name: 'input_extensions')
+  final Iterable<String> inputExtensions;
+
+  /// The name of the dart_library target that contains `import`.
+  ///
+  /// May be null or unreliable and should not be used.
+  @deprecated
+  final String target;
+
+  final TargetBuilderConfigDefaults defaults;
+
+  PostProcessBuilderDefinition({
+    @required this.builderFactory,
+    @required this.import,
+    this.inputExtensions,
+    this.target,
+    TargetBuilderConfigDefaults defaults,
+  }) : defaults = defaults ?? const TargetBuilderConfigDefaults();
+
+  factory PostProcessBuilderDefinition.fromJson(Map json) =>
+      _$PostProcessBuilderDefinitionFromJson(json);
+
+  @override
+  String toString() => {
+        'import': import,
+        'builderFactory': builderFactory,
+        'defaults': defaults,
+      }.toString();
+}
+
+/// Default values that builder authors can specify when users don't fill in the
+/// corresponding key for [TargetBuilderConfig].
+@JsonSerializable(createToJson: false, disallowUnrecognizedKeys: true)
+class TargetBuilderConfigDefaults {
+  @JsonKey(name: 'generate_for')
+  final InputSet generateFor;
+
+  final Map<String, dynamic> options;
+
+  @JsonKey(name: 'dev_options')
+  final Map<String, dynamic> devOptions;
+
+  @JsonKey(name: 'release_options')
+  final Map<String, dynamic> releaseOptions;
+
+  const TargetBuilderConfigDefaults({
+    InputSet generateFor,
+    Map<String, dynamic> options,
+    Map<String, dynamic> devOptions,
+    Map<String, dynamic> releaseOptions,
+  })  : generateFor = generateFor ?? InputSet.anything,
+        options = options ?? const {},
+        devOptions = devOptions ?? const {},
+        releaseOptions = releaseOptions ?? const {};
+
+  factory TargetBuilderConfigDefaults.fromJson(Map json) =>
+      _$TargetBuilderConfigDefaultsFromJson(json);
+}
diff --git a/build_config/lib/src/builder_definition.g.dart b/build_config/lib/src/builder_definition.g.dart
new file mode 100644
index 0000000..550710f
--- /dev/null
+++ b/build_config/lib/src/builder_definition.g.dart
@@ -0,0 +1,191 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'builder_definition.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+BuilderDefinition _$BuilderDefinitionFromJson(Map json) {
+  return $checkedNew('BuilderDefinition', json, () {
+    $checkKeys(json, allowedKeys: const [
+      'builder_factories',
+      'import',
+      'build_extensions',
+      'target',
+      'auto_apply',
+      'required_inputs',
+      'runs_before',
+      'applies_builders',
+      'is_optional',
+      'build_to',
+      'defaults'
+    ], requiredKeys: const [
+      'builder_factories',
+      'import',
+      'build_extensions'
+    ], disallowNullValues: const [
+      'builder_factories',
+      'import',
+      'build_extensions'
+    ]);
+    final val = BuilderDefinition(
+      builderFactories: $checkedConvert(json, 'builder_factories',
+          (v) => (v as List).map((e) => e as String).toList()),
+      buildExtensions: $checkedConvert(
+          json,
+          'build_extensions',
+          (v) => (v as Map).map(
+                (k, e) => MapEntry(
+                    k as String, (e as List).map((e) => e as String).toList()),
+              )),
+      import: $checkedConvert(json, 'import', (v) => v as String),
+      target: $checkedConvert(json, 'target', (v) => v as String),
+      autoApply: $checkedConvert(json, 'auto_apply',
+          (v) => _$enumDecodeNullable(_$AutoApplyEnumMap, v)),
+      requiredInputs: $checkedConvert(
+          json, 'required_inputs', (v) => (v as List)?.map((e) => e as String)),
+      runsBefore: $checkedConvert(
+          json, 'runs_before', (v) => (v as List)?.map((e) => e as String)),
+      appliesBuilders: $checkedConvert(json, 'applies_builders',
+          (v) => (v as List)?.map((e) => e as String)),
+      isOptional: $checkedConvert(json, 'is_optional', (v) => v as bool),
+      buildTo: $checkedConvert(
+          json, 'build_to', (v) => _$enumDecodeNullable(_$BuildToEnumMap, v)),
+      defaults: $checkedConvert(
+          json,
+          'defaults',
+          (v) => v == null
+              ? null
+              : TargetBuilderConfigDefaults.fromJson(v as Map)),
+    );
+    return val;
+  }, fieldKeyMap: const {
+    'builderFactories': 'builder_factories',
+    'buildExtensions': 'build_extensions',
+    'autoApply': 'auto_apply',
+    'requiredInputs': 'required_inputs',
+    'runsBefore': 'runs_before',
+    'appliesBuilders': 'applies_builders',
+    'isOptional': 'is_optional',
+    'buildTo': 'build_to'
+  });
+}
+
+T _$enumDecode<T>(
+  Map<T, dynamic> enumValues,
+  dynamic source, {
+  T unknownValue,
+}) {
+  if (source == null) {
+    throw ArgumentError('A value must be provided. Supported values: '
+        '${enumValues.values.join(', ')}');
+  }
+
+  final value = enumValues.entries
+      .singleWhere((e) => e.value == source, orElse: () => null)
+      ?.key;
+
+  if (value == null && unknownValue == null) {
+    throw ArgumentError('`$source` is not one of the supported values: '
+        '${enumValues.values.join(', ')}');
+  }
+  return value ?? unknownValue;
+}
+
+T _$enumDecodeNullable<T>(
+  Map<T, dynamic> enumValues,
+  dynamic source, {
+  T unknownValue,
+}) {
+  if (source == null) {
+    return null;
+  }
+  return _$enumDecode<T>(enumValues, source, unknownValue: unknownValue);
+}
+
+const _$AutoApplyEnumMap = {
+  AutoApply.none: 'none',
+  AutoApply.dependents: 'dependents',
+  AutoApply.allPackages: 'all_packages',
+  AutoApply.rootPackage: 'root_package',
+};
+
+const _$BuildToEnumMap = {
+  BuildTo.source: 'source',
+  BuildTo.cache: 'cache',
+};
+
+PostProcessBuilderDefinition _$PostProcessBuilderDefinitionFromJson(Map json) {
+  return $checkedNew('PostProcessBuilderDefinition', json, () {
+    $checkKeys(json, allowedKeys: const [
+      'builder_factory',
+      'import',
+      'input_extensions',
+      'target',
+      'defaults'
+    ], requiredKeys: const [
+      'builder_factory',
+      'import'
+    ], disallowNullValues: const [
+      'builder_factory',
+      'import'
+    ]);
+    final val = PostProcessBuilderDefinition(
+      builderFactory:
+          $checkedConvert(json, 'builder_factory', (v) => v as String),
+      import: $checkedConvert(json, 'import', (v) => v as String),
+      inputExtensions: $checkedConvert(json, 'input_extensions',
+          (v) => (v as List)?.map((e) => e as String)),
+      target: $checkedConvert(json, 'target', (v) => v as String),
+      defaults: $checkedConvert(
+          json,
+          'defaults',
+          (v) => v == null
+              ? null
+              : TargetBuilderConfigDefaults.fromJson(v as Map)),
+    );
+    return val;
+  }, fieldKeyMap: const {
+    'builderFactory': 'builder_factory',
+    'inputExtensions': 'input_extensions'
+  });
+}
+
+TargetBuilderConfigDefaults _$TargetBuilderConfigDefaultsFromJson(Map json) {
+  return $checkedNew('TargetBuilderConfigDefaults', json, () {
+    $checkKeys(json, allowedKeys: const [
+      'generate_for',
+      'options',
+      'dev_options',
+      'release_options'
+    ]);
+    final val = TargetBuilderConfigDefaults(
+      generateFor: $checkedConvert(
+          json, 'generate_for', (v) => v == null ? null : InputSet.fromJson(v)),
+      options: $checkedConvert(
+          json,
+          'options',
+          (v) => (v as Map)?.map(
+                (k, e) => MapEntry(k as String, e),
+              )),
+      devOptions: $checkedConvert(
+          json,
+          'dev_options',
+          (v) => (v as Map)?.map(
+                (k, e) => MapEntry(k as String, e),
+              )),
+      releaseOptions: $checkedConvert(
+          json,
+          'release_options',
+          (v) => (v as Map)?.map(
+                (k, e) => MapEntry(k as String, e),
+              )),
+    );
+    return val;
+  }, fieldKeyMap: const {
+    'generateFor': 'generate_for',
+    'devOptions': 'dev_options',
+    'releaseOptions': 'release_options'
+  });
+}
diff --git a/build_config/lib/src/common.dart b/build_config/lib/src/common.dart
new file mode 100644
index 0000000..ad77ebd
--- /dev/null
+++ b/build_config/lib/src/common.dart
@@ -0,0 +1,36 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+final _defaultDependenciesZoneKey = Symbol('buildConfigDefaultDependencies');
+final _packageZoneKey = Symbol('buildConfigPackage');
+
+T runInBuildConfigZone<T>(
+        T Function() fn, String package, List<String> defaultDependencies) =>
+    runZoned(fn, zoneValues: {
+      _packageZoneKey: package,
+      _defaultDependenciesZoneKey: defaultDependencies,
+    });
+
+String get currentPackage {
+  var package = Zone.current[_packageZoneKey] as String;
+  if (package == null) {
+    throw StateError(
+        'Must be running inside a build config zone, which can be done using '
+        'the `runInBuildConfigZone` function.');
+  }
+  return package;
+}
+
+List<String> get currentPackageDefaultDependencies {
+  var defaultDependencies =
+      Zone.current[_defaultDependenciesZoneKey] as List<String>;
+  if (defaultDependencies == null) {
+    throw StateError(
+        'Must be running inside a build config zone, which can be done using '
+        'the `runInBuildConfigZone` function.');
+  }
+  return defaultDependencies;
+}
diff --git a/build_config/lib/src/expandos.dart b/build_config/lib/src/expandos.dart
new file mode 100644
index 0000000..2cc9e01
--- /dev/null
+++ b/build_config/lib/src/expandos.dart
@@ -0,0 +1,7 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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.
+
+final builderKeyExpando = Expando<String>();
+
+final packageExpando = Expando<String>();
diff --git a/build_config/lib/src/input_set.dart b/build_config/lib/src/input_set.dart
new file mode 100644
index 0000000..e077b3b
--- /dev/null
+++ b/build_config/lib/src/input_set.dart
@@ -0,0 +1,63 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'package:json_annotation/json_annotation.dart';
+
+part 'input_set.g.dart';
+
+/// A filter on files inputs or sources.
+///
+/// Takes a list of strings in glob format for [include] and [exclude]. Matches
+/// the `glob()` function in skylark.
+@JsonSerializable(createToJson: false, disallowUnrecognizedKeys: true)
+class InputSet {
+  static const anything = InputSet();
+
+  /// The globs to include in the set.
+  ///
+  /// May be null or empty which means every possible path (like `'**'`).
+  final List<String> include;
+
+  /// The globs as a subset of [include] to remove from the set.
+  ///
+  /// May be null or empty which means every path in [include].
+  final List<String> exclude;
+
+  const InputSet({this.include, this.exclude});
+
+  factory InputSet.fromJson(dynamic json) {
+    if (json is List) {
+      json = {'include': json};
+    } else if (json is! Map) {
+      throw ArgumentError.value(json, 'sources',
+          'Expected a Map or a List but got a ${json.runtimeType}');
+    }
+    final parsed = _$InputSetFromJson(json as Map);
+    if (parsed.include != null &&
+        parsed.include.any((s) => s == null || s.isEmpty)) {
+      throw ArgumentError.value(
+          parsed.include, 'include', 'Include globs must not be empty');
+    }
+    if (parsed.exclude != null &&
+        parsed.exclude.any((s) => s == null || s.isEmpty)) {
+      throw ArgumentError.value(
+          parsed.exclude, 'exclude', 'Exclude globs must not be empty');
+    }
+    return parsed;
+  }
+
+  @override
+  String toString() {
+    final result = StringBuffer();
+    if (include == null || include.isEmpty) {
+      result.write('any path');
+    } else {
+      result.write('paths matching $include');
+    }
+    if (exclude != null && exclude.isNotEmpty) {
+      result.write(' except $exclude');
+    }
+    return '$result';
+  }
+}
diff --git a/build_config/lib/src/input_set.g.dart b/build_config/lib/src/input_set.g.dart
new file mode 100644
index 0000000..6733313
--- /dev/null
+++ b/build_config/lib/src/input_set.g.dart
@@ -0,0 +1,20 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'input_set.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+InputSet _$InputSetFromJson(Map json) {
+  return $checkedNew('InputSet', json, () {
+    $checkKeys(json, allowedKeys: const ['include', 'exclude']);
+    final val = InputSet(
+      include: $checkedConvert(json, 'include',
+          (v) => (v as List)?.map((e) => e as String)?.toList()),
+      exclude: $checkedConvert(json, 'exclude',
+          (v) => (v as List)?.map((e) => e as String)?.toList()),
+    );
+    return val;
+  });
+}
diff --git a/build_config/lib/src/key_normalization.dart b/build_config/lib/src/key_normalization.dart
new file mode 100644
index 0000000..62b5eae
--- /dev/null
+++ b/build_config/lib/src/key_normalization.dart
@@ -0,0 +1,99 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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.
+
+const _defaultTargetNamePlaceholder = r'$default';
+
+/// Returns the normalized [builderKey] definition when used from [packageName].
+///
+/// Example normalizations:
+///
+///   - "some_builder" => "$packageName:some_builder"
+///   - ":some_builder" => "$packageName:some_builder"
+///   - "some_package:some_builder" => "some_package:some_builder"
+///
+/// If the legacy separator `|` is used it will be transformed to `:`
+String normalizeBuilderKeyDefinition(String builderKey, String packageName) =>
+    _normalizeDefinition(
+        builderKey.contains('|')
+            ? builderKey.replaceFirst('|', ':')
+            : builderKey,
+        packageName);
+
+/// Returns the normalized [builderKey] usage when used from [packageName].
+///
+/// Example normalizations:
+///
+///   - "some_package" => "some_package:some_package"
+///   - ":some_builder" => "$packageName:some_builder"
+///   - "some_package:some_builder" => "some_package:some_builder"
+///
+/// If the legacy separator `|` is used it will be transformed to `:`
+String normalizeBuilderKeyUsage(String builderKey, String packageName) =>
+    _normalizeUsage(
+        builderKey.contains('|')
+            ? builderKey.replaceFirst('|', ':')
+            : builderKey,
+        packageName);
+
+/// Returns the normalized [targetKey] definition when used from [packageName].
+///
+/// Example normalizations:
+///
+///   - "$default" => "$packageName:$packageName"
+///   - "some_target" => "$packageName:some_target"
+///   - ":some_target" => "$packageName:some_target"
+///   - "some_package:some_target" => "some_package|some_target"
+String normalizeTargetKeyDefinition(String targetKey, String packageName) =>
+    targetKey == _defaultTargetNamePlaceholder
+        ? '$packageName:$packageName'
+        : _normalizeDefinition(targetKey, packageName);
+
+/// Returns the normalized [targetKey] usage when used from [packageName].
+///
+/// Example normalizations:
+///
+///   - "$default" => "$packageName:$packageName"
+///   - ":$default" => "$packageName:$packageName"
+///   - "$default:$default" => "$packageName:$packageName"
+///   - "some_package" => "some_package:some_package"
+///   - ":some_target" => "$packageName:some_target"
+///   - "some_package:some_target" => "some_package:some_target"
+String normalizeTargetKeyUsage(String targetKey, String packageName) {
+  switch (targetKey) {
+    case _defaultTargetNamePlaceholder:
+    case ':$_defaultTargetNamePlaceholder':
+    case '$_defaultTargetNamePlaceholder:$_defaultTargetNamePlaceholder':
+      return '$packageName:$packageName';
+    default:
+      return _normalizeUsage(targetKey, packageName);
+  }
+}
+
+/// Gives a full unique key for [name] used from [packageName].
+///
+/// If [name] omits the separator we assume it's referring to a target or
+/// builder named after a package (which is not this package). If [name] starts
+/// with the separator we assume it's referring to a target within the package
+/// it's used from.
+///
+/// For example: If I depend on `angular` from `my_package` it is treated as a
+/// dependency on the globally unique `angular:angular`.
+String _normalizeUsage(String name, String packageName) {
+  if (name.startsWith(':')) return '$packageName$name';
+  if (!name.contains(':')) return '$name:$name';
+  return name;
+}
+
+/// Gives a full unique key for [name] definied within [packageName].
+///
+/// The result is always '$packageName$separator$name since at definition the
+/// key must be referring to something within [packageName].
+///
+/// For example: If I expose a builder `my_builder` within `my_package` it is
+/// turned into the globally unique `my_package|my_builder`.
+String _normalizeDefinition(String name, String packageName) {
+  if (name.startsWith(':')) return '$packageName$name';
+  if (!name.contains(':')) return '$packageName:$name';
+  return name;
+}
diff --git a/build_config/mono_pkg.yaml b/build_config/mono_pkg.yaml
new file mode 100644
index 0000000..e317dee
--- /dev/null
+++ b/build_config/mono_pkg.yaml
@@ -0,0 +1,20 @@
+dart:
+  - dev
+
+stages:
+  - analyze_and_format:
+    - group:
+        - dartfmt: sdk
+        - dartanalyzer: --fatal-infos --fatal-warnings .
+    - dartanalyzer: --fatal-warnings .
+      dart:
+        - 2.6.0
+  - unit_test:
+    - command: pub run test
+      os:
+        - linux
+        - windows
+
+cache:
+  directories:
+    - .dart_tool/build
diff --git a/build_config/pubspec.yaml b/build_config/pubspec.yaml
new file mode 100644
index 0000000..12a59db
--- /dev/null
+++ b/build_config/pubspec.yaml
@@ -0,0 +1,21 @@
+name: build_config
+version: 0.4.2
+description: Support for parsing `build.yaml` configuration.
+homepage: https://github.com/dart-lang/build/tree/master/build_config
+
+environment:
+  sdk: '>=2.6.0 <3.0.0'
+
+dependencies:
+  checked_yaml: ^1.0.0
+  json_annotation: '>=1.0.0 <4.0.0'
+  meta: ^1.1.0
+  path: ^1.4.0
+  pubspec_parse: ^0.1.5
+  yaml: ^2.1.11
+
+dev_dependencies:
+  build_runner: ^1.0.0
+  json_serializable: ^3.0.0
+  term_glyph: ^1.0.0
+  test: ^1.6.0
diff --git a/build_modules/BUILD.gn b/build_modules/BUILD.gn
new file mode 100644
index 0000000..cee16c9
--- /dev/null
+++ b/build_modules/BUILD.gn
@@ -0,0 +1,31 @@
+# This file is generated by importer.py for build_modules-2.8.0
+
+import("//build/dart/dart_library.gni")
+
+dart_library("build_modules") {
+  package_name = "build_modules"
+
+  # This parameter is left empty as we don't care about analysis or exporting
+  # these sources outside of the tree.
+  sources = []
+
+  disable_analysis = true
+
+  deps = [
+    "//third_party/dart-pkg/pub/pedantic",
+    "//third_party/dart-pkg/pub/logging",
+    "//third_party/dart-pkg/pub/glob",
+    "//third_party/dart-pkg/pub/crypto",
+    "//third_party/dart-pkg/pub/collection",
+    "//third_party/dart-pkg/pub/graphs",
+    "//third_party/dart-pkg/pub/build",
+    "//third_party/dart-pkg/pub/meta",
+    "//third_party/dart-pkg/pub/scratch_space",
+    "//third_party/dart-pkg/pub/analyzer",
+    "//third_party/dart-pkg/pub/async",
+    "//third_party/dart-pkg/pub/build_config",
+    "//third_party/dart-pkg/pub/path",
+    "//third_party/dart-pkg/pub/bazel_worker",
+    "//third_party/dart-pkg/pub/json_annotation",
+  ]
+}
diff --git a/build_modules/CHANGELOG.md b/build_modules/CHANGELOG.md
new file mode 100644
index 0000000..92c2fbe
--- /dev/null
+++ b/build_modules/CHANGELOG.md
@@ -0,0 +1,346 @@
+## 2.8.0
+
+- Add the ability to pass a list of experiments to enable to the KernelBuilder.
+
+## 2.7.0
+
+- Add support for an environment variable `BUILD_DART2JS_VM_ARGS` which can
+  be used to supply Dart vm arguments for the dart2js processes.
+
+## 2.6.3
+
+- Keep cached deserialized module instances in more cases. This may improve
+  performance of incremental builds in watch mode.
+- **Deprecated**: The package specific unsupported module whitelist option
+  provided by `computeTransitiveDependencies`. The only known uses are being
+  removed.
+- Allow analyzer version `0.39.x`.
+
+## 2.6.2
+
+Republish of `2.6.0` with the proper min sdk contraint.
+
+## 2.6.1
+
+### Bug fix for issue #2464
+
+Ignore the `trackUnusedInputs` option that was added in `2.6.0`.
+
+This option will be respected again in the next release which will have the
+proper minimum sdk constraint.
+
+## 2.6.0
+
+Add support for dependency pruning to the `KernelBuilder`. This should greatly
+improve the invalidation semantics for builds, meaning that less code will be
+recompiled for each edit you make.
+
+This is not enabled by default but can be enabled by passing
+`trackUnusedInputs: true` to the `KernelBuilder` constructor.
+
+## 2.5.0
+
+- Add an option to skip the unsupported module check for modules in specified
+  packages.
+
+## 2.4.3
+
+- Allow analyzer version 0.38.0.
+
+## 2.4.2
+
+- Support the latest release of `package:json_annotation`.
+
+## 2.4.1
+
+- Require non-empty output from kernel build steps.
+
+## 2.4.0
+
+- Allow overriding the target name passed to the kernel worker.
+
+## 2.3.1
+
+- Allow analyzer version 0.37.0.
+
+## 2.3.0
+
+- Add a `hasMain` boolean to the `ModuleLibrary` class.
+  - This is now used instead of `isEntrypoint` for determining whether or not
+    to copy module files for application discovery.
+- Fix `computeTransitiveDeps` to do error checking for the root module as well
+  as transitive modules.
+
+## 2.2.0
+
+- Make the `librariesPath` in the `KernelBuilder` configurable.
+- Fixed bug where the configured dart SDK was ignored.
+
+## 2.1.3
+
+- Skip compiling modules with kernel when the primary source isn't the primary
+  input (only shows up in non-lazy builds - essentially just tests).
+
+## 2.1.2
+
+- Output additional `.module` files for all entrypoints to ease discovery of
+  modules for compilers.
+
+## 2.1.1
+
+- Allow `build_config` `0.4.x`.
+
+## 2.1.0
+
+- Make using the incremental compiler in the `KernelBuilder` configurable.
+
+## 2.0.0
+
+### Breaking Changes
+
+- Remove the `merge` method from `Module` and replace with a static
+  `Module.merge`. Module instances are now immutable.
+- Remove `jsId`, and `jsSourceMapId` from `Module`.
+- `DartPlatform` no longer has hard coded platforms, and its constructor is now
+  public. Anybody is now free to create their own `platform`.
+- Removed the platform specific builder factories from the `builders.dart` file.
+  - Packages that want to target compilation for a platform should create their
+    own builder factories.
+- Removed completely the analyzer based builders - `UnlinkedSummaryBuilder` and
+  `LinkedSummaryBuilder`.
+  - All backends should now be using the `KernelBuilder` instead.
+- Removed the default module builders for each supported platform. These
+  must now be created by the packages that want to add compilation targeting a
+  specific platform.
+  - This will help reduce asset graph bloat caused by platforms that you weren't
+    actually targeting.
+
+### Improvements
+
+- Update the kernel worker to pass input digests, along with
+  `--reuse-compiler-result` and `--use-incremental-compiler`.
+- Increased the upper bound for `package:analyzer` to `<0.37.0`.
+
+## 1.0.11
+
+- Allow build_config 0.4.x.
+
+## 1.0.10
+
+- Fix a performance issue in the kernel_builder, especially for larger projects.
+
+## 1.0.9
+
+- Allow configuring the platform SDK directory in the `KernelBuilder` builder.
+
+## 1.0.8
+
+- Don't follow `dart.library.isolate` conditional imports for the DDC
+  platform.
+
+## 1.0.7+2
+
+- Update `dart2js` snapshot arguments for upcoming SDK.
+
+## 1.0.7+1
+
+- Fix broken release by updating `dart2js` worker and restricting sdk version.
+
+## 1.0.7
+
+- Explicitly skip dart-ext uris during module creation.
+  - Filed Issue #2047 to track real support for native extensions.
+- Run workers with mode `detachedWithStdio` if no terminal is connected.
+
+## 1.0.6
+
+- Improved the time tracking for kernel and analyzer actions by not reporting
+  time spent waiting for a worker to be available.
+
+## 1.0.5
+
+- Increased the upper bound for `package:analyzer` to `<0.36.0`.
+
+## 1.0.4
+
+- Update to `package:graphs` version `0.2.0`.
+- Use `dartdevc --kernel` instead of `dartdevk`.
+
+## 1.0.3
+
+- Increased the upper bound for `package:analyzer` to `<0.35.0`.
+
+## 1.0.2
+
+- Support the latest `package:json_annotation`.
+
+## 1.0.1
+
+- Increased the upper bound for `package:analyzer` to '<0.34.0'.
+
+## 1.0.0
+
+### Breaking Changes
+
+- The `Module` constructor has an additional required parameter `isSupported`,
+  which indicates if a module is supported on that module's platform.
+
+## 0.4.0
+
+### Improvements
+
+- Modules are now platform specific, and config specific imports using
+  `dart.library.*` constants are supported.
+
+### Breaking Configuration Changes
+
+- Module granularity now has to be configured per platform, so instead of
+  configuring it using the `build_modules|modules` builder, you now need to
+  configure the builder for each specific platform:
+
+```yaml
+targets:
+  $default:
+    build_modules|dartdevc:
+      options:
+        strategy: fine
+```
+
+  The supported platforms are currently `dart2js`, `dartdevc`, `flutter`, and
+  `vm`.
+
+### Breaking API Changes
+
+- Output extensions of builders have changed to include the platform being built
+  for.
+  - All the top level file extension getters are now methods that take a
+    platform and return the extension for that platform.
+- Most builders are no longer applied by default, you must manually apply them
+  using applies_builders in your builder.
+- Most builder constructors now require a `platform` argument.
+
+## 0.3.2
+
+- Module strategies are now respected for all packages instead of just the root
+  package.
+- Can now mix and match fine and coarse strategies at will, even within package
+  cycles (although this may cause larger modules).
+- Removed analyzer dependency.
+
+## 0.3.1+1
+
+- Support `package:json_annotation` v1.
+
+## 0.3.1
+
+- Change the default module strategy for the root package to `coarse`.
+
+## 0.3.0
+
+### Improvements
+
+- Updated dart2js support so that it can do multiple builds concurrently and
+  will restart workers periodically to mitigate the effects of
+  dart-lang/sdk#33708.
+- Increased the upper bound for the sdk to `<3.0.0`.
+
+### Breaking Changes
+
+- Removed the `kernelSummaryExtension`, and renamed the `KernelSummaryBuilder`
+  to `KernelBuilder`. The new builder can be used to create summaries or full
+  kernel files, and requires users to give it a custom sdk.
+- Changed `metaModuleCleanBuilder` to read `.module.library` files which are
+  produced by the `moduleLibrayBuilder`. Clients using the automatically
+  generated build script will get this automatically. Clients which have
+  manually written build scripts will need to add it.
+
+## 0.2.3
+
+- Update to the latest `package:scratch_space` and don't manually clear it out
+  between builds. This provides significant speed improvements for large
+  projects.
+
+## 0.2.2+6
+
+- Support the latest `package:build_config`.
+
+## 0.2.2+5
+
+- Updated the missing modules message to highlight that the error is likely due
+  to a missing dependency.
+
+## 0.2.2+4
+
+- Support `package:analyzer` `0.32.0`.
+
+## 0.2.2+3
+
+- Fix a race condition where we could fail to find the modules for some
+  dependencies.
+
+## 0.2.2+2
+
+- Fix an issue where modules were unnecessarily being built with DDC.
+  [#1375](https://github.com/dart-lang/build/issues/1375).
+
+## 0.2.2+1
+
+- Fix an issue with new files causing subsequent build failures
+  [#1358](https://github.com/dart-lang/build/issues/1358).
+- Expose `MetaModuleCleanBuilder` and `metaModuleCleanExtension` publicly for
+  usage in tests and manual build scripts.
+
+## 0.2.2
+
+- Clean up `.module` and summary files from the output and server.
+- Add new `ModuleBuilder` strategies. By default the `coarse` strategy is used
+  for all non-root packages and will create a minimum number of modules. This
+  strategy can not be overridden. However, for the root package, the `fine`
+  strategy will be used which creates a module for each strongly
+  connected component. You can override this behavior by providing `coarse`
+  to the `strategy` option.
+
+  Example configuration:
+
+  ```yaml
+  targets:
+    $default:
+      builders:
+        build_modules|modules:
+          options:
+            strategy: coarse
+  ```
+
+## 0.2.1
+
+- Give a guaranteed reverse dependency order for
+  `Module.computeTransitiveDependencies`
+
+## 0.2.0+2
+
+- Fix use of `whereType` in `MissingModulesException`,
+  https://github.com/dart-lang/build/issues/1123.
+
+## 0.2.0+1
+
+- Fix null pointer error in `MissingModulesException`,
+  https://github.com/dart-lang/build/issues/1092.
+
+## 0.2.0
+
+- `computeTransitiveDependencies` now throws a `MissingModulesException` instead
+  of logging a warning if it discovers a missing module.
+
+## 0.1.0+2
+
+- Fix a bug with the dart2js workers where the worker could hang if you try to
+  re-use it after calling `terminateWorkers`. This only really surfaces in test
+  environments.
+
+## 0.1.0+1
+
+- Fix a bug with imports to libraries that have names starting with `dart.`
+
+## 0.1.0
+
+- Split from `build_web_compilers`.
diff --git a/build_modules/LICENSE b/build_modules/LICENSE
new file mode 100644
index 0000000..389ce98
--- /dev/null
+++ b/build_modules/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2017, the Dart project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of Google Inc. nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/build_modules/README.md b/build_modules/README.md
new file mode 100644
index 0000000..4ce5c7d
--- /dev/null
+++ b/build_modules/README.md
@@ -0,0 +1,102 @@
+<p align="center">
+  Module builders for modular compilation pipelines.
+  <br>
+  <a href="https://travis-ci.org/dart-lang/build">
+    <img src="https://travis-ci.org/dart-lang/build.svg?branch=master" alt="Build Status" />
+  </a>
+  <a href="https://github.com/dart-lang/build/labels/package%3A%20build_modules">
+    <img src="https://img.shields.io/github/issues-raw/dart-lang/build/package%3A%20build_modules.svg" alt="Issues related to build_modules" />
+  </a>
+  <a href="https://pub.dev/packages/build_modules">
+    <img src="https://img.shields.io/pub/v/build_modules.svg" alt="Pub Package Version" />
+  </a>
+  <a href="https://pub.dev/documentation/build_modules/latest/">
+    <img src="https://img.shields.io/badge/dartdocs-latest-blue.svg" alt="Latest Dartdocs" />
+  </a>
+  <a href="https://gitter.im/dart-lang/build">
+    <img src="https://badges.gitter.im/dart-lang/build.svg" alt="Join the chat on Gitter" />
+  </a>
+</p>
+
+This package provides generic module builders which can be used to create custom
+compilation pipelines. It is used by [`build_web_compilers`][] and
+[`build_vm_compilers`][] package which provides standard builders for the web
+and vm platforms.
+
+## Usage
+
+There should be no need to import this package directly unless you are
+developing a custom compiler pipeline. See documentation in
+[`build_web_compilers`][] and [`build_vm_compilers`][] for more details on
+building your Dart code.
+
+[`build_web_compilers`]: https://pub.dev/packages/build_web_compilers
+[`build_vm_compilers`]: https://pub.dev/packages/build_vm_compilers
+
+## Module creation
+
+The overall process for emitting modules follows these steps:
+
+1. Emit a `.module.library` asset for every `.dart` library containing a
+   description of the directives (imports, exports, and parts), a list of the
+   `dart:` imports used, and a determination of whether the library is an
+   "entrypoint". Entrypoint libraries are either those which are likely to be
+   directly imported (they are under `lib/` but not `lib/src/`) or which have a
+   `main`. Only the libraries that can be imported (they aren't part files) will
+   have an output. This step is mainly separated out for better invalidation
+   behavior since the output will change less frequently than the code. The
+   outputs from this step are non differentiated by platform.
+2. Emit package level `.meta_module` assets. This is an aggregation of the
+   `.module.library` information with a first run at creating a module
+   structure. The output depends on the algorithm, described below. The outputs
+   from this step are specific to a platform and will indicate which libraries
+   contain unsupported `dart:` imports for a platform. Conditional imports will
+   also be resolved to a concrete dependency based on the platform. In this step
+   the dependencies of modules references the imported library, which may not
+   match the primary source of the module containing that library.
+3. Emit package level `.meta_module.clean` assets. This step performs an extra
+   run of the strongly connected components algorithm so that any libraries
+   which are in an import cycle are grouped into a single module. This is done
+   as a separate step so that it can be sure it may read all the `.meta_module`
+   results across packages in a dependency cycle so that it may resolve import
+   cycles across packages. As modules are merged due to import cycles the new
+   module becomes the union of their sources and dependencies. Dependencies are
+   resolved to the "primary source" of the module containing the imported
+   library.
+4. Emit `.module` assets which contain a filtered view of the package level
+   meta-module specific to a single module. These are emitted for the "primary
+   source" of each module, as well as each library which is an entrypoint.
+
+## Module algorithms
+
+### Fine
+
+The fine-grained module algorithm is straightforward - any dart library which
+_can_ be it's own module, _is_ it's own module. Libraries are only grouped into
+a module when there is an import cycle between them.
+
+The `.meta_module` step filters unsupported libraries and does no clustering.
+Import cycles are resolved by the `.meta_module.clean` step.
+
+### Coarse
+
+The coarse-grained module algorithm attempts to cluster libraries into larger
+modules without bringing in more code than may be imported by downstream code.
+It assumes that libraries under `lib/src/` won't be imported directly outside of
+the package. Assuming that external packages only import the libraries outside
+of `lib/src/` then no code outside of the transitive imports will be brought in
+due to module clustering.
+
+The `.meta_module` step performs strongly connected components and libraries
+which are in an import cycle are grouped into modules since they can't be
+ordered otherwise. Within each top level directory under the package (`lib/`,
+`test/`, etc) the libraries are further bundled into modules where possible.
+Each "entrypoint" library is always kept in it's own modules (other than in the
+case of import cycles). Non entrypoint libraries are grouped where possible
+based on the entrypoints that transitively import them. Any non-entrypoint which
+is only transitively imported by a single entrypoint is merged into the module
+for that entrypoint. Any non-entrypoints which are imported by the same set of
+entrypoints are merged into their own module. The "primary source" for any
+module is always the lowest alpha-sorted source, regardless of whether it is an
+entrypoint. As libraries are merged the module becomes the union of their
+sources and dependencies.
diff --git a/build_modules/build.yaml b/build_modules/build.yaml
new file mode 100644
index 0000000..a73fedf
--- /dev/null
+++ b/build_modules/build.yaml
@@ -0,0 +1,16 @@
+builders:
+  module_library:
+    import: "package:build_modules/builders.dart"
+    builder_factories:
+      - moduleLibraryBuilder
+    build_extensions:
+      .dart:
+        - .module.library
+    auto_apply: all_packages
+    is_optional: True
+    required_inputs: [".dart"]
+    applies_builders: ["|module_cleanup"]
+post_process_builders:
+  module_cleanup:
+    import: "package:build_modules/builders.dart"
+    builder_factory: "moduleCleanup"
diff --git a/build_modules/dart_test.yaml b/build_modules/dart_test.yaml
new file mode 100644
index 0000000..56f2311
--- /dev/null
+++ b/build_modules/dart_test.yaml
@@ -0,0 +1,4 @@
+tags:
+  presubmit-only:
+    skip: "Should only be run during presubmit"
+    presets: {presubmit: {skip: false}}
diff --git a/build_modules/lib/build_modules.dart b/build_modules/lib/build_modules.dart
new file mode 100644
index 0000000..216b65d
--- /dev/null
+++ b/build_modules/lib/build_modules.dart
@@ -0,0 +1,18 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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.
+
+export 'src/errors.dart' show MissingModulesException, UnsupportedModules;
+export 'src/kernel_builder.dart'
+    show KernelBuilder, multiRootScheme, reportUnusedKernelInputs;
+export 'src/meta_module_builder.dart'
+    show MetaModuleBuilder, metaModuleExtension;
+export 'src/meta_module_clean_builder.dart'
+    show MetaModuleCleanBuilder, metaModuleCleanExtension;
+export 'src/module_builder.dart' show ModuleBuilder, moduleExtension;
+export 'src/module_library_builder.dart'
+    show ModuleLibraryBuilder, moduleLibraryExtension;
+export 'src/modules.dart';
+export 'src/platform.dart' show DartPlatform;
+export 'src/scratch_space.dart' show scratchSpace, scratchSpaceResource;
+export 'src/workers.dart' show dart2JsWorkerResource, dartdevkDriverResource;
diff --git a/build_modules/lib/builders.dart b/build_modules/lib/builders.dart
new file mode 100644
index 0000000..4366a82
--- /dev/null
+++ b/build_modules/lib/builders.dart
@@ -0,0 +1,12 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'package:build/build.dart';
+import 'package:build_modules/build_modules.dart';
+import 'package:build_modules/src/module_cleanup.dart';
+import 'package:build_modules/src/module_library_builder.dart';
+
+Builder moduleLibraryBuilder(_) => const ModuleLibraryBuilder();
+
+PostProcessBuilder moduleCleanup(_) => const ModuleCleanup();
diff --git a/build_modules/lib/src/common.dart b/build_modules/lib/src/common.dart
new file mode 100644
index 0000000..e7b7197
--- /dev/null
+++ b/build_modules/lib/src/common.dart
@@ -0,0 +1,59 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:io';
+
+import 'package:build/build.dart';
+import 'package:path/path.dart' as p;
+import 'package:scratch_space/scratch_space.dart';
+
+import 'kernel_builder.dart';
+
+final defaultAnalysisOptionsId =
+    AssetId('build_modules', 'lib/src/analysis_options.default.yaml');
+
+String defaultAnalysisOptionsArg(ScratchSpace scratchSpace) =>
+    '--options=${scratchSpace.fileFor(defaultAnalysisOptionsId).path}';
+
+// TODO: better solution for a .packages file, today we just create a new one
+// for every kernel build action.
+Future<File> createPackagesFile(Iterable<AssetId> allAssets) async {
+  var allPackages = allAssets.map((id) => id.package).toSet();
+  var packagesFileDir =
+      await Directory.systemTemp.createTemp('kernel_builder_');
+  var packagesFile = File(p.join(packagesFileDir.path, '.packages'));
+  await packagesFile.create();
+  await packagesFile.writeAsString(allPackages
+      .map((pkg) => '$pkg:$multiRootScheme:///packages/$pkg')
+      .join('\r\n'));
+  return packagesFile;
+}
+
+enum ModuleStrategy { fine, coarse }
+
+ModuleStrategy moduleStrategy(BuilderOptions options) {
+  var config = options.config['strategy'] as String ?? 'coarse';
+  switch (config) {
+    case 'coarse':
+      return ModuleStrategy.coarse;
+    case 'fine':
+      return ModuleStrategy.fine;
+    default:
+      throw ArgumentError('Unexpected ModuleBuilder strategy: $config');
+  }
+}
+
+/// Validates that [config] only has the top level keys [supportedOptions].
+///
+/// Throws an [ArgumentError] if not.
+void validateOptions(Map<String, dynamic> config, List<String> supportedOptions,
+    String builderKey) {
+  var unsupportedOptions =
+      config.keys.where((o) => !supportedOptions.contains(o));
+  if (unsupportedOptions.isNotEmpty) {
+    throw ArgumentError.value(unsupportedOptions.join(', '), builderKey,
+        'only $supportedOptions are supported options, but got');
+  }
+}
diff --git a/build_modules/lib/src/errors.dart b/build_modules/lib/src/errors.dart
new file mode 100644
index 0000000..d3dfbf1
--- /dev/null
+++ b/build_modules/lib/src/errors.dart
@@ -0,0 +1,140 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+// ignore: deprecated_member_use
+import 'package:analyzer/analyzer.dart';
+import 'package:build/build.dart';
+
+import 'module_library.dart';
+import 'module_library_builder.dart';
+import 'modules.dart';
+
+/// An [Exception] that is thrown when a worker returns an error.
+abstract class _WorkerException implements Exception {
+  final AssetId failedAsset;
+
+  final String error;
+
+  /// A message to prepend to [toString] output.
+  String get message;
+
+  _WorkerException(this.failedAsset, this.error);
+
+  @override
+  String toString() => '$message:$failedAsset\n\nResponse:$error\n';
+}
+
+/// An [Exception] that is thrown when the analyzer fails to create a summary.
+class AnalyzerSummaryException extends _WorkerException {
+  @override
+  final String message = 'Error creating summary for module';
+
+  AnalyzerSummaryException(AssetId summaryId, String error)
+      : super(summaryId, error);
+}
+
+/// An [Exception] that is thrown when the common frontend fails to create a
+/// kernel summary.
+class KernelException extends _WorkerException {
+  @override
+  final String message = 'Error creating kernel summary for module';
+
+  KernelException(AssetId summaryId, String error) : super(summaryId, error);
+}
+
+/// An [Exception] that is thrown when there are some missing modules.
+class MissingModulesException implements Exception {
+  final String message;
+
+  @override
+  String toString() => message;
+
+  MissingModulesException._(this.message);
+
+  static Future<MissingModulesException> create(Set<AssetId> missingSources,
+      Iterable<Module> transitiveModules, AssetReader reader) async {
+    var buffer = StringBuffer('''
+Unable to find modules for some sources, this is usually the result of either a
+bad import, a missing dependency in a package (or possibly a dev_dependency
+needs to move to a real dependency), or a build failure (if importing a
+generated file).
+
+Please check the following imports:\n
+''');
+
+    var checkedSourceDependencies = <AssetId, Set<AssetId>>{};
+    for (var module in transitiveModules) {
+      var missingIds = module.directDependencies.intersection(missingSources);
+      for (var missingId in missingIds) {
+        var checkedAlready =
+            checkedSourceDependencies.putIfAbsent(missingId, () => <AssetId>{});
+        for (var sourceId in module.sources) {
+          if (checkedAlready.contains(sourceId)) {
+            continue;
+          }
+          checkedAlready.add(sourceId);
+          var message =
+              await _missingImportMessage(sourceId, missingId, reader);
+          if (message != null) buffer.writeln(message);
+        }
+      }
+    }
+
+    return MissingModulesException._(buffer.toString());
+  }
+}
+
+/// Checks if [sourceId] directly imports [missingId], and returns an error
+/// message if so.
+Future<String> _missingImportMessage(
+    AssetId sourceId, AssetId missingId, AssetReader reader) async {
+  var contents = await reader.readAsString(sourceId);
+  // ignore: deprecated_member_use
+  var parsed = parseDirectives(contents, suppressErrors: true);
+  var import =
+      parsed.directives.whereType<UriBasedDirective>().firstWhere((directive) {
+    var uriString = directive.uri.stringValue;
+    if (uriString.startsWith('dart:')) return false;
+    var id = AssetId.resolve(uriString, from: sourceId);
+    return id == missingId;
+  }, orElse: () => null);
+  if (import == null) return null;
+  var lineInfo = parsed.lineInfo.getLocation(import.offset);
+  return '`$import` from $sourceId at $lineInfo';
+}
+
+/// An [Exception] that is thrown when there are some unsupported modules.
+class UnsupportedModules implements Exception {
+  final Set<Module> unsupportedModules;
+
+  UnsupportedModules(this.unsupportedModules);
+
+  Stream<ModuleLibrary> exactLibraries(AssetReader reader) async* {
+    for (var module in unsupportedModules) {
+      for (var source in module.sources) {
+        var libraryId = source.changeExtension(moduleLibraryExtension);
+        ModuleLibrary library;
+        if (await reader.canRead(libraryId)) {
+          library = ModuleLibrary.deserialize(
+              libraryId, await reader.readAsString(libraryId));
+        } else {
+          // A missing .module.library file indicates a part file, which can't
+          // have import statements, so we just skip them.
+          continue;
+        }
+        if (library.sdkDeps
+            .any((lib) => !module.platform.supportsLibrary(lib))) {
+          yield library;
+        }
+      }
+    }
+  }
+
+  @override
+  String toString() =>
+      'Some modules contained libraries that were incompatible '
+      'with the current platform.';
+}
diff --git a/build_modules/lib/src/kernel_builder.dart b/build_modules/lib/src/kernel_builder.dart
new file mode 100644
index 0000000..24e2abf
--- /dev/null
+++ b/build_modules/lib/src/kernel_builder.dart
@@ -0,0 +1,424 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:collection';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:bazel_worker/bazel_worker.dart';
+import 'package:build/build.dart';
+import 'package:crypto/crypto.dart';
+import 'package:graphs/graphs.dart' show crawlAsync;
+import 'package:meta/meta.dart';
+import 'package:path/path.dart' as p;
+import 'package:scratch_space/scratch_space.dart';
+
+import 'common.dart';
+import 'errors.dart';
+import 'module_builder.dart';
+import 'module_cache.dart';
+import 'modules.dart';
+import 'platform.dart';
+import 'scratch_space.dart';
+import 'workers.dart';
+
+const multiRootScheme = 'org-dartlang-app';
+
+/// A builder which can output kernel files for a given sdk.
+///
+/// This creates kernel files based on [moduleExtension] files, which are what
+/// determine the module structure of an application.
+class KernelBuilder implements Builder {
+  @override
+  final Map<String, List<String>> buildExtensions;
+
+  final bool useIncrementalCompiler;
+
+  final bool trackUnusedInputs;
+
+  final String outputExtension;
+
+  final DartPlatform platform;
+
+  /// Whether this should create summary kernel files or full kernel files.
+  ///
+  /// Summary files only contain the "outline" of the module - you can think of
+  /// this as everything but the method bodies.
+  final bool summaryOnly;
+
+  /// The sdk kernel file for the current platform.
+  final String sdkKernelPath;
+
+  /// The root directory of the platform's dart SDK.
+  ///
+  /// If not provided, defaults to the directory of
+  /// [Platform.resolvedExecutable].
+  ///
+  /// On flutter this is the path to the root of the flutter_patched_sdk
+  /// directory, which contains the platform kernel files.
+  final String platformSdk;
+
+  /// The absolute path to the libraries file for the current platform.
+  ///
+  /// If not provided, defaults to "lib/libraries.json" in the sdk directory.
+  final String librariesPath;
+
+  /// The `--target` argument passed to the kernel worker.
+  ///
+  /// Optional. When omitted the [platform] name is used.
+  final String kernelTargetName;
+
+  /// Experiments to pass to kernel (as --enable-experiment=<experiment> args).
+  final Iterable<String> experiments;
+
+  KernelBuilder(
+      {@required this.platform,
+      @required this.summaryOnly,
+      @required this.sdkKernelPath,
+      @required this.outputExtension,
+      String librariesPath,
+      bool useIncrementalCompiler,
+      bool trackUnusedInputs,
+      String platformSdk,
+      String kernelTargetName,
+      Iterable<String> experiments})
+      : platformSdk = platformSdk ?? sdkDir,
+        kernelTargetName = kernelTargetName ?? platform.name,
+        librariesPath = librariesPath ??
+            p.join(platformSdk ?? sdkDir, 'lib', 'libraries.json'),
+        useIncrementalCompiler = useIncrementalCompiler ?? false,
+        trackUnusedInputs = trackUnusedInputs ?? false,
+        buildExtensions = {
+          moduleExtension(platform): [outputExtension]
+        },
+        experiments = experiments ?? [];
+
+  @override
+  Future build(BuildStep buildStep) async {
+    var module = Module.fromJson(
+        json.decode(await buildStep.readAsString(buildStep.inputId))
+            as Map<String, dynamic>);
+    // Entrypoints always have a `.module` file for ease of looking them up,
+    // but they might not be the primary source.
+    if (module.primarySource.changeExtension(moduleExtension(platform)) !=
+        buildStep.inputId) {
+      return;
+    }
+
+    try {
+      await _createKernel(
+          module: module,
+          buildStep: buildStep,
+          summaryOnly: summaryOnly,
+          outputExtension: outputExtension,
+          targetName: kernelTargetName,
+          dartSdkDir: platformSdk,
+          sdkKernelPath: sdkKernelPath,
+          librariesPath: librariesPath,
+          useIncrementalCompiler: useIncrementalCompiler,
+          trackUnusedInputs: trackUnusedInputs,
+          experiments: experiments);
+    } on MissingModulesException catch (e) {
+      log.severe(e.toString());
+    } on KernelException catch (e, s) {
+      log.severe(
+          'Error creating '
+          '${module.primarySource.changeExtension(outputExtension)}',
+          e,
+          s);
+    }
+  }
+}
+
+/// Creates a kernel file for [module].
+Future<void> _createKernel(
+    {@required Module module,
+    @required BuildStep buildStep,
+    @required bool summaryOnly,
+    @required String outputExtension,
+    @required String targetName,
+    @required String dartSdkDir,
+    @required String sdkKernelPath,
+    @required String librariesPath,
+    @required bool useIncrementalCompiler,
+    @required bool trackUnusedInputs,
+    @required Iterable<String> experiments}) async {
+  var request = WorkRequest();
+  var scratchSpace = await buildStep.fetchResource(scratchSpaceResource);
+  var outputId = module.primarySource.changeExtension(outputExtension);
+  var outputFile = scratchSpace.fileFor(outputId);
+
+  File packagesFile;
+  var kernelDeps = <AssetId>[];
+
+  // Maps the inputs paths we provide to the kernel worker to asset ids,
+  // if `trackUnusedInputs` is `true`.
+  Map<String, AssetId> kernelInputPathToId;
+  // If `trackUnusedInputs` is `true`, this is the file we will use to
+  // communicate the used inputs with the kernel worker.
+  File usedInputsFile;
+
+  await buildStep.trackStage('CollectDeps', () async {
+    var sourceDeps = <AssetId>[];
+
+    await _findModuleDeps(
+        module, kernelDeps, sourceDeps, buildStep, outputExtension);
+
+    var allAssetIds = <AssetId>{
+      ...module.sources,
+      ...kernelDeps,
+      ...sourceDeps,
+    };
+    await scratchSpace.ensureAssets(allAssetIds, buildStep);
+
+    packagesFile = await createPackagesFile(allAssetIds);
+    if (trackUnusedInputs) {
+      usedInputsFile = await File(p.join(
+              (await Directory.systemTemp.createTemp('kernel_builder_')).path,
+              'used_inputs.txt'))
+          .create();
+      kernelInputPathToId = {};
+    }
+
+    await _addRequestArguments(
+        request,
+        module,
+        kernelDeps,
+        targetName,
+        dartSdkDir,
+        sdkKernelPath,
+        librariesPath,
+        outputFile,
+        packagesFile,
+        summaryOnly,
+        useIncrementalCompiler,
+        buildStep,
+        experiments,
+        usedInputsFile: usedInputsFile,
+        kernelInputPathToId: kernelInputPathToId);
+  });
+
+  // We need to make sure and clean up the temp dir, even if we fail to compile.
+  try {
+    var frontendWorker = await buildStep.fetchResource(frontendDriverResource);
+    var response = await frontendWorker.doWork(request,
+        trackWork: (response) => buildStep
+            .trackStage('Kernel Generate', () => response, isExternal: true));
+    if (response.exitCode != EXIT_CODE_OK || !await outputFile.exists()) {
+      throw KernelException(
+          outputId, '${request.arguments.join(' ')}\n${response.output}');
+    }
+
+    if (response.output?.isEmpty == false) {
+      log.info(response.output);
+    }
+
+    // Copy the output back using the buildStep.
+    await scratchSpace.copyOutput(outputId, buildStep, requireContent: true);
+
+    // Note that we only want to do this on success, we can't trust the unused
+    // inputs if there is a failure.
+    if (usedInputsFile != null) {
+      await reportUnusedKernelInputs(
+          usedInputsFile, kernelDeps, kernelInputPathToId, buildStep);
+    }
+  } finally {
+    await packagesFile.parent.delete(recursive: true);
+    await usedInputsFile?.parent?.delete(recursive: true);
+  }
+}
+
+/// Reports any unused kernel inputs based on the [usedInputsFile] we get
+/// back from the kernel/ddk workers.
+///
+/// This file logs paths as they were given in the original [WorkRequest],
+/// so [inputPathToId] is used to map those paths back to the kernel asset ids.
+///
+/// This function will not report any unused dependencies if:
+///
+/// - It isn't able to match all reported used dependencies to an asset id (it
+///   would be unsafe to do so in that case).
+/// - No used dependencies are reported (it is assumed something went wrong
+///   or there were zero deps to begin with).
+Future<void> reportUnusedKernelInputs(
+    File usedInputsFile,
+    Iterable<AssetId> transitiveKernelDeps,
+    Map<String, AssetId> inputPathToId,
+    BuildStep buildStep) async {
+  var usedPaths = await usedInputsFile.readAsLines();
+  if (usedPaths.isEmpty || usedPaths.first == '') return;
+
+  String firstMissingInputPath;
+  var usedIds = usedPaths.map((usedPath) {
+    var id = inputPathToId[usedPath];
+    if (id == null) firstMissingInputPath ??= usedPath;
+    return id;
+  }).toSet();
+
+  if (firstMissingInputPath != null) {
+    log.warning('Error reporting unused kernel deps, unable to map path: '
+        '`$firstMissingInputPath` back to an asset id.\n\nPlease file an issue '
+        'at https://github.com/dart-lang/build/issues/new.');
+    return;
+  }
+
+  buildStep.reportUnusedAssets(
+      transitiveKernelDeps.where((id) => !usedIds.contains(id)));
+}
+
+/// Finds the transitive dependencies of [root] and categorizes them as
+/// [kernelDeps] or [sourceDeps].
+///
+/// A module will have it's kernel file in [kernelDeps] if it and all of it's
+/// transitive dependencies have readable kernel files. If any module has no
+/// readable kernel file then it, and all of it's dependents will be categorized
+/// as [sourceDeps] which will have all of their [Module.sources].
+Future<void> _findModuleDeps(
+    Module root,
+    List<AssetId> kernelDeps,
+    List<AssetId> sourceDeps,
+    BuildStep buildStep,
+    String outputExtension) async {
+  final resolvedModules = await _resolveTransitiveModules(root, buildStep);
+
+  final sourceOnly = await _parentsOfMissingKernelFiles(
+      resolvedModules, buildStep, outputExtension);
+
+  for (final module in resolvedModules) {
+    if (sourceOnly.contains(module.primarySource)) {
+      sourceDeps.addAll(module.sources);
+    } else {
+      kernelDeps.add(module.primarySource.changeExtension(outputExtension));
+    }
+  }
+}
+
+/// The transitive dependencies of [root], not including [root] itself.
+Future<List<Module>> _resolveTransitiveModules(
+    Module root, BuildStep buildStep) async {
+  var missing = <AssetId>{};
+  var modules = await crawlAsync<AssetId, Module>(
+          [root.primarySource],
+          (id) => buildStep.fetchResource(moduleCache).then((c) async {
+                var moduleId =
+                    id.changeExtension(moduleExtension(root.platform));
+                var module = await c.find(moduleId, buildStep);
+                if (module == null) {
+                  missing.add(moduleId);
+                } else if (module.isMissing) {
+                  missing.add(module.primarySource);
+                }
+                return module;
+              }),
+          (id, module) => module.directDependencies)
+      .skip(1) // Skip the root.
+      .toList();
+
+  if (missing.isNotEmpty) {
+    throw await MissingModulesException.create(
+        missing, [...modules, root], buildStep);
+  }
+
+  return modules;
+}
+
+/// Finds the primary source of all transitive parents of any module which does
+/// not have a readable kernel file.
+///
+/// Inverts the direction of the graph and then crawls to all reachables nodes
+/// from the modules which do not have a readable kernel file
+Future<Set<AssetId>> _parentsOfMissingKernelFiles(
+    List<Module> modules, BuildStep buildStep, String outputExtension) async {
+  final sourceOnly = <AssetId>{};
+  final parents = <AssetId, Set<AssetId>>{};
+  for (final module in modules) {
+    for (final dep in module.directDependencies) {
+      parents.putIfAbsent(dep, () => <AssetId>{}).add(module.primarySource);
+    }
+    if (!await buildStep
+        .canRead(module.primarySource.changeExtension(outputExtension))) {
+      sourceOnly.add(module.primarySource);
+    }
+  }
+  final toCrawl = Queue.of(sourceOnly);
+  while (toCrawl.isNotEmpty) {
+    final current = toCrawl.removeFirst();
+    if (!parents.containsKey(current)) continue;
+    for (final next in parents[current]) {
+      if (!sourceOnly.add(next)) {
+        toCrawl.add(next);
+      }
+    }
+  }
+  return sourceOnly;
+}
+
+/// Fills in all the required arguments for [request] in order to compile the
+/// kernel file for [module].
+Future<void> _addRequestArguments(
+  WorkRequest request,
+  Module module,
+  Iterable<AssetId> transitiveKernelDeps,
+  String targetName,
+  String sdkDir,
+  String sdkKernelPath,
+  String librariesPath,
+  File outputFile,
+  File packagesFile,
+  bool summaryOnly,
+  bool useIncrementalCompiler,
+  AssetReader reader,
+  Iterable<String> experiments, {
+  File usedInputsFile,
+  Map<String, AssetId> kernelInputPathToId,
+}) async {
+  // Add all kernel outlines as summary inputs, with digests.
+  var inputs = await Future.wait(transitiveKernelDeps.map((id) async {
+    var relativePath = p.url.relative(scratchSpace.fileFor(id).uri.path,
+        from: scratchSpace.tempDir.uri.path);
+    var path = '$multiRootScheme:///$relativePath';
+    if (kernelInputPathToId != null) {
+      kernelInputPathToId[path] = id;
+    }
+    return Input()
+      ..path = path
+      ..digest = (await reader.digest(id)).bytes;
+  }));
+  request.arguments.addAll([
+    '--dart-sdk-summary=${Uri.file(p.join(sdkDir, sdkKernelPath))}',
+    '--output=${outputFile.path}',
+    '--packages-file=${packagesFile.uri}',
+    '--multi-root-scheme=$multiRootScheme',
+    '--exclude-non-sources',
+    summaryOnly ? '--summary-only' : '--no-summary-only',
+    '--target=$targetName',
+    '--libraries-file=${p.toUri(librariesPath)}',
+    if (useIncrementalCompiler) ...[
+      '--reuse-compiler-result',
+      '--use-incremental-compiler',
+    ],
+    if (usedInputsFile != null)
+      '--used-inputs=${usedInputsFile.uri.toFilePath()}',
+    for (var input in inputs)
+      '--input-${summaryOnly ? 'summary' : 'linked'}=${input.path}',
+    for (var experiment in experiments) '--enable-experiment=$experiment',
+    for (var source in module.sources) _sourceArg(source),
+  ]);
+
+  request.inputs.addAll([
+    ...inputs,
+    Input()
+      ..path = '${Uri.file(p.join(sdkDir, sdkKernelPath))}'
+      // Sdk updates fully invalidate the build anyways.
+      ..digest = md5.convert(utf8.encode(targetName)).bytes,
+  ]);
+}
+
+String _sourceArg(AssetId id) {
+  var uri = id.path.startsWith('lib')
+      ? canonicalUriFor(id)
+      : '$multiRootScheme:///${id.path}';
+  return '--source=$uri';
+}
diff --git a/build_modules/lib/src/meta_module.dart b/build_modules/lib/src/meta_module.dart
new file mode 100644
index 0000000..476cace
--- /dev/null
+++ b/build_modules/lib/src/meta_module.dart
@@ -0,0 +1,271 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'package:build/build.dart';
+import 'package:graphs/graphs.dart';
+import 'package:path/path.dart' as p;
+import 'package:json_annotation/json_annotation.dart';
+
+import 'common.dart';
+import 'module_library.dart';
+import 'modules.dart';
+import 'platform.dart';
+
+part 'meta_module.g.dart';
+
+/// Returns the top level directory in [path].
+///
+/// Throws an [ArgumentError] if [path] is just a filename with no directory.
+String _topLevelDir(String path) {
+  var parts = p.url.split(p.url.normalize(path));
+  String error;
+  if (parts.length == 1) {
+    error = 'The path `$path` does not contain a directory.';
+  } else if (parts.first == '..') {
+    error = 'The path `$path` reaches outside the root directory.';
+  }
+  if (error != null) {
+    throw ArgumentError(
+        'Cannot compute top level dir for path `$path`. $error');
+  }
+  return parts.first;
+}
+
+/// Creates a module containing [componentLibraries].
+Module _moduleForComponent(
+    List<ModuleLibrary> componentLibraries, DartPlatform platform) {
+  // Name components based on first alphabetically sorted node, preferring
+  // public srcs (not under lib/src).
+  var sources = componentLibraries.map((n) => n.id).toSet();
+  var nonSrcIds = sources.where((id) => !id.path.startsWith('lib/src/'));
+  var primaryId =
+      nonSrcIds.isNotEmpty ? nonSrcIds.reduce(_min) : sources.reduce(_min);
+  // Expand to include all the part files of each node, these aren't
+  // included as individual `_AssetNodes`s in `connectedComponents`.
+  sources.addAll(componentLibraries.expand((n) => n.parts));
+  var directDependencies = <AssetId>{}
+    ..addAll(componentLibraries.expand((n) => n.depsForPlatform(platform)))
+    ..removeAll(sources);
+  var isSupported = componentLibraries
+      .expand((l) => l.sdkDeps)
+      .every(platform.supportsLibrary);
+  return Module(primaryId, sources, directDependencies, platform, isSupported);
+}
+
+/// Gets the local (same top level dir of the same package) transitive deps of
+/// [module] using [assetsToModules].
+Set<AssetId> _localTransitiveDeps(
+    Module module, Map<AssetId, Module> assetsToModules) {
+  var localTransitiveDeps = <AssetId>{};
+  var nextIds = module.directDependencies;
+  var seenIds = <AssetId>{};
+  while (nextIds.isNotEmpty) {
+    var ids = nextIds;
+    seenIds.addAll(ids);
+    nextIds = <AssetId>{};
+    for (var id in ids) {
+      var module = assetsToModules[id];
+      if (module == null) continue; // Skip non-local modules
+      if (localTransitiveDeps.add(module.primarySource)) {
+        nextIds.addAll(module.directDependencies.difference(seenIds));
+      }
+    }
+  }
+  return localTransitiveDeps;
+}
+
+/// Creates a map of modules to the entrypoint modules that transitively
+/// depend on those modules.
+Map<AssetId, Set<AssetId>> _findReverseEntrypointDeps(
+    Iterable<Module> entrypointModules, Iterable<Module> modules) {
+  var reverseDeps = <AssetId, Set<AssetId>>{};
+  var assetsToModules = <AssetId, Module>{};
+  for (var module in modules) {
+    for (var assetId in module.sources) {
+      assetsToModules[assetId] = module;
+    }
+  }
+  for (var module in entrypointModules) {
+    for (var moduleDep in _localTransitiveDeps(module, assetsToModules)) {
+      reverseDeps
+          .putIfAbsent(moduleDep, () => <AssetId>{})
+          .add(module.primarySource);
+    }
+  }
+  return reverseDeps;
+}
+
+/// Merges [modules] into a minimum set of [Module]s using the
+/// following rules:
+///
+///   * If it is an entrypoint module do not merge it.
+///   * If it is not depended on my any entrypoint do not merge it.
+///   * If it is depended on by no entrypoint merge it into the entrypoint
+///   modules
+///   * Else merge it into with others that are depended on by the same set of
+///   entrypoints
+List<Module> _mergeModules(Iterable<Module> modules, Set<AssetId> entrypoints) {
+  var entrypointModules =
+      modules.where((m) => m.sources.any(entrypoints.contains)).toList();
+
+  // Groups of modules that can be merged into an existing entrypoint module.
+  var entrypointModuleGroups = {
+    for (var m in entrypointModules) m.primarySource: [m],
+  };
+
+  // Maps modules to entrypoint modules that transitively depend on them.
+  var modulesToEntryPoints =
+      _findReverseEntrypointDeps(entrypointModules, modules);
+
+  // Modules which are not depended on by any entrypoint
+  var standaloneModules = <Module>[];
+
+  // Modules which are merged with others.
+  var mergedModules = <String, List<Module>>{};
+
+  for (var module in modules) {
+    // Skip entrypoint modules.
+    if (entrypointModuleGroups.containsKey(module.primarySource)) continue;
+
+    // The entry points that transitively import this module.
+    var entrypointIds = modulesToEntryPoints[module.primarySource];
+
+    // If no entrypoint imports the module, just leave it alone.
+    if (entrypointIds == null || entrypointIds.isEmpty) {
+      standaloneModules.add(module);
+      continue;
+    }
+
+    // If there are multiple entry points for a given resource we must create
+    // a new shared module. Use `$` to signal that it is a shared module.
+    if (entrypointIds.length > 1) {
+      var mId = (entrypointIds.toList()..sort()).map((m) => m.path).join('\$');
+      mergedModules.putIfAbsent(mId, () => []).add(module);
+    } else {
+      entrypointModuleGroups[entrypointIds.single].add(module);
+    }
+  }
+
+  return mergedModules.values
+      .map(Module.merge)
+      .map(_withConsistentPrimarySource)
+      .followedBy(entrypointModuleGroups.values.map(Module.merge))
+      .followedBy(standaloneModules)
+      .toList();
+}
+
+Module _withConsistentPrimarySource(Module m) => Module(m.sources.reduce(_min),
+    m.sources, m.directDependencies, m.platform, m.isSupported);
+
+T _min<T extends Comparable<T>>(T a, T b) => a.compareTo(b) < 0 ? a : b;
+
+/// Compute modules for the  internal strongly connected components of
+/// [libraries].
+///
+/// This should only be called with [libraries] all in the same package and top
+/// level directory within the package.
+///
+/// A dependency is considered "internal" if it is within [libraries]. Any
+/// "external" deps are ignored during this computation since we are only
+/// considering the strongly connected components within [libraries], but they
+/// will be maintained as a dependency of the module to be used at a later step.
+///
+/// Part files are also tracked but ignored during computation of strongly
+/// connected components, as they must always be a part of the containing
+/// library's module.
+List<Module> _computeModules(
+    Map<AssetId, ModuleLibrary> libraries, DartPlatform platform) {
+  assert(() {
+    var dir = _topLevelDir(libraries.values.first.id.path);
+    return libraries.values.every((l) => _topLevelDir(l.id.path) == dir);
+  }());
+
+  final connectedComponents = stronglyConnectedComponents<ModuleLibrary>(
+      libraries.values,
+      (n) => n
+          .depsForPlatform(platform)
+          // Only "internal" dependencies
+          .where(libraries.containsKey)
+          .map((dep) => libraries[dep]),
+      equals: (a, b) => a.id == b.id,
+      hashCode: (l) => l.id.hashCode);
+
+  final entryIds =
+      libraries.values.where((l) => l.isEntryPoint).map((l) => l.id).toSet();
+  return _mergeModules(
+      connectedComponents.map((c) => _moduleForComponent(c, platform)),
+      entryIds);
+}
+
+@JsonSerializable()
+class MetaModule {
+  @JsonKey(name: 'm', nullable: false)
+  final List<Module> modules;
+
+  MetaModule(List<Module> modules) : modules = List.unmodifiable(modules);
+
+  /// Generated factory constructor.
+  factory MetaModule.fromJson(Map<String, dynamic> json) =>
+      _$MetaModuleFromJson(json);
+
+  Map<String, dynamic> toJson() => _$MetaModuleToJson(this);
+
+  static Future<MetaModule> forLibraries(
+      AssetReader reader,
+      List<AssetId> libraryIds,
+      ModuleStrategy strategy,
+      DartPlatform platform) async {
+    var libraries = <ModuleLibrary>[];
+    for (var id in libraryIds) {
+      libraries.add(ModuleLibrary.deserialize(
+          id.changeExtension('').changeExtension('.dart'),
+          await reader.readAsString(id)));
+    }
+    switch (strategy) {
+      case ModuleStrategy.fine:
+        return _fineModulesForLibraries(reader, libraries, platform);
+      case ModuleStrategy.coarse:
+        return _coarseModulesForLibraries(reader, libraries, platform);
+    }
+    throw StateError('Unrecognized module strategy $strategy');
+  }
+}
+
+MetaModule _coarseModulesForLibraries(
+    AssetReader reader, List<ModuleLibrary> libraries, DartPlatform platform) {
+  var librariesByDirectory = <String, Map<AssetId, ModuleLibrary>>{};
+  for (var library in libraries) {
+    final dir = _topLevelDir(library.id.path);
+    if (!librariesByDirectory.containsKey(dir)) {
+      librariesByDirectory[dir] = <AssetId, ModuleLibrary>{};
+    }
+    librariesByDirectory[dir][library.id] = library;
+  }
+  final modules = librariesByDirectory.values
+      .expand((libs) => _computeModules(libs, platform))
+      .toList();
+  _sortModules(modules);
+  return MetaModule(modules);
+}
+
+MetaModule _fineModulesForLibraries(
+    AssetReader reader, List<ModuleLibrary> libraries, DartPlatform platform) {
+  var modules = libraries
+      .map((library) => Module(
+          library.id,
+          library.parts.followedBy([library.id]),
+          library.depsForPlatform(platform),
+          platform,
+          library.sdkDeps.every(platform.supportsLibrary)))
+      .toList();
+  _sortModules(modules);
+  return MetaModule(modules);
+}
+
+/// Sorts [modules] in place so we get deterministic output.
+void _sortModules(List<Module> modules) {
+  modules.sort((a, b) => a.primarySource.compareTo(b.primarySource));
+}
diff --git a/build_modules/lib/src/meta_module.g.dart b/build_modules/lib/src/meta_module.g.dart
new file mode 100644
index 0000000..908194a
--- /dev/null
+++ b/build_modules/lib/src/meta_module.g.dart
@@ -0,0 +1,20 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'meta_module.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+MetaModule _$MetaModuleFromJson(Map<String, dynamic> json) {
+  return MetaModule(
+    (json['m'] as List)
+        .map((e) => Module.fromJson(e as Map<String, dynamic>))
+        .toList(),
+  );
+}
+
+Map<String, dynamic> _$MetaModuleToJson(MetaModule instance) =>
+    <String, dynamic>{
+      'm': instance.modules,
+    };
diff --git a/build_modules/lib/src/meta_module_builder.dart b/build_modules/lib/src/meta_module_builder.dart
new file mode 100644
index 0000000..bc92e96
--- /dev/null
+++ b/build_modules/lib/src/meta_module_builder.dart
@@ -0,0 +1,56 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'package:build/build.dart';
+import 'package:glob/glob.dart';
+
+import 'common.dart';
+import 'meta_module.dart';
+import 'module_cache.dart';
+import 'module_library_builder.dart';
+import 'platform.dart';
+
+/// The extension for serialized meta module assets for a specific platform.
+String metaModuleExtension(DartPlatform platform) =>
+    '.${platform.name}.meta_module.raw';
+
+/// Creates `.meta_module` file for any Dart library.
+///
+/// This file contains information about the full computed
+/// module structure for the package.
+class MetaModuleBuilder implements Builder {
+  @override
+  final Map<String, List<String>> buildExtensions;
+
+  final ModuleStrategy strategy;
+
+  final DartPlatform _platform;
+
+  MetaModuleBuilder(this._platform, {ModuleStrategy strategy})
+      : strategy = strategy ?? ModuleStrategy.coarse,
+        buildExtensions = {
+          r'$lib$': [metaModuleExtension(_platform)]
+        };
+
+  factory MetaModuleBuilder.forOptions(
+          DartPlatform platform, BuilderOptions options) =>
+      MetaModuleBuilder(platform, strategy: moduleStrategy(options));
+
+  @override
+  Future build(BuildStep buildStep) async {
+    if (buildStep.inputId.package == r'$sdk') return;
+
+    var libraryAssets =
+        await buildStep.findAssets(Glob('**$moduleLibraryExtension')).toList();
+
+    var metaModule = await MetaModule.forLibraries(
+        buildStep, libraryAssets, strategy, _platform);
+    var id = AssetId(
+        buildStep.inputId.package, 'lib/${metaModuleExtension(_platform)}');
+    var metaModules = await buildStep.fetchResource(metaModuleCache);
+    await metaModules.write(id, buildStep, metaModule);
+  }
+}
diff --git a/build_modules/lib/src/meta_module_clean_builder.dart b/build_modules/lib/src/meta_module_clean_builder.dart
new file mode 100644
index 0000000..fb05802
--- /dev/null
+++ b/build_modules/lib/src/meta_module_clean_builder.dart
@@ -0,0 +1,167 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:collection';
+import 'dart:convert';
+
+import 'package:build/build.dart';
+import 'package:graphs/graphs.dart';
+
+import 'meta_module.dart';
+import 'meta_module_builder.dart';
+import 'module_cache.dart';
+import 'modules.dart';
+import 'platform.dart';
+
+/// The extension for serialized clean meta module assets.
+///
+/// Clean in this context means all dependencies are primary sources.
+/// Furthermore cyclic modules are merged into a single module.
+String metaModuleCleanExtension(DartPlatform platform) =>
+    '.${platform.name}.meta_module.clean';
+
+/// Creates `.meta_module.clean` file for any Dart library.
+///
+/// This file contains information about the full computed
+/// module structure for the package where each module's dependencies
+/// are only primary sources.
+///
+/// Note if the raw meta module file can't be found for any of the
+/// module's transitive dependencies there will be no output.
+class MetaModuleCleanBuilder implements Builder {
+  @override
+  final Map<String, List<String>> buildExtensions;
+
+  final DartPlatform _platform;
+
+  MetaModuleCleanBuilder(this._platform)
+      : buildExtensions = {
+          metaModuleExtension(_platform): [metaModuleCleanExtension(_platform)]
+        };
+
+  @override
+  Future build(BuildStep buildStep) async {
+    var assetToModule = (await buildStep.fetchResource(_assetToModule))
+        .putIfAbsent(_platform, () => {});
+    var assetToPrimary = (await buildStep.fetchResource(_assetToPrimary))
+        .putIfAbsent(_platform, () => {});
+    var modules = await _transitiveModules(
+        buildStep, buildStep.inputId, assetToModule, assetToPrimary, _platform);
+    var connectedComponents = stronglyConnectedComponents<Module>(
+        modules,
+        (m) => m.directDependencies.map((d) =>
+            assetToModule[d] ??
+            Module(d, [d], [], _platform, false, isMissing: true)),
+        equals: (a, b) => a.primarySource == b.primarySource,
+        hashCode: (m) => m.primarySource.hashCode);
+    Module merge(List<Module> c) =>
+        _mergeComponent(c, assetToPrimary, _platform);
+    bool primarySourceInPackage(Module m) =>
+        m.primarySource.package == buildStep.inputId.package;
+    // Ensure deterministic output by sorting the modules.
+    var cleanModules = SplayTreeSet<Module>(
+        (a, b) => a.primarySource.compareTo(b.primarySource))
+      ..addAll(connectedComponents.map(merge).where(primarySourceInPackage));
+    await buildStep.writeAsString(
+        AssetId(buildStep.inputId.package,
+            'lib/${metaModuleCleanExtension(_platform)}'),
+        jsonEncode(MetaModule(cleanModules.toList())));
+  }
+}
+
+/// Map of [AssetId] to corresponding non clean containing [Module] per
+/// [DartPlatform].
+final _assetToModule = Resource<Map<DartPlatform, Map<AssetId, Module>>>(
+    () => {},
+    dispose: (map) => map.clear());
+
+/// Map of [AssetId] to corresponding primary [AssetId] within the same
+/// clean [Module] per platform.
+final _assetToPrimary = Resource<Map<DartPlatform, Map<AssetId, AssetId>>>(
+    () => {},
+    dispose: (map) => map.clear());
+
+/// Returns a set of all modules transitively reachable from the provided meta
+/// module asset.
+Future<Set<Module>> _transitiveModules(
+    BuildStep buildStep,
+    AssetId metaAsset,
+    Map<AssetId, Module> assetToModule,
+    Map<AssetId, AssetId> assetToPrimary,
+    DartPlatform platform) async {
+  var dependentModules = <Module>{};
+  // Ensures we only process a meta file once.
+  var seenMetas = <AssetId>{}..add(metaAsset);
+  var metaModules = await buildStep.fetchResource(metaModuleCache);
+  var meta = await metaModules.find(buildStep.inputId, buildStep);
+  var nextModules = List.of(meta.modules);
+  while (nextModules.isNotEmpty) {
+    var module = nextModules.removeLast();
+    dependentModules.add(module);
+    for (var source in module.sources) {
+      assetToModule[source] = module;
+      // The asset to primary map will be updated when the merged modules are
+      // created. This is why we can't use the assetToModule map.
+      assetToPrimary[source] = module.primarySource;
+    }
+    for (var dep in module.directDependencies) {
+      var depMetaAsset =
+          AssetId(dep.package, 'lib/${metaModuleExtension(platform)}');
+      // The testing package is an odd package used by package:frontend_end
+      // which doesn't really exist.
+      // https://github.com/dart-lang/sdk/issues/32952
+      if (seenMetas.contains(depMetaAsset) || dep.package == 'testing') {
+        continue;
+      }
+      seenMetas.add(depMetaAsset);
+      if (!await buildStep.canRead(depMetaAsset)) {
+        log.warning('Unable to read module information for '
+            'package:${depMetaAsset.package}, make sure you have a dependency '
+            'on it in your pubspec.');
+        continue;
+      }
+      var depMeta = await metaModules.find(depMetaAsset, buildStep);
+      nextModules.addAll(depMeta.modules);
+    }
+  }
+  return dependentModules;
+}
+
+/// Merges the modules in a strongly connected component.
+///
+/// Note this will clean the module dependencies as the merge happens.
+/// The result will be that all dependencies are primary sources.
+Module _mergeComponent(List<Module> connectedComponent,
+    Map<AssetId, AssetId> assetToPrimary, DartPlatform platform) {
+  var sources = <AssetId>{};
+  var deps = <AssetId>{};
+  // Sort the modules to deterministicly select the primary source.
+  var components =
+      SplayTreeSet<Module>((a, b) => a.primarySource.compareTo(b.primarySource))
+        ..addAll(connectedComponent);
+  var primarySource = components.first.primarySource;
+  var isSupported = true;
+  for (var module in connectedComponent) {
+    sources.addAll(module.sources);
+    isSupported = isSupported && module.isSupported;
+    for (var dep in module.directDependencies) {
+      var primaryDep = assetToPrimary[dep];
+      if (primaryDep == null) continue;
+      // This dep is now merged into sources so skip it.
+      if (!components
+          .map((c) => c.sources)
+          .any((s) => s.contains(primaryDep))) {
+        deps.add(primaryDep);
+      }
+    }
+  }
+  // Update map so that sources now point to the merged module.
+  var mergedModule =
+      Module(primarySource, sources, deps, platform, isSupported);
+  for (var source in mergedModule.sources) {
+    assetToPrimary[source] = mergedModule.primarySource;
+  }
+  return mergedModule;
+}
diff --git a/build_modules/lib/src/module_builder.dart b/build_modules/lib/src/module_builder.dart
new file mode 100644
index 0000000..ee8dcb9
--- /dev/null
+++ b/build_modules/lib/src/module_builder.dart
@@ -0,0 +1,59 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'package:build/build.dart';
+
+import 'meta_module_clean_builder.dart';
+import 'module_cache.dart';
+import 'module_library.dart';
+import 'module_library_builder.dart' show moduleLibraryExtension;
+import 'modules.dart';
+import 'platform.dart';
+
+/// The extension for serialized module assets.
+String moduleExtension(DartPlatform platform) => '.${platform.name}.module';
+
+/// Creates `.module` files for any `.dart` file that is the primary dart
+/// source of a [Module].
+class ModuleBuilder implements Builder {
+  final DartPlatform _platform;
+
+  ModuleBuilder(this._platform)
+      : buildExtensions = {
+          '.dart': [moduleExtension(_platform)]
+        };
+
+  @override
+  final Map<String, List<String>> buildExtensions;
+
+  @override
+  Future build(BuildStep buildStep) async {
+    final cleanMetaModules = await buildStep.fetchResource(metaModuleCache);
+    final metaModule = await cleanMetaModules.find(
+        AssetId(buildStep.inputId.package,
+            'lib/${metaModuleCleanExtension(_platform)}'),
+        buildStep);
+    var outputModule = metaModule.modules.firstWhere(
+        (m) => m.primarySource == buildStep.inputId,
+        orElse: () => null);
+    if (outputModule == null) {
+      final serializedLibrary = await buildStep.readAsString(
+          buildStep.inputId.changeExtension(moduleLibraryExtension));
+      final libraryModule =
+          ModuleLibrary.deserialize(buildStep.inputId, serializedLibrary);
+      if (libraryModule.hasMain) {
+        outputModule = metaModule.modules
+            .firstWhere((m) => m.sources.contains(buildStep.inputId));
+      }
+    }
+    if (outputModule == null) return;
+    final modules = await buildStep.fetchResource(moduleCache);
+    await modules.write(
+        buildStep.inputId.changeExtension(moduleExtension(_platform)),
+        buildStep,
+        outputModule);
+  }
+}
diff --git a/build_modules/lib/src/module_cache.dart b/build_modules/lib/src/module_cache.dart
new file mode 100644
index 0000000..5a91108
--- /dev/null
+++ b/build_modules/lib/src/module_cache.dart
@@ -0,0 +1,104 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS 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 'dart:convert';
+
+import 'package:async/async.dart';
+import 'package:build/build.dart';
+import 'package:crypto/crypto.dart';
+
+import 'meta_module.dart';
+import 'modules.dart';
+
+Map<String, dynamic> _deserialize(List<int> bytes) =>
+    jsonDecode(utf8.decode(bytes)) as Map<String, dynamic>;
+
+List<int> _serialize(Map<String, dynamic> data) =>
+    utf8.encode(jsonEncode(data));
+
+final metaModuleCache = DecodingCache.resource(
+    (m) => MetaModule.fromJson(_deserialize(m)), (m) => _serialize(m.toJson()));
+
+final moduleCache = DecodingCache.resource(
+    (m) => Module.fromJson(_deserialize(m)), (m) => _serialize(m.toJson()));
+
+/// A cache of objects decoded from written assets suitable for use as a
+/// [Resource].
+///
+/// Instances that are decoded will be cached throughout the duration of a build
+/// and invalidated between builds. Instances that are shared through the cache
+/// should be treated as immutable to avoid leaking any information which was
+/// not loaded from the underlying asset.
+class DecodingCache<T> {
+  /// Create a [Resource] which can decoded instances of [T] serialized via json
+  /// to assets.
+  static Resource<DecodingCache<T>> resource<T>(
+          T Function(List<int>) fromBytes, List<int> Function(T) toBytes) =>
+      Resource<DecodingCache<T>>(() => DecodingCache._(fromBytes, toBytes),
+          dispose: (c) => c._dispose());
+
+  final _cached = <AssetId, _Entry<T>>{};
+
+  final T Function(List<int>) _fromBytes;
+  final List<int> Function(T) _toBytes;
+
+  DecodingCache._(this._fromBytes, this._toBytes);
+
+  void _dispose() {
+    _cached.removeWhere((_, entry) => entry.digest == null);
+    for (var entry in _cached.values) {
+      entry.needsCheck = true;
+    }
+  }
+
+  /// Find and deserialize a [T] stored in [id].
+  ///
+  /// If the asset at [id] is unreadable the returned future will resolve to
+  /// `null`. If the instance is cached it will not be decoded again, but the
+  /// content dependencies will be tracked through [reader].
+  Future<T> find(AssetId id, AssetReader reader) async {
+    if (!await reader.canRead(id)) return null;
+    _Entry<T> entry;
+    if (!_cached.containsKey(id)) {
+      entry = _cached[id] = _Entry()
+        ..needsCheck = false
+        ..value = Result.capture(reader.readAsBytes(id).then(_fromBytes))
+        ..digest = Result.capture(reader.digest(id));
+    } else {
+      entry = _cached[id];
+      if (entry.needsCheck) {
+        await (entry.onGoingCheck ??= () async {
+          var previousDigest = await Result.release(entry.digest);
+          entry.digest = Result.capture(reader.digest(id));
+          if (await Result.release(entry.digest) != previousDigest) {
+            entry.value =
+                Result.capture(reader.readAsBytes(id).then(_fromBytes));
+          }
+          entry
+            ..needsCheck = false
+            ..onGoingCheck = null;
+        }());
+      }
+    }
+    return Result.release(entry.value);
+  }
+
+  /// Serialized and write a [T] to [id].
+  ///
+  /// The instance will be cached so that later calls to [find] may return the
+  /// instances without deserializing it.
+  Future<void> write(AssetId id, AssetWriter writer, T instance) async {
+    await writer.writeAsBytes(id, _toBytes(instance));
+    _cached[id] = _Entry()
+      ..needsCheck = false
+      ..value = Result.capture(Future.value(instance));
+  }
+}
+
+class _Entry<T> {
+  bool needsCheck = false;
+  Future<Result<T>> value;
+  Future<Result<Digest>> digest;
+  Future<void> onGoingCheck;
+}
diff --git a/build_modules/lib/src/module_cleanup.dart b/build_modules/lib/src/module_cleanup.dart
new file mode 100644
index 0000000..ff1af73
--- /dev/null
+++ b/build_modules/lib/src/module_cleanup.dart
@@ -0,0 +1,24 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'package:build/build.dart';
+
+import 'module_library_builder.dart';
+
+class ModuleCleanup implements PostProcessBuilder {
+  const ModuleCleanup();
+
+  @override
+  void build(PostProcessBuildStep buildStep) {
+    buildStep.deletePrimaryInput();
+  }
+
+  @override
+  final inputExtensions = const [
+    moduleLibraryExtension,
+    '.meta_module.raw',
+    '.meta_module.clean',
+    '.module',
+  ];
+}
diff --git a/build_modules/lib/src/module_library.dart b/build_modules/lib/src/module_library.dart
new file mode 100644
index 0000000..c00c93a
--- /dev/null
+++ b/build_modules/lib/src/module_library.dart
@@ -0,0 +1,224 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:convert';
+
+// ignore: deprecated_member_use
+import 'package:analyzer/analyzer.dart';
+import 'package:build/build.dart';
+import 'package:meta/meta.dart';
+
+import 'platform.dart';
+
+/// A Dart library within a module.
+///
+/// Modules can be computed based on library dependencies (imports and exports)
+/// and parts.
+class ModuleLibrary {
+  /// The AssetId of the original Dart source file.
+  final AssetId id;
+
+  /// Whether this library can be imported.
+  ///
+  /// This will be false if the source file is a "part of", or imports code that
+  /// can't be used outside the SDK.
+  final bool isImportable;
+
+  /// Whether this library is an entrypoint.
+  ///
+  /// True if the library is in `lib/` but not `lib/src`, or if it is outside of
+  /// `lib/` and contains a `main` method. Always false if this is not an
+  /// importable library.
+  final bool isEntryPoint;
+
+  /// Deps that are imported with a conditional import.
+  ///
+  /// Keys are the stringified ast node for the conditional, and the default
+  /// import is under the magic `$default` key.
+  final List<Map<String, AssetId>> conditionalDeps;
+
+  /// The IDs of libraries that are imported or exported by this library.
+  ///
+  /// Null if this is not an importable library.
+  final Set<AssetId> _deps;
+
+  /// The "part" files for this library.
+  ///
+  /// Null if this is not an importable library.
+  final Set<AssetId> parts;
+
+  /// The `dart:` libraries that this library directly depends on.
+  final Set<String> sdkDeps;
+
+  /// Whether this library has a `main` function.
+  final bool hasMain;
+
+  ModuleLibrary._(this.id,
+      {@required this.isEntryPoint,
+      @required Set<AssetId> deps,
+      @required this.parts,
+      @required this.conditionalDeps,
+      @required this.sdkDeps,
+      @required this.hasMain})
+      : _deps = deps,
+        isImportable = true;
+
+  ModuleLibrary._nonImportable(this.id)
+      : isImportable = false,
+        isEntryPoint = false,
+        _deps = null,
+        parts = null,
+        conditionalDeps = null,
+        sdkDeps = null,
+        hasMain = false;
+
+  factory ModuleLibrary._fromCompilationUnit(
+      AssetId id, bool isEntryPoint, CompilationUnit parsed) {
+    var deps = <AssetId>{};
+    var parts = <AssetId>{};
+    var sdkDeps = <String>{};
+    var conditionalDeps = <Map<String, AssetId>>[];
+    for (var directive in parsed.directives) {
+      if (directive is! UriBasedDirective) continue;
+      var path = (directive as UriBasedDirective).uri.stringValue;
+      var uri = Uri.parse(path);
+      if (uri.isScheme('dart-ext')) {
+        // TODO: What should we do for native extensions?
+        continue;
+      }
+      if (uri.scheme == 'dart') {
+        sdkDeps.add(uri.path);
+        continue;
+      }
+      var linkedId = AssetId.resolve(path, from: id);
+      if (linkedId == null) continue;
+      if (directive is PartDirective) {
+        parts.add(linkedId);
+        continue;
+      }
+
+      List<Configuration> conditionalDirectiveConfigurations;
+
+      if (directive is ImportDirective && directive.configurations.isNotEmpty) {
+        conditionalDirectiveConfigurations = directive.configurations;
+      } else if (directive is ExportDirective &&
+          directive.configurations.isNotEmpty) {
+        conditionalDirectiveConfigurations = directive.configurations;
+      }
+      if (conditionalDirectiveConfigurations != null) {
+        var conditions = <String, AssetId>{r'$default': linkedId};
+        for (var condition in conditionalDirectiveConfigurations) {
+          if (Uri.parse(condition.uri.stringValue).scheme == 'dart') {
+            throw ArgumentError('Unsupported conditional import of '
+                '`${condition.uri.stringValue}` found in $id.');
+          }
+          conditions[condition.name.toSource()] =
+              AssetId.resolve(condition.uri.stringValue, from: id);
+        }
+        conditionalDeps.add(conditions);
+      } else {
+        deps.add(linkedId);
+      }
+    }
+    return ModuleLibrary._(id,
+        isEntryPoint: isEntryPoint,
+        deps: deps,
+        parts: parts,
+        sdkDeps: sdkDeps,
+        conditionalDeps: conditionalDeps,
+        hasMain: _hasMainMethod(parsed));
+  }
+
+  /// Parse the directives from [source] and compute the library information.
+  static ModuleLibrary fromSource(AssetId id, String source) {
+    final isLibDir = id.path.startsWith('lib/');
+    // ignore: deprecated_member_use
+    final parsed = parseCompilationUnit(source,
+        name: id.path, suppressErrors: true, parseFunctionBodies: false);
+    // Packages within the SDK but published might have libraries that can't be
+    // used outside the SDK.
+    if (parsed.directives.any((d) =>
+        d is UriBasedDirective &&
+        d.uri.stringValue.startsWith('dart:_') &&
+        id.package != 'dart_internal')) {
+      return ModuleLibrary._nonImportable(id);
+    }
+    if (_isPart(parsed)) {
+      return ModuleLibrary._nonImportable(id);
+    }
+
+    final isEntryPoint =
+        (isLibDir && !id.path.startsWith('lib/src/')) || _hasMainMethod(parsed);
+    return ModuleLibrary._fromCompilationUnit(id, isEntryPoint, parsed);
+  }
+
+  /// Parses the output of [serialize] back into a [ModuleLibrary].
+  ///
+  /// Importable libraries can be round tripped to a String. Non-importable
+  /// libraries should not be printed or parsed.
+  factory ModuleLibrary.deserialize(AssetId id, String encoded) {
+    var json = jsonDecode(encoded);
+
+    return ModuleLibrary._(id,
+        isEntryPoint: json['isEntrypoint'] as bool,
+        deps: _deserializeAssetIds(json['deps'] as Iterable),
+        parts: _deserializeAssetIds(json['parts'] as Iterable),
+        sdkDeps: Set.of((json['sdkDeps'] as Iterable).cast<String>()),
+        conditionalDeps:
+            (json['conditionalDeps'] as Iterable).map((conditions) {
+          return Map.of((conditions as Map<String, dynamic>)
+              .map((k, v) => MapEntry(k, AssetId.parse(v as String))));
+        }).toList(),
+        hasMain: json['hasMain'] as bool);
+  }
+
+  String serialize() => jsonEncode({
+        'isEntrypoint': isEntryPoint,
+        'deps': _deps.map((id) => id.toString()).toList(),
+        'parts': parts.map((id) => id.toString()).toList(),
+        'conditionalDeps': conditionalDeps
+            .map((conditions) =>
+                conditions.map((k, v) => MapEntry(k, v.toString())))
+            .toList(),
+        'sdkDeps': sdkDeps.toList(),
+        'hasMain': hasMain,
+      });
+
+  List<AssetId> depsForPlatform(DartPlatform platform) {
+    return _deps.followedBy(conditionalDeps.map((conditions) {
+      var selectedImport = conditions[r'$default'];
+      assert(selectedImport != null);
+      for (var condition in conditions.keys) {
+        if (condition == r'$default') continue;
+        if (!condition.startsWith('dart.library.')) {
+          throw UnsupportedError(
+              '$condition not supported for config specific imports. Only the '
+              'dart.library.<name> constants are supported.');
+        }
+        var library = condition.substring('dart.library.'.length);
+        if (platform.supportsLibrary(library)) {
+          selectedImport = conditions[condition];
+          break;
+        }
+      }
+      return selectedImport;
+    })).toList();
+  }
+}
+
+Set<AssetId> _deserializeAssetIds(Iterable serlialized) =>
+    Set.from(serlialized.map((decoded) => AssetId.parse(decoded as String)));
+
+bool _isPart(CompilationUnit dart) =>
+    dart.directives.any((directive) => directive is PartOfDirective);
+
+/// Allows two or fewer arguments to `main` so that entrypoints intended for
+/// use with `spawnUri` get counted.
+//
+// TODO: This misses the case where a Dart file doesn't contain main(),
+// but has a part that does, or it exports a `main` from another library.
+bool _hasMainMethod(CompilationUnit dart) => dart.declarations.any((node) =>
+    node is FunctionDeclaration &&
+    node.name.name == 'main' &&
+    node.functionExpression.parameters.parameters.length <= 2);
diff --git a/build_modules/lib/src/module_library_builder.dart b/build_modules/lib/src/module_library_builder.dart
new file mode 100644
index 0000000..e5defec
--- /dev/null
+++ b/build_modules/lib/src/module_library_builder.dart
@@ -0,0 +1,39 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'package:build/build.dart';
+
+import 'module_library.dart';
+
+const moduleLibraryExtension = '.module.library';
+
+/// Creates `.module.library` assets listing the dependencies and parts for a
+/// Dart library, as well as whether it is an entrypoint.
+///
+///
+/// The output format is determined by [ModuleLibrary.serialize] and can be
+/// restored by [ModuleLibrary.deserialize].
+///
+/// Non-importable Dart source files will not get a `.module.library` asset
+/// output. See [ModuleLibrary.isImportable].
+class ModuleLibraryBuilder implements Builder {
+  const ModuleLibraryBuilder();
+
+  @override
+  final buildExtensions = const {
+    '.dart': [moduleLibraryExtension]
+  };
+
+  @override
+  Future build(BuildStep buildStep) async {
+    final library = ModuleLibrary.fromSource(
+        buildStep.inputId, await buildStep.readAsString(buildStep.inputId));
+    if (!library.isImportable) return;
+    await buildStep.writeAsString(
+        buildStep.inputId.changeExtension(moduleLibraryExtension),
+        library.serialize());
+  }
+}
diff --git a/build_modules/lib/src/modules.dart b/build_modules/lib/src/modules.dart
new file mode 100644
index 0000000..8f3e3b0
--- /dev/null
+++ b/build_modules/lib/src/modules.dart
@@ -0,0 +1,210 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:collection';
+
+import 'package:build/build.dart';
+import 'package:collection/collection.dart' show UnmodifiableSetView;
+import 'package:graphs/graphs.dart';
+import 'package:json_annotation/json_annotation.dart';
+
+import 'errors.dart';
+import 'module_builder.dart';
+import 'module_cache.dart';
+import 'platform.dart';
+
+part 'modules.g.dart';
+
+/// A collection of Dart libraries in a strongly connected component of the
+/// import graph.
+///
+/// Modules track their sources and the other modules they depend on.
+/// modules they depend on.
+/// Modules can span pub package boundaries when there are import cycles across
+/// packages.
+@JsonSerializable()
+@_AssetIdConverter()
+@_DartPlatformConverter()
+class Module {
+  /// Merge the sources and dependencies from [modules] into a single module.
+  ///
+  /// All modules must have the same [platform].
+  /// [primarySource] will be the earliest value from the combined [sources] if
+  /// they were sorted.
+  /// [isMissing] will be true if any input module is missing.
+  /// [isSupported] will be true if all input modules are supported.
+  /// [directDependencies] will be merged for all modules, but if any module
+  /// depended on a source from any other they will be filtered out.
+  static Module merge(List<Module> modules) {
+    assert(modules.isNotEmpty);
+    if (modules.length == 1) return modules.single;
+    assert(modules.every((m) => m.platform == modules.first.platform));
+
+    final allSources = HashSet.of(modules.expand((m) => m.sources));
+    final allDependencies =
+        HashSet.of(modules.expand((m) => m.directDependencies))
+          ..removeAll(allSources);
+    final primarySource =
+        allSources.reduce((a, b) => a.compareTo(b) < 0 ? a : b);
+    final isMissing = modules.any((m) => m.isMissing);
+    final isSupported = modules.every((m) => m.isSupported);
+    return Module(primarySource, allSources, allDependencies,
+        modules.first.platform, isSupported,
+        isMissing: isMissing);
+  }
+
+  /// The library which will be used to reference any library in [sources].
+  ///
+  /// The assets which are built once per module, such as DDC compiled output or
+  /// Analyzer summaries, will be named after the primary source and will
+  /// encompass everything in [sources].
+  @JsonKey(name: 'p', nullable: false)
+  final AssetId primarySource;
+
+  /// The libraries in the strongly connected import cycle with [primarySource].
+  ///
+  /// In most cases without cyclic imports this will contain only the primary
+  /// source. For libraries with an import cycle all of the libraries in the
+  /// cycle will be contained in `sources`. For example:
+  ///
+  /// ```dart
+  /// library foo;
+  ///
+  /// import 'bar.dart';
+  /// ```
+  ///
+  /// ```dart
+  /// library bar;
+  ///
+  /// import 'foo.dart';
+  /// ```
+  ///
+  /// Libraries `foo` and `bar` form an import cycle so they would be grouped in
+  /// the same module. Every Dart library will only be contained in a single
+  /// [Module].
+  @JsonKey(name: 's', nullable: false, toJson: _toJsonAssetIds)
+  final Set<AssetId> sources;
+
+  /// The [primarySource]s of the [Module]s which contain any library imported
+  /// from any of the [sources] in this module.
+  @JsonKey(name: 'd', nullable: false, toJson: _toJsonAssetIds)
+  final Set<AssetId> directDependencies;
+
+  /// Missing modules are created if a module depends on another non-existent
+  /// module.
+  ///
+  /// We want to report these errors lazily to allow for builds to succeed if it
+  /// won't actually impact any apps negatively.
+  @JsonKey(name: 'm', nullable: true, defaultValue: false)
+  final bool isMissing;
+
+  /// Whether or not this module is supported for [platform].
+  ///
+  /// Note that this only indicates support for the [sources] within this
+  /// module, and not its transitive (or direct) dependencies.
+  ///
+  /// Compilers can use this to either silently skip compilation of this module
+  /// or throw early errors or warnings.
+  ///
+  /// Modules are allowed to exist even if they aren't supported, which can help
+  /// with discovering root causes of incompatibility.
+  @JsonKey(name: 'is', nullable: false)
+  final bool isSupported;
+
+  @JsonKey(name: 'pf', nullable: false)
+  final DartPlatform platform;
+
+  Module(this.primarySource, Iterable<AssetId> sources,
+      Iterable<AssetId> directDependencies, this.platform, this.isSupported,
+      {bool isMissing})
+      : sources = UnmodifiableSetView(HashSet.of(sources)),
+        directDependencies =
+            UnmodifiableSetView(HashSet.of(directDependencies)),
+        isMissing = isMissing ?? false;
+
+  /// Generated factory constructor.
+  factory Module.fromJson(Map<String, dynamic> json) => _$ModuleFromJson(json);
+
+  Map<String, dynamic> toJson() => _$ModuleToJson(this);
+
+  /// Returns all [Module]s in the transitive dependencies of this module in
+  /// reverse dependency order.
+  ///
+  /// Throws a [MissingModulesException] if there are any missing modules. This
+  /// typically means that somebody is trying to import a non-existing file.
+  ///
+  /// If [throwIfUnsupported] is `true`, then an [UnsupportedModules]
+  /// will be thrown if there are any modules that are not supported.
+  Future<List<Module>> computeTransitiveDependencies(BuildStep buildStep,
+      {bool throwIfUnsupported = false,
+      @deprecated Set<String> skipPlatformCheckPackages = const {}}) async {
+    throwIfUnsupported ??= false;
+    skipPlatformCheckPackages ??= const {};
+    final modules = await buildStep.fetchResource(moduleCache);
+    var transitiveDeps = <AssetId, Module>{};
+    var modulesToCrawl = {primarySource};
+    var missingModuleSources = <AssetId>{};
+    var unsupportedModules = <Module>{};
+
+    while (modulesToCrawl.isNotEmpty) {
+      var next = modulesToCrawl.last;
+      modulesToCrawl.remove(next);
+      if (transitiveDeps.containsKey(next)) continue;
+      var nextModuleId = next.changeExtension(moduleExtension(platform));
+      var module = await modules.find(nextModuleId, buildStep);
+      if (module == null || module.isMissing) {
+        missingModuleSources.add(next);
+        continue;
+      }
+      if (throwIfUnsupported &&
+          !module.isSupported &&
+          !skipPlatformCheckPackages.contains(module.primarySource.package)) {
+        unsupportedModules.add(module);
+      }
+      // Don't include the root module in the transitive deps.
+      if (next != primarySource) transitiveDeps[next] = module;
+      modulesToCrawl.addAll(module.directDependencies);
+    }
+
+    if (missingModuleSources.isNotEmpty) {
+      throw await MissingModulesException.create(missingModuleSources,
+          transitiveDeps.values.toList()..add(this), buildStep);
+    }
+    if (throwIfUnsupported && unsupportedModules.isNotEmpty) {
+      throw UnsupportedModules(unsupportedModules);
+    }
+    var orderedModules = stronglyConnectedComponents<Module>(
+        transitiveDeps.values,
+        (m) => m.directDependencies.map((s) => transitiveDeps[s]),
+        equals: (a, b) => a.primarySource == b.primarySource,
+        hashCode: (m) => m.primarySource.hashCode);
+    return orderedModules.map((c) => c.single).toList();
+  }
+}
+
+class _AssetIdConverter implements JsonConverter<AssetId, List> {
+  const _AssetIdConverter();
+
+  @override
+  AssetId fromJson(List json) => AssetId.deserialize(json);
+
+  @override
+  List toJson(AssetId object) => object.serialize() as List;
+}
+
+class _DartPlatformConverter implements JsonConverter<DartPlatform, String> {
+  const _DartPlatformConverter();
+
+  @override
+  DartPlatform fromJson(String json) => DartPlatform.byName(json);
+
+  @override
+  String toJson(DartPlatform object) => object.name;
+}
+
+/// Ensure sets of asset IDs are sorted before writing them for a consistent
+/// output.
+List<List> _toJsonAssetIds(Set<AssetId> ids) =>
+    (ids.toList()..sort()).map((i) => i.serialize() as List).toList();
diff --git a/build_modules/lib/src/modules.g.dart b/build_modules/lib/src/modules.g.dart
new file mode 100644
index 0000000..1e7a80d
--- /dev/null
+++ b/build_modules/lib/src/modules.g.dart
@@ -0,0 +1,29 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'modules.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+Module _$ModuleFromJson(Map<String, dynamic> json) {
+  return Module(
+    const _AssetIdConverter().fromJson(json['p'] as List),
+    (json['s'] as List)
+        .map((e) => const _AssetIdConverter().fromJson(e as List)),
+    (json['d'] as List)
+        .map((e) => const _AssetIdConverter().fromJson(e as List)),
+    const _DartPlatformConverter().fromJson(json['pf'] as String),
+    json['is'] as bool,
+    isMissing: json['m'] as bool ?? false,
+  );
+}
+
+Map<String, dynamic> _$ModuleToJson(Module instance) => <String, dynamic>{
+      'p': const _AssetIdConverter().toJson(instance.primarySource),
+      's': _toJsonAssetIds(instance.sources),
+      'd': _toJsonAssetIds(instance.directDependencies),
+      'm': instance.isMissing,
+      'is': instance.isSupported,
+      'pf': const _DartPlatformConverter().toJson(instance.platform),
+    };
diff --git a/build_modules/lib/src/platform.dart b/build_modules/lib/src/platform.dart
new file mode 100644
index 0000000..c74e47a
--- /dev/null
+++ b/build_modules/lib/src/platform.dart
@@ -0,0 +1,80 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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.
+
+/// A supported "platform" for compilation of Dart libraries.
+///
+/// Each "platform" has its own compilation pipeline and builders, and could
+/// differ from other platforms in many ways:
+///
+/// - The core libs that are supported
+/// - The implementations of the core libs
+/// - The compilation steps that are required (frontends or backends could be
+///   different).
+///
+/// Typically these should correspond to `libraries.json` files in the SDK.
+///
+/// New platforms should be created with [register], and can later be
+/// fetched by name using the [DartPlatform.byName] static method.
+class DartPlatform {
+  /// A list of all registered platforms by name, populated by
+  /// [register].
+  static final _platformsByName = <String, DartPlatform>{};
+
+  final List<String> _supportedLibraries;
+
+  final String name;
+
+  /// Returns a [DartPlatform] instance by name.
+  ///
+  /// Throws an [UnrecognizedDartPlatform] if [name] has not been
+  /// registered with [DartPlatform.register].
+  static DartPlatform byName(String name) =>
+      _platformsByName[name] ?? (throw UnrecognizedDartPlatform(name));
+
+  /// Registers a new [DartPlatform].
+  ///
+  /// Throws a [DartPlatformAlreadyRegistered] if [name] has already
+  /// been registered by somebody else.
+  static DartPlatform register(String name, List<String> supportedLibraries) {
+    if (_platformsByName.containsKey(name)) {
+      throw DartPlatformAlreadyRegistered(name);
+    }
+
+    return _platformsByName[name] =
+        DartPlatform._(name, List.unmodifiable(supportedLibraries));
+  }
+
+  const DartPlatform._(this.name, this._supportedLibraries);
+
+  /// Returns whether or not [library] is supported on this platform.
+  ///
+  /// The [library] is path portion of a `dart:` import (should not include the
+  /// scheme).
+  bool supportsLibrary(String library) => _supportedLibraries.contains(library);
+
+  @override
+  int get hashCode => name.hashCode;
+
+  @override
+  bool operator ==(other) => other is DartPlatform && other.name == name;
+}
+
+class DartPlatformAlreadyRegistered implements Exception {
+  final String name;
+
+  const DartPlatformAlreadyRegistered(this.name);
+
+  @override
+  String toString() => 'The platform `$name`, has already been registered.';
+}
+
+class UnrecognizedDartPlatform implements Exception {
+  final String name;
+
+  const UnrecognizedDartPlatform(this.name);
+
+  @override
+  String toString() => 'Unrecognized platform `$name`, it must be registered '
+      'first using `DartPlatform.register`';
+}
diff --git a/build_modules/lib/src/scratch_space.dart b/build_modules/lib/src/scratch_space.dart
new file mode 100644
index 0000000..6149341
--- /dev/null
+++ b/build_modules/lib/src/scratch_space.dart
@@ -0,0 +1,63 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:io';
+import 'dart:math' as math;
+
+import 'package:build/build.dart';
+import 'package:logging/logging.dart';
+import 'package:scratch_space/scratch_space.dart';
+
+import 'workers.dart';
+
+final _logger = Logger('BuildModules');
+
+/// A shared [ScratchSpace] for ddc and analyzer workers that persists
+/// throughout builds.
+final scratchSpace = ScratchSpace();
+
+/// A shared [Resource] for a [ScratchSpace], which cleans up the contents of
+/// the [ScratchSpace] in dispose, but doesn't delete it entirely.
+final scratchSpaceResource = Resource<ScratchSpace>(() {
+  if (!scratchSpace.exists) {
+    scratchSpace.tempDir.createSync(recursive: true);
+    scratchSpace.exists = true;
+  }
+  return scratchSpace;
+}, beforeExit: () async {
+  // The workers are running inside the scratch space, so wait for them to
+  // shut down before deleting it.
+  await dartdevkWorkersAreDone;
+  await frontendWorkersAreDone;
+  await dart2jsWorkersAreDone;
+  // Attempt to clean up the scratch space. Even after waiting for the workers
+  // to shut down we might get file system exceptions on windows for an
+  // arbitrary amount of time, so do retries with an exponential backoff.
+  var numTries = 0;
+  while (true) {
+    numTries++;
+    if (numTries > 3) {
+      _logger
+          .warning('Failed to clean up temp dir ${scratchSpace.tempDir.path}.');
+      return;
+    }
+    try {
+      // TODO(https://github.com/dart-lang/build/issues/656):  The scratch
+      // space throws on `delete` if it thinks it was already deleted.
+      // Manually clean up in this case.
+      if (scratchSpace.exists) {
+        await scratchSpace.delete();
+      } else {
+        await scratchSpace.tempDir.delete(recursive: true);
+      }
+      return;
+    } on FileSystemException {
+      var delayMs = math.pow(10, numTries).floor();
+      _logger.info('Failed to delete temp dir ${scratchSpace.tempDir.path}, '
+          'retrying in ${delayMs}ms');
+      await Future.delayed(Duration(milliseconds: delayMs));
+    }
+  }
+});
diff --git a/build_modules/lib/src/workers.dart b/build_modules/lib/src/workers.dart
new file mode 100644
index 0000000..c7fc0b2
--- /dev/null
+++ b/build_modules/lib/src/workers.dart
@@ -0,0 +1,347 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:collection';
+import 'dart:convert';
+import 'dart:io';
+import 'dart:math' show min;
+
+import 'package:bazel_worker/driver.dart';
+import 'package:build/build.dart';
+import 'package:path/path.dart' as p;
+import 'package:pedantic/pedantic.dart';
+
+import 'scratch_space.dart';
+
+final sdkDir = p.dirname(p.dirname(Platform.resolvedExecutable));
+
+// If no terminal is attached, prevent a new one from launching.
+final _processMode = stdin.hasTerminal
+    ? ProcessStartMode.normal
+    : ProcessStartMode.detachedWithStdio;
+
+/// Completes once the dartdevk workers have been shut down.
+Future<void> get dartdevkWorkersAreDone =>
+    _dartdevkWorkersAreDoneCompleter?.future ?? Future.value();
+Completer<void> _dartdevkWorkersAreDoneCompleter;
+
+/// Completes once the dart2js workers have been shut down.
+Future<void> get dart2jsWorkersAreDone =>
+    _dart2jsWorkersAreDoneCompleter?.future ?? Future.value();
+Completer<void> _dart2jsWorkersAreDoneCompleter;
+
+/// Completes once the common frontend workers have been shut down.
+Future<void> get frontendWorkersAreDone =>
+    _frontendWorkersAreDoneCompleter?.future ?? Future.value();
+Completer<void> _frontendWorkersAreDoneCompleter;
+
+final int _defaultMaxWorkers = min((Platform.numberOfProcessors / 2).ceil(), 4);
+
+const _maxWorkersEnvVar = 'BUILD_MAX_WORKERS_PER_TASK';
+
+final int _maxWorkersPerTask = () {
+  var toParse =
+      Platform.environment[_maxWorkersEnvVar] ?? '$_defaultMaxWorkers';
+  var parsed = int.tryParse(toParse);
+  if (parsed == null) {
+    log.warning('Invalid value for $_maxWorkersEnvVar environment variable, '
+        'expected an int but got `$toParse`. Falling back to default value '
+        'of $_defaultMaxWorkers.');
+    return _defaultMaxWorkers;
+  }
+  return parsed;
+}();
+
+/// Manages a shared set of persistent dartdevk workers.
+BazelWorkerDriver get _dartdevkDriver {
+  _dartdevkWorkersAreDoneCompleter ??= Completer<void>();
+  return __dartdevkDriver ??= BazelWorkerDriver(
+      () => Process.start(
+          p.join(sdkDir, 'bin', 'dart'),
+          [
+            p.join(sdkDir, 'bin', 'snapshots', 'dartdevc.dart.snapshot'),
+            '--kernel',
+            '--persistent_worker'
+          ],
+          mode: _processMode,
+          workingDirectory: scratchSpace.tempDir.path),
+      maxWorkers: _maxWorkersPerTask);
+}
+
+BazelWorkerDriver __dartdevkDriver;
+
+/// Resource for fetching the current [BazelWorkerDriver] for dartdevk.
+final dartdevkDriverResource =
+    Resource<BazelWorkerDriver>(() => _dartdevkDriver, beforeExit: () async {
+  await _dartdevkDriver?.terminateWorkers();
+  _dartdevkWorkersAreDoneCompleter.complete();
+  _dartdevkWorkersAreDoneCompleter = null;
+  __dartdevkDriver = null;
+});
+
+/// Manages a shared set of persistent common frontend workers.
+BazelWorkerDriver get _frontendDriver {
+  _frontendWorkersAreDoneCompleter ??= Completer<void>();
+  return __frontendDriver ??= BazelWorkerDriver(
+      () => Process.start(
+          p.join(sdkDir, 'bin', 'dart'),
+          [
+            p.join(sdkDir, 'bin', 'snapshots', 'kernel_worker.dart.snapshot'),
+            '--persistent_worker'
+          ],
+          mode: _processMode,
+          workingDirectory: scratchSpace.tempDir.path),
+      maxWorkers: _maxWorkersPerTask);
+}
+
+BazelWorkerDriver __frontendDriver;
+
+/// Resource for fetching the current [BazelWorkerDriver] for common frontend.
+final frontendDriverResource =
+    Resource<BazelWorkerDriver>(() => _frontendDriver, beforeExit: () async {
+  await _frontendDriver?.terminateWorkers();
+  _frontendWorkersAreDoneCompleter.complete();
+  _frontendWorkersAreDoneCompleter = null;
+  __frontendDriver = null;
+});
+
+const _dart2jsVmArgsEnvVar = 'BUILD_DART2JS_VM_ARGS';
+final _dart2jsVmArgs = () {
+  var env = Platform.environment[_dart2jsVmArgsEnvVar];
+  return env?.split(' ') ?? <String>[];
+}();
+
+/// Manages a shared set of persistent dart2js workers.
+Dart2JsBatchWorkerPool get _dart2jsWorkerPool {
+  _dart2jsWorkersAreDoneCompleter ??= Completer<void>();
+  var librariesSpec = p.joinAll([sdkDir, 'lib', 'libraries.json']);
+  return __dart2jsWorkerPool ??= Dart2JsBatchWorkerPool(() => Process.start(
+      p.join(sdkDir, 'bin', 'dart'),
+      [
+        ..._dart2jsVmArgs,
+        p.join(sdkDir, 'bin', 'snapshots', 'dart2js.dart.snapshot'),
+        '--libraries-spec=$librariesSpec',
+        '--batch',
+      ],
+      mode: _processMode,
+      workingDirectory: scratchSpace.tempDir.path));
+}
+
+Dart2JsBatchWorkerPool __dart2jsWorkerPool;
+
+/// Resource for fetching the current [Dart2JsBatchWorkerPool] for dart2js.
+final dart2JsWorkerResource = Resource<Dart2JsBatchWorkerPool>(
+    () => _dart2jsWorkerPool, beforeExit: () async {
+  await _dart2jsWorkerPool.terminateWorkers();
+  _dart2jsWorkersAreDoneCompleter.complete();
+  _dart2jsWorkersAreDoneCompleter = null;
+});
+
+/// Manages a pool of persistent [_Dart2JsWorker]s running in batch mode and
+/// schedules jobs among them.
+class Dart2JsBatchWorkerPool {
+  final Future<Process> Function() _spawnWorker;
+
+  final _workQueue = Queue<_Dart2JsJob>();
+
+  bool _queueIsActive = false;
+
+  final _availableWorkers = Queue<_Dart2JsWorker>();
+
+  final _allWorkers = <_Dart2JsWorker>[];
+
+  Dart2JsBatchWorkerPool(this._spawnWorker);
+
+  Future<Dart2JsResult> compile(List<String> args) async {
+    var job = _Dart2JsJob(args);
+    _workQueue.add(job);
+    if (!_queueIsActive) _startWorkQueue();
+    return job.result;
+  }
+
+  void _startWorkQueue() {
+    assert(!_queueIsActive);
+    _queueIsActive = true;
+    () async {
+      while (_workQueue.isNotEmpty) {
+        _Dart2JsWorker worker;
+        if (_availableWorkers.isEmpty &&
+            _allWorkers.length < _maxWorkersPerTask) {
+          worker = _Dart2JsWorker(_spawnWorker);
+          _allWorkers.add(worker);
+        }
+
+        _Dart2JsWorker nextWorker() => _availableWorkers.isNotEmpty
+            ? _availableWorkers.removeFirst()
+            : null;
+
+        worker ??= nextWorker();
+        while (worker == null) {
+          // TODO: something smarter here? in practice this seems to work
+          // reasonably well though and simplifies things a lot ¯\_(ツ)_/¯.
+          await Future.delayed(Duration(seconds: 1));
+          worker = nextWorker();
+        }
+        unawaited(worker
+            .doJob(_workQueue.removeFirst())
+            .whenComplete(() => _availableWorkers.add(worker)));
+      }
+      _queueIsActive = false;
+    }();
+  }
+
+  Future<void> terminateWorkers() async {
+    var allWorkers = _allWorkers.toList();
+    _allWorkers.clear();
+    _availableWorkers.clear();
+    await Future.wait(allWorkers.map((w) => w.terminate()));
+  }
+}
+
+/// A single dart2js worker process running in batch mode.
+///
+/// This may actually spawn multiple processes over time, if a running worker
+/// dies or it decides that it should be restarted for some reason.
+class _Dart2JsWorker {
+  final Future<Process> Function() _spawnWorker;
+
+  int _jobsSinceLastRestartCount = 0;
+  static const int _jobsBeforeRestartMax = 5;
+  static const int _retryCountMax = 2;
+
+  Stream<String> __workerStderrLines;
+  Stream<String> get _workerStderrLines {
+    assert(__worker != null);
+    return __workerStderrLines ??= __worker.stderr
+        .transform(utf8.decoder)
+        .transform(const LineSplitter())
+        .asBroadcastStream();
+  }
+
+  Stream<String> __workerStdoutLines;
+  Stream<String> get _workerStdoutLines {
+    assert(__worker != null);
+    return __workerStdoutLines ??= __worker.stdout
+        .transform(utf8.decoder)
+        .transform(const LineSplitter())
+        .asBroadcastStream();
+  }
+
+  Process __worker;
+  Future<Process> _spawningWorker;
+  Future<Process> get _worker {
+    if (__worker != null) return Future.value(__worker);
+    return _spawningWorker ??= () async {
+      if (__worker == null) {
+        _jobsSinceLastRestartCount = 0;
+        __worker ??= await _spawnWorker();
+        _spawningWorker = null;
+        // exitCode can be null: https://github.com/dart-lang/sdk/issues/35874
+        unawaited(__worker.exitCode?.then((_) {
+          __worker = null;
+          __workerStdoutLines = null;
+          __workerStderrLines = null;
+          _currentJobResult
+              ?.completeError('Dart2js exited with an unknown error');
+        }));
+      }
+      return __worker;
+    }();
+  }
+
+  Completer<Dart2JsResult> _currentJobResult;
+
+  _Dart2JsWorker(this._spawnWorker);
+
+  /// Performs [job], gracefully handling worker failures by retrying
+  /// [_retryCountMax] times and restarting the worker between jobs based on
+  /// [_jobsBeforeRestartMax] to limit memory consumption.
+  ///
+  /// Only one job may be performed at a time.
+  Future<void> doJob(_Dart2JsJob job) async {
+    assert(_currentJobResult == null);
+    var tryCount = 0;
+    var succeeded = false;
+    while (tryCount < _retryCountMax && !succeeded) {
+      tryCount++;
+      _jobsSinceLastRestartCount++;
+      var worker = await _worker;
+      var output = StringBuffer();
+      _currentJobResult = Completer<Dart2JsResult>();
+      var sawError = false;
+      var stderrListener = _workerStderrLines.listen((line) {
+        if (line == '>>> EOF STDERR') {
+          _currentJobResult?.complete(
+              Dart2JsResult(!sawError, 'Dart2Js finished with:\n\n$output'));
+        }
+        if (!line.startsWith('>>> ')) {
+          output.writeln(line);
+        }
+      });
+      var stdoutListener = _workerStdoutLines.listen((line) {
+        if (line.contains('>>> TEST FAIL')) {
+          sawError = true;
+        }
+        if (!line.startsWith('>>> ')) {
+          output.writeln(line);
+        }
+      });
+
+      log.info('Running dart2js with ${job.args.join(' ')}\n');
+      worker.stdin.writeln(job.args.join(' '));
+
+      Dart2JsResult result;
+      try {
+        result = await _currentJobResult.future;
+        job.resultCompleter.complete(result);
+        succeeded = true;
+      } catch (e) {
+        log.warning('Dart2Js failure: $e');
+        succeeded = false;
+        if (tryCount >= _retryCountMax) {
+          job.resultCompleter.complete(_currentJobResult.future);
+        }
+      } finally {
+        _currentJobResult = null;
+        // TODO: Remove this hack once dart-lang/sdk#33708 is resolved.
+        if (_jobsSinceLastRestartCount >= _jobsBeforeRestartMax) {
+          await terminate();
+        }
+        await stderrListener.cancel();
+        await stdoutListener.cancel();
+      }
+    }
+  }
+
+  Future<void> terminate() async {
+    var worker = __worker ?? await _spawningWorker;
+    __worker = null;
+    __workerStdoutLines = null;
+    __workerStderrLines = null;
+    if (worker != null) {
+      worker.kill();
+      await worker.stdin.close();
+    }
+    await worker?.exitCode;
+  }
+}
+
+/// A single dart2js job, consisting of [args] and a [result].
+class _Dart2JsJob {
+  final List<String> args;
+
+  final resultCompleter = Completer<Dart2JsResult>();
+  Future<Dart2JsResult> get result => resultCompleter.future;
+
+  _Dart2JsJob(this.args);
+}
+
+/// The result of a [_Dart2JsJob]
+class Dart2JsResult {
+  final bool succeeded;
+  final String output;
+
+  Dart2JsResult(this.succeeded, this.output);
+}
diff --git a/build_modules/mono_pkg.yaml b/build_modules/mono_pkg.yaml
new file mode 100644
index 0000000..3d1ec9e
--- /dev/null
+++ b/build_modules/mono_pkg.yaml
@@ -0,0 +1,22 @@
+dart:
+  - dev
+
+stages:
+  - analyze_and_format:
+    - group:
+        - dartfmt: sdk
+        - dartanalyzer: --fatal-infos --fatal-warnings .
+    - dartanalyzer: --fatal-warnings .
+      dart:
+        - 2.5.0
+  - unit_test:
+    # Run the script directly - running from snapshot has issues for packages
+    # that are depended on by build_runner itself.
+    - command: dart $(pub run build_runner generate-build-script) test --delete-conflicting-outputs -- -P presubmit
+      os:
+        - linux
+        - windows
+
+cache:
+  directories:
+    - .dart_tool/build
diff --git a/build_modules/pubspec.yaml b/build_modules/pubspec.yaml
new file mode 100644
index 0000000..4161b17
--- /dev/null
+++ b/build_modules/pubspec.yaml
@@ -0,0 +1,38 @@
+name: build_modules
+version: 2.8.0
+description: Builders for Dart modules
+homepage: https://github.com/dart-lang/build/tree/master/build_modules
+
+environment:
+  sdk: ">=2.5.0 <3.0.0"
+
+dependencies:
+  analyzer: ">0.35.0 <0.40.0"
+  async: ^2.0.0
+  bazel_worker: ^0.1.20
+  build: ">=1.2.0 <2.0.0"
+  build_config: ">=0.3.0 <0.5.0"
+  collection: ^1.0.0
+  crypto: ^2.0.0
+  glob: ^1.0.0
+  graphs: ^0.2.0
+  json_annotation: ">=1.2.0 <4.0.0"
+  logging: ^0.11.2
+  meta: ^1.1.0
+  path: ^1.4.2
+  pedantic: ^1.0.0
+  scratch_space: ^0.0.4
+
+dev_dependencies:
+  build_runner: ^1.0.0
+  build_test: ^0.10.9
+  build_vm_compilers: ^1.0.0
+  test: ^1.6.0
+  a:
+    path: test/fixtures/a
+  b:
+    path: test/fixtures/b
+
+dependency_overrides:
+  build_vm_compilers:
+    path: ../build_vm_compilers
diff --git a/build_resolvers/BUILD.gn b/build_resolvers/BUILD.gn
new file mode 100644
index 0000000..5abdb1a
--- /dev/null
+++ b/build_resolvers/BUILD.gn
@@ -0,0 +1,24 @@
+# This file is generated by importer.py for build_resolvers-1.3.3
+
+import("//build/dart/dart_library.gni")
+
+dart_library("build_resolvers") {
+  package_name = "build_resolvers"
+
+  # This parameter is left empty as we don't care about analysis or exporting
+  # these sources outside of the tree.
+  sources = []
+
+  disable_analysis = true
+
+  deps = [
+    "//third_party/dart-pkg/pub/logging",
+    "//third_party/dart-pkg/pub/graphs",
+    "//third_party/dart-pkg/pub/crypto",
+    "//third_party/dart-pkg/pub/analyzer",
+    "//third_party/dart-pkg/pub/yaml",
+    "//third_party/dart-pkg/pub/build",
+    "//third_party/dart-pkg/pub/package_resolver",
+    "//third_party/dart-pkg/pub/path",
+  ]
+}
diff --git a/build_resolvers/CHANGELOG.md b/build_resolvers/CHANGELOG.md
new file mode 100644
index 0000000..cc491dc
--- /dev/null
+++ b/build_resolvers/CHANGELOG.md
@@ -0,0 +1,190 @@
+## 1.3.3
+
+- Fix an issue where non-existing Dart assets weren't visible to the analyzer, even
+  when they are created later.
+
+## 1.3.2
+
+- Improve detection of the flutter SDK for older flutter versions.  
+
+## 1.3.1
+
+- Add an exception on trying to resolve an `AssetId` that is not a Dart
+  library with `libraryFor` to fit the contract expressed by the doc comment on
+  `Resolver`.
+
+## 1.3.0
+
+### New feature
+
+You can now resolve additional libraries other than those imported by the
+primary entrypoint.
+
+  - This is supported through the `isLibrary` and `libraryFor` methods on
+    `Resolver`, which will now resolve the provided asset if it is not already
+    resolved.
+  - **Note**: Doing this may affect the result of subsequent calls to
+    `resolver.libraries` and `resolver.findLibraryByName` if new libraries are
+    discovered.
+
+**Note**: If using `build_runner` then this will also require you to upgrade
+to version `4.2.0` of `build_runner_core` .
+
+### Other
+
+- Changed a `hide` declaration to a `show` declaration to support a
+`package:analyzer` change.
+
+## 1.2.2
+
+- Update to `package:analyzer` version `0.39.0`.
+
+## 1.2.1
+
+Check the build_resolvers version as a part of sdk summary invalidation.
+
+## 1.2.0
+
+Add flutters embedded sdk to the summary if available. This has the effect of
+making `dart:ui` and any future libraries available if using the flutter sdk
+instead of the dart sdk.
+
+## 1.1.1
+
+### Bug Fix
+
+Check the analyzer path before reading cached summaries in addition to the
+SDK version.
+
+## 1.1.0
+
+### Bug Fix: #38499
+
+Update the `AnalysisResolvers` class to no longer use the SDK summary that is
+shipped with the SDK by default. This is not guaranteed compatible with
+analyzer versions shipped on pub and should not be used by any non-sdk code.
+
+In order to fix this the `AnalysisResolvers` class now takes an optional method
+that returns the path to an arbitrary SDK summary. By default it will lazily
+generate a summary under `.dart_tool/build_resolvers` which is invalidated
+based on the `Platform.version` from `dart:io`.
+
+## 1.0.8
+
+- Allow `build` version 1.2.x.
+
+## 1.0.7
+
+- Allow analyzer version 0.38.0.
+
+## 1.0.6
+
+- Allow analyzer version 0.37.0.
+
+## 1.0.5
+
+- Fix a race condition where some assets may be resolved with missing
+  dependencies. This was likely to have only manifested in tests using
+  `resolveSource`.
+
+## 1.0.4
+
+- Increase lower bound sdk constraint to 2.1.0.
+- Increased the upper bound for `package:analyzer` to `<0.37.0`.
+
+## 1.0.3
+
+- Fixes a bug where transitive `dart-ext:` imports would cause the resolver
+  to fail. These uris will now be ignored.
+
+## 1.0.2
+
+- Ensure that `BuildAssetUriResolver.restoreAbsolute` never returns null.
+  - Fixes a crash when a resolver is requested but not all transitive sources
+    are available yet.
+
+## 1.0.1
+
+- Fix a bug causing crashes on windows.
+
+## 1.0.0
+
+- Migrate to `AnalysisDriver`. There are behavior changes which may be breaking.
+  The `LibraryElement` instances returned by the resolver will now:
+  - Have non-working `context` fields.
+  - Have no source offsets for annotations or their errors.
+  - Have working `session` fields.
+  - Have `Source` instances with different URIs than before.
+  - Not include missing libraries in the `importedLibraries` getter. You can
+    instead use the `imports` getter to see all the imports.
+
+## 0.2.3
+
+- Update to `build` `1.1.0` and add `assetIdForElement`.
+
+## 0.2.2+7
+
+- Updated _AssetUriResolver to prepare for a future release of the analyzer.
+- Increased the upper bound for `package:analyzer` to `<0.35.0`.
+
+## 0.2.2+6
+
+- Increased the upper bound for `package:analyzer` to `<0.34.0`.
+
+## 0.2.2+5
+
+- Increased the upper bound for the `build` to `<1.1.0`.
+
+## 0.2.2+4
+
+- Removed dependency on cli_util.
+- Increased the upper bound for the `build` to `<0.12.9`.
+
+## 0.2.2+3
+
+- Don't log a severe message when a URI cannot be resolved. Just return `null`.
+
+## 0.2.2+2
+
+- Use sdk summaries for the analysis context, which makes getting the initial
+  resolver faster (reapplied).
+
+## 0.2.2+1
+
+- Restore `new` keyword for a working release on Dart 1 VM.
+
+## 0.2.2
+
+- Use sdk summaries for the analysis context, which makes getting the initial
+  resolver faster.
+- Release broken on Dart 1 VM.
+
+## 0.2.1+1
+
+- Increased the upper bound for the sdk to `<3.0.0`.
+
+## 0.2.1
+
+- Allow passing in custom `AnalysisOptions`.
+
+## 0.2.0+2
+
+- Support `package:analyzer` `0.32.0`.
+
+## 0.2.0+1
+
+- Switch to `firstWhere(orElse)` for compatibility with the SDK dev.45
+
+## 0.2.0
+
+- Removed locking between uses of the Resolver and added a mandatory `reset`
+  call to indicate that a complete build is finished.
+
+## 0.1.1
+
+- Support the latest `analyzer` package.
+
+## 0.1.0
+
+- Initial release as a migration from `code_transformers` with a near-identical
+  implementation.
diff --git a/build_resolvers/LICENSE b/build_resolvers/LICENSE
new file mode 100644
index 0000000..c4dc9ba
--- /dev/null
+++ b/build_resolvers/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2018, the Dart project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of Google Inc. nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/build_resolvers/README.md b/build_resolvers/README.md
new file mode 100644
index 0000000..9aa64bf
--- /dev/null
+++ b/build_resolvers/README.md
@@ -0,0 +1,6 @@
+Provides an in-memory `Resolvers` implementation for use with `package:build`.
+
+This implementation does a monolithic analysis from source, with fine grained
+invalidation, which works well when it can be shared across multiple build steps
+in the same process. It is not however suitable for use in more general build
+systems, which should build up their analysis context using analyzer summaries.
diff --git a/build_resolvers/lib/build_resolvers.dart b/build_resolvers/lib/build_resolvers.dart
new file mode 100644
index 0000000..9a72353
--- /dev/null
+++ b/build_resolvers/lib/build_resolvers.dart
@@ -0,0 +1,5 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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.
+
+export 'src/resolver.dart' show AnalyzerResolvers;
diff --git a/build_resolvers/lib/src/analysis_driver.dart b/build_resolvers/lib/src/analysis_driver.dart
new file mode 100644
index 0000000..acfbd8d
--- /dev/null
+++ b/build_resolvers/lib/src/analysis_driver.dart
@@ -0,0 +1,50 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS 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 'package:analyzer/src/dart/analysis/byte_store.dart'
+    show MemoryByteStore;
+import 'package:analyzer/src/dart/analysis/driver.dart';
+import 'package:analyzer/src/dart/analysis/file_state.dart';
+import 'package:analyzer/src/dart/analysis/performance_logger.dart'
+    show PerformanceLog;
+import 'package:analyzer/src/generated/engine.dart'
+    show AnalysisOptionsImpl, AnalysisOptions;
+import 'package:analyzer/src/generated/source.dart';
+import 'package:analyzer/src/summary/package_bundle_reader.dart';
+import 'package:analyzer/src/summary/summary_sdk.dart' show SummaryBasedDartSdk;
+
+import 'build_asset_uri_resolver.dart';
+
+/// Builds an [AnalysisDriver] backed by a summary SDK and package summary
+/// files.
+///
+/// Any code which is not covered by the summaries must be resolvable through
+/// [buildAssetUriResolver].
+AnalysisDriver analysisDriver(BuildAssetUriResolver buildAssetUriResolver,
+    AnalysisOptions analysisOptions, String sdkSummaryPath) {
+  var sdk = SummaryBasedDartSdk(sdkSummaryPath, true);
+  var sdkResolver = DartUriResolver(sdk);
+
+  var resolvers = [sdkResolver, buildAssetUriResolver];
+  var sourceFactory = SourceFactory(resolvers);
+
+  var dataStore = SummaryDataStore([sdkSummaryPath]);
+
+  var logger = PerformanceLog(null);
+  var scheduler = AnalysisDriverScheduler(logger);
+  var driver = AnalysisDriver(
+    scheduler,
+    logger,
+    buildAssetUriResolver.resourceProvider,
+    MemoryByteStore(),
+    FileContentOverlay(),
+    null,
+    sourceFactory,
+    (analysisOptions as AnalysisOptionsImpl) ?? AnalysisOptionsImpl(),
+    externalSummaries: dataStore,
+  );
+
+  scheduler.start();
+  return driver;
+}
diff --git a/build_resolvers/lib/src/build_asset_uri_resolver.dart b/build_resolvers/lib/src/build_asset_uri_resolver.dart
new file mode 100644
index 0000000..35b34a5
--- /dev/null
+++ b/build_resolvers/lib/src/build_asset_uri_resolver.dart
@@ -0,0 +1,187 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS 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 'dart:async';
+import 'dart:collection';
+
+// ignore: deprecated_member_use
+import 'package:analyzer/analyzer.dart' show parseDirectives;
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/file_system/memory_file_system.dart';
+import 'package:analyzer/src/dart/analysis/driver.dart' show AnalysisDriver;
+import 'package:analyzer/src/generated/source.dart';
+import 'package:build/build.dart' show AssetId, BuildStep;
+import 'package:crypto/crypto.dart';
+import 'package:graphs/graphs.dart';
+import 'package:path/path.dart' as p;
+
+const _ignoredSchemes = ['dart', 'dart-ext'];
+
+class BuildAssetUriResolver extends UriResolver {
+  /// A cache of the directives for each Dart library.
+  ///
+  /// This is stored across builds and is only invalidated if we read a file and
+  /// see that it's content is different from what it was last time it was read.
+  final _cachedAssetDependencies = <AssetId, Set<AssetId>>{};
+
+  /// A cache of the digest for each Dart asset.
+  ///
+  /// This is stored across builds and used to invalidate the values in
+  /// [_cachedAssetDependencies] only when the actual content of the library
+  /// changed.
+  final _cachedAssetDigests = <AssetId, Digest>{};
+
+  final resourceProvider = MemoryResourceProvider(context: p.posix);
+
+  /// The assets which are known to be readable at some point during the current
+  /// build.
+  ///
+  /// When actions can run out of order an asset can move from being readable
+  /// (in the later phase) to being unreadable (in the earlier phase which ran
+  /// later). If this happens we don't want to hide the asset from the analyzer.
+  final globallySeenAssets = HashSet<AssetId>();
+
+  /// The assets which have been resolved from a [BuildStep], either as an
+  /// input, subsequent calls to a resolver, or a transitive import thereof.
+  final _buildStepAssets = <BuildStep, HashSet<AssetId>>{};
+
+  /// Crawl the transitive imports from [entryPoints] and ensure that the
+  /// content of each asset is updated in [resourceProvider] and [driver].
+  Future<void> performResolve(BuildStep buildStep, List<AssetId> entryPoints,
+      AnalysisDriver driver) async {
+    final seenInBuildStep =
+        _buildStepAssets.putIfAbsent(buildStep, () => HashSet());
+    bool notCrawled(AssetId asset) => !seenInBuildStep.contains(asset);
+
+    final changedPaths = await crawlAsync<AssetId, _AssetState>(
+            entryPoints.where(notCrawled), (id) async {
+      final path = assetPath(id);
+      if (!await buildStep.canRead(id)) {
+        if (globallySeenAssets.contains(id)) {
+          // ignore from this graph, some later build step may still be using it
+          // so it shouldn't be removed from [resourceProvider], but we also
+          // don't care about it's transitive imports.
+          return null;
+        }
+        _cachedAssetDependencies.remove(id);
+        _cachedAssetDigests.remove(id);
+        if (resourceProvider.getFile(path).exists) {
+          resourceProvider.deleteFile(path);
+        }
+        return _AssetState.removed(path);
+      }
+      globallySeenAssets.add(id);
+      seenInBuildStep.add(id);
+      final digest = await buildStep.digest(id);
+      if (_cachedAssetDigests[id] == digest) {
+        return _AssetState.unchanged(path, _cachedAssetDependencies[id]);
+      } else {
+        final isChange = _cachedAssetDigests.containsKey(id);
+        final content = await buildStep.readAsString(id);
+        _cachedAssetDigests[id] = digest;
+        final dependencies =
+            _cachedAssetDependencies[id] = _parseDirectives(content, id);
+        if (isChange) {
+          resourceProvider.updateFile(path, content);
+          return _AssetState.changed(path, dependencies);
+        } else {
+          resourceProvider.newFile(path, content);
+          return _AssetState.newAsset(path, dependencies);
+        }
+      }
+    }, (id, state) => state.directives.where(notCrawled))
+        .where((state) => state.isAssetUpdate)
+        .map((state) => state.path)
+        .toList();
+    changedPaths.forEach(driver.changeFile);
+  }
+
+  /// Attempts to parse [uri] into an [AssetId].
+  ///
+  /// Handles 'package:' or 'asset:' URIs, as well as 'file:' URIs that have the
+  /// same pattern used by [assetPath].
+  ///
+  /// Returns null if the Uri cannot be parsed.
+  AssetId parseAsset(Uri uri) {
+    if (_ignoredSchemes.any(uri.isScheme)) return null;
+    if (uri.isScheme('package') || uri.isScheme('asset')) {
+      return AssetId.resolve('$uri');
+    }
+    if (uri.isScheme('file')) {
+      final parts = p.split(uri.path);
+      return AssetId(parts[1], p.posix.joinAll(parts.skip(2)));
+    }
+    return null;
+  }
+
+  /// Attempts to parse [uri] into an [AssetId] and returns it if it is cached.
+  ///
+  /// Handles 'package:' or 'asset:' URIs, as well as 'file:' URIs that have the
+  /// same pattern used by [assetPath].
+  ///
+  /// Returns null if the Uri cannot be parsed or is not cached.
+  AssetId lookupCachedAsset(Uri uri) {
+    final assetId = parseAsset(uri);
+    if (assetId == null || !_cachedAssetDigests.containsKey(assetId)) {
+      return null;
+    }
+
+    return assetId;
+  }
+
+  void notifyComplete(BuildStep step) {
+    _buildStepAssets.remove(step);
+  }
+
+  /// Clear cached information specific to an individual build.
+  void reset() {
+    assert(_buildStepAssets.isEmpty,
+        'Reset was called before all build steps completed');
+    globallySeenAssets.clear();
+  }
+
+  @override
+  Source resolveAbsolute(Uri uri, [Uri actualUri]) {
+    final assetId = parseAsset(uri);
+    if (assetId == null) return null;
+
+    return resourceProvider
+        .getFile(assetPath(assetId))
+        .createSource(assetId.uri);
+  }
+
+  @override
+  Uri restoreAbsolute(Source source) =>
+      lookupCachedAsset(source.uri)?.uri ?? source.uri;
+}
+
+String assetPath(AssetId assetId) =>
+    p.posix.join('/${assetId.package}', assetId.path);
+
+/// Returns all the directives from a Dart library that can be resolved to an
+/// [AssetId].
+Set<AssetId> _parseDirectives(String content, AssetId from) =>
+    // ignore: deprecated_member_use
+    HashSet.of(parseDirectives(content, suppressErrors: true)
+        .directives
+        .whereType<UriBasedDirective>()
+        .where((directive) {
+          var uri = Uri.parse(directive.uri.stringValue);
+          return !_ignoredSchemes.any(uri.isScheme);
+        })
+        .map((d) => AssetId.resolve(d.uri.stringValue, from: from))
+        .where((id) => id != null));
+
+class _AssetState {
+  final String path;
+  final bool isAssetUpdate;
+  final Iterable<AssetId> directives;
+
+  _AssetState.removed(this.path)
+      : isAssetUpdate = false,
+        directives = const [];
+  _AssetState.changed(this.path, this.directives) : isAssetUpdate = true;
+  _AssetState.unchanged(this.path, this.directives) : isAssetUpdate = false;
+  _AssetState.newAsset(this.path, this.directives) : isAssetUpdate = true;
+}
diff --git a/build_resolvers/lib/src/human_readable_duration.dart b/build_resolvers/lib/src/human_readable_duration.dart
new file mode 100644
index 0000000..736e915
--- /dev/null
+++ b/build_resolvers/lib/src/human_readable_duration.dart
@@ -0,0 +1,31 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS 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.
+
+/// Returns a human readable string for a duration.
+///
+/// Handles durations that span up to hours - this will not be a good fit for
+/// durations that are longer than days.
+///
+/// Always attempts 2 'levels' of precision. Will show hours/minutes,
+/// minutes/seconds, seconds/tenths of a second, or milliseconds depending on
+/// the largest level that needs to be displayed.
+///
+// TODO: This is copied from `package:build_runner_core`, at some point we
+// may want to move this to a shared dependency.
+String humanReadable(Duration duration) {
+  if (duration < const Duration(seconds: 1)) {
+    return '${duration.inMilliseconds}ms';
+  }
+  if (duration < const Duration(minutes: 1)) {
+    return '${(duration.inMilliseconds / 1000.0).toStringAsFixed(1)}s';
+  }
+  if (duration < const Duration(hours: 1)) {
+    final minutes = duration.inMinutes;
+    final remaining = duration - Duration(minutes: minutes);
+    return '${minutes}m ${remaining.inSeconds}s';
+  }
+  final hours = duration.inHours;
+  final remaining = duration - Duration(hours: hours);
+  return '${hours}h ${remaining.inMinutes}m';
+}
diff --git a/build_resolvers/lib/src/resolver.dart b/build_resolvers/lib/src/resolver.dart
new file mode 100644
index 0000000..35c255a
--- /dev/null
+++ b/build_resolvers/lib/src/resolver.dart
@@ -0,0 +1,335 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:collection';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:analyzer/src/summary/summary_file_builder.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/file_system/file_system.dart' hide File;
+import 'package:analyzer/file_system/physical_file_system.dart';
+import 'package:analyzer/src/generated/sdk.dart';
+import 'package:analyzer/src/generated/source.dart';
+import 'package:analyzer/src/dart/analysis/driver.dart' show AnalysisDriver;
+import 'package:analyzer/src/dart/sdk/sdk.dart';
+import 'package:analyzer/src/generated/engine.dart'
+    show AnalysisOptions, AnalysisOptionsImpl;
+import 'package:build/build.dart';
+import 'package:logging/logging.dart';
+import 'package:package_resolver/package_resolver.dart';
+import 'package:path/path.dart' as p;
+import 'package:yaml/yaml.dart';
+
+import 'analysis_driver.dart';
+import 'build_asset_uri_resolver.dart';
+import 'human_readable_duration.dart';
+
+final _logger = Logger('build_resolvers');
+
+/// Implements [Resolver.libraries] and [Resolver.findLibraryByName] by crawling
+/// down from entrypoints.
+class PerActionResolver implements ReleasableResolver {
+  final AnalyzerResolver _delegate;
+  final BuildStep _step;
+
+  final Set<AssetId> _entryPoints;
+
+  PerActionResolver(this._delegate, this._step, Iterable<AssetId> entryPoints)
+      : _entryPoints = entryPoints.toSet();
+
+  @override
+  Stream<LibraryElement> get libraries async* {
+    final seen = <LibraryElement>{};
+    final toVisit = Queue<LibraryElement>();
+
+    // keep a copy of entry points in case [_resolveIfNecessary] is called
+    // before this stream is done.
+    final entryPoints = _entryPoints.toList();
+    for (final entryPoint in entryPoints) {
+      if (!await _delegate.isLibrary(entryPoint)) continue;
+      final library = await _delegate.libraryFor(entryPoint);
+      toVisit.add(library);
+      seen.add(library);
+    }
+    while (toVisit.isNotEmpty) {
+      final current = toVisit.removeFirst();
+      // TODO - avoid crawling or returning libraries which are not visible via
+      // `BuildStep.canRead`. They'd still be reachable by crawling the element
+      // model manually.
+      yield current;
+      final toCrawl = current.importedLibraries
+          .followedBy(current.exportedLibraries)
+          .where((l) => !seen.contains(l))
+          .toSet();
+      toVisit.addAll(toCrawl);
+      seen.addAll(toCrawl);
+    }
+  }
+
+  @override
+  Future<LibraryElement> findLibraryByName(String libraryName) async =>
+      libraries.firstWhere((l) => l.name == libraryName, orElse: () => null);
+
+  @override
+  Future<bool> isLibrary(AssetId assetId) async {
+    await _resolveIfNecesssary(assetId);
+    return _delegate.isLibrary(assetId);
+  }
+
+  @override
+  Future<LibraryElement> libraryFor(AssetId assetId) async {
+    await _resolveIfNecesssary(assetId);
+    return _delegate.libraryFor(assetId);
+  }
+
+  Future<void> _resolveIfNecesssary(AssetId id) async {
+    if (!_entryPoints.contains(id)) {
+      _entryPoints.add(id);
+
+      // the resolver will only visit assets that haven't been resolved in this
+      // step yet
+      await _delegate._uriResolver
+          .performResolve(_step, [id], _delegate._driver);
+    }
+  }
+
+  @override
+  void release() {
+    _delegate._uriResolver.notifyComplete(_step);
+    _delegate.release();
+  }
+
+  @override
+  Future<AssetId> assetIdForElement(Element element) =>
+      _delegate.assetIdForElement(element);
+}
+
+class AnalyzerResolver implements ReleasableResolver {
+  final BuildAssetUriResolver _uriResolver;
+  final AnalysisDriver _driver;
+
+  AnalyzerResolver(this._driver, this._uriResolver);
+
+  @override
+  Future<bool> isLibrary(AssetId assetId) async {
+    var source = _driver.sourceFactory.forUri2(assetId.uri);
+    return source != null &&
+        (await _driver.getSourceKind(assetPath(assetId))) == SourceKind.LIBRARY;
+  }
+
+  @override
+  Future<LibraryElement> libraryFor(AssetId assetId) async {
+    var path = assetPath(assetId);
+    var uri = assetId.uri;
+    var source = _driver.sourceFactory.forUri2(uri);
+    if (source == null) throw ArgumentError('missing source for $uri');
+    var kind = await _driver.getSourceKind(path);
+    if (kind != SourceKind.LIBRARY) throw NonLibraryAssetException(assetId);
+    return _driver.getLibraryByUri(assetId.uri.toString());
+  }
+
+  @override
+  // Do nothing
+  void release() {}
+
+  @override
+  Stream<LibraryElement> get libraries {
+    // We don't know what libraries to expose without leaking libraries written
+    // by later phases.
+    throw UnimplementedError();
+  }
+
+  @override
+  Future<LibraryElement> findLibraryByName(String libraryName) {
+    // We don't know what libraries to expose without leaking libraries written
+    // by later phases.
+    throw UnimplementedError();
+  }
+
+  @override
+  Future<AssetId> assetIdForElement(Element element) async {
+    final uri = element.source.uri;
+    if (!uri.isScheme('package') && !uri.isScheme('asset')) {
+      throw UnresolvableAssetException(
+          '${element.name} in ${element.source.uri}');
+    }
+    return AssetId.resolve('${element.source.uri}');
+  }
+}
+
+class AnalyzerResolvers implements Resolvers {
+  /// Nullable, the default analysis options are used if not provided.
+  final AnalysisOptions _analysisOptions;
+
+  /// A function that returns the path to the SDK summary when invoked.
+  ///
+  /// Defaults to [_defaultSdkSummaryGenerator].
+  final Future<String> Function() _sdkSummaryGenerator;
+
+  // Lazy, all access must be preceded by a call to `_ensureInitialized`.
+  AnalyzerResolver _resolver;
+  BuildAssetUriResolver _uriResolver;
+
+  /// Nullable, should not be accessed outside of [_ensureInitialized].
+  Future<void> _initialized;
+
+  AnalyzerResolvers(
+      [this._analysisOptions, Future<String> Function() sdkSummaryGenerator])
+      : _sdkSummaryGenerator =
+            sdkSummaryGenerator ?? _defaultSdkSummaryGenerator;
+
+  /// Create a Resolvers backed by an `AnalysisContext` using options
+  /// [_analysisOptions].
+  Future<void> _ensureInitialized() {
+    return _initialized ??= () async {
+      _uriResolver = BuildAssetUriResolver();
+      var driver = analysisDriver(
+          _uriResolver, _analysisOptions, await _sdkSummaryGenerator());
+      _resolver = AnalyzerResolver(driver, _uriResolver);
+    }();
+  }
+
+  @override
+  Future<ReleasableResolver> get(BuildStep buildStep) async {
+    await _ensureInitialized();
+
+    await _uriResolver.performResolve(
+        buildStep, [buildStep.inputId], _resolver._driver);
+    return PerActionResolver(_resolver, buildStep, [buildStep.inputId]);
+  }
+
+  /// Must be called between each build.
+  @override
+  void reset() {
+    _uriResolver?.reset();
+  }
+}
+
+/// Lazily creates a summary of the users SDK and caches it under
+/// `.dart_tool/build_resolvers`.
+///
+/// This is only intended for use in typical dart packages, which must
+/// have an already existing `.dart_tool` directory (this is how we
+/// validate we are running under a typical dart package and not a custom
+/// environment).
+Future<String> _defaultSdkSummaryGenerator() async {
+  var dartToolPath = '.dart_tool';
+  if (!await Directory(dartToolPath).exists()) {
+    throw StateError(
+        'The default analyzer resolver can only be used when the current '
+        'working directory is a standard pub package.');
+  }
+
+  var cacheDir = p.join(dartToolPath, 'build_resolvers');
+  var summaryPath = p.join(cacheDir, 'sdk.sum');
+  var depsFile = File('$summaryPath.deps');
+  var summaryFile = File(summaryPath);
+
+  var currentDeps = {
+    'sdk': Platform.version,
+    for (var package in _packageDepsToCheck)
+      package: await PackageResolver.current.packagePath(package),
+  };
+
+  // Invalidate existing summary/version/analyzer files if present.
+  if (await depsFile.exists()) {
+    if (!await _checkDeps(depsFile, currentDeps)) {
+      await depsFile.delete();
+      if (await summaryFile.exists()) await summaryFile.delete();
+    }
+  } else if (await summaryFile.exists()) {
+    // Fallback for cases where we could not do a proper version check.
+    await summaryFile.delete();
+  }
+
+  // Generate the summary and version files if necessary.
+  if (!await summaryFile.exists()) {
+    var watch = Stopwatch()..start();
+    _logger.info('Generating SDK summary...');
+    await summaryFile.create(recursive: true);
+    await summaryFile.writeAsBytes(_buildSdkSummary());
+
+    await _createDepsFile(depsFile, currentDeps);
+    watch.stop();
+    _logger.info('Generating SDK summary completed, took '
+        '${humanReadable(watch.elapsed)}\n');
+  }
+
+  return p.absolute(summaryPath);
+}
+
+final _packageDepsToCheck = ['analyzer', 'build_resolvers'];
+
+Future<bool> _checkDeps(
+    File versionsFile, Map<String, Object> currentDeps) async {
+  var previous =
+      jsonDecode(await versionsFile.readAsString()) as Map<String, Object>;
+
+  if (previous.keys.length != currentDeps.keys.length) return false;
+
+  for (var entry in previous.entries) {
+    if (entry.value != currentDeps[entry.key]) return false;
+  }
+
+  return true;
+}
+
+Future<void> _createDepsFile(
+    File depsFile, Map<String, Object> currentDeps) async {
+  await depsFile.create(recursive: true);
+  await depsFile.writeAsString(jsonEncode(currentDeps));
+}
+
+List<int> _buildSdkSummary() {
+  var resourceProvider = PhysicalResourceProvider.INSTANCE;
+  var dartSdkFolder = resourceProvider.getFolder(_runningDartSdkPath);
+  var sdk = FolderBasedDartSdk(resourceProvider, dartSdkFolder)
+    ..useSummary = false
+    ..analysisOptions = AnalysisOptionsImpl();
+
+  if (isFlutter) {
+    _addFlutterLibraries(sdk, resourceProvider);
+  }
+
+  var sdkSources = {
+    for (var library in sdk.sdkLibraries) sdk.mapDartUri(library.shortName),
+  };
+
+  return SummaryBuilder(sdkSources, sdk.context).build();
+}
+
+/// Loads the flutter engine _embedder.yaml file and adds any new libraries to
+/// [sdk].
+void _addFlutterLibraries(
+    AbstractDartSdk sdk, ResourceProvider resourceProvider) {
+  var embedderYamlFile =
+      resourceProvider.getFile(p.join(_dartUiPath, '_embedder.yaml'));
+  if (!embedderYamlFile.exists) {
+    throw StateError('Unable to find flutter libraries, please run '
+        '`flutter precache` and try again.');
+  }
+
+  var embedderYaml = loadYaml(embedderYamlFile.readAsStringSync()) as YamlMap;
+  var flutterSdk = EmbedderSdk(resourceProvider,
+      {resourceProvider.getFolder(_dartUiPath): embedderYaml});
+
+  for (var library in flutterSdk.sdkLibraries) {
+    if (sdk.libraryMap.getLibrary(library.shortName) != null) continue;
+    sdk.libraryMap.setLibrary(library.shortName, library as SdkLibraryImpl);
+  }
+}
+
+/// Path to the running dart's SDK root.
+final _runningDartSdkPath = p.dirname(p.dirname(Platform.resolvedExecutable));
+
+/// Path where the dart:ui package will be found, if executing via the dart
+/// binary provided by the Flutter SDK.
+final _dartUiPath =
+    p.normalize(p.join(_runningDartSdkPath, '..', 'pkg', 'sky_engine', 'lib'));
+
+/// `true` if the currently running dart was provided by the Flutter SDK.
+final isFlutter =
+    Platform.version.contains('flutter') || Directory(_dartUiPath).existsSync();
diff --git a/build_resolvers/mono_pkg.yaml b/build_resolvers/mono_pkg.yaml
new file mode 100644
index 0000000..3e88b03
--- /dev/null
+++ b/build_resolvers/mono_pkg.yaml
@@ -0,0 +1,22 @@
+dart:
+  - dev
+
+stages:
+  - analyze_and_format:
+    - group:
+        - dartfmt: sdk
+        - dartanalyzer: --fatal-infos --fatal-warnings .
+    - dartanalyzer: --fatal-warnings .
+      dart:
+        - 2.3.0
+  - unit_test:
+    - group:
+      - command: pub run build_runner test
+      - command: test/flutter_test.sh
+      os:
+        - linux
+        - windows
+
+cache:
+  directories:
+    - .dart_tool/build
diff --git a/build_resolvers/pubspec.yaml b/build_resolvers/pubspec.yaml
new file mode 100644
index 0000000..a129cfb
--- /dev/null
+++ b/build_resolvers/pubspec.yaml
@@ -0,0 +1,30 @@
+name: build_resolvers
+version: 1.3.3
+description: Resolve Dart code in a Builder
+homepage: https://github.com/dart-lang/build/tree/master/build_resolvers
+
+environment:
+  sdk: ">=2.3.0 <3.0.0"
+
+dependencies:
+  analyzer: ^0.39.0
+  build: ">=1.1.0 <1.3.0"
+  crypto: ^2.0.0
+  graphs: ^0.2.0
+  logging: ^0.11.2
+  package_resolver: ^1.0.0
+  path: ^1.1.0
+  yaml: ^2.0.0
+
+dev_dependencies:
+  test: ^1.0.0
+  build_test: ^0.10.1
+  build_runner: ^1.0.0
+  build_vm_compilers: ">=0.1.0 <2.0.0"
+  build_modules: any
+
+dependency_overrides:
+  build_vm_compilers:
+    path: ../build_vm_compilers
+  build_modules:
+    path: ../build_modules
diff --git a/build_runner/BUILD.gn b/build_runner/BUILD.gn
new file mode 100644
index 0000000..6a8d0eb
--- /dev/null
+++ b/build_runner/BUILD.gn
@@ -0,0 +1,48 @@
+# This file is generated by importer.py for build_runner-1.7.4
+
+import("//build/dart/dart_library.gni")
+
+dart_library("build_runner") {
+  package_name = "build_runner"
+
+  # This parameter is left empty as we don't care about analysis or exporting
+  # these sources outside of the tree.
+  sources = []
+
+  disable_analysis = true
+
+  deps = [
+    "//third_party/dart-pkg/pub/shelf_web_socket",
+    "//third_party/dart-pkg/pub/code_builder",
+    "//third_party/dart-pkg/pub/watcher",
+    "//third_party/dart-pkg/pub/pubspec_parse",
+    "//third_party/dart-pkg/pub/meta",
+    "//third_party/dart-pkg/pub/io",
+    "//third_party/dart-pkg/pub/build_config",
+    "//third_party/dart-pkg/pub/pub_semver",
+    "//third_party/dart-pkg/pub/graphs",
+    "//third_party/dart-pkg/pub/web_socket_channel",
+    "//third_party/dart-pkg/pub/yaml",
+    "//third_party/dart-pkg/pub/build",
+    "//third_party/dart-pkg/pub/dart_style",
+    "//third_party/dart-pkg/pub/build_runner_core",
+    "//third_party/dart-pkg/pub/stack_trace",
+    "//third_party/dart-pkg/pub/pedantic",
+    "//third_party/dart-pkg/pub/build_daemon",
+    "//third_party/dart-pkg/pub/shelf",
+    "//third_party/dart-pkg/pub/glob",
+    "//third_party/dart-pkg/pub/args",
+    "//third_party/dart-pkg/pub/http_multi_server",
+    "//third_party/dart-pkg/pub/collection",
+    "//third_party/dart-pkg/pub/js",
+    "//third_party/dart-pkg/pub/mime",
+    "//third_party/dart-pkg/pub/timing",
+    "//third_party/dart-pkg/pub/path",
+    "//third_party/dart-pkg/pub/pool",
+    "//third_party/dart-pkg/pub/logging",
+    "//third_party/dart-pkg/pub/crypto",
+    "//third_party/dart-pkg/pub/stream_transform",
+    "//third_party/dart-pkg/pub/build_resolvers",
+    "//third_party/dart-pkg/pub/async",
+  ]
+}
diff --git a/build_runner/CHANGELOG.md b/build_runner/CHANGELOG.md
new file mode 100644
index 0000000..684e5b2
--- /dev/null
+++ b/build_runner/CHANGELOG.md
@@ -0,0 +1,962 @@
+## 1.7.4
+
+- Give a warning instead of a stack trace when using a build config override
+  file for a package that does not exist.
+- Allow the latest build_config version.
+
+## 1.7.3
+
+- Improve the error message when a `--hostname` argument is invalid.
+- Require SDK version `2.6.0` to enable extension methods.
+- Allow the latest `stream_transform`.
+
+## 1.7.2
+
+- Enable the native windows directory watcher by default.
+  - Added a --use-polling-watcher option which overrides this to use a polling
+    watcher again.
+  - Increased the lower bound for the SDK to a version which contains various
+    fixes for the native windows directory watcher.
+- Give a more consistent ordering for Builders when their ordering is allowed to
+  be arbitrary.
+- Handle more `--help` invocations without generating the build script.
+
+## 1.7.1
+
+- Allow `build` version 1.2.x.
+
+## 1.7.0
+
+### New Feature: Build Filters
+
+Build filters allow you to choose explicitly which files to build instead of
+building entire directories.
+
+A build filter is a combination of a package and a path, with glob syntax
+supported for each.
+
+Whenever a build filter is provided, only required outputs matching one of the
+build filters will be built, in addition to the inputs to those outputs.
+
+### Command Line Usage
+
+Build filters are supplied using the new `--build-filter` option, which accepts
+relative paths under the package as well as `package:` uris.
+
+Glob syntax is allowed in both package names and paths.
+
+**Example**: The following would build and serve the JS output for an
+application, as well as copy over the required SDK resources for that app:
+
+```
+pub run build_runner serve \
+  --build-filter="web/main.dart.js" \
+  --build-filter="package:build_web_compilers/**/*.js"
+```
+
+### Build Daemon Usage
+
+The build daemon now accepts build filters when registering a build target. If
+no filters are supplied these default filters are used, which is designed to
+match the previous behavior as closely as possible:
+
+- `<target-dir>/**`
+- `package:*/**`
+
+**Note**: There is one small difference compared to the previous behavior,
+which is that build to source outputs from other top level directories in the
+root package will no longer be built when they would have before. This should
+have no meaningful impact other than being more efficient.
+
+### Common Use Cases
+
+**Note**: For all the listed use cases it is up to the user or tool the user is
+using to request all the required files for the desired task. This package only
+provides the core building blocks for these use cases.
+
+#### Testing
+
+If you have a large number of tests but only want to run a single one you can
+now build just that test instead of all tests under the `test` directory.
+
+This can greatly speed up iteration times in packages with lots of tests.
+
+**Example**: This will build a single web test and run it:
+
+```
+pub run build_runner test \
+  --build-filter="test/my_test.dart.browser_test.dart.js" \
+  --build-filter="package:build_web_compilers/**/*.js" \
+  -- -p chrome test/my_test.dart
+```
+
+**Note**: If your test requires any other generated files (css, etc) you will
+need to add additional filters.
+
+#### Applications
+
+This feature works as expected with the `--output <dir>` and the `serve`
+command.  This means you can create an output directory for a single
+application in your package instead of all applications under the same
+directory.
+
+The serve command also uses the build filters to restrict what files are
+available, which means it ensures if something works in serve mode it will
+also work when you create an output directory.
+
+## 1.6.9
+
+- Fix bugs in snapshot invalidation logic that prevented invalidation when
+  core packages changed and always created a new snapshot on the second build.
+
+## 1.6.8
+
+- Improve the manual change detector to do a file system scan on demand instead
+  of using a file watcher.
+
+## 1.6.7
+
+- Set the `charset` to `utf-8` for Dart content returned by the `AssetHandler`.
+
+## 1.6.6
+
+- Added watch event debouncing to the `daemon` command to line up with the
+  `watch` command. This makes things work more nicely with swap files as well
+  as "save all" type scenarios (you will only get a single build most times).
+
+## 1.6.5
+
+- Require `package:build_config` `">=0.4.1 <0.4.2"`. Use new API that improves
+  error information when a build configuration file is malformed.
+
+## 1.6.4
+
+- Fix an issue where warning logs on startup were accidentally upgraded to
+  severe logs in the daemon mode.
+
+## 1.6.3
+
+- Preemptively re-snapshot when the `build_runner` or `build_daemon` packages
+  are updated.
+
+## 1.6.2
+
+- Support the latest `build_daemon` version.
+- Expose `assetServerPort` as a top level helper method.
+
+## 1.6.1
+
+- Update the `test` command to wait to exit until the inner test process exits.
+
+## 1.6.0
+
+- Depend on the latest `build_daemon` and provide a shutdown notification on
+  build script updates.
+
+## 1.5.2
+
+- Use a polling directory watcher by default on windows again.
+
+## 1.5.1
+
+- Fix an issue where exit codes were not set correctly when running the
+  generated build script directly.
+
+## 1.5.0
+
+- Update to the latest `build_daemon`.
+
+## 1.4.0
+
+- Add a `run` command to execute VM entrypoints with generated sources.
+
+## 1.3.5
+
+- Use the latest `build_daemon`.
+
+## 1.3.4
+
+- Use the latest `build_config`.
+
+## 1.3.3
+
+- Use `HttpMultiServer.loopback` for the daemon asset server.
+
+## 1.3.2
+
+- Fix an error where daemon mode would claim support for prompts when it can't
+  actually support them and would hang instead.
+- Improve logging when the daemon fails to start up, previously no logs would
+  be shown.
+
+## 1.3.1
+
+- Remove usage of set literals to fix errors on older sdks that don't support
+  them.
+
+## 1.3.0
+
+- Fix an issue where we might re-use stale build snapshots, which could only be
+  resolved by deleting the `.dart_tool` dir (or doing a `clean`).
+- Depend on the latest `build_runner_core` and `build_daemon` releases.
+
+## 1.2.8
+
+- Fix issue where daemon command wouldn't properly shutdown.
+- Allow running when the root package, or a path dependency, is named `test`.
+
+## 1.2.7
+
+- Fix issue where daemon command would occasionally color a log.
+
+## 1.2.6
+
+- Prevent terminals being launched when running the daemon command on Windows.
+- No longer assumeTty when logging through the daemon command.
+- Update `build_daemon` to version `0.4.0`.
+- Update `build_runner_core` to version `2.0.3`.
+- Ensure the daemon command always exits.
+
+## 1.2.5
+
+- Fix a bug with the build daemon where the output options were ignored.
+
+## 1.2.4
+
+- Update `build_resolvers` to version `1.0.0`.
+
+## 1.2.3
+
+- Fix a bug where changing between `--live-reload` and `--hot-reload` might not
+  work due to the etags for the injected JS not changing when they should.
+
+## 1.2.2
+
+- Change the format of Build Daemon messages.
+- Build Daemon asset server now properly waits for build results.
+- Build Daemon now properly signals the start of a build.
+- Fix path issues with Daemon command under Windows.
+
+## 1.2.1
+
+- Update `package:build_runner_core` to version `2.0.1`.
+
+## 1.2.0
+
+- Support building through `package:build_daemon`.
+- Update `package:build_runner_core` to version `2.0.0`.
+
+## 1.1.3
+
+- Update to `package:graphs` version `0.2.0`.
+- Fix an issue where when running from source in watch mode the script would
+  delete itself when it shouldn't.
+- Add digest string to the asset graph visualization.
+- Added a filter box to the asset graph visualization.
+- Allow `build` version `1.1.x`.
+
+## 1.1.2
+
+- Improve error message when the generated build script cannot be parsed or
+  compiled and exit with code `78` to indicate that there is a problem with
+  configuration in the project or a dependency's `build.yaml`.
+
+## 1.1.1
+
+### Bug Fixes
+
+- Handle re-snapshotting the build script on SDK updates.
+- Suppress header for the `Bootstrap` logger.
+
+## 1.1.0
+
+### New Features
+
+- The build script will now be ran from snapshot, which improves startup times.
+- The build script will automatically re-run itself when the build script is
+  changed, instead of requiring the user to re-run it manually.
+
+## 1.0.0
+
+### Breaking Changes
+
+- Massive cleanup of the public api. The only thing exported from this package
+  is now the `run` method. The goal is to reduce the surface area in order to
+  stabilize this package, since it is directly depended on by all users.
+  - Removed all exports from build_runner_core, if you are creating a custom
+    build script you will need to import build_runner_core directly and add a
+    dependency on it.
+  - Stopped exporting the `build` and `watch` functions directly, as well as the
+    `ServeHandler`.
+  - If this has broken your use case please file an issue on the package and
+    request that we export the api you were using previously. These will be
+    considered on an individual basis but the bar for additional exports will be
+    high.
+- Removed support for the --[no-]assume-tty command line argument as it is no
+  longer needed.
+
+## 0.10.3
+
+- Improve performance tracking and visualization using the `timing` package.
+- Handle bad asset graph in the `clean` command.
+
+## 0.10.2
+
+- Added `--hot-reload` cli option and appropriate functionality.
+  See [hot-module-reloading](../docs/hot_module_reloading.md) for more info.
+- Removed dependency on cli_util.
+
+## 0.10.1+1
+
+- Added better error handling when a socket is already in use in `serve` mode.
+
+## 0.10.1
+
+- Added `--live-reload` cli option and appropriate functionality
+- Migrated glob tracking to a specialized node type to fix dart-lang/build#1702.
+
+## 0.10.0
+
+### Breaking Changes
+
+- Implementations of `BuildEnvironment` must now implement the `finalizeBuild`
+  method. There is a default implementation if you extend `BuildEnvironment`
+  that is a no-op.
+  - This method is invoked at the end of the build that allows you to do
+    arbitrary additional work, such as creating merged output directories.
+- The `assumeTty` argument on `IOEnvironment` has moved to a named argument
+  since `null` is an accepted value.
+- The `outputMap` field on `BuildOptions` has moved to the `IOEnvironment`
+  class.
+
+### New Features/Updates
+
+- Added a `outputSymlinksOnly` option to `IOEnvironment` constructor, that
+  causes the merged output directories to contain only symlinks, which is much
+  faster than copying files.
+- Added the `FinalizedAssetView` class which provides a list of all available
+  assets to the `BuildEnvironment` during the build finalization phase.
+  - `outputMap` has moved from `BuildOptions` to this constructor, as a named
+    argument.
+- The `OverridableEnvironment` now supports overriding the new `finalizeBuild`
+  api.
+- The number of concurrent actions per phase is now limited (currently to 16),
+  which should help with memory and cpu usage for large builds.
+
+## 0.9.2
+
+- Changed the default file caching logic to use an LRU cache.
+
+## 0.9.1+1
+
+- Increased the upper bound for the sdk to `<3.0.0`.
+
+## 0.9.1
+
+- The hash dir for the asset graph under `.dart_tool/build` is now based on a
+  relative path to the build script instead of the absolute path.
+  - This enables `.dart_tool/build` directories to be reused across different
+    computers and directories for the same project.
+
+## 0.9.0
+
+### New Features
+
+- Added the `--log-performance <dir>` option which will dump performance
+  information to `<dir>` after each build.
+- The `BuildPerformance` class is now serializable, it has a `fromJson`
+  constructor and a `toJson` instance method.
+- Added support for `global_options` in `build.yaml` of the root package.
+- Allow overriding the default `Resolvers` implementation.
+- Allows building with symlinked files. Note that changes to the linked files
+  will not trigger rebuilds in watch or serve mode.
+
+### Breaking changes
+
+- `BuildPhasePerformance.action` has been replaced with
+  `BuildPhasePerformance.builderKeys`.
+- `BuilderActionPerformance.builder` has been replaced with
+  `BuilderActionPerformance.builderKey`.
+- `BuildResult` no longer has an `exception` or `stackTrace` field.
+- The 'test' command through `run` will no longer set an exit code. All manual
+  build scripts which call `run` should use the `Future<int>` return to set the
+  exit code for the process.
+- Dropped `failOnSevere` arguments and `--fail-on-severe` flag. Severe logs are
+  always considered failing.
+- Severe level logs now go to `stdout` along with other logs rather than
+  `stderr`. Uncaught exceptions from the `build_runner` system itself still go
+  to `stderr`.
+
+## Other
+
+- Updated to the latest camel case constants from the sdk.
+- Minimum sdk version is now `>=2.0.0-dev.61`.
+
+## 0.8.10
+
+- All builders with `build_to: source` will now be ran regardless of which
+  directory is currently being built, see
+  https://github.com/dart-lang/build/issues/1454 for context.
+- `build` will now throw instead of returning a failed build result if nothing
+  was built.
+- Improve error message when a dependency has a bad `build.yaml` with a missing
+  dependency.
+- Sources that are not a part of a `target` will no longer appear in the asset
+  graph, so they will not be readable or globbable.
+- Updated the generated build script to not rely on json encode/decode for the
+  builder options object. Instead it now directly inlines a map literal.
+
+## 0.8.9
+
+- Added support for building only specified top level directories.
+  - The `build`, `watch` commands support positional arguments which are the
+    directories to build. For example, `pub run build_runner build web` will
+    only build the `web` directory.
+  - The `serve` command treats positional args as it did before, except it will
+    only build the directories you ask it to serve.
+  - The `test` command will automatically only build the `test` directory.
+  - If using the `-o` option, with the `<dir-to-build>:<output-dir>` syntax,
+    then the `<dir-to-build>` will be added to the list of directories to build.
+    - If additional directories are supplied with positional arguments, then
+      those will also be built.
+- Update to latest analyzer and build packages.
+- Updated the `serve` logic to only serve files which were part of the actual
+  build, and not stale assets. This brings the semantics exactly in line with
+  what would be copied to the `-o` output directory.
+
+## 0.8.8
+
+- Improve search behavior on the `/$graph` page. Users can now query for
+  paths and `AssetID` values – `pkg_name|lib/pkg_name.dart`.
+- Commands that don't support trailing args will now appropriately fail with a
+  usage exception.
+- Fix a bug where some rebuilds would not happen after adding a new file that
+  has outputs which were missing during the previous build.
+- Fix a bug where failing actions which are no longer required can still cause
+  the overall build to fail.
+
+## 0.8.7
+
+- Improve error handling on the `/$graph` page.
+- Support the latest `package:builde`
+
+## 0.8.6
+
+- Forward default options for `PostProcessBuilder`s in the generated build
+  script.
+- If a build appears to be not making progress (no actions completed for 15
+  seconds), then a warning will now be logged with the pending actions.
+- Now fail when a build is requested which does not build anything.
+- Clean up some error messages.
+
+## 0.8.5
+
+- Add log message for merged output errors.
+
+## 0.8.4
+
+- Log the number of completed actions on successful builds.
+- Support the new `isRoot` field for `BuilderOptions` so that builders can
+  do different things for the root package.
+- Deprecated `PostProcessBuilder` and `PostProcessBuildStep`. These should be
+  imported from `package:build` instead.
+
+## 0.8.3
+
+- Clean and summarize stack traces printed with `--verbose`.
+- Added a `clean` command which deletes generated to source files and the entire
+  build cache directory.
+- Bug Fix: Use the same order to compute the digest of input files to a build
+  step when writing it as when comparing it. Previously builds would not be
+  pruned as efficiently as they can be because the inputs erroneously looked
+  different.
+
+## 0.8.2+2
+
+- The `.packages` file is now always created in the root of the output directory
+  instead of under each top level directory.
+
+## 0.8.2+1
+
+- Bug Fix: Correctly parse Window's paths with new `--output` semantics.
+
+## 0.8.2
+
+- Allow passing multiple `--output` options. Each option will be split on
+  `:`. The first value will be the root input directory, the second value will
+  be the output directory. If no delimeter is provided, all resources
+  will be copied to the output directory.
+- Allow deleting files in the post process build step.
+- Bug Fix: Correctly include the default whitelist when multiple targets
+  without include are provided.
+- Allow logging from within a build factory.
+- Allow serving assets from successful build steps if the overall build fails.
+- Add a `--release` flag to choose the options from `release_options` in
+  `build.yaml`. This should replace the need to use `--config` pointing to a
+  release version of `build.yaml`.
+
+## 0.8.1
+
+- Improved the layout of `/$perf`, especially after browser window resize.
+- `pub run build_runner` exits with a error when invoked with an unsupported
+  command.
+- Bug Fix: Update outputs in merged directory for sources which are not used
+  during the build. For example if `web/index.html` is not read to produce any
+  generated outputs changes to this file will now get picked up during `pub run
+  build_runner watch --output build`.
+- Don't allow a thrown exception from a Builder to take down the entire build
+  process - instead record it as a failed action. The overall build will still
+  be marked as a failure, but it won't crash the process.
+
+## 0.8.0
+
+### New Features
+
+- Added the new `PostProcessBuilder` class. These are not supported in bazel,
+  and are different than a normal `Builder` in some fundamental ways:
+  - They don't have to declare output extensions, and can output any file as
+    long as it doesn't conflict with an existing one. This is only checked at
+    build time.
+  - They can only read their primary input.
+  - They will not cause optional actions to run - they will only run on assets
+    that were built as a part of the normal build.
+  - They can not be optional themselves, and can only output to cache.
+  - Because they all run in a single phase, after other builders, none of their
+    outputs can be used as inputs to any actions.
+- Added `applyPostProccess` method which takes `PostProcessBuilderFactory`s
+  instead of `BuilderFactory`s.
+
+### Breaking Changes
+
+- `BuilderApplication` now has a `builderActionFactories` getter instead of a
+  `builderFactories` getter.
+- The default constructor for `BuilderApplication` has been replaced with
+  `BuilderApplication.forBuilder` and
+  `BuilderApplication.forPostProcessBuilder`.
+
+## 0.7.14
+
+- Warn when using `--define` or `build.yaml` configuration for invalid builders.
+
+## 0.7.13+1
+
+- Fix a concurrent modification error when using `listAssets` when an asset
+  could be written.
+
+## 0.7.13
+
+- Fix a bug where a chain of `Builder`s would fail to run on some outputs from
+  previous steps when the generated asset did not match the target's `sources`.
+- Added support for serving on IPv4 loopback when the server hostname is
+  'localhost' (the default).
+- Added support for serving on any connection (both IPv4 and IPv6) when the
+  hostname is 'any'.
+- Improved stdout output.
+
+## 0.7.12
+
+- Added the `--log-requests` flag to the `serve` command, which will log all
+  requests to the server.
+- Build actions using `findAssets` will be more smartly invalidated.
+- Added a warning if using `serve` mode but no directories were found to serve.
+- The `--output` option now only outputs files that were required for the latest
+  build. Previously when switching js compilers you could end up with ddc
+  modules in your dart2js output, even though they weren't required. See
+  https://github.com/dart-lang/build/issues/1033.
+- The experimental `create_merged_dir` binary is now removed, it can't be easily
+  supported any more and has been replaced by the `--output` option.
+- Builders which write to `source` are no longer guaranteed to run before
+  builders which write to `cache`.
+- Honors `runs_before` configuration from Builder definitions.
+- Honors `applies_builders` configuration from Builder definitions.
+
+## 0.7.11+1
+
+- Switch to use a `PollingDirectoryWatcher` on windows, which should fix file
+  watching with the `--output` option. Follow along at
+  https://github.com/dart-lang/watcher/issues/52 for more details.
+
+## 0.7.11
+
+- Performance tracking is now disabled by default, and you must pass the
+  `--track-performance` flag to enable it.
+- The heartbeat logger will now log the current number of completed versus
+  scheduled actions, and it will log once a second instead of every 5 seconds.
+- Builds will now be invalidated when the dart SDK is updated.
+- Fixed the error message when missing a build_test dependency but trying to run
+  the `test` command.
+- The build script will now exit on changes to `build.yaml` files.
+
+## 0.7.10+1
+
+- Fix bug where relative imports in a dependencies build.yaml would break
+  all downstream users, https://github.com/dart-lang/build/issues/995.
+
+## 0.7.10
+
+### New Features
+
+- Added a basic performance visualization. When running with `serve` you can
+  now navigate to `/$perf` and get a timeline of all actions. If you are
+  experiencing slow builds (especially incremental ones), you can save the
+  html of that page and attach it to bug reports!
+
+### Bug Fixes
+
+- When using `--output` we will only clean up files we know we previously output
+  to the specified directory. This should allow running long lived processes
+  such as servers in that directory (as long as they don't hold open file
+  handles).
+
+## 0.7.9+2
+
+- Fixed a bug with build to source and watch mode that would result in an
+  infinite build loop, [#962](https://github.com/dart-lang/build/issues/962).
+
+## 0.7.9+1
+
+- Support the latest `analyzer` package.
+
+## 0.7.9
+
+### New Features
+
+- Added command line args to override config for builders globally. The format
+  is `--define "<builder_key>=<option>=<value>"`. As an example, enabling the
+  dart2js compiler for the `build_web_compilers|entrypoint` builder would look
+  like this: `--define "build_web_compilers|entrypoint=compiler=dart2js"`.
+
+### Bug Fixes
+
+- Fixed an issue with mixed mode builds, see
+  https://github.com/dart-lang/build/issues/924.
+- Fixed some issues with exit codes and --fail-on-severe, although there are
+  still some outstanding problems. See
+  https://github.com/dart-lang/build/issues/910 for status updates.
+- Fixed an issue where the process would hang on exceptions, see
+  https://github.com/dart-lang/build/issues/883.
+- Fixed an issue with etags not getting updated for source files that weren't
+  inputs to any build actions, https://github.com/dart-lang/build/issues/894.
+- Fixed an issue with hidden .DS_Store files on mac in the generated directory,
+  https://github.com/dart-lang/build/issues/902.
+- Fixed test output so it will use the compact reporter,
+  https://github.com/dart-lang/build/issues/821.
+
+## 0.7.8
+
+- Add `--config` option to use a different `build.yaml` at build time.
+
+## 0.7.7+1
+
+- Avoid watching hosted dependencies for file changes.
+
+## 0.7.7
+
+- The top level `run` method now returns an `int` which represents an `exitCode`
+  for the command that was executed.
+  - For now we still set the exitCode manually as well but this will likely
+    change in the next breaking release. In manual scripts you should `await`
+    the call to `run` and assign that to `exitCode` to be future-proofed.
+
+## 0.7.6
+
+- Update to package:build version `0.12.0`.
+- Removed the `DigestAssetReader` interface, the `digest` method has now moved
+  to the core `AssetReader` interface. We are treating this as a non-breaking
+  change because there are no known users of this interface.
+
+## 0.7.5+1
+
+- Bug fix for using the `--output` flag when you have no `test` directory.
+
+## 0.7.5
+
+- Add more human friendly duration printing.
+- Added the `--output <dir>` (or `-o`) argument which will create a merged
+  output directory after each build.
+- Added the `--verbose` (or `-v`) flag which enables verbose logging.
+  - Disables stack trace folding and terse stack traces.
+  - Disables the overwriting of previous info logs.
+  - Sets the default log level to `Level.ALL`.
+- Added `pubspec.yaml` and `pubspec.lock` to the whitelist for the root package
+  sources.
+
+## 0.7.4
+
+- Allows using files in any build targets in the root package as sources if they
+  fall outside the hardcoded whitelist.
+- Changes to the root `.packages` file during watch mode will now cause the
+  build script to exit and prompt the user to restart the build.
+
+## 0.7.3
+
+- Added the flag `--low-resources-mode`, which defaults to `false`.
+
+## 0.7.2
+
+- Added the flag `--fail-on-severe`, which defaults to `false`. In a future
+  version this will default to `true`, which means that logging a message via
+  `log.severe` will fail the build instead of just printing to the terminal.
+  This would match the current behavior in `bazel_codegen`.
+- Added the `test` command to the `build_runner` binary.
+
+## 0.7.1+1
+
+- **BUG FIX**: Running the `build_runner` binary without arguments no longer
+  causes a crash saying `Could not find an option named "assume-tty".`.
+
+## 0.7.1
+
+- Run Builders which write to the source tree before those which write to the
+  build cache.
+
+## 0.7.0
+
+### New Features
+
+- Added `toRoot` Package filter.
+- Actions are now invalidated at a fine grained level when `BuilderOptions`
+  change.
+- Added magic placeholder files in all packages, which can be used when your
+  builder doesn't have a clear primary input file.
+  - For non-root packages the placeholder exists at `lib/$lib$`, you should
+    declare your `buildExtensions` like this `{r'$lib$': 'my_output_file.txt'}`,
+    which would result in an output file at `lib/my_output_file.txt` in the
+    package.
+  - For the root package there are also placeholders at `web/$web$` and
+    `test/$test$` which should cover most use cases. Please file an issue if you
+    need additional placeholders.
+  - Note that these placeholders are not real assets and attempting to read them
+    will result in an `AssetNotFoundException`.
+
+### Breaking Changes
+
+- Removed `BuildAction`. Changed `build` and `watch` to take a
+  `List<BuilderApplication>`. See `apply` and `applyToRoot` to set these up.
+- Changed `apply` to take a single String argument - a Builder key from
+  `package:build_config` rather than a separate package and builder name.
+- Changed the default value of `hideOutput` from `false` to `true` for `apply`.
+  With `applyToRoot` the value remains `false`.
+- There is now a whitelist of top level directories that will be used as a part
+  of the build, and other files will be ignored. For now those directories
+  include 'benchmark', 'bin', 'example', 'lib', 'test', 'tool', and 'web'.
+  - If this breaks your workflow please file an issue and we can look at either
+    adding additional directories or making the list configurable per project.
+- Remove `PackageGraph.orderedPackages` and `PackageGraph.dependentsOf`.
+- Remove `writeToCache` argument of `build` and `watch`. Each `apply` call
+  should specify `hideOutput` to keep this behavior.
+- Removed `PackageBuilder` and `PackageBuildActions` classes. Use the new
+  magic placeholder files instead (see new features section for this release).
+
+The following changes are technically breaking but should not impact most
+clients:
+
+- Upgrade to `build_barback` v0.5.0 which uses strong mode analysis and no
+  longer analyzes method bodies.
+- Removed `dependencyType`, `version`, `includes`, and `excludes` from
+  `PackageNode`.
+- Removed `PackageNode.noPubspec` constructor.
+- Removed `InputSet`.
+- PackageGraph instances enforce that the `root` node is the only node with
+  `isRoot == true`.
+
+## 0.6.1
+
+### New Features
+
+- Add an `enableLowResourcesMode` option to `build` and `watch`, which will
+  consume less memory at the cost of slower builds. This is intended for use in
+  resource constrained environments such as Travis.
+- Add `createBuildActions`. After finding a list of Builders to run, and defining
+  which packages need them applied, use this tool to apply them in the correct
+  order across the package graph.
+
+### Deprecations
+
+- Deprecate `PackageGraph.orderedPackages` and `PackageGraph.dependentsOf`.
+
+### Internal Improvements
+
+- Outputs will no longer be rebuilt unless their inputs actually changed,
+  previously if any transtive dependency changed they would be invalidated.
+- Switched to using semantic analyzer summaries, this combined with the better
+  input validation means that, ddc/summary builds are much faster on non-api
+  affecting edits (dependent modules will no longer be rebuilt).
+- Build script invalidation is now much faster, which speeds up all builds.
+
+### Bug Fixes
+
+- The build actions are now checked against the previous builds actions, and if
+  they do not match then a full build is performed. Previously the behavior in
+  this case was undefined.
+- Fixed an issue where once an edge between an output and an input was created
+  it was never removed, causing extra builds to happen that weren't necessary.
+- Build actions are now checked for overlapping outputs in non-checked mode,
+  previously this was only an assert.
+- Fixed an issue where nodes could get in an inconsistent state for short
+  periods of time, leading to various errors.
+- Fixed an issue on windows where incremental builds didn't work.
+
+## 0.6.0+1
+
+### Internal Improvements
+
+- Now using `package:pool` to limit the number of open file handles.
+
+### Bug fixes
+
+- Fixed an issue where the asset graph could get in an invalid state if you
+  aren't setting `writeToCache: true`.
+
+## 0.6.0
+
+### New features
+
+- Added `orderedPackages` and `dependentsOf` utilities to `PackageGraph`.
+- Added the `noPubspec` constructor to `PackageNode`.
+- Added the `PackageBuilder` and `PackageBuildAction` classes. These builders
+  only run once per package, and have no primary input. Outputs must be well
+  known ahead of time and are declared with the `Iterable<String> get outputs`
+  field, which returns relative paths under the current package.
+- Added the `isOptional` field to `BuildAction`. Setting this to `true` means
+  that the action will not run unless some other non-optional action tries to
+  read one of the outputs of the action.
+- **Breaking**: `PackageNode.location` has become `PackageNode.path`, and is
+  now a `String` (absolute path) instead of a `Uri`; this prevents needing
+  conversions to/from `Uri` across the package.
+- **Breaking**: `RunnerAssetReader` interface requires you to implement
+  `MultiPackageAssetReader` and `DigestAssetReader`. This means the
+  `packageName` named argument has changed to `package`, and you have to add the
+  `Future<Digest> digest(AssetId id)` method. While technically breaking most
+  users do not rely on this interface explicitly.
+  - You also no longer have to implement the
+    `Future<DateTime> lastModified(AssetId id)` method, as it has been replaced
+    with the `DigestAssetReader` interface.
+- **Breaking**: `ServeHandler.handle` has been replaced with
+  `Handler ServeHandler.handleFor(String rootDir)`. This allows you to create
+  separate handlers per directory you want to serve, which maintains pub serve
+  conventions and allows interoperation with `pub run test --pub-serve=$PORT`.
+
+### Bug fixes
+
+- **Breaking**: All `AssetReader#findAssets` implementations now return a
+  `Stream<AssetId>` to match the latest `build` package. This should not affect
+  most users unless you are extending the built in `AssetReader`s or using them
+  in a custom way.
+- Fixed an issue where `findAssets` could return declared outputs from previous
+  phases that did not actually output the asset.
+- Fixed two issues with `writeToCache`:
+  - Over-declared outputs will no longer attempt to build on each startup.
+  - Unrecognized files in the cache dir will no longer be treated as inputs.
+- Asset invalidation has changed from using last modified timestamps to content
+  hashes. This is generally much more reliable, and unblocks other desired
+  features.
+
+### Internal changes
+
+- Added `PackageGraphWatcher` and `PackageNodeWatcher` as a wrapper API,
+  including an `AssetChange` class that is now consistently used across the
+  package.
+
+## 0.5.0
+
+- **Breaking**: Removed `buildType` field from `BuildResult`.
+- **Breaking**: `watch` now returns a `ServeHandler` instead of a
+  `Stream<BuildResult>`. Use `ServeHandler.buildResults` to get back to the
+  original stream.
+- **Breaking**: `serve` has been removed. Instead use `watch` and use the
+  resulting `ServeHandler.handle` method along with a server created in the
+  client script to start a server.
+- Prevent reads into `.dart_tool` for more hermetic builds.
+- Bug Fix: Rebuild entire asset graph if the build script changes.
+- Add `writeToCache` argument to `build` and `watch` which separates generated
+  files from the source directory and allows running builders against other
+  packages.
+- Allow the latest version of `package:shelf`.
+
+## 0.4.0+3
+
+- Bug fix: Don't try to delete files generated for other packages.
+
+## 0.4.0+2
+
+- Bug fix: Don't crash after a Builder reads a file from another package.
+
+## 0.4.0+1
+
+- Depend on `build` 0.10.x and `build_barback` 0.4.x
+
+## 0.4.0
+
+- **Breaking**: The `PhaseGroup` class has been replaced with a
+  `List<BuildAction>` in `build`, `watch`, and `serve`. The `PhaseGroup` and
+  `Phase` classes are removed.
+  If your current build has multiple actions in a single phase which are
+  depending on *not* seeing the outputs from other actions in the phase you will
+  need to instead set up the `InputSet`s so that the outputs are filtered out.
+- **Breaking**: The `resolvers` argument has been removed from `build`, `watch`,
+  and `serve`.
+- Allow `package:build` v0.10.x
+
+## 0.3.4+1
+
+- Support the latest release of `build_barback`.
+
+## 0.3.4
+
+- Support the latest release of `analyzer`.
+
+## 0.3.2
+
+- Support for build 0.9.0
+
+## 0.3.1+1
+
+- Bug Fix: Update AssetGraph version so builds can be run without manually
+  deleting old build directory.
+- Bug Fix: Check for unreadable assets in an async method rather than throw
+  synchronously
+
+## 0.3.1
+
+- Internal refactoring of RunnerAssetReader.
+- Support for build 0.8.0
+- Add findAssets on AssetReader implementations
+- Limit Asset reads to those which were available at the start of the phase.
+  This might cause some reads which uses to succeed to fail.
+
+## 0.3.0
+
+### Bug Fixes
+
+- Fixed a race condition bug [175](https://github.com/dart-lang/build/issues/175)
+  that could cause invalid output errors.
+
+### Breaking Changes
+
+- `RunnerAssetWriter` now requires an additional field, `onDelete` which is a
+  callback that must be called synchronously within `delete`.
+
+## 0.2.0
+
+Add support for the new bytes apis in `build`.
+
+### New Features
+
+- `FileBasedAssetReader` and `FileBasedAssetWriter` now support reading/writing
+  as bytes.
+
+### Breaking Changes
+
+- Removed the `AssetCache`, `CachedAssetReader`, and `CachedAssetWriter`. These
+  may come back at a later time if deemed necessary, but for now they just
+  complicate things unnecessarily without proven benefits.
+- `BuildResult#outputs` now has a type of `List<AssetId>` instead of
+  `List<Asset>`, since the `Asset` class no longer exists. Additionally this was
+  wasting memory by keeping all output contents around when it's not generally
+  a very useful thing outside of tests (which retain this information in other
+  ways).
+
+## 0.0.1
+
+- Initial separate release - split off from `build` package.
diff --git a/build_runner/LICENSE b/build_runner/LICENSE
new file mode 100644
index 0000000..82e9b52
--- /dev/null
+++ b/build_runner/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2016, the Dart project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of Google Inc. nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/build_runner/README.md b/build_runner/README.md
new file mode 100644
index 0000000..8845b2f
--- /dev/null
+++ b/build_runner/README.md
@@ -0,0 +1,256 @@
+<p align="center">
+  Standalone generator and watcher for Dart using <a href="https://pub.dev/packages/build"><code>package:build</code></a>.
+  <br>
+  <a href="https://travis-ci.org/dart-lang/build">
+    <img src="https://travis-ci.org/dart-lang/build.svg?branch=master" alt="Build Status" />
+  </a>
+  <a href="https://github.com/dart-lang/build/labels/package%3A%20build_runner">
+    <img src="https://img.shields.io/github/issues-raw/dart-lang/build/package%3A%20build_runner.svg" alt="Issues related to build_runner" />
+  </a>
+  <a href="https://pub.dev/packages/build_runner">
+    <img src="https://img.shields.io/pub/v/build_runner.svg" alt="Pub Package Version" />
+  </a>
+  <a href="https://pub.dev/documentation/build_runner/latest">
+    <img src="https://img.shields.io/badge/dartdocs-latest-blue.svg" alt="Latest Dartdocs" />
+  </a>
+  <a href="https://gitter.im/dart-lang/build">
+    <img src="https://badges.gitter.im/dart-lang/build.svg" alt="Join the chat on Gitter" />
+  </a>
+</p>
+
+The `build_runner` package provides a concrete way of generating files using
+Dart code, outside of tools like `pub`. Unlike `pub serve/build`, files are
+always generated directly on disk, and rebuilds are _incremental_ - inspired by
+tools such as [Bazel][].
+
+> **NOTE**: Are you a user of this package? You may be interested in
+> simplified user-facing documentation, such as our
+> [getting started guide][getting-started-link].
+
+[getting-started-link]: https://goo.gl/b9o2j6
+
+* [Installation](#installation)
+* [Usage](#usage)
+  * [Built-in commands](#built-in-commands)
+  * [Inputs](#inputs)
+  * [Outputs](#outputs)
+  * [Source control](#source-control)
+  * [Publishing packages](#publishing-packages)
+* [Contributing](#contributing)
+  * [Testing](#testing)
+
+## Installation
+
+This package is intended to support development of Dart projects with
+[`package:build`][]. In general, put it under [dev_dependencies][], in your
+[`pubspec.yaml`][pubspec].
+
+```yaml
+dev_dependencies:
+  build_runner:
+```
+
+## Usage
+
+When the packages providing `Builder`s are configured with a `build.yaml` file
+they are designed to be consumed using an generated build script. Most builders
+should need little or no configuration, see the documentation provided with the
+Builder to decide whether the build needs to be customized. If it does you may
+also provide a `build.yaml` with the configuration. See the
+`package:build_config` README for more information on this file.
+
+To have web code compiled to js add a `dev_dependency` on `build_web_compilers`.
+
+### Built-in Commands
+
+The `build_runner` package exposes a binary by the same name, which can be
+invoked using `pub run build_runner <command>`.
+
+The available commands are `build`, `watch`, `serve`, and `test`.
+
+- `build`: Runs a single build and exits.
+- `watch`: Runs a persistent build server that watches the files system for
+  edits and does rebuilds as necessary.
+- `serve`: Same as `watch`, but runs a development server as well.
+  - By default this serves the `web` and `test` directories, on port `8080` and
+    `8081` respectively. See below for how to configure this.
+- `test`: Runs a single build, creates a merged output directory, and then runs
+  `pub run test --precompiled <merged-output-dir>`. See below for instructions
+  on passing custom args to the test command.
+
+#### Command Line Options
+
+All the above commands support the following arguments:
+
+- `--help`: Print usage information for the command.
+- `--assume-tty`: Enables colors and interactive input when the script does not
+  appear to be running directly in a terminal, for instance when it is a
+  subprocess.
+- `--delete-conflicting-outputs`: Assume conflicting outputs in the users
+  package are from previous builds, and skip the user prompt that would usually
+  be provided.
+- `--[no-]fail-on-severe`: Whether to consider the build a failure on an error
+  logged. By default this is false.
+
+Some commands also have additional options:
+
+##### serve
+
+- `--hostname`: The host to run the server on.
+- `--live-reload`: Enables automatic page reloading on rebuilds.
+  Can't be used together with `--hot-reload`.
+- `--hot-reload`: Enables automatic reloading of changed modules on rebuilds.
+  See [hot-module-reloading](../docs/hot_module_reloading.md) for more info.
+  Can't be used together with `--live-reload`.
+
+Trailing args of the form `<directory>:<port>` are supported to customize what
+directories are served, and on what ports.
+
+For example to serve the `example` and `web` directories on ports 8000 and 8001
+you would do `pub run build_runner serve example:8000 web:8001`.
+
+##### test
+
+The test command will forward any arguments after an empty `--` arg to the
+`pub run test` command.
+
+For example if you wanted to pass `-p chrome` you would do
+`pub run build_runner test -- -p chrome`.
+
+### Inputs
+
+Valid inputs follow the general dart package rules. You can read any files under
+the top level `lib` folder any package dependency, and you can read all files
+from the current package.
+
+In general it is best to be as specific as possible with your `InputSet`s,
+because all matching files will be checked against a `Builder`'s
+[`buildExtensions`][build_extensions] - see [outputs](#outputs) for more
+information.
+
+### Outputs
+
+* You may output files anywhere in the current package.
+
+> **NOTE**: When a `BuilderApplication` specifies `hideOutput: true` it may
+> output under the `lib` folder of _any_ package you depend on.
+
+* Builders are not allowed to overwrite existing files, only create new ones.
+* Outputs from previous builds will not be treated as inputs to later ones.
+* You may use a previous `BuilderApplications`'s outputs as an input to a later
+  action.
+
+### Source control
+
+This package creates a top level `.dart_tool` folder in your package, which
+should not be submitted to your source control repository. You can see [our own
+`.gitignore`](https://github.com/dart-lang/build/blob/master/.gitignore) as an
+example.
+
+```git
+# Files generated by dart tools
+.dart_tool
+```
+
+When it comes to _generated_ files it is generally best to not submit them to
+source control, but a specific `Builder` may provide a recommendation otherwise.
+
+It should be noted that if you do submit generated files to your repo then when
+you change branches or merge in changes you may get a warning on your next build
+about declared outputs that already exist. This will be followed up with a
+prompt to delete those files. You can type `l` to list the files, and then type
+`y` to delete them if everything looks correct. If you think something is wrong
+you can type `n` to abandon the build without taking any action.
+
+### Publishing packages
+
+In general generated files **should** be published with your package, but this
+may not always be the case. Some `Builder`s may provide a recommendation for
+this as well.
+
+
+## Legacy Usage
+
+If the generated script does not do everything you need it's possible to
+manually write one. With this approach every package which *uses* a
+[`Builder`][builder] must have it's own script, they cannot be reused
+from other packages. A package which defines a [`Builder`][builder] may have an
+example you can reference, but a unique script must be written for the consuming
+packages as well. You can reference the generated script at
+`.dart_tool/build/entrypoint/build.dart` for an example.
+
+Your script should use one of the following functions defined by this library:
+
+- [**`run`**][run_fn]: Use the same argument parsing as the generated approach.
+- [**`build`**][build_fn]: Run a single build and exit.
+- [**`watch`**][watch_fn]: Continuously run builds as you edit files.
+
+### Configuring
+
+[`run`][run_fn], [`build`][build_fn], and [`watch`][watch_fn] have a required
+argument which is a `List<BuilderApplication>`. These correspond to the
+`BuilderDefinition` class from `package:build_config`. See `apply` and
+`applyToRoot` to create instances of this class. These will be translated into
+actions by crawling through dependencies. The order of this list is important.
+Each Builder may read the generated outputs of any Builder that ran on a package
+earlier in the dependency graph, but for the package it is running on it may
+only read the generated outputs from Builders earlier in the list of
+`BuilderApplication`s.
+
+**NOTE**: Any time you change your build script (or any of its dependencies),
+the next build will be a full rebuild. This is because the system has no way
+of knowing how that change may have affected the outputs.
+
+## Contributing
+
+We welcome a diverse set of contributions, including, but not limited to:
+
+* [Filing bugs and feature requests][file_an_issue]
+* [Send a pull request][pull_request]
+* Or, create something awesome using this API and share with us and others!
+
+For the stability of the API and existing users, consider opening an issue
+first before implementing a large new feature or breaking an API. For smaller
+changes (like documentation, minor bug fixes), just send a pull request.
+
+### Testing
+
+All pull requests are validated against [travis][travis], and must pass. The
+`build_runner` package lives in a mono repository with other `build` packages,
+and _all_ of the following checks must pass for _each_ package.
+
+Ensure code passes all our [analyzer checks][analysis_options]:
+
+```sh
+$ dartanalyzer .
+```
+
+Ensure all code is formatted with the latest [dev-channel SDK][dev_sdk].
+
+```sh
+$ dartfmt -w .
+```
+
+Run all of our unit tests:
+
+```sh
+$ pub run test
+```
+
+[Bazel]: https://bazel.build/
+[`package:build`]: https://pub.dev/packages/build
+[analysis_options]: https://github.com/dart-lang/build/blob/master/analysis_options.yaml
+
+[builder]: https://pub.dev/documentation/build/latest/build/Builder-class.html
+[run_fn]: https://pub.dev/documentation/build_runner/latest/build_runner/run.html
+[build_fn]: https://pub.dev/documentation/build_runner/latest/build_runner/build.html
+[watch_fn]: https://pub.dev/documentation/build_runner/latest/build_runner/watch.html
+[builder_application]: https://pub.dev/documentation/build_runner/latest/build_runner/BuilderApplication-class.html
+[build_extensions]: https://pub.dev/documentation/build/latest/build/Builder/buildExtensions.html
+
+[travis]: https://travis-ci.org/
+[dev_sdk]: https://dart.dev/get-dart
+[dev_dependencies]: https://dart.dev/tools/pub/dependencies#dev-dependencies
+[pubspec]: https://dart.dev/tools/pub/pubspec
+[file_an_issue]: https://github.com/dart-lang/build/issues/new
+[pull_request]: https://github.com/dart-lang/build/pulls
diff --git a/build_runner/bin/build_runner.dart b/build_runner/bin/build_runner.dart
new file mode 100644
index 0000000..e790a51
--- /dev/null
+++ b/build_runner/bin/build_runner.dart
@@ -0,0 +1,88 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:io';
+
+import 'package:args/args.dart';
+import 'package:args/command_runner.dart';
+import 'package:io/ansi.dart';
+import 'package:io/io.dart';
+import 'package:logging/logging.dart';
+
+import 'package:build_runner/src/build_script_generate/bootstrap.dart';
+import 'package:build_runner/src/entrypoint/runner.dart';
+import 'package:build_runner/src/logging/std_io_logging.dart';
+
+import 'src/commands/clean.dart';
+import 'src/commands/generate_build_script.dart';
+
+Future<void> main(List<String> args) async {
+  // Use the actual command runner to parse the args and immediately print the
+  // usage information if there is no command provided or the help command was
+  // explicitly invoked.
+  var commandRunner = BuildCommandRunner([]);
+  var localCommands = [CleanCommand(), GenerateBuildScript()];
+  var localCommandNames = localCommands.map((c) => c.name).toSet();
+  localCommands.forEach(commandRunner.addCommand);
+
+  ArgResults parsedArgs;
+  try {
+    parsedArgs = commandRunner.parse(args);
+  } on UsageException catch (e) {
+    print(red.wrap(e.message));
+    print('');
+    print(e.usage);
+    exitCode = ExitCode.usage.code;
+    return;
+  }
+
+  var commandName = parsedArgs.command?.name;
+
+  if (parsedArgs.rest.isNotEmpty) {
+    print(
+        yellow.wrap('Could not find a command named "${parsedArgs.rest[0]}".'));
+    print('');
+    print(commandRunner.usageWithoutDescription);
+    exitCode = ExitCode.usage.code;
+    return;
+  }
+
+  if (commandName == 'help' ||
+      parsedArgs.wasParsed('help') ||
+      (parsedArgs.command?.wasParsed('help') ?? false)) {
+    await commandRunner.runCommand(parsedArgs);
+    return;
+  }
+
+  if (commandName == null) {
+    commandRunner.printUsage();
+    exitCode = ExitCode.usage.code;
+    return;
+  }
+
+  StreamSubscription logListener;
+  if (commandName == 'daemon') {
+    // Simple logs only in daemon mode. These get converted into info or
+    // severe logs by the client.
+    logListener = Logger.root.onRecord.listen((record) {
+      if (record.level >= Level.SEVERE) {
+        var buffer = StringBuffer(record.message);
+        if (record.error != null) buffer.writeln(record.error);
+        if (record.stackTrace != null) buffer.writeln(record.stackTrace);
+        stderr.writeln(buffer);
+      } else {
+        stdout.writeln(record.message);
+      }
+    });
+  } else {
+    logListener = Logger.root.onRecord.listen(stdIOLogListener());
+  }
+  if (localCommandNames.contains(commandName)) {
+    exitCode = await commandRunner.runCommand(parsedArgs);
+  } else {
+    while ((exitCode = await generateAndRun(args)) == ExitCode.tempFail.code) {}
+  }
+  await logListener?.cancel();
+}
diff --git a/build_runner/bin/graph_inspector.dart b/build_runner/bin/graph_inspector.dart
new file mode 100644
index 0000000..21e6ea0
--- /dev/null
+++ b/build_runner/bin/graph_inspector.dart
@@ -0,0 +1,247 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:io';
+
+import 'package:args/args.dart';
+import 'package:args/command_runner.dart';
+import 'package:build/build.dart';
+import 'package:glob/glob.dart';
+import 'package:logging/logging.dart';
+import 'package:path/path.dart' as p;
+
+import 'package:build_runner_core/build_runner_core.dart';
+import 'package:build_runner_core/src/asset_graph/graph.dart';
+import 'package:build_runner_core/src/asset_graph/node.dart';
+
+AssetGraph assetGraph;
+PackageGraph packageGraph;
+
+final logger = Logger('graph_inspector');
+
+Future<void> main(List<String> args) async {
+  final logSubscription =
+      Logger.root.onRecord.listen((record) => print(record.message));
+  logger.warning(
+      'Warning: this tool is unsupported and usage may change at any time, '
+      'use at your own risk.');
+
+  final argParser = ArgParser()
+    ..addOption('graph-file',
+        abbr: 'g', help: 'Specify the asset_graph.json file to inspect.')
+    ..addOption('build-script',
+        abbr: 'b',
+        help: 'Specify the build script to find the asset graph for.',
+        defaultsTo: '.dart_tool/build/entrypoint/build.dart');
+
+  final results = argParser.parse(args);
+
+  if (results.wasParsed('graph-file') && results.wasParsed('build-script')) {
+    throw ArgumentError(
+        'Expected exactly one of `--graph-file` or `--build-script`.');
+  }
+
+  var assetGraphFile = File(_findAssetGraph(results));
+  if (!assetGraphFile.existsSync()) {
+    throw ArgumentError('Unable to find AssetGraph.');
+  }
+  stdout.writeln('Loading asset graph at ${assetGraphFile.path}...');
+
+  assetGraph = AssetGraph.deserialize(assetGraphFile.readAsBytesSync());
+  packageGraph = PackageGraph.forThisPackage();
+
+  var commandRunner = CommandRunner<bool>(
+      '', 'A tool for inspecting the AssetGraph for your build')
+    ..addCommand(InspectNodeCommand())
+    ..addCommand(GraphCommand())
+    ..addCommand(QuitCommand());
+
+  stdout.writeln('Ready, please type in a command:');
+
+  var shouldExit = false;
+  while (!shouldExit) {
+    stdout
+      ..writeln('')
+      ..write('> ');
+    var nextCommand = stdin.readLineSync();
+    stdout.writeln('');
+    try {
+      shouldExit = await commandRunner.run(nextCommand.split(' '));
+    } on UsageException {
+      stdout.writeln('Unrecognized option');
+      await commandRunner.run(['help']);
+    }
+  }
+  await logSubscription.cancel();
+}
+
+String _findAssetGraph(ArgResults results) {
+  if (results.wasParsed('graph-file')) return results['graph-file'] as String;
+  final scriptPath = results['build-script'] as String;
+  final scriptFile = File(scriptPath);
+  if (!scriptFile.existsSync()) {
+    throw ArgumentError(
+        'Expected a build script at $scriptPath but didn\'t find one.');
+  }
+  return assetGraphPathFor(p.url.joinAll(p.split(scriptPath)));
+}
+
+class InspectNodeCommand extends Command<bool> {
+  @override
+  String get name => 'inspect';
+
+  @override
+  String get description =>
+      'Lists all the information about an asset using a relative or package: uri';
+
+  @override
+  String get invocation => '${super.invocation} <dart-uri>';
+
+  InspectNodeCommand() {
+    argParser.addFlag('verbose', abbr: 'v');
+  }
+
+  @override
+  bool run() {
+    var stringUris = argResults.rest;
+    if (stringUris.isEmpty) {
+      stderr.writeln('Expected at least one uri for a node to inspect.');
+    }
+    for (var stringUri in stringUris) {
+      var id = _idFromString(stringUri);
+      if (id == null) {
+        continue;
+      }
+      var node = assetGraph.get(id);
+      if (node == null) {
+        stderr.writeln('Unable to find an asset node for $stringUri.');
+        continue;
+      }
+
+      var description = StringBuffer()
+        ..writeln('Asset: $stringUri')
+        ..writeln('  type: ${node.runtimeType}');
+
+      if (node is GeneratedAssetNode) {
+        description
+          ..writeln('  state: ${node.state}')
+          ..writeln('  wasOutput: ${node.wasOutput}')
+          ..writeln('  phase: ${node.phaseNumber}')
+          ..writeln('  isFailure: ${node.isFailure}');
+      }
+
+      void _printAsset(AssetId asset) =>
+          _listAsset(asset, description, indentation: '    ');
+
+      if (argResults['verbose'] == true) {
+        description.writeln('  primary outputs:');
+        node.primaryOutputs.forEach(_printAsset);
+
+        description.writeln('  secondary outputs:');
+        node.outputs.difference(node.primaryOutputs).forEach(_printAsset);
+
+        if (node is NodeWithInputs) {
+          description.writeln('  inputs:');
+          assetGraph.allNodes
+              .where((n) => n.outputs.contains(node.id))
+              .map((n) => n.id)
+              .forEach(_printAsset);
+        }
+      }
+
+      stdout.write(description);
+    }
+    return false;
+  }
+}
+
+class GraphCommand extends Command<bool> {
+  @override
+  String get name => 'graph';
+
+  @override
+  String get description => 'Lists all the nodes in the graph.';
+
+  @override
+  String get invocation => '${super.invocation} <dart-uri>';
+
+  GraphCommand() {
+    argParser
+      ..addFlag('generated',
+          abbr: 'g', help: 'Show only generated assets.', defaultsTo: false)
+      ..addFlag('original',
+          abbr: 'o',
+          help: 'Show only original source assets.',
+          defaultsTo: false)
+      ..addOption('package',
+          abbr: 'p', help: 'Filters nodes to a certain package')
+      ..addOption('pattern', abbr: 'm', help: 'glob pattern for path matching');
+  }
+
+  @override
+  bool run() {
+    var showGenerated = argResults['generated'] as bool;
+    var showSources = argResults['original'] as bool;
+    Iterable<AssetId> assets;
+    if (showGenerated) {
+      assets = assetGraph.outputs;
+    } else if (showSources) {
+      assets = assetGraph.sources;
+    } else {
+      assets = assetGraph.allNodes.map((n) => n.id);
+    }
+
+    var package = argResults['package'] as String;
+    if (package != null) {
+      assets = assets.where((id) => id.package == package);
+    }
+
+    var pattern = argResults['pattern'] as String;
+    if (pattern != null) {
+      var glob = Glob(pattern);
+      assets = assets.where((id) => glob.matches(id.path));
+    }
+
+    for (var id in assets) {
+      _listAsset(id, stdout, indentation: '  ');
+    }
+    return false;
+  }
+}
+
+class QuitCommand extends Command<bool> {
+  @override
+  String get name => 'quit';
+
+  @override
+  String get description => 'Exit the inspector';
+
+  @override
+  bool run() => true;
+}
+
+AssetId _idFromString(String stringUri) {
+  var uri = Uri.parse(stringUri);
+  if (uri.scheme == 'package') {
+    return AssetId(uri.pathSegments.first,
+        p.url.join('lib', p.url.joinAll(uri.pathSegments.skip(1))));
+  } else if (!uri.isAbsolute && (uri.scheme == '' || uri.scheme == 'file')) {
+    return AssetId(packageGraph.root.name, uri.path);
+  } else {
+    stderr.writeln('Unrecognized uri $uri, must be a package: uri or a '
+        'relative path.');
+    return null;
+  }
+}
+
+void _listAsset(AssetId output, StringSink buffer,
+    {String indentation = '  '}) {
+  var outputUri = output.uri;
+  if (outputUri.scheme == 'package') {
+    buffer.writeln('$indentation${output.uri}');
+  } else {
+    buffer.writeln('$indentation${output.path}');
+  }
+}
diff --git a/build_runner/bin/src/commands/clean.dart b/build_runner/bin/src/commands/clean.dart
new file mode 100644
index 0000000..70b0113
--- /dev/null
+++ b/build_runner/bin/src/commands/clean.dart
@@ -0,0 +1,34 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'package:args/args.dart';
+import 'package:args/command_runner.dart';
+import 'package:build_runner_core/build_runner_core.dart';
+import 'package:logging/logging.dart';
+
+import 'package:build_runner/src/build_script_generate/build_script_generate.dart';
+import 'package:build_runner/src/entrypoint/base_command.dart' show lineLength;
+import 'package:build_runner/src/entrypoint/clean.dart' show cleanFor;
+
+class CleanCommand extends Command<int> {
+  @override
+  final argParser = ArgParser(usageLineLength: lineLength);
+
+  @override
+  String get name => 'clean';
+  final logger = Logger('clean');
+
+  @override
+  String get description =>
+      'Cleans up output from previous builds. Does not clean up --output '
+      'directories.';
+
+  @override
+  Future<int> run() async {
+    await cleanFor(assetGraphPathFor(scriptLocation), logger);
+    return 0;
+  }
+}
diff --git a/build_runner/bin/src/commands/generate_build_script.dart b/build_runner/bin/src/commands/generate_build_script.dart
new file mode 100644
index 0000000..002abc8
--- /dev/null
+++ b/build_runner/bin/src/commands/generate_build_script.dart
@@ -0,0 +1,41 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:io';
+
+import 'package:args/args.dart';
+import 'package:args/command_runner.dart';
+import 'package:logging/logging.dart';
+import 'package:path/path.dart' as p;
+
+import 'package:build_runner/src/build_script_generate/build_script_generate.dart';
+import 'package:build_runner/src/entrypoint/base_command.dart' show lineLength;
+
+class GenerateBuildScript extends Command<int> {
+  @override
+  final argParser = ArgParser(usageLineLength: lineLength);
+
+  @override
+  String get description =>
+      'Generate a script to run builds and print the file path '
+      'with no other logging. Useful for wrapping builds with other tools.';
+
+  @override
+  String get name => 'generate-build-script';
+
+  @override
+  bool get hidden => true;
+
+  @override
+  Future<int> run() async {
+    Logger.root.clearListeners();
+    var buildScript = await generateBuildScript();
+    File(scriptLocation)
+      ..createSync(recursive: true)
+      ..writeAsStringSync(buildScript);
+    print(p.absolute(scriptLocation));
+    return 0;
+  }
+}
diff --git a/build_runner/build.yaml b/build_runner/build.yaml
new file mode 100644
index 0000000..d1e3e0f
--- /dev/null
+++ b/build_runner/build.yaml
@@ -0,0 +1,25 @@
+targets:
+  $default:
+    builders:
+      build_web_compilers:entrypoint:
+        options:
+          compiler: dart2js
+          dart2js_args:
+            - -O4
+        generate_for:
+          - web/graph_viz_main.dart
+          - web/hot_reload_client.dart
+      build_runner:client_js_copy_builder:
+        enabled: true
+builders:
+  client_js_copy_builder:
+    import: "tool/builders.dart"
+    builder_factories:
+        - copyCompiledJs
+    build_extensions:
+      web/graph_viz_main.dart.js:
+        - lib/src/server/graph_viz_main.dart.js
+      web/hot_reload_client.dart.js:
+        - lib/src/server/build_updates_client/hot_reload_client.dart.js
+    auto_apply: none
+    build_to: source
diff --git a/build_runner/dart_test.yaml b/build_runner/dart_test.yaml
new file mode 100644
index 0000000..112148b
--- /dev/null
+++ b/build_runner/dart_test.yaml
@@ -0,0 +1,3 @@
+tags:
+  integration:
+    timeout: 16x
diff --git a/build_runner/lib/build_runner.dart b/build_runner/lib/build_runner.dart
new file mode 100644
index 0000000..e4ce00d
--- /dev/null
+++ b/build_runner/lib/build_runner.dart
@@ -0,0 +1,6 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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.
+
+export 'src/daemon/constants.dart' show assetServerPort;
+export 'src/entrypoint/run.dart' show run;
diff --git a/build_runner/lib/build_script_generate.dart b/build_runner/lib/build_script_generate.dart
new file mode 100644
index 0000000..ee76280
--- /dev/null
+++ b/build_runner/lib/build_script_generate.dart
@@ -0,0 +1,6 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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.
+
+export 'src/build_script_generate/build_script_generate.dart'
+    show generateBuildScript, scriptLocation;
diff --git a/build_runner/lib/src/build_script_generate/bootstrap.dart b/build_runner/lib/src/build_script_generate/bootstrap.dart
new file mode 100644
index 0000000..3426822
--- /dev/null
+++ b/build_runner/lib/src/build_script_generate/bootstrap.dart
@@ -0,0 +1,210 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+import 'dart:isolate';
+
+import 'package:build_runner/src/build_script_generate/build_script_generate.dart';
+import 'package:build_runner_core/build_runner_core.dart';
+import 'package:io/io.dart';
+import 'package:logging/logging.dart';
+import 'package:path/path.dart' as p;
+import 'package:stack_trace/stack_trace.dart';
+
+final _logger = Logger('Bootstrap');
+
+/// Generates the build script, snapshots it if needed, and runs it.
+///
+/// Will retry once on [IsolateSpawnException]s to handle SDK updates.
+///
+/// Returns the exit code from running the build script.
+///
+/// If an exit code of 75 is returned, this function should be re-ran.
+Future<int> generateAndRun(List<String> args, {Logger logger}) async {
+  logger ??= _logger;
+  ReceivePort exitPort;
+  ReceivePort errorPort;
+  ReceivePort messagePort;
+  StreamSubscription errorListener;
+  int scriptExitCode;
+
+  var tryCount = 0;
+  var succeeded = false;
+  while (tryCount < 2 && !succeeded) {
+    tryCount++;
+    exitPort?.close();
+    errorPort?.close();
+    messagePort?.close();
+    await errorListener?.cancel();
+
+    try {
+      var buildScript = File(scriptLocation);
+      var oldContents = '';
+      if (buildScript.existsSync()) {
+        oldContents = buildScript.readAsStringSync();
+      }
+      var newContents = await generateBuildScript();
+      // Only trigger a build script update if necessary.
+      if (newContents != oldContents) {
+        buildScript
+          ..createSync(recursive: true)
+          ..writeAsStringSync(newContents);
+      }
+    } on CannotBuildException {
+      return ExitCode.config.code;
+    }
+
+    scriptExitCode = await _createSnapshotIfNeeded(logger);
+    if (scriptExitCode != 0) return scriptExitCode;
+
+    exitPort = ReceivePort();
+    errorPort = ReceivePort();
+    messagePort = ReceivePort();
+    errorListener = errorPort.listen((e) {
+      final error = e[0];
+      final trace = e[1] as String;
+      stderr
+        ..writeln('\n\nYou have hit a bug in build_runner')
+        ..writeln('Please file an issue with reproduction steps at '
+            'https://github.com/dart-lang/build/issues\n\n')
+        ..writeln(error)
+        ..writeln(Trace.parse(trace).terse);
+      if (scriptExitCode == 0) scriptExitCode = 1;
+    });
+    try {
+      await Isolate.spawnUri(Uri.file(p.absolute(scriptSnapshotLocation)), args,
+          messagePort.sendPort,
+          errorsAreFatal: true,
+          onExit: exitPort.sendPort,
+          onError: errorPort.sendPort);
+      succeeded = true;
+    } on IsolateSpawnException catch (e) {
+      if (tryCount > 1) {
+        logger.severe(
+            'Failed to spawn build script after retry. '
+            'This is likely due to a misconfigured builder definition. '
+            'See the generated script at $scriptLocation to find errors.',
+            e);
+        messagePort.sendPort.send(ExitCode.config.code);
+        exitPort.sendPort.send(null);
+      } else {
+        logger.warning(
+            'Error spawning build script isolate, this is likely due to a Dart '
+            'SDK update. Deleting snapshot and retrying...');
+      }
+      await File(scriptSnapshotLocation).delete();
+    }
+  }
+
+  StreamSubscription exitCodeListener;
+  exitCodeListener = messagePort.listen((isolateExitCode) {
+    if (isolateExitCode is int) {
+      scriptExitCode = isolateExitCode;
+    } else {
+      throw StateError(
+          'Bad response from isolate, expected an exit code but got '
+          '$isolateExitCode');
+    }
+    exitCodeListener.cancel();
+    exitCodeListener = null;
+  });
+  await exitPort.first;
+  await errorListener.cancel();
+  await exitCodeListener?.cancel();
+
+  return scriptExitCode;
+}
+
+/// Creates a script snapshot for the build script in necessary.
+///
+/// A snapshot is generated if:
+///
+/// - It doesn't exist currently
+/// - Either build_runner or build_daemon point at a different location than
+///   they used to, see https://github.com/dart-lang/build/issues/1929.
+///
+/// Returns zero for success or a number for failure which should be set to the
+/// exit code.
+Future<int> _createSnapshotIfNeeded(Logger logger) async {
+  var assetGraphFile = File(assetGraphPathFor(scriptSnapshotLocation));
+  var snapshotFile = File(scriptSnapshotLocation);
+
+  if (await snapshotFile.exists()) {
+    // If we failed to serialize an asset graph for the snapshot, then we don't
+    // want to re-use it because we can't check if it is up to date.
+    if (!await assetGraphFile.exists()) {
+      await snapshotFile.delete();
+      logger.warning('Deleted previous snapshot due to missing asset graph.');
+    } else if (!await _checkImportantPackageDeps()) {
+      await snapshotFile.delete();
+      logger.warning('Deleted previous snapshot due to core package update');
+    }
+  }
+
+  String stderr;
+  if (!await snapshotFile.exists()) {
+    var mode = stdin.hasTerminal
+        ? ProcessStartMode.normal
+        : ProcessStartMode.detachedWithStdio;
+    await logTimedAsync(logger, 'Creating build script snapshot...', () async {
+      var snapshot = await Process.start(Platform.executable,
+          ['--snapshot=$scriptSnapshotLocation', scriptLocation],
+          mode: mode);
+      stderr = (await snapshot.stderr
+              .transform(utf8.decoder)
+              .transform(LineSplitter())
+              .toList())
+          .join('');
+    });
+    if (!await snapshotFile.exists()) {
+      logger.severe('Failed to snapshot build script $scriptLocation.\n'
+          'This is likely caused by a misconfigured builder definition.');
+      if (stderr.isNotEmpty) {
+        logger.severe(stderr);
+      }
+      return ExitCode.config.code;
+    }
+    // Create _previousLocationsFile.
+    await _checkImportantPackageDeps();
+  }
+  return 0;
+}
+
+const _importantPackages = [
+  'build_daemon',
+  'build_runner',
+];
+final _previousLocationsFile = File(
+    p.url.join(p.url.dirname(scriptSnapshotLocation), '.packageLocations'));
+
+/// Returns whether the [_importantPackages] are all pointing at same locations
+/// from the previous run.
+///
+/// Also updates the [_previousLocationsFile] with the new locations if not.
+///
+/// This is used to detect potential changes to the user facing api and
+/// pre-emptively resolve them by resnapshotting, see
+/// https://github.com/dart-lang/build/issues/1929.
+Future<bool> _checkImportantPackageDeps() async {
+  var currentLocations = await Future.wait(_importantPackages.map((pkg) =>
+      Isolate.resolvePackageUri(
+          Uri(scheme: 'package', path: '$pkg/fake.dart'))));
+  var currentLocationsContent = currentLocations.join('\n');
+
+  if (!_previousLocationsFile.existsSync()) {
+    _logger.fine('Core package locations file does not exist');
+    _previousLocationsFile.writeAsStringSync(currentLocationsContent);
+    return false;
+  }
+
+  if (currentLocationsContent != _previousLocationsFile.readAsStringSync()) {
+    _logger.fine('Core packages locations have changed');
+    _previousLocationsFile.writeAsStringSync(currentLocationsContent);
+    return false;
+  }
+
+  return true;
+}
diff --git a/build_runner/lib/src/build_script_generate/build_script_generate.dart b/build_runner/lib/src/build_script_generate/build_script_generate.dart
new file mode 100644
index 0000000..b06e2ba
--- /dev/null
+++ b/build_runner/lib/src/build_script_generate/build_script_generate.dart
@@ -0,0 +1,259 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'package:build/build.dart' show BuilderOptions;
+import 'package:build_config/build_config.dart';
+import 'package:build_runner_core/build_runner_core.dart';
+import 'package:code_builder/code_builder.dart';
+import 'package:dart_style/dart_style.dart';
+import 'package:graphs/graphs.dart';
+import 'package:logging/logging.dart';
+import 'package:path/path.dart' as p;
+
+import '../package_graph/build_config_overrides.dart';
+import 'builder_ordering.dart';
+
+const scriptLocation = '$entryPointDir/build.dart';
+const scriptSnapshotLocation = '$scriptLocation.snapshot';
+
+final _log = Logger('Entrypoint');
+
+Future<String> generateBuildScript() =>
+    logTimedAsync(_log, 'Generating build script', _generateBuildScript);
+
+Future<String> _generateBuildScript() async {
+  final builders = await _findBuilderApplications();
+  final library = Library((b) => b.body.addAll([
+        literalList(
+                builders,
+                refer('BuilderApplication',
+                    'package:build_runner_core/build_runner_core.dart'))
+            .assignFinal('_builders')
+            .statement,
+        _main()
+      ]));
+  final emitter = DartEmitter(Allocator.simplePrefixing());
+  try {
+    return DartFormatter().format('''
+      // ignore_for_file: directives_ordering
+
+      ${library.accept(emitter)}''');
+  } on FormatterException {
+    _log.severe('Generated build script could not be parsed.\n'
+        'This is likely caused by a misconfigured builder definition.');
+    throw CannotBuildException();
+  }
+}
+
+/// Finds expressions to create all the `BuilderApplication` instances that
+/// should be applied packages in the build.
+///
+/// Adds `apply` expressions based on the BuildefDefinitions from any package
+/// which has a `build.yaml`.
+Future<Iterable<Expression>> _findBuilderApplications() async {
+  final builderApplications = <Expression>[];
+  final packageGraph = PackageGraph.forThisPackage();
+  final orderedPackages = stronglyConnectedComponents<PackageNode>(
+    [packageGraph.root],
+    (node) => node.dependencies,
+    equals: (a, b) => a.name == b.name,
+    hashCode: (n) => n.name.hashCode,
+  ).expand((c) => c);
+  final buildConfigOverrides =
+      await findBuildConfigOverrides(packageGraph, null);
+  Future<BuildConfig> _packageBuildConfig(PackageNode package) async {
+    if (buildConfigOverrides.containsKey(package.name)) {
+      return buildConfigOverrides[package.name];
+    }
+    try {
+      return await BuildConfig.fromBuildConfigDir(
+          package.name, package.dependencies.map((n) => n.name), package.path);
+    } on ArgumentError catch (_) {
+      // During the build an error will be logged.
+      return BuildConfig.useDefault(
+          package.name, package.dependencies.map((n) => n.name));
+    }
+  }
+
+  bool _isValidDefinition(dynamic definition) {
+    // Filter out builderDefinitions with relative imports that aren't
+    // from the root package, because they will never work.
+    if (definition.import.startsWith('package:') as bool) return true;
+    return definition.package == packageGraph.root.name;
+  }
+
+  final orderedConfigs =
+      await Future.wait(orderedPackages.map(_packageBuildConfig));
+  final builderDefinitions = orderedConfigs
+      .expand((c) => c.builderDefinitions.values)
+      .where(_isValidDefinition);
+
+  final orderedBuilders = findBuilderOrder(builderDefinitions).toList();
+  builderApplications.addAll(orderedBuilders.map(_applyBuilder));
+
+  final postProcessBuilderDefinitions = orderedConfigs
+      .expand((c) => c.postProcessBuilderDefinitions.values)
+      .where(_isValidDefinition);
+  builderApplications
+      .addAll(postProcessBuilderDefinitions.map(_applyPostProcessBuilder));
+
+  return builderApplications;
+}
+
+/// A method forwarding to `run`.
+Method _main() => Method((b) => b
+  ..name = 'main'
+  ..returns = refer('void')
+  ..modifier = MethodModifier.async
+  ..requiredParameters.add(Parameter((b) => b
+    ..name = 'args'
+    ..type = TypeReference((b) => b
+      ..symbol = 'List'
+      ..types.add(refer('String')))))
+  ..optionalParameters.add(Parameter((b) => b
+    ..name = 'sendPort'
+    ..type = refer('SendPort', 'dart:isolate')))
+  ..body = Block.of([
+    refer('run', 'package:build_runner/build_runner.dart')
+        .call([refer('args'), refer('_builders')])
+        .awaited
+        .assignVar('result')
+        .statement,
+    refer('sendPort')
+        .nullSafeProperty('send')
+        .call([refer('result')]).statement,
+    refer('exitCode', 'dart:io').assign(refer('result')).statement,
+  ]));
+
+/// An expression calling `apply` with appropriate setup for a Builder.
+Expression _applyBuilder(BuilderDefinition definition) {
+  final namedArgs = <String, Expression>{};
+  if (definition.isOptional) {
+    namedArgs['isOptional'] = literalTrue;
+  }
+  if (definition.buildTo == BuildTo.cache) {
+    namedArgs['hideOutput'] = literalTrue;
+  } else {
+    namedArgs['hideOutput'] = literalFalse;
+  }
+  if (!identical(definition.defaults?.generateFor, InputSet.anything)) {
+    final inputSetArgs = <String, Expression>{};
+    if (definition.defaults.generateFor.include != null) {
+      inputSetArgs['include'] =
+          literalConstList(definition.defaults.generateFor.include);
+    }
+    if (definition.defaults.generateFor.exclude != null) {
+      inputSetArgs['exclude'] =
+          literalConstList(definition.defaults.generateFor.exclude);
+    }
+    namedArgs['defaultGenerateFor'] =
+        refer('InputSet', 'package:build_config/build_config.dart')
+            .constInstance([], inputSetArgs);
+  }
+  if (definition.defaults?.options?.isNotEmpty ?? false) {
+    namedArgs['defaultOptions'] =
+        _constructBuilderOptions(definition.defaults.options);
+  }
+  if (definition.defaults?.devOptions?.isNotEmpty ?? false) {
+    namedArgs['defaultDevOptions'] =
+        _constructBuilderOptions(definition.defaults.devOptions);
+  }
+  if (definition.defaults?.releaseOptions?.isNotEmpty ?? false) {
+    namedArgs['defaultReleaseOptions'] =
+        _constructBuilderOptions(definition.defaults.releaseOptions);
+  }
+  if (definition.appliesBuilders.isNotEmpty) {
+    namedArgs['appliesBuilders'] = literalList(definition.appliesBuilders);
+  }
+  var import = _buildScriptImport(definition.import);
+  return refer('apply', 'package:build_runner_core/build_runner_core.dart')
+      .call([
+    literalString(definition.key),
+    literalList(
+        definition.builderFactories.map((f) => refer(f, import)).toList()),
+    _findToExpression(definition),
+  ], namedArgs);
+}
+
+/// An expression calling `applyPostProcess` with appropriate setup for a
+/// PostProcessBuilder.
+Expression _applyPostProcessBuilder(PostProcessBuilderDefinition definition) {
+  final namedArgs = <String, Expression>{};
+  if (definition.defaults?.generateFor != null) {
+    final inputSetArgs = <String, Expression>{};
+    if (definition.defaults.generateFor.include != null) {
+      inputSetArgs['include'] =
+          literalConstList(definition.defaults.generateFor.include);
+    }
+    if (definition.defaults.generateFor.exclude != null) {
+      inputSetArgs['exclude'] =
+          literalConstList(definition.defaults.generateFor.exclude);
+    }
+    if (definition.defaults?.options?.isNotEmpty ?? false) {
+      namedArgs['defaultOptions'] =
+          _constructBuilderOptions(definition.defaults.options);
+    }
+    if (definition.defaults?.devOptions?.isNotEmpty ?? false) {
+      namedArgs['defaultDevOptions'] =
+          _constructBuilderOptions(definition.defaults.devOptions);
+    }
+    if (definition.defaults?.releaseOptions?.isNotEmpty ?? false) {
+      namedArgs['defaultReleaseOptions'] =
+          _constructBuilderOptions(definition.defaults.releaseOptions);
+    }
+    namedArgs['defaultGenerateFor'] =
+        refer('InputSet', 'package:build_config/build_config.dart')
+            .constInstance([], inputSetArgs);
+  }
+  var import = _buildScriptImport(definition.import);
+  return refer('applyPostProcess',
+          'package:build_runner_core/build_runner_core.dart')
+      .call([
+    literalString(definition.key),
+    refer(definition.builderFactory, import),
+  ], namedArgs);
+}
+
+/// Returns the actual import to put in the generated script based on an import
+/// found in the build.yaml.
+String _buildScriptImport(String import) {
+  if (import.startsWith('package:')) {
+    return import;
+  } else if (import.startsWith('../') || import.startsWith('/')) {
+    _log.warning('The `../` import syntax in build.yaml is now deprecated, '
+        'instead do a normal relative import as if it was from the root of '
+        'the package. Found `$import` in your `build.yaml` file.');
+    return import;
+  } else {
+    return p.url.relative(import, from: p.url.dirname(scriptLocation));
+  }
+}
+
+Expression _findToExpression(BuilderDefinition definition) {
+  switch (definition.autoApply) {
+    case AutoApply.none:
+      return refer('toNoneByDefault',
+              'package:build_runner_core/build_runner_core.dart')
+          .call([]);
+    case AutoApply.dependents:
+      return refer('toDependentsOf',
+              'package:build_runner_core/build_runner_core.dart')
+          .call([literalString(definition.package)]);
+    case AutoApply.allPackages:
+      return refer('toAllPackages',
+              'package:build_runner_core/build_runner_core.dart')
+          .call([]);
+    case AutoApply.rootPackage:
+      return refer('toRoot', 'package:build_runner_core/build_runner_core.dart')
+          .call([]);
+  }
+  throw ArgumentError('Unhandled AutoApply type: ${definition.autoApply}');
+}
+
+/// An expression creating a [BuilderOptions] from a json string.
+Expression _constructBuilderOptions(Map<String, dynamic> options) =>
+    refer('BuilderOptions', 'package:build/build.dart')
+        .newInstance([literalMap(options)]);
diff --git a/build_runner/lib/src/build_script_generate/builder_ordering.dart b/build_runner/lib/src/build_script_generate/builder_ordering.dart
new file mode 100644
index 0000000..c681697
--- /dev/null
+++ b/build_runner/lib/src/build_script_generate/builder_ordering.dart
@@ -0,0 +1,45 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'package:build_config/build_config.dart';
+import 'package:graphs/graphs.dart';
+
+/// Put [builders] into an order such that any builder which specifies
+/// [BuilderDefinition.requiredInputs] will come after any builder which
+/// produces a desired output.
+///
+/// Builders will be ordered such that their `required_inputs` and `runs_before`
+/// constraints are met, but the rest of the ordering is arbitrary.
+Iterable<BuilderDefinition> findBuilderOrder(
+    Iterable<BuilderDefinition> builders) {
+  final consistentOrderBuilders = builders.toList()
+    ..sort((a, b) => a.key.compareTo(b.key));
+  Iterable<BuilderDefinition> dependencies(BuilderDefinition parent) =>
+      consistentOrderBuilders.where((child) =>
+          _hasInputDependency(parent, child) || _mustRunBefore(parent, child));
+  var components = stronglyConnectedComponents<BuilderDefinition>(
+    consistentOrderBuilders,
+    dependencies,
+    equals: (a, b) => a.key == b.key,
+    hashCode: (b) => b.key.hashCode,
+  );
+  return components.map((component) {
+    if (component.length > 1) {
+      throw ArgumentError('Required input cycle for ${component.toList()}');
+    }
+    return component.single;
+  }).toList();
+}
+
+/// Whether [parent] has a `required_input` that wants to read outputs produced
+/// by [child].
+bool _hasInputDependency(BuilderDefinition parent, BuilderDefinition child) {
+  final childOutputs = child.buildExtensions.values.expand((v) => v).toSet();
+  return parent.requiredInputs
+      .any((input) => childOutputs.any((output) => output.endsWith(input)));
+}
+
+/// Whether [child] specifies that it wants to run before [parent].
+bool _mustRunBefore(BuilderDefinition parent, BuilderDefinition child) =>
+    child.runsBefore.contains(parent.key);
diff --git a/build_runner/lib/src/daemon/asset_server.dart b/build_runner/lib/src/daemon/asset_server.dart
new file mode 100644
index 0000000..524a7d6
--- /dev/null
+++ b/build_runner/lib/src/daemon/asset_server.dart
@@ -0,0 +1,36 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:io';
+
+import 'package:http_multi_server/http_multi_server.dart';
+import 'package:shelf/shelf.dart';
+import 'package:shelf/shelf_io.dart' as shelf_io;
+
+import '../server/server.dart';
+import 'daemon_builder.dart';
+
+class AssetServer {
+  final HttpServer _server;
+
+  AssetServer._(this._server);
+
+  int get port => _server.port;
+
+  Future<void> stop() => _server.close(force: true);
+
+  static Future<AssetServer> run(
+    BuildRunnerDaemonBuilder builder,
+    String rootPackage,
+  ) async {
+    var server = await HttpMultiServer.loopback(0);
+    var cascade = Cascade().add((_) async {
+      await builder.building;
+      return Response.notFound('');
+    }).add(AssetHandler(builder.reader, rootPackage).handle);
+    shelf_io.serveRequests(server, cascade.handler);
+    return AssetServer._(server);
+  }
+}
diff --git a/build_runner/lib/src/daemon/change_providers.dart b/build_runner/lib/src/daemon/change_providers.dart
new file mode 100644
index 0000000..8d52c0a
--- /dev/null
+++ b/build_runner/lib/src/daemon/change_providers.dart
@@ -0,0 +1,39 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS 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 'package:build_daemon/change_provider.dart';
+import 'package:build_runner_core/src/generate/build_definition.dart';
+import 'package:watcher/src/watch_event.dart';
+
+/// Continually updates the [changes] stream as watch events are seen on the
+/// input stream.
+///
+/// The [collectChanges] method is a no-op for this implementation.
+class AutoChangeProvider implements ChangeProvider {
+  @override
+  final Stream<List<WatchEvent>> changes;
+
+  AutoChangeProvider(this.changes);
+
+  @override
+  Future<List<WatchEvent>> collectChanges() async => [];
+}
+
+/// Computes changes with a file scan when requested by a call to
+/// [collectChanges].
+class ManualChangeProvider implements ChangeProvider {
+  final AssetTracker _assetTracker;
+
+  ManualChangeProvider(this._assetTracker);
+
+  @override
+  Future<List<WatchEvent>> collectChanges() async {
+    var updates = await _assetTracker.collectChanges();
+    return List.of(updates.entries
+        .map((entry) => WatchEvent(entry.value, '${entry.key}')));
+  }
+
+  @override
+  Stream<List<WatchEvent>> get changes => Stream.empty();
+}
diff --git a/build_runner/lib/src/daemon/constants.dart b/build_runner/lib/src/daemon/constants.dart
new file mode 100644
index 0000000..956fcea
--- /dev/null
+++ b/build_runner/lib/src/daemon/constants.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS 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 'dart:io';
+
+import 'package:build_daemon/constants.dart';
+import 'package:path/path.dart' as p;
+
+String assetServerPortFilePath(String workingDirectory) =>
+    p.join(daemonWorkspace(workingDirectory), '.asset_server_port');
+
+/// Returns the port of the daemon asset server.
+int assetServerPort(String workingDirectory) {
+  var portFile = File(assetServerPortFilePath(workingDirectory));
+  if (!portFile.existsSync()) {
+    throw Exception('Unable to read daemon asset port file.');
+  }
+  return int.parse(portFile.readAsStringSync());
+}
diff --git a/build_runner/lib/src/daemon/daemon_builder.dart b/build_runner/lib/src/daemon/daemon_builder.dart
new file mode 100644
index 0000000..b9104af
--- /dev/null
+++ b/build_runner/lib/src/daemon/daemon_builder.dart
@@ -0,0 +1,233 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'package:build/build.dart';
+import 'package:build_daemon/change_provider.dart';
+import 'package:build_daemon/constants.dart';
+import 'package:build_daemon/daemon_builder.dart';
+import 'package:build_daemon/data/build_status.dart';
+import 'package:build_daemon/data/build_target.dart' hide OutputLocation;
+import 'package:build_daemon/data/server_log.dart';
+import 'package:build_runner/src/entrypoint/options.dart';
+import 'package:build_runner/src/package_graph/build_config_overrides.dart';
+import 'package:build_runner/src/watcher/asset_change.dart';
+import 'package:build_runner/src/watcher/change_filter.dart';
+import 'package:build_runner/src/watcher/collect_changes.dart';
+import 'package:build_runner/src/watcher/delete_writer.dart';
+import 'package:build_runner/src/watcher/graph_watcher.dart';
+import 'package:build_runner/src/watcher/node_watcher.dart';
+import 'package:build_runner_core/build_runner_core.dart'
+    hide BuildResult, BuildStatus;
+import 'package:build_runner_core/build_runner_core.dart' as core
+    show BuildStatus;
+import 'package:build_runner_core/src/generate/build_definition.dart';
+import 'package:build_runner_core/src/generate/build_impl.dart';
+import 'package:stream_transform/stream_transform.dart';
+import 'package:watcher/watcher.dart';
+
+import 'change_providers.dart';
+
+/// A Daemon Builder that uses build_runner_core for building.
+class BuildRunnerDaemonBuilder implements DaemonBuilder {
+  final _buildResults = StreamController<BuildResults>();
+
+  final BuildImpl _builder;
+  final BuildOptions _buildOptions;
+  final StreamController<ServerLog> _outputStreamController;
+  final ChangeProvider changeProvider;
+
+  Completer<Null> _buildingCompleter;
+
+  @override
+  final Stream<ServerLog> logs;
+
+  BuildRunnerDaemonBuilder._(
+    this._builder,
+    this._buildOptions,
+    this._outputStreamController,
+    this.changeProvider,
+  ) : logs = _outputStreamController.stream.asBroadcastStream();
+
+  /// Waits for a running build to complete before returning.
+  ///
+  /// If there is no running build, it will return immediately.
+  Future<void> get building => _buildingCompleter?.future;
+
+  @override
+  Stream<BuildResults> get builds => _buildResults.stream;
+
+  FinalizedReader get reader => _builder.finalizedReader;
+
+  final _buildScriptUpdateCompleter = Completer();
+  Future<void> get buildScriptUpdated => _buildScriptUpdateCompleter.future;
+
+  @override
+  Future<void> build(
+      Set<BuildTarget> targets, Iterable<WatchEvent> fileChanges) async {
+    var defaultTargets = targets.cast<DefaultBuildTarget>();
+    var changes = fileChanges
+        .map<AssetChange>(
+            (change) => AssetChange(AssetId.parse(change.path), change.type))
+        .toList();
+    if (!_buildOptions.skipBuildScriptCheck &&
+        _builder.buildScriptUpdates.hasBeenUpdated(
+            changes.map<AssetId>((change) => change.id).toSet())) {
+      _buildScriptUpdateCompleter.complete();
+      return;
+    }
+    var targetNames = targets.map((t) => t.target).toSet();
+    _logMessage(Level.INFO, 'About to build ${targetNames.toList()}...');
+    _signalStart(targetNames);
+    var results = <BuildResult>[];
+    var buildDirs = <BuildDirectory>{};
+    var buildFilters = <BuildFilter>{};
+    for (var target in defaultTargets) {
+      OutputLocation outputLocation;
+      if (target.outputLocation != null) {
+        outputLocation = OutputLocation(target.outputLocation.output,
+            useSymlinks: target.outputLocation.useSymlinks,
+            hoist: target.outputLocation.hoist);
+      }
+      buildDirs
+          .add(BuildDirectory(target.target, outputLocation: outputLocation));
+      if (target.buildFilters != null && target.buildFilters.isNotEmpty) {
+        buildFilters.addAll([
+          for (var pattern in target.buildFilters)
+            BuildFilter.fromArg(pattern, _buildOptions.packageGraph.root.name)
+        ]);
+      } else {
+        buildFilters
+          ..add(BuildFilter.fromArg(
+              'package:*/**', _buildOptions.packageGraph.root.name))
+          ..add(BuildFilter.fromArg(
+              '${target.target}/**', _buildOptions.packageGraph.root.name));
+      }
+    }
+    try {
+      var mergedChanges = collectChanges([changes]);
+      var result = await _builder.run(mergedChanges,
+          buildDirs: buildDirs, buildFilters: buildFilters);
+      for (var target in targets) {
+        if (result.status == core.BuildStatus.success) {
+          // TODO(grouma) - Can we notify if a target was cached?
+          results.add(DefaultBuildResult((b) => b
+            ..status = BuildStatus.succeeded
+            ..target = target.target));
+        } else {
+          results.add(DefaultBuildResult((b) => b
+            ..status = BuildStatus.failed
+            // TODO(grouma) - We should forward the error messages instead.
+            // We can use the AssetGraph and FailureReporter to provide a better
+            // error message.
+            ..error = 'FailureType: ${result.failureType.exitCode}'
+            ..target = target.target));
+        }
+      }
+    } catch (e) {
+      for (var target in targets) {
+        results.add(DefaultBuildResult((b) => b
+          ..status = BuildStatus.failed
+          ..error = '$e'
+          ..target = target.target));
+      }
+      _logMessage(Level.SEVERE, 'Build Failed:\n${e.toString()}');
+    }
+    _signalEnd(results);
+  }
+
+  @override
+  Future<void> stop() async {
+    await _builder.beforeExit();
+    await _buildOptions.logListener.cancel();
+  }
+
+  void _logMessage(Level level, String message) =>
+      _outputStreamController.add(ServerLog(
+        (b) => b
+          ..message = message
+          ..level = level,
+      ));
+
+  void _signalEnd(Iterable<BuildResult> results) {
+    _buildingCompleter.complete();
+    _buildResults.add(BuildResults((b) => b..results.addAll(results)));
+  }
+
+  void _signalStart(Iterable<String> targets) {
+    _buildingCompleter = Completer();
+    var results = <BuildResult>[];
+    for (var target in targets) {
+      results.add(DefaultBuildResult((b) => b
+        ..status = BuildStatus.started
+        ..target = target));
+    }
+    _buildResults.add(BuildResults((b) => b..results.addAll(results)));
+  }
+
+  static Future<BuildRunnerDaemonBuilder> create(
+    PackageGraph packageGraph,
+    List<BuilderApplication> builders,
+    DaemonOptions daemonOptions,
+  ) async {
+    var expectedDeletes = <AssetId>{};
+    var outputStreamController = StreamController<ServerLog>();
+
+    var environment = OverrideableEnvironment(
+        IOEnvironment(packageGraph,
+            outputSymlinksOnly: daemonOptions.outputSymlinksOnly),
+        onLog: (record) {
+      outputStreamController.add(ServerLog.fromLogRecord(record));
+    });
+
+    var daemonEnvironment = OverrideableEnvironment(environment,
+        writer: OnDeleteWriter(environment.writer, expectedDeletes.add));
+
+    var logSubscription =
+        LogSubscription(environment, verbose: daemonOptions.verbose);
+
+    var overrideBuildConfig =
+        await findBuildConfigOverrides(packageGraph, daemonOptions.configKey);
+
+    var buildOptions = await BuildOptions.create(
+      logSubscription,
+      packageGraph: packageGraph,
+      deleteFilesByDefault: daemonOptions.deleteFilesByDefault,
+      overrideBuildConfig: overrideBuildConfig,
+      skipBuildScriptCheck: daemonOptions.skipBuildScriptCheck,
+      enableLowResourcesMode: daemonOptions.enableLowResourcesMode,
+      trackPerformance: daemonOptions.trackPerformance,
+      logPerformanceDir: daemonOptions.logPerformanceDir,
+    );
+
+    var builder = await BuildImpl.create(buildOptions, daemonEnvironment,
+        builders, daemonOptions.builderConfigOverrides,
+        isReleaseBuild: daemonOptions.isReleaseBuild);
+
+    // Only actually used for the AutoChangeProvider.
+    Stream<List<WatchEvent>> graphEvents() => PackageGraphWatcher(packageGraph,
+            watch: (node) => PackageNodeWatcher(node,
+                watch: daemonOptions.directoryWatcherFactory))
+        .watch()
+        .where((change) => shouldProcess(
+              change,
+              builder.assetGraph,
+              buildOptions,
+              // Assume we will create an outputDir.
+              true,
+              expectedDeletes,
+            ))
+        .map((data) => WatchEvent(data.type, '${data.id}'))
+        .debounceBuffer(buildOptions.debounceDelay);
+
+    var changeProvider = daemonOptions.buildMode == BuildMode.Auto
+        ? AutoChangeProvider(graphEvents())
+        : ManualChangeProvider(AssetTracker(builder.assetGraph,
+            daemonEnvironment.reader, buildOptions.targetGraph));
+
+    return BuildRunnerDaemonBuilder._(
+        builder, buildOptions, outputStreamController, changeProvider);
+  }
+}
diff --git a/build_runner/lib/src/entrypoint/base_command.dart b/build_runner/lib/src/entrypoint/base_command.dart
new file mode 100644
index 0000000..27a8873
--- /dev/null
+++ b/build_runner/lib/src/entrypoint/base_command.dart
@@ -0,0 +1,108 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:io';
+
+import 'package:args/args.dart';
+import 'package:args/command_runner.dart';
+import 'package:build_runner_core/build_runner_core.dart';
+import 'package:logging/logging.dart';
+
+import 'options.dart';
+import 'runner.dart';
+
+final lineLength = stdout.hasTerminal ? stdout.terminalColumns : 80;
+
+abstract class BuildRunnerCommand extends Command<int> {
+  Logger get logger => Logger(name);
+
+  List<BuilderApplication> get builderApplications =>
+      (runner as BuildCommandRunner).builderApplications;
+
+  PackageGraph get packageGraph => (runner as BuildCommandRunner).packageGraph;
+
+  BuildRunnerCommand({bool symlinksDefault}) {
+    _addBaseFlags(symlinksDefault ?? false);
+  }
+
+  @override
+  final argParser = ArgParser(usageLineLength: lineLength);
+
+  void _addBaseFlags(bool symlinksDefault) {
+    argParser
+      ..addFlag(deleteFilesByDefaultOption,
+          help:
+              'By default, the user will be prompted to delete any files which '
+              'already exist but were not known to be generated by this '
+              'specific build script.\n\n'
+              'Enabling this option skips the prompt and deletes the files. '
+              'This should typically be used in continues integration servers '
+              'and tests, but not otherwise.',
+          negatable: false,
+          defaultsTo: false)
+      ..addFlag(lowResourcesModeOption,
+          help: 'Reduce the amount of memory consumed by the build process. '
+              'This will slow down builds but allow them to progress in '
+              'resource constrained environments.',
+          negatable: false,
+          defaultsTo: false)
+      ..addOption(configOption,
+          help: 'Read `build.<name>.yaml` instead of the default `build.yaml`',
+          abbr: 'c')
+      ..addFlag('fail-on-severe',
+          help: 'Deprecated argument - always enabled',
+          negatable: true,
+          defaultsTo: true,
+          hide: true)
+      ..addFlag(trackPerformanceOption,
+          help: r'Enables performance tracking and the /$perf page.',
+          negatable: true,
+          defaultsTo: false)
+      ..addOption(logPerformanceOption,
+          help: 'A directory to write performance logs to, must be in the '
+              'current package. Implies `--track-performance`.')
+      ..addFlag(skipBuildScriptCheckOption,
+          help: r'Skip validation for the digests of files imported by the '
+              'build script.',
+          hide: true,
+          defaultsTo: false)
+      ..addMultiOption(outputOption,
+          help: 'A directory to copy the fully built package to. Or a mapping '
+              'from a top-level directory in the package to the directory to '
+              'write a filtered build output to. For example "web:deploy".',
+          abbr: 'o')
+      ..addFlag(verboseOption,
+          abbr: 'v',
+          defaultsTo: false,
+          negatable: false,
+          help: 'Enables verbose logging.')
+      ..addFlag(releaseOption,
+          abbr: 'r',
+          defaultsTo: false,
+          negatable: true,
+          help: 'Build with release mode defaults for builders.')
+      ..addMultiOption(defineOption,
+          splitCommas: false,
+          help: 'Sets the global `options` config for a builder by key.')
+      ..addFlag(symlinkOption,
+          defaultsTo: symlinksDefault,
+          negatable: true,
+          help: 'Symlink files in the output directories, instead of copying.')
+      ..addMultiOption(buildFilterOption,
+          help: 'An explicit filter of files to build. Relative paths and '
+              '`package:` uris are supported, including glob syntax for paths '
+              'portions (but not package names).\n\n'
+              'If multiple filters are applied then outputs matching any filter '
+              'will be built (they do not need to match all filters).');
+  }
+
+  /// Must be called inside [run] so that [argResults] is non-null.
+  ///
+  /// You may override this to return more specific options if desired, but they
+  /// must extend [SharedOptions].
+  SharedOptions readOptions() {
+    return SharedOptions.fromParsedArgs(
+        argResults, argResults.rest, packageGraph.root.name, this);
+  }
+}
diff --git a/build_runner/lib/src/entrypoint/build.dart b/build_runner/lib/src/entrypoint/build.dart
new file mode 100644
index 0000000..2b983ee
--- /dev/null
+++ b/build_runner/lib/src/entrypoint/build.dart
@@ -0,0 +1,50 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'package:build_runner_core/build_runner_core.dart';
+import 'package:io/io.dart';
+
+import '../generate/build.dart';
+import 'base_command.dart';
+
+/// A command that does a single build and then exits.
+class BuildCommand extends BuildRunnerCommand {
+  @override
+  String get invocation => '${super.invocation} [directories]';
+
+  @override
+  String get name => 'build';
+
+  @override
+  String get description =>
+      'Performs a single build on the specified targets and then exits.';
+
+  @override
+  Future<int> run() async {
+    var options = readOptions();
+    var result = await build(
+      builderApplications,
+      buildFilters: options.buildFilters,
+      deleteFilesByDefault: options.deleteFilesByDefault,
+      enableLowResourcesMode: options.enableLowResourcesMode,
+      configKey: options.configKey,
+      buildDirs: options.buildDirs,
+      outputSymlinksOnly: options.outputSymlinksOnly,
+      packageGraph: packageGraph,
+      verbose: options.verbose,
+      builderConfigOverrides: options.builderConfigOverrides,
+      isReleaseBuild: options.isReleaseBuild,
+      trackPerformance: options.trackPerformance,
+      skipBuildScriptCheck: options.skipBuildScriptCheck,
+      logPerformanceDir: options.logPerformanceDir,
+    );
+    if (result.status == BuildStatus.success) {
+      return ExitCode.success.code;
+    } else {
+      return result.failureType.exitCode;
+    }
+  }
+}
diff --git a/build_runner/lib/src/entrypoint/clean.dart b/build_runner/lib/src/entrypoint/clean.dart
new file mode 100644
index 0000000..9aa4fba
--- /dev/null
+++ b/build_runner/lib/src/entrypoint/clean.dart
@@ -0,0 +1,93 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:io';
+
+import 'package:args/args.dart';
+import 'package:args/command_runner.dart';
+import 'package:build_runner_core/build_runner_core.dart';
+import 'package:build_runner_core/src/asset_graph/graph.dart';
+import 'package:build_runner_core/src/asset_graph/node.dart';
+import 'package:logging/logging.dart';
+
+import '../logging/std_io_logging.dart';
+import 'base_command.dart';
+
+class CleanCommand extends Command<int> {
+  @override
+  final argParser = ArgParser(usageLineLength: lineLength);
+
+  @override
+  String get name => 'clean';
+
+  @override
+  String get description =>
+      'Cleans up output from previous builds. Does not clean up --output '
+      'directories.';
+
+  Logger get logger => Logger(name);
+
+  @override
+  Future<int> run() async {
+    var logSubscription = Logger.root.onRecord.listen(stdIOLogListener());
+    await cleanFor(assetGraphPath, logger);
+    await logSubscription.cancel();
+    return 0;
+  }
+}
+
+Future<void> cleanFor(String assetGraphPath, Logger logger) async {
+  logger.warning('Deleting cache and generated source files.\n'
+      'This shouldn\'t be necessary for most applications, unless you have '
+      'made intentional edits to generated files (i.e. for testing). '
+      'Consider filing a bug at '
+      'https://github.com/dart-lang/build/issues/new if you are using this '
+      'to work around an apparent (and reproducible) bug.');
+
+  await logTimedAsync(logger, 'Cleaning up source outputs', () async {
+    var assetGraphFile = File(assetGraphPath);
+    if (!assetGraphFile.existsSync()) {
+      logger.warning('No asset graph found. '
+          'Skipping cleanup of generated files in source directories.');
+      return;
+    }
+    AssetGraph assetGraph;
+    try {
+      assetGraph = AssetGraph.deserialize(await assetGraphFile.readAsBytes());
+    } catch (_) {
+      logger.warning('Failed to deserialize AssetGraph. '
+          'Skipping cleanup of generated files in source directories.');
+      return;
+    }
+    var packageGraph = PackageGraph.forThisPackage();
+    await _cleanUpSourceOutputs(assetGraph, packageGraph);
+  });
+
+  await logTimedAsync(
+      logger, 'Cleaning up cache directory', _cleanUpGeneratedDirectory);
+}
+
+Future<void> _cleanUpSourceOutputs(
+    AssetGraph assetGraph, PackageGraph packageGraph) async {
+  var writer = FileBasedAssetWriter(packageGraph);
+  for (var id in assetGraph.outputs) {
+    if (id.package != packageGraph.root.name) continue;
+    var node = assetGraph.get(id) as GeneratedAssetNode;
+    if (node.wasOutput) {
+      // Note that this does a file.exists check in the root package and
+      // only tries to delete the file if it exists. This way we only
+      // actually delete to_source outputs, without reading in the build
+      // actions.
+      await writer.delete(id);
+    }
+  }
+}
+
+Future<void> _cleanUpGeneratedDirectory() async {
+  var generatedDir = Directory(cacheDir);
+  if (await generatedDir.exists()) {
+    await generatedDir.delete(recursive: true);
+  }
+}
diff --git a/build_runner/lib/src/entrypoint/daemon.dart b/build_runner/lib/src/entrypoint/daemon.dart
new file mode 100644
index 0000000..b7ef05b
--- /dev/null
+++ b/build_runner/lib/src/entrypoint/daemon.dart
@@ -0,0 +1,124 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:build_daemon/constants.dart';
+import 'package:build_daemon/daemon.dart';
+import 'package:build_daemon/data/serializers.dart';
+import 'package:build_daemon/data/server_log.dart';
+import 'package:build_runner/src/daemon/constants.dart';
+import 'package:logging/logging.dart' hide Level;
+import 'package:pedantic/pedantic.dart';
+
+import '../daemon/asset_server.dart';
+import '../daemon/daemon_builder.dart';
+import 'options.dart';
+import 'watch.dart';
+
+/// A command that starts the Build Daemon.
+class DaemonCommand extends WatchCommand {
+  @override
+  String get description => 'Starts the build daemon.';
+
+  @override
+  bool get hidden => true;
+
+  @override
+  String get name => 'daemon';
+
+  DaemonCommand() {
+    argParser.addOption(buildModeFlag,
+        help: 'Specify the build mode of the daemon, e.g. auto or manual.',
+        defaultsTo: 'BuildMode.Auto');
+  }
+
+  @override
+  DaemonOptions readOptions() => DaemonOptions.fromParsedArgs(
+      argResults, argResults.rest, packageGraph.root.name, this);
+
+  @override
+  Future<int> run() async {
+    var workingDirectory = Directory.current.path;
+    var options = readOptions();
+    var daemon = Daemon(workingDirectory);
+    var requestedOptions = argResults.arguments.toSet();
+    if (!daemon.hasLock) {
+      var runningOptions = await daemon.currentOptions();
+      var version = await daemon.runningVersion();
+      if (version != currentVersion) {
+        stdout
+          ..writeln('Running Version: $version')
+          ..writeln('Current Version: $currentVersion')
+          ..writeln(versionSkew);
+        return 1;
+      } else if (!(runningOptions.length == requestedOptions.length &&
+          runningOptions.containsAll(requestedOptions))) {
+        stdout
+          ..writeln('Running Options: $runningOptions')
+          ..writeln('Requested Options: $requestedOptions')
+          ..writeln(optionsSkew);
+        return 1;
+      } else {
+        stdout.writeln('Daemon is already running.');
+        print(readyToConnectLog);
+        return 0;
+      }
+    } else {
+      stdout.writeln('Starting daemon...');
+      BuildRunnerDaemonBuilder builder;
+      // Ensure we capture any logs that happen during startup.
+      //
+      // These are serialized between special `<log-record>` and `</log-record>`
+      // tags to make parsing them on stdout easier. They can have multiline
+      // strings so we can't just serialize the json on a single line.
+      var startupLogSub =
+          Logger.root.onRecord.listen((record) => stdout.writeln('''
+$logStartMarker
+${jsonEncode(serializers.serialize(ServerLog.fromLogRecord(record)))}
+$logEndMarker'''));
+      builder = await BuildRunnerDaemonBuilder.create(
+        packageGraph,
+        builderApplications,
+        options,
+      );
+      await startupLogSub.cancel();
+
+      // Forward server logs to daemon command STDIO.
+      var logSub = builder.logs.listen((log) {
+        if (log.level > Level.INFO) {
+          var buffer = StringBuffer(log.message);
+          if (log.error != null) buffer.writeln(log.error);
+          if (log.stackTrace != null) buffer.writeln(log.stackTrace);
+          stderr.writeln(buffer);
+        } else {
+          stdout.writeln(log.message);
+        }
+      });
+      var server = await AssetServer.run(builder, packageGraph.root.name);
+      File(assetServerPortFilePath(workingDirectory))
+          .writeAsStringSync('${server.port}');
+      unawaited(builder.buildScriptUpdated.then((_) async {
+        await daemon.stop(
+            message: 'Build script updated. Shutting down the Build Daemon.',
+            failureType: 75);
+      }));
+      await daemon.start(requestedOptions, builder, builder.changeProvider,
+          timeout: Duration(seconds: 60));
+      stdout.writeln(readyToConnectLog);
+      await logSub.cancel();
+      await daemon.onDone.whenComplete(() async {
+        await server.stop();
+      });
+      // Clients can disconnect from the daemon mid build.
+      // As a result we try to relinquish resources which can
+      // cause the build to hang. To ensure there are no ghost processes
+      // fast exit.
+      exit(0);
+    }
+    return 0;
+  }
+}
diff --git a/build_runner/lib/src/entrypoint/doctor.dart b/build_runner/lib/src/entrypoint/doctor.dart
new file mode 100644
index 0000000..c1325e4
--- /dev/null
+++ b/build_runner/lib/src/entrypoint/doctor.dart
@@ -0,0 +1,123 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'package:build/build.dart';
+import 'package:build_config/build_config.dart';
+import 'package:build_runner_core/build_runner_core.dart';
+import 'package:build_runner_core/src/generate/phase.dart';
+import 'package:io/io.dart';
+import 'package:logging/logging.dart';
+
+import '../logging/std_io_logging.dart';
+import '../package_graph/build_config_overrides.dart';
+import 'base_command.dart';
+
+/// A command that validates the build environment.
+class DoctorCommand extends BuildRunnerCommand {
+  @override
+  String get name => 'doctor';
+
+  @override
+  bool get hidden => true;
+
+  @override
+  String get description => 'Check for misconfiguration of the build.';
+
+  @override
+  Future<int> run() async {
+    final options = readOptions();
+    final verbose = options.verbose ?? false;
+    Logger.root.level = verbose ? Level.ALL : Level.INFO;
+    final logSubscription =
+        Logger.root.onRecord.listen(stdIOLogListener(verbose: verbose));
+
+    final config = await _loadBuilderDefinitions();
+
+    var isOk = true;
+    for (final builderApplication in builderApplications) {
+      final builderOk = _checkBuildExtensions(builderApplication, config);
+      isOk = isOk && builderOk;
+    }
+
+    if (isOk) {
+      logger.info('No problems found!\n');
+    }
+    await logSubscription.cancel();
+    return isOk ? ExitCode.success.code : ExitCode.config.code;
+  }
+
+  Future<Map<String, BuilderDefinition>> _loadBuilderDefinitions() async {
+    final packageGraph = PackageGraph.forThisPackage();
+    final buildConfigOverrides =
+        await findBuildConfigOverrides(packageGraph, null);
+    Future<BuildConfig> _packageBuildConfig(PackageNode package) async {
+      if (buildConfigOverrides.containsKey(package.name)) {
+        return buildConfigOverrides[package.name];
+      }
+      try {
+        return await BuildConfig.fromBuildConfigDir(package.name,
+            package.dependencies.map((n) => n.name), package.path);
+      } on ArgumentError catch (e) {
+        logger.severe(
+            'Failed to parse a `build.yaml` file for ${package.name}', e);
+        return BuildConfig.useDefault(
+            package.name, package.dependencies.map((n) => n.name));
+      }
+    }
+
+    final allConfig = await Future.wait(
+        packageGraph.allPackages.values.map(_packageBuildConfig));
+    final allBuilders = <String, BuilderDefinition>{};
+    for (final config in allConfig) {
+      allBuilders.addAll(config.builderDefinitions);
+    }
+    return allBuilders;
+  }
+
+  /// Returns true of [builderApplication] has sane build extension
+  /// configuration.
+  ///
+  /// If there are any problems they will be logged and `false` returned.
+  bool _checkBuildExtensions(BuilderApplication builderApplication,
+      Map<String, BuilderDefinition> config) {
+    var phases = builderApplication.buildPhaseFactories
+        .map((f) => f(PackageNode(null, null, null, isRoot: true),
+            BuilderOptions.empty, InputSet.anything, InputSet.anything, true))
+        .whereType<InBuildPhase>()
+        .toList();
+    if (phases.isEmpty) return true;
+    if (!config.containsKey(builderApplication.builderKey)) return false;
+
+    var problemFound = false;
+    var allowed = Map.of(config[builderApplication.builderKey].buildExtensions);
+    for (final phase in phases.whereType<InBuildPhase>()) {
+      final extensions = phase.builder.buildExtensions;
+      for (final extension in extensions.entries) {
+        if (!allowed.containsKey(extension.key)) {
+          logger.warning('Builder ${builderApplication.builderKey} '
+              'uses input extension ${extension.key} '
+              'which is not specified in the `build.yaml`');
+          problemFound = true;
+          continue;
+        }
+        final allowedOutputs = List.of(allowed[extension.key]);
+        for (final output in extension.value) {
+          if (!allowedOutputs.contains(output)) {
+            logger.warning('Builder ${builderApplication.builderKey} '
+                'outputs $output  from ${extension.key} '
+                'which is not specified in the `build.yaml`');
+            problemFound = true;
+          }
+          // Allow subsequent phases to use these outputs as inputs
+          if (allowedOutputs.length > 1) {
+            allowed.putIfAbsent(output, () => []).addAll(allowedOutputs);
+          }
+        }
+      }
+    }
+    return !problemFound;
+  }
+}
diff --git a/build_runner/lib/src/entrypoint/options.dart b/build_runner/lib/src/entrypoint/options.dart
new file mode 100644
index 0000000..ad4099d
--- /dev/null
+++ b/build_runner/lib/src/entrypoint/options.dart
@@ -0,0 +1,521 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:convert';
+import 'dart:io';
+
+import 'package:args/args.dart';
+import 'package:args/command_runner.dart';
+import 'package:build_config/build_config.dart';
+import 'package:build_daemon/constants.dart';
+import 'package:build_runner_core/build_runner_core.dart';
+import 'package:meta/meta.dart';
+import 'package:path/path.dart' as p;
+import 'package:watcher/watcher.dart';
+
+import '../generate/directory_watcher_factory.dart';
+
+const buildFilterOption = 'build-filter';
+const configOption = 'config';
+const defineOption = 'define';
+const deleteFilesByDefaultOption = 'delete-conflicting-outputs';
+const failOnSevereOption = 'fail-on-severe';
+const hostnameOption = 'hostname';
+const hotReloadOption = 'hot-reload';
+const liveReloadOption = 'live-reload';
+const logPerformanceOption = 'log-performance';
+const logRequestsOption = 'log-requests';
+const lowResourcesModeOption = 'low-resources-mode';
+const outputOption = 'output';
+const releaseOption = 'release';
+const trackPerformanceOption = 'track-performance';
+const skipBuildScriptCheckOption = 'skip-build-script-check';
+const symlinkOption = 'symlink';
+const usePollingWatcherOption = 'use-polling-watcher';
+const verboseOption = 'verbose';
+
+enum BuildUpdatesOption { none, liveReload, hotReload }
+
+final _defaultWebDirs = const ['web', 'test', 'example', 'benchmark'];
+
+/// Base options that are shared among all commands.
+class SharedOptions {
+  /// A set of explicit filters for files to build.
+  ///
+  /// If provided no other files will be built that don't match these filters
+  /// unless they are an input to something matching a filter.
+  final Set<BuildFilter> buildFilters;
+
+  /// By default, the user will be prompted to delete any files which already
+  /// exist but were not generated by this specific build script.
+  ///
+  /// This option can be set to `true` to skip this prompt.
+  final bool deleteFilesByDefault;
+
+  final bool enableLowResourcesMode;
+
+  /// Read `build.$configKey.yaml` instead of `build.yaml`.
+  final String configKey;
+
+  /// A set of targets to build with their corresponding output locations.
+  final Set<BuildDirectory> buildDirs;
+
+  /// Whether or not the output directories should contain only symlinks,
+  /// or full copies of all files.
+  final bool outputSymlinksOnly;
+
+  /// Enables performance tracking and the `/$perf` page.
+  final bool trackPerformance;
+
+  /// A directory to log performance information to.
+  String logPerformanceDir;
+
+  /// Check digest of imports to the build script to invalidate the build.
+  final bool skipBuildScriptCheck;
+
+  final bool verbose;
+
+  // Global config overrides by builder.
+  //
+  // Keys are the builder keys, such as my_package|my_builder, and values
+  // represent config objects. All keys in the config will override the parsed
+  // config for that key.
+  final Map<String, Map<String, dynamic>> builderConfigOverrides;
+
+  final bool isReleaseBuild;
+
+  SharedOptions._({
+    @required this.buildFilters,
+    @required this.deleteFilesByDefault,
+    @required this.enableLowResourcesMode,
+    @required this.configKey,
+    @required this.buildDirs,
+    @required this.outputSymlinksOnly,
+    @required this.trackPerformance,
+    @required this.skipBuildScriptCheck,
+    @required this.verbose,
+    @required this.builderConfigOverrides,
+    @required this.isReleaseBuild,
+    @required this.logPerformanceDir,
+  });
+
+  factory SharedOptions.fromParsedArgs(ArgResults argResults,
+      Iterable<String> positionalArgs, String rootPackage, Command command) {
+    var buildDirs = {
+      ..._parseBuildDirs(argResults),
+      ..._parsePositionalBuildDirs(positionalArgs, command),
+    };
+    var buildFilters = _parseBuildFilters(argResults, rootPackage);
+
+    return SharedOptions._(
+      buildFilters: buildFilters,
+      deleteFilesByDefault: argResults[deleteFilesByDefaultOption] as bool,
+      enableLowResourcesMode: argResults[lowResourcesModeOption] as bool,
+      configKey: argResults[configOption] as String,
+      buildDirs: buildDirs,
+      outputSymlinksOnly: argResults[symlinkOption] as bool,
+      trackPerformance: argResults[trackPerformanceOption] as bool,
+      skipBuildScriptCheck: argResults[skipBuildScriptCheckOption] as bool,
+      verbose: argResults[verboseOption] as bool,
+      builderConfigOverrides:
+          _parseBuilderConfigOverrides(argResults[defineOption], rootPackage),
+      isReleaseBuild: argResults[releaseOption] as bool,
+      logPerformanceDir: argResults[logPerformanceOption] as String,
+    );
+  }
+}
+
+/// Options specific to the `daemon` command.
+class DaemonOptions extends WatchOptions {
+  BuildMode buildMode;
+
+  DaemonOptions._({
+    @required Set<BuildFilter> buildFilters,
+    @required this.buildMode,
+    @required bool deleteFilesByDefault,
+    @required bool enableLowResourcesMode,
+    @required String configKey,
+    @required Set<BuildDirectory> buildDirs,
+    @required bool outputSymlinksOnly,
+    @required bool trackPerformance,
+    @required bool skipBuildScriptCheck,
+    @required bool verbose,
+    @required Map<String, Map<String, dynamic>> builderConfigOverrides,
+    @required bool isReleaseBuild,
+    @required String logPerformanceDir,
+    @required bool usePollingWatcher,
+  }) : super._(
+          buildFilters: buildFilters,
+          deleteFilesByDefault: deleteFilesByDefault,
+          enableLowResourcesMode: enableLowResourcesMode,
+          configKey: configKey,
+          buildDirs: buildDirs,
+          outputSymlinksOnly: outputSymlinksOnly,
+          trackPerformance: trackPerformance,
+          skipBuildScriptCheck: skipBuildScriptCheck,
+          verbose: verbose,
+          builderConfigOverrides: builderConfigOverrides,
+          isReleaseBuild: isReleaseBuild,
+          logPerformanceDir: logPerformanceDir,
+          usePollingWatcher: usePollingWatcher,
+        );
+
+  factory DaemonOptions.fromParsedArgs(ArgResults argResults,
+      Iterable<String> positionalArgs, String rootPackage, Command command) {
+    var buildDirs = {
+      ..._parseBuildDirs(argResults),
+      ..._parsePositionalBuildDirs(positionalArgs, command),
+    };
+    var buildFilters = _parseBuildFilters(argResults, rootPackage);
+
+    var buildModeValue = argResults[buildModeFlag] as String;
+    BuildMode buildMode;
+    if (buildModeValue == BuildMode.Auto.toString()) {
+      buildMode = BuildMode.Auto;
+    } else if (buildModeValue == BuildMode.Manual.toString()) {
+      buildMode = BuildMode.Manual;
+    } else {
+      throw UsageException(
+          'Unexpected value for $buildModeFlag: $buildModeValue',
+          command.usage);
+    }
+
+    return DaemonOptions._(
+      buildFilters: buildFilters,
+      buildMode: buildMode,
+      deleteFilesByDefault: argResults[deleteFilesByDefaultOption] as bool,
+      enableLowResourcesMode: argResults[lowResourcesModeOption] as bool,
+      configKey: argResults[configOption] as String,
+      buildDirs: buildDirs,
+      outputSymlinksOnly: argResults[symlinkOption] as bool,
+      trackPerformance: argResults[trackPerformanceOption] as bool,
+      skipBuildScriptCheck: argResults[skipBuildScriptCheckOption] as bool,
+      verbose: argResults[verboseOption] as bool,
+      builderConfigOverrides:
+          _parseBuilderConfigOverrides(argResults[defineOption], rootPackage),
+      isReleaseBuild: argResults[releaseOption] as bool,
+      logPerformanceDir: argResults[logPerformanceOption] as String,
+      usePollingWatcher: argResults[usePollingWatcherOption] as bool,
+    );
+  }
+}
+
+class WatchOptions extends SharedOptions {
+  final bool usePollingWatcher;
+
+  DirectoryWatcher Function(String) get directoryWatcherFactory =>
+      usePollingWatcher
+          ? pollingDirectoryWatcherFactory
+          : defaultDirectoryWatcherFactory;
+
+  WatchOptions._({
+    @required this.usePollingWatcher,
+    @required Set<BuildFilter> buildFilters,
+    @required bool deleteFilesByDefault,
+    @required bool enableLowResourcesMode,
+    @required String configKey,
+    @required Set<BuildDirectory> buildDirs,
+    @required bool outputSymlinksOnly,
+    @required bool trackPerformance,
+    @required bool skipBuildScriptCheck,
+    @required bool verbose,
+    @required Map<String, Map<String, dynamic>> builderConfigOverrides,
+    @required bool isReleaseBuild,
+    @required String logPerformanceDir,
+  }) : super._(
+          buildFilters: buildFilters,
+          deleteFilesByDefault: deleteFilesByDefault,
+          enableLowResourcesMode: enableLowResourcesMode,
+          configKey: configKey,
+          buildDirs: buildDirs,
+          outputSymlinksOnly: outputSymlinksOnly,
+          trackPerformance: trackPerformance,
+          skipBuildScriptCheck: skipBuildScriptCheck,
+          verbose: verbose,
+          builderConfigOverrides: builderConfigOverrides,
+          isReleaseBuild: isReleaseBuild,
+          logPerformanceDir: logPerformanceDir,
+        );
+
+  factory WatchOptions.fromParsedArgs(ArgResults argResults,
+      Iterable<String> positionalArgs, String rootPackage, Command command) {
+    var buildDirs = {
+      ..._parseBuildDirs(argResults),
+      ..._parsePositionalBuildDirs(positionalArgs, command),
+    };
+    var buildFilters = _parseBuildFilters(argResults, rootPackage);
+
+    return WatchOptions._(
+      buildFilters: buildFilters,
+      deleteFilesByDefault: argResults[deleteFilesByDefaultOption] as bool,
+      enableLowResourcesMode: argResults[lowResourcesModeOption] as bool,
+      configKey: argResults[configOption] as String,
+      buildDirs: buildDirs,
+      outputSymlinksOnly: argResults[symlinkOption] as bool,
+      trackPerformance: argResults[trackPerformanceOption] as bool,
+      skipBuildScriptCheck: argResults[skipBuildScriptCheckOption] as bool,
+      verbose: argResults[verboseOption] as bool,
+      builderConfigOverrides:
+          _parseBuilderConfigOverrides(argResults[defineOption], rootPackage),
+      isReleaseBuild: argResults[releaseOption] as bool,
+      logPerformanceDir: argResults[logPerformanceOption] as String,
+      usePollingWatcher: argResults[usePollingWatcherOption] as bool,
+    );
+  }
+}
+
+/// Options specific to the `serve` command.
+class ServeOptions extends WatchOptions {
+  final String hostName;
+  final BuildUpdatesOption buildUpdates;
+  final bool logRequests;
+  final List<ServeTarget> serveTargets;
+
+  ServeOptions._({
+    @required this.hostName,
+    @required this.buildUpdates,
+    @required this.logRequests,
+    @required this.serveTargets,
+    @required Set<BuildFilter> buildFilters,
+    @required bool deleteFilesByDefault,
+    @required bool enableLowResourcesMode,
+    @required String configKey,
+    @required Set<BuildDirectory> buildDirs,
+    @required bool outputSymlinksOnly,
+    @required bool trackPerformance,
+    @required bool skipBuildScriptCheck,
+    @required bool verbose,
+    @required Map<String, Map<String, dynamic>> builderConfigOverrides,
+    @required bool isReleaseBuild,
+    @required String logPerformanceDir,
+    @required bool usePollingWatcher,
+  }) : super._(
+          buildFilters: buildFilters,
+          deleteFilesByDefault: deleteFilesByDefault,
+          enableLowResourcesMode: enableLowResourcesMode,
+          configKey: configKey,
+          buildDirs: buildDirs,
+          outputSymlinksOnly: outputSymlinksOnly,
+          trackPerformance: trackPerformance,
+          skipBuildScriptCheck: skipBuildScriptCheck,
+          verbose: verbose,
+          builderConfigOverrides: builderConfigOverrides,
+          isReleaseBuild: isReleaseBuild,
+          logPerformanceDir: logPerformanceDir,
+          usePollingWatcher: usePollingWatcher,
+        );
+
+  factory ServeOptions.fromParsedArgs(ArgResults argResults,
+      Iterable<String> positionalArgs, String rootPackage, Command command) {
+    var serveTargets = <ServeTarget>[];
+    var nextDefaultPort = 8080;
+    for (var arg in positionalArgs) {
+      var parts = arg.split(':');
+      if (parts.length > 2) {
+        throw UsageException(
+            'Invalid format for positional argument to serve `$arg`'
+            ', expected <directory>:<port>.',
+            command.usage);
+      }
+
+      var port = parts.length == 2 ? int.tryParse(parts[1]) : nextDefaultPort++;
+      if (port == null) {
+        throw UsageException(
+            'Unable to parse port number in `$arg`', command.usage);
+      }
+
+      var path = parts.first;
+      var pathParts = p.split(path);
+      if (pathParts.length > 1 || path == '.') {
+        throw UsageException(
+            'Only top level directories such as `web` or `test` are allowed as '
+            'positional args, but got `$path`',
+            command.usage);
+      }
+
+      serveTargets.add(ServeTarget(path, port));
+    }
+    if (serveTargets.isEmpty) {
+      for (var dir in _defaultWebDirs) {
+        if (Directory(dir).existsSync()) {
+          serveTargets.add(ServeTarget(dir, nextDefaultPort++));
+        }
+      }
+    }
+
+    var buildDirs = _parseBuildDirs(argResults);
+    for (var target in serveTargets) {
+      buildDirs.add(BuildDirectory(target.dir));
+    }
+
+    var buildFilters = _parseBuildFilters(argResults, rootPackage);
+
+    BuildUpdatesOption buildUpdates;
+    if (argResults[liveReloadOption] as bool &&
+        argResults[hotReloadOption] as bool) {
+      throw UsageException(
+          'Options --$liveReloadOption and --$hotReloadOption '
+          "can't both be used together",
+          command.usage);
+    } else if (argResults[liveReloadOption] as bool) {
+      buildUpdates = BuildUpdatesOption.liveReload;
+    } else if (argResults[hotReloadOption] as bool) {
+      buildUpdates = BuildUpdatesOption.hotReload;
+    }
+
+    return ServeOptions._(
+      buildFilters: buildFilters,
+      hostName: argResults[hostnameOption] as String,
+      buildUpdates: buildUpdates,
+      logRequests: argResults[logRequestsOption] as bool,
+      serveTargets: serveTargets,
+      deleteFilesByDefault: argResults[deleteFilesByDefaultOption] as bool,
+      enableLowResourcesMode: argResults[lowResourcesModeOption] as bool,
+      configKey: argResults[configOption] as String,
+      buildDirs: buildDirs,
+      outputSymlinksOnly: argResults[symlinkOption] as bool,
+      trackPerformance: argResults[trackPerformanceOption] as bool,
+      skipBuildScriptCheck: argResults[skipBuildScriptCheckOption] as bool,
+      verbose: argResults[verboseOption] as bool,
+      builderConfigOverrides:
+          _parseBuilderConfigOverrides(argResults[defineOption], rootPackage),
+      isReleaseBuild: argResults[releaseOption] as bool,
+      logPerformanceDir: argResults[logPerformanceOption] as String,
+      usePollingWatcher: argResults[usePollingWatcherOption] as bool,
+    );
+  }
+}
+
+/// A target to serve, representing a directory and a port.
+class ServeTarget {
+  final String dir;
+  final int port;
+
+  ServeTarget(this.dir, this.port);
+}
+
+Map<String, Map<String, dynamic>> _parseBuilderConfigOverrides(
+    dynamic parsedArg, String rootPackage) {
+  final builderConfigOverrides = <String, Map<String, dynamic>>{};
+  if (parsedArg == null) return builderConfigOverrides;
+  var allArgs = parsedArg is List<String> ? parsedArg : [parsedArg as String];
+  for (final define in allArgs) {
+    final parts = define.split('=');
+    const expectedFormat = '--define "<builder_key>=<option>=<value>"';
+    if (parts.length < 3) {
+      throw ArgumentError.value(
+          define,
+          defineOption,
+          'Expected at least 2 `=` signs, should be of the format like '
+          '$expectedFormat');
+    } else if (parts.length > 3) {
+      var rest = parts.sublist(2);
+      parts
+        ..removeRange(2, parts.length)
+        ..add(rest.join('='));
+    }
+    final builderKey = normalizeBuilderKeyUsage(parts[0], rootPackage);
+    final option = parts[1];
+    dynamic value;
+    // Attempt to parse the value as JSON, and if that fails then treat it as
+    // a normal string.
+    try {
+      value = json.decode(parts[2]);
+    } on FormatException catch (_) {
+      value = parts[2];
+    }
+    final config = builderConfigOverrides.putIfAbsent(
+        builderKey, () => <String, dynamic>{});
+    if (config.containsKey(option)) {
+      throw ArgumentError(
+          'Got duplicate overrides for the same builder option: '
+          '$builderKey=$option. Only one is allowed.');
+    }
+    config[option] = value;
+  }
+  return builderConfigOverrides;
+}
+
+/// Returns build directories with output information parsed from output
+/// arguments.
+///
+/// Each output option is split on `:` where the first value is the
+/// root input directory and the second value output directory.
+/// If no delimeter is provided the root input directory will be null.
+Set<BuildDirectory> _parseBuildDirs(ArgResults argResults) {
+  var outputs = argResults[outputOption] as List<String>;
+  if (outputs == null) return <BuildDirectory>{};
+  var result = <BuildDirectory>{};
+  var outputPaths = <String>{};
+
+  void checkExisting(String outputDir) {
+    if (outputPaths.contains(outputDir)) {
+      throw ArgumentError.value(outputs.join(' '), '--output',
+          'Duplicate output directories are not allowed, got');
+    }
+    outputPaths.add(outputDir);
+  }
+
+  for (var option in argResults[outputOption] as List<String>) {
+    var split = option.split(':');
+    if (split.length == 1) {
+      var output = split.first;
+      checkExisting(output);
+      result.add(BuildDirectory('',
+          outputLocation: OutputLocation(output, hoist: false)));
+    } else if (split.length >= 2) {
+      var output = split.sublist(1).join(':');
+      checkExisting(output);
+      var root = split.first;
+      if (root.contains('/')) {
+        throw ArgumentError.value(
+            option, '--output', 'Input root can not be nested');
+      }
+      result.add(
+          BuildDirectory(split.first, outputLocation: OutputLocation(output)));
+    }
+  }
+  return result;
+}
+
+/// Throws a [UsageException] if [arg] looks like anything other than a top
+/// level directory.
+String _checkTopLevel(String arg, Command command) {
+  var parts = p.split(arg);
+  if (parts.length > 1 || arg == '.') {
+    throw UsageException(
+        'Only top level directories such as `web` or `test` are allowed as '
+        'positional args, but got `$arg`',
+        command.usage);
+  }
+  return arg;
+}
+
+/// Parses positional arguments as plain build directories.
+Set<BuildDirectory> _parsePositionalBuildDirs(
+        Iterable<String> positionalArgs, Command command) =>
+    {
+      for (var arg in positionalArgs)
+        BuildDirectory(_checkTopLevel(arg, command))
+    };
+
+/// Returns build filters parsed from [buildFilterOption] arguments.
+///
+/// These support `package:` uri syntax as well as regular path syntax,
+/// with glob support for both package names and paths.
+Set<BuildFilter> _parseBuildFilters(ArgResults argResults, String rootPackage) {
+  var filterArgs = argResults[buildFilterOption] as List<String>;
+  if (filterArgs?.isEmpty ?? true) return null;
+  try {
+    return {
+      for (var arg in filterArgs) BuildFilter.fromArg(arg, rootPackage),
+    };
+  } on FormatException catch (e) {
+    throw ArgumentError.value(
+        e.source,
+        '--build-filter',
+        'Not a valid build filter, must be either a relative path or '
+            '`package:` uri.\n\n$e');
+  }
+}
diff --git a/build_runner/lib/src/entrypoint/run.dart b/build_runner/lib/src/entrypoint/run.dart
new file mode 100644
index 0000000..ceba164
--- /dev/null
+++ b/build_runner/lib/src/entrypoint/run.dart
@@ -0,0 +1,65 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:io';
+
+import 'package:args/command_runner.dart';
+import 'package:build_runner_core/build_runner_core.dart';
+import 'package:io/ansi.dart' as ansi;
+import 'package:io/io.dart' show ExitCode;
+
+import 'clean.dart';
+import 'runner.dart';
+
+/// A common entry point to parse command line arguments and build or serve with
+/// [builders].
+///
+/// Returns the exit code that should be set when the calling process exits. `0`
+/// implies success.
+Future<int> run(List<String> args, List<BuilderApplication> builders) async {
+  var runner = BuildCommandRunner(builders)..addCommand(CleanCommand());
+  try {
+    var result = await runner.run(args);
+    return result ?? 0;
+  } on UsageException catch (e) {
+    print(ansi.red.wrap(e.message));
+    print('');
+    print(e.usage);
+    return ExitCode.usage.code;
+  } on ArgumentError catch (e) {
+    print(ansi.red.wrap(e.toString()));
+    return ExitCode.usage.code;
+  } on CannotBuildException {
+    // A message should have already been logged.
+    return ExitCode.config.code;
+  } on BuildScriptChangedException {
+    _deleteAssetGraph();
+    if (_runningFromSnapshot) _deleteSelf();
+    return ExitCode.tempFail.code;
+  } on BuildConfigChangedException {
+    return ExitCode.tempFail.code;
+  }
+}
+
+/// Deletes the asset graph for the current build script from disk.
+void _deleteAssetGraph() {
+  var graph = File(assetGraphPath);
+  if (graph.existsSync()) {
+    graph.deleteSync();
+  }
+}
+
+/// Deletes the current running script.
+///
+/// This should only happen if the current script is a snapshot, and it has
+/// been invalidated.
+void _deleteSelf() {
+  var self = File(Platform.script.toFilePath());
+  if (self.existsSync()) {
+    self.deleteSync();
+  }
+}
+
+bool get _runningFromSnapshot => !Platform.script.path.endsWith('.dart');
diff --git a/build_runner/lib/src/entrypoint/run_script.dart b/build_runner/lib/src/entrypoint/run_script.dart
new file mode 100644
index 0000000..6c4fbd3
--- /dev/null
+++ b/build_runner/lib/src/entrypoint/run_script.dart
@@ -0,0 +1,187 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:io';
+import 'dart:isolate';
+
+import 'package:args/command_runner.dart';
+import 'package:build_runner/src/logging/std_io_logging.dart';
+import 'package:build_runner_core/build_runner_core.dart';
+import 'package:io/io.dart';
+import 'package:logging/logging.dart';
+import 'package:path/path.dart' as p;
+
+import '../generate/build.dart';
+import 'base_command.dart';
+import 'options.dart';
+
+class RunCommand extends BuildRunnerCommand {
+  @override
+  String get name => 'run';
+
+  @override
+  String get description => 'Performs a single build, and executes '
+      'a Dart script with the given arguments.';
+
+  @override
+  String get invocation =>
+      '${super.invocation.replaceFirst('[arguments]', '[build-arguments]')} '
+      '<executable> [-- [script-arguments]]';
+
+  @override
+  SharedOptions readOptions() {
+    // Here we validate that [argResults.rest] is exactly equal to all the
+    // arguments after the `--`.
+
+    var separatorPos = argResults.arguments.indexOf('--');
+
+    if (separatorPos >= 0) {
+      void throwUsageException() {
+        throw UsageException(
+            'The `run` command does not support positional args before the '
+            '`--` separator which should separate build args from script args.',
+            usage);
+      }
+
+      var expectedRest = argResults.arguments.skip(separatorPos + 1).toList();
+
+      // Since we expect the first argument to be the name of a script,
+      // we should skip it when comparing extra arguments.
+      var effectiveRest = argResults.rest.skip(1).toList();
+
+      if (effectiveRest.length != expectedRest.length) {
+        throwUsageException();
+      }
+
+      for (var i = 0; i < effectiveRest.length; i++) {
+        if (expectedRest[i] != effectiveRest[i]) {
+          throwUsageException();
+        }
+      }
+    }
+
+    return SharedOptions.fromParsedArgs(
+        argResults, [], packageGraph.root.name, this);
+  }
+
+  @override
+  FutureOr<int> run() async {
+    var options = readOptions();
+    var logSubscription =
+        Logger.root.onRecord.listen(stdIOLogListener(verbose: options.verbose));
+
+    try {
+      // Ensure that the user passed the name of a file to run.
+      if (argResults.rest.isEmpty) {
+        logger..severe('Must specify an executable to run.')..severe(usage);
+        return ExitCode.usage.code;
+      }
+
+      var scriptName = argResults.rest[0];
+      var passedArgs = argResults.rest.skip(1).toList();
+
+      // Ensure the extension is .dart.
+      if (p.extension(scriptName) != '.dart') {
+        logger.severe('$scriptName is not a valid Dart file '
+            'and cannot be run in the VM.');
+        return ExitCode.usage.code;
+      }
+
+      // Create a temporary directory in which to execute the script.
+      var tempPath = Directory.systemTemp
+          .createTempSync('build_runner_run_script')
+          .absolute
+          .uri
+          .toFilePath();
+
+      // Create two ReceivePorts, so that we can quit when the isolate is done.
+      //
+      // Define these before starting the isolate, so that we can close
+      // them if there is a spawn exception.
+      ReceivePort onExit, onError;
+
+      // Use a completer to determine the exit code.
+      var exitCodeCompleter = Completer<int>();
+
+      try {
+        var buildDirs = (options.buildDirs ?? <BuildDirectory>{})
+          ..add(BuildDirectory('',
+              outputLocation: OutputLocation(tempPath,
+                  useSymlinks: options.outputSymlinksOnly, hoist: false)));
+        var result = await build(
+          builderApplications,
+          deleteFilesByDefault: options.deleteFilesByDefault,
+          enableLowResourcesMode: options.enableLowResourcesMode,
+          configKey: options.configKey,
+          buildDirs: buildDirs,
+          packageGraph: packageGraph,
+          verbose: options.verbose,
+          builderConfigOverrides: options.builderConfigOverrides,
+          isReleaseBuild: options.isReleaseBuild,
+          trackPerformance: options.trackPerformance,
+          skipBuildScriptCheck: options.skipBuildScriptCheck,
+          logPerformanceDir: options.logPerformanceDir,
+          buildFilters: options.buildFilters,
+        );
+
+        if (result.status == BuildStatus.failure) {
+          logger.warning('Skipping script run due to build failure');
+          return result.failureType.exitCode;
+        }
+
+        // Find the path of the script to run.
+        var scriptPath = p.join(tempPath, scriptName);
+        var packageConfigPath = p.join(tempPath, '.packages');
+
+        onExit = ReceivePort();
+        onError = ReceivePort();
+
+        // Cleanup after exit.
+        onExit.listen((_) {
+          // If no error was thrown, return 0.
+          if (!exitCodeCompleter.isCompleted) exitCodeCompleter.complete(0);
+        });
+
+        // On an error, kill the isolate, and log the error.
+        onError.listen((e) {
+          onExit.close();
+          onError.close();
+          logger.severe('Unhandled error from script: $scriptName', e[0],
+              StackTrace.fromString(e[1].toString()));
+          if (!exitCodeCompleter.isCompleted) exitCodeCompleter.complete(1);
+        });
+
+        await Isolate.spawnUri(
+          p.toUri(scriptPath),
+          passedArgs,
+          null,
+          errorsAreFatal: true,
+          onExit: onExit.sendPort,
+          onError: onError.sendPort,
+          packageConfig: p.toUri(packageConfigPath),
+        );
+
+        return await exitCodeCompleter.future;
+      } on IsolateSpawnException catch (e) {
+        logger.severe(
+            'Could not spawn isolate. Ensure that your file is in a valid directory (i.e. "bin", "benchmark", "example", "test", "tool").',
+            e);
+        return ExitCode.ioError.code;
+      } finally {
+        // Clean up the output dir.
+        var dir = Directory(tempPath);
+        if (await dir.exists()) await dir.delete(recursive: true);
+
+        onExit?.close();
+        onError?.close();
+        if (!exitCodeCompleter.isCompleted) {
+          exitCodeCompleter.complete(ExitCode.success.code);
+        }
+      }
+    } finally {
+      await logSubscription.cancel();
+    }
+  }
+}
diff --git a/build_runner/lib/src/entrypoint/runner.dart b/build_runner/lib/src/entrypoint/runner.dart
new file mode 100644
index 0000000..4cb309f
--- /dev/null
+++ b/build_runner/lib/src/entrypoint/runner.dart
@@ -0,0 +1,47 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:convert';
+
+import 'package:args/args.dart';
+import 'package:args/command_runner.dart';
+import 'package:build_runner_core/build_runner_core.dart';
+
+import 'base_command.dart' show lineLength;
+import 'build.dart';
+import 'daemon.dart';
+import 'doctor.dart';
+import 'run_script.dart';
+import 'serve.dart';
+import 'test.dart';
+import 'watch.dart';
+
+/// Unified command runner for all build_runner commands.
+class BuildCommandRunner extends CommandRunner<int> {
+  @override
+  final argParser = ArgParser(usageLineLength: lineLength);
+
+  final List<BuilderApplication> builderApplications;
+
+  final packageGraph = PackageGraph.forThisPackage();
+
+  BuildCommandRunner(List<BuilderApplication> builderApplications)
+      : builderApplications = List.unmodifiable(builderApplications),
+        super('build_runner', 'Unified interface for running Dart builds.') {
+    addCommand(BuildCommand());
+    addCommand(DaemonCommand());
+    addCommand(DoctorCommand());
+    addCommand(RunCommand());
+    addCommand(ServeCommand());
+    addCommand(TestCommand(packageGraph));
+    addCommand(WatchCommand());
+  }
+
+  // CommandRunner._usageWithoutDescription is private – this is a reasonable
+  // facsimile.
+  /// Returns [usage] with [description] removed from the beginning.
+  String get usageWithoutDescription => LineSplitter.split(usage)
+      .skipWhile((line) => line == description || line.isEmpty)
+      .join('\n');
+}
diff --git a/build_runner/lib/src/entrypoint/serve.dart b/build_runner/lib/src/entrypoint/serve.dart
new file mode 100644
index 0000000..aacc363
--- /dev/null
+++ b/build_runner/lib/src/entrypoint/serve.dart
@@ -0,0 +1,153 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:io';
+
+import 'package:http_multi_server/http_multi_server.dart';
+import 'package:build_runner_core/build_runner_core.dart';
+import 'package:io/io.dart';
+import 'package:logging/logging.dart';
+import 'package:shelf/shelf_io.dart';
+
+import '../generate/build.dart';
+import '../logging/std_io_logging.dart';
+import '../server/server.dart';
+import 'options.dart';
+import 'watch.dart';
+
+/// Extends [WatchCommand] with dev server functionality.
+class ServeCommand extends WatchCommand {
+  ServeCommand() {
+    argParser
+      ..addOption(hostnameOption,
+          help: 'Specify the hostname to serve on', defaultsTo: 'localhost')
+      ..addFlag(logRequestsOption,
+          defaultsTo: false,
+          negatable: false,
+          help: 'Enables logging for each request to the server.')
+      ..addFlag(liveReloadOption,
+          defaultsTo: false,
+          negatable: false,
+          help: 'Enables automatic page reloading on rebuilds. '
+              "Can't be used together with --$hotReloadOption.")
+      ..addFlag(hotReloadOption,
+          defaultsTo: false,
+          negatable: false,
+          help: 'Enables automatic reloading of changed modules on rebuilds. '
+              "Can't be used together with --$liveReloadOption.");
+  }
+
+  @override
+  String get invocation => '${super.invocation} [<directory>[:<port>]]...';
+
+  @override
+  String get name => 'serve';
+
+  @override
+  String get description =>
+      'Runs a development server that serves the specified targets and runs '
+      'builds based on file system updates.';
+
+  @override
+  ServeOptions readOptions() => ServeOptions.fromParsedArgs(
+      argResults, argResults.rest, packageGraph.root.name, this);
+
+  @override
+  Future<int> run() async {
+    final servers = <ServeTarget, HttpServer>{};
+    return _runServe(servers).whenComplete(() async {
+      await Future.wait(
+          servers.values.map((server) => server.close(force: true)));
+    });
+  }
+
+  Future<int> _runServe(Map<ServeTarget, HttpServer> servers) async {
+    var options = readOptions();
+    try {
+      await Future.wait(options.serveTargets.map((target) async {
+        servers[target] =
+            await HttpMultiServer.bind(options.hostName, target.port);
+      }));
+    } on SocketException catch (e) {
+      var listener = Logger.root.onRecord.listen(stdIOLogListener());
+      if (e.address != null && e.port != null) {
+        logger.severe(
+            'Error starting server at ${e.address.address}:${e.port}, address '
+            'is already in use. Please kill the server running on that port or '
+            'serve on a different port and restart this process.');
+      } else {
+        logger.severe('Error starting server on ${options.hostName}.');
+      }
+      await listener.cancel();
+      return ExitCode.osError.code;
+    }
+
+    var handler = await watch(
+      builderApplications,
+      deleteFilesByDefault: options.deleteFilesByDefault,
+      enableLowResourcesMode: options.enableLowResourcesMode,
+      configKey: options.configKey,
+      buildDirs: options.buildDirs,
+      outputSymlinksOnly: options.outputSymlinksOnly,
+      packageGraph: packageGraph,
+      trackPerformance: options.trackPerformance,
+      skipBuildScriptCheck: options.skipBuildScriptCheck,
+      verbose: options.verbose,
+      builderConfigOverrides: options.builderConfigOverrides,
+      isReleaseBuild: options.isReleaseBuild,
+      logPerformanceDir: options.logPerformanceDir,
+      directoryWatcherFactory: options.directoryWatcherFactory,
+      buildFilters: options.buildFilters,
+    );
+
+    if (handler == null) return ExitCode.config.code;
+
+    servers.forEach((target, server) {
+      serveRequests(
+          server,
+          handler.handlerFor(target.dir,
+              logRequests: options.logRequests,
+              buildUpdates: options.buildUpdates));
+    });
+
+    _ensureBuildWebCompilersDependency(packageGraph, logger);
+
+    final completer = Completer<int>();
+    handleBuildResultsStream(handler.buildResults, completer);
+    _logServerPorts(handler, options, logger);
+    return completer.future;
+  }
+
+  void _logServerPorts(
+      ServeHandler serveHandler, ServeOptions options, Logger logger) async {
+    await serveHandler.currentBuild;
+    // Warn if in serve mode with no servers.
+    if (options.serveTargets.isEmpty) {
+      logger.warning(
+          'Found no known web directories to serve, but running in `serve` '
+          'mode. You may expliclity provide a directory to serve with trailing '
+          'args in <dir>[:<port>] format.');
+    } else {
+      for (var target in options.serveTargets) {
+        stdout.writeln('Serving `${target.dir}` on '
+            'http://${options.hostName}:${target.port}');
+      }
+    }
+  }
+}
+
+void _ensureBuildWebCompilersDependency(PackageGraph packageGraph, Logger log) {
+  if (!packageGraph.allPackages.containsKey('build_web_compilers')) {
+    log.warning('''
+    Missing dev dependency on package:build_web_compilers, which is required to serve Dart compiled to JavaScript.
+
+    Please update your dev_dependencies section of your pubspec.yaml:
+
+    dev_dependencies:
+      build_runner: any
+      build_test: any
+      build_web_compilers: any''');
+  }
+}
diff --git a/build_runner/lib/src/entrypoint/test.dart b/build_runner/lib/src/entrypoint/test.dart
new file mode 100644
index 0000000..fbd69db
--- /dev/null
+++ b/build_runner/lib/src/entrypoint/test.dart
@@ -0,0 +1,188 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:io';
+
+import 'package:args/command_runner.dart';
+import 'package:async/async.dart';
+import 'package:build_runner_core/build_runner_core.dart';
+import 'package:io/io.dart';
+import 'package:path/path.dart' as p;
+import 'package:pub_semver/pub_semver.dart';
+import 'package:pubspec_parse/pubspec_parse.dart';
+
+import '../generate/build.dart';
+import 'base_command.dart';
+import 'options.dart';
+
+/// A command that does a single build and then runs tests using the compiled
+/// assets.
+class TestCommand extends BuildRunnerCommand {
+  TestCommand(PackageGraph packageGraph)
+      : super(
+          // Use symlinks by default, if package:test supports it.
+          symlinksDefault:
+              _packageTestSupportsSymlinks(packageGraph) && !Platform.isWindows,
+        );
+
+  @override
+  String get invocation =>
+      '${super.invocation.replaceFirst('[arguments]', '[build-arguments]')} '
+      '[-- [test-arguments]]';
+
+  @override
+  String get name => 'test';
+
+  @override
+  String get description =>
+      'Performs a single build of the test directory only and then runs tests '
+      'using the compiled assets.';
+
+  @override
+  SharedOptions readOptions() {
+    // This command doesn't allow specifying directories to build, instead it
+    // always builds the `test` directory.
+    //
+    // Here we validate that [argResults.rest] is exactly equal to all the
+    // arguments after the `--`.
+    if (argResults.rest.isNotEmpty) {
+      void throwUsageException() {
+        throw UsageException(
+            'The `test` command does not support positional args before the, '
+            '`--` separator, which should separate build args from test args.',
+            usage);
+      }
+
+      var separatorPos = argResults.arguments.indexOf('--');
+      if (separatorPos < 0) {
+        throwUsageException();
+      }
+      var expectedRest = argResults.arguments.skip(separatorPos + 1).toList();
+      if (argResults.rest.length != expectedRest.length) {
+        throwUsageException();
+      }
+      for (var i = 0; i < argResults.rest.length; i++) {
+        if (expectedRest[i] != argResults.rest[i]) {
+          throwUsageException();
+        }
+      }
+    }
+
+    return SharedOptions.fromParsedArgs(
+        argResults, ['test'], packageGraph.root.name, this);
+  }
+
+  @override
+  Future<int> run() async {
+    SharedOptions options;
+    // We always run our tests in a temp dir.
+    var tempPath = Directory.systemTemp
+        .createTempSync('build_runner_test')
+        .absolute
+        .uri
+        .toFilePath();
+    try {
+      _ensureBuildTestDependency(packageGraph);
+      options = readOptions();
+      var buildDirs = (options.buildDirs ?? <BuildDirectory>{})
+        // Build test by default.
+        ..add(BuildDirectory('test',
+            outputLocation: OutputLocation(tempPath,
+                useSymlinks: options.outputSymlinksOnly, hoist: false)));
+
+      var result = await build(
+        builderApplications,
+        deleteFilesByDefault: options.deleteFilesByDefault,
+        enableLowResourcesMode: options.enableLowResourcesMode,
+        configKey: options.configKey,
+        buildDirs: buildDirs,
+        outputSymlinksOnly: options.outputSymlinksOnly,
+        packageGraph: packageGraph,
+        trackPerformance: options.trackPerformance,
+        skipBuildScriptCheck: options.skipBuildScriptCheck,
+        verbose: options.verbose,
+        builderConfigOverrides: options.builderConfigOverrides,
+        isReleaseBuild: options.isReleaseBuild,
+        logPerformanceDir: options.logPerformanceDir,
+        buildFilters: options.buildFilters,
+      );
+
+      if (result.status == BuildStatus.failure) {
+        stdout.writeln('Skipping tests due to build failure');
+        return result.failureType.exitCode;
+      }
+
+      return await _runTests(tempPath);
+    } on _BuildTestDependencyError catch (e) {
+      stdout.writeln(e);
+      return ExitCode.config.code;
+    } finally {
+      // Clean up the output dir.
+      await Directory(tempPath).delete(recursive: true);
+    }
+  }
+
+  /// Runs tests using [precompiledPath] as the precompiled test directory.
+  Future<int> _runTests(String precompiledPath) async {
+    stdout.writeln('Running tests...\n');
+    var extraTestArgs = argResults.rest;
+    var testProcess = await Process.start(
+        pubBinary,
+        [
+          'run',
+          'test',
+          '--precompiled',
+          precompiledPath,
+          ...extraTestArgs,
+        ],
+        mode: ProcessStartMode.inheritStdio);
+    _ensureProcessExit(testProcess);
+    return testProcess.exitCode;
+  }
+}
+
+bool _packageTestSupportsSymlinks(PackageGraph packageGraph) {
+  var testPackage = packageGraph['test'];
+  if (testPackage == null) return false;
+  var pubspecPath = p.join(testPackage.path, 'pubspec.yaml');
+  var pubspec = Pubspec.parse(File(pubspecPath).readAsStringSync());
+  if (pubspec.version == null) return false;
+  return pubspec.version >= Version(1, 3, 0);
+}
+
+void _ensureBuildTestDependency(PackageGraph packageGraph) {
+  if (!packageGraph.allPackages.containsKey('build_test')) {
+    throw _BuildTestDependencyError();
+  }
+}
+
+void _ensureProcessExit(Process process) {
+  var signalsSub = _exitProcessSignals.listen((signal) async {
+    stdout.writeln('waiting for subprocess to exit...');
+  });
+  process.exitCode.then((_) {
+    signalsSub?.cancel();
+    signalsSub = null;
+  });
+}
+
+Stream<ProcessSignal> get _exitProcessSignals => Platform.isWindows
+    ? ProcessSignal.sigint.watch()
+    : StreamGroup.merge(
+        [ProcessSignal.sigterm.watch(), ProcessSignal.sigint.watch()]);
+
+class _BuildTestDependencyError extends StateError {
+  _BuildTestDependencyError() : super('''
+Missing dev dependency on package:build_test, which is required to run tests.
+
+Please update your dev_dependencies section of your pubspec.yaml:
+
+  dev_dependencies:
+    build_runner: any
+    build_test: any
+    # If you need to run web tests, you will also need this dependency.
+    build_web_compilers: any
+''');
+}
diff --git a/build_runner/lib/src/entrypoint/watch.dart b/build_runner/lib/src/entrypoint/watch.dart
new file mode 100644
index 0000000..fea22ac
--- /dev/null
+++ b/build_runner/lib/src/entrypoint/watch.dart
@@ -0,0 +1,84 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'package:build_runner/src/entrypoint/options.dart';
+import 'package:build_runner_core/build_runner_core.dart';
+import 'package:io/io.dart';
+
+import '../generate/build.dart';
+import 'base_command.dart';
+
+/// A command that watches the file system for updates and rebuilds as
+/// appropriate.
+class WatchCommand extends BuildRunnerCommand {
+  @override
+  String get invocation => '${super.invocation} [directories]';
+
+  @override
+  String get name => 'watch';
+
+  @override
+  String get description =>
+      'Builds the specified targets, watching the file system for updates and '
+      'rebuilding as appropriate.';
+
+  WatchCommand() {
+    argParser.addFlag(usePollingWatcherOption,
+        help: 'Use a polling watcher instead of the current platforms default '
+            'watcher implementation. This should generally only be used if '
+            'you are having problems with the default watcher, as it is '
+            'generally less efficient.');
+  }
+
+  @override
+  WatchOptions readOptions() => WatchOptions.fromParsedArgs(
+      argResults, argResults.rest, packageGraph.root.name, this);
+
+  @override
+  Future<int> run() async {
+    var options = readOptions();
+    var handler = await watch(
+      builderApplications,
+      deleteFilesByDefault: options.deleteFilesByDefault,
+      enableLowResourcesMode: options.enableLowResourcesMode,
+      configKey: options.configKey,
+      buildDirs: options.buildDirs,
+      outputSymlinksOnly: options.outputSymlinksOnly,
+      packageGraph: packageGraph,
+      trackPerformance: options.trackPerformance,
+      skipBuildScriptCheck: options.skipBuildScriptCheck,
+      verbose: options.verbose,
+      builderConfigOverrides: options.builderConfigOverrides,
+      isReleaseBuild: options.isReleaseBuild,
+      logPerformanceDir: options.logPerformanceDir,
+      directoryWatcherFactory: options.directoryWatcherFactory,
+      buildFilters: options.buildFilters,
+    );
+    if (handler == null) return ExitCode.config.code;
+
+    final completer = Completer<int>();
+    handleBuildResultsStream(handler.buildResults, completer);
+    return completer.future;
+  }
+
+  /// Listens to [buildResults], handling certain types of errors and completing
+  /// [completer] appropriately.
+  void handleBuildResultsStream(
+      Stream<BuildResult> buildResults, Completer<int> completer) async {
+    var subscription = buildResults.listen((result) {
+      if (completer.isCompleted) return;
+      if (result.status == BuildStatus.failure) {
+        if (result.failureType == FailureType.buildScriptChanged) {
+          completer.completeError(BuildScriptChangedException());
+        } else if (result.failureType == FailureType.buildConfigChanged) {
+          completer.completeError(BuildConfigChangedException());
+        }
+      }
+    });
+    await subscription.asFuture();
+    if (!completer.isCompleted) completer.complete(ExitCode.success.code);
+  }
+}
diff --git a/build_runner/lib/src/generate/build.dart b/build_runner/lib/src/generate/build.dart
new file mode 100644
index 0000000..c17594a
--- /dev/null
+++ b/build_runner/lib/src/generate/build.dart
@@ -0,0 +1,183 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:io';
+
+import 'package:build/build.dart';
+import 'package:build_runner/src/generate/terminator.dart';
+import 'package:build_runner_core/build_runner_core.dart';
+import 'package:logging/logging.dart';
+import 'package:shelf/shelf.dart';
+import 'package:watcher/watcher.dart';
+
+import '../logging/std_io_logging.dart';
+import '../package_graph/build_config_overrides.dart';
+import '../server/server.dart';
+import 'watch_impl.dart' as watch_impl;
+
+/// Runs all of the BuilderApplications in [builders] once.
+///
+/// By default, the user will be prompted to delete any files which already
+/// exist but were not generated by this specific build script. The
+/// [deleteFilesByDefault] option can be set to `true` to skip this prompt.
+///
+/// A [packageGraph] may be supplied, otherwise one will be constructed using
+/// [PackageGraph.forThisPackage]. The default functionality assumes you are
+/// running in the root directory of a package, with both a `pubspec.yaml` and
+/// `.packages` file present.
+///
+/// A [reader] and [writer] may also be supplied, which can read/write assets
+/// to arbitrary locations or file systems. By default they will write directly
+/// to the root package directory, and will use the [packageGraph] to know where
+/// to read files from.
+///
+/// Logging may be customized by passing a custom [logLevel] below which logs
+/// will be ignored, as well as an [onLog] handler which defaults to [print].
+///
+/// The [terminateEventStream] is a stream which can send termination events.
+/// By default the [ProcessSignal.sigint] stream is used. In this mode, it
+/// will simply consume the first event and allow the build to continue.
+/// Multiple termination events will cause a normal shutdown.
+///
+/// If [outputSymlinksOnly] is `true`, then the merged output directories will
+/// contain only symlinks, which is much faster but not generally suitable for
+/// deployment.
+///
+/// If [verbose] is `true` then verbose logging will be enabled. This changes
+/// the default [logLevel] to [Level.ALL] and removes stack frame folding, among
+/// other things.
+Future<BuildResult> build(List<BuilderApplication> builders,
+    {bool deleteFilesByDefault,
+    bool assumeTty,
+    String configKey,
+    PackageGraph packageGraph,
+    RunnerAssetReader reader,
+    RunnerAssetWriter writer,
+    Resolvers resolvers,
+    Level logLevel,
+    void Function(LogRecord) onLog,
+    Stream terminateEventStream,
+    bool enableLowResourcesMode,
+    Set<BuildDirectory> buildDirs,
+    bool outputSymlinksOnly,
+    bool trackPerformance,
+    bool skipBuildScriptCheck,
+    bool verbose,
+    bool isReleaseBuild,
+    Map<String, Map<String, dynamic>> builderConfigOverrides,
+    String logPerformanceDir,
+    Set<BuildFilter> buildFilters}) async {
+  builderConfigOverrides ??= const {};
+  packageGraph ??= PackageGraph.forThisPackage();
+  var environment = OverrideableEnvironment(
+      IOEnvironment(
+        packageGraph,
+        assumeTty: assumeTty,
+        outputSymlinksOnly: outputSymlinksOnly,
+      ),
+      reader: reader,
+      writer: writer,
+      onLog: onLog ?? stdIOLogListener(assumeTty: assumeTty, verbose: verbose));
+  var logSubscription =
+      LogSubscription(environment, verbose: verbose, logLevel: logLevel);
+  var options = await BuildOptions.create(
+    logSubscription,
+    deleteFilesByDefault: deleteFilesByDefault,
+    packageGraph: packageGraph,
+    skipBuildScriptCheck: skipBuildScriptCheck,
+    overrideBuildConfig:
+        await findBuildConfigOverrides(packageGraph, configKey),
+    enableLowResourcesMode: enableLowResourcesMode,
+    trackPerformance: trackPerformance,
+    logPerformanceDir: logPerformanceDir,
+    resolvers: resolvers,
+  );
+  var terminator = Terminator(terminateEventStream);
+  try {
+    var build = await BuildRunner.create(
+      options,
+      environment,
+      builders,
+      builderConfigOverrides,
+      isReleaseBuild: isReleaseBuild ?? false,
+    );
+    var result =
+        await build.run({}, buildDirs: buildDirs, buildFilters: buildFilters);
+    await build?.beforeExit();
+    return result;
+  } finally {
+    await terminator.cancel();
+    await options.logListener.cancel();
+  }
+}
+
+/// Same as [build], except it watches the file system and re-runs builds
+/// automatically.
+///
+/// Call [ServeHandler.handlerFor] to create a [Handler] for use with
+/// `package:shelf`. Requests for assets will be blocked while builds are
+/// running then served with the latest version of the asset. Only source and
+/// generated assets can be served through this handler.
+///
+/// The [debounceDelay] controls how often builds will run. As long as files
+/// keep changing with less than that amount of time apart, builds will be put
+/// off.
+///
+/// The [directoryWatcherFactory] allows you to inject a way of creating custom
+/// `DirectoryWatcher`s. By default a normal `DirectoryWatcher` will be used.
+///
+/// The [terminateEventStream] is a stream which can send termination events.
+/// By default the [ProcessSignal.sigint] stream is used. In this mode, the
+/// first event will allow any ongoing builds to finish, and then the program
+/// will complete normally. Subsequent events are not handled (and will
+/// typically cause a shutdown).
+Future<ServeHandler> watch(List<BuilderApplication> builders,
+        {bool deleteFilesByDefault,
+        bool assumeTty,
+        String configKey,
+        PackageGraph packageGraph,
+        RunnerAssetReader reader,
+        RunnerAssetWriter writer,
+        Resolvers resolvers,
+        Level logLevel,
+        void Function(LogRecord) onLog,
+        Duration debounceDelay,
+        DirectoryWatcher Function(String) directoryWatcherFactory,
+        Stream terminateEventStream,
+        bool enableLowResourcesMode,
+        Set<BuildDirectory> buildDirs,
+        bool outputSymlinksOnly,
+        bool trackPerformance,
+        bool skipBuildScriptCheck,
+        bool verbose,
+        bool isReleaseBuild,
+        Map<String, Map<String, dynamic>> builderConfigOverrides,
+        String logPerformanceDir,
+        Set<BuildFilter> buildFilters}) =>
+    watch_impl.watch(
+      builders,
+      assumeTty: assumeTty,
+      deleteFilesByDefault: deleteFilesByDefault,
+      configKey: configKey,
+      packageGraph: packageGraph,
+      reader: reader,
+      writer: writer,
+      resolvers: resolvers,
+      logLevel: logLevel,
+      onLog: onLog,
+      debounceDelay: debounceDelay,
+      directoryWatcherFactory: directoryWatcherFactory,
+      terminateEventStream: terminateEventStream,
+      enableLowResourcesMode: enableLowResourcesMode,
+      buildDirs: buildDirs,
+      outputSymlinksOnly: outputSymlinksOnly,
+      trackPerformance: trackPerformance,
+      skipBuildScriptCheck: skipBuildScriptCheck,
+      verbose: verbose,
+      builderConfigOverrides: builderConfigOverrides,
+      isReleaseBuild: isReleaseBuild,
+      logPerformanceDir: logPerformanceDir,
+      buildFilters: buildFilters,
+    );
diff --git a/build_runner/lib/src/generate/directory_watcher_factory.dart b/build_runner/lib/src/generate/directory_watcher_factory.dart
new file mode 100644
index 0000000..a211528
--- /dev/null
+++ b/build_runner/lib/src/generate/directory_watcher_factory.dart
@@ -0,0 +1,11 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 'package:watcher/watcher.dart';
+
+DirectoryWatcher defaultDirectoryWatcherFactory(String path) =>
+    DirectoryWatcher(path);
+
+DirectoryWatcher pollingDirectoryWatcherFactory(String path) =>
+    PollingDirectoryWatcher(path);
diff --git a/build_runner/lib/src/generate/terminator.dart b/build_runner/lib/src/generate/terminator.dart
new file mode 100644
index 0000000..3e44b8c
--- /dev/null
+++ b/build_runner/lib/src/generate/terminator.dart
@@ -0,0 +1,38 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:io';
+
+/// Fires [shouldTerminate] once a `SIGINT` is intercepted.
+///
+/// The `SIGINT` stream can optionally be replaced with another Stream in the
+/// constructor. [cancel] should be called after work is finished. If multiple
+/// events are receieved on the terminate event stream before work is finished
+/// the process will be terminated with [exit].
+class Terminator {
+  /// A Future that fires when a signal has been received indicating that builds
+  /// should stop.
+  final Future shouldTerminate;
+  final StreamSubscription _subscription;
+
+  factory Terminator([Stream terminateEventStream]) {
+    var shouldTerminate = Completer<void>();
+    terminateEventStream ??= ProcessSignal.sigint.watch();
+    var numEventsSeen = 0;
+    var terminateListener = terminateEventStream.listen((_) {
+      numEventsSeen++;
+      if (numEventsSeen == 1) {
+        shouldTerminate.complete();
+      } else {
+        exit(2);
+      }
+    });
+    return Terminator._(shouldTerminate.future, terminateListener);
+  }
+
+  Terminator._(this.shouldTerminate, this._subscription);
+
+  Future cancel() => _subscription.cancel();
+}
diff --git a/build_runner/lib/src/generate/watch_impl.dart b/build_runner/lib/src/generate/watch_impl.dart
new file mode 100644
index 0000000..3be7ee4
--- /dev/null
+++ b/build_runner/lib/src/generate/watch_impl.dart
@@ -0,0 +1,361 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'package:build/build.dart';
+import 'package:build_config/build_config.dart';
+import 'package:build_runner/src/package_graph/build_config_overrides.dart';
+import 'package:build_runner/src/watcher/asset_change.dart';
+import 'package:build_runner/src/watcher/change_filter.dart';
+import 'package:build_runner/src/watcher/collect_changes.dart';
+import 'package:build_runner/src/watcher/delete_writer.dart';
+import 'package:build_runner/src/watcher/graph_watcher.dart';
+import 'package:build_runner/src/watcher/node_watcher.dart';
+import 'package:build_runner_core/build_runner_core.dart';
+import 'package:build_runner_core/src/asset_graph/graph.dart';
+import 'package:build_runner_core/src/generate/build_impl.dart';
+import 'package:crypto/crypto.dart';
+import 'package:logging/logging.dart';
+import 'package:pedantic/pedantic.dart';
+import 'package:stream_transform/stream_transform.dart';
+import 'package:watcher/watcher.dart';
+
+import '../logging/std_io_logging.dart';
+import '../server/server.dart';
+import 'terminator.dart';
+
+final _logger = Logger('Watch');
+
+Future<ServeHandler> watch(
+  List<BuilderApplication> builders, {
+  bool deleteFilesByDefault,
+  bool assumeTty,
+  String configKey,
+  PackageGraph packageGraph,
+  RunnerAssetReader reader,
+  RunnerAssetWriter writer,
+  Resolvers resolvers,
+  Level logLevel,
+  void Function(LogRecord) onLog,
+  Duration debounceDelay,
+  DirectoryWatcher Function(String) directoryWatcherFactory,
+  Stream terminateEventStream,
+  bool skipBuildScriptCheck,
+  bool enableLowResourcesMode,
+  Map<String, BuildConfig> overrideBuildConfig,
+  Set<BuildDirectory> buildDirs,
+  bool outputSymlinksOnly,
+  bool trackPerformance,
+  bool verbose,
+  Map<String, Map<String, dynamic>> builderConfigOverrides,
+  bool isReleaseBuild,
+  String logPerformanceDir,
+  Set<BuildFilter> buildFilters,
+}) async {
+  builderConfigOverrides ??= const {};
+  packageGraph ??= PackageGraph.forThisPackage();
+  buildDirs ??= <BuildDirectory>{};
+  buildFilters ??= <BuildFilter>{};
+
+  var environment = OverrideableEnvironment(
+      IOEnvironment(packageGraph,
+          assumeTty: assumeTty, outputSymlinksOnly: outputSymlinksOnly),
+      reader: reader,
+      writer: writer,
+      onLog: onLog ?? stdIOLogListener(assumeTty: assumeTty, verbose: verbose));
+  var logSubscription =
+      LogSubscription(environment, verbose: verbose, logLevel: logLevel);
+  overrideBuildConfig ??=
+      await findBuildConfigOverrides(packageGraph, configKey);
+  var options = await BuildOptions.create(
+    logSubscription,
+    deleteFilesByDefault: deleteFilesByDefault,
+    packageGraph: packageGraph,
+    overrideBuildConfig: overrideBuildConfig,
+    debounceDelay: debounceDelay,
+    skipBuildScriptCheck: skipBuildScriptCheck,
+    enableLowResourcesMode: enableLowResourcesMode,
+    trackPerformance: trackPerformance,
+    logPerformanceDir: logPerformanceDir,
+    resolvers: resolvers,
+  );
+  var terminator = Terminator(terminateEventStream);
+
+  var watch = _runWatch(
+      options,
+      environment,
+      builders,
+      builderConfigOverrides,
+      terminator.shouldTerminate,
+      directoryWatcherFactory,
+      configKey,
+      buildDirs
+          .any((target) => target?.outputLocation?.path?.isNotEmpty ?? false),
+      buildDirs,
+      buildFilters,
+      isReleaseMode: isReleaseBuild ?? false);
+
+  unawaited(watch.buildResults.drain().then((_) async {
+    await terminator.cancel();
+    await options.logListener.cancel();
+  }));
+
+  return createServeHandler(watch);
+}
+
+/// Repeatedly run builds as files change on disk until [until] fires.
+///
+/// Sets up file watchers and collects changes then triggers new builds. When
+/// [until] fires the file watchers will be stopped and up to one additional
+/// build may run if there were pending changes.
+///
+/// The [BuildState.buildResults] stream will end after the final build has been
+/// run.
+WatchImpl _runWatch(
+        BuildOptions options,
+        BuildEnvironment environment,
+        List<BuilderApplication> builders,
+        Map<String, Map<String, dynamic>> builderConfigOverrides,
+        Future until,
+        DirectoryWatcher Function(String) directoryWatcherFactory,
+        String configKey,
+        bool willCreateOutputDirs,
+        Set<BuildDirectory> buildDirs,
+        Set<BuildFilter> buildFilters,
+        {bool isReleaseMode = false}) =>
+    WatchImpl(
+        options,
+        environment,
+        builders,
+        builderConfigOverrides,
+        until,
+        directoryWatcherFactory,
+        configKey,
+        willCreateOutputDirs,
+        buildDirs,
+        buildFilters,
+        isReleaseMode: isReleaseMode);
+
+class WatchImpl implements BuildState {
+  BuildImpl _build;
+
+  AssetGraph get assetGraph => _build?.assetGraph;
+
+  final _readyCompleter = Completer<void>();
+  Future<void> get ready => _readyCompleter.future;
+
+  final String _configKey; // may be null
+
+  /// Delay to wait for more file watcher events.
+  final Duration _debounceDelay;
+
+  /// Injectable factory for creating directory watchers.
+  final DirectoryWatcher Function(String) _directoryWatcherFactory;
+
+  /// Whether or not we will be creating any output directories.
+  ///
+  /// If not, then we don't care about source edits that don't have outputs.
+  final bool _willCreateOutputDirs;
+
+  /// Should complete when we need to kill the build.
+  final _terminateCompleter = Completer<Null>();
+
+  /// The [PackageGraph] for the current program.
+  final PackageGraph packageGraph;
+
+  /// The directories to build upon file changes and where to output them.
+  final Set<BuildDirectory> _buildDirs;
+
+  /// Filters for specific files to build.
+  final Set<BuildFilter> _buildFilters;
+
+  @override
+  Future<BuildResult> currentBuild;
+
+  /// Pending expected delete events from the build.
+  final Set<AssetId> _expectedDeletes = <AssetId>{};
+
+  FinalizedReader _reader;
+  FinalizedReader get reader => _reader;
+
+  WatchImpl(
+      BuildOptions options,
+      BuildEnvironment environment,
+      List<BuilderApplication> builders,
+      Map<String, Map<String, dynamic>> builderConfigOverrides,
+      Future until,
+      this._directoryWatcherFactory,
+      this._configKey,
+      this._willCreateOutputDirs,
+      this._buildDirs,
+      this._buildFilters,
+      {bool isReleaseMode = false})
+      : _debounceDelay = options.debounceDelay,
+        packageGraph = options.packageGraph {
+    buildResults = _run(
+            options, environment, builders, builderConfigOverrides, until,
+            isReleaseMode: isReleaseMode)
+        .asBroadcastStream();
+  }
+
+  @override
+  Stream<BuildResult> buildResults;
+
+  /// Runs a build any time relevant files change.
+  ///
+  /// Only one build will run at a time, and changes are batched.
+  ///
+  /// File watchers are scheduled synchronously.
+  Stream<BuildResult> _run(
+      BuildOptions options,
+      BuildEnvironment environment,
+      List<BuilderApplication> builders,
+      Map<String, Map<String, dynamic>> builderConfigOverrides,
+      Future until,
+      {bool isReleaseMode = false}) {
+    var watcherEnvironment = OverrideableEnvironment(environment,
+        writer: OnDeleteWriter(environment.writer, _expectedDeletes.add));
+    var firstBuildCompleter = Completer<BuildResult>();
+    currentBuild = firstBuildCompleter.future;
+    var controller = StreamController<BuildResult>();
+
+    Future<BuildResult> doBuild(List<List<AssetChange>> changes) async {
+      assert(_build != null);
+      _logger..info('${'-' * 72}\n')..info('Starting Build\n');
+      var mergedChanges = collectChanges(changes);
+
+      _expectedDeletes.clear();
+      if (!options.skipBuildScriptCheck) {
+        if (_build.buildScriptUpdates
+            .hasBeenUpdated(mergedChanges.keys.toSet())) {
+          _terminateCompleter.complete();
+          _logger.severe('Terminating builds due to build script update');
+          return BuildResult(BuildStatus.failure, [],
+              failureType: FailureType.buildScriptChanged);
+        }
+      }
+      return _build.run(mergedChanges,
+          buildDirs: _buildDirs, buildFilters: _buildFilters);
+    }
+
+    var terminate = Future.any([until, _terminateCompleter.future]).then((_) {
+      _logger.info('Terminating. No further builds will be scheduled\n');
+    });
+
+    Digest originalRootPackagesDigest;
+    final rootPackagesId = AssetId(packageGraph.root.name, '.packages');
+
+    // Start watching files immediately, before the first build is even started.
+    var graphWatcher = PackageGraphWatcher(packageGraph,
+        logger: _logger,
+        watch: (node) =>
+            PackageNodeWatcher(node, watch: _directoryWatcherFactory));
+    graphWatcher
+        .watch()
+        .asyncMap<AssetChange>((change) {
+          // Delay any events until the first build is completed.
+          if (firstBuildCompleter.isCompleted) return change;
+          return firstBuildCompleter.future.then((_) => change);
+        })
+        .asyncMap<AssetChange>((change) {
+          var id = change.id;
+          assert(originalRootPackagesDigest != null);
+          if (id == rootPackagesId) {
+            // Kill future builds if the root packages file changes.
+            return watcherEnvironment.reader
+                .readAsBytes(rootPackagesId)
+                .then((bytes) {
+              if (md5.convert(bytes) != originalRootPackagesDigest) {
+                _terminateCompleter.complete();
+                _logger
+                    .severe('Terminating builds due to package graph update, '
+                        'please restart the build.');
+              }
+              return change;
+            });
+          } else if (_isBuildYaml(id) ||
+              _isConfiguredBuildYaml(id) ||
+              _isPackageBuildYamlOverride(id)) {
+            controller.add(BuildResult(BuildStatus.failure, [],
+                failureType: FailureType.buildConfigChanged));
+
+            // Kill future builds if the build.yaml files change.
+            _terminateCompleter.complete();
+            _logger.severe(
+                'Terminating builds due to ${id.package}:${id.path} update.');
+          }
+          return change;
+        })
+        .where((change) {
+          assert(_readyCompleter.isCompleted);
+          return shouldProcess(
+            change,
+            assetGraph,
+            options,
+            _willCreateOutputDirs,
+            _expectedDeletes,
+          );
+        })
+        .debounceBuffer(_debounceDelay)
+        .takeUntil(terminate)
+        .asyncMapBuffer((changes) => currentBuild = doBuild(changes)
+          ..whenComplete(() => currentBuild = null))
+        .listen((BuildResult result) {
+          if (controller.isClosed) return;
+          controller.add(result);
+        })
+        .onDone(() async {
+          await currentBuild;
+          await _build?.beforeExit();
+          if (!controller.isClosed) await controller.close();
+          _logger.info('Builds finished. Safe to exit\n');
+        });
+
+    // Schedule the actual first build for the future so we can return the
+    // stream synchronously.
+    () async {
+      await logTimedAsync(_logger, 'Waiting for all file watchers to be ready',
+          () => graphWatcher.ready);
+      originalRootPackagesDigest = md5
+          .convert(await watcherEnvironment.reader.readAsBytes(rootPackagesId));
+
+      BuildResult firstBuild;
+      try {
+        _build = await BuildImpl.create(
+            options, watcherEnvironment, builders, builderConfigOverrides,
+            isReleaseBuild: isReleaseMode);
+
+        firstBuild = await _build
+            .run({}, buildDirs: _buildDirs, buildFilters: _buildFilters);
+      } on CannotBuildException {
+        _terminateCompleter.complete();
+
+        firstBuild = BuildResult(BuildStatus.failure, []);
+      } on BuildScriptChangedException {
+        _terminateCompleter.complete();
+
+        firstBuild = BuildResult(BuildStatus.failure, [],
+            failureType: FailureType.buildScriptChanged);
+      }
+
+      _reader = _build?.finalizedReader;
+      _readyCompleter.complete();
+      // It is possible this is already closed if the user kills the process
+      // early, which results in an exception without this check.
+      if (!controller.isClosed) controller.add(firstBuild);
+      firstBuildCompleter.complete(firstBuild);
+    }();
+
+    return controller.stream;
+  }
+
+  bool _isBuildYaml(AssetId id) => id.path == 'build.yaml';
+  bool _isConfiguredBuildYaml(AssetId id) =>
+      id.package == packageGraph.root.name &&
+      id.path == 'build.$_configKey.yaml';
+  bool _isPackageBuildYamlOverride(AssetId id) =>
+      id.package == packageGraph.root.name &&
+      id.path.contains(_packageBuildYamlRegexp);
+  final _packageBuildYamlRegexp = RegExp(r'^[a-z0-9_]+\.build\.yaml$');
+}
diff --git a/build_runner/lib/src/logging/std_io_logging.dart b/build_runner/lib/src/logging/std_io_logging.dart
new file mode 100644
index 0000000..fab916b
--- /dev/null
+++ b/build_runner/lib/src/logging/std_io_logging.dart
@@ -0,0 +1,67 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:convert';
+import 'dart:io';
+
+import 'package:io/ansi.dart';
+import 'package:logging/logging.dart';
+import 'package:stack_trace/stack_trace.dart';
+
+void Function(LogRecord) stdIOLogListener({bool assumeTty, bool verbose}) =>
+    (record) => overrideAnsiOutput(assumeTty == true || ansiOutputEnabled, () {
+          _stdIOLogListener(record, verbose: verbose ?? false);
+        });
+
+StringBuffer colorLog(LogRecord record, {bool verbose}) {
+  AnsiCode color;
+  if (record.level < Level.WARNING) {
+    color = cyan;
+  } else if (record.level < Level.SEVERE) {
+    color = yellow;
+  } else {
+    color = red;
+  }
+  final level = color.wrap('[${record.level}]');
+  final eraseLine = ansiOutputEnabled && !verbose ? '\x1b[2K\r' : '';
+  var lines = <Object>[
+    '$eraseLine$level ${_recordHeader(record, verbose)}${record.message}'
+  ];
+
+  if (record.error != null) {
+    lines.add(record.error);
+  }
+
+  if (record.stackTrace != null && verbose) {
+    var trace = Trace.from(record.stackTrace).foldFrames((f) {
+      return f.package == 'build_runner' || f.package == 'build';
+    }, terse: true);
+
+    lines.add(trace);
+  }
+
+  var message = StringBuffer(lines.join('\n'));
+
+  // We always add an extra newline at the end of each message, so it
+  // isn't multiline unless we see > 2 lines.
+  var multiLine = LineSplitter.split(message.toString()).length > 2;
+
+  if (record.level > Level.INFO || !ansiOutputEnabled || multiLine || verbose) {
+    // Add an extra line to the output so the last line isn't written over.
+    message.writeln('');
+  }
+  return message;
+}
+
+void _stdIOLogListener(LogRecord record, {bool verbose}) =>
+    stdout.write(colorLog(record, verbose: verbose));
+
+/// Filter out the Logger names which aren't coming from specific builders and
+/// splits the header for levels >= WARNING.
+String _recordHeader(LogRecord record, bool verbose) {
+  var maybeSplit = record.level >= Level.WARNING ? '\n' : '';
+  return verbose || record.loggerName.contains(' ')
+      ? '${record.loggerName}:$maybeSplit'
+      : '';
+}
diff --git a/build_runner/lib/src/package_graph/build_config_overrides.dart b/build_runner/lib/src/package_graph/build_config_overrides.dart
new file mode 100644
index 0000000..8210b93
--- /dev/null
+++ b/build_runner/lib/src/package_graph/build_config_overrides.dart
@@ -0,0 +1,57 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:io';
+
+import 'package:build_config/build_config.dart';
+import 'package:build_runner_core/build_runner_core.dart';
+import 'package:glob/glob.dart';
+import 'package:logging/logging.dart';
+import 'package:path/path.dart' as p;
+
+final _log = Logger('BuildConfigOverrides');
+
+Future<Map<String, BuildConfig>> findBuildConfigOverrides(
+    PackageGraph packageGraph, String configKey) async {
+  final configs = <String, BuildConfig>{};
+  final configFiles = Glob('*.build.yaml').list();
+  await for (final file in configFiles) {
+    if (file is File) {
+      final packageName = p.basename(file.path).split('.').first;
+      final packageNode = packageGraph.allPackages[packageName];
+      if (packageNode == null) {
+        _log.warning('A build config override is provided for $packageName but '
+            'that package does not exist. '
+            'Remove the ${p.basename(file.path)} override or add a dependency '
+            'on $packageName.');
+        continue;
+      }
+      final yaml = file.readAsStringSync();
+      final config = BuildConfig.parse(
+        packageName,
+        packageNode.dependencies.map((n) => n.name),
+        yaml,
+        configYamlPath: file.path,
+      );
+      configs[packageName] = config;
+    }
+  }
+  if (configKey != null) {
+    final file = File('build.$configKey.yaml');
+    if (!file.existsSync()) {
+      _log.warning('Cannot find build.$configKey.yaml for specified config.');
+      throw CannotBuildException();
+    }
+    final yaml = file.readAsStringSync();
+    final config = BuildConfig.parse(
+      packageGraph.root.name,
+      packageGraph.root.dependencies.map((n) => n.name),
+      yaml,
+      configYamlPath: file.path,
+    );
+    configs[packageGraph.root.name] = config;
+  }
+  return configs;
+}
diff --git a/build_runner/lib/src/server/README.md b/build_runner/lib/src/server/README.md
new file mode 100644
index 0000000..e40bb70
--- /dev/null
+++ b/build_runner/lib/src/server/README.md
@@ -0,0 +1,4 @@
+## Regenerating the graph_vis_main.dart.js{.map} files
+
+To regenerate these files, you should use the custom build script at
+`tool/build.dart`. This supports all the normal build_runner commands.
diff --git a/build_runner/lib/src/server/asset_graph_handler.dart b/build_runner/lib/src/server/asset_graph_handler.dart
new file mode 100644
index 0000000..78921e2
--- /dev/null
+++ b/build_runner/lib/src/server/asset_graph_handler.dart
@@ -0,0 +1,145 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:build/build.dart';
+import 'package:glob/glob.dart';
+import 'package:shelf/shelf.dart' as shelf;
+
+import 'package:build_runner_core/src/asset_graph/graph.dart';
+import 'package:build_runner_core/src/asset_graph/node.dart';
+import 'path_to_asset_id.dart';
+
+/// A handler for `/$graph` requests under a specific `rootDir`.
+class AssetGraphHandler {
+  final AssetReader _reader;
+  final String _rootPackage;
+  final AssetGraph _assetGraph;
+
+  AssetGraphHandler(this._reader, this._rootPackage, this._assetGraph);
+
+  /// Returns a response with the information about a specific node in the
+  /// graph.
+  ///
+  /// For an empty path, returns the HTML page to render the graph.
+  ///
+  /// For queries with `q=QUERY` will look for the assetNode referenced.
+  /// QUERY can be an [AssetId] or a path.
+  ///
+  /// [AssetId] as `package|path`
+  ///
+  /// path as:
+  /// - `packages/<package>/<path_under_lib>`
+  /// - `<path_under_root_package>`
+  /// - `<path_under_rootDir>`
+  ///
+  /// There may be some ambiguity between paths which are under the top-level of
+  /// the root package, and those which are under the rootDir. Preference is
+  /// given to the asset (if it exists) which is not under the implicit
+  /// `rootDir`. For instance if the request is `$graph/web/main.dart` this will
+  /// prefer to serve `<package>|web/main.dart`, but if it does not exist will
+  /// fall back to `<package>|web/web/main.dart`.
+  FutureOr<shelf.Response> handle(shelf.Request request, String rootDir) async {
+    switch (request.url.path) {
+      case '':
+        if (!request.url.hasQuery) {
+          return shelf.Response.ok(
+              await _reader.readAsString(
+                  AssetId('build_runner', 'lib/src/server/graph_viz.html')),
+              headers: {HttpHeaders.contentTypeHeader: 'text/html'});
+        }
+
+        var query = request.url.queryParameters['q']?.trim();
+        if (query != null && query.isNotEmpty) {
+          var filter = request.url.queryParameters['f']?.trim();
+          return _handleQuery(query, rootDir, filter: filter);
+        }
+        break;
+      case 'assets.json':
+        return _jsonResponse(_assetGraph.serialize());
+    }
+
+    return shelf.Response.notFound('Bad request: "${request.url}".');
+  }
+
+  Future<shelf.Response> _handleQuery(String query, String rootDir,
+      {String filter}) async {
+    var filterGlob = filter != null ? Glob(filter) : null;
+    var pipeIndex = query.indexOf('|');
+
+    AssetId assetId;
+    if (pipeIndex < 0) {
+      var querySplit = query.split('/');
+
+      assetId = pathToAssetId(
+          _rootPackage, querySplit.first, querySplit.skip(1).toList());
+
+      if (!_assetGraph.contains(assetId)) {
+        var secondTry = pathToAssetId(_rootPackage, rootDir, querySplit);
+
+        if (!_assetGraph.contains(secondTry)) {
+          return shelf.Response.notFound(
+              'Could not find asset for path "$query". Tried:\n'
+              '- $assetId\n'
+              '- $secondTry');
+        }
+        assetId = secondTry;
+      }
+    } else {
+      assetId = AssetId.parse(query);
+      if (!_assetGraph.contains(assetId)) {
+        return shelf.Response.notFound(
+            'Could not find asset in build graph: $assetId');
+      }
+    }
+    var node = _assetGraph.get(assetId);
+    var currentEdge = 0;
+    var nodes = [
+      {'id': '${node.id}', 'label': '${node.id}'}
+    ];
+    var edges = <Map<String, String>>[];
+    for (final output in node.outputs) {
+      if (filterGlob != null && !filterGlob.matches(output.toString())) {
+        continue;
+      }
+      edges.add(
+          {'from': '${node.id}', 'to': '$output', 'id': 'e${currentEdge++}'});
+      nodes.add({'id': '$output', 'label': '$output'});
+    }
+    if (node is NodeWithInputs) {
+      for (final input in node.inputs) {
+        if (filterGlob != null && !filterGlob.matches(input.toString())) {
+          continue;
+        }
+        edges.add(
+            {'from': '$input', 'to': '${node.id}', 'id': 'e${currentEdge++}'});
+        nodes.add({'id': '$input', 'label': '$input'});
+      }
+    }
+    var result = <String, dynamic>{
+      'primary': {
+        'id': '${node.id}',
+        'hidden': node is GeneratedAssetNode ? node.isHidden : null,
+        'state': node is NodeWithInputs ? '${node.state}' : null,
+        'wasOutput': node is GeneratedAssetNode ? node.wasOutput : null,
+        'isFailure': node is GeneratedAssetNode ? node.isFailure : null,
+        'phaseNumber': node is NodeWithInputs ? node.phaseNumber : null,
+        'type': node.runtimeType.toString(),
+        'glob': node is GlobAssetNode ? node.glob.pattern : null,
+        'lastKnownDigest': node.lastKnownDigest.toString(),
+      },
+      'edges': edges,
+      'nodes': nodes,
+    };
+    return _jsonResponse(_jsonUtf8Encoder.convert(result));
+  }
+}
+
+final _jsonUtf8Encoder = JsonUtf8Encoder();
+
+shelf.Response _jsonResponse(List<int> body) => shelf.Response.ok(body,
+    headers: {HttpHeaders.contentTypeHeader: 'application/json'});
diff --git a/build_runner/lib/src/server/build_updates_client/module.dart b/build_runner/lib/src/server/build_updates_client/module.dart
new file mode 100644
index 0000000..6604f9b
--- /dev/null
+++ b/build_runner/lib/src/server/build_updates_client/module.dart
@@ -0,0 +1,71 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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.
+
+abstract class Library {
+  Object onDestroy();
+
+  bool onSelfUpdate([Object data]);
+
+  bool onChildUpdate(String childId, Library child, [Object data]);
+}
+
+/// Used for representation of amd modules that wraps several dart libraries
+/// inside
+class Module {
+  /// Grouped by absolute library path starting with `package:`
+  final Map<String, Library> libraries;
+
+  Module(this.libraries);
+
+  /// Calls onDestroy on each of underlined libraries and combines returned data
+  Map<String, Object> onDestroy() {
+    var data = <String, Object>{};
+    for (var key in libraries.keys) {
+      data[key] = libraries[key].onDestroy();
+    }
+    return data;
+  }
+
+  /// Calls onSelfUpdate on each of underlined libraries, returns aggregated
+  /// result as "maximum" assuming true < null < false. Stops execution on first
+  /// false result
+  bool onSelfUpdate(Map<String, Object> data) {
+    var result = true;
+    for (var key in libraries.keys) {
+      var success = libraries[key].onSelfUpdate(data[key]);
+      if (success == false) {
+        return false;
+      } else if (success == null) {
+        result = success;
+      }
+    }
+    return result;
+  }
+
+  /// Calls onChildUpdate on each of underlined libraries, returns aggregated
+  /// result as "maximum" assuming true < null < false. Stops execution on first
+  /// false result
+  bool onChildUpdate(String childId, Module child, Map<String, Object> data) {
+    var result = true;
+    // TODO(inayd): This is a rought implementation with lots of false positive
+    // reloads. In current implementation every library in parent module should
+    // know how to handle each library in child module. Also [roughLibraryKeyDecode]
+    // depends on unreliable implementation details. Proper implementation
+    // should rely on inner graph of dependencies between libraries in module,
+    // to require only parent libraries which really depend on child ones to
+    // handle it's updates. See dart-lang/build#1767.
+    for (var parentKey in libraries.keys) {
+      for (var childKey in child.libraries.keys) {
+        var success = libraries[parentKey]
+            .onChildUpdate(childKey, child.libraries[childKey], data[childKey]);
+        if (success == false) {
+          return false;
+        } else if (success == null) {
+          result = success;
+        }
+      }
+    }
+    return result;
+  }
+}
diff --git a/build_runner/lib/src/server/build_updates_client/reload_handler.dart b/build_runner/lib/src/server/build_updates_client/reload_handler.dart
new file mode 100644
index 0000000..0087931
--- /dev/null
+++ b/build_runner/lib/src/server/build_updates_client/reload_handler.dart
@@ -0,0 +1,36 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:convert';
+
+import 'reloading_manager.dart';
+
+/// Provides [listener] to handle web socket connection and reload invalidated
+/// modules using [ReloadingManager]
+class ReloadHandler {
+  final String Function(String) _moduleIdByPath;
+  final Map<String, String> _digests;
+  final ReloadingManager _reloadingManager;
+
+  ReloadHandler(this._digests, this._moduleIdByPath, this._reloadingManager);
+
+  void listener(String data) async {
+    var updatedAssetDigests = json.decode(data) as Map<String, dynamic>;
+    var moduleIdsToReload = <String>[];
+    for (var path in updatedAssetDigests.keys) {
+      if (_digests[path] == updatedAssetDigests[path]) {
+        continue;
+      }
+      var moduleId = _moduleIdByPath(path);
+      if (_digests.containsKey(path) && moduleId != null) {
+        moduleIdsToReload.add(moduleId);
+      }
+      _digests[path] = updatedAssetDigests[path] as String;
+    }
+    if (moduleIdsToReload.isNotEmpty) {
+      _reloadingManager.updateGraph();
+      await _reloadingManager.reload(moduleIdsToReload);
+    }
+  }
+}
diff --git a/build_runner/lib/src/server/build_updates_client/reloading_manager.dart b/build_runner/lib/src/server/build_updates_client/reloading_manager.dart
new file mode 100644
index 0000000..969aaee
--- /dev/null
+++ b/build_runner/lib/src/server/build_updates_client/reloading_manager.dart
@@ -0,0 +1,118 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:collection';
+
+import 'package:graphs/graphs.dart' as graphs;
+
+import 'module.dart';
+
+class HotReloadFailedException implements Exception {
+  HotReloadFailedException(this._s);
+
+  @override
+  String toString() => "HotReloadFailedException: '$_s'";
+  final String _s;
+}
+
+/// Handles reloading order and hooks invocation
+class ReloadingManager {
+  final Future<Module> Function(String) _reloadModule;
+  final Module Function(String) _moduleLibraries;
+  final void Function() _reloadPage;
+  final List<String> Function(String moduleId) _moduleParents;
+  final Iterable<String> Function() _allModules;
+
+  final Map<String, int> _moduleOrdering = {};
+  SplayTreeSet<String> _dirtyModules;
+  Completer<void> _running = Completer()..complete();
+
+  int moduleTopologicalCompare(String module1, String module2) {
+    var topological =
+        Comparable.compare(_moduleOrdering[module2], _moduleOrdering[module1]);
+    // If modules are in cycle (same strongly connected component) compare their
+    // string id, to ensure total ordering for SplayTreeSet uniqueness.
+    return topological != 0 ? topological : module1.compareTo(module2);
+  }
+
+  void updateGraph() {
+    var allModules = _allModules();
+    var stronglyConnectedComponents =
+        graphs.stronglyConnectedComponents(allModules, _moduleParents);
+    _moduleOrdering.clear();
+    for (var i = 0; i < stronglyConnectedComponents.length; i++) {
+      for (var module in stronglyConnectedComponents[i]) {
+        _moduleOrdering[module] = i;
+      }
+    }
+  }
+
+  ReloadingManager(this._reloadModule, this._moduleLibraries, this._reloadPage,
+      this._moduleParents, this._allModules) {
+    _dirtyModules = SplayTreeSet(moduleTopologicalCompare);
+  }
+
+  Future<void> reload(List<String> modules) async {
+    _dirtyModules.addAll(modules);
+
+    // As function is async, it can potentially be called second time while
+    // first invocation is still running. In this case just mark as dirty and
+    // wait until loop from the first call will do the work
+    if (!_running.isCompleted) return await _running.future;
+    _running = Completer();
+
+    var reloadedModules = 0;
+
+    try {
+      while (_dirtyModules.isNotEmpty) {
+        var moduleId = _dirtyModules.first;
+        _dirtyModules.remove(moduleId);
+        ++reloadedModules;
+
+        var existing = _moduleLibraries(moduleId);
+        var data = existing.onDestroy();
+
+        var newVersion = await _reloadModule(moduleId);
+        var success = newVersion.onSelfUpdate(data);
+        if (success == true) continue;
+        if (success == false) {
+          print("Module '$moduleId' is marked as unreloadable. "
+              'Firing full page reload.');
+          _reloadPage();
+          _running.complete();
+          return;
+        }
+
+        var parentIds = _moduleParents(moduleId);
+        if (parentIds == null || parentIds.isEmpty) {
+          print("Module reloading wasn't handled by any of parents. "
+              'Firing full page reload.');
+          _reloadPage();
+          _running.complete();
+          return;
+        }
+        parentIds.sort(moduleTopologicalCompare);
+        for (var parentId in parentIds) {
+          var parentModule = _moduleLibraries(parentId);
+          success = parentModule.onChildUpdate(moduleId, newVersion, data);
+          if (success == true) continue;
+          if (success == false) {
+            print("Module '$moduleId' is marked as unreloadable. "
+                'Firing full page reload.');
+            _reloadPage();
+            _running.complete();
+            return;
+          }
+          _dirtyModules.add(parentId);
+        }
+      }
+      print('$reloadedModules modules were hot-reloaded.');
+    } on HotReloadFailedException catch (e) {
+      print('Error during script reloading. Firing full page reload. $e');
+      _reloadPage();
+    }
+    _running.complete();
+  }
+}
diff --git a/build_runner/lib/src/server/path_to_asset_id.dart b/build_runner/lib/src/server/path_to_asset_id.dart
new file mode 100644
index 0000000..3169b0f
--- /dev/null
+++ b/build_runner/lib/src/server/path_to_asset_id.dart
@@ -0,0 +1,24 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'package:build/build.dart';
+import 'package:path/path.dart' as p;
+
+AssetId pathToAssetId(
+    String rootPackage, String rootDir, List<String> pathSegments) {
+  var packagesIndex = pathSegments.indexOf('packages');
+  rootDir ??= '';
+  return packagesIndex >= 0
+      ? AssetId(pathSegments[packagesIndex + 1],
+          p.join('lib', p.joinAll(pathSegments.sublist(packagesIndex + 2))))
+      : AssetId(rootPackage, p.joinAll([rootDir].followedBy(pathSegments)));
+}
+
+/// Returns null for paths that neither a lib nor starts from a rootDir
+String assetIdToPath(AssetId assetId, String rootDir) =>
+    assetId.path.startsWith('lib/')
+        ? assetId.path.replaceFirst('lib/', 'packages/${assetId.package}/')
+        : assetId.path.startsWith('$rootDir/')
+            ? assetId.path.substring(rootDir.length + 1)
+            : null;
diff --git a/build_runner/lib/src/server/server.dart b/build_runner/lib/src/server/server.dart
new file mode 100644
index 0000000..9c568b8
--- /dev/null
+++ b/build_runner/lib/src/server/server.dart
@@ -0,0 +1,681 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:build/build.dart';
+import 'package:build_runner/src/entrypoint/options.dart';
+import 'package:build_runner_core/build_runner_core.dart';
+import 'package:build_runner_core/src/generate/performance_tracker.dart';
+import 'package:crypto/crypto.dart';
+import 'package:glob/glob.dart';
+import 'package:logging/logging.dart';
+import 'package:mime/mime.dart';
+import 'package:path/path.dart' as p;
+import 'package:shelf/shelf.dart' as shelf;
+import 'package:shelf_web_socket/shelf_web_socket.dart';
+import 'package:timing/timing.dart';
+import 'package:web_socket_channel/web_socket_channel.dart';
+
+import '../generate/watch_impl.dart';
+import 'asset_graph_handler.dart';
+import 'path_to_asset_id.dart';
+
+const _performancePath = r'$perf';
+final _graphPath = r'$graph';
+final _assetsDigestPath = r'$assetDigests';
+final _buildUpdatesProtocol = r'$buildUpdates';
+final entrypointExtensionMarker = '/* ENTRYPOINT_EXTENTION_MARKER */';
+
+final _logger = Logger('Serve');
+
+enum PerfSortOrder {
+  startTimeAsc,
+  startTimeDesc,
+  stopTimeAsc,
+  stopTimeDesc,
+  durationAsc,
+  durationDesc,
+  innerDurationAsc,
+  innerDurationDesc
+}
+
+ServeHandler createServeHandler(WatchImpl watch) {
+  var rootPackage = watch.packageGraph.root.name;
+  var assetGraphHanderCompleter = Completer<AssetGraphHandler>();
+  var assetHandlerCompleter = Completer<AssetHandler>();
+  watch.ready.then((_) async {
+    assetHandlerCompleter.complete(AssetHandler(watch.reader, rootPackage));
+    assetGraphHanderCompleter.complete(
+        AssetGraphHandler(watch.reader, rootPackage, watch.assetGraph));
+  });
+  return ServeHandler._(watch, assetHandlerCompleter.future,
+      assetGraphHanderCompleter.future, rootPackage);
+}
+
+class ServeHandler implements BuildState {
+  final WatchImpl _state;
+  BuildResult _lastBuildResult;
+  final String _rootPackage;
+
+  final Future<AssetHandler> _assetHandler;
+  final Future<AssetGraphHandler> _assetGraphHandler;
+
+  final BuildUpdatesWebSocketHandler _webSocketHandler;
+
+  ServeHandler._(this._state, this._assetHandler, this._assetGraphHandler,
+      this._rootPackage)
+      : _webSocketHandler = BuildUpdatesWebSocketHandler(_state) {
+    _state.buildResults.listen((result) {
+      _lastBuildResult = result;
+      _webSocketHandler.emitUpdateMessage(result);
+    }).onDone(_webSocketHandler.close);
+  }
+
+  @override
+  Future<BuildResult> get currentBuild => _state.currentBuild;
+
+  @override
+  Stream<BuildResult> get buildResults => _state.buildResults;
+
+  shelf.Handler handlerFor(String rootDir,
+      {bool logRequests, BuildUpdatesOption buildUpdates}) {
+    buildUpdates ??= BuildUpdatesOption.none;
+    logRequests ??= false;
+    if (p.url.split(rootDir).length != 1 || rootDir == '.') {
+      throw ArgumentError.value(
+        rootDir,
+        'directory',
+        'Only top level directories such as `web` or `test` can be served, got',
+      );
+    }
+    _state.currentBuild.then((_) {
+      // If the first build fails with a handled exception, we might not have
+      // an asset graph and can't do this check.
+      if (_state.assetGraph == null) return;
+      _warnForEmptyDirectory(rootDir);
+    });
+    var cascade = shelf.Cascade();
+    if (buildUpdates != BuildUpdatesOption.none) {
+      cascade = cascade.add(_webSocketHandler.createHandlerByRootDir(rootDir));
+    }
+    cascade =
+        cascade.add(_blockOnCurrentBuild).add((shelf.Request request) async {
+      if (request.url.path == _performancePath) {
+        return _performanceHandler(request);
+      }
+      if (request.url.path == _assetsDigestPath) {
+        return _assetsDigestHandler(request, rootDir);
+      }
+      if (request.url.path.startsWith(_graphPath)) {
+        var graphHandler = await _assetGraphHandler;
+        return await graphHandler.handle(
+            request.change(path: _graphPath), rootDir);
+      }
+      var assetHandler = await _assetHandler;
+      return assetHandler.handle(request, rootDir: rootDir);
+    });
+    var pipeline = shelf.Pipeline();
+    if (logRequests) {
+      pipeline = pipeline.addMiddleware(_logRequests);
+    }
+    switch (buildUpdates) {
+      case BuildUpdatesOption.liveReload:
+        pipeline = pipeline.addMiddleware(_injectLiveReloadClientCode);
+        break;
+      case BuildUpdatesOption.hotReload:
+        pipeline = pipeline.addMiddleware(_injectHotReloadClientCode);
+        break;
+      case BuildUpdatesOption.none:
+        break;
+    }
+    return pipeline.addHandler(cascade.handler);
+  }
+
+  Future<shelf.Response> _blockOnCurrentBuild(void _) async {
+    await currentBuild;
+    return shelf.Response.notFound('');
+  }
+
+  shelf.Response _performanceHandler(shelf.Request request) {
+    var hideSkipped = false;
+    var detailedSlices = false;
+    var slicesResolution = 5;
+    var sortOrder = PerfSortOrder.startTimeAsc;
+    var filter = request.url.queryParameters['filter'] ?? '';
+    if (request.url.queryParameters['hideSkipped']?.toLowerCase() == 'true') {
+      hideSkipped = true;
+    }
+    if (request.url.queryParameters['detailedSlices']?.toLowerCase() ==
+        'true') {
+      detailedSlices = true;
+    }
+    if (request.url.queryParameters.containsKey('slicesResolution')) {
+      slicesResolution =
+          int.parse(request.url.queryParameters['slicesResolution']);
+    }
+    if (request.url.queryParameters.containsKey('sortOrder')) {
+      sortOrder = PerfSortOrder
+          .values[int.parse(request.url.queryParameters['sortOrder'])];
+    }
+    return shelf.Response.ok(
+        _renderPerformance(_lastBuildResult.performance, hideSkipped,
+            detailedSlices, slicesResolution, sortOrder, filter),
+        headers: {HttpHeaders.contentTypeHeader: 'text/html'});
+  }
+
+  Future<shelf.Response> _assetsDigestHandler(
+      shelf.Request request, String rootDir) async {
+    var assertPathList =
+        (jsonDecode(await request.readAsString()) as List).cast<String>();
+    var rootPackage = _state.packageGraph.root.name;
+    var results = <String, String>{};
+    for (final path in assertPathList) {
+      try {
+        var assetId = pathToAssetId(rootPackage, rootDir, p.url.split(path));
+        var digest = await _state.reader.digest(assetId);
+        results[path] = digest.toString();
+      } on AssetNotFoundException {
+        results.remove(path);
+      }
+    }
+    return shelf.Response.ok(jsonEncode(results),
+        headers: {HttpHeaders.contentTypeHeader: 'application/json'});
+  }
+
+  void _warnForEmptyDirectory(String rootDir) {
+    if (!_state.assetGraph
+        .packageNodes(_rootPackage)
+        .any((n) => n.id.path.startsWith('$rootDir/'))) {
+      _logger.warning('Requested a server for `$rootDir` but this directory '
+          'has no assets in the build. You may need to add some sources or '
+          'include this directory in some target in your `build.yaml`');
+    }
+  }
+}
+
+/// Class that manages web socket connection handler to inform clients about
+/// build updates
+class BuildUpdatesWebSocketHandler {
+  final connectionsByRootDir = <String, List<WebSocketChannel>>{};
+  final shelf.Handler Function(Function, {Iterable<String> protocols})
+      _handlerFactory;
+  final _internalHandlers = <String, shelf.Handler>{};
+  final WatchImpl _state;
+
+  BuildUpdatesWebSocketHandler(this._state,
+      [this._handlerFactory = webSocketHandler]);
+
+  shelf.Handler createHandlerByRootDir(String rootDir) {
+    if (!_internalHandlers.containsKey(rootDir)) {
+      var closureForRootDir = (WebSocketChannel webSocket, String protocol) =>
+          _handleConnection(webSocket, protocol, rootDir);
+      _internalHandlers[rootDir] = _handlerFactory(closureForRootDir,
+          protocols: [_buildUpdatesProtocol]);
+    }
+    return _internalHandlers[rootDir];
+  }
+
+  Future emitUpdateMessage(BuildResult buildResult) async {
+    if (buildResult.status != BuildStatus.success) return;
+    var digests = <AssetId, String>{};
+    for (var assetId in buildResult.outputs) {
+      var digest = await _state.reader.digest(assetId);
+      digests[assetId] = digest.toString();
+    }
+    for (var rootDir in connectionsByRootDir.keys) {
+      var resultMap = <String, String>{};
+      for (var assetId in digests.keys) {
+        var path = assetIdToPath(assetId, rootDir);
+        if (path != null) {
+          resultMap[path] = digests[assetId];
+        }
+      }
+      for (var connection in connectionsByRootDir[rootDir]) {
+        connection.sink.add(jsonEncode(resultMap));
+      }
+    }
+  }
+
+  void _handleConnection(
+      WebSocketChannel webSocket, String protocol, String rootDir) async {
+    if (!connectionsByRootDir.containsKey(rootDir)) {
+      connectionsByRootDir[rootDir] = [];
+    }
+    connectionsByRootDir[rootDir].add(webSocket);
+    await webSocket.stream.drain();
+    connectionsByRootDir[rootDir].remove(webSocket);
+    if (connectionsByRootDir[rootDir].isEmpty) {
+      connectionsByRootDir.remove(rootDir);
+    }
+  }
+
+  Future<void> close() {
+    return Future.wait(connectionsByRootDir.values
+        .expand((x) => x)
+        .map((connection) => connection.sink.close()));
+  }
+}
+
+shelf.Handler Function(shelf.Handler) _injectBuildUpdatesClientCode(
+        String scriptName) =>
+    (innerHandler) {
+      return (shelf.Request request) async {
+        if (!request.url.path.endsWith('.js')) {
+          return innerHandler(request);
+        }
+        var response = await innerHandler(request);
+        // TODO: Find a way how to check and/or modify body without reading it
+        // whole.
+        var body = await response.readAsString();
+        if (body.startsWith(entrypointExtensionMarker)) {
+          body += _buildUpdatesInjectedJS(scriptName);
+          var originalEtag = response.headers[HttpHeaders.etagHeader];
+          if (originalEtag != null) {
+            var newEtag = base64.encode(md5.convert(body.codeUnits).bytes);
+            var newHeaders = Map.of(response.headers);
+            newHeaders[HttpHeaders.etagHeader] = newEtag;
+
+            if (request.headers[HttpHeaders.ifNoneMatchHeader] == newEtag) {
+              return shelf.Response.notModified(headers: newHeaders);
+            }
+
+            response = response.change(headers: newHeaders);
+          }
+        }
+        return response.change(body: body);
+      };
+    };
+
+final _injectHotReloadClientCode =
+    _injectBuildUpdatesClientCode('hot_reload_client.dart');
+
+final _injectLiveReloadClientCode =
+    _injectBuildUpdatesClientCode('live_reload_client');
+
+/// Hot-/live- reload config
+///
+/// Listen WebSocket for updates in build results
+String _buildUpdatesInjectedJS(String scriptName) => '''\n
+// Injected by build_runner for build updates support
+window.\$dartLoader.forceLoadModule('packages/build_runner/src/server/build_updates_client/$scriptName');
+''';
+
+class AssetHandler {
+  final FinalizedReader _reader;
+  final String _rootPackage;
+
+  final _typeResolver = MimeTypeResolver();
+
+  AssetHandler(this._reader, this._rootPackage);
+
+  Future<shelf.Response> handle(shelf.Request request, {String rootDir}) =>
+      (request.url.path.endsWith('/') || request.url.path.isEmpty)
+          ? _handle(
+              request.headers,
+              pathToAssetId(
+                  _rootPackage,
+                  rootDir,
+                  request.url.pathSegments
+                      .followedBy(const ['index.html']).toList()),
+              fallbackToDirectoryList: true)
+          : _handle(request.headers,
+              pathToAssetId(_rootPackage, rootDir, request.url.pathSegments));
+
+  Future<shelf.Response> _handle(
+      Map<String, String> requestHeaders, AssetId assetId,
+      {bool fallbackToDirectoryList = false}) async {
+    try {
+      if (!await _reader.canRead(assetId)) {
+        var reason = await _reader.unreadableReason(assetId);
+        switch (reason) {
+          case UnreadableReason.failed:
+            return shelf.Response.internalServerError(
+                body: 'Build failed for $assetId');
+          case UnreadableReason.notOutput:
+            return shelf.Response.notFound('$assetId was not output');
+          case UnreadableReason.notFound:
+            if (fallbackToDirectoryList) {
+              return shelf.Response.notFound(await _findDirectoryList(assetId));
+            }
+            return shelf.Response.notFound('Not Found');
+          default:
+            return shelf.Response.notFound('Not Found');
+        }
+      }
+    } on ArgumentError catch (_) {
+      return shelf.Response.notFound('Not Found');
+    }
+
+    var etag = base64.encode((await _reader.digest(assetId)).bytes);
+    var contentType = _typeResolver.lookup(assetId.path);
+    if (contentType == 'text/x-dart') contentType += '; charset=utf-8';
+    var headers = {
+      HttpHeaders.contentTypeHeader: contentType,
+      HttpHeaders.etagHeader: etag,
+      // We always want this revalidated, which requires specifying both
+      // max-age=0 and must-revalidate.
+      //
+      // See spec https://goo.gl/Lhvttg for more info about this header.
+      HttpHeaders.cacheControlHeader: 'max-age=0, must-revalidate',
+    };
+
+    if (requestHeaders[HttpHeaders.ifNoneMatchHeader] == etag) {
+      // This behavior is still useful for cases where a file is hit
+      // without a cache-busting query string.
+      return shelf.Response.notModified(headers: headers);
+    }
+
+    var bytes = await _reader.readAsBytes(assetId);
+    headers[HttpHeaders.contentLengthHeader] = '${bytes.length}';
+    return shelf.Response.ok(bytes, headers: headers);
+  }
+
+  Future<String> _findDirectoryList(AssetId from) async {
+    var directoryPath = p.url.dirname(from.path);
+    var glob = p.url.join(directoryPath, '*');
+    var result =
+        await _reader.findAssets(Glob(glob)).map((a) => a.path).toList();
+    var message = StringBuffer('Could not find ${from.path}');
+    if (result.isEmpty) {
+      message.write(' or any files in $directoryPath. ');
+    } else {
+      message
+        ..write('. $directoryPath contains:')
+        ..writeAll(result, '\n')
+        ..writeln();
+    }
+    message
+        .write(' See https://github.com/dart-lang/build/blob/master/docs/faq.md'
+            '#why-cant-i-see-a-file-i-know-exists');
+    return '$message';
+  }
+}
+
+String _renderPerformance(
+    BuildPerformance performance,
+    bool hideSkipped,
+    bool detailedSlices,
+    int slicesResolution,
+    PerfSortOrder sortOrder,
+    String filter) {
+  try {
+    var rows = StringBuffer();
+    final resolution = Duration(milliseconds: slicesResolution);
+    var count = 0,
+        maxSlices = 1,
+        max = 0,
+        min = performance.stopTime.millisecondsSinceEpoch -
+            performance.startTime.millisecondsSinceEpoch;
+
+    void writeRow(BuilderActionPerformance action,
+        BuilderActionStagePerformance stage, TimeSlice slice) {
+      var actionKey = '${action.builderKey}:${action.primaryInput}';
+      var tooltip = '<div class=perf-tooltip>'
+          '<p><b>Builder:</b> ${action.builderKey}</p>'
+          '<p><b>Input:</b> ${action.primaryInput}</p>'
+          '<p><b>Stage:</b> ${stage.label}</p>'
+          '<p><b>Stage time:</b> '
+          '${stage.startTime.difference(performance.startTime).inMilliseconds / 1000}s - '
+          '${stage.stopTime.difference(performance.startTime).inMilliseconds / 1000}s</p>'
+          '<p><b>Stage real duration:</b> ${stage.duration.inMilliseconds / 1000} seconds</p>'
+          '<p><b>Stage user duration:</b> ${stage.innerDuration.inMilliseconds / 1000} seconds</p>';
+      if (slice != stage) {
+        tooltip += '<p><b>Slice time:</b> '
+            '${slice.startTime.difference(performance.startTime).inMilliseconds / 1000}s - '
+            '${slice.stopTime.difference(performance.startTime).inMilliseconds / 1000}s</p>'
+            '<p><b>Slice duration:</b> ${slice.duration.inMilliseconds / 1000} seconds</p>';
+      }
+      tooltip += '</div>';
+      var start = slice.startTime.millisecondsSinceEpoch -
+          performance.startTime.millisecondsSinceEpoch;
+      var end = slice.stopTime.millisecondsSinceEpoch -
+          performance.startTime.millisecondsSinceEpoch;
+
+      if (min > start) min = start;
+      if (max < end) max = end;
+
+      rows.writeln(
+          '          ["$actionKey", "${stage.label}", "$tooltip", $start, $end],');
+      ++count;
+    }
+
+    final filterRegex = filter.isNotEmpty ? RegExp(filter) : null;
+
+    final actions = performance.actions
+        .where((action) =>
+            !hideSkipped ||
+            action.stages.any((stage) => stage.label == 'Build'))
+        .where((action) =>
+            filterRegex == null ||
+            filterRegex.hasMatch('${action.builderKey}:${action.primaryInput}'))
+        .toList();
+
+    int Function(BuilderActionPerformance, BuilderActionPerformance) comparator;
+    switch (sortOrder) {
+      case PerfSortOrder.startTimeAsc:
+        comparator = (a1, a2) => a1.startTime.compareTo(a2.startTime);
+        break;
+      case PerfSortOrder.startTimeDesc:
+        comparator = (a1, a2) => a2.startTime.compareTo(a1.startTime);
+        break;
+      case PerfSortOrder.stopTimeAsc:
+        comparator = (a1, a2) => a1.stopTime.compareTo(a2.stopTime);
+        break;
+      case PerfSortOrder.stopTimeDesc:
+        comparator = (a1, a2) => a2.stopTime.compareTo(a1.stopTime);
+        break;
+      case PerfSortOrder.durationAsc:
+        comparator = (a1, a2) => a1.duration.compareTo(a2.duration);
+        break;
+      case PerfSortOrder.durationDesc:
+        comparator = (a1, a2) => a2.duration.compareTo(a1.duration);
+        break;
+      case PerfSortOrder.innerDurationAsc:
+        comparator = (a1, a2) => a1.innerDuration.compareTo(a2.innerDuration);
+        break;
+      case PerfSortOrder.innerDurationDesc:
+        comparator = (a1, a2) => a2.innerDuration.compareTo(a1.innerDuration);
+        break;
+    }
+    actions.sort(comparator);
+
+    for (var action in actions) {
+      if (hideSkipped &&
+          !action.stages.any((stage) => stage.label == 'Build')) {
+        continue;
+      }
+      for (var stage in action.stages) {
+        if (!detailedSlices) {
+          writeRow(action, stage, stage);
+          continue;
+        }
+        var slices = stage.slices.fold<List<TimeSlice>>([], (list, slice) {
+          if (list.isNotEmpty &&
+              slice.startTime.difference(list.last.stopTime) < resolution) {
+            // concat with previous if gap less than resolution
+            list.last = TimeSlice(list.last.startTime, slice.stopTime);
+          } else {
+            if (list.length > 1 && list.last.duration < resolution) {
+              // remove previous if its duration less than resolution
+              list.last = slice;
+            } else {
+              list.add(slice);
+            }
+          }
+          return list;
+        });
+        if (slices.isNotEmpty) {
+          for (var slice in slices) {
+            writeRow(action, stage, slice);
+          }
+        } else {
+          writeRow(action, stage, stage);
+        }
+        if (maxSlices < slices.length) maxSlices = slices.length;
+      }
+    }
+    if (max - min < 1000) {
+      rows.writeln('          ['
+          '"https://github.com/google/google-visualization-issues/issues/2269"'
+          ', "", "", $min, ${min + 1000}]');
+    }
+    return '''
+  <html>
+    <head>
+      <script src="https://www.gstatic.com/charts/loader.js"></script>
+      <script>
+        google.charts.load('current', {'packages':['timeline']});
+        google.charts.setOnLoadCallback(drawChart);
+        function drawChart() {
+          var container = document.getElementById('timeline');
+          var chart = new google.visualization.Timeline(container);
+          var dataTable = new google.visualization.DataTable();
+
+          dataTable.addColumn({ type: 'string', id: 'ActionKey' });
+          dataTable.addColumn({ type: 'string', id: 'Stage' });
+          dataTable.addColumn({ type: 'string', role: 'tooltip', p: { html: true } });
+          dataTable.addColumn({ type: 'number', id: 'Start' });
+          dataTable.addColumn({ type: 'number', id: 'End' });
+          dataTable.addRows([
+  $rows
+          ]);
+
+          console.log('rendering', $count, 'blocks, max', $maxSlices,
+            'slices in stage, resolution', $slicesResolution, 'ms');
+          var options = {
+            tooltip: { isHtml: true }
+          };
+          var statusText = document.getElementById('status');
+          var timeoutId;
+          var updateFunc = function () {
+              if (timeoutId) {
+                  // don't schedule more than one at a time
+                  return;
+              }
+              statusText.innerText = 'Drawing table...';
+              console.time('draw-time');
+
+              timeoutId = setTimeout(function () {
+                  chart.draw(dataTable, options);
+                  console.timeEnd('draw-time');
+                  statusText.innerText = '';
+                  timeoutId = null;
+              });
+          };
+
+          updateFunc();
+
+          window.addEventListener('resize', updateFunc);
+        }
+      </script>
+      <style>
+      html, body {
+        width: 100%;
+        height: 100%;
+        margin: 0;
+      }
+
+      body {
+        display: flex;
+        flex-direction: column;
+      }
+
+      #timeline {
+        display: flex;
+        flex-direction: row;
+        flex: 1;
+      }
+      .controls-header p {
+        display: inline-block;
+        margin: 0.5em;
+      }
+      .perf-tooltip {
+        margin: 0.5em;
+      }
+      </style>
+    </head>
+    <body>
+      <form class="controls-header" action="/$_performancePath" onchange="this.submit()">
+        <p><label><input type="checkbox" name="hideSkipped" value="true" ${hideSkipped ? 'checked' : ''}> Hide Skipped Actions</label></p>
+        <p><label><input type="checkbox" name="detailedSlices" value="true" ${detailedSlices ? 'checked' : ''}> Show Async Slices</label></p>
+        <p>Sort by: <select name="sortOrder">
+          <option value="0" ${sortOrder.index == 0 ? 'selected' : ''}>Start Time Asc</option>
+          <option value="1" ${sortOrder.index == 1 ? 'selected' : ''}>Start Time Desc</option>
+          <option value="2" ${sortOrder.index == 2 ? 'selected' : ''}>Stop Time Asc</option>
+          <option value="3" ${sortOrder.index == 3 ? 'selected' : ''}>Stop Time Desc</option>
+          <option value="5" ${sortOrder.index == 4 ? 'selected' : ''}>Real Duration Asc</option>
+          <option value="5" ${sortOrder.index == 5 ? 'selected' : ''}>Real Duration Desc</option>
+          <option value="6" ${sortOrder.index == 6 ? 'selected' : ''}>User Duration Asc</option>
+          <option value="7" ${sortOrder.index == 7 ? 'selected' : ''}>User Duration Desc</option>
+        </select></p>
+        <p>Slices Resolution: <select name="slicesResolution">
+          <option value="0" ${slicesResolution == 0 ? 'selected' : ''}>0</option>
+          <option value="1" ${slicesResolution == 1 ? 'selected' : ''}>1</option>
+          <option value="3" ${slicesResolution == 3 ? 'selected' : ''}>3</option>
+          <option value="5" ${slicesResolution == 5 ? 'selected' : ''}>5</option>
+          <option value="10" ${slicesResolution == 10 ? 'selected' : ''}>10</option>
+          <option value="15" ${slicesResolution == 15 ? 'selected' : ''}>15</option>
+          <option value="20" ${slicesResolution == 20 ? 'selected' : ''}>20</option>
+          <option value="25" ${slicesResolution == 25 ? 'selected' : ''}>25</option>
+        </select></p>
+        <p>Filter (RegExp): <input type="text" name="filter" value="$filter"></p>
+        <p id="status"></p>
+      </form>
+      <div id="timeline"></div>
+    </body>
+  </html>
+  ''';
+  } on UnimplementedError catch (_) {
+    return _enablePerformanceTracking;
+  } on UnsupportedError catch (_) {
+    return _enablePerformanceTracking;
+  }
+}
+
+final _enablePerformanceTracking = '''
+<html>
+  <body>
+    <p>
+      Performance information not available, you must pass the
+      `--track-performance` command line arg to enable performance tracking.
+    </p>
+  <body>
+</html>
+''';
+
+/// [shelf.Middleware] that logs all requests, inspired by [shelf.logRequests].
+shelf.Handler _logRequests(shelf.Handler innerHandler) {
+  return (shelf.Request request) {
+    var startTime = DateTime.now();
+    var watch = Stopwatch()..start();
+
+    return Future.sync(() => innerHandler(request)).then((response) {
+      var logFn = response.statusCode >= 500 ? _logger.warning : _logger.info;
+      var msg = _getMessage(startTime, response.statusCode,
+          request.requestedUri, request.method, watch.elapsed);
+      logFn(msg);
+      return response;
+    }, onError: (dynamic error, StackTrace stackTrace) {
+      if (error is shelf.HijackException) throw error;
+      var msg = _getMessage(
+          startTime, 500, request.requestedUri, request.method, watch.elapsed);
+      _logger.severe('$msg\r\n$error\r\n$stackTrace', true);
+      throw error;
+    });
+  };
+}
+
+String _getMessage(DateTime requestTime, int statusCode, Uri requestedUri,
+    String method, Duration elapsedTime) {
+  return '${requestTime.toIso8601String()} '
+      '${humanReadable(elapsedTime)} '
+      '$method [$statusCode] '
+      '${requestedUri.path}${_formatQuery(requestedUri.query)}\r\n';
+}
+
+String _formatQuery(String query) {
+  return query == '' ? '' : '?$query';
+}
diff --git a/build_runner/lib/src/watcher/asset_change.dart b/build_runner/lib/src/watcher/asset_change.dart
new file mode 100644
index 0000000..7796ede
--- /dev/null
+++ b/build_runner/lib/src/watcher/asset_change.dart
@@ -0,0 +1,51 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'package:build/build.dart';
+import 'package:path/path.dart' as p;
+import 'package:watcher/watcher.dart';
+
+import 'package:build_runner_core/build_runner_core.dart';
+
+/// Represents an [id] that was modified on disk as a result of [type].
+class AssetChange {
+  /// Asset that was changed.
+  final AssetId id;
+
+  /// What caused the asset to be detected as changed.
+  final ChangeType type;
+
+  const AssetChange(this.id, this.type);
+
+  /// Creates a new change record in [package] from an existing watcher [event].
+  factory AssetChange.fromEvent(PackageNode package, WatchEvent event) {
+    return AssetChange(
+      AssetId(
+        package.name,
+        _normalizeRelativePath(package, event),
+      ),
+      event.type,
+    );
+  }
+
+  static String _normalizeRelativePath(PackageNode package, WatchEvent event) {
+    final pkgPath = package.path;
+    var absoluteEventPath =
+        p.isAbsolute(event.path) ? event.path : p.absolute(event.path);
+    if (!p.isWithin(pkgPath, absoluteEventPath)) {
+      throw ArgumentError('"$absoluteEventPath" is not in "$pkgPath".');
+    }
+    return p.relative(absoluteEventPath, from: pkgPath);
+  }
+
+  @override
+  int get hashCode => id.hashCode ^ type.hashCode;
+
+  @override
+  bool operator ==(Object other) =>
+      other is AssetChange && other.id == id && other.type == type;
+
+  @override
+  String toString() => 'AssetChange {asset: $id, type: $type}';
+}
diff --git a/build_runner/lib/src/watcher/change_filter.dart b/build_runner/lib/src/watcher/change_filter.dart
new file mode 100644
index 0000000..3da8192
--- /dev/null
+++ b/build_runner/lib/src/watcher/change_filter.dart
@@ -0,0 +1,39 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS 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 'package:build/build.dart';
+import 'package:build_runner/src/watcher/asset_change.dart';
+import 'package:build_runner_core/build_runner_core.dart';
+import 'package:build_runner_core/src/asset_graph/graph.dart';
+import 'package:build_runner_core/src/asset_graph/node.dart';
+import 'package:watcher/watcher.dart';
+
+/// Returns if a given asset change should be considered for building.
+bool shouldProcess(
+  AssetChange change,
+  AssetGraph assetGraph,
+  BuildOptions buildOptions,
+  bool willCreateOutputDir,
+  Set<AssetId> expectedDeletes,
+) {
+  if (_isCacheFile(change) && !assetGraph.contains(change.id)) return false;
+  var node = assetGraph.get(change.id);
+  if (node != null) {
+    if (!willCreateOutputDir && !node.isInteresting) return false;
+    if (_isAddOrEditOnGeneratedFile(node, change.type)) return false;
+  } else {
+    if (change.type != ChangeType.ADD) return false;
+    if (!buildOptions.targetGraph.anyMatchesAsset(change.id)) return false;
+  }
+  if (_isExpectedDelete(change, expectedDeletes)) return false;
+  return true;
+}
+
+bool _isAddOrEditOnGeneratedFile(AssetNode node, ChangeType changeType) =>
+    node.isGenerated && changeType != ChangeType.REMOVE;
+
+bool _isCacheFile(AssetChange change) => change.id.path.startsWith(cacheDir);
+
+bool _isExpectedDelete(AssetChange change, Set<AssetId> expectedDeletes) =>
+    expectedDeletes.remove(change.id);
diff --git a/build_runner/lib/src/watcher/collect_changes.dart b/build_runner/lib/src/watcher/collect_changes.dart
new file mode 100644
index 0000000..eb6b8d8
--- /dev/null
+++ b/build_runner/lib/src/watcher/collect_changes.dart
@@ -0,0 +1,53 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS 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 'package:build/build.dart';
+import 'package:build_runner/src/watcher/asset_change.dart';
+import 'package:watcher/watcher.dart';
+
+/// Merges [AssetChange] events.
+///
+/// For example, if an asset was added then immediately deleted, no event will
+/// be recorded for the given asset.
+Map<AssetId, ChangeType> collectChanges(List<List<AssetChange>> changes) {
+  var changeMap = <AssetId, ChangeType>{};
+  for (var change in changes.expand((l) => l)) {
+    var originalChangeType = changeMap[change.id];
+    if (originalChangeType != null) {
+      switch (originalChangeType) {
+        case ChangeType.ADD:
+          if (change.type == ChangeType.REMOVE) {
+            // ADD followed by REMOVE, just remove the change.
+            changeMap.remove(change.id);
+          }
+          break;
+        case ChangeType.REMOVE:
+          if (change.type == ChangeType.ADD) {
+            // REMOVE followed by ADD, convert to a MODIFY
+            changeMap[change.id] = ChangeType.MODIFY;
+          } else if (change.type == ChangeType.MODIFY) {
+            // REMOVE followed by MODIFY isn't sensible, just throw.
+            throw StateError(
+                'Internal error, got REMOVE event followed by MODIFY event for '
+                '${change.id}.');
+          }
+          break;
+        case ChangeType.MODIFY:
+          if (change.type == ChangeType.REMOVE) {
+            // MODIFY followed by REMOVE, convert to REMOVE
+            changeMap[change.id] = change.type;
+          } else if (change.type == ChangeType.ADD) {
+            // MODIFY followed by ADD isn't sensible, just throw.
+            throw StateError(
+                'Internal error, got MODIFY event followed by ADD event for '
+                '${change.id}.');
+          }
+          break;
+      }
+    } else {
+      changeMap[change.id] = change.type;
+    }
+  }
+  return changeMap;
+}
diff --git a/build_runner/lib/src/watcher/delete_writer.dart b/build_runner/lib/src/watcher/delete_writer.dart
new file mode 100644
index 0000000..e8092f5
--- /dev/null
+++ b/build_runner/lib/src/watcher/delete_writer.dart
@@ -0,0 +1,32 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:convert';
+
+import 'package:build/build.dart';
+import 'package:build_runner_core/build_runner_core.dart';
+
+/// A [RunnerAssetWriter] that forwards delete events to [_onDelete];
+class OnDeleteWriter implements RunnerAssetWriter {
+  final RunnerAssetWriter _writer;
+  final void Function(AssetId id) _onDelete;
+
+  OnDeleteWriter(this._writer, this._onDelete);
+
+  @override
+  Future delete(AssetId id) {
+    _onDelete(id);
+    return _writer.delete(id);
+  }
+
+  @override
+  Future writeAsBytes(AssetId id, List<int> bytes) =>
+      _writer.writeAsBytes(id, bytes);
+
+  @override
+  Future writeAsString(AssetId id, String contents,
+          {Encoding encoding = utf8}) =>
+      _writer.writeAsString(id, contents, encoding: encoding);
+}
diff --git a/build_runner/lib/src/watcher/graph_watcher.dart b/build_runner/lib/src/watcher/graph_watcher.dart
new file mode 100644
index 0000000..816d1dd
--- /dev/null
+++ b/build_runner/lib/src/watcher/graph_watcher.dart
@@ -0,0 +1,95 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:io';
+
+import 'package:async/async.dart';
+import 'package:build_runner_core/build_runner_core.dart';
+import 'package:logging/logging.dart';
+
+import 'asset_change.dart';
+import 'node_watcher.dart';
+
+PackageNodeWatcher _default(PackageNode node) => PackageNodeWatcher(node);
+
+/// Allows watching an entire graph of packages to schedule rebuilds.
+class PackageGraphWatcher {
+  // TODO: Consider pulling logging out and providing hooks instead.
+  final Logger _logger;
+  final PackageNodeWatcher Function(PackageNode) _strategy;
+  final PackageGraph _graph;
+
+  final _readyCompleter = Completer<void>();
+  Future<void> get ready => _readyCompleter.future;
+
+  bool _isWatching = false;
+
+  /// Creates a new watcher for a [PackageGraph].
+  ///
+  /// May optionally specify a [watch] strategy, otherwise will attempt a
+  /// reasonable default based on the current platform.
+  PackageGraphWatcher(
+    this._graph, {
+    Logger logger,
+    PackageNodeWatcher Function(PackageNode node) watch,
+  })  : _logger = logger ?? Logger('build_runner'),
+        _strategy = watch ?? _default;
+
+  /// Returns a stream of records for assets that changed in the package graph.
+  Stream<AssetChange> watch() {
+    assert(!_isWatching);
+    _isWatching = true;
+    return LazyStream(
+        () => logTimedSync(_logger, 'Setting up file watchers', _watch));
+  }
+
+  Stream<AssetChange> _watch() {
+    final allWatchers = _graph.allPackages.values
+        .where((node) => node.dependencyType == DependencyType.path)
+        .map(_strategy)
+        .toList();
+    final filteredEvents = allWatchers
+        .map((w) => w
+                .watch()
+                .where(_nestedPathFilter(w.node))
+                .handleError((dynamic e, StackTrace s) {
+              _logger.severe(
+                  'Error from directory watcher for package:${w.node.name}\n\n'
+                  'If you see this consistently then it is recommended that '
+                  'you enable the polling file watcher with '
+                  '--use-polling-watcher.');
+              throw e;
+            }))
+        .toList();
+    // Asynchronously complete the `_readyCompleter` once all the watchers
+    // are done.
+    () async {
+      await Future.wait(
+          allWatchers.map((nodeWatcher) => nodeWatcher.watcher.ready));
+      _readyCompleter.complete();
+    }();
+    return StreamGroup.merge(filteredEvents);
+  }
+
+  bool Function(AssetChange) _nestedPathFilter(PackageNode rootNode) {
+    final ignorePaths = _nestedPaths(rootNode);
+    return (change) => !ignorePaths.any(change.id.path.startsWith);
+  }
+
+  // Returns a set of all package paths that are "nested" within a node.
+  //
+  // This allows the watcher to optimize and avoid duplicate events.
+  List<String> _nestedPaths(PackageNode rootNode) {
+    return _graph.allPackages.values
+        .where((node) {
+          return node.path.length > rootNode.path.length &&
+              node.path.startsWith(rootNode.path);
+        })
+        .map((node) =>
+            node.path.substring(rootNode.path.length + 1) +
+            Platform.pathSeparator)
+        .toList();
+  }
+}
diff --git a/build_runner/lib/src/watcher/node_watcher.dart b/build_runner/lib/src/watcher/node_watcher.dart
new file mode 100644
index 0000000..ebfd119
--- /dev/null
+++ b/build_runner/lib/src/watcher/node_watcher.dart
@@ -0,0 +1,40 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'package:build_runner_core/build_runner_core.dart';
+import 'package:watcher/watcher.dart';
+
+import 'asset_change.dart';
+
+Watcher _default(String path) => Watcher(path);
+
+/// Allows watching significant files and directories in a given package.
+class PackageNodeWatcher {
+  final Watcher Function(String) _strategy;
+  final PackageNode node;
+
+  /// The actual watcher instance.
+  Watcher _watcher;
+  Watcher get watcher => _watcher;
+
+  /// Creates a new watcher for a [PackageNode].
+  ///
+  /// May optionally specify a [watch] strategy, otherwise will attempt a
+  /// reasonable default based on the current platform and the type of path
+  /// (i.e. a file versus directory).
+  PackageNodeWatcher(
+    this.node, {
+    Watcher Function(String path) watch,
+  }) : _strategy = watch ?? _default;
+
+  /// Returns a stream of records for assets that change recursively.
+  Stream<AssetChange> watch() {
+    assert(_watcher == null);
+    _watcher = _strategy(node.path);
+    final events = _watcher.events;
+    return events.map((e) => AssetChange.fromEvent(node, e));
+  }
+}
diff --git a/build_runner/mono_pkg.yaml b/build_runner/mono_pkg.yaml
new file mode 100644
index 0000000..f61139b
--- /dev/null
+++ b/build_runner/mono_pkg.yaml
@@ -0,0 +1,16 @@
+dart:
+  - dev
+
+stages:
+  - analyze_and_format:
+    - group:
+      - dartfmt: sdk
+      - dartanalyzer: --fatal-infos --fatal-warnings .
+  - unit_test:
+    - test: -x integration
+  - e2e_test:
+    - test: -t integration --total-shards 5 --shard-index 0
+    - test: -t integration --total-shards 5 --shard-index 1
+    - test: -t integration --total-shards 5 --shard-index 2
+    - test: -t integration --total-shards 5 --shard-index 3
+    - test: -t integration --total-shards 5 --shard-index 4
diff --git a/build_runner/pubspec.yaml b/build_runner/pubspec.yaml
new file mode 100644
index 0000000..9429017
--- /dev/null
+++ b/build_runner/pubspec.yaml
@@ -0,0 +1,53 @@
+name: build_runner
+version: 1.7.4
+description: Tools to write binaries that run builders.
+homepage: https://github.com/dart-lang/build/tree/master/build_runner
+
+environment:
+  sdk: ">=2.6.0 <3.0.0"
+
+dependencies:
+  args: ">=1.4.0 <2.0.0"
+  async: ">=1.13.3 <3.0.0"
+  build: ">=1.0.0 <1.3.0"
+  build_config: ">=0.4.1 <0.4.3"
+  build_daemon: ^2.1.0
+  build_resolvers: "^1.0.0"
+  build_runner_core: ^4.0.0
+  code_builder: ">2.3.0 <4.0.0"
+  collection: ^1.14.0
+  crypto: ">=0.9.2 <3.0.0"
+  dart_style: ^1.0.0
+  glob: ^1.1.0
+  graphs: ^0.2.0
+  http_multi_server: ^2.1.0
+  io: ^0.3.0
+  js: ^0.6.1+1
+  logging: ^0.11.2
+  meta: ^1.1.0
+  mime: ^0.9.3+3
+  path: ^1.1.0
+  pedantic: ^1.0.0
+  pool: ^1.0.0
+  pub_semver: ^1.4.0
+  pubspec_parse: ^0.1.0
+  shelf: ">=0.6.5 <0.8.0"
+  shelf_web_socket: ^0.2.2+4
+  stack_trace: ^1.9.0
+  stream_transform: ">=0.0.20 <2.0.0"
+  timing: ^0.1.1
+  watcher: ^0.9.7
+  web_socket_channel: ^1.0.9
+  yaml: ^2.1.0
+
+dev_dependencies:
+  build_test: ^0.10.0
+  build_web_compilers: ^2.0.0
+  mockito: ^4.0.0
+  package_resolver: ^1.0.2
+  stream_channel: ">=1.6.0 <3.0.0"
+  test: ^1.3.3
+  test_descriptor: ^1.0.0
+  test_process: ^1.0.0
+  _test_common:
+    path: ../_test_common
diff --git a/build_runner/tool/builders.dart b/build_runner/tool/builders.dart
new file mode 100644
index 0000000..c8eb2ff
--- /dev/null
+++ b/build_runner/tool/builders.dart
@@ -0,0 +1,30 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS 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 'package:build/build.dart';
+
+Builder copyCompiledJs(void _) => _CopyBuilder();
+
+/// A Builder to copy compiled dart2js output to source.
+class _CopyBuilder extends Builder {
+  @override
+  final buildExtensions = {
+    'web/graph_viz_main.dart.js': ['lib/src/server/graph_viz_main.dart.js'],
+    'web/hot_reload_client.dart.js': [
+      'lib/src/server/build_updates_client/hot_reload_client.dart.js'
+    ]
+  };
+
+  @override
+  void build(BuildStep buildStep) {
+    if (!buildExtensions.containsKey(buildStep.inputId.path)) {
+      throw StateError('Unexpected input for `CopyBuilder` '
+          'expected only ${buildExtensions.keys}');
+    }
+    buildStep.writeAsString(
+        AssetId(buildStep.inputId.package,
+            buildExtensions[buildStep.inputId.path].single),
+        buildStep.readAsString(buildStep.inputId));
+  }
+}
diff --git a/build_runner/web/graph_viz_main.dart b/build_runner/web/graph_viz_main.dart
new file mode 100644
index 0000000..b2d5e54
--- /dev/null
+++ b/build_runner/web/graph_viz_main.dart
@@ -0,0 +1,74 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:convert';
+import 'dart:html';
+import 'dart:js' as js;
+
+final _graphReference = js.context[r'$build'];
+final _details = document.getElementById('details');
+
+void main() async {
+  var filterBox = document.getElementById('filter') as InputElement;
+  var searchBox = document.getElementById('searchbox') as InputElement;
+  var searchForm = document.getElementById('searchform');
+  searchForm.onSubmit.listen((e) {
+    e.preventDefault();
+    _focus(searchBox.value.trim(),
+        filter: filterBox.value.isNotEmpty ? filterBox.value : null);
+    return null;
+  });
+  _graphReference.callMethod('initializeGraph', [_focus]);
+}
+
+void _error(String message, [Object error, StackTrace stack]) {
+  var msg = [message, error, stack].where((e) => e != null).join('\n');
+  _details.innerHtml = '<pre>$msg</pre>';
+}
+
+Future _focus(String query, {String filter}) async {
+  if (query.isEmpty) {
+    _error('Provide content in the query.');
+    return;
+  }
+
+  Map nodeInfo;
+  var queryParams = {'q': query};
+  if (filter != null) queryParams['f'] = filter;
+  var uri = Uri(queryParameters: queryParams);
+  try {
+    nodeInfo = json.decode(await HttpRequest.getString(uri.toString()))
+        as Map<String, dynamic>;
+  } catch (e, stack) {
+    var msg = 'Error requesting query "$query".';
+    if (e is ProgressEvent) {
+      var target = e.target;
+      if (target is HttpRequest) {
+        msg = [
+          msg,
+          '${target.status} ${target.statusText}',
+          target.responseText
+        ].join('\n');
+      }
+      _error(msg);
+    } else {
+      _error(msg, e, stack);
+    }
+    return;
+  }
+
+  var graphData = {'edges': nodeInfo['edges'], 'nodes': nodeInfo['nodes']};
+  _graphReference.callMethod('setData', [js.JsObject.jsify(graphData)]);
+  var primaryNode = nodeInfo['primary'];
+  _details.innerHtml = '<strong>ID:</strong> ${primaryNode['id']} <br />'
+      '<strong>Type:</strong> ${primaryNode['type']}<br />'
+      '<strong>Hidden:</strong> ${primaryNode['hidden']} <br />'
+      '<strong>State:</strong> ${primaryNode['state']} <br />'
+      '<strong>Was Output:</strong> ${primaryNode['wasOutput']} <br />'
+      '<strong>Failed:</strong> ${primaryNode['isFailure']} <br />'
+      '<strong>Phase:</strong> ${primaryNode['phaseNumber']} <br />'
+      '<strong>Glob:</strong> ${primaryNode['glob']}<br />'
+      '<strong>Last Digest:</strong> ${primaryNode['lastKnownDigest']}<br />';
+}
diff --git a/build_runner/web/hot_reload_client.dart b/build_runner/web/hot_reload_client.dart
new file mode 100644
index 0000000..5735341
--- /dev/null
+++ b/build_runner/web/hot_reload_client.dart
@@ -0,0 +1,203 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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.
+
+@JS()
+library hot_reload_client;
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:html';
+
+import 'package:js/js.dart';
+import 'package:js/js_util.dart';
+
+import 'package:build_runner/src/server/build_updates_client/module.dart';
+import 'package:build_runner/src/server/build_updates_client/reload_handler.dart';
+import 'package:build_runner/src/server/build_updates_client/reloading_manager.dart';
+
+final _assetsDigestPath = r'$assetDigests';
+final _buildUpdatesProtocol = r'$buildUpdates';
+
+@anonymous
+@JS()
+abstract class HotReloadableLibrary {
+  /// Implement this function with any code to release resources before destroy.
+  ///
+  /// Any object returned from this function will be passed to update hooks. Use
+  /// it to save any state you need to be preserved between hot reloadings.
+  /// Try do not use any custom types here, as it might prevent their code from
+  /// reloading. Better serialise to JSON or plain types.
+  ///
+  /// This function will be called on old version of module before unloading.
+  @JS()
+  external Object hot$onDestroy();
+
+  /// Implement this function to handle update of the module itself.
+  ///
+  /// May return nullable bool. To indicate that reload completes successfully
+  /// return true. To indicate that hot-reload is undoable return false - this
+  /// will lead to full page reload. If null returned, reloading will be
+  /// propagated to parent.
+  ///
+  /// If any state was saved from previous version, it will be passed to [data].
+  ///
+  /// This function will be called on new version of module after reloading.
+  @JS()
+  external bool hot$onSelfUpdate([Object data]);
+
+  /// Implement this function to handle update of child modules.
+  ///
+  /// May return nullable bool. To indicate that reload of child completes
+  /// successfully return true. To indicate that hot-reload is undoable for this
+  /// child return false - this will lead to full page reload. If null returned,
+  /// reloading will be propagated to current module itself.
+  ///
+  /// The name of the child will be provided in [childId]. New version of child
+  /// module object will be provided in [child].
+  /// If any state was saved from previous version, it will be passed to [data].
+  ///
+  /// This function will be called on old version of module current after child
+  /// reloading.
+  @JS()
+  external bool hot$onChildUpdate(String childId, HotReloadableLibrary child,
+      [Object data]);
+}
+
+class LibraryWrapper implements Library {
+  final HotReloadableLibrary _internal;
+
+  LibraryWrapper(this._internal);
+
+  @override
+  Object onDestroy() {
+    if (_internal != null && hasProperty(_internal, r'hot$onDestroy')) {
+      return _internal.hot$onDestroy();
+    }
+    return null;
+  }
+
+  @override
+  bool onSelfUpdate([Object data]) {
+    if (_internal != null && hasProperty(_internal, r'hot$onSelfUpdate')) {
+      return _internal.hot$onSelfUpdate(data);
+    }
+    // ignore: avoid_returning_null
+    return null;
+  }
+
+  @override
+  bool onChildUpdate(String childId, Library child, [Object data]) {
+    if (_internal != null && hasProperty(_internal, r'hot$onChildUpdate')) {
+      return _internal.hot$onChildUpdate(
+          childId, (child as LibraryWrapper)._internal, data);
+    }
+    // ignore: avoid_returning_null
+    return null;
+  }
+}
+
+@JS('Map')
+abstract class JsMap<K, V> {
+  @JS()
+  external Object keys();
+
+  @JS()
+  external V get(K key);
+}
+
+@JS('Error')
+abstract class JsError {
+  @JS()
+  external String get message;
+
+  @JS()
+  external String get stack;
+}
+
+@anonymous
+@JS()
+class DartLoader {
+  @JS()
+  external JsMap<String, String> get urlToModuleId;
+
+  @JS()
+  external JsMap<String, List<String>> get moduleParentsGraph;
+
+  @JS()
+  external void forceLoadModule(String moduleId, void Function() callback,
+      void Function(JsError e) onError);
+
+  @JS()
+  external Object getModuleLibraries(String moduleId);
+}
+
+@JS(r'$dartLoader')
+external DartLoader get dartLoader;
+
+@JS('Array.from')
+external List _jsArrayFrom(Object any);
+
+@JS('Object.keys')
+external List _jsObjectKeys(Object any);
+
+@JS('Object.values')
+external List _jsObjectValues(Object any);
+
+List<K> keys<K, V>(JsMap<K, V> map) {
+  return List.from(_jsArrayFrom(map.keys()));
+}
+
+Module _moduleLibraries(String moduleId) {
+  var moduleObj = dartLoader.getModuleLibraries(moduleId);
+  if (moduleObj == null) {
+    throw HotReloadFailedException("Failed to get module '$moduleId'. "
+        "This error might appear if such module doesn't exist or isn't already loaded");
+  }
+  var moduleKeys = List<String>.from(_jsObjectKeys(moduleObj));
+  var moduleValues =
+      List<HotReloadableLibrary>.from(_jsObjectValues(moduleObj));
+  var moduleLibraries = moduleValues.map((x) => LibraryWrapper(x));
+  return Module(Map.fromIterables(moduleKeys, moduleLibraries));
+}
+
+Future<Module> _reloadModule(String moduleId) {
+  var completer = Completer<Module>();
+  var stackTrace = StackTrace.current;
+  dartLoader.forceLoadModule(moduleId, allowInterop(() {
+    completer.complete(_moduleLibraries(moduleId));
+  }),
+      allowInterop((e) => completer.completeError(
+          HotReloadFailedException(e.message), stackTrace)));
+  return completer.future;
+}
+
+void _reloadPage() {
+  window.location.reload();
+}
+
+void main() async {
+  var currentOrigin = '${window.location.origin}/';
+  var modulePaths = keys(dartLoader.urlToModuleId)
+      .map((key) => key.replaceFirst(currentOrigin, ''))
+      .toList();
+  var modulePathsJson = json.encode(modulePaths);
+
+  var request = await HttpRequest.request('/$_assetsDigestPath',
+      responseType: 'json', sendData: modulePathsJson, method: 'POST');
+  var digests = (request.response as Map).cast<String, String>();
+
+  var manager = ReloadingManager(
+      _reloadModule,
+      _moduleLibraries,
+      _reloadPage,
+      (module) => dartLoader.moduleParentsGraph.get(module),
+      () => keys(dartLoader.moduleParentsGraph));
+
+  var handler = ReloadHandler(digests,
+      (path) => dartLoader.urlToModuleId.get(currentOrigin + path), manager);
+
+  var webSocket =
+      WebSocket('ws://${window.location.host}', [_buildUpdatesProtocol]);
+  webSocket.onMessage.listen((event) => handler.listener(event.data as String));
+}
diff --git a/build_runner_core/BUILD.gn b/build_runner_core/BUILD.gn
new file mode 100644
index 0000000..f234d4c
--- /dev/null
+++ b/build_runner_core/BUILD.gn
@@ -0,0 +1,34 @@
+# This file is generated by importer.py for build_runner_core-4.4.0
+
+import("//build/dart/dart_library.gni")
+
+dart_library("build_runner_core") {
+  package_name = "build_runner_core"
+
+  # This parameter is left empty as we don't care about analysis or exporting
+  # these sources outside of the tree.
+  sources = []
+
+  disable_analysis = true
+
+  deps = [
+    "//third_party/dart-pkg/pub/pedantic",
+    "//third_party/dart-pkg/pub/glob",
+    "//third_party/dart-pkg/pub/collection",
+    "//third_party/dart-pkg/pub/watcher",
+    "//third_party/dart-pkg/pub/meta",
+    "//third_party/dart-pkg/pub/path",
+    "//third_party/dart-pkg/pub/timing",
+    "//third_party/dart-pkg/pub/build_config",
+    "//third_party/dart-pkg/pub/pool",
+    "//third_party/dart-pkg/pub/convert",
+    "//third_party/dart-pkg/pub/logging",
+    "//third_party/dart-pkg/pub/graphs",
+    "//third_party/dart-pkg/pub/crypto",
+    "//third_party/dart-pkg/pub/yaml",
+    "//third_party/dart-pkg/pub/build",
+    "//third_party/dart-pkg/pub/build_resolvers",
+    "//third_party/dart-pkg/pub/async",
+    "//third_party/dart-pkg/pub/json_annotation",
+  ]
+}
diff --git a/build_runner_core/CHANGELOG.md b/build_runner_core/CHANGELOG.md
new file mode 100644
index 0000000..466bba4
--- /dev/null
+++ b/build_runner_core/CHANGELOG.md
@@ -0,0 +1,305 @@
+## 4.4.0
+
+- Support the `auto_apply_builders` target configuration added in
+  `build_config` version `0.4.2`.
+
+## 4.3.0
+
+- Add the `$package$` synthetic placeholder file and update the docs to prefer
+  using only that or `lib/$lib$`.
+- Add the `assets` directory and `$package$` placeholders to the default
+  sources whitelist.
+
+## 4.2.1
+
+- Bug fix: Changing the root package name will no longer cause subsequent
+  builds to fail (Issue #2566).
+
+## 4.2.0
+
+### New Feature
+
+- Allow reading assets created previously by the same `BuildStep`.
+
+## 4.1.0
+
+- Add support for trimming builds based on `BuildStep.reportUnusedAssets`
+  calls. See the `build` package for more details.
+- Include `node/**` in the default set of sources (when there is no target
+  defined) for the root package.
+
+## 4.0.0
+
+### New Feature: Build Filters
+
+- Added a new `BuildFilter` class which matches a set of assets with glob
+  syntax support for both package and file names.
+- Added `buildFilters` to `BuildOptions` which is a `Set<BuildFilter>` and
+  is used to filter exactly which outputs will be generated.
+  - Note that any inputs to the specified files will also necessarily be built.
+- `BuildRunner.run` also now accepts an optional `Set<BuildFilter>` argument.
+- `FinalizedReader` also  now accepts a `Set<BuildFilter>` optional parameter
+  and will only allow reading matched files.
+  - This means you can create output directories or servers that respect build
+    filters.
+
+### Breaking Changes
+
+- `FinalizedReader.reset` now requires an additional `Set<BuildFilter>`
+  argument.
+
+## 3.1.1
+
+- When skipping build script updates, don't check if the build script is a
+  part of the asset graph either.
+
+## 3.1.0
+
+- Factor out the logic to do a manual file system scan for changes into a
+  new `AssetTracker` class.
+  - This is not exposed publicly and is only intended to be used from the
+    `build_runner` package.
+
+## 3.0.9
+
+- Support the latest release of `package:json_annotation`.
+
+## 3.0.8
+
+- Fix --log-performance crash on windows by ensuring we use valid
+  windows directory names.
+
+## 3.0.7
+
+- Support the latest `package:build_config`.
+
+## 3.0.6
+
+- Handle symlink creation failures and link to dev mode docs for windows.
+
+## 3.0.5
+
+- Explicitly require Dart SDK `>=2.2.0 <3.0.0`.
+- Fix an error that could occur when serializing outdated glob nodes.
+
+## 3.0.4
+
+- Add additional error details and a fallback for
+  https://github.com/dart-lang/build/issues/1804
+
+## 3.0.3
+
+- Share an asset graph when building regardless of whether the build script was
+  started from a snapshot.
+
+## 3.0.2
+
+- Only track valid and readable assets as inputs to globs. Fixes a crash when
+  attempting to check outputs from an invalid asset.
+
+## 3.0.1
+
+- Remove usage of set literals to fix errors on older sdks that don't support
+  them.
+
+## 3.0.0
+
+- Fix an issue where `--symlink` was forcing outputs to not be hoisted.
+- `BuildImpl` now takes an optional list of  `BuildTargets` instead of a list of
+  `buildDirs`.
+- Warn when there are no assets to write in a specified output directory.
+
+## 2.0.3
+
+- Handle asset graph decode failures.
+
+## 2.0.2
+
+- Update `build_resolvers` to version `1.0.0`.
+
+## 2.0.1
+
+- Fix an issue where the `finalizedReader` was not `reset` prior to build.
+
+## 2.0.0
+
+- The `build` method now requires a list of `buildDirs`.
+- Remove `buildDirs` from `BuildOptions`.
+- Added the `overrideGeneratedDirectory` method which overrides the directory
+  for generated outputs.
+  - Must be invoked before creating a `BuildRunner` instance.
+
+## 1.1.3
+
+- Update to `package:graphs` version `0.2.0`.
+- Allow `build` version `1.1.x`.
+- Update the way combined input hashes are computed to not rely on ordering.
+  - Digest implementations must now include the AssetId, not just the contents.
+- Require package:build version 1.1.0, which meets the new requirements for
+  digests.
+
+## 1.1.2
+
+- Fix a `NoSuchMethodError` that the user could get when adding new
+  dependencies.
+
+## 1.1.1
+
+- Fix a bug where adding new dependencies or removing dependencies could cause
+  subsequent build errors, requiring a `pub run build_runner clean` to fix.
+
+## 1.1.0
+
+- Support running the build script as a snapshot.
+- Added new exceptions, `BuildScriptChangedException` and
+  `BuildConfigChangedException`. These should be handled by scripts as described
+  in the documentation.
+- Added new `FailureType`s of `buildScriptChanged` and `buildConfigChanged`.
+
+## 1.0.2
+
+- Support the latest `package:json_annotation`.
+
+## 1.0.1
+
+- Update `package:build` version constraint to `>1.0.0 <1.0.1`.
+
+## 1.0.0
+
+### Breaking Changes
+
+- The performance tracking apis have changed significantly, and performance
+  tracking now uses the `timing` package.
+- The `BuildOptions` static factory now takes a `LogSubscription` instead of a
+  `BuildEnvironment`. Logging should be start as early as possible to catch logs
+  emitted during setup.
+
+### New Features
+
+- Use the `timing` package for performance tracking.
+- Added support for `BuildStep.trackStage` to track performance of custom build
+  stages within your builder.
+
+### Bug Fixes
+
+- Fixed a node invalidation issue when fixing build errors that could cause a
+  situation which was only resolvable with a full rebuild.
+
+## 0.3.1+5
+
+- Fixed an issue where builders that didn't read their primary input would get
+  invalidated on fresh builds when they shouldn't.
+
+## 0.3.1+4
+
+- Removed the constraint on reading files that output to cache from files that
+  output to source.
+
+## 0.3.1+3
+
+- Bug Fix: Don't output a `packages` symlink within the `packages` directory.
+
+## 0.3.1+2
+
+- Restore `new` keyword for a working release on Dart 1 VM.
+- Bug Fix: Don't include any non-lib assets from dependencies in the build, even
+  if they are a source in a target.
+
+## 0.3.1+1
+
+- Bug Fix: Don't include any non-lib assets from dependencies in the build, even
+  if they are a source in a target.
+- Release broken on Dart 1 VM.
+
+## 0.3.1
+
+- Migrated glob tracking to a specialized node type to fix dart-lang/build#1702.
+
+## 0.3.0
+
+### Breaking Changes
+
+- Implementations of `BuildEnvironment` must now implement the `finalizeBuild`
+  method. There is a default implementation if you extend `BuildEnvironment`
+  that is a no-op.
+  - This method is invoked at the end of the build that allows you to do
+    arbitrary additional work, such as creating merged output directories.
+- The `assumeTty` argument on `IOEnvironment` has moved to a named argument
+  since `null` is an accepted value.
+- The `outputMap` field on `BuildOptions` has moved to the `IOEnvironment`
+  class.
+
+### New Features/Updates
+
+- Added a `outputSymlinksOnly` option to `IOEnvironment` constructor, that
+  causes the merged output directories to contain only symlinks, which is much
+  faster than copying files.
+- Added the `FinalizedAssetView` class which provides a list of all available
+  assets to the `BuildEnvironment` during the build finalization phase.
+  - `outputMap` has moved from `BuildOptions` to this constructor, as a named
+    argument.
+- The `OverridableEnvironment` now supports overriding the new `finalizeBuild`
+  api.
+- The number of concurrent actions per phase is now limited (currently to 16),
+  which should help with memory and cpu usage for large builds.
+
+## 0.2.2+2
+
+- Support `package:json_annotation` v1.
+
+## 0.2.2+1
+
+- Tag errors from cached actions when they are printed.
+
+## 0.2.2
+
+- Changed the default file caching logic to use an LRU cache.
+
+## 0.2.1+2
+
+- Clarify wording for conflicting output directory options. No behavioral
+  differences.
+- Reduce the memory consumption required to create an output dir significantly.
+- Increased the upper bound for the sdk to `<3.0.0`.
+
+## 0.2.1+1
+
+- Allow reuse cache between machines with different OS
+
+## 0.2.1
+
+- The hash dir for the asset graph under `.dart_tool/build` is now based on a
+  relative path to the build script instead of the absolute path.
+  - This enables `.dart_tool/build` directories to be reused across different
+    computers and directories for the same project.
+
+## 0.2.0
+
+### New Features
+
+- The `BuildPerformance` class is now serializable, it has a `fromJson`
+  constructor and a `toJson` instance method.
+- Added `BuildOptions.logPerformanceDir`, performance logs will be continuously
+  written to that directory if provided.
+- Added support for `global_options` in `build.yaml` of the root package.
+- Allow overriding the default `Resolvers` implementation.
+- Allows building with symlinked files. Note that changes to the linked files
+  will not trigger rebuilds in watch or serve mode.
+
+### Breaking changes
+
+- `BuildPhasePerformance.action` has been replaced with
+  `BuildPhasePerformance.builderKeys`.
+- `BuilderActionPerformance.builder` has been replaced with
+  `BuilderActionPerformance.builderKey`.
+- `BuildResult` no longer has an `exception` or `stackTrace` field.
+- Dropped `failOnSevere` arguments. Severe logs are always considered failing.
+
+### Internal changes
+
+- Remove dependency on package:cli_util.
+
+## 0.1.0
+
+Initial release, migrating the core functionality of package:build_runner to
+this package.
diff --git a/build_runner_core/LICENSE b/build_runner_core/LICENSE
new file mode 100644
index 0000000..c4dc9ba
--- /dev/null
+++ b/build_runner_core/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2018, the Dart project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of Google Inc. nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/build_runner_core/README.md b/build_runner_core/README.md
new file mode 100644
index 0000000..0fabf3e
--- /dev/null
+++ b/build_runner_core/README.md
@@ -0,0 +1,21 @@
+<p align="center">
+  Core functionality of the build_runner package. Exposes the imperative apis
+  for running pure Dart builds for
+  <a href="https://pub.dev/packages/build"><code>package:build</code></a>.
+  <br>
+  <a href="https://travis-ci.org/dart-lang/build">
+    <img src="https://travis-ci.org/dart-lang/build.svg?branch=master" alt="Build Status" />
+  </a>
+  <a href="https://github.com/dart-lang/build/labels/package%3A%20build_runner">
+    <img src="https://img.shields.io/github/issues-raw/dart-lang/build/package%3A%20build_runner_core.svg" alt="Issues related to build_runner_core" />
+  </a>
+  <a href="https://pub.dev/packages/build_runner_core">
+    <img src="https://img.shields.io/pub/v/build_runner_core.svg" alt="Pub Package Version" />
+  </a>
+  <a href="https://pub.dev/documentation/build_runner_core/latest">
+    <img src="https://img.shields.io/badge/dartdocs-latest-blue.svg" alt="Latest Dartdocs" />
+  </a>
+  <a href="https://gitter.im/dart-lang/build">
+    <img src="https://badges.gitter.im/dart-lang/build.svg" alt="Join the chat on Gitter" />
+  </a>
+</p>
diff --git a/build_runner_core/lib/build_runner_core.dart b/build_runner_core/lib/build_runner_core.dart
new file mode 100644
index 0000000..e908e19
--- /dev/null
+++ b/build_runner_core/lib/build_runner_core.dart
@@ -0,0 +1,47 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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.
+
+export 'package:build/build.dart' show PostProcessBuilder, PostProcessBuildStep;
+
+export 'src/asset/file_based.dart';
+export 'src/asset/finalized_reader.dart';
+export 'src/asset/reader.dart' show RunnerAssetReader;
+export 'src/asset/writer.dart';
+export 'src/environment/build_environment.dart';
+export 'src/environment/io_environment.dart';
+export 'src/environment/overridable_environment.dart';
+export 'src/generate/build_directory.dart';
+export 'src/generate/build_result.dart';
+export 'src/generate/build_runner.dart';
+export 'src/generate/exceptions.dart'
+    show
+        BuildConfigChangedException,
+        BuildScriptChangedException,
+        CannotBuildException;
+export 'src/generate/finalized_assets_view.dart' show FinalizedAssetsView;
+export 'src/generate/options.dart'
+    show
+        defaultRootPackageWhitelist,
+        LogSubscription,
+        BuildOptions,
+        BuildFilter;
+export 'src/generate/performance_tracker.dart'
+    show BuildPerformance, BuilderActionPerformance, BuildPhasePerformance;
+export 'src/logging/human_readable_duration.dart';
+export 'src/logging/logging.dart';
+export 'src/package_graph/apply_builders.dart'
+    show
+        BuilderApplication,
+        apply,
+        applyPostProcess,
+        applyToRoot,
+        toAll,
+        toAllPackages,
+        toDependentsOf,
+        toNoneByDefault,
+        toPackage,
+        toPackages,
+        toRoot;
+export 'src/package_graph/package_graph.dart';
+export 'src/util/constants.dart';
diff --git a/build_runner_core/lib/src/asset/build_cache.dart b/build_runner_core/lib/src/asset/build_cache.dart
new file mode 100644
index 0000000..b052ba3
--- /dev/null
+++ b/build_runner_core/lib/src/asset/build_cache.dart
@@ -0,0 +1,103 @@
+import 'dart:async';
+import 'dart:convert';
+
+import 'package:build/build.dart';
+import 'package:crypto/crypto.dart';
+import 'package:glob/glob.dart';
+
+import '../asset_graph/graph.dart';
+import '../asset_graph/node.dart';
+import '../util/constants.dart';
+import 'reader.dart';
+import 'writer.dart';
+
+/// Wraps an [AssetReader] and translates reads for generated files into reads
+/// from the build cache directory
+class BuildCacheReader implements AssetReader {
+  final AssetGraph _assetGraph;
+  final AssetReader _delegate;
+  final String _rootPackage;
+
+  BuildCacheReader._(this._delegate, this._assetGraph, this._rootPackage);
+
+  factory BuildCacheReader(
+          AssetReader delegate, AssetGraph assetGraph, String rootPackage) =>
+      delegate is PathProvidingAssetReader
+          ? _PathProvidingBuildCacheReader._(delegate, assetGraph, rootPackage)
+          : BuildCacheReader._(delegate, assetGraph, rootPackage);
+
+  @override
+  Future<bool> canRead(AssetId id) =>
+      _delegate.canRead(_cacheLocation(id, _assetGraph, _rootPackage));
+
+  @override
+  Future<Digest> digest(AssetId id) =>
+      _delegate.digest(_cacheLocation(id, _assetGraph, _rootPackage));
+
+  @override
+  Future<List<int>> readAsBytes(AssetId id) =>
+      _delegate.readAsBytes(_cacheLocation(id, _assetGraph, _rootPackage));
+
+  @override
+  Future<String> readAsString(AssetId id, {Encoding encoding = utf8}) =>
+      _delegate.readAsString(_cacheLocation(id, _assetGraph, _rootPackage),
+          encoding: encoding);
+
+  @override
+  Stream<AssetId> findAssets(Glob glob) => throw UnimplementedError(
+      'Asset globbing should be done per phase with the AssetGraph');
+}
+
+class _PathProvidingBuildCacheReader extends BuildCacheReader
+    implements PathProvidingAssetReader {
+  @override
+  PathProvidingAssetReader get _delegate =>
+      super._delegate as PathProvidingAssetReader;
+
+  _PathProvidingBuildCacheReader._(PathProvidingAssetReader delegate,
+      AssetGraph assetGraph, String rootPackage)
+      : super._(delegate, assetGraph, rootPackage);
+
+  @override
+  String pathTo(AssetId id) =>
+      _delegate.pathTo(_cacheLocation(id, _assetGraph, _rootPackage));
+}
+
+class BuildCacheWriter implements RunnerAssetWriter {
+  final AssetGraph _assetGraph;
+  final RunnerAssetWriter _delegate;
+  final String _rootPackage;
+
+  BuildCacheWriter(this._delegate, this._assetGraph, this._rootPackage);
+
+  @override
+  Future writeAsBytes(AssetId id, List<int> content) => _delegate.writeAsBytes(
+      _cacheLocation(id, _assetGraph, _rootPackage), content);
+
+  @override
+  Future writeAsString(AssetId id, String content,
+          {Encoding encoding = utf8}) =>
+      _delegate.writeAsString(
+          _cacheLocation(id, _assetGraph, _rootPackage), content,
+          encoding: encoding);
+
+  @override
+  Future delete(AssetId id) =>
+      _delegate.delete(_cacheLocation(id, _assetGraph, _rootPackage));
+}
+
+AssetId _cacheLocation(AssetId id, AssetGraph assetGraph, String rootPackage) {
+  if (id.path.startsWith(generatedOutputDirectory) ||
+      id.path.startsWith(cacheDir)) {
+    return id;
+  }
+  if (!assetGraph.contains(id)) {
+    return id;
+  }
+  final assetNode = assetGraph.get(id);
+  if (assetNode is GeneratedAssetNode && assetNode.isHidden) {
+    return AssetId(
+        rootPackage, '$generatedOutputDirectory/${id.package}/${id.path}');
+  }
+  return id;
+}
diff --git a/build_runner_core/lib/src/asset/cache.dart b/build_runner_core/lib/src/asset/cache.dart
new file mode 100644
index 0000000..12fabeb
--- /dev/null
+++ b/build_runner_core/lib/src/asset/cache.dart
@@ -0,0 +1,129 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:convert';
+import 'dart:typed_data';
+
+import 'package:build/build.dart';
+import 'package:crypto/crypto.dart';
+import 'package:glob/glob.dart';
+
+import 'lru_cache.dart';
+import 'reader.dart';
+
+/// An [AssetReader] that caches all results from the delegate.
+///
+/// Assets are cached until [invalidate] is invoked.
+///
+/// Does not implement [findAssets].
+class CachingAssetReader implements AssetReader {
+  /// Cached results of [readAsBytes].
+  final _bytesContentCache = LruCache<AssetId, List<int>>(
+      1024 * 1024,
+      1024 * 1024 * 512,
+      (value) => value is Uint8List ? value.lengthInBytes : value.length * 8);
+
+  /// Pending [readAsBytes] operations.
+  final _pendingBytesContentCache = <AssetId, Future<List<int>>>{};
+
+  /// Cached results of [canRead].
+  ///
+  /// Don't bother using an LRU cache for this since it's just booleans.
+  final _canReadCache = <AssetId, Future<bool>>{};
+
+  /// Cached results of [readAsString].
+  ///
+  /// These are computed and stored lazily using [readAsBytes].
+  ///
+  /// Only files read with [utf8] encoding (the default) will ever be cached.
+  final _stringContentCache = LruCache<AssetId, String>(
+      1024 * 1024, 1024 * 1024 * 512, (value) => value.length);
+
+  /// Pending `readAsString` operations.
+  final _pendingStringContentCache = <AssetId, Future<String>>{};
+
+  final AssetReader _delegate;
+
+  CachingAssetReader._(this._delegate);
+
+  factory CachingAssetReader(AssetReader delegate) =>
+      delegate is PathProvidingAssetReader
+          ? _PathProvidingCachingAssetReader._(delegate)
+          : CachingAssetReader._(delegate);
+
+  @override
+  Future<bool> canRead(AssetId id) =>
+      _canReadCache.putIfAbsent(id, () => _delegate.canRead(id));
+
+  @override
+  Future<Digest> digest(AssetId id) => _delegate.digest(id);
+
+  @override
+  Stream<AssetId> findAssets(Glob glob) =>
+      throw UnimplementedError('unimplemented!');
+
+  @override
+  Future<List<int>> readAsBytes(AssetId id, {bool cache = true}) {
+    var cached = _bytesContentCache[id];
+    if (cached != null) return Future.value(cached);
+
+    return _pendingBytesContentCache.putIfAbsent(
+        id,
+        () => _delegate.readAsBytes(id).then((result) {
+              if (cache) _bytesContentCache[id] = result;
+              _pendingBytesContentCache.remove(id);
+              return result;
+            }));
+  }
+
+  @override
+  Future<String> readAsString(AssetId id, {Encoding encoding}) {
+    encoding ??= utf8;
+
+    if (encoding != utf8) {
+      // Fallback case, we never cache the String value for the non-default,
+      // encoding but we do allow it to cache the bytes.
+      return readAsBytes(id).then(encoding.decode);
+    }
+
+    var cached = _stringContentCache[id];
+    if (cached != null) return Future.value(cached);
+
+    return _pendingStringContentCache.putIfAbsent(
+        id,
+        () => readAsBytes(id, cache: false).then((bytes) {
+              var decoded = encoding.decode(bytes);
+              _stringContentCache[id] = decoded;
+              _pendingStringContentCache.remove(id);
+              return decoded;
+            }));
+  }
+
+  /// Clears all [ids] from all caches.
+  void invalidate(Iterable<AssetId> ids) {
+    for (var id in ids) {
+      _bytesContentCache.remove(id);
+      _canReadCache.remove(id);
+      _stringContentCache.remove(id);
+
+      _pendingBytesContentCache.remove(id);
+      _pendingStringContentCache.remove(id);
+    }
+  }
+}
+
+/// A version of a [CachingAssetReader] that implements
+/// [PathProvidingAssetReader].
+class _PathProvidingCachingAssetReader extends CachingAssetReader
+    implements PathProvidingAssetReader {
+  @override
+  PathProvidingAssetReader get _delegate =>
+      super._delegate as PathProvidingAssetReader;
+
+  _PathProvidingCachingAssetReader._(AssetReader delegate) : super._(delegate);
+
+  @override
+  String pathTo(AssetId id) => _delegate.pathTo(id);
+}
diff --git a/build_runner_core/lib/src/asset/file_based.dart b/build_runner_core/lib/src/asset/file_based.dart
new file mode 100644
index 0000000..4b1efc0
--- /dev/null
+++ b/build_runner_core/lib/src/asset/file_based.dart
@@ -0,0 +1,137 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:build/build.dart';
+import 'package:glob/glob.dart';
+import 'package:path/path.dart' as path;
+import 'package:pool/pool.dart';
+
+import '../package_graph/package_graph.dart';
+import 'reader.dart';
+import 'writer.dart';
+
+/// Pool for async file operations, we don't want to use too many file handles.
+final _descriptorPool = Pool(32);
+
+/// Basic [AssetReader] which uses a [PackageGraph] to look up where to read
+/// files from disk.
+class FileBasedAssetReader extends AssetReader
+    implements RunnerAssetReader, PathProvidingAssetReader {
+  final PackageGraph packageGraph;
+
+  FileBasedAssetReader(this.packageGraph);
+
+  @override
+  Future<bool> canRead(AssetId id) =>
+      _descriptorPool.withResource(() => _fileFor(id, packageGraph).exists());
+
+  @override
+  Future<List<int>> readAsBytes(AssetId id) => _fileForOrThrow(id, packageGraph)
+      .then((file) => _descriptorPool.withResource(file.readAsBytes));
+
+  @override
+  Future<String> readAsString(AssetId id, {Encoding encoding}) =>
+      _fileForOrThrow(id, packageGraph).then((file) => _descriptorPool
+          .withResource(() => file.readAsString(encoding: encoding ?? utf8)));
+
+  @override
+  Stream<AssetId> findAssets(Glob glob, {String package}) {
+    var packageNode =
+        package == null ? packageGraph.root : packageGraph[package];
+    if (packageNode == null) {
+      throw ArgumentError(
+          "Could not find package '$package' which was listed as "
+          'an input. Please ensure you have that package in your deps, or '
+          'remove it from your input sets.');
+    }
+    return glob
+        .list(followLinks: true, root: packageNode.path)
+        .where((e) => e is File && !path.basename(e.path).startsWith('._'))
+        .cast<File>()
+        .map((file) => _fileToAssetId(file, packageNode));
+  }
+
+  @override
+  String pathTo(AssetId id) => _filePathFor(id, packageGraph);
+}
+
+/// Creates an [AssetId] for [file], which is a part of [packageNode].
+AssetId _fileToAssetId(File file, PackageNode packageNode) {
+  var filePath = path.normalize(file.absolute.path);
+  var relativePath = path.relative(filePath, from: packageNode.path);
+  return AssetId(packageNode.name, relativePath);
+}
+
+/// Basic [AssetWriter] which uses a [PackageGraph] to look up where to write
+/// files to disk.
+class FileBasedAssetWriter implements RunnerAssetWriter {
+  final PackageGraph packageGraph;
+
+  FileBasedAssetWriter(this.packageGraph);
+
+  @override
+  Future writeAsBytes(AssetId id, List<int> bytes) async {
+    var file = _fileFor(id, packageGraph);
+    await _descriptorPool.withResource(() async {
+      await file.create(recursive: true);
+      await file.writeAsBytes(bytes);
+    });
+  }
+
+  @override
+  Future writeAsString(AssetId id, String contents,
+      {Encoding encoding = utf8}) async {
+    var file = _fileFor(id, packageGraph);
+    await _descriptorPool.withResource(() async {
+      await file.create(recursive: true);
+      await file.writeAsString(contents, encoding: encoding);
+    });
+  }
+
+  @override
+  Future delete(AssetId id) {
+    if (id.package != packageGraph.root.name) {
+      throw InvalidOutputException(
+          id, 'Should not delete assets outside of ${packageGraph.root.name}');
+    }
+
+    var file = _fileFor(id, packageGraph);
+    return _descriptorPool.withResource(() async {
+      if (await file.exists()) {
+        await file.delete();
+      }
+    });
+  }
+}
+
+/// Returns the path to [id] for a given [packageGraph].
+String _filePathFor(AssetId id, PackageGraph packageGraph) {
+  var package = packageGraph[id.package];
+  if (package == null) {
+    throw PackageNotFoundException(id.package);
+  }
+  return path.join(package.path, id.path);
+}
+
+/// Returns a [File] for [id] given [packageGraph].
+File _fileFor(AssetId id, PackageGraph packageGraph) {
+  return File(_filePathFor(id, packageGraph));
+}
+
+/// Returns a [Future<File>] for [id] given [packageGraph].
+///
+/// Throws an `AssetNotFoundException` if it doesn't exist.
+Future<File> _fileForOrThrow(AssetId id, PackageGraph packageGraph) {
+  if (packageGraph[id.package] == null) {
+    return Future.error(PackageNotFoundException(id.package));
+  }
+  var file = _fileFor(id, packageGraph);
+  return _descriptorPool.withResource(file.exists).then((exists) {
+    if (!exists) throw AssetNotFoundException(id);
+    return file;
+  });
+}
diff --git a/build_runner_core/lib/src/asset/finalized_reader.dart b/build_runner_core/lib/src/asset/finalized_reader.dart
new file mode 100644
index 0000000..38bdb03
--- /dev/null
+++ b/build_runner_core/lib/src/asset/finalized_reader.dart
@@ -0,0 +1,91 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:convert';
+
+import 'package:build/build.dart';
+import 'package:build_runner_core/build_runner_core.dart';
+import 'package:build_runner_core/src/generate/phase.dart';
+import 'package:crypto/crypto.dart';
+import 'package:glob/glob.dart';
+
+import '../asset_graph/graph.dart';
+import '../asset_graph/node.dart';
+import '../asset_graph/optional_output_tracker.dart';
+
+/// An [AssetReader] which ignores deleted files.
+class FinalizedReader implements AssetReader {
+  final AssetReader _delegate;
+  final AssetGraph _assetGraph;
+  OptionalOutputTracker _optionalOutputTracker;
+  final String _rootPackage;
+  final List<BuildPhase> _buildPhases;
+
+  void reset(Set<String> buildDirs, Set<BuildFilter> buildFilters) {
+    _optionalOutputTracker = OptionalOutputTracker(
+        _assetGraph, buildDirs, buildFilters, _buildPhases);
+  }
+
+  FinalizedReader(
+      this._delegate, this._assetGraph, this._buildPhases, this._rootPackage);
+
+  /// Returns a reason why [id] is not readable, or null if it is readable.
+  Future<UnreadableReason> unreadableReason(AssetId id) async {
+    if (!_assetGraph.contains(id)) return UnreadableReason.notFound;
+    var node = _assetGraph.get(id);
+    if (node.isDeleted) return UnreadableReason.deleted;
+    if (!node.isReadable) return UnreadableReason.assetType;
+    if (node is GeneratedAssetNode) {
+      if (node.isFailure) return UnreadableReason.failed;
+      if (!(node.wasOutput && (_optionalOutputTracker.isRequired(node.id)))) {
+        return UnreadableReason.notOutput;
+      }
+    }
+    if (await _delegate.canRead(id)) return null;
+    return UnreadableReason.unknown;
+  }
+
+  @override
+  Future<bool> canRead(AssetId id) async =>
+      (await unreadableReason(id)) == null;
+
+  @override
+  Future<Digest> digest(AssetId id) => _delegate.digest(id);
+
+  @override
+  Future<List<int>> readAsBytes(AssetId id) => _delegate.readAsBytes(id);
+
+  @override
+  Future<String> readAsString(AssetId id, {Encoding encoding = utf8}) async {
+    if (_assetGraph.get(id)?.isDeleted ?? true) {
+      throw AssetNotFoundException(id);
+    }
+    return _delegate.readAsString(id, encoding: encoding);
+  }
+
+  @override
+  Stream<AssetId> findAssets(Glob glob) async* {
+    var potentialNodes = _assetGraph
+        .packageNodes(_rootPackage)
+        .where((n) => glob.matches(n.id.path))
+        .toList();
+    var potentialIds = potentialNodes.map((n) => n.id).toList();
+
+    for (var id in potentialIds) {
+      if (await _delegate.canRead(id)) {
+        yield id;
+      }
+    }
+  }
+}
+
+enum UnreadableReason {
+  notFound,
+  notOutput,
+  assetType,
+  deleted,
+  failed,
+  unknown,
+}
diff --git a/build_runner_core/lib/src/asset/lru_cache.dart b/build_runner_core/lib/src/asset/lru_cache.dart
new file mode 100644
index 0000000..c1e2299
--- /dev/null
+++ b/build_runner_core/lib/src/asset/lru_cache.dart
@@ -0,0 +1,104 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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.
+
+/// A basic LRU Cache.
+class LruCache<K, V> {
+  _Link<K, V> _head;
+  _Link<K, V> _tail;
+
+  final int Function(V) _computeWeight;
+
+  int _currentWeightTotal = 0;
+  final int _individualWeightMax;
+  final int _totalWeightMax;
+
+  final _entries = <K, _Link<K, V>>{};
+
+  LruCache(
+      this._individualWeightMax, this._totalWeightMax, this._computeWeight);
+
+  V operator [](K key) {
+    var entry = _entries[key];
+    if (entry == null) return null;
+
+    _promote(entry);
+    return entry.value;
+  }
+
+  void operator []=(K key, V value) {
+    var entry = _Link(key, value, _computeWeight(value));
+    // Don't cache at all if above the individual weight max.
+    if (entry.weight > _individualWeightMax) {
+      return;
+    }
+
+    _entries[key] = entry;
+    _currentWeightTotal += entry.weight;
+    _promote(entry);
+
+    while (_currentWeightTotal > _totalWeightMax) {
+      assert(_tail != null);
+      remove(_tail.key);
+    }
+  }
+
+  /// Removes the value at [key] from the cache, and returns the value if it
+  /// existed.
+  V remove(K key) {
+    var entry = _entries[key];
+    if (entry == null) return null;
+
+    _currentWeightTotal -= entry.weight;
+    _entries.remove(key);
+
+    if (entry == _tail) {
+      _tail = entry.next;
+      _tail?.previous = null;
+    }
+    if (entry == _head) {
+      _head = entry.previous;
+      _head?.next = null;
+    }
+
+    return entry.value;
+  }
+
+  /// Moves [link] to the [_head] of the list.
+  void _promote(_Link<K, V> link) {
+    if (link == _head) return;
+
+    if (link == _tail) {
+      _tail = link.next;
+    }
+
+    if (link.previous != null) {
+      link.previous.next = link.next;
+    }
+    if (link.next != null) {
+      link.next.previous = link.previous;
+    }
+
+    _head?.next = link;
+    link.previous = _head;
+    _head = link;
+    _tail ??= link;
+    link.next = null;
+  }
+}
+
+/// A [MapEntry] which is also a part of a doubly linked list.
+class _Link<K, V> implements MapEntry<K, V> {
+  _Link<K, V> next;
+  _Link<K, V> previous;
+
+  final int weight;
+
+  @override
+  final K key;
+
+  @override
+  final V value;
+
+  _Link(this.key, this.value, this.weight);
+}
diff --git a/build_runner_core/lib/src/asset/reader.dart b/build_runner_core/lib/src/asset/reader.dart
new file mode 100644
index 0000000..f3c0131
--- /dev/null
+++ b/build_runner_core/lib/src/asset/reader.dart
@@ -0,0 +1,167 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:collection';
+import 'dart:convert';
+
+import 'package:async/async.dart';
+import 'package:build/build.dart';
+import 'package:crypto/crypto.dart';
+import 'package:glob/glob.dart';
+import 'package:meta/meta.dart';
+import '../asset_graph/graph.dart';
+import '../asset_graph/node.dart';
+import '../util/async.dart';
+
+/// A [RunnerAssetReader] must implement [MultiPackageAssetReader].
+abstract class RunnerAssetReader implements MultiPackageAssetReader {}
+
+/// An [AssetReader] that can provide actual paths to assets on disk.
+abstract class PathProvidingAssetReader implements AssetReader {
+  String pathTo(AssetId id);
+}
+
+/// Describes if and how a [SingleStepReader] should read an [AssetId].
+class Readability {
+  final bool canRead;
+  final bool inSamePhase;
+
+  const Readability({@required this.canRead, @required this.inSamePhase});
+
+  /// Determines readability for a node written in a previous build phase, which
+  /// means that [ownOutput] is impossible.
+  factory Readability.fromPreviousPhase(bool readable) =>
+      readable ? Readability.readable : Readability.notReadable;
+
+  static const Readability notReadable =
+      Readability(canRead: false, inSamePhase: false);
+  static const Readability readable =
+      Readability(canRead: true, inSamePhase: false);
+  static const Readability ownOutput =
+      Readability(canRead: true, inSamePhase: true);
+}
+
+typedef IsReadable = FutureOr<Readability> Function(
+    AssetNode node, int phaseNum, AssetWriterSpy writtenAssets);
+
+/// An [AssetReader] with a lifetime equivalent to that of a single step in a
+/// build.
+///
+/// A step is a single Builder and primary input (or package for package
+/// builders) combination.
+///
+/// Limits reads to the assets which are sources or were generated by previous
+/// phases.
+///
+/// Tracks the assets and globs read during this step for input dependency
+/// tracking.
+class SingleStepReader implements AssetReader {
+  final AssetGraph _assetGraph;
+  final AssetReader _delegate;
+  final int _phaseNumber;
+  final String _primaryPackage;
+  final AssetWriterSpy _writtenAssets;
+  final IsReadable _isReadableNode;
+  final FutureOr<GlobAssetNode> Function(
+      Glob glob, String package, int phaseNum) _getGlobNode;
+
+  /// The assets read during this step.
+  final assetsRead = HashSet<AssetId>();
+
+  SingleStepReader(this._delegate, this._assetGraph, this._phaseNumber,
+      this._primaryPackage, this._isReadableNode,
+      [this._getGlobNode, this._writtenAssets]);
+
+  /// Checks whether [id] can be read by this step - attempting to build the
+  /// asset if necessary.
+  FutureOr<bool> _isReadable(AssetId id) {
+    final node = _assetGraph.get(id);
+    if (node == null) {
+      assetsRead.add(id);
+      _assetGraph.add(SyntheticSourceAssetNode(id));
+      return false;
+    }
+
+    return doAfter(_isReadableNode(node, _phaseNumber, _writtenAssets),
+        (Readability readability) {
+      if (!readability.inSamePhase) {
+        assetsRead.add(id);
+      }
+
+      return readability.canRead;
+    });
+  }
+
+  @override
+  Future<bool> canRead(AssetId id) {
+    return toFuture(doAfter(_isReadable(id), (bool isReadable) {
+      if (!isReadable) return false;
+      var node = _assetGraph.get(id);
+      FutureOr<bool> _canRead() {
+        if (node is GeneratedAssetNode) {
+          // Short circut, we know this file exists because its readable and it was
+          // output.
+          return true;
+        } else {
+          return _delegate.canRead(id);
+        }
+      }
+
+      return doAfter(_canRead(), (bool canRead) {
+        if (!canRead) return false;
+        return doAfter(_ensureDigest(id), (_) => true);
+      });
+    }));
+  }
+
+  @override
+  Future<Digest> digest(AssetId id) {
+    return toFuture(doAfter(_isReadable(id), (bool isReadable) {
+      if (!isReadable) {
+        return Future.error(AssetNotFoundException(id));
+      }
+      return _ensureDigest(id);
+    }));
+  }
+
+  @override
+  Future<List<int>> readAsBytes(AssetId id) {
+    return toFuture(doAfter(_isReadable(id), (bool isReadable) {
+      if (!isReadable) {
+        return Future.error(AssetNotFoundException(id));
+      }
+      return doAfter(_ensureDigest(id), (_) => _delegate.readAsBytes(id));
+    }));
+  }
+
+  @override
+  Future<String> readAsString(AssetId id, {Encoding encoding = utf8}) {
+    return toFuture(doAfter(_isReadable(id), (bool isReadable) {
+      if (!isReadable) {
+        return Future.error(AssetNotFoundException(id));
+      }
+      return doAfter(_ensureDigest(id),
+          (_) => _delegate.readAsString(id, encoding: encoding));
+    }));
+  }
+
+  @override
+  Stream<AssetId> findAssets(Glob glob) {
+    var streamCompleter = StreamCompleter<AssetId>();
+
+    doAfter(_getGlobNode(glob, _primaryPackage, _phaseNumber),
+        (GlobAssetNode globNode) {
+      assetsRead.add(globNode.id);
+      streamCompleter.setSourceStream(Stream.fromIterable(globNode.results));
+    });
+    return streamCompleter.stream;
+  }
+
+  FutureOr<Digest> _ensureDigest(AssetId id) {
+    var node = _assetGraph.get(id);
+    if (node?.lastKnownDigest != null) return node.lastKnownDigest;
+    return _delegate.digest(id).then((digest) => node.lastKnownDigest = digest);
+  }
+}
diff --git a/build_runner_core/lib/src/asset/writer.dart b/build_runner_core/lib/src/asset/writer.dart
new file mode 100644
index 0000000..e0c1827
--- /dev/null
+++ b/build_runner_core/lib/src/asset/writer.dart
@@ -0,0 +1,14 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'package:build/build.dart';
+
+@deprecated
+typedef OnDelete = void Function(AssetId id);
+
+abstract class RunnerAssetWriter implements AssetWriter {
+  Future delete(AssetId id);
+}
diff --git a/build_runner_core/lib/src/asset_graph/exceptions.dart b/build_runner_core/lib/src/asset_graph/exceptions.dart
new file mode 100644
index 0000000..46f9d69
--- /dev/null
+++ b/build_runner_core/lib/src/asset_graph/exceptions.dart
@@ -0,0 +1,26 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 'package:build/build.dart';
+
+class DuplicateAssetNodeException implements Exception {
+  final String rootPackage;
+  final AssetId assetId;
+  final String initialBuilderLabel;
+  final String newBuilderLabel;
+
+  DuplicateAssetNodeException(this.rootPackage, this.assetId,
+      this.initialBuilderLabel, this.newBuilderLabel);
+  @override
+  String toString() {
+    final friendlyAsset =
+        assetId.package == rootPackage ? assetId.path : assetId.uri;
+    return 'Both $initialBuilderLabel and $newBuilderLabel may output '
+        '$friendlyAsset. Potential outputs must be unique across all builders. '
+        'See https://github.com/dart-lang/build/blob/master/docs/faq.md'
+        '#why-do-builders-need-unique-outputs';
+  }
+}
+
+class AssetGraphCorruptedException implements Exception {}
diff --git a/build_runner_core/lib/src/asset_graph/graph.dart b/build_runner_core/lib/src/asset_graph/graph.dart
new file mode 100644
index 0000000..d7e6444
--- /dev/null
+++ b/build_runner_core/lib/src/asset_graph/graph.dart
@@ -0,0 +1,556 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:collection';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:build/build.dart';
+import 'package:convert/convert.dart';
+import 'package:crypto/crypto.dart';
+import 'package:glob/glob.dart';
+import 'package:meta/meta.dart';
+import 'package:watcher/watcher.dart';
+
+import '../generate/phase.dart';
+import '../package_graph/package_graph.dart';
+import 'exceptions.dart';
+import 'node.dart';
+
+part 'serialization.dart';
+
+/// All the [AssetId]s involved in a build, and all of their outputs.
+class AssetGraph {
+  /// All the [AssetNode]s in the graph, indexed by package and then path.
+  final _nodesByPackage = <String, Map<String, AssetNode>>{};
+
+  /// A [Digest] of the build actions this graph was originally created with.
+  ///
+  /// When an [AssetGraph] is deserialized we check whether or not it matches
+  /// the new [BuildPhase]s and throw away the graph if it doesn't.
+  final Digest buildPhasesDigest;
+
+  /// The [Platform.version] this graph was created with.
+  final String dartVersion;
+
+  AssetGraph._(this.buildPhasesDigest, this.dartVersion);
+
+  /// Deserializes this graph.
+  factory AssetGraph.deserialize(List<int> serializedGraph) =>
+      _AssetGraphDeserializer(serializedGraph).deserialize();
+
+  static Future<AssetGraph> build(
+      List<BuildPhase> buildPhases,
+      Set<AssetId> sources,
+      Set<AssetId> internalSources,
+      PackageGraph packageGraph,
+      AssetReader digestReader) async {
+    var graph =
+        AssetGraph._(computeBuildPhasesDigest(buildPhases), Platform.version);
+    var placeholders = graph._addPlaceHolderNodes(packageGraph);
+    var sourceNodes = graph._addSources(sources);
+    graph
+      .._addBuilderOptionsNodes(buildPhases)
+      .._addOutputsForSources(buildPhases, sources, packageGraph.root.name,
+          placeholders: placeholders);
+    // Pre-emptively compute digests for the nodes we know have outputs.
+    await graph._setLastKnownDigests(
+        sourceNodes.where((node) => node.primaryOutputs.isNotEmpty),
+        digestReader);
+    // Always compute digests for all internal nodes.
+    var internalNodes = graph._addInternalSources(internalSources);
+    await graph._setLastKnownDigests(internalNodes, digestReader);
+    return graph;
+  }
+
+  List<int> serialize() => _AssetGraphSerializer(this).serialize();
+
+  /// Checks if [id] exists in the graph.
+  bool contains(AssetId id) =>
+      _nodesByPackage[id.package]?.containsKey(id.path) ?? false;
+
+  /// Gets the [AssetNode] for [id], if one exists.
+  AssetNode get(AssetId id) {
+    var pkg = _nodesByPackage[id?.package];
+    if (pkg == null) return null;
+    return pkg[id.path];
+  }
+
+  /// Adds [node] to the graph if it doesn't exist.
+  ///
+  /// Throws a [StateError] if it already exists in the graph.
+  void _add(AssetNode node) {
+    var existing = get(node.id);
+    if (existing != null) {
+      if (existing is SyntheticSourceAssetNode) {
+        // Don't call _removeRecursive, that recursively removes all transitive
+        // primary outputs. We only want to remove this node.
+        _nodesByPackage[existing.id.package].remove(existing.id.path);
+        node.outputs.addAll(existing.outputs);
+        node.primaryOutputs.addAll(existing.primaryOutputs);
+      } else {
+        throw StateError(
+            'Tried to add node ${node.id} to the asset graph but it already '
+            'exists.');
+      }
+    }
+    _nodesByPackage.putIfAbsent(node.id.package, () => {})[node.id.path] = node;
+  }
+
+  /// Adds [assetIds] as [InternalAssetNode]s to this graph.
+  Iterable<AssetNode> _addInternalSources(Set<AssetId> assetIds) sync* {
+    for (var id in assetIds) {
+      var node = InternalAssetNode(id);
+      _add(node);
+      yield node;
+    }
+  }
+
+  /// Adds [PlaceHolderAssetNode]s for every package in [packageGraph].
+  Set<AssetId> _addPlaceHolderNodes(PackageGraph packageGraph) {
+    var placeholders = placeholderIdsFor(packageGraph);
+    for (var id in placeholders) {
+      _add(PlaceHolderAssetNode(id));
+    }
+    return placeholders;
+  }
+
+  /// Adds [assetIds] as [AssetNode]s to this graph, and returns the newly
+  /// created nodes.
+  List<AssetNode> _addSources(Set<AssetId> assetIds) {
+    return assetIds.map((id) {
+      var node = SourceAssetNode(id);
+      _add(node);
+      return node;
+    }).toList();
+  }
+
+  /// Adds [BuilderOptionsAssetNode]s for all [buildPhases] to this graph.
+  void _addBuilderOptionsNodes(List<BuildPhase> buildPhases) {
+    for (var phaseNum = 0; phaseNum < buildPhases.length; phaseNum++) {
+      var phase = buildPhases[phaseNum];
+      if (phase is InBuildPhase) {
+        add(BuilderOptionsAssetNode(builderOptionsIdForAction(phase, phaseNum),
+            computeBuilderOptionsDigest(phase.builderOptions)));
+      } else if (phase is PostBuildPhase) {
+        var actionNum = 0;
+        for (var builderAction in phase.builderActions) {
+          add(BuilderOptionsAssetNode(
+              builderOptionsIdForAction(builderAction, actionNum),
+              computeBuilderOptionsDigest(builderAction.builderOptions)));
+          actionNum++;
+        }
+      } else {
+        throw StateError('Invalid action type $phase');
+      }
+    }
+  }
+
+  /// Uses [digestReader] to compute the [Digest] for [nodes] and set the
+  /// `lastKnownDigest` field.
+  Future<void> _setLastKnownDigests(
+      Iterable<AssetNode> nodes, AssetReader digestReader) async {
+    await Future.wait(nodes.map((node) async {
+      node.lastKnownDigest = await digestReader.digest(node.id);
+    }));
+  }
+
+  /// Removes the node representing [id] from the graph, and all of its
+  /// `primaryOutput`s.
+  ///
+  /// Also removes all edges between all removed nodes and other nodes.
+  ///
+  /// Returns a [Set<AssetId>] of all removed nodes.
+  Set<AssetId> _removeRecursive(AssetId id, {Set<AssetId> removedIds}) {
+    removedIds ??= <AssetId>{};
+    var node = get(id);
+    if (node == null) return removedIds;
+    removedIds.add(id);
+    for (var anchor in node.anchorOutputs.toList()) {
+      _removeRecursive(anchor, removedIds: removedIds);
+    }
+    for (var output in node.primaryOutputs.toList()) {
+      _removeRecursive(output, removedIds: removedIds);
+    }
+    for (var output in node.outputs) {
+      var inputsNode = get(output) as NodeWithInputs;
+      if (inputsNode != null) {
+        inputsNode.inputs.remove(id);
+        if (inputsNode is GlobAssetNode) {
+          inputsNode.results.remove(id);
+        }
+      }
+    }
+    if (node is NodeWithInputs) {
+      for (var input in node.inputs) {
+        var inputNode = get(input);
+        // We may have already removed this node entirely.
+        if (inputNode != null) {
+          inputNode.outputs.remove(id);
+          inputNode.primaryOutputs.remove(id);
+        }
+      }
+      if (node is GeneratedAssetNode) {
+        get(node.builderOptionsId).outputs.remove(id);
+      }
+    }
+    // Synthetic nodes need to be kept to retain dependency tracking.
+    if (node is! SyntheticSourceAssetNode) {
+      _nodesByPackage[id.package].remove(id.path);
+    }
+    return removedIds;
+  }
+
+  /// All nodes in the graph, whether source files or generated outputs.
+  Iterable<AssetNode> get allNodes =>
+      _nodesByPackage.values.expand((pkdIds) => pkdIds.values);
+
+  /// All nodes in the graph for `package`.
+  Iterable<AssetNode> packageNodes(String package) =>
+      _nodesByPackage[package]?.values ?? [];
+
+  /// All the generated outputs in the graph.
+  Iterable<AssetId> get outputs =>
+      allNodes.where((n) => n.isGenerated).map((n) => n.id);
+
+  /// The outputs which were, or would have been, produced by failing actions.
+  Iterable<GeneratedAssetNode> get failedOutputs => allNodes
+      .where((n) =>
+          n is GeneratedAssetNode &&
+          n.isFailure &&
+          n.state == NodeState.upToDate)
+      .map((n) => n as GeneratedAssetNode);
+
+  /// All the generated outputs for a particular phase.
+  Iterable<GeneratedAssetNode> outputsForPhase(String package, int phase) =>
+      packageNodes(package)
+          .whereType<GeneratedAssetNode>()
+          .where((n) => n.phaseNumber == phase);
+
+  /// All the source files in the graph.
+  Iterable<AssetId> get sources =>
+      allNodes.whereType<SourceAssetNode>().map((n) => n.id);
+
+  /// Updates graph structure, invalidating and deleting any outputs that were
+  /// affected.
+  ///
+  /// Returns the list of [AssetId]s that were invalidated.
+  Future<Set<AssetId>> updateAndInvalidate(
+      List<BuildPhase> buildPhases,
+      Map<AssetId, ChangeType> updates,
+      String rootPackage,
+      Future Function(AssetId id) delete,
+      AssetReader digestReader) async {
+    var newIds = <AssetId>{};
+    var modifyIds = <AssetId>{};
+    var removeIds = <AssetId>{};
+    updates.forEach((id, changeType) {
+      if (changeType != ChangeType.ADD && get(id) == null) return;
+      switch (changeType) {
+        case ChangeType.ADD:
+          newIds.add(id);
+          break;
+        case ChangeType.MODIFY:
+          modifyIds.add(id);
+          break;
+        case ChangeType.REMOVE:
+          removeIds.add(id);
+          break;
+      }
+    });
+
+    var newAndModifiedNodes = modifyIds.map(get).toList()
+      ..addAll(_addSources(newIds));
+    // Pre-emptively compute digests for the new and modified nodes we know have
+    // outputs.
+    await _setLastKnownDigests(
+        newAndModifiedNodes.where((node) =>
+            node.isValidInput &&
+            (node.outputs.isNotEmpty ||
+                node.primaryOutputs.isNotEmpty ||
+                node.lastKnownDigest != null)),
+        digestReader);
+
+    // Collects the set of all transitive ids to be removed from the graph,
+    // based on the removed `SourceAssetNode`s by following the
+    // `primaryOutputs`.
+    var transitiveRemovedIds = <AssetId>{};
+    void addTransitivePrimaryOutputs(AssetId id) {
+      transitiveRemovedIds.add(id);
+      get(id).primaryOutputs.forEach(addTransitivePrimaryOutputs);
+    }
+
+    removeIds
+        .where((id) => get(id) is SourceAssetNode)
+        .forEach(addTransitivePrimaryOutputs);
+
+    // The generated nodes to actually delete from the file system.
+    var idsToDelete = Set<AssetId>.from(transitiveRemovedIds)
+      ..removeAll(removeIds);
+
+    // We definitely need to update manually deleted outputs.
+    for (var deletedOutput
+        in removeIds.map(get).whereType<GeneratedAssetNode>()) {
+      deletedOutput.state = NodeState.definitelyNeedsUpdate;
+    }
+
+    // Transitively invalidates all assets. This needs to happen after the
+    // structure of the graph has been updated.
+    var invalidatedIds = <AssetId>{};
+
+    var newGeneratedOutputs =
+        _addOutputsForSources(buildPhases, newIds, rootPackage);
+    var allNewAndDeletedIds =
+        Set.of(newGeneratedOutputs.followedBy(transitiveRemovedIds));
+
+    void invalidateNodeAndDeps(AssetId id) {
+      var node = get(id);
+      if (node == null) return;
+      if (!invalidatedIds.add(id)) return;
+
+      if (node is NodeWithInputs && node.state == NodeState.upToDate) {
+        node.state = NodeState.mayNeedUpdate;
+      }
+
+      // Update all outputs of this asset as well.
+      for (var output in node.outputs) {
+        invalidateNodeAndDeps(output);
+      }
+    }
+
+    for (var changed in updates.keys.followedBy(newGeneratedOutputs)) {
+      invalidateNodeAndDeps(changed);
+    }
+
+    // For all new or deleted assets, check if they match any glob nodes and
+    // invalidate those.
+    for (var id in allNewAndDeletedIds) {
+      var samePackageGlobNodes = packageNodes(id.package)
+          .whereType<GlobAssetNode>()
+          .where((n) => n.state == NodeState.upToDate);
+      for (final node in samePackageGlobNodes) {
+        if (node.glob.matches(id.path)) {
+          invalidateNodeAndDeps(node.id);
+          node.state = NodeState.mayNeedUpdate;
+        }
+      }
+    }
+
+    // Delete all the invalidated assets, then remove them from the graph. This
+    // order is important because some `AssetWriter`s throw if the id is not in
+    // the graph.
+    await Future.wait(idsToDelete.map(delete));
+
+    // Remove all deleted source assets from the graph, which also recursively
+    // removes all their primary outputs.
+    for (var id in removeIds.where((id) => get(id) is SourceAssetNode)) {
+      invalidateNodeAndDeps(id);
+      _removeRecursive(id);
+    }
+
+    return invalidatedIds;
+  }
+
+  /// Crawl up primary inputs to see if the original Source file matches the
+  /// glob on [action].
+  bool _actionMatches(BuildAction action, AssetId input) {
+    if (input.package != action.package) return false;
+    if (!action.generateFor.matches(input)) return false;
+    Iterable<String> inputExtensions;
+    if (action is InBuildPhase) {
+      inputExtensions = action.builder.buildExtensions.keys;
+    } else if (action is PostBuildAction) {
+      inputExtensions = action.builder.inputExtensions;
+    } else {
+      throw StateError('Unrecognized action type $action');
+    }
+    if (!inputExtensions.any(input.path.endsWith)) {
+      return false;
+    }
+    var inputNode = get(input);
+    while (inputNode is GeneratedAssetNode) {
+      inputNode = get((inputNode as GeneratedAssetNode).primaryInput);
+    }
+    return action.targetSources.matches(inputNode.id);
+  }
+
+  /// Returns a set containing [newSources] plus any new generated sources
+  /// based on [buildPhases], and updates this graph to contain all the
+  /// new outputs.
+  ///
+  /// If [placeholders] is supplied they will be added to [newSources] to create
+  /// the full input set.
+  Set<AssetId> _addOutputsForSources(
+      List<BuildPhase> buildPhases, Set<AssetId> newSources, String rootPackage,
+      {Set<AssetId> placeholders}) {
+    var allInputs = Set<AssetId>.from(newSources);
+    if (placeholders != null) allInputs.addAll(placeholders);
+
+    for (var phaseNum = 0; phaseNum < buildPhases.length; phaseNum++) {
+      var phase = buildPhases[phaseNum];
+      if (phase is InBuildPhase) {
+        allInputs.addAll(_addInBuildPhaseOutputs(
+          phase,
+          phaseNum,
+          allInputs,
+          buildPhases,
+          rootPackage,
+        ));
+      } else if (phase is PostBuildPhase) {
+        _addPostBuildPhaseAnchors(phase, allInputs);
+      } else {
+        throw StateError('Unrecognized phase type $phase');
+      }
+    }
+    return allInputs;
+  }
+
+  /// Adds all [GeneratedAssetNode]s for [phase] given [allInputs].
+  ///
+  /// May remove some items from [allInputs], if they are deemed to actually be
+  /// outputs of this phase and not original sources.
+  ///
+  /// Returns all newly created asset ids.
+  Set<AssetId> _addInBuildPhaseOutputs(
+      InBuildPhase phase,
+      int phaseNum,
+      Set<AssetId> allInputs,
+      List<BuildPhase> buildPhases,
+      String rootPackage) {
+    var phaseOutputs = <AssetId>{};
+    var buildOptionsNodeId = builderOptionsIdForAction(phase, phaseNum);
+    var builderOptionsNode = get(buildOptionsNodeId) as BuilderOptionsAssetNode;
+    var inputs =
+        allInputs.where((input) => _actionMatches(phase, input)).toList();
+    for (var input in inputs) {
+      // We might have deleted some inputs during this loop, if they turned
+      // out to be generated assets.
+      if (!allInputs.contains(input)) continue;
+      var node = get(input);
+      assert(node != null, 'The node from `$input` does not exist.');
+
+      var outputs = expectedOutputs(phase.builder, input);
+      phaseOutputs.addAll(outputs);
+      node.primaryOutputs.addAll(outputs);
+      var deleted = _addGeneratedOutputs(
+          outputs, phaseNum, builderOptionsNode, buildPhases, rootPackage,
+          primaryInput: input, isHidden: phase.hideOutput);
+      allInputs.removeAll(deleted);
+      // We may delete source nodes that were producing outputs previously.
+      // Detect this by checking for deleted nodes that no longer exist in the
+      // graph at all, and remove them from `phaseOutputs`.
+      phaseOutputs.removeAll(deleted.where((id) => !contains(id)));
+    }
+    return phaseOutputs;
+  }
+
+  /// Adds all [PostProcessAnchorNode]s for [phase] given [allInputs];
+  ///
+  /// Does not return anything because [PostProcessAnchorNode]s are synthetic
+  /// and should not be treated as inputs.
+  void _addPostBuildPhaseAnchors(PostBuildPhase phase, Set<AssetId> allInputs) {
+    var actionNum = 0;
+    for (var action in phase.builderActions) {
+      var inputs = allInputs.where((input) => _actionMatches(action, input));
+      for (var input in inputs) {
+        var buildOptionsNodeId = builderOptionsIdForAction(action, actionNum);
+        var anchor = PostProcessAnchorNode.forInputAndAction(
+            input, actionNum, buildOptionsNodeId);
+        add(anchor);
+        get(input).anchorOutputs.add(anchor.id);
+      }
+      actionNum++;
+    }
+  }
+
+  /// Adds [outputs] as [GeneratedAssetNode]s to the graph.
+  ///
+  /// If there are existing [SourceAssetNode]s or [SyntheticSourceAssetNode]s
+  /// that overlap the [GeneratedAssetNode]s, then they will be replaced with
+  /// [GeneratedAssetNode]s, and all their `primaryOutputs` will be removed
+  /// from the graph as well. The return value is the set of assets that were
+  /// removed from the graph.
+  Set<AssetId> _addGeneratedOutputs(
+      Iterable<AssetId> outputs,
+      int phaseNumber,
+      BuilderOptionsAssetNode builderOptionsNode,
+      List<BuildPhase> buildPhases,
+      String rootPackage,
+      {AssetId primaryInput,
+      @required bool isHidden}) {
+    var removed = <AssetId>{};
+    for (var output in outputs) {
+      AssetNode existing;
+      // When any outputs aren't hidden we can pick up old generated outputs as
+      // regular `AssetNode`s, we need to delete them and all their primary
+      // outputs, and replace them with a `GeneratedAssetNode`.
+      if (contains(output)) {
+        existing = get(output);
+        if (existing is GeneratedAssetNode) {
+          throw DuplicateAssetNodeException(
+              rootPackage,
+              existing.id,
+              (buildPhases[existing.phaseNumber] as InBuildPhase).builderLabel,
+              (buildPhases[phaseNumber] as InBuildPhase).builderLabel);
+        }
+        _removeRecursive(output, removedIds: removed);
+      }
+
+      var newNode = GeneratedAssetNode(output,
+          phaseNumber: phaseNumber,
+          primaryInput: primaryInput,
+          state: NodeState.definitelyNeedsUpdate,
+          wasOutput: false,
+          isFailure: false,
+          builderOptionsId: builderOptionsNode.id,
+          isHidden: isHidden);
+      if (existing != null) {
+        newNode.outputs.addAll(existing.outputs);
+      }
+      builderOptionsNode.outputs.add(output);
+      _add(newNode);
+    }
+    return removed;
+  }
+
+  @override
+  String toString() => allNodes.toList().toString();
+
+  // TODO remove once tests are updated
+  void add(AssetNode node) => _add(node);
+  Set<AssetId> remove(AssetId id) => _removeRecursive(id);
+}
+
+/// Computes a [Digest] for [buildPhases] which can be used to compare one set
+/// of [BuildPhase]s against another.
+Digest computeBuildPhasesDigest(Iterable<BuildPhase> buildPhases) {
+  var digestSink = AccumulatorSink<Digest>();
+  md5.startChunkedConversion(digestSink)
+    ..add(buildPhases.map((phase) => phase.identity).toList())
+    ..close();
+  assert(digestSink.events.length == 1);
+  return digestSink.events.first;
+}
+
+Digest computeBuilderOptionsDigest(BuilderOptions options) =>
+    md5.convert(utf8.encode(json.encode(options.config)));
+
+AssetId builderOptionsIdForAction(BuildAction action, int actionNum) {
+  if (action is InBuildPhase) {
+    return AssetId(action.package, 'Phase$actionNum.builderOptions');
+  } else if (action is PostBuildAction) {
+    return AssetId(action.package, 'PostPhase$actionNum.builderOptions');
+  } else {
+    throw StateError('Unsupported action type $action');
+  }
+}
+
+Set<AssetId> placeholderIdsFor(PackageGraph packageGraph) =>
+    Set<AssetId>.from(packageGraph.allPackages.keys.expand((package) => [
+          AssetId(package, r'lib/$lib$'),
+          AssetId(package, r'test/$test$'),
+          AssetId(package, r'web/$web$'),
+          AssetId(package, r'$package$'),
+        ]));
diff --git a/build_runner_core/lib/src/asset_graph/node.dart b/build_runner_core/lib/src/asset_graph/node.dart
new file mode 100644
index 0000000..5419caa
--- /dev/null
+++ b/build_runner_core/lib/src/asset_graph/node.dart
@@ -0,0 +1,300 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 'dart:collection';
+import 'dart:convert';
+
+import 'package:build/build.dart';
+import 'package:crypto/crypto.dart';
+import 'package:glob/glob.dart';
+import 'package:meta/meta.dart';
+
+import '../generate/phase.dart';
+
+/// A node in the asset graph which may be an input to other assets.
+abstract class AssetNode {
+  final AssetId id;
+
+  /// The assets that any [Builder] in the build graph declares it may output
+  /// when run on this asset.
+  final Set<AssetId> primaryOutputs = <AssetId>{};
+
+  /// The [AssetId]s of all generated assets which are output by a [Builder]
+  /// which reads this asset.
+  final Set<AssetId> outputs = <AssetId>{};
+
+  /// The [AssetId]s of all [PostProcessAnchorNode] assets for which this node
+  /// is the primary input.
+  final Set<AssetId> anchorOutputs = <AssetId>{};
+
+  /// The [Digest] for this node in its last known state.
+  ///
+  /// May be `null` if this asset has no outputs, or if it doesn't actually
+  /// exist.
+  Digest lastKnownDigest;
+
+  /// Whether or not this node was an output of this build.
+  bool get isGenerated => false;
+
+  /// Whether or not this asset type can be read.
+  ///
+  /// This does not indicate whether or not this specific node actually exists
+  /// at this moment in time.
+  bool get isReadable => true;
+
+  /// The IDs of the [PostProcessAnchorNode] for post process builder which
+  /// requested to delete this asset.
+  final Set<AssetId> deletedBy = <AssetId>{};
+
+  /// Whether the node is deleted.
+  ///
+  /// Deleted nodes are ignored in the final merge step and watch handlers.
+  bool get isDeleted => deletedBy.isNotEmpty;
+
+  /// Whether or not this node can be read by a builder as a primary or
+  /// secondary input.
+  ///
+  /// Some nodes are valid primary inputs but are not readable (see
+  /// [PlaceHolderAssetNode]), while others are readable in the overall build
+  /// system  but are not valid builder inputs (see [InternalAssetNode]).
+  bool get isValidInput => true;
+
+  /// Whether or not changes to this node will have any effect on other nodes.
+  ///
+  /// Be default, if we haven't computed a digest for this asset and it has no
+  /// outputs, then it isn't interesting.
+  ///
+  /// Checking for a digest alone isn't enough because a file may be deleted
+  /// and re-added, in which case it won't have a digest.
+  bool get isInteresting => outputs.isNotEmpty || lastKnownDigest != null;
+
+  AssetNode(this.id, {this.lastKnownDigest});
+
+  /// Work around issue where you can't mixin classes into a class with optional
+  /// constructor args.
+  AssetNode._forMixins(this.id);
+
+  /// Work around issue where you can't mixin classes into a class with optional
+  /// constructor args, this one includes the digest.
+  AssetNode._forMixinsWithDigest(this.id, this.lastKnownDigest);
+
+  @override
+  String toString() => 'AssetNode: $id';
+}
+
+/// A node representing some internal asset.
+///
+/// These nodes are not used as primary inputs, but they are tracked in the
+/// asset graph and are readable.
+class InternalAssetNode extends AssetNode {
+  // These don't have [outputs] but they are interesting regardless.
+  @override
+  bool get isInteresting => true;
+
+  @override
+  bool get isValidInput => false;
+
+  InternalAssetNode(AssetId id, {Digest lastKnownDigest})
+      : super(id, lastKnownDigest: lastKnownDigest);
+
+  @override
+  String toString() => 'InternalAssetNode: $id';
+}
+
+/// A node which is an original source asset (not generated).
+class SourceAssetNode extends AssetNode {
+  SourceAssetNode(AssetId id, {Digest lastKnownDigest})
+      : super(id, lastKnownDigest: lastKnownDigest);
+
+  @override
+  String toString() => 'SourceAssetNode: $id';
+}
+
+/// States for nodes that can be invalidated.
+enum NodeState {
+  // This node does not need an update, and no checks need to be performed.
+  upToDate,
+  // This node may need an update, the inputs hash should be checked for
+  // changes.
+  mayNeedUpdate,
+  // This node definitely needs an update, the inputs hash check can be skipped.
+  definitelyNeedsUpdate,
+}
+
+/// A generated node in the asset graph.
+class GeneratedAssetNode extends AssetNode implements NodeWithInputs {
+  @override
+  bool get isGenerated => true;
+
+  @override
+  final int phaseNumber;
+
+  /// The primary input which generated this node.
+  final AssetId primaryInput;
+
+  @override
+  NodeState state;
+
+  /// Whether the asset was actually output.
+  bool wasOutput;
+
+  /// All the inputs that were read when generating this asset, or deciding not
+  /// to generate it.
+  ///
+  /// This needs to be an ordered set because we compute combined input digests
+  /// using this later on.
+  @override
+  HashSet<AssetId> inputs;
+
+  /// A digest combining all digests of all previous inputs.
+  ///
+  /// Used to determine whether all the inputs to a build step are identical to
+  /// the previous run, indicating that the previous output is still valid.
+  Digest previousInputsDigest;
+
+  /// Whether the action which did or would produce this node failed.
+  bool isFailure;
+
+  /// The [AssetId] of the node representing the [BuilderOptions] used to create
+  /// this node.
+  final AssetId builderOptionsId;
+
+  /// Whether the asset should be placed in the build cache.
+  final bool isHidden;
+
+  GeneratedAssetNode(
+    AssetId id, {
+    Digest lastKnownDigest,
+    Iterable<AssetId> inputs,
+    this.previousInputsDigest,
+    @required this.isHidden,
+    @required this.state,
+    @required this.phaseNumber,
+    @required this.wasOutput,
+    @required this.isFailure,
+    @required this.primaryInput,
+    @required this.builderOptionsId,
+  })  : inputs = inputs != null ? HashSet.from(inputs) : HashSet(),
+        super(id, lastKnownDigest: lastKnownDigest);
+
+  @override
+  String toString() =>
+      'GeneratedAssetNode: $id generated from input $primaryInput.';
+}
+
+/// A node which is not a generated or source asset.
+///
+/// These are typically not readable or valid as inputs.
+abstract class _SyntheticAssetNode implements AssetNode {
+  @override
+  bool get isReadable => false;
+
+  @override
+  bool get isValidInput => false;
+}
+
+/// A [_SyntheticAssetNode] representing a non-existent source.
+///
+/// Typically these are created as a result of `canRead` calls for assets that
+/// don't exist in the graph. We still need to set up proper dependencies so
+/// that if that asset gets added later the outputs are properly invalidated.
+class SyntheticSourceAssetNode extends AssetNode with _SyntheticAssetNode {
+  SyntheticSourceAssetNode(AssetId id) : super._forMixins(id);
+}
+
+/// A [_SyntheticAssetNode] which represents an individual [BuilderOptions]
+/// object.
+///
+/// These are used to track the state of a [BuilderOptions] object, and all
+/// [GeneratedAssetNode]s should depend on one of these nodes, which represents
+/// their configuration.
+class BuilderOptionsAssetNode extends AssetNode with _SyntheticAssetNode {
+  BuilderOptionsAssetNode(AssetId id, Digest lastKnownDigest)
+      : super._forMixinsWithDigest(id, lastKnownDigest);
+
+  @override
+  String toString() => 'BuildOptionsAssetNode: $id';
+}
+
+/// Placeholder assets are magic files that are usable as inputs but are not
+/// readable.
+class PlaceHolderAssetNode extends AssetNode with _SyntheticAssetNode {
+  @override
+  bool get isValidInput => true;
+
+  PlaceHolderAssetNode(AssetId id) : super._forMixins(id);
+
+  @override
+  String toString() => 'PlaceHolderAssetNode: $id';
+}
+
+/// A [_SyntheticAssetNode] which is created for each [primaryInput] to a
+/// [PostBuildAction].
+///
+/// The [outputs] of this node are the individual outputs created for the
+/// [primaryInput] during the [PostBuildAction] at index [actionNumber].
+class PostProcessAnchorNode extends AssetNode with _SyntheticAssetNode {
+  final int actionNumber;
+  final AssetId builderOptionsId;
+  final AssetId primaryInput;
+  Digest previousInputsDigest;
+
+  PostProcessAnchorNode(
+      AssetId id, this.primaryInput, this.actionNumber, this.builderOptionsId,
+      {this.previousInputsDigest})
+      : super._forMixins(id);
+
+  factory PostProcessAnchorNode.forInputAndAction(
+      AssetId primaryInput, int actionNumber, AssetId builderOptionsId) {
+    return PostProcessAnchorNode(
+        primaryInput.addExtension('.post_anchor.$actionNumber'),
+        primaryInput,
+        actionNumber,
+        builderOptionsId);
+  }
+}
+
+/// A node representing a glob ran from a builder.
+///
+/// The [id] must always be unique to a given package, phase, and glob
+/// pattern.
+class GlobAssetNode extends InternalAssetNode implements NodeWithInputs {
+  final Glob glob;
+
+  /// All the potential inputs matching this glob.
+  ///
+  /// This field differs from [results] in that [GeneratedAssetNode] which may
+  /// have been readable but were not output are included here and not in
+  /// [results].
+  @override
+  HashSet<AssetId> inputs;
+
+  @override
+  bool get isReadable => false;
+
+  @override
+  final int phaseNumber;
+
+  /// The actual results of the glob.
+  List<AssetId> results;
+
+  @override
+  NodeState state;
+
+  GlobAssetNode(AssetId id, this.glob, this.phaseNumber, this.state,
+      {this.inputs, Digest lastKnownDigest, this.results})
+      : super(id, lastKnownDigest: lastKnownDigest);
+
+  static AssetId createId(String package, Glob glob, int phaseNum) => AssetId(
+      package, 'glob.$phaseNum.${base64.encode(utf8.encode(glob.pattern))}');
+}
+
+/// A node which has [inputs], a [NodeState], and a [phaseNumber].
+abstract class NodeWithInputs implements AssetNode {
+  HashSet<AssetId> inputs;
+
+  int get phaseNumber;
+
+  NodeState state;
+}
diff --git a/build_runner_core/lib/src/asset_graph/optional_output_tracker.dart b/build_runner_core/lib/src/asset_graph/optional_output_tracker.dart
new file mode 100644
index 0000000..f5a40b4
--- /dev/null
+++ b/build_runner_core/lib/src/asset_graph/optional_output_tracker.dart
@@ -0,0 +1,79 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'package:build/build.dart';
+import 'package:build_runner_core/build_runner_core.dart';
+
+import '../generate/phase.dart';
+import '../util/build_dirs.dart';
+import 'graph.dart';
+import 'node.dart';
+
+/// A cache of the results of checking whether outputs from optional build steps
+/// were required by in the current build.
+///
+/// An optional output becomes required if:
+/// - Any of it's transitive outputs is required (based on the criteria below).
+/// - It was output by the same build step as any required output.
+///
+/// Any outputs from non-optional phases are considered required, unless the
+/// following are all true.
+///  - [_buildDirs] is non-empty.
+///  - The output lives in a non-lib directory.
+///  - The outputs path is not prefixed by one of [_buildDirs].
+///  - If [_buildFilters] is non-empty and the output doesn't match one of the
+///    filters.
+///
+/// Non-required optional output might still exist in the generated directory
+/// and the asset graph but we should avoid serving them, outputting them in
+/// the merged directories, or considering a failed output as an overall.
+class OptionalOutputTracker {
+  final _checkedOutputs = <AssetId, bool>{};
+  final AssetGraph _assetGraph;
+  final Set<String> _buildDirs;
+  final Set<BuildFilter> _buildFilters;
+  final List<BuildPhase> _buildPhases;
+
+  OptionalOutputTracker(
+      this._assetGraph, this._buildDirs, this._buildFilters, this._buildPhases);
+
+  /// Returns whether [output] is required.
+  ///
+  /// If necessary crawls transitive outputs that read [output] or any other
+  /// assets generated by the same phase until it finds one which is required.
+  ///
+  /// [currentlyChecking] is used to aovid repeatedly checking the same outputs.
+  bool isRequired(AssetId output, [Set<AssetId> currentlyChecking]) {
+    currentlyChecking ??= <AssetId>{};
+    if (currentlyChecking.contains(output)) return false;
+    currentlyChecking.add(output);
+
+    final node = _assetGraph.get(output);
+    if (node is! GeneratedAssetNode) return true;
+    final generatedNode = node as GeneratedAssetNode;
+    final phase = _buildPhases[generatedNode.phaseNumber];
+    if (!phase.isOptional &&
+        shouldBuildForDirs(output, _buildDirs, _buildFilters, phase)) {
+      return true;
+    }
+    return _checkedOutputs.putIfAbsent(
+        output,
+        () =>
+            generatedNode.outputs
+                .any((o) => isRequired(o, currentlyChecking)) ||
+            _assetGraph
+                .outputsForPhase(output.package, generatedNode.phaseNumber)
+                .where((n) => n.primaryInput == generatedNode.primaryInput)
+                .map((n) => n.id)
+                .any((o) => isRequired(o, currentlyChecking)));
+  }
+
+  /// Clears the cache of which assets were required.
+  ///
+  /// If the tracker is used across multiple builds it must be reset in between
+  /// each one.
+  void reset() {
+    _checkedOutputs.clear();
+  }
+}
diff --git a/build_runner_core/lib/src/asset_graph/serialization.dart b/build_runner_core/lib/src/asset_graph/serialization.dart
new file mode 100644
index 0000000..de14470
--- /dev/null
+++ b/build_runner_core/lib/src/asset_graph/serialization.dart
@@ -0,0 +1,513 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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.
+
+part of 'graph.dart';
+
+/// Part of the serialized graph, used to ensure versioning constraints.
+///
+/// This should be incremented any time the serialize/deserialize formats
+/// change.
+const _version = 22;
+
+/// Deserializes an [AssetGraph] from a [Map].
+class _AssetGraphDeserializer {
+  // Iteration order does not matter
+  final _idToAssetId = HashMap<int, AssetId>();
+  final Map _serializedGraph;
+
+  _AssetGraphDeserializer._(this._serializedGraph);
+
+  factory _AssetGraphDeserializer(List<int> bytes) {
+    dynamic decoded;
+    try {
+      decoded = jsonDecode(utf8.decode(bytes));
+    } on FormatException {
+      throw AssetGraphCorruptedException();
+    }
+    if (decoded is! Map) throw AssetGraphCorruptedException();
+    final serializedGraph = decoded as Map;
+    if (serializedGraph['version'] != _version) {
+      throw AssetGraphCorruptedException();
+    }
+    return _AssetGraphDeserializer._(serializedGraph);
+  }
+
+  /// Perform the deserialization, should only be called once.
+  AssetGraph deserialize() {
+    var graph = AssetGraph._(
+        _deserializeDigest(_serializedGraph['buildActionsDigest'] as String),
+        _serializedGraph['dart_version'] as String);
+
+    var packageNames = _serializedGraph['packages'] as List;
+
+    // Read in the id => AssetId map from the graph first.
+    var assetPaths = _serializedGraph['assetPaths'] as List;
+    for (var i = 0; i < assetPaths.length; i += 2) {
+      var packageName = packageNames[assetPaths[i + 1] as int] as String;
+      _idToAssetId[i ~/ 2] = AssetId(packageName, assetPaths[i] as String);
+    }
+
+    // Read in all the nodes and their outputs.
+    //
+    // Note that this does not read in the inputs of generated nodes.
+    for (var serializedItem in _serializedGraph['nodes']) {
+      graph._add(_deserializeAssetNode(serializedItem as List));
+    }
+
+    // Update the inputs of all generated nodes based on the outputs of the
+    // current nodes.
+    for (var node in graph.allNodes) {
+      // These aren't explicitly added as inputs.
+      if (node is BuilderOptionsAssetNode) continue;
+
+      for (var output in node.outputs) {
+        if (output == null) {
+          log.severe('Found a null output from ${node.id} which is a '
+              '${node.runtimeType}. If you encounter this error please copy '
+              'the details from this message and add them to '
+              'https://github.com/dart-lang/build/issues/1804.');
+          throw AssetGraphCorruptedException();
+        }
+        var inputsNode = graph.get(output) as NodeWithInputs;
+        if (inputsNode == null) {
+          log.severe('Failed to locate $output referenced from ${node.id} '
+              'which is a ${node.runtimeType}. If you encounter this error '
+              'please copy the details from this message and add them to '
+              'https://github.com/dart-lang/build/issues/1804.');
+          throw AssetGraphCorruptedException();
+        }
+        inputsNode.inputs ??= HashSet<AssetId>();
+        inputsNode.inputs.add(node.id);
+      }
+
+      if (node is PostProcessAnchorNode) {
+        graph.get(node.primaryInput).anchorOutputs.add(node.id);
+      }
+    }
+
+    return graph;
+  }
+
+  AssetNode _deserializeAssetNode(List serializedNode) {
+    AssetNode node;
+    var typeId =
+        _NodeType.values[serializedNode[_AssetField.NodeType.index] as int];
+    var id = _idToAssetId[serializedNode[_AssetField.Id.index] as int];
+    var serializedDigest = serializedNode[_AssetField.Digest.index] as String;
+    var digest = _deserializeDigest(serializedDigest);
+    switch (typeId) {
+      case _NodeType.Source:
+        assert(serializedNode.length == _WrappedAssetNode._length);
+        node = SourceAssetNode(id, lastKnownDigest: digest);
+        break;
+      case _NodeType.SyntheticSource:
+        assert(serializedNode.length == _WrappedAssetNode._length);
+        node = SyntheticSourceAssetNode(id);
+        break;
+      case _NodeType.Generated:
+        assert(serializedNode.length == _WrappedGeneratedAssetNode._length);
+        var offset = _AssetField.values.length;
+        node = GeneratedAssetNode(
+          id,
+          phaseNumber:
+              serializedNode[_GeneratedField.PhaseNumber.index + offset] as int,
+          primaryInput: _idToAssetId[
+              serializedNode[_GeneratedField.PrimaryInput.index + offset]
+                  as int],
+          state: NodeState.values[
+              serializedNode[_GeneratedField.State.index + offset] as int],
+          wasOutput: _deserializeBool(
+              serializedNode[_GeneratedField.WasOutput.index + offset] as int),
+          isFailure: _deserializeBool(
+              serializedNode[_GeneratedField.IsFailure.index + offset] as int),
+          builderOptionsId: _idToAssetId[
+              serializedNode[_GeneratedField.BuilderOptions.index + offset]
+                  as int],
+          lastKnownDigest: digest,
+          previousInputsDigest: _deserializeDigest(serializedNode[
+              _GeneratedField.PreviousInputsDigest.index + offset] as String),
+          isHidden: _deserializeBool(
+              serializedNode[_GeneratedField.IsHidden.index + offset] as int),
+        );
+        break;
+      case _NodeType.Glob:
+        assert(serializedNode.length == _WrappedGlobAssetNode._length);
+        var offset = _AssetField.values.length;
+        node = GlobAssetNode(
+          id,
+          Glob(serializedNode[_GlobField.Glob.index + offset] as String),
+          serializedNode[_GlobField.PhaseNumber.index + offset] as int,
+          NodeState
+              .values[serializedNode[_GlobField.State.index + offset] as int],
+          lastKnownDigest: digest,
+          results: _deserializeAssetIds(
+                  serializedNode[_GlobField.Results.index + offset] as List)
+              ?.toList(),
+        );
+        break;
+      case _NodeType.Internal:
+        assert(serializedNode.length == _WrappedAssetNode._length);
+        node = InternalAssetNode(id, lastKnownDigest: digest);
+        break;
+      case _NodeType.BuilderOptions:
+        assert(serializedNode.length == _WrappedAssetNode._length);
+        node = BuilderOptionsAssetNode(id, digest);
+        break;
+      case _NodeType.Placeholder:
+        assert(serializedNode.length == _WrappedAssetNode._length);
+        node = PlaceHolderAssetNode(id);
+        break;
+      case _NodeType.PostProcessAnchor:
+        assert(serializedNode.length == _WrappedPostProcessAnchorNode._length);
+        var offset = _AssetField.values.length;
+        node = PostProcessAnchorNode(
+            id,
+            _idToAssetId[
+                serializedNode[_PostAnchorField.PrimaryInput.index + offset]
+                    as int],
+            serializedNode[_PostAnchorField.ActionNumber.index + offset] as int,
+            _idToAssetId[
+                serializedNode[_PostAnchorField.BuilderOptions.index + offset]
+                    as int],
+            previousInputsDigest: _deserializeDigest(serializedNode[
+                    _PostAnchorField.PreviousInputsDigest.index + offset]
+                as String));
+        break;
+    }
+    node.outputs.addAll(_deserializeAssetIds(
+        serializedNode[_AssetField.Outputs.index] as List));
+    node.primaryOutputs.addAll(_deserializeAssetIds(
+        serializedNode[_AssetField.PrimaryOutputs.index] as List));
+    node.deletedBy.addAll(_deserializeAssetIds(
+        (serializedNode[_AssetField.DeletedBy.index] as List)?.cast<int>()));
+    return node;
+  }
+
+  Iterable<AssetId> _deserializeAssetIds(List serializedIds) =>
+      serializedIds.map((id) => _idToAssetId[id]);
+
+  bool _deserializeBool(int value) => value != 0;
+}
+
+/// Serializes an [AssetGraph] into a [Map].
+class _AssetGraphSerializer {
+  // Iteration order does not matter
+  final _assetIdToId = HashMap<AssetId, int>();
+
+  final AssetGraph _graph;
+
+  _AssetGraphSerializer(this._graph);
+
+  /// Perform the serialization, should only be called once.
+  List<int> serialize() {
+    var pathId = 0;
+    // [path0, packageId0, path1, packageId1, ...]
+    var assetPaths = <dynamic>[];
+    var packages = _graph._nodesByPackage.keys.toList(growable: false);
+    for (var node in _graph.allNodes) {
+      _assetIdToId[node.id] = pathId;
+      pathId++;
+      assetPaths..add(node.id.path)..add(packages.indexOf(node.id.package));
+    }
+
+    var result = <String, dynamic>{
+      'version': _version,
+      'dart_version': _graph.dartVersion,
+      'nodes': _graph.allNodes.map(_serializeNode).toList(growable: false),
+      'buildActionsDigest': _serializeDigest(_graph.buildPhasesDigest),
+      'packages': packages,
+      'assetPaths': assetPaths,
+    };
+    return utf8.encode(json.encode(result));
+  }
+
+  List _serializeNode(AssetNode node) {
+    if (node is GeneratedAssetNode) {
+      return _WrappedGeneratedAssetNode(node, this);
+    } else if (node is PostProcessAnchorNode) {
+      return _WrappedPostProcessAnchorNode(node, this);
+    } else if (node is GlobAssetNode) {
+      return _WrappedGlobAssetNode(node, this);
+    } else {
+      return _WrappedAssetNode(node, this);
+    }
+  }
+
+  int findAssetIndex(AssetId id,
+      {@required AssetId from, @required String field}) {
+    final index = _assetIdToId[id];
+    if (index == null) {
+      log.severe('The $field field in $from references a non-existent asset '
+          '$id and will corrupt the asset graph. '
+          'If you encounter this error please copy '
+          'the details from this message and add them to '
+          'https://github.com/dart-lang/build/issues/1804.');
+    }
+    return index;
+  }
+}
+
+/// Used to serialize the type of a node using an int.
+enum _NodeType {
+  Source,
+  SyntheticSource,
+  Generated,
+  Internal,
+  BuilderOptions,
+  Placeholder,
+  PostProcessAnchor,
+  Glob,
+}
+
+/// Field indexes for all [AssetNode]s
+enum _AssetField {
+  NodeType,
+  Id,
+  Outputs,
+  PrimaryOutputs,
+  Digest,
+  DeletedBy,
+}
+
+/// Field indexes for [GeneratedAssetNode]s
+enum _GeneratedField {
+  PrimaryInput,
+  WasOutput,
+  IsFailure,
+  PhaseNumber,
+  State,
+  PreviousInputsDigest,
+  BuilderOptions,
+  IsHidden,
+}
+
+/// Field indexes for [GlobAssetNode]s
+enum _GlobField {
+  PhaseNumber,
+  State,
+  Glob,
+  Results,
+}
+
+/// Field indexes for [PostProcessAnchorNode]s.
+enum _PostAnchorField {
+  ActionNumber,
+  BuilderOptions,
+  PreviousInputsDigest,
+  PrimaryInput,
+}
+
+/// Wraps an [AssetNode] in a class that implements [List] instead of
+/// creating a new list for each one.
+class _WrappedAssetNode extends Object with ListMixin implements List {
+  final AssetNode node;
+  final _AssetGraphSerializer serializer;
+
+  _WrappedAssetNode(this.node, this.serializer);
+
+  static final int _length = _AssetField.values.length;
+
+  @override
+  int get length => _length;
+
+  @override
+  set length(void _) => throw UnsupportedError(
+      'length setter not unsupported for WrappedAssetNode');
+
+  @override
+  Object operator [](int index) {
+    var fieldId = _AssetField.values[index];
+    switch (fieldId) {
+      case _AssetField.NodeType:
+        if (node is SourceAssetNode) {
+          return _NodeType.Source.index;
+        } else if (node is GeneratedAssetNode) {
+          return _NodeType.Generated.index;
+        } else if (node is GlobAssetNode) {
+          return _NodeType.Glob.index;
+        } else if (node is SyntheticSourceAssetNode) {
+          return _NodeType.SyntheticSource.index;
+        } else if (node is InternalAssetNode) {
+          return _NodeType.Internal.index;
+        } else if (node is BuilderOptionsAssetNode) {
+          return _NodeType.BuilderOptions.index;
+        } else if (node is PlaceHolderAssetNode) {
+          return _NodeType.Placeholder.index;
+        } else if (node is PostProcessAnchorNode) {
+          return _NodeType.PostProcessAnchor.index;
+        } else {
+          throw StateError('Unrecognized node type');
+        }
+        break;
+      case _AssetField.Id:
+        return serializer.findAssetIndex(node.id, from: node.id, field: 'id');
+      case _AssetField.Outputs:
+        return node.outputs
+            .map((id) =>
+                serializer.findAssetIndex(id, from: node.id, field: 'outputs'))
+            .toList(growable: false);
+      case _AssetField.PrimaryOutputs:
+        return node.primaryOutputs
+            .map((id) => serializer.findAssetIndex(id,
+                from: node.id, field: 'primaryOutputs'))
+            .toList(growable: false);
+      case _AssetField.Digest:
+        return _serializeDigest(node.lastKnownDigest);
+      case _AssetField.DeletedBy:
+        return node.deletedBy
+            .map((id) => serializer.findAssetIndex(id,
+                from: node.id, field: 'deletedBy'))
+            .toList(growable: false);
+      default:
+        throw RangeError.index(index, this);
+    }
+  }
+
+  @override
+  operator []=(void _, void __) =>
+      throw UnsupportedError('[]= not supported for WrappedAssetNode');
+}
+
+/// Wraps a [GeneratedAssetNode] in a class that implements [List] instead of
+/// creating a new list for each one.
+class _WrappedGeneratedAssetNode extends _WrappedAssetNode {
+  final GeneratedAssetNode generatedNode;
+
+  /// Offset in the serialized format for additional fields in this class but
+  /// not in [_WrappedAssetNode].
+  ///
+  /// Indexes below this number are forwarded to `super[index]`.
+  static final int _serializedOffset = _AssetField.values.length;
+
+  static final int _length = _serializedOffset + _GeneratedField.values.length;
+
+  @override
+  int get length => _length;
+
+  _WrappedGeneratedAssetNode(
+      this.generatedNode, _AssetGraphSerializer serializer)
+      : super(generatedNode, serializer);
+
+  @override
+  Object operator [](int index) {
+    if (index < _serializedOffset) return super[index];
+    var fieldId = _GeneratedField.values[index - _serializedOffset];
+    switch (fieldId) {
+      case _GeneratedField.PrimaryInput:
+        return generatedNode.primaryInput != null
+            ? serializer.findAssetIndex(generatedNode.primaryInput,
+                from: generatedNode.id, field: 'primaryInput')
+            : null;
+      case _GeneratedField.WasOutput:
+        return _serializeBool(generatedNode.wasOutput);
+      case _GeneratedField.IsFailure:
+        return _serializeBool(generatedNode.isFailure);
+      case _GeneratedField.PhaseNumber:
+        return generatedNode.phaseNumber;
+      case _GeneratedField.State:
+        return generatedNode.state.index;
+      case _GeneratedField.PreviousInputsDigest:
+        return _serializeDigest(generatedNode.previousInputsDigest);
+      case _GeneratedField.BuilderOptions:
+        return serializer.findAssetIndex(generatedNode.builderOptionsId,
+            from: generatedNode.id, field: 'builderOptions');
+      case _GeneratedField.IsHidden:
+        return _serializeBool(generatedNode.isHidden);
+      default:
+        throw RangeError.index(index, this);
+    }
+  }
+}
+
+/// Wraps a [GlobAssetNode] in a class that implements [List] instead of
+/// creating a new list for each one.
+class _WrappedGlobAssetNode extends _WrappedAssetNode {
+  final GlobAssetNode globNode;
+
+  /// Offset in the serialized format for additional fields in this class but
+  /// not in [_WrappedAssetNode].
+  ///
+  /// Indexes below this number are forwarded to `super[index]`.
+  static final int _serializedOffset = _AssetField.values.length;
+
+  static final int _length = _serializedOffset + _GlobField.values.length;
+
+  @override
+  int get length => _length;
+
+  _WrappedGlobAssetNode(this.globNode, _AssetGraphSerializer serializer)
+      : super(globNode, serializer);
+
+  @override
+  Object operator [](int index) {
+    if (index < _serializedOffset) return super[index];
+    var fieldId = _GlobField.values[index - _serializedOffset];
+    switch (fieldId) {
+      case _GlobField.PhaseNumber:
+        return globNode.phaseNumber;
+      case _GlobField.State:
+        return globNode.state.index;
+      case _GlobField.Glob:
+        return globNode.glob.pattern;
+      case _GlobField.Results:
+        return globNode.results
+            .map((id) => serializer.findAssetIndex(id,
+                from: globNode.id, field: 'results'))
+            .toList(growable: false);
+      default:
+        throw RangeError.index(index, this);
+    }
+  }
+}
+
+/// Wraps a [PostProcessAnchorNode] in a class that implements [List] instead of
+/// creating a new list for each one.
+class _WrappedPostProcessAnchorNode extends _WrappedAssetNode {
+  final PostProcessAnchorNode wrappedNode;
+
+  /// Offset in the serialized format for additional fields in this class but
+  /// not in [_WrappedAssetNode].
+  ///
+  /// Indexes below this number are forwarded to `super[index]`.
+  static final int _serializedOffset = _AssetField.values.length;
+
+  static final int _length = _serializedOffset + _PostAnchorField.values.length;
+
+  @override
+  int get length => _length;
+
+  _WrappedPostProcessAnchorNode(
+      this.wrappedNode, _AssetGraphSerializer serializer)
+      : super(wrappedNode, serializer);
+
+  @override
+  Object operator [](int index) {
+    if (index < _serializedOffset) return super[index];
+    var fieldId = _PostAnchorField.values[index - _serializedOffset];
+    switch (fieldId) {
+      case _PostAnchorField.ActionNumber:
+        return wrappedNode.actionNumber;
+      case _PostAnchorField.BuilderOptions:
+        return serializer.findAssetIndex(wrappedNode.builderOptionsId,
+            from: wrappedNode.id, field: 'builderOptions');
+      case _PostAnchorField.PreviousInputsDigest:
+        return _serializeDigest(wrappedNode.previousInputsDigest);
+      case _PostAnchorField.PrimaryInput:
+        return wrappedNode.primaryInput != null
+            ? serializer.findAssetIndex(wrappedNode.primaryInput,
+                from: wrappedNode.id, field: 'primaryInput')
+            : null;
+      default:
+        throw RangeError.index(index, this);
+    }
+  }
+}
+
+Digest _deserializeDigest(String serializedDigest) =>
+    serializedDigest == null ? null : Digest(base64.decode(serializedDigest));
+
+String _serializeDigest(Digest digest) =>
+    digest == null ? null : base64.encode(digest.bytes);
+
+int _serializeBool(bool value) => value ? 1 : 0;
diff --git a/build_runner_core/lib/src/changes/build_script_updates.dart b/build_runner_core/lib/src/changes/build_script_updates.dart
new file mode 100644
index 0000000..ed4c4f1
--- /dev/null
+++ b/build_runner_core/lib/src/changes/build_script_updates.dart
@@ -0,0 +1,127 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:mirrors';
+
+import 'package:build/build.dart';
+import 'package:logging/logging.dart';
+import 'package:path/path.dart' as p;
+
+import '../asset/reader.dart';
+import '../asset_graph/graph.dart';
+import '../package_graph/package_graph.dart';
+
+/// Functionality for detecting if the build script itself or any of its
+/// transitive imports have changed.
+abstract class BuildScriptUpdates {
+  /// Checks if the current running program has been updated, based on
+  /// [updatedIds].
+  bool hasBeenUpdated(Set<AssetId> updatedIds);
+
+  /// Creates a [BuildScriptUpdates] object, using [reader] to ensure that
+  /// the [assetGraph] is tracking digests for all transitive sources.
+  ///
+  /// If [disabled] is `true` then all checks are skipped and
+  /// [hasBeenUpdated] will always return `false`.
+  static Future<BuildScriptUpdates> create(RunnerAssetReader reader,
+      PackageGraph packageGraph, AssetGraph assetGraph,
+      {bool disabled = false}) async {
+    disabled ??= false;
+    if (disabled) return _NoopBuildScriptUpdates();
+    return _MirrorBuildScriptUpdates.create(reader, packageGraph, assetGraph);
+  }
+}
+
+/// Uses mirrors to find all transitive imports of the current script.
+class _MirrorBuildScriptUpdates implements BuildScriptUpdates {
+  final Set<AssetId> _allSources;
+  final bool _supportsIncrementalRebuilds;
+
+  _MirrorBuildScriptUpdates._(
+      this._supportsIncrementalRebuilds, this._allSources);
+
+  static Future<BuildScriptUpdates> create(RunnerAssetReader reader,
+      PackageGraph packageGraph, AssetGraph graph) async {
+    var supportsIncrementalRebuilds = true;
+    var rootPackage = packageGraph.root.name;
+    Set<AssetId> allSources;
+    var logger = Logger('BuildScriptUpdates');
+    try {
+      allSources = _urisForThisScript
+          .map((id) => _idForUri(id, rootPackage))
+          .where((id) => id != null)
+          .toSet();
+      var missing = allSources.firstWhere((id) => !graph.contains(id),
+          orElse: () => null);
+      if (missing != null) {
+        supportsIncrementalRebuilds = false;
+        logger.warning('$missing was not found in the asset graph, '
+            'incremental builds will not work.\n This probably means you '
+            'don\'t have your dependencies specified fully in your '
+            'pubspec.yaml.');
+      } else {
+        // Make sure we are tracking changes for all ids in [allSources].
+        for (var id in allSources) {
+          graph.get(id).lastKnownDigest ??= await reader.digest(id);
+        }
+      }
+    } on ArgumentError catch (_) {
+      supportsIncrementalRebuilds = false;
+      allSources = <AssetId>{};
+    }
+    return _MirrorBuildScriptUpdates._(supportsIncrementalRebuilds, allSources);
+  }
+
+  static Iterable<Uri> get _urisForThisScript =>
+      currentMirrorSystem().libraries.keys;
+
+  /// Checks if the current running program has been updated, based on
+  /// [updatedIds].
+  @override
+  bool hasBeenUpdated(Set<AssetId> updatedIds) {
+    if (!_supportsIncrementalRebuilds) return true;
+    return updatedIds.intersection(_allSources).isNotEmpty;
+  }
+
+  /// Attempts to return an [AssetId] for [uri].
+  ///
+  /// Returns `null` if the uri should be ignored, or throws an [ArgumentError]
+  /// if the [uri] is not recognized.
+  static AssetId _idForUri(Uri uri, String _rootPackage) {
+    switch (uri.scheme) {
+      case 'dart':
+        // TODO: check for sdk updates!
+        break;
+      case 'package':
+        var parts = uri.pathSegments;
+        return AssetId(parts[0],
+            p.url.joinAll(['lib', ...parts.getRange(1, parts.length)]));
+      case 'file':
+        var relativePath = p.relative(uri.toFilePath(), from: p.current);
+        return AssetId(_rootPackage, relativePath);
+      case 'data':
+        // Test runner uses a `data` scheme, don't invalidate for those.
+        if (uri.path.contains('package:test')) break;
+        continue unsupported;
+      case 'http':
+        continue unsupported;
+      unsupported:
+      default:
+        throw ArgumentError('Unsupported uri scheme `${uri.scheme}` found for '
+            'library in build script.\n'
+            'This probably means you are running in an unsupported '
+            'context, such as in an isolate or via `pub run`.\n'
+            'Full uri was: $uri.');
+    }
+    return null;
+  }
+}
+
+/// Always returns false for [hasBeenUpdated], used when we want to skip
+/// the build script checks.
+class _NoopBuildScriptUpdates implements BuildScriptUpdates {
+  @override
+  bool hasBeenUpdated(void _) => false;
+}
diff --git a/build_runner_core/lib/src/environment/build_environment.dart b/build_runner_core/lib/src/environment/build_environment.dart
new file mode 100644
index 0000000..23335d8
--- /dev/null
+++ b/build_runner_core/lib/src/environment/build_environment.dart
@@ -0,0 +1,57 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'package:build/build.dart';
+import 'package:logging/logging.dart';
+
+import '../asset/reader.dart';
+import '../asset/writer.dart';
+import '../generate/build_directory.dart';
+import '../generate/build_result.dart';
+import '../generate/finalized_assets_view.dart';
+
+/// Utilities to interact with the environment in which a build is running.
+///
+/// All side effects and user interaction should go through the build
+/// environment. An IO based environment can write to disk and interact through
+/// stdout/stdin, while a theoretical web or remote environment might interact
+/// over HTTP.
+abstract class BuildEnvironment {
+  RunnerAssetReader get reader;
+  RunnerAssetWriter get writer;
+
+  void onLog(LogRecord record);
+
+  /// Prompt the user for input.
+  ///
+  /// The message and choices are displayed to the user and the index of the
+  /// chosen option is returned.
+  ///
+  /// If this environmment is non-interactive (such as when running in a test)
+  /// this method should throw [NonInteractiveBuildException].
+  Future<int> prompt(String message, List<String> choices);
+
+  /// Invoked after each build, can modify the [BuildResult] in any way, even
+  /// converting it to a failure.
+  ///
+  /// The [finalizedAssetsView] can only be used until the returned [Future]
+  /// completes, it will expire afterwords since it can no longer guarantee a
+  /// consistent state.
+  ///
+  /// By default this returns the original result.
+  ///
+  /// Any operation may be performed, as determined by environment.
+  Future<BuildResult> finalizeBuild(
+          BuildResult buildResult,
+          FinalizedAssetsView finalizedAssetsView,
+          AssetReader assetReader,
+          Set<BuildDirectory> buildDirs) =>
+      Future.value(buildResult);
+}
+
+/// Thrown when the build attempts to prompt the users but no prompt is
+/// possible.
+class NonInteractiveBuildException implements Exception {}
diff --git a/build_runner_core/lib/src/environment/create_merged_dir.dart b/build_runner_core/lib/src/environment/create_merged_dir.dart
new file mode 100644
index 0000000..24ea463
--- /dev/null
+++ b/build_runner_core/lib/src/environment/create_merged_dir.dart
@@ -0,0 +1,323 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:io';
+
+import 'package:build/build.dart';
+import 'package:logging/logging.dart';
+import 'package:path/path.dart' as p;
+import 'package:pool/pool.dart';
+
+import '../asset/reader.dart';
+import '../environment/build_environment.dart';
+import '../generate/build_directory.dart';
+import '../generate/finalized_assets_view.dart';
+import '../logging/logging.dart';
+import '../package_graph/package_graph.dart';
+
+/// Pool for async file operations, we don't want to use too many file handles.
+final _descriptorPool = Pool(32);
+
+final _logger = Logger('CreateOutputDir');
+const _manifestName = '.build.manifest';
+const _manifestSeparator = '\n';
+
+/// Creates merged output directories for each [OutputLocation].
+///
+/// Returns whether it succeeded or not.
+Future<bool> createMergedOutputDirectories(
+    Set<BuildDirectory> buildDirs,
+    PackageGraph packageGraph,
+    BuildEnvironment environment,
+    AssetReader reader,
+    FinalizedAssetsView finalizedAssetsView,
+    bool outputSymlinksOnly) async {
+  if (outputSymlinksOnly && reader is! PathProvidingAssetReader) {
+    _logger.severe(
+        'The current environment does not support symlinks, but symlinks were '
+        'requested.');
+    return false;
+  }
+  var conflictingOutputs = _conflicts(buildDirs);
+  if (conflictingOutputs.isNotEmpty) {
+    _logger.severe('Unable to create merged directory. '
+        'Conflicting outputs for $conflictingOutputs');
+    return false;
+  }
+
+  for (var target in buildDirs) {
+    var output = target.outputLocation?.path;
+    if (output != null) {
+      if (!await _createMergedOutputDir(
+          output,
+          target.directory,
+          packageGraph,
+          environment,
+          reader,
+          finalizedAssetsView,
+          // TODO(grouma) - retrieve symlink information from target only.
+          outputSymlinksOnly || target.outputLocation.useSymlinks,
+          target.outputLocation.hoist)) {
+        _logger.severe('Unable to create merged directory for $output.');
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
+Set<String> _conflicts(Set<BuildDirectory> buildDirs) {
+  final seen = <String>{};
+  final conflicts = <String>{};
+  var outputLocations =
+      buildDirs.map((d) => d.outputLocation?.path).where((p) => p != null);
+  for (var location in outputLocations) {
+    if (!seen.add(location)) conflicts.add(location);
+  }
+  return conflicts;
+}
+
+Future<bool> _createMergedOutputDir(
+    String outputPath,
+    String root,
+    PackageGraph packageGraph,
+    BuildEnvironment environment,
+    AssetReader reader,
+    FinalizedAssetsView finalizedOutputsView,
+    bool symlinkOnly,
+    bool hoist) async {
+  try {
+    if (root == null) return false;
+    var outputDir = Directory(outputPath);
+    var outputDirExists = await outputDir.exists();
+    if (outputDirExists) {
+      if (!await _cleanUpOutputDir(outputDir, environment)) return false;
+    }
+    var builtAssets = finalizedOutputsView.allAssets(rootDir: root).toList();
+    if (root != '' &&
+        !builtAssets
+            .where((id) => id.package == packageGraph.root.name)
+            .any((id) => p.isWithin(root, id.path))) {
+      _logger.severe('No assets exist in $root, skipping output');
+      return false;
+    }
+
+    var outputAssets = <AssetId>[];
+
+    await logTimedAsync(_logger, 'Creating merged output dir `$outputPath`',
+        () async {
+      if (!outputDirExists) {
+        await outputDir.create(recursive: true);
+      }
+
+      outputAssets.addAll(await Future.wait(builtAssets.map((id) => _writeAsset(
+          id, outputDir, root, packageGraph, reader, symlinkOnly, hoist))));
+
+      var packagesFileContent = packageGraph.allPackages.keys
+          .map((p) => '$p:packages/$p/')
+          .join('\r\n');
+      var packagesAsset = AssetId(packageGraph.root.name, '.packages');
+      await _writeAsString(outputDir, packagesAsset, packagesFileContent);
+      outputAssets.add(packagesAsset);
+
+      if (!hoist) {
+        for (var dir in _findRootDirs(builtAssets, outputPath)) {
+          var link = Link(p.join(outputDir.path, dir, 'packages'));
+          if (!link.existsSync()) {
+            link.createSync(p.join('..', 'packages'), recursive: true);
+          }
+        }
+      }
+    });
+
+    await logTimedAsync(_logger, 'Writing asset manifest', () async {
+      var paths = outputAssets.map((id) => id.path).toList()..sort();
+      var content = paths.join(_manifestSeparator);
+      await _writeAsString(
+          outputDir, AssetId(packageGraph.root.name, _manifestName), content);
+    });
+
+    return true;
+  } on FileSystemException catch (e) {
+    if (e.osError?.errorCode != 1314) rethrow;
+    var devModeLink =
+        'https://docs.microsoft.com/en-us/windows/uwp/get-started/'
+        'enable-your-device-for-development';
+    _logger.severe('Unable to create symlink ${e.path}. Note that to create '
+        'symlinks on windows you need to either run in a console with admin '
+        'privileges or enable developer mode (see $devModeLink).');
+    return false;
+  }
+}
+
+Set<String> _findRootDirs(Iterable<AssetId> allAssets, String outputPath) {
+  var rootDirs = <String>{};
+  for (var id in allAssets) {
+    var parts = p.url.split(id.path);
+    if (parts.length == 1) continue;
+    var dir = parts.first;
+    if (dir == outputPath || dir == 'lib') continue;
+    rootDirs.add(parts.first);
+  }
+  return rootDirs;
+}
+
+Future<AssetId> _writeAsset(
+    AssetId id,
+    Directory outputDir,
+    String root,
+    PackageGraph packageGraph,
+    AssetReader reader,
+    bool symlinkOnly,
+    bool hoist) {
+  return _descriptorPool.withResource(() async {
+    String assetPath;
+    if (id.path.startsWith('lib/')) {
+      assetPath =
+          p.url.join('packages', id.package, id.path.substring('lib/'.length));
+    } else {
+      assetPath = id.path;
+      assert(id.package == packageGraph.root.name);
+      if (hoist && p.isWithin(root, id.path)) {
+        assetPath = p.relative(id.path, from: root);
+      }
+    }
+
+    var outputId = AssetId(packageGraph.root.name, assetPath);
+    try {
+      if (symlinkOnly) {
+        await Link(_filePathFor(outputDir, outputId)).create(
+            // We assert at the top of `createMergedOutputDirectories` that the
+            // reader implements this type when requesting symlinks.
+            (reader as PathProvidingAssetReader).pathTo(id),
+            recursive: true);
+      } else {
+        await _writeAsBytes(outputDir, outputId, await reader.readAsBytes(id));
+      }
+    } on AssetNotFoundException catch (e, __) {
+      if (p.basename(id.path).startsWith('.')) {
+        _logger.fine('Skipping missing hidden file ${id.path}');
+      } else {
+        _logger.severe(
+            'Missing asset ${e.assetId}, it may have been deleted during the '
+            'build. Please try rebuilding and if you continue to see the '
+            'error then file a bug at '
+            'https://github.com/dart-lang/build/issues/new.');
+        rethrow;
+      }
+    }
+    return outputId;
+  });
+}
+
+Future<void> _writeAsBytes(Directory outputDir, AssetId id, List<int> bytes) =>
+    _fileFor(outputDir, id).then((file) => file.writeAsBytes(bytes));
+
+Future<void> _writeAsString(Directory outputDir, AssetId id, String contents) =>
+    _fileFor(outputDir, id).then((file) => file.writeAsString(contents));
+
+Future<File> _fileFor(Directory outputDir, AssetId id) {
+  return File(_filePathFor(outputDir, id)).create(recursive: true);
+}
+
+String _filePathFor(Directory outputDir, AssetId id) {
+  String relativePath;
+  if (id.path.startsWith('lib')) {
+    relativePath =
+        p.join('packages', id.package, p.joinAll(p.url.split(id.path).skip(1)));
+  } else {
+    relativePath = id.path;
+  }
+  return p.join(outputDir.path, relativePath);
+}
+
+/// Checks for a manifest file in [outputDir] and deletes all referenced files.
+///
+/// Prompts the user with a few options if no manifest file is found.
+///
+/// Returns whether or not the directory was successfully cleaned up.
+Future<bool> _cleanUpOutputDir(
+    Directory outputDir, BuildEnvironment environment) async {
+  var outputPath = outputDir.path;
+  var manifestFile = File(p.join(outputPath, _manifestName));
+  if (!manifestFile.existsSync()) {
+    if (outputDir.listSync(recursive: false).isNotEmpty) {
+      var choices = [
+        'Leave the directory unchanged and skip writing the build output',
+        'Delete the directory and all contents',
+        'Leave the directory in place and write over any existing files',
+      ];
+      int choice;
+      try {
+        choice = await environment.prompt(
+            'Found existing directory `$outputPath` but no manifest file.\n'
+            'Please choose one of the following options:',
+            choices);
+      } on NonInteractiveBuildException catch (_) {
+        _logger.severe('Unable to create merged directory at $outputPath.\n'
+            'Choose a different directory or delete the contents of that '
+            'directory.');
+        return false;
+      }
+      switch (choice) {
+        case 0:
+          _logger.severe('Skipped creation of the merged output directory.');
+          return false;
+        case 1:
+          try {
+            outputDir.deleteSync(recursive: true);
+          } catch (e) {
+            _logger.severe(
+                'Failed to delete output dir at `$outputPath` with error:\n\n'
+                '$e');
+            return false;
+          }
+          // Actually recreate the directory, but as an empty one.
+          outputDir.createSync();
+          break;
+        case 2:
+          // Just do nothing here, we overwrite files by default.
+          break;
+      }
+    }
+  } else {
+    var previousOutputs = logTimedSync(
+        _logger,
+        'Reading manifest at ${manifestFile.path}',
+        () => manifestFile.readAsStringSync().split(_manifestSeparator));
+
+    logTimedSync(_logger, 'Deleting previous outputs in `$outputPath`', () {
+      for (var path in previousOutputs) {
+        var file = File(p.join(outputPath, path));
+        if (file.existsSync()) file.deleteSync();
+      }
+      _cleanEmptyDirectories(outputPath, previousOutputs);
+    });
+  }
+  return true;
+}
+
+/// Deletes all the directories which used to contain any path in
+/// [removedFilePaths] if that directory is now empty.
+void _cleanEmptyDirectories(
+    String outputPath, Iterable<String> removedFilePaths) {
+  for (var directory in removedFilePaths
+      .map((path) => p.join(outputPath, p.dirname(path)))
+      .toSet()) {
+    _deleteUp(directory, outputPath);
+  }
+}
+
+/// Deletes the directory at [from] and and any parent directories which are
+/// subdirectories of [to] if they are empty.
+void _deleteUp(String from, String to) {
+  var directoryPath = from;
+  while (p.isWithin(to, directoryPath)) {
+    var directory = Directory(directoryPath);
+    if (!directory.existsSync() || directory.listSync().isNotEmpty) return;
+    directory.deleteSync();
+    directoryPath = p.dirname(directoryPath);
+  }
+}
diff --git a/build_runner_core/lib/src/environment/io_environment.dart b/build_runner_core/lib/src/environment/io_environment.dart
new file mode 100644
index 0000000..a2d4948
--- /dev/null
+++ b/build_runner_core/lib/src/environment/io_environment.dart
@@ -0,0 +1,105 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:io';
+
+import 'package:build/build.dart';
+import 'package:logging/logging.dart';
+
+import '../asset/file_based.dart';
+import '../asset/reader.dart';
+import '../asset/writer.dart';
+import '../generate/build_directory.dart';
+import '../generate/build_result.dart';
+import '../generate/finalized_assets_view.dart';
+import '../package_graph/package_graph.dart';
+import 'build_environment.dart';
+import 'create_merged_dir.dart';
+
+final _logger = Logger('IOEnvironment');
+
+/// A [BuildEnvironment] writing to disk and stdout.
+class IOEnvironment implements BuildEnvironment {
+  @override
+  final RunnerAssetReader reader;
+
+  @override
+  final RunnerAssetWriter writer;
+
+  final bool _isInteractive;
+
+  final bool _outputSymlinksOnly;
+
+  final PackageGraph _packageGraph;
+
+  IOEnvironment(this._packageGraph, {bool assumeTty, bool outputSymlinksOnly})
+      : _isInteractive = assumeTty == true || _canPrompt(),
+        _outputSymlinksOnly = outputSymlinksOnly ?? false,
+        reader = FileBasedAssetReader(_packageGraph),
+        writer = FileBasedAssetWriter(_packageGraph) {
+    if (_outputSymlinksOnly && Platform.isWindows) {
+      _logger.warning('Symlinks to files are not yet working on Windows, you '
+          'may experience issues using this mode. Follow '
+          'https://github.com/dart-lang/sdk/issues/33966 for updates.');
+    }
+  }
+
+  @override
+  void onLog(LogRecord record) {
+    if (record.level >= Level.SEVERE) {
+      stderr.writeln(record);
+    } else {
+      stdout.writeln(record);
+    }
+  }
+
+  @override
+  Future<int> prompt(String message, List<String> choices) async {
+    if (!_isInteractive) throw NonInteractiveBuildException();
+    while (true) {
+      stdout.writeln('\n$message');
+      for (var i = 0, l = choices.length; i < l; i++) {
+        stdout.writeln('${i + 1} - ${choices[i]}');
+      }
+      final input = stdin.readLineSync();
+      final choice = int.tryParse(input) ?? -1;
+      if (choice > 0 && choice <= choices.length) return choice - 1;
+      stdout.writeln('Unrecognized option $input, '
+          'a number between 1 and ${choices.length} expected');
+    }
+  }
+
+  @override
+  Future<BuildResult> finalizeBuild(
+      BuildResult buildResult,
+      FinalizedAssetsView finalizedAssetsView,
+      AssetReader reader,
+      Set<BuildDirectory> buildDirs) async {
+    if (buildDirs.any(
+            (target) => target.outputLocation?.path?.isNotEmpty ?? false) &&
+        buildResult.status == BuildStatus.success) {
+      if (!await createMergedOutputDirectories(buildDirs, _packageGraph, this,
+          reader, finalizedAssetsView, _outputSymlinksOnly)) {
+        return _convertToFailure(buildResult,
+            failureType: FailureType.cantCreate);
+      }
+    }
+    return buildResult;
+  }
+}
+
+bool _canPrompt() =>
+    stdioType(stdin) == StdioType.terminal &&
+    // Assume running inside a test if the code is running as a `data:` URI
+    Platform.script.scheme != 'data';
+
+BuildResult _convertToFailure(BuildResult previous,
+        {FailureType failureType}) =>
+    BuildResult(
+      BuildStatus.failure,
+      previous.outputs,
+      performance: previous.performance,
+      failureType: failureType,
+    );
diff --git a/build_runner_core/lib/src/environment/overridable_environment.dart b/build_runner_core/lib/src/environment/overridable_environment.dart
new file mode 100644
index 0000000..a5575aa
--- /dev/null
+++ b/build_runner_core/lib/src/environment/overridable_environment.dart
@@ -0,0 +1,70 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'package:build/build.dart';
+import 'package:logging/logging.dart';
+
+import '../asset/reader.dart';
+import '../asset/writer.dart';
+import '../generate/build_directory.dart';
+import '../generate/build_result.dart';
+import '../generate/finalized_assets_view.dart';
+import 'build_environment.dart';
+
+/// A [BuildEnvironment] which can have individual features overridden.
+class OverrideableEnvironment implements BuildEnvironment {
+  final BuildEnvironment _default;
+
+  final RunnerAssetReader _reader;
+  final RunnerAssetWriter _writer;
+
+  final void Function(LogRecord) _onLog;
+
+  final Future<BuildResult> Function(
+          BuildResult, FinalizedAssetsView, AssetReader, Set<BuildDirectory>)
+      _finalizeBuild;
+
+  OverrideableEnvironment(
+    this._default, {
+    RunnerAssetReader reader,
+    RunnerAssetWriter writer,
+    void Function(LogRecord) onLog,
+    Future<BuildResult> Function(
+            BuildResult, FinalizedAssetsView, AssetReader, Set<BuildDirectory>)
+        finalizeBuild,
+  })  : _reader = reader,
+        _writer = writer,
+        _onLog = onLog,
+        _finalizeBuild = finalizeBuild;
+
+  @override
+  RunnerAssetReader get reader => _reader ?? _default.reader;
+
+  @override
+  RunnerAssetWriter get writer => _writer ?? _default.writer;
+
+  @override
+  Future<BuildResult> finalizeBuild(
+          BuildResult buildResult,
+          FinalizedAssetsView finalizedAssetsView,
+          AssetReader reader,
+          Set<BuildDirectory> buildDirs) =>
+      (_finalizeBuild ?? _default.finalizeBuild)(
+          buildResult, finalizedAssetsView, reader, buildDirs);
+
+  @override
+  void onLog(LogRecord record) {
+    if (_onLog != null) {
+      _onLog(record);
+    } else {
+      _default.onLog(record);
+    }
+  }
+
+  @override
+  Future<int> prompt(String message, List<String> choices) =>
+      _default.prompt(message, choices);
+}
diff --git a/build_runner_core/lib/src/generate/build_definition.dart b/build_runner_core/lib/src/generate/build_definition.dart
new file mode 100644
index 0000000..447ef22
--- /dev/null
+++ b/build_runner_core/lib/src/generate/build_definition.dart
@@ -0,0 +1,531 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:io';
+
+import 'package:async/async.dart';
+import 'package:build/build.dart';
+import 'package:glob/glob.dart';
+import 'package:logging/logging.dart';
+import 'package:watcher/watcher.dart';
+
+import '../asset/build_cache.dart';
+import '../asset/reader.dart';
+import '../asset/writer.dart';
+import '../asset_graph/exceptions.dart';
+import '../asset_graph/graph.dart';
+import '../asset_graph/node.dart';
+import '../changes/build_script_updates.dart';
+import '../environment/build_environment.dart';
+import '../logging/failure_reporter.dart';
+import '../logging/logging.dart';
+import '../package_graph/package_graph.dart';
+import '../package_graph/target_graph.dart';
+import '../util/constants.dart';
+import '../util/sdk_version_match.dart';
+import 'exceptions.dart';
+import 'options.dart';
+import 'phase.dart';
+
+final _logger = Logger('BuildDefinition');
+
+class BuildDefinition {
+  final AssetGraph assetGraph;
+  final TargetGraph targetGraph;
+
+  final AssetReader reader;
+  final RunnerAssetWriter writer;
+
+  final PackageGraph packageGraph;
+  final bool deleteFilesByDefault;
+  final ResourceManager resourceManager;
+
+  final BuildScriptUpdates buildScriptUpdates;
+
+  /// Whether or not to run in a mode that conserves RAM at the cost of build
+  /// speed.
+  final bool enableLowResourcesMode;
+
+  final BuildEnvironment environment;
+
+  BuildDefinition._(
+      this.assetGraph,
+      this.targetGraph,
+      this.reader,
+      this.writer,
+      this.packageGraph,
+      this.deleteFilesByDefault,
+      this.resourceManager,
+      this.buildScriptUpdates,
+      this.enableLowResourcesMode,
+      this.environment);
+
+  static Future<BuildDefinition> prepareWorkspace(BuildEnvironment environment,
+          BuildOptions options, List<BuildPhase> buildPhases) =>
+      _Loader(environment, options, buildPhases).prepareWorkspace();
+}
+
+/// Understands how to find all assets relevant to a build as well as compute
+/// updates to those assets.
+class AssetTracker {
+  final AssetGraph _assetGraph;
+  final RunnerAssetReader _reader;
+  final TargetGraph _targetGraph;
+
+  AssetTracker(this._assetGraph, this._reader, this._targetGraph);
+
+  /// Checks for and returns any file system changes compared to the current
+  /// state of the asset graph.
+  Future<Map<AssetId, ChangeType>> collectChanges() async {
+    var inputSources = await _findInputSources();
+    var generatedSources = await _findCacheDirSources();
+    var internalSources = await _findInternalSources();
+    return _computeSourceUpdates(
+        inputSources, generatedSources, internalSources);
+  }
+
+  /// Returns the all the sources found in the cache directory.
+  Future<Set<AssetId>> _findCacheDirSources() =>
+      _listGeneratedAssetIds().toSet();
+
+  /// Returns the set of original package inputs on disk.
+  Future<Set<AssetId>> _findInputSources() {
+    final targets =
+        Stream<TargetNode>.fromIterable(_targetGraph.allModules.values);
+    return targets.asyncExpand(_listAssetIds).toSet();
+  }
+
+  /// Returns all the internal sources, such as those under [entryPointDir].
+  Future<Set<AssetId>> _findInternalSources() =>
+      _listIdsSafe(Glob('$entryPointDir/**')).toSet();
+
+  /// Finds the asset changes which have happened while unwatched between builds
+  /// by taking a difference between the assets in the graph and the assets on
+  /// disk.
+  Future<Map<AssetId, ChangeType>> _computeSourceUpdates(
+      Set<AssetId> inputSources,
+      Set<AssetId> generatedSources,
+      Set<AssetId> internalSources) async {
+    final allSources = <AssetId>{}
+      ..addAll(inputSources)
+      ..addAll(generatedSources)
+      ..addAll(internalSources);
+    var updates = <AssetId, ChangeType>{};
+    void addUpdates(Iterable<AssetId> assets, ChangeType type) {
+      for (var asset in assets) {
+        updates[asset] = type;
+      }
+    }
+
+    var newSources = inputSources.difference(_assetGraph.allNodes
+        .where((node) => node.isValidInput)
+        .map((node) => node.id)
+        .toSet());
+    addUpdates(newSources, ChangeType.ADD);
+    var removedAssets = _assetGraph.allNodes
+        .where((n) {
+          if (!n.isReadable) return false;
+          if (n is GeneratedAssetNode) return n.wasOutput;
+          return true;
+        })
+        .map((n) => n.id)
+        .where((id) => !allSources.contains(id));
+
+    addUpdates(removedAssets, ChangeType.REMOVE);
+
+    var originalGraphSources = _assetGraph.sources.toSet();
+    var preExistingSources = originalGraphSources.intersection(inputSources)
+      ..addAll(internalSources.where(_assetGraph.contains));
+    var modifyChecks = preExistingSources.map((id) async {
+      var node = _assetGraph.get(id);
+      assert(node != null);
+      var originalDigest = node.lastKnownDigest;
+      if (originalDigest == null) return;
+      var currentDigest = await _reader.digest(id);
+      if (currentDigest != originalDigest) {
+        updates[id] = ChangeType.MODIFY;
+      }
+    });
+    await Future.wait(modifyChecks);
+    return updates;
+  }
+
+  Stream<AssetId> _listAssetIds(TargetNode targetNode) => targetNode
+          .sourceIncludes.isEmpty
+      ? Stream<AssetId>.empty()
+      : StreamGroup.merge(targetNode.sourceIncludes.map((glob) =>
+          _listIdsSafe(glob, package: targetNode.package.name)
+              .where((id) =>
+                  targetNode.package.isRoot || id.pathSegments.first == 'lib')
+              .where((id) => !targetNode.excludesSource(id))));
+
+  Stream<AssetId> _listGeneratedAssetIds() {
+    var glob = Glob('$generatedOutputDirectory/**');
+
+    return _listIdsSafe(glob).map((id) {
+      var packagePath = id.path.substring(generatedOutputDirectory.length + 1);
+      var firstSlash = packagePath.indexOf('/');
+      if (firstSlash == -1) return null;
+      var package = packagePath.substring(0, firstSlash);
+      var path = packagePath.substring(firstSlash + 1);
+      return AssetId(package, path);
+    }).where((id) => id != null);
+  }
+
+  /// Lists asset ids and swallows file not found errors.
+  ///
+  /// Ideally we would warn but in practice the default whitelist will give this
+  /// error a lot and it would be noisy.
+  Stream<AssetId> _listIdsSafe(Glob glob, {String package}) =>
+      _reader.findAssets(glob, package: package).handleError((void _) {},
+          test: (e) => e is FileSystemException && e.osError.errorCode == 2);
+}
+
+class _Loader {
+  final List<BuildPhase> _buildPhases;
+  final BuildOptions _options;
+  final BuildEnvironment _environment;
+
+  _Loader(this._environment, this._options, this._buildPhases);
+
+  Future<BuildDefinition> prepareWorkspace() async {
+    _checkBuildPhases();
+
+    _logger.info('Initializing inputs');
+
+    var assetGraph = await _tryReadCachedAssetGraph();
+    var assetTracker =
+        AssetTracker(assetGraph, _environment.reader, _options.targetGraph);
+    var inputSources = await assetTracker._findInputSources();
+    var cacheDirSources = await assetTracker._findCacheDirSources();
+    var internalSources = await assetTracker._findInternalSources();
+
+    BuildScriptUpdates buildScriptUpdates;
+    if (assetGraph != null) {
+      var updates = await logTimedAsync(
+          _logger,
+          'Checking for updates since last build',
+          () => _updateAssetGraph(assetGraph, assetTracker, _buildPhases,
+              inputSources, cacheDirSources, internalSources));
+
+      buildScriptUpdates = await BuildScriptUpdates.create(
+          _environment.reader, _options.packageGraph, assetGraph,
+          disabled: _options.skipBuildScriptCheck);
+      if (!_options.skipBuildScriptCheck &&
+          buildScriptUpdates.hasBeenUpdated(updates.keys.toSet())) {
+        _logger.warning('Invalidating asset graph due to build script update!');
+        var deletedSourceOutputs = await _cleanupOldOutputs(assetGraph);
+
+        if (_runningFromSnapshot) {
+          // We have to be regenerated if running from a snapshot.
+          throw BuildScriptChangedException();
+        }
+
+        inputSources.removeAll(deletedSourceOutputs);
+        assetGraph = null;
+        buildScriptUpdates = null;
+      }
+    }
+
+    if (assetGraph == null) {
+      Set<AssetId> conflictingOutputs;
+
+      await logTimedAsync(_logger, 'Building new asset graph', () async {
+        try {
+          assetGraph = await AssetGraph.build(_buildPhases, inputSources,
+              internalSources, _options.packageGraph, _environment.reader);
+        } on DuplicateAssetNodeException catch (e, st) {
+          _logger.severe('Conflicting outputs', e, st);
+          throw CannotBuildException();
+        }
+        buildScriptUpdates = await BuildScriptUpdates.create(
+            _environment.reader, _options.packageGraph, assetGraph,
+            disabled: _options.skipBuildScriptCheck);
+        conflictingOutputs = assetGraph.outputs
+            .where((n) => n.package == _options.packageGraph.root.name)
+            .where(inputSources.contains)
+            .toSet();
+        final conflictsInDeps = assetGraph.outputs
+            .where((n) => n.package != _options.packageGraph.root.name)
+            .where(inputSources.contains)
+            .toSet();
+        if (conflictsInDeps.isNotEmpty) {
+          log.severe('There are existing files in dependencies which conflict '
+              'with files that a Builder may produce. These must be removed or '
+              'the Builders disabled before a build can continue: '
+              '${conflictsInDeps.map((a) => a.uri).join('\n')}');
+          throw CannotBuildException();
+        }
+      });
+
+      await logTimedAsync(
+          _logger,
+          'Checking for unexpected pre-existing outputs.',
+          () => _initialBuildCleanup(conflictingOutputs,
+              _wrapWriter(_environment.writer, assetGraph)));
+    }
+
+    return BuildDefinition._(
+        assetGraph,
+        _options.targetGraph,
+        _wrapReader(_environment.reader, assetGraph),
+        _wrapWriter(_environment.writer, assetGraph),
+        _options.packageGraph,
+        _options.deleteFilesByDefault,
+        ResourceManager(),
+        buildScriptUpdates,
+        _options.enableLowResourcesMode,
+        _environment);
+  }
+
+  /// Checks that the [_buildPhases] are valid based on whether they are
+  /// written to the build cache.
+  void _checkBuildPhases() {
+    final root = _options.packageGraph.root.name;
+    for (final action in _buildPhases) {
+      if (!action.hideOutput) {
+        // Only `InBuildPhase`s can be not hidden.
+        if (action is InBuildPhase && action.package != root) {
+          // This should happen only with a manual build script since the build
+          // script generation filters these out.
+          _logger.severe('A build phase (${action.builderLabel}) is attempting '
+              'to operate on package "${action.package}", but the build script '
+              'is located in package "$root". It\'s not valid to attempt to '
+              'generate files for another package unless the BuilderApplication'
+              'specified "hideOutput".'
+              '\n\n'
+              'Did you mean to write:\n'
+              '  new BuilderApplication(..., toRoot())\n'
+              'or\n'
+              '  new BuilderApplication(..., hideOutput: true)\n'
+              '... instead?');
+          throw CannotBuildException();
+        }
+      }
+    }
+  }
+
+  /// Deletes the generated output directory.
+  ///
+  /// Typically this should be done whenever an asset graph is thrown away.
+  Future<void> _deleteGeneratedDir() async {
+    var generatedDir = Directory(generatedOutputDirectory);
+    if (await generatedDir.exists()) {
+      await generatedDir.delete(recursive: true);
+    }
+  }
+
+  /// Attempts to read in an [AssetGraph] from disk, and returns `null` if it
+  /// fails for any reason.
+  Future<AssetGraph> _tryReadCachedAssetGraph() async {
+    final assetGraphId =
+        AssetId(_options.packageGraph.root.name, assetGraphPath);
+    if (!await _environment.reader.canRead(assetGraphId)) {
+      return null;
+    }
+
+    return logTimedAsync(_logger, 'Reading cached asset graph', () async {
+      try {
+        var cachedGraph = AssetGraph.deserialize(
+            await _environment.reader.readAsBytes(assetGraphId));
+        if (computeBuildPhasesDigest(_buildPhases) !=
+            cachedGraph.buildPhasesDigest) {
+          _logger.warning(
+              'Throwing away cached asset graph because the build phases have '
+              'changed. This most commonly would happen as a result of adding a '
+              'new dependency or updating your dependencies.');
+          await Future.wait([
+            _cleanupOldOutputs(cachedGraph),
+            FailureReporter.cleanErrorCache(),
+          ]);
+          if (_runningFromSnapshot) {
+            throw BuildScriptChangedException();
+          }
+          return null;
+        }
+        if (!isSameSdkVersion(cachedGraph.dartVersion, Platform.version)) {
+          _logger.warning(
+              'Throwing away cached asset graph due to Dart SDK update.');
+          await Future.wait([
+            _cleanupOldOutputs(cachedGraph),
+            FailureReporter.cleanErrorCache(),
+          ]);
+          if (_runningFromSnapshot) {
+            throw BuildScriptChangedException();
+          }
+          return null;
+        }
+        return cachedGraph;
+      } on AssetGraphCorruptedException catch (_) {
+        // Start fresh if the cached asset_graph cannot be deserialized
+        _logger.warning('Throwing away cached asset graph due to '
+            'version mismatch or corrupted asset graph.');
+        await Future.wait([
+          _deleteGeneratedDir(),
+          FailureReporter.cleanErrorCache(),
+        ]);
+        return null;
+      }
+    });
+  }
+
+  /// Deletes all the old outputs from [graph] that were written to the source
+  /// tree, and deletes the entire generated directory.
+  Future<Iterable<AssetId>> _cleanupOldOutputs(AssetGraph graph) async {
+    var deletedSources = <AssetId>[];
+    await logTimedAsync(_logger, 'Cleaning up outputs from previous builds.',
+        () async {
+      // Delete all the non-hidden outputs.
+      await Future.wait(graph.outputs.map((id) {
+        var node = graph.get(id) as GeneratedAssetNode;
+        if (node.wasOutput && !node.isHidden) {
+          var idToDelete = id;
+          // If the package no longer exists, then the user must have renamed
+          // the root package.
+          //
+          // In that case we change `idToDelete` to be in the root package.
+          if (_options.packageGraph[id.package] == null) {
+            idToDelete = AssetId(_options.packageGraph.root.name, id.path);
+          }
+          deletedSources.add(idToDelete);
+          return _environment.writer.delete(idToDelete);
+        }
+        return null;
+      }).whereType<Future>());
+
+      await _deleteGeneratedDir();
+    });
+    return deletedSources;
+  }
+
+  /// Updates [assetGraph] based on a the new view of the world.
+  ///
+  /// Once done, this returns a map of [AssetId] to [ChangeType] for all the
+  /// changes.
+  Future<Map<AssetId, ChangeType>> _updateAssetGraph(
+      AssetGraph assetGraph,
+      AssetTracker assetTracker,
+      List<BuildPhase> buildPhases,
+      Set<AssetId> inputSources,
+      Set<AssetId> cacheDirSources,
+      Set<AssetId> internalSources) async {
+    var updates = await assetTracker._computeSourceUpdates(
+        inputSources, cacheDirSources, internalSources);
+    updates.addAll(_computeBuilderOptionsUpdates(assetGraph, buildPhases));
+    await assetGraph.updateAndInvalidate(
+        _buildPhases,
+        updates,
+        _options.packageGraph.root.name,
+        (id) => _wrapWriter(_environment.writer, assetGraph).delete(id),
+        _wrapReader(_environment.reader, assetGraph));
+    return updates;
+  }
+
+  /// Wraps [original] in a [BuildCacheWriter].
+  RunnerAssetWriter _wrapWriter(
+      RunnerAssetWriter original, AssetGraph assetGraph) {
+    assert(assetGraph != null);
+    return BuildCacheWriter(
+        original, assetGraph, _options.packageGraph.root.name);
+  }
+
+  /// Wraps [original] in a [BuildCacheReader].
+  AssetReader _wrapReader(AssetReader original, AssetGraph assetGraph) {
+    assert(assetGraph != null);
+    return BuildCacheReader(
+        original, assetGraph, _options.packageGraph.root.name);
+  }
+
+  /// Checks for any updates to the [BuilderOptionsAssetNode]s for
+  /// [buildPhases] compared to the last known state.
+  Map<AssetId, ChangeType> _computeBuilderOptionsUpdates(
+      AssetGraph assetGraph, List<BuildPhase> buildPhases) {
+    var result = <AssetId, ChangeType>{};
+
+    void updateBuilderOptionsNode(
+        AssetId builderOptionsId, BuilderOptions options) {
+      var builderOptionsNode =
+          assetGraph.get(builderOptionsId) as BuilderOptionsAssetNode;
+      var oldDigest = builderOptionsNode.lastKnownDigest;
+      builderOptionsNode.lastKnownDigest = computeBuilderOptionsDigest(options);
+      if (builderOptionsNode.lastKnownDigest != oldDigest) {
+        result[builderOptionsId] = ChangeType.MODIFY;
+      }
+    }
+
+    for (var phase = 0; phase < buildPhases.length; phase++) {
+      var action = buildPhases[phase];
+      if (action is InBuildPhase) {
+        updateBuilderOptionsNode(
+            builderOptionsIdForAction(action, phase), action.builderOptions);
+      } else if (action is PostBuildPhase) {
+        var actionNum = 0;
+        for (var builderAction in action.builderActions) {
+          updateBuilderOptionsNode(
+              builderOptionsIdForAction(builderAction, actionNum),
+              builderAction.builderOptions);
+          actionNum++;
+        }
+      }
+    }
+    return result;
+  }
+
+  /// Handles cleanup of pre-existing outputs for initial builds (where there is
+  /// no cached graph).
+  Future<void> _initialBuildCleanup(
+      Set<AssetId> conflictingAssets, RunnerAssetWriter writer) async {
+    if (conflictingAssets.isEmpty) return;
+
+    // Skip the prompt if using this option.
+    if (_options.deleteFilesByDefault) {
+      _logger.info('Deleting ${conflictingAssets.length} declared outputs '
+          'which already existed on disk.');
+      await Future.wait(conflictingAssets.map((id) => writer.delete(id)));
+      return;
+    }
+
+    // Prompt the user to delete files that are declared as outputs.
+    _logger.info('Found ${conflictingAssets.length} declared outputs '
+        'which already exist on disk. This is likely because the'
+        '`$cacheDir` folder was deleted, or you are submitting generated '
+        'files to your source repository.');
+
+    var done = false;
+    while (!done) {
+      try {
+        var choice = await _environment.prompt('Delete these files?',
+            ['Delete', 'Cancel build', 'List conflicts']);
+        switch (choice) {
+          case 0:
+            _logger.info('Deleting files...');
+            done = true;
+            await Future.wait(conflictingAssets.map((id) => writer.delete(id)));
+            break;
+          case 1:
+            _logger.severe('The build will not be able to contiue until the '
+                'conflicting assets are removed or the Builders which may '
+                'output them are disabled. The outputs are: '
+                '${conflictingAssets.map((a) => a.path).join('\n')}');
+            throw CannotBuildException();
+            break;
+          case 2:
+            _logger.info('Conflicts:\n${conflictingAssets.join('\n')}');
+            // Logging should be sync :(
+            await Future(() {});
+        }
+      } on NonInteractiveBuildException {
+        _logger.severe('Conflicting outputs were detected and the build '
+            'is unable to prompt for permission to remove them. '
+            'These outputs must be removed manually or the build can be '
+            'run with `--delete-conflicting-outputs`. The outputs are: '
+            '${conflictingAssets.map((a) => a.path).join('\n')}');
+        throw CannotBuildException();
+      }
+    }
+  }
+}
+
+bool get _runningFromSnapshot => !Platform.script.path.endsWith('.dart');
diff --git a/build_runner_core/lib/src/generate/build_directory.dart b/build_runner_core/lib/src/generate/build_directory.dart
new file mode 100644
index 0000000..bbfedff
--- /dev/null
+++ b/build_runner_core/lib/src/generate/build_directory.dart
@@ -0,0 +1,54 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS 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 '../util/hash.dart';
+
+class BuildDirectory {
+  final String directory;
+  final OutputLocation outputLocation;
+  BuildDirectory(this.directory, {this.outputLocation});
+
+  @override
+  bool operator ==(Object other) =>
+      other is BuildDirectory &&
+      other.directory == directory &&
+      other.outputLocation == outputLocation;
+
+  @override
+  int get hashCode {
+    var hash = 0;
+    hash = hashCombine(hash, directory.hashCode);
+    hash = hashCombine(hash, outputLocation.hashCode);
+    return hashComplete(hash);
+  }
+}
+
+class OutputLocation {
+  final String path;
+  final bool useSymlinks;
+  final bool hoist;
+  OutputLocation(this.path, {bool useSymlinks, bool hoist})
+      : useSymlinks = useSymlinks ?? false,
+        hoist = hoist ?? true {
+    if (path.isEmpty && hoist) {
+      throw ArgumentError('Can not build everything and hoist');
+    }
+  }
+
+  @override
+  bool operator ==(Object other) =>
+      other is OutputLocation &&
+      other.path == path &&
+      other.useSymlinks == useSymlinks &&
+      other.hoist == hoist;
+
+  @override
+  int get hashCode {
+    var hash = 0;
+    hash = hashCombine(hash, path.hashCode);
+    hash = hashCombine(hash, useSymlinks.hashCode);
+    hash = hashCombine(hash, hoist.hashCode);
+    return hashComplete(hash);
+  }
+}
diff --git a/build_runner_core/lib/src/generate/build_impl.dart b/build_runner_core/lib/src/generate/build_impl.dart
new file mode 100644
index 0000000..4c3180a
--- /dev/null
+++ b/build_runner_core/lib/src/generate/build_impl.dart
@@ -0,0 +1,896 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:collection';
+import 'dart:convert';
+import 'dart:typed_data';
+
+import 'package:build/build.dart';
+import 'package:crypto/crypto.dart';
+import 'package:glob/glob.dart';
+import 'package:logging/logging.dart';
+import 'package:path/path.dart' as p;
+import 'package:pedantic/pedantic.dart';
+import 'package:pool/pool.dart';
+import 'package:watcher/watcher.dart';
+
+import '../asset/cache.dart';
+import '../asset/finalized_reader.dart';
+import '../asset/reader.dart';
+import '../asset/writer.dart';
+import '../asset_graph/graph.dart';
+import '../asset_graph/node.dart';
+import '../asset_graph/optional_output_tracker.dart';
+import '../changes/build_script_updates.dart';
+import '../environment/build_environment.dart';
+import '../logging/build_for_input_logger.dart';
+import '../logging/failure_reporter.dart';
+import '../logging/human_readable_duration.dart';
+import '../logging/logging.dart';
+import '../package_graph/apply_builders.dart';
+import '../package_graph/package_graph.dart';
+import '../performance_tracking/performance_tracking_resolvers.dart';
+import '../util/async.dart';
+import '../util/build_dirs.dart';
+import '../util/constants.dart';
+import 'build_definition.dart';
+import 'build_directory.dart';
+import 'build_result.dart';
+import 'finalized_assets_view.dart';
+import 'heartbeat.dart';
+import 'options.dart';
+import 'performance_tracker.dart';
+import 'phase.dart';
+
+final _logger = Logger('Build');
+
+Set<String> _buildPaths(Set<BuildDirectory> buildDirs) =>
+    // The empty string means build everything.
+    buildDirs.any((b) => b.directory == '')
+        ? <String>{}
+        : buildDirs.map((b) => b.directory).toSet();
+
+class BuildImpl {
+  final FinalizedReader finalizedReader;
+
+  final AssetGraph assetGraph;
+
+  final BuildScriptUpdates buildScriptUpdates;
+
+  final List<BuildPhase> _buildPhases;
+  final PackageGraph _packageGraph;
+  final AssetReader _reader;
+  final Resolvers _resolvers;
+  final ResourceManager _resourceManager;
+  final RunnerAssetWriter _writer;
+  final bool _trackPerformance;
+  final BuildEnvironment _environment;
+  final String _logPerformanceDir;
+
+  Future<void> beforeExit() => _resourceManager.beforeExit();
+
+  BuildImpl._(BuildDefinition buildDefinition, BuildOptions options,
+      this._buildPhases, this.finalizedReader)
+      : buildScriptUpdates = buildDefinition.buildScriptUpdates,
+        _packageGraph = buildDefinition.packageGraph,
+        _reader = options.enableLowResourcesMode
+            ? buildDefinition.reader
+            : CachingAssetReader(buildDefinition.reader),
+        _resolvers = options.resolvers,
+        _writer = buildDefinition.writer,
+        assetGraph = buildDefinition.assetGraph,
+        _resourceManager = buildDefinition.resourceManager,
+        _environment = buildDefinition.environment,
+        _trackPerformance = options.trackPerformance,
+        _logPerformanceDir = options.logPerformanceDir;
+
+  Future<BuildResult> run(Map<AssetId, ChangeType> updates,
+      {Set<BuildDirectory> buildDirs, Set<BuildFilter> buildFilters}) {
+    buildDirs ??= <BuildDirectory>{};
+    buildFilters ??= {};
+    finalizedReader.reset(_buildPaths(buildDirs), buildFilters);
+    return _SingleBuild(this, buildDirs, buildFilters).run(updates)
+      ..whenComplete(_resolvers.reset);
+  }
+
+  static Future<BuildImpl> create(
+      BuildOptions options,
+      BuildEnvironment environment,
+      List<BuilderApplication> builders,
+      Map<String, Map<String, dynamic>> builderConfigOverrides,
+      {bool isReleaseBuild = false}) async {
+    // Don't allow any changes to the generated asset directory after this
+    // point.
+    lockGeneratedOutputDirectory();
+
+    var buildPhases = await createBuildPhases(
+        options.targetGraph, builders, builderConfigOverrides, isReleaseBuild);
+    if (buildPhases.isEmpty) {
+      _logger.severe('Nothing can be built, yet a build was requested.');
+    }
+    var buildDefinition = await BuildDefinition.prepareWorkspace(
+        environment, options, buildPhases);
+    var singleStepReader = SingleStepReader(
+        buildDefinition.reader,
+        buildDefinition.assetGraph,
+        buildPhases.length,
+        options.packageGraph.root.name,
+        _isReadableAfterBuildFactory(buildPhases));
+    var finalizedReader = FinalizedReader(
+        singleStepReader,
+        buildDefinition.assetGraph,
+        buildPhases,
+        options.packageGraph.root.name);
+    var build =
+        BuildImpl._(buildDefinition, options, buildPhases, finalizedReader);
+    return build;
+  }
+
+  static IsReadable _isReadableAfterBuildFactory(List<BuildPhase> buildPhases) {
+    return (AssetNode node, int phaseNum, AssetWriterSpy writtenAssets) {
+      if (node is GeneratedAssetNode) {
+        return Readability.fromPreviousPhase(node.wasOutput && !node.isFailure);
+      }
+
+      return Readability.fromPreviousPhase(
+          node.isReadable && node.isValidInput);
+    };
+  }
+}
+
+/// Performs a single build and manages state that only lives for a single
+/// build.
+class _SingleBuild {
+  final AssetGraph _assetGraph;
+  final Set<BuildFilter> _buildFilters;
+  final List<BuildPhase> _buildPhases;
+  final List<Pool> _buildPhasePool;
+  final BuildEnvironment _environment;
+  final _lazyPhases = <String, Future<Iterable<AssetId>>>{};
+  final _lazyGlobs = <AssetId, Future<void>>{};
+  final PackageGraph _packageGraph;
+  final BuildPerformanceTracker _performanceTracker;
+  final AssetReader _reader;
+  final Resolvers _resolvers;
+  final ResourceManager _resourceManager;
+  final RunnerAssetWriter _writer;
+  final Set<BuildDirectory> _buildDirs;
+  final String _logPerformanceDir;
+  final _failureReporter = FailureReporter();
+
+  int actionsCompletedCount = 0;
+  int actionsStartedCount = 0;
+
+  final pendingActions = SplayTreeMap<int, Set<String>>();
+
+  /// Can't be final since it needs access to [pendingActions].
+  HungActionsHeartbeat hungActionsHeartbeat;
+
+  _SingleBuild(BuildImpl buildImpl, Set<BuildDirectory> buildDirs,
+      Set<BuildFilter> buildFilters)
+      : _assetGraph = buildImpl.assetGraph,
+        _buildFilters = buildFilters,
+        _buildPhases = buildImpl._buildPhases,
+        _buildPhasePool = List(buildImpl._buildPhases.length),
+        _environment = buildImpl._environment,
+        _packageGraph = buildImpl._packageGraph,
+        _performanceTracker = buildImpl._trackPerformance
+            ? BuildPerformanceTracker()
+            : BuildPerformanceTracker.noOp(),
+        _reader = buildImpl._reader,
+        _resolvers = buildImpl._resolvers,
+        _resourceManager = buildImpl._resourceManager,
+        _writer = buildImpl._writer,
+        _buildDirs = buildDirs,
+        _logPerformanceDir = buildImpl._logPerformanceDir {
+    hungActionsHeartbeat = HungActionsHeartbeat(() {
+      final message = StringBuffer();
+      const actionsToLogMax = 5;
+      var descriptions = pendingActions.values.fold(
+          <String>[],
+          (combined, actions) =>
+              combined..addAll(actions)).take(actionsToLogMax);
+      for (final description in descriptions) {
+        message.writeln('  - $description');
+      }
+      var additionalActionsCount =
+          actionsStartedCount - actionsCompletedCount - actionsToLogMax;
+      if (additionalActionsCount > 0) {
+        message.writeln('  .. and $additionalActionsCount more');
+      }
+      return '$message';
+    });
+  }
+
+  Future<BuildResult> run(Map<AssetId, ChangeType> updates) async {
+    var watch = Stopwatch()..start();
+    var result = await _safeBuild(updates);
+    var optionalOutputTracker = OptionalOutputTracker(
+        _assetGraph, _buildPaths(_buildDirs), _buildFilters, _buildPhases);
+    if (result.status == BuildStatus.success) {
+      final failures = _assetGraph.failedOutputs
+          .where((n) => optionalOutputTracker.isRequired(n.id));
+      if (failures.isNotEmpty) {
+        await _failureReporter.reportErrors(failures);
+        result = BuildResult(BuildStatus.failure, result.outputs,
+            performance: result.performance);
+      }
+    }
+    await _resourceManager.disposeAll();
+    result = await _environment.finalizeBuild(
+        result,
+        FinalizedAssetsView(_assetGraph, optionalOutputTracker),
+        _reader,
+        _buildDirs);
+    if (result.status == BuildStatus.success) {
+      _logger.info('Succeeded after ${humanReadable(watch.elapsed)} with '
+          '${result.outputs.length} outputs '
+          '($actionsCompletedCount actions)\n');
+    } else {
+      _logger.severe('Failed after ${humanReadable(watch.elapsed)}');
+    }
+    return result;
+  }
+
+  Future<void> _updateAssetGraph(Map<AssetId, ChangeType> updates) async {
+    await logTimedAsync(_logger, 'Updating asset graph', () async {
+      var invalidated = await _assetGraph.updateAndInvalidate(
+          _buildPhases, updates, _packageGraph.root.name, _delete, _reader);
+      if (_reader is CachingAssetReader) {
+        (_reader as CachingAssetReader).invalidate(invalidated);
+      }
+    });
+  }
+
+  /// Runs a build inside a zone with an error handler and stack chain
+  /// capturing.
+  Future<BuildResult> _safeBuild(Map<AssetId, ChangeType> updates) {
+    var done = Completer<BuildResult>();
+
+    var heartbeat = HeartbeatLogger(
+        transformLog: (original) => '$original, ${_buildProgress()}',
+        waitDuration: Duration(seconds: 1))
+      ..start();
+    hungActionsHeartbeat.start();
+    done.future.whenComplete(() {
+      heartbeat.stop();
+      hungActionsHeartbeat.stop();
+    });
+
+    runZoned(() async {
+      if (updates.isNotEmpty) {
+        await _updateAssetGraph(updates);
+      }
+      // Run a fresh build.
+      var result = await logTimedAsync(_logger, 'Running build', _runPhases);
+
+      // Write out the dependency graph file.
+      await logTimedAsync(_logger, 'Caching finalized dependency graph',
+          () async {
+        await _writer.writeAsBytes(
+            AssetId(_packageGraph.root.name, assetGraphPath),
+            _assetGraph.serialize());
+      });
+
+      // Log performance information if requested
+      if (_logPerformanceDir != null) {
+        assert(result.performance != null);
+        var now = DateTime.now();
+        var logPath = p.join(
+            _logPerformanceDir,
+            '${now.year}-${_twoDigits(now.month)}-${_twoDigits(now.day)}'
+            '_${_twoDigits(now.hour)}-${_twoDigits(now.minute)}-'
+            '${_twoDigits(now.second)}');
+        await logTimedAsync(_logger, 'Writing performance log to $logPath', () {
+          var performanceLogId = AssetId(_packageGraph.root.name, logPath);
+          var serialized = jsonEncode(result.performance);
+          return _writer.writeAsString(performanceLogId, serialized);
+        });
+      }
+
+      if (!done.isCompleted) done.complete(result);
+    }, onError: (Object e, StackTrace st) {
+      if (!done.isCompleted) {
+        _logger.severe('Unhandled build failure!', e, st);
+        done.complete(BuildResult(BuildStatus.failure, []));
+      }
+    });
+    return done.future;
+  }
+
+  /// Returns a message describing the progress of the current build.
+  String _buildProgress() =>
+      '$actionsCompletedCount/$actionsStartedCount actions completed.';
+
+  /// Runs the actions in [_buildPhases] and returns a [Future<BuildResult>]
+  /// which completes once all [BuildPhase]s are done.
+  Future<BuildResult> _runPhases() {
+    return _performanceTracker.track(() async {
+      final outputs = <AssetId>[];
+      for (var phaseNum = 0; phaseNum < _buildPhases.length; phaseNum++) {
+        var phase = _buildPhases[phaseNum];
+        if (phase.isOptional) continue;
+        outputs
+            .addAll(await _performanceTracker.trackBuildPhase(phase, () async {
+          if (phase is InBuildPhase) {
+            var primaryInputs =
+                await _matchingPrimaryInputs(phase.package, phaseNum);
+            return _runBuilder(phaseNum, phase, primaryInputs);
+          } else if (phase is PostBuildPhase) {
+            return _runPostProcessPhase(phaseNum, phase);
+          } else {
+            throw StateError('Unrecognized BuildPhase type $phase');
+          }
+        }));
+      }
+      await Future.forEach(
+          _lazyPhases.values,
+          (Future<Iterable<AssetId>> lazyOuts) async =>
+              outputs.addAll(await lazyOuts));
+      // Assume success, `_assetGraph.failedOutputs` will be checked later.
+      return BuildResult(BuildStatus.success, outputs,
+          performance: _performanceTracker);
+    });
+  }
+
+  /// Gets a list of all inputs matching the [phaseNumber], as well as
+  /// its [Builder]s primary inputs.
+  ///
+  /// Lazily builds any optional build actions that might potentially produce
+  /// a primary input to this phase.
+  Future<Set<AssetId>> _matchingPrimaryInputs(
+      String package, int phaseNumber) async {
+    var ids = <AssetId>{};
+    var phase = _buildPhases[phaseNumber];
+    await Future.wait(
+        _assetGraph.outputsForPhase(package, phaseNumber).map((node) async {
+      if (!shouldBuildForDirs(
+          node.id, _buildPaths(_buildDirs), _buildFilters, phase)) {
+        return;
+      }
+
+      var input = _assetGraph.get(node.primaryInput);
+      if (input is GeneratedAssetNode) {
+        if (input.state != NodeState.upToDate) {
+          await _runLazyPhaseForInput(input.phaseNumber, input.primaryInput);
+        }
+        if (!input.wasOutput) return;
+        if (input.isFailure) return;
+      }
+      ids.add(input.id);
+    }));
+    return ids;
+  }
+
+  /// Runs a normal builder with [primaryInputs] as inputs and returns only the
+  /// outputs that were newly created.
+  ///
+  /// Does not return outputs that didn't need to be re-ran or were declared
+  /// but not output.
+  Future<Iterable<AssetId>> _runBuilder(int phaseNumber, InBuildPhase action,
+      Iterable<AssetId> primaryInputs) async {
+    var outputLists = await Future.wait(
+        primaryInputs.map((input) => _runForInput(phaseNumber, action, input)));
+    return outputLists.fold<List<AssetId>>(
+        <AssetId>[], (combined, next) => combined..addAll(next));
+  }
+
+  /// Lazily runs [phaseNumber] with [input]..
+  Future<Iterable<AssetId>> _runLazyPhaseForInput(
+      int phaseNumber, AssetId input) {
+    return _lazyPhases.putIfAbsent('$phaseNumber|$input', () async {
+      // First check if `input` is generated, and whether or not it was
+      // actually output. If it wasn't then we just return an empty list here.
+      var inputNode = _assetGraph.get(input);
+      if (inputNode is GeneratedAssetNode) {
+        // Make sure the `inputNode` is up to date, and rebuild it if not.
+        if (inputNode.state != NodeState.upToDate) {
+          await _runLazyPhaseForInput(
+              inputNode.phaseNumber, inputNode.primaryInput);
+        }
+        if (!inputNode.wasOutput || inputNode.isFailure) return <AssetId>[];
+      }
+
+      // We can never lazily build `PostProcessBuildAction`s.
+      var action = _buildPhases[phaseNumber] as InBuildPhase;
+
+      return _runForInput(phaseNumber, action, input);
+    });
+  }
+
+  /// Checks whether [node] can be read by this step - attempting to build the
+  /// asset if necessary.
+  FutureOr<Readability> _isReadableNode(
+      AssetNode node, int phaseNum, AssetWriterSpy writtenAssets) {
+    if (node is GeneratedAssetNode) {
+      if (node.phaseNumber > phaseNum) {
+        return Readability.notReadable;
+      } else if (node.phaseNumber == phaseNum) {
+        // allow a build step to read its outputs (contained in writtenAssets)
+        final isInBuild = _buildPhases[phaseNum] is InBuildPhase &&
+            writtenAssets.assetsWritten.contains(node.id);
+
+        return isInBuild ? Readability.ownOutput : Readability.notReadable;
+      }
+
+      return doAfter(
+          // ignore: void_checks
+          _ensureAssetIsBuilt(node),
+          (_) =>
+              Readability.fromPreviousPhase(node.wasOutput && !node.isFailure));
+    }
+    return Readability.fromPreviousPhase(node.isReadable && node.isValidInput);
+  }
+
+  FutureOr<void> _ensureAssetIsBuilt(AssetNode node) {
+    if (node is GeneratedAssetNode && node.state != NodeState.upToDate) {
+      return _runLazyPhaseForInput(node.phaseNumber, node.primaryInput);
+    }
+    return null;
+  }
+
+  Future<Iterable<AssetId>> _runForInput(
+      int phaseNumber, InBuildPhase phase, AssetId input) {
+    var pool = _buildPhasePool[phaseNumber] ??= Pool(buildPhasePoolSize);
+    return pool.withResource(() {
+      final builder = phase.builder;
+      var tracker =
+          _performanceTracker.addBuilderAction(input, phase.builderLabel);
+      return tracker.track(() async {
+        var builderOutputs = expectedOutputs(builder, input);
+
+        // Add `builderOutputs` to the primary outputs of the input.
+        var inputNode = _assetGraph.get(input);
+        assert(inputNode != null,
+            'Inputs should be known in the static graph. Missing $input');
+        assert(
+            inputNode.primaryOutputs.containsAll(builderOutputs),
+            'input $input with builder $builder missing primary outputs: \n'
+                    'Got ${inputNode.primaryOutputs.join(', ')} '
+                    'which was missing:\n' +
+                builderOutputs
+                    .where((id) => !inputNode.primaryOutputs.contains(id))
+                    .join(', '));
+
+        var wrappedWriter = AssetWriterSpy(_writer);
+
+        var wrappedReader = SingleStepReader(_reader, _assetGraph, phaseNumber,
+            input.package, _isReadableNode, _getUpdatedGlobNode, wrappedWriter);
+
+        if (!await tracker.trackStage(
+            'Setup', () => _buildShouldRun(builderOutputs, wrappedReader))) {
+          return <AssetId>[];
+        }
+
+        await _cleanUpStaleOutputs(builderOutputs);
+        await FailureReporter.clean(phaseNumber, input);
+
+        // We may have read some inputs in the call to `_buildShouldRun`, we want
+        // to remove those.
+        wrappedReader.assetsRead.clear();
+
+        var actionDescription =
+            _actionLoggerName(phase, input, _packageGraph.root.name);
+        var logger = BuildForInputLogger(Logger(actionDescription));
+
+        actionsStartedCount++;
+        pendingActions
+            .putIfAbsent(phaseNumber, () => <String>{})
+            .add(actionDescription);
+
+        var unusedAssets = <AssetId>{};
+        await tracker.trackStage(
+            'Build',
+            () => runBuilder(
+                  builder,
+                  [input],
+                  wrappedReader,
+                  wrappedWriter,
+                  PerformanceTrackingResolvers(_resolvers, tracker),
+                  logger: logger,
+                  resourceManager: _resourceManager,
+                  stageTracker: tracker,
+                  reportUnusedAssetsForInput: (_, assets) =>
+                      unusedAssets.addAll(assets),
+                ).catchError((void _) {
+                  // Errors tracked through the logger
+                }));
+        actionsCompletedCount++;
+        hungActionsHeartbeat.ping();
+        pendingActions[phaseNumber].remove(actionDescription);
+
+        // Reset the state for all the `builderOutputs` nodes based on what was
+        // read and written.
+        await tracker.trackStage(
+            'Finalize',
+            () => _setOutputsState(
+                  builderOutputs,
+                  wrappedReader,
+                  wrappedWriter,
+                  actionDescription,
+                  logger.errorsSeen,
+                  unusedAssets: unusedAssets,
+                ));
+
+        return wrappedWriter.assetsWritten;
+      });
+    });
+  }
+
+  Future<Iterable<AssetId>> _runPostProcessPhase(
+      int phaseNum, PostBuildPhase phase) async {
+    var actionNum = 0;
+    var outputLists = await Future.wait(phase.builderActions
+        .map((action) => _runPostProcessAction(phaseNum, actionNum++, action)));
+    return outputLists.fold<List<AssetId>>(
+        <AssetId>[], (combined, next) => combined..addAll(next));
+  }
+
+  Future<Iterable<AssetId>> _runPostProcessAction(
+      int phaseNum, int actionNum, PostBuildAction action) async {
+    var anchorNodes = _assetGraph.packageNodes(action.package).where((node) {
+      if (node is PostProcessAnchorNode && node.actionNumber == actionNum) {
+        var inputNode = _assetGraph.get(node.primaryInput);
+        if (inputNode is SourceAssetNode) {
+          return true;
+        } else if (inputNode is GeneratedAssetNode) {
+          return inputNode.wasOutput &&
+              !inputNode.isFailure &&
+              inputNode.state == NodeState.upToDate;
+        }
+      }
+      return false;
+    }).cast<PostProcessAnchorNode>();
+    var outputLists = await Future.wait(anchorNodes.map((anchorNode) =>
+        _runPostProcessBuilderForAnchor(
+            phaseNum, actionNum, action.builder, anchorNode)));
+    return outputLists.fold<List<AssetId>>(
+        <AssetId>[], (combined, next) => combined..addAll(next));
+  }
+
+  Future<Iterable<AssetId>> _runPostProcessBuilderForAnchor(
+      int phaseNum,
+      int actionNum,
+      PostProcessBuilder builder,
+      PostProcessAnchorNode anchorNode) async {
+    var input = anchorNode.primaryInput;
+    var inputNode = _assetGraph.get(input);
+    assert(inputNode != null,
+        'Inputs should be known in the static graph. Missing $input');
+
+    var wrappedWriter = AssetWriterSpy(_writer);
+    var wrappedReader = SingleStepReader(_reader, _assetGraph, phaseNum,
+        input.package, _isReadableNode, null, wrappedWriter);
+
+    if (!await _postProcessBuildShouldRun(anchorNode, wrappedReader)) {
+      return <AssetId>[];
+    }
+    // We may have read some inputs in the call to `_buildShouldRun`, we want
+    // to remove those.
+    wrappedReader.assetsRead.clear();
+
+    // Clean out the impacts of the previous run
+    await FailureReporter.clean(phaseNum, input);
+    await _cleanUpStaleOutputs(anchorNode.outputs);
+    anchorNode.outputs
+      ..toList().forEach(_assetGraph.remove)
+      ..clear();
+    inputNode.deletedBy.remove(anchorNode.id);
+
+    var actionDescription = '$builder on $input';
+    var logger = BuildForInputLogger(Logger(actionDescription));
+
+    actionsStartedCount++;
+    pendingActions
+        .putIfAbsent(phaseNum, () => <String>{})
+        .add(actionDescription);
+
+    await runPostProcessBuilder(
+        builder, input, wrappedReader, wrappedWriter, logger,
+        addAsset: (assetId) {
+      if (_assetGraph.contains(assetId)) {
+        throw InvalidOutputException(assetId, 'Asset already exists');
+      }
+      var node = GeneratedAssetNode(assetId,
+          primaryInput: input,
+          builderOptionsId: anchorNode.builderOptionsId,
+          isHidden: true,
+          phaseNumber: phaseNum,
+          wasOutput: true,
+          isFailure: false,
+          state: NodeState.upToDate);
+      _assetGraph.add(node);
+      anchorNode.outputs.add(assetId);
+    }, deleteAsset: (assetId) {
+      if (!_assetGraph.contains(assetId)) {
+        throw AssetNotFoundException(assetId);
+      }
+      if (assetId != input) {
+        throw InvalidOutputException(assetId, 'Can only delete primary input');
+      }
+      _assetGraph.get(assetId).deletedBy.add(anchorNode.id);
+    }).catchError((void _) {
+      // Errors tracked through the logger
+    });
+    actionsCompletedCount++;
+    hungActionsHeartbeat.ping();
+    pendingActions[phaseNum].remove(actionDescription);
+
+    var assetsWritten = wrappedWriter.assetsWritten.toSet();
+
+    // Reset the state for all the output nodes based on what was read and
+    // written.
+    inputNode.primaryOutputs.addAll(assetsWritten);
+    await _setOutputsState(assetsWritten, wrappedReader, wrappedWriter,
+        actionDescription, logger.errorsSeen);
+
+    return assetsWritten;
+  }
+
+  /// Checks and returns whether any [outputs] need to be updated.
+  Future<bool> _buildShouldRun(
+      Iterable<AssetId> outputs, AssetReader reader) async {
+    assert(
+        outputs.every(_assetGraph.contains),
+        'Outputs should be known statically. Missing '
+        '${outputs.where((o) => !_assetGraph.contains(o)).toList()}');
+    assert(outputs.isNotEmpty, 'Can\'t run a build with no outputs');
+
+    // We only check the first output, because all outputs share the same inputs
+    // and invalidation state.
+    var firstOutput = outputs.first;
+    var node = _assetGraph.get(firstOutput) as GeneratedAssetNode;
+    assert(
+        outputs.skip(1).every((output) =>
+            (_assetGraph.get(output) as GeneratedAssetNode)
+                .inputs
+                .difference(node.inputs)
+                .isEmpty),
+        'All outputs of a build action should share the same inputs.');
+
+    // No need to build an up to date output
+    if (node.state == NodeState.upToDate) return false;
+    // Early bail out condition, this is a forced update.
+    if (node.state == NodeState.definitelyNeedsUpdate) return true;
+    // This is a fresh build or the first time we've seen this output.
+    if (node.previousInputsDigest == null) return true;
+
+    var digest = await _computeCombinedDigest(
+        node.inputs, node.builderOptionsId, reader);
+    if (digest != node.previousInputsDigest) {
+      return true;
+    } else {
+      // Make sure to update the `state` field for all outputs.
+      for (var id in outputs) {
+        (_assetGraph.get(id) as NodeWithInputs).state = NodeState.upToDate;
+      }
+      return false;
+    }
+  }
+
+  /// Checks if a post process build should run based on [anchorNode].
+  Future<bool> _postProcessBuildShouldRun(
+      PostProcessAnchorNode anchorNode, AssetReader reader) async {
+    var inputsDigest = await _computeCombinedDigest(
+        [anchorNode.primaryInput], anchorNode.builderOptionsId, reader);
+
+    if (inputsDigest != anchorNode.previousInputsDigest) {
+      anchorNode.previousInputsDigest = inputsDigest;
+      return true;
+    }
+
+    return false;
+  }
+
+  /// Deletes any of [outputs] which previously were output.
+  ///
+  /// This should be called after deciding that an asset really needs to be
+  /// regenerated based on its inputs hash changing. All assets in [outputs]
+  /// must correspond to a [GeneratedAssetNode].
+  Future<void> _cleanUpStaleOutputs(Iterable<AssetId> outputs) =>
+      Future.wait(outputs
+          .map(_assetGraph.get)
+          .cast<GeneratedAssetNode>()
+          .where((n) => n.wasOutput)
+          .map((n) => _delete(n.id)));
+
+  Future<GlobAssetNode> _getUpdatedGlobNode(
+      Glob glob, String package, int phaseNum) {
+    var globNodeId = GlobAssetNode.createId(package, glob, phaseNum);
+    var globNode = _assetGraph.get(globNodeId) as GlobAssetNode;
+    if (globNode == null) {
+      globNode = GlobAssetNode(
+          globNodeId, glob, phaseNum, NodeState.definitelyNeedsUpdate);
+      _assetGraph.add(globNode);
+    }
+
+    return toFuture(doAfter(
+        // ignore: void_checks
+        _updateGlobNodeIfNecessary(globNode),
+        (_) => globNode));
+  }
+
+  FutureOr<void> _updateGlobNodeIfNecessary(GlobAssetNode globNode) {
+    if (globNode.state == NodeState.upToDate) return null;
+
+    return _lazyGlobs.putIfAbsent(globNode.id, () async {
+      var potentialNodes = _assetGraph
+          .packageNodes(globNode.id.package)
+          .where((n) => n.isReadable && n.isValidInput)
+          .where((n) =>
+              n is! GeneratedAssetNode ||
+              (n as GeneratedAssetNode).phaseNumber < globNode.phaseNumber)
+          .where((n) => globNode.glob.matches(n.id.path))
+          .toList();
+
+      await Future.wait(potentialNodes
+          .whereType<GeneratedAssetNode>()
+          .map(_ensureAssetIsBuilt)
+          .map(toFuture));
+
+      var actualMatches = <AssetId>[];
+      for (var node in potentialNodes) {
+        node.outputs.add(globNode.id);
+        if (node is GeneratedAssetNode && (!node.wasOutput || node.isFailure)) {
+          continue;
+        }
+        actualMatches.add(node.id);
+      }
+
+      globNode
+        ..results = actualMatches
+        ..inputs = HashSet.of(potentialNodes.map((n) => n.id))
+        ..state = NodeState.upToDate
+        ..lastKnownDigest =
+            md5.convert(utf8.encode(globNode.results.join(' ')));
+
+      unawaited(_lazyGlobs.remove(globNode.id));
+    });
+  }
+
+  /// Computes a single [Digest] based on the combined [Digest]s of [ids] and
+  /// [builderOptionsId].
+  Future<Digest> _computeCombinedDigest(Iterable<AssetId> ids,
+      AssetId builderOptionsId, AssetReader reader) async {
+    var combinedBytes = Uint8List.fromList(List.filled(16, 0));
+    void _combine(Uint8List other) {
+      assert(other.length == 16);
+      assert(other is Uint8List);
+      for (var i = 0; i < 16; i++) {
+        combinedBytes[i] ^= other[i];
+      }
+    }
+
+    var builderOptionsNode = _assetGraph.get(builderOptionsId);
+    _combine(builderOptionsNode.lastKnownDigest.bytes as Uint8List);
+
+    // Limit the total number of digests we are computing at a time. Otherwise
+    // this can overload the event queue.
+    await Future.wait(ids.map((id) async {
+      var node = _assetGraph.get(id);
+      if (node is GlobAssetNode) {
+        await _updateGlobNodeIfNecessary(node);
+      } else if (!await reader.canRead(id)) {
+        // We want to add something here, a missing/unreadable input should be
+        // different from no input at all.
+        //
+        // This needs to be unique per input so we use the md5 hash of the id.
+        _combine(md5.convert(id.toString().codeUnits).bytes as Uint8List);
+        return;
+      } else {
+        node.lastKnownDigest ??= await reader.digest(id);
+      }
+      _combine(node.lastKnownDigest.bytes as Uint8List);
+    }));
+
+    return Digest(combinedBytes);
+  }
+
+  /// Sets the state for all [outputs] of a build step, by:
+  ///
+  /// - Setting `needsUpdate` to `false` for each output
+  /// - Setting `wasOutput` based on `writer.assetsWritten`.
+  /// - Setting `isFailed` based on action success.
+  /// - Adding `outputs` as outputs to all `reader.assetsRead`.
+  /// - Setting the `lastKnownDigest` on each output based on the new contents.
+  /// - Setting the `previousInputsDigest` on each output based on the inputs.
+  /// - Storing the error message with the [_failureReporter].
+  Future<void> _setOutputsState(
+      Iterable<AssetId> outputs,
+      SingleStepReader reader,
+      AssetWriterSpy writer,
+      String actionDescription,
+      Iterable<ErrorReport> errors,
+      {Set<AssetId> unusedAssets}) async {
+    if (outputs.isEmpty) return;
+    var usedInputs = unusedAssets != null
+        ? reader.assetsRead.difference(unusedAssets)
+        : reader.assetsRead;
+
+    final inputsDigest = await _computeCombinedDigest(
+        usedInputs,
+        (_assetGraph.get(outputs.first) as GeneratedAssetNode).builderOptionsId,
+        reader);
+
+    final isFailure = errors.isNotEmpty;
+
+    for (var output in outputs) {
+      var wasOutput = writer.assetsWritten.contains(output);
+      var digest = wasOutput ? await _reader.digest(output) : null;
+      var node = _assetGraph.get(output) as GeneratedAssetNode;
+
+      // **IMPORTANT**: All updates to `node` must be synchronous. With lazy
+      // builders we can run arbitrary code between updates otherwise, at which
+      // time a node might not be in a valid state.
+      _removeOldInputs(node, usedInputs);
+      _addNewInputs(node, usedInputs);
+      node
+        ..state = NodeState.upToDate
+        ..wasOutput = wasOutput
+        ..isFailure = isFailure
+        ..lastKnownDigest = digest
+        ..previousInputsDigest = inputsDigest;
+
+      if (isFailure) {
+        await _failureReporter.markReported(actionDescription, node, errors);
+        var needsMarkAsFailure = Queue.of(node.primaryOutputs);
+        var allSkippedFailures = <GeneratedAssetNode>[];
+        while (needsMarkAsFailure.isNotEmpty) {
+          var output = needsMarkAsFailure.removeLast();
+          var outputNode = _assetGraph.get(output) as GeneratedAssetNode
+            ..state = NodeState.upToDate
+            ..wasOutput = false
+            ..isFailure = true
+            ..lastKnownDigest = null
+            ..previousInputsDigest = null;
+          allSkippedFailures.add(outputNode);
+          needsMarkAsFailure.addAll(outputNode.primaryOutputs);
+
+          // Make sure output invalidation follows primary outputs for builds
+          // that won't run.
+          node.outputs.add(output);
+          outputNode.inputs.add(node.id);
+        }
+        await _failureReporter.markSkipped(allSkippedFailures);
+      }
+    }
+  }
+
+  /// Removes old inputs from [node] based on [updatedInputs], and cleans up all
+  /// the old edges.
+  void _removeOldInputs(GeneratedAssetNode node, Set<AssetId> updatedInputs) {
+    var removedInputs = node.inputs.difference(updatedInputs);
+    node.inputs.removeAll(removedInputs);
+    for (var input in removedInputs) {
+      var inputNode = _assetGraph.get(input);
+      assert(inputNode != null, 'Asset Graph is missing $input');
+      inputNode.outputs.remove(node.id);
+    }
+  }
+
+  /// Adds new inputs to [node] based on [updatedInputs], and adds the
+  /// appropriate edges.
+  void _addNewInputs(GeneratedAssetNode node, Set<AssetId> updatedInputs) {
+    var newInputs = updatedInputs.difference(node.inputs);
+    node.inputs.addAll(newInputs);
+    for (var input in newInputs) {
+      var inputNode = _assetGraph.get(input);
+      assert(inputNode != null, 'Asset Graph is missing $input');
+      inputNode.outputs.add(node.id);
+    }
+  }
+
+  Future _delete(AssetId id) => _writer.delete(id);
+}
+
+String _actionLoggerName(
+    InBuildPhase phase, AssetId primaryInput, String rootPackageName) {
+  var asset = primaryInput.package == rootPackageName
+      ? primaryInput.path
+      : primaryInput.uri;
+  return '${phase.builderLabel} on $asset';
+}
+
+String _twoDigits(int n) => '$n'.padLeft(2, '0');
diff --git a/build_runner_core/lib/src/generate/build_result.dart b/build_runner_core/lib/src/generate/build_result.dart
new file mode 100644
index 0000000..d96e52f
--- /dev/null
+++ b/build_runner_core/lib/src/generate/build_result.dart
@@ -0,0 +1,68 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'package:build/build.dart';
+import 'package:meta/meta.dart';
+
+import 'performance_tracker.dart';
+
+/// The result of an individual build, this may be an incremental build or
+/// a full build.
+class BuildResult {
+  /// The status of this build.
+  final BuildStatus status;
+
+  /// The type of failure.
+  final FailureType failureType;
+
+  /// All outputs created/updated during this build.
+  final List<AssetId> outputs;
+
+  /// The [BuildPerformance] broken out by build action, may be `null`.
+  @experimental
+  final BuildPerformance performance;
+
+  BuildResult(this.status, List<AssetId> outputs,
+      {this.performance, FailureType failureType})
+      : outputs = List.unmodifiable(outputs),
+        failureType = failureType == null && status == BuildStatus.failure
+            ? FailureType.general
+            : failureType;
+  @override
+  String toString() {
+    if (status == BuildStatus.success) {
+      return '''
+
+Build Succeeded!
+''';
+    } else {
+      return '''
+
+Build Failed :(
+''';
+    }
+  }
+}
+
+/// The status of a build.
+enum BuildStatus {
+  success,
+  failure,
+}
+
+/// The type of failure
+class FailureType {
+  static final general = FailureType._(1);
+  static final cantCreate = FailureType._(73);
+  static final buildConfigChanged = FailureType._(75);
+  static final buildScriptChanged = FailureType._(75);
+  final int exitCode;
+  FailureType._(this.exitCode);
+}
+
+abstract class BuildState {
+  Future<BuildResult> get currentBuild;
+  Stream<BuildResult> get buildResults;
+}
diff --git a/build_runner_core/lib/src/generate/build_runner.dart b/build_runner_core/lib/src/generate/build_runner.dart
new file mode 100644
index 0000000..b19e265
--- /dev/null
+++ b/build_runner_core/lib/src/generate/build_runner.dart
@@ -0,0 +1,37 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'package:build/build.dart';
+import 'package:watcher/watcher.dart';
+
+import '../environment/build_environment.dart';
+import '../package_graph/apply_builders.dart';
+import 'build_directory.dart';
+import 'build_impl.dart';
+import 'build_result.dart';
+import 'options.dart';
+
+class BuildRunner {
+  final BuildImpl _build;
+  BuildRunner._(this._build);
+
+  Future<void> beforeExit() => _build.beforeExit();
+
+  Future<BuildResult> run(Map<AssetId, ChangeType> updates,
+          {Set<BuildDirectory> buildDirs, Set<BuildFilter> buildFilters}) =>
+      _build.run(updates, buildDirs: buildDirs, buildFilters: buildFilters);
+
+  static Future<BuildRunner> create(
+      BuildOptions options,
+      BuildEnvironment environment,
+      List<BuilderApplication> builders,
+      Map<String, Map<String, dynamic>> builderConfigOverrides,
+      {bool isReleaseBuild = false}) async {
+    return BuildRunner._(await BuildImpl.create(
+        options, environment, builders, builderConfigOverrides,
+        isReleaseBuild: isReleaseBuild));
+  }
+}
diff --git a/build_runner_core/lib/src/generate/exceptions.dart b/build_runner_core/lib/src/generate/exceptions.dart
new file mode 100644
index 0000000..659d8ae
--- /dev/null
+++ b/build_runner_core/lib/src/generate/exceptions.dart
@@ -0,0 +1,28 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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.
+
+/// Indicates that a build config file has changed, and the build needs to be
+/// re-ran.
+///
+/// An exit code of 75 should be set when handling this exception.
+class BuildConfigChangedException implements Exception {
+  const BuildConfigChangedException();
+}
+
+/// Indicates that the build script itself has changed, and needs to be re-ran.
+///
+/// If the build is running from a snapshot, the snapshot should also be
+/// deleted before exiting.
+///
+/// An exit code of 75 should be set when handling this exception.
+class BuildScriptChangedException implements Exception {
+  const BuildScriptChangedException();
+}
+
+/// Indicates that the build cannot be attempted.
+///
+/// Before throwing this exception a user actionable message should be logged.
+class CannotBuildException implements Exception {
+  const CannotBuildException();
+}
diff --git a/build_runner_core/lib/src/generate/finalized_assets_view.dart b/build_runner_core/lib/src/generate/finalized_assets_view.dart
new file mode 100644
index 0000000..9f74ae3
--- /dev/null
+++ b/build_runner_core/lib/src/generate/finalized_assets_view.dart
@@ -0,0 +1,65 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'package:build/build.dart';
+import 'package:path/path.dart' as p;
+
+import '../asset_graph/graph.dart';
+import '../asset_graph/node.dart';
+import '../asset_graph/optional_output_tracker.dart';
+
+/// A lazily computed view of all the assets available after a build.
+///
+/// Note that this class has a limited lifetime during which it is available,
+/// and should not be used outside of the scope in which it is given. It will
+/// throw a [StateError] if you attempt to use it once it has expired.
+class FinalizedAssetsView {
+  final AssetGraph _assetGraph;
+  final OptionalOutputTracker _optionalOutputTracker;
+
+  bool _expired = false;
+
+  FinalizedAssetsView(this._assetGraph, this._optionalOutputTracker);
+
+  List<AssetId> allAssets({String rootDir}) {
+    if (_expired) {
+      throw StateError(
+          'Cannot use a FinalizedAssetsView after it has expired!');
+    }
+    return _assetGraph.allNodes
+        .map((node) {
+          if (_shouldSkipNode(node, rootDir, _optionalOutputTracker)) {
+            return null;
+          }
+          return node.id;
+        })
+        .where((id) => id != null)
+        .toList();
+  }
+
+  void markExpired() {
+    assert(!_expired);
+    _expired = true;
+  }
+}
+
+bool _shouldSkipNode(AssetNode node, String rootDir,
+    OptionalOutputTracker optionalOutputTracker) {
+  if (!node.isReadable) return true;
+  if (node.isDeleted) return true;
+  if (rootDir != null &&
+      !node.id.path.startsWith('lib/') &&
+      !p.isWithin(rootDir, node.id.path)) {
+    return true;
+  }
+  if (node is InternalAssetNode) return true;
+  if (node is GeneratedAssetNode) {
+    if (!node.wasOutput || node.isFailure || node.state != NodeState.upToDate) {
+      return true;
+    }
+    return !optionalOutputTracker.isRequired(node.id);
+  }
+  if (node.id.path == '.packages') return true;
+  return false;
+}
diff --git a/build_runner_core/lib/src/generate/heartbeat.dart b/build_runner_core/lib/src/generate/heartbeat.dart
new file mode 100644
index 0000000..2917f54
--- /dev/null
+++ b/build_runner_core/lib/src/generate/heartbeat.dart
@@ -0,0 +1,137 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'package:logging/logging.dart';
+
+import '../logging/human_readable_duration.dart';
+
+var _logger = Logger('Heartbeat');
+
+/// Base class for a heartbeat implementation.
+///
+/// Once [start]ed, if [waitDuration] passes between calls to [ping], then
+/// [onTimeout] will be invoked with the duration.
+abstract class Heartbeat {
+  Stopwatch _intervalWatch;
+  Timer _timer;
+
+  /// The interval at which to check if [waitDuration] has passed.
+  final Duration checkInterval;
+
+  /// The amount of time between heartbeats.
+  final Duration waitDuration;
+
+  Heartbeat({Duration checkInterval, Duration waitDuration})
+      : checkInterval = checkInterval ?? const Duration(milliseconds: 100),
+        waitDuration = waitDuration ?? const Duration(seconds: 5);
+
+  /// Invoked if [waitDuration] time has elapsed since the last call to [ping].
+  void onTimeout(Duration elapsed);
+
+  /// Resets the internal timers. If more than [waitDuration] elapses without
+  /// this method being called, then [onTimeout] will be invoked with the
+  /// duration since the last ping.
+  void ping() {
+    _intervalWatch.reset();
+  }
+
+  /// Starts this heartbeat logger, must not already be started.
+  ///
+  /// This method can be overridden to add additional logic for handling calls
+  /// to [ping], but you must call `super.start()`.
+  void start() {
+    if (_intervalWatch != null || _timer != null) {
+      throw StateError('HeartbeatLogger already started');
+    }
+    _intervalWatch = Stopwatch()..start();
+    ping();
+    _timer = Timer.periodic(checkInterval, _checkDuration);
+  }
+
+  /// Stops this heartbeat logger, must already be started.
+  ///
+  /// This method can be overridden to add additional logic for cleanup
+  /// purposes, but you must call `super.stop()`.
+  void stop() {
+    if (_intervalWatch == null || _timer == null) {
+      throw StateError('HeartbeatLogger was never started');
+    }
+    _intervalWatch.stop();
+    _intervalWatch = null;
+    _timer.cancel();
+    _timer = null;
+  }
+
+  void _checkDuration(void _) {
+    if (_intervalWatch.elapsed < waitDuration) return;
+    onTimeout(_intervalWatch.elapsed);
+    ping();
+  }
+}
+
+/// Watches [Logger.root] and if there are no logs for [waitDuration] then it
+/// will log a heartbeat message with the current elapsed time since [start] was
+/// originally invoked.
+class HeartbeatLogger extends Heartbeat {
+  StreamSubscription<LogRecord> _listener;
+  Stopwatch _totalWatch;
+
+  /// Will be invoked with each original log message and the returned value will
+  /// be logged instead.
+  final String Function(String original) transformLog;
+
+  HeartbeatLogger(
+      {Duration checkInterval, Duration waitDuration, this.transformLog})
+      : super(checkInterval: checkInterval, waitDuration: waitDuration);
+
+  /// Start listening to logs.
+  @override
+  void start() {
+    _totalWatch = Stopwatch()..start();
+    super.start();
+    _listener = Logger.root.onRecord.listen((_) => ping());
+  }
+
+  /// Stops listenting to the logger;
+  @override
+  void stop() {
+    super.stop();
+    _listener.cancel();
+    _listener = null;
+    _totalWatch.stop();
+    _totalWatch = null;
+  }
+
+  /// Logs a heartbeat message if we reach the timeout.
+  @override
+  void onTimeout(void _) {
+    var formattedTime = humanReadable(_totalWatch.elapsed);
+    var message = '$formattedTime elapsed';
+    if (transformLog != null) {
+      message = transformLog(message);
+    }
+    _logger.info(message);
+  }
+}
+
+class HungActionsHeartbeat extends Heartbeat {
+  /// Returns a description of pending actions
+  final String Function() listActions;
+
+  HungActionsHeartbeat(this.listActions,
+      {Duration checkInterval, Duration waitDuration})
+      : super(
+            checkInterval: checkInterval,
+            waitDuration: waitDuration ?? Duration(seconds: 15));
+
+  @override
+  void onTimeout(Duration elapsed) {
+    var formattedTime = humanReadable(elapsed);
+    var message = 'No actions completed for $formattedTime, '
+        'waiting on:\n${listActions()}';
+    _logger.warning(message);
+  }
+}
diff --git a/build_runner_core/lib/src/generate/input_matcher.dart b/build_runner_core/lib/src/generate/input_matcher.dart
new file mode 100644
index 0000000..dc644aa
--- /dev/null
+++ b/build_runner_core/lib/src/generate/input_matcher.dart
@@ -0,0 +1,77 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 'package:build/build.dart';
+import 'package:build_config/build_config.dart';
+import 'package:collection/collection.dart';
+import 'package:glob/glob.dart';
+
+/// A filter on files to run through a Builder.
+class InputMatcher {
+  /// The files to include
+  ///
+  /// Null or empty means include everything.
+  final List<Glob> includeGlobs;
+
+  /// The files within [includeGlobs] to exclude.
+  ///
+  /// Null or empty means exclude nothing.
+  final List<Glob> excludeGlobs;
+
+  InputMatcher(InputSet inputSet, {List<String> defaultInclude})
+      : includeGlobs =
+            (inputSet.include ?? defaultInclude)?.map((p) => Glob(p))?.toList(),
+        excludeGlobs = inputSet.exclude?.map((p) => Glob(p))?.toList();
+
+  /// Whether [input] is included in this set of assets.
+  bool matches(AssetId input) => includes(input) && !excludes(input);
+
+  /// Whether [input] matches any [includeGlobs].
+  ///
+  /// If there are no [includeGlobs] this always returns `true`.
+  bool includes(AssetId input) =>
+      includeGlobs == null ||
+      includeGlobs.isEmpty ||
+      includeGlobs.any((g) => g.matches(input.path));
+
+  /// Whether [input] matches any [excludeGlobs].
+  ///
+  /// If there are no [excludeGlobs] this always returns `false`.
+  bool excludes(AssetId input) =>
+      excludeGlobs != null &&
+      excludeGlobs.isNotEmpty &&
+      excludeGlobs.any((g) => g.matches(input.path));
+
+  @override
+  String toString() {
+    final result = StringBuffer();
+    if (includeGlobs == null || includeGlobs.isEmpty) {
+      result.write('any assets');
+    } else {
+      result.write('assets matching ${_patterns(includeGlobs).toList()}');
+    }
+    if (excludeGlobs != null && excludeGlobs.isNotEmpty) {
+      result.write(' except ${_patterns(excludeGlobs).toList()}');
+    }
+    return '$result';
+  }
+
+  @override
+  bool operator ==(Object other) =>
+      identical(this, other) ||
+      (other is InputMatcher &&
+          _deepEquals.equals(
+              _patterns(includeGlobs), _patterns(other.includeGlobs)) &&
+          _deepEquals.equals(
+              _patterns(excludeGlobs), _patterns(other.excludeGlobs)));
+
+  @override
+  int get hashCode =>
+      _deepEquals.hash([_patterns(includeGlobs), _patterns(excludeGlobs)]);
+}
+
+final _deepEquals = const DeepCollectionEquality();
+
+Iterable<String> _patterns(Iterable<Glob> globs) =>
+    globs?.map((g) => g.pattern);
diff --git a/build_runner_core/lib/src/generate/options.dart b/build_runner_core/lib/src/generate/options.dart
new file mode 100644
index 0000000..eb2681e
--- /dev/null
+++ b/build_runner_core/lib/src/generate/options.dart
@@ -0,0 +1,207 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'package:build/build.dart';
+import 'package:build_config/build_config.dart';
+import 'package:build_resolvers/build_resolvers.dart';
+import 'package:glob/glob.dart';
+import 'package:logging/logging.dart';
+import 'package:meta/meta.dart';
+import 'package:path/path.dart' as p;
+
+import '../environment/build_environment.dart';
+import '../package_graph/package_graph.dart';
+import '../package_graph/target_graph.dart';
+import '../util/hash.dart';
+import 'exceptions.dart';
+
+/// The default list of files to include when an explicit include is not
+/// provided.
+const List<String> defaultRootPackageWhitelist = [
+  'assets/**',
+  'benchmark/**',
+  'bin/**',
+  'example/**',
+  'lib/**',
+  'test/**',
+  'tool/**',
+  'web/**',
+  'node/**',
+  'pubspec.yaml',
+  'pubspec.lock',
+  r'$package$',
+];
+
+final _logger = Logger('BuildOptions');
+
+class LogSubscription {
+  factory LogSubscription(BuildEnvironment environment,
+      {bool verbose, Level logLevel}) {
+    // Set up logging
+    verbose ??= false;
+    logLevel ??= verbose ? Level.ALL : Level.INFO;
+
+    // Severe logs can fail the build and should always be shown.
+    if (logLevel == Level.OFF) logLevel = Level.SEVERE;
+
+    Logger.root.level = logLevel;
+
+    var logListener = Logger.root.onRecord.listen(environment.onLog);
+    return LogSubscription._(logListener);
+  }
+
+  LogSubscription._(this.logListener);
+
+  final StreamSubscription<LogRecord> logListener;
+}
+
+/// Describes a set of files that should be built.
+class BuildFilter {
+  /// The package name glob that files must live under in order to match.
+  final Glob _package;
+
+  /// A glob for files under [_package] that must match.
+  final Glob _path;
+
+  BuildFilter(this._package, this._path);
+
+  /// Builds a [BuildFilter] from a command line argument.
+  ///
+  /// Both relative paths and package: uris are supported. Relative
+  /// paths are treated as relative to the [rootPackage].
+  ///
+  /// Globs are supported in package names and paths.
+  factory BuildFilter.fromArg(String arg, String rootPackage) {
+    var uri = Uri.parse(arg);
+    if (uri.scheme == 'package') {
+      var package = uri.pathSegments.first;
+      var glob = Glob(p.url.joinAll([
+        'lib',
+        ...uri.pathSegments.skip(1),
+      ]));
+      return BuildFilter(Glob(package), glob);
+    } else if (uri.scheme.isEmpty) {
+      return BuildFilter(Glob(rootPackage), Glob(uri.path));
+    } else {
+      throw FormatException('Unsupported scheme ${uri.scheme}', uri);
+    }
+  }
+
+  /// Returns whether or not [id] mathes this filter.
+  bool matches(AssetId id) =>
+      _package.matches(id.package) && _path.matches(id.path);
+
+  @override
+  int get hashCode {
+    var hash = 0;
+    hash = hashCombine(hash, _package.context.hashCode);
+    hash = hashCombine(hash, _package.pattern.hashCode);
+    hash = hashCombine(hash, _package.recursive.hashCode);
+    hash = hashCombine(hash, _path.context.hashCode);
+    hash = hashCombine(hash, _path.pattern.hashCode);
+    hash = hashCombine(hash, _path.recursive.hashCode);
+    return hashComplete(hash);
+  }
+
+  @override
+  bool operator ==(Object other) =>
+      other is BuildFilter &&
+      other._path.context == _path.context &&
+      other._path.pattern == _path.pattern &&
+      other._path.recursive == _path.recursive &&
+      other._package.context == _package.context &&
+      other._package.pattern == _package.pattern &&
+      other._package.recursive == _package.recursive;
+}
+
+/// Manages setting up consistent defaults for all options and build modes.
+class BuildOptions {
+  final bool deleteFilesByDefault;
+  final bool enableLowResourcesMode;
+  final StreamSubscription logListener;
+
+  /// If present, the path to a directory to write performance logs to.
+  final String logPerformanceDir;
+
+  final PackageGraph packageGraph;
+  final Resolvers resolvers;
+  final TargetGraph targetGraph;
+  final bool trackPerformance;
+
+  // Watch mode options.
+  Duration debounceDelay;
+
+  // For testing only, skips the build script updates check.
+  bool skipBuildScriptCheck;
+
+  BuildOptions._({
+    @required this.debounceDelay,
+    @required this.deleteFilesByDefault,
+    @required this.enableLowResourcesMode,
+    @required this.logListener,
+    @required this.packageGraph,
+    @required this.skipBuildScriptCheck,
+    @required this.trackPerformance,
+    @required this.targetGraph,
+    @required this.logPerformanceDir,
+    @required this.resolvers,
+  });
+
+  static Future<BuildOptions> create(
+    LogSubscription logSubscription, {
+    Duration debounceDelay,
+    bool deleteFilesByDefault,
+    bool enableLowResourcesMode,
+    @required PackageGraph packageGraph,
+    Map<String, BuildConfig> overrideBuildConfig,
+    bool skipBuildScriptCheck,
+    bool trackPerformance,
+    String logPerformanceDir,
+    Resolvers resolvers,
+  }) async {
+    TargetGraph targetGraph;
+    try {
+      targetGraph = await TargetGraph.forPackageGraph(packageGraph,
+          overrideBuildConfig: overrideBuildConfig,
+          defaultRootPackageWhitelist: defaultRootPackageWhitelist);
+    } on BuildConfigParseException catch (e, s) {
+      _logger.severe(
+          'Failed to parse `build.yaml` for ${e.packageName}.', e.exception, s);
+      throw CannotBuildException();
+    }
+
+    /// Set up other defaults.
+    debounceDelay ??= const Duration(milliseconds: 250);
+    deleteFilesByDefault ??= false;
+    skipBuildScriptCheck ??= false;
+    enableLowResourcesMode ??= false;
+    trackPerformance ??= false;
+    if (logPerformanceDir != null) {
+      // Requiring this to be under the root package allows us to use an
+      // `AssetWriter` to write logs.
+      if (!p.isWithin(p.current, logPerformanceDir)) {
+        _logger.severe('Performance logs may only be output under the root '
+            'package, but got `$logPerformanceDir` which is not.');
+        throw CannotBuildException();
+      }
+      trackPerformance = true;
+    }
+    resolvers ??= AnalyzerResolvers();
+
+    return BuildOptions._(
+      debounceDelay: debounceDelay,
+      deleteFilesByDefault: deleteFilesByDefault,
+      enableLowResourcesMode: enableLowResourcesMode,
+      logListener: logSubscription.logListener,
+      packageGraph: packageGraph,
+      skipBuildScriptCheck: skipBuildScriptCheck,
+      trackPerformance: trackPerformance,
+      targetGraph: targetGraph,
+      logPerformanceDir: logPerformanceDir,
+      resolvers: resolvers,
+    );
+  }
+}
diff --git a/build_runner_core/lib/src/generate/performance_tracker.dart b/build_runner_core/lib/src/generate/performance_tracker.dart
new file mode 100644
index 0000000..beb598a
--- /dev/null
+++ b/build_runner_core/lib/src/generate/performance_tracker.dart
@@ -0,0 +1,341 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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.
+
+@experimental
+library build_runner.src.generate.performance_tracker;
+
+import 'dart:async';
+
+import 'package:build/build.dart';
+import 'package:json_annotation/json_annotation.dart';
+import 'package:meta/meta.dart';
+import 'package:timing/timing.dart';
+
+import 'phase.dart';
+
+part 'performance_tracker.g.dart';
+
+/// The [TimeSlice] of an entire build, including all its [actions].
+@JsonSerializable()
+class BuildPerformance extends TimeSlice {
+  /// The [TimeSlice] of each phase ran in this build.
+  final Iterable<BuildPhasePerformance> phases;
+
+  /// The [TimeSlice] of running an individual [Builder] on an individual input.
+  final Iterable<BuilderActionPerformance> actions;
+
+  BuildPerformance(
+      this.phases, this.actions, DateTime startTime, DateTime stopTime)
+      : super(startTime, stopTime);
+
+  factory BuildPerformance.fromJson(Map<String, dynamic> json) =>
+      _$BuildPerformanceFromJson(json);
+
+  @override
+  Map<String, dynamic> toJson() => _$BuildPerformanceToJson(this);
+}
+
+/// The [TimeSlice] of a full [BuildPhase] within a larger build.
+@JsonSerializable()
+class BuildPhasePerformance extends TimeSlice {
+  final List<String> builderKeys;
+
+  BuildPhasePerformance(this.builderKeys, DateTime startTime, DateTime stopTime)
+      : super(startTime, stopTime);
+
+  factory BuildPhasePerformance.fromJson(Map<String, dynamic> json) =>
+      _$BuildPhasePerformanceFromJson(json);
+
+  @override
+  Map<String, dynamic> toJson() => _$BuildPhasePerformanceToJson(this);
+}
+
+/// The [TimeSlice] of a [builderKey] running on [primaryInput] within a build.
+@JsonSerializable()
+class BuilderActionPerformance extends TimeSlice {
+  final String builderKey;
+
+  @JsonKey(fromJson: _assetIdFromJson, toJson: _assetIdToJson)
+  final AssetId primaryInput;
+
+  final Iterable<BuilderActionStagePerformance> stages;
+
+  Duration get innerDuration => stages.fold(
+      Duration.zero, (duration, stage) => duration + stage.innerDuration);
+
+  BuilderActionPerformance(this.builderKey, this.primaryInput, this.stages,
+      DateTime startTime, DateTime stopTime)
+      : super(startTime, stopTime);
+
+  factory BuilderActionPerformance.fromJson(Map<String, dynamic> json) =>
+      _$BuilderActionPerformanceFromJson(json);
+
+  @override
+  Map<String, dynamic> toJson() => _$BuilderActionPerformanceToJson(this);
+}
+
+/// The [TimeSlice] of a particular task within a builder action.
+///
+/// This is some slice of overall [BuilderActionPerformance].
+@JsonSerializable()
+class BuilderActionStagePerformance extends TimeSliceGroup {
+  final String label;
+
+  BuilderActionStagePerformance(this.label, List<TimeSlice> slices)
+      : super(slices);
+
+  factory BuilderActionStagePerformance.fromJson(Map<String, dynamic> json) =>
+      _$BuilderActionStagePerformanceFromJson(json);
+
+  @override
+  Map<String, dynamic> toJson() => _$BuilderActionStagePerformanceToJson(this);
+}
+
+/// Interface for tracking the overall performance of a build.
+abstract class BuildPerformanceTracker
+    implements TimeTracker, BuildPerformance {
+  /// Tracks [runPhase] which performs [action] on all inputs in a phase, and
+  /// return the outputs generated.
+  Future<Iterable<AssetId>> trackBuildPhase(
+      BuildPhase action, Future<Iterable<AssetId>> Function() runPhase);
+
+  /// Returns a [BuilderActionTracker] for tracking [builderKey] on
+  /// [primaryInput] and adds it to [actions].
+  BuilderActionTracker addBuilderAction(
+      AssetId primaryInput, String builderKey);
+
+  factory BuildPerformanceTracker() => _BuildPerformanceTrackerImpl();
+
+  /// A [BuildPerformanceTracker] with as little overhead as possible. Does no
+  /// actual tracking and does not implement many fields/methods.
+  factory BuildPerformanceTracker.noOp() =>
+      _NoOpBuildPerformanceTracker.sharedInstance;
+}
+
+/// Real implementation of [BuildPerformanceTracker].
+///
+/// Use [BuildPerformanceTracker] factory to get an instance.
+class _BuildPerformanceTrackerImpl extends SimpleAsyncTimeTracker
+    implements BuildPerformanceTracker {
+  @override
+  Iterable<BuildPhaseTracker> get phases => _phases;
+  final _phases = <BuildPhaseTracker>[];
+
+  @override
+  Iterable<BuilderActionTracker> get actions => _actions;
+  final _actions = <BuilderActionTracker>[];
+
+  /// Tracks [action] which is ran with [runPhase].
+  ///
+  /// This represents running a [Builder] on a group of sources.
+  ///
+  /// Returns all the outputs generated by the phase.
+  @override
+  Future<Iterable<AssetId>> trackBuildPhase(
+      BuildPhase action, Future<Iterable<AssetId>> Function() runPhase) {
+    assert(isTracking);
+    var tracker = BuildPhaseTracker(action);
+    _phases.add(tracker);
+    return tracker.track(runPhase);
+  }
+
+  /// Returns a new [BuilderActionTracker] and adds it to [actions].
+  ///
+  /// The [BuilderActionTracker] will already be started, but you must stop it.
+  @override
+  BuilderActionTracker addBuilderAction(
+      AssetId primaryInput, String builderKey) {
+    assert(isTracking);
+    var tracker = BuilderActionTracker(primaryInput, builderKey);
+    _actions.add(tracker);
+    return tracker;
+  }
+
+  @override
+  Map<String, dynamic> toJson() => _$BuildPerformanceToJson(this);
+}
+
+/// No-op implementation of [BuildPerformanceTracker].
+///
+/// Read-only fields will throw, and tracking methods just directly invoke their
+/// closures without tracking anything.
+///
+/// Use [BuildPerformanceTracker.noOp] to get an instance.
+class _NoOpBuildPerformanceTracker extends NoOpTimeTracker
+    implements BuildPerformanceTracker {
+  static final _NoOpBuildPerformanceTracker sharedInstance =
+      _NoOpBuildPerformanceTracker();
+
+  @override
+  Iterable<BuilderActionTracker> get actions => throw UnimplementedError();
+
+  @override
+  Iterable<BuildPhaseTracker> get phases => throw UnimplementedError();
+
+  @override
+  BuilderActionTracker addBuilderAction(
+          AssetId primaryInput, String builderKey) =>
+      BuilderActionTracker.noOp();
+
+  @override
+  Future<Iterable<AssetId>> trackBuildPhase(
+          BuildPhase action, Future<Iterable<AssetId>> Function() runPhase) =>
+      runPhase();
+
+  @override
+  Map<String, dynamic> toJson() => _$BuildPerformanceToJson(this);
+}
+
+/// Internal class that tracks the [TimeSlice] of an entire [BuildPhase].
+///
+/// Tracks total time it took to run on all inputs available to that action.
+///
+/// Use [track] to start actually tracking an operation.
+///
+/// This is only meaningful for non-lazy phases.
+class BuildPhaseTracker extends SimpleAsyncTimeTracker
+    implements BuildPhasePerformance {
+  @override
+  final List<String> builderKeys;
+
+  BuildPhaseTracker(BuildPhase action)
+      : builderKeys = action is InBuildPhase
+            ? [action.builderLabel]
+            : (action as PostBuildPhase)
+                .builderActions
+                .map((action) => action.builderLabel)
+                .toList();
+
+  @override
+  Map<String, dynamic> toJson() => _$BuildPhasePerformanceToJson(this);
+}
+
+/// Interface for tracking the [TimeSlice] of an individual [Builder] on a given
+/// primary input.
+abstract class BuilderActionTracker
+    implements TimeTracker, BuilderActionPerformance, StageTracker {
+  /// Tracks the time of [runStage] and associates it with [label].
+  ///
+  /// You can specify [runStage] as [isExternal] (waiting for some external
+  /// resource like network, process or file IO). In that case [runStage] will
+  /// be tracked as single time slice from the beginning of the stage till
+  /// completion of Future returned by [runStage].
+  ///
+  /// Otherwise all separate time slices of asynchronous execution will be
+  /// tracked, but waiting for external resources will be a gap.
+  @override
+  T trackStage<T>(String label, T Function() runStage,
+      {bool isExternal = false});
+
+  factory BuilderActionTracker(AssetId primaryInput, String builderKey) =>
+      _BuilderActionTrackerImpl(primaryInput, builderKey);
+
+  /// A [BuilderActionTracker] with as little overhead as possible. Does no
+  /// actual tracking and does not implement many fields/methods.
+  factory BuilderActionTracker.noOp() =>
+      _NoOpBuilderActionTracker._sharedInstance;
+}
+
+/// Real implementation of [BuilderActionTracker] which records timings.
+///
+/// Use the [BuilderActionTracker] factory to get an instance.
+class _BuilderActionTrackerImpl extends SimpleAsyncTimeTracker
+    implements BuilderActionTracker {
+  @override
+  final String builderKey;
+  @override
+  final AssetId primaryInput;
+
+  @override
+  final List<BuilderActionStageTracker> stages = [];
+
+  @override
+  Duration get innerDuration => stages.fold(
+      Duration.zero, (duration, stage) => duration + stage.innerDuration);
+
+  _BuilderActionTrackerImpl(this.primaryInput, this.builderKey);
+
+  @override
+  T trackStage<T>(String label, T Function() action,
+      {bool isExternal = false}) {
+    var tracker = isExternal
+        ? BuilderActionStageSimpleTracker(label)
+        : BuilderActionStageAsyncTracker(label);
+    stages.add(tracker);
+    return tracker.track(action);
+  }
+
+  @override
+  Map<String, dynamic> toJson() => _$BuilderActionPerformanceToJson(this);
+}
+
+/// No-op instance of [BuilderActionTracker] which does nothing and throws an
+/// unimplemented error for everything but [trackStage], which delegates directly to
+/// the wrapped function.
+///
+/// Use the [BuilderActionTracker.noOp] factory to get an instance.
+class _NoOpBuilderActionTracker extends NoOpTimeTracker
+    implements BuilderActionTracker {
+  static final _NoOpBuilderActionTracker _sharedInstance =
+      _NoOpBuilderActionTracker();
+
+  @override
+  String get builderKey => throw UnimplementedError();
+
+  @override
+  Duration get duration => throw UnimplementedError();
+
+  @override
+  Iterable<BuilderActionStagePerformance> get stages =>
+      throw UnimplementedError();
+
+  @override
+  Duration get innerDuration => throw UnimplementedError();
+
+  @override
+  AssetId get primaryInput => throw UnimplementedError();
+
+  @override
+  T trackStage<T>(String label, T Function() runStage,
+          {bool isExternal = false}) =>
+      runStage();
+
+  @override
+  Map<String, dynamic> toJson() => _$BuilderActionPerformanceToJson(this);
+}
+
+/// Tracks the [TimeSliceGroup] of an individual task.
+///
+/// These represent a slice of the [BuilderActionPerformance].
+abstract class BuilderActionStageTracker
+    implements BuilderActionStagePerformance {
+  T track<T>(T Function() action);
+}
+
+class BuilderActionStageAsyncTracker extends AsyncTimeTracker
+    implements BuilderActionStageTracker {
+  @override
+  final String label;
+
+  BuilderActionStageAsyncTracker(this.label) : super(trackNested: false);
+
+  @override
+  Map<String, dynamic> toJson() => _$BuilderActionStagePerformanceToJson(this);
+}
+
+class BuilderActionStageSimpleTracker extends BuilderActionStagePerformance
+    implements BuilderActionStageTracker {
+  final _tracker = SimpleAsyncTimeTracker();
+
+  BuilderActionStageSimpleTracker(String label) : super(label, []) {
+    slices.add(_tracker);
+  }
+
+  @override
+  T track<T>(T Function() action) => _tracker.track(action);
+}
+
+AssetId _assetIdFromJson(String json) => AssetId.parse(json);
+
+String _assetIdToJson(AssetId id) => id.toString();
diff --git a/build_runner_core/lib/src/generate/performance_tracker.g.dart b/build_runner_core/lib/src/generate/performance_tracker.g.dart
new file mode 100644
index 0000000..29ea027
--- /dev/null
+++ b/build_runner_core/lib/src/generate/performance_tracker.g.dart
@@ -0,0 +1,91 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of build_runner.src.generate.performance_tracker;
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+BuildPerformance _$BuildPerformanceFromJson(Map<String, dynamic> json) {
+  return BuildPerformance(
+      (json['phases'] as List)?.map((e) => e == null
+          ? null
+          : BuildPhasePerformance.fromJson(e as Map<String, dynamic>)),
+      (json['actions'] as List)?.map((e) => e == null
+          ? null
+          : BuilderActionPerformance.fromJson(e as Map<String, dynamic>)),
+      json['startTime'] == null
+          ? null
+          : DateTime.parse(json['startTime'] as String),
+      json['stopTime'] == null
+          ? null
+          : DateTime.parse(json['stopTime'] as String));
+}
+
+Map<String, dynamic> _$BuildPerformanceToJson(BuildPerformance instance) =>
+    <String, dynamic>{
+      'startTime': instance.startTime?.toIso8601String(),
+      'stopTime': instance.stopTime?.toIso8601String(),
+      'phases': instance.phases?.toList(),
+      'actions': instance.actions?.toList()
+    };
+
+BuildPhasePerformance _$BuildPhasePerformanceFromJson(
+    Map<String, dynamic> json) {
+  return BuildPhasePerformance(
+      (json['builderKeys'] as List)?.map((e) => e as String)?.toList(),
+      json['startTime'] == null
+          ? null
+          : DateTime.parse(json['startTime'] as String),
+      json['stopTime'] == null
+          ? null
+          : DateTime.parse(json['stopTime'] as String));
+}
+
+Map<String, dynamic> _$BuildPhasePerformanceToJson(
+        BuildPhasePerformance instance) =>
+    <String, dynamic>{
+      'startTime': instance.startTime?.toIso8601String(),
+      'stopTime': instance.stopTime?.toIso8601String(),
+      'builderKeys': instance.builderKeys
+    };
+
+BuilderActionPerformance _$BuilderActionPerformanceFromJson(
+    Map<String, dynamic> json) {
+  return BuilderActionPerformance(
+      json['builderKey'] as String,
+      _assetIdFromJson(json['primaryInput'] as String),
+      (json['stages'] as List)?.map((e) => e == null
+          ? null
+          : BuilderActionStagePerformance.fromJson(e as Map<String, dynamic>)),
+      json['startTime'] == null
+          ? null
+          : DateTime.parse(json['startTime'] as String),
+      json['stopTime'] == null
+          ? null
+          : DateTime.parse(json['stopTime'] as String));
+}
+
+Map<String, dynamic> _$BuilderActionPerformanceToJson(
+        BuilderActionPerformance instance) =>
+    <String, dynamic>{
+      'startTime': instance.startTime?.toIso8601String(),
+      'stopTime': instance.stopTime?.toIso8601String(),
+      'builderKey': instance.builderKey,
+      'primaryInput': _assetIdToJson(instance.primaryInput),
+      'stages': instance.stages?.toList()
+    };
+
+BuilderActionStagePerformance _$BuilderActionStagePerformanceFromJson(
+    Map<String, dynamic> json) {
+  return BuilderActionStagePerformance(
+      json['label'] as String,
+      (json['slices'] as List)
+          ?.map((e) =>
+              e == null ? null : TimeSlice.fromJson(e as Map<String, dynamic>))
+          ?.toList());
+}
+
+Map<String, dynamic> _$BuilderActionStagePerformanceToJson(
+        BuilderActionStagePerformance instance) =>
+    <String, dynamic>{'slices': instance.slices, 'label': instance.label};
diff --git a/build_runner_core/lib/src/generate/phase.dart b/build_runner_core/lib/src/generate/phase.dart
new file mode 100644
index 0000000..b6b83b0
--- /dev/null
+++ b/build_runner_core/lib/src/generate/phase.dart
@@ -0,0 +1,218 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 'package:build/build.dart';
+import 'package:build_config/build_config.dart';
+import 'package:collection/collection.dart';
+import 'package:meta/meta.dart';
+
+import 'input_matcher.dart';
+
+/// A "phase" in the build graph, which represents running a one or more
+/// builders on a set of sources.
+abstract class BuildPhase {
+  /// Whether to run lazily when an output is read.
+  ///
+  /// An optional build action will only run if one of its outputs is read by
+  /// a later [Builder], or is used as a primary input to a later [Builder].
+  ///
+  /// If no build actions read the output of an optional action, then it will
+  /// never run.
+  bool get isOptional;
+
+  /// Whether generated assets should be placed in the build cache.
+  ///
+  /// When this is `false` the Builder may not run on any package other than
+  /// the root.
+  bool get hideOutput;
+
+  /// The identity of this action in terms of a build graph. If the identity of
+  /// any action changes the build will be invalidated.
+  ///
+  /// This should take into account everything except for the builderOptions,
+  /// which are tracked separately via a `BuilderOptionsNode` which supports
+  /// more fine grained invalidation.
+  int get identity;
+}
+
+/// An "action" in the build graph which represents running a single builder
+/// on a set of sources.
+abstract class BuildAction {
+  /// Either a [Builder] or a [PostProcessBuilder].
+  dynamic get builder;
+  String get builderLabel;
+  BuilderOptions get builderOptions;
+  InputMatcher get generateFor;
+  String get package;
+  InputMatcher get targetSources;
+}
+
+/// A [BuildPhase] that uses a single [Builder] to generate files.
+class InBuildPhase extends BuildPhase implements BuildAction {
+  @override
+  final Builder builder;
+  @override
+  final String builderLabel;
+  @override
+  final BuilderOptions builderOptions;
+  @override
+  final InputMatcher generateFor;
+  @override
+  final String package;
+  @override
+  final InputMatcher targetSources;
+
+  @override
+  final bool isOptional;
+  @override
+  final bool hideOutput;
+
+  InBuildPhase._(this.package, this.builder, this.builderOptions,
+      {@required this.targetSources,
+      @required this.generateFor,
+      @required this.builderLabel,
+      bool isOptional,
+      bool hideOutput})
+      : isOptional = isOptional ?? false,
+        hideOutput = hideOutput ?? false;
+
+  /// Creates an [BuildPhase] for a normal [Builder].
+  ///
+  /// The build target is defined by [package] as well as [targetSources]. By
+  /// default all sources in the target are used as primary inputs to the
+  /// builder, but it can be further filtered with [generateFor].
+  ///
+  /// [isOptional] specifies that a Builder may not be run unless some other
+  /// Builder in a later phase attempts to read one of the potential outputs.
+  ///
+  /// [hideOutput] specifies that the generated asses should be placed in the
+  /// build cache rather than the source tree.
+  factory InBuildPhase(
+    Builder builder,
+    String package, {
+    String builderKey,
+    InputSet targetSources,
+    InputSet generateFor,
+    BuilderOptions builderOptions,
+    bool isOptional,
+    bool hideOutput,
+  }) {
+    var targetSourceMatcher = InputMatcher(targetSources ?? const InputSet());
+    var generateForMatcher = InputMatcher(generateFor ?? const InputSet());
+    builderOptions ??= const BuilderOptions({});
+    return InBuildPhase._(package, builder, builderOptions,
+        targetSources: targetSourceMatcher,
+        generateFor: generateForMatcher,
+        builderLabel: builderKey == null || builderKey.isEmpty
+            ? _builderLabel(builder)
+            : _simpleBuilderKey(builderKey),
+        isOptional: isOptional,
+        hideOutput: hideOutput);
+  }
+
+  @override
+  String toString() {
+    final settings = <String>[];
+    if (isOptional) settings.add('optional');
+    if (hideOutput) settings.add('hidden');
+    var result = '$builderLabel on $targetSources in $package';
+    if (settings.isNotEmpty) result += ' $settings';
+    return result;
+  }
+
+  @override
+  int get identity => _deepEquals.hash([
+        builderLabel,
+        builder.buildExtensions,
+        package,
+        targetSources,
+        generateFor,
+        isOptional,
+        hideOutput
+      ]);
+}
+
+/// A [BuildPhase] that can run multiple [PostBuildAction]s to
+/// generate files.
+///
+/// There should only be one of these per build, and it should be the final
+/// phase.
+class PostBuildPhase extends BuildPhase {
+  final List<PostBuildAction> builderActions;
+
+  @override
+  bool get hideOutput => true;
+  @override
+  bool get isOptional => false;
+
+  PostBuildPhase(this.builderActions);
+
+  @override
+  String toString() =>
+      '${builderActions.map((a) => a.builderLabel).join(', ')}';
+
+  @override
+  int get identity =>
+      _deepEquals.hash(builderActions.map<dynamic>((b) => b.identity).toList()
+        ..addAll([
+          isOptional,
+          hideOutput,
+        ]));
+}
+
+/// Part of a larger [PostBuildPhase], applies a single
+/// [PostProcessBuilder] to a single [package] with some additional options.
+class PostBuildAction implements BuildAction {
+  @override
+  final PostProcessBuilder builder;
+  @override
+  final String builderLabel;
+  @override
+  final BuilderOptions builderOptions;
+  @override
+  final InputMatcher generateFor;
+  @override
+  final String package;
+  @override
+  final InputMatcher targetSources;
+
+  PostBuildAction(this.builder, this.package,
+      {String builderKey,
+      @required BuilderOptions builderOptions,
+      @required InputSet targetSources,
+      @required InputSet generateFor})
+      : builderLabel = builderKey == null || builderKey.isEmpty
+            ? _builderLabel(builder)
+            : _simpleBuilderKey(builderKey),
+        builderOptions = builderOptions ?? const BuilderOptions({}),
+        targetSources = InputMatcher(targetSources ?? const InputSet()),
+        generateFor = InputMatcher(generateFor ?? const InputSet());
+
+  int get identity => _deepEquals.hash([
+        builderLabel,
+        builder.inputExtensions.toList(),
+        generateFor,
+        package,
+        targetSources,
+      ]);
+}
+
+/// If we have no key find a human friendly name for the Builder.
+String _builderLabel(Object builder) {
+  var label = '$builder';
+  if (label.startsWith('Instance of \'')) {
+    label = label.substring(13, label.length - 1);
+  }
+  return label;
+}
+
+/// Change "angular|angular" to "angular".
+String _simpleBuilderKey(String builderKey) {
+  if (!builderKey.contains('|')) return builderKey;
+  var parts = builderKey.split('|');
+  if (parts[0] == parts[1]) return parts[0];
+  return builderKey;
+}
+
+final _deepEquals = const DeepCollectionEquality();
diff --git a/build_runner_core/lib/src/logging/build_for_input_logger.dart b/build_runner_core/lib/src/logging/build_for_input_logger.dart
new file mode 100644
index 0000000..1e45120
--- /dev/null
+++ b/build_runner_core/lib/src/logging/build_for_input_logger.dart
@@ -0,0 +1,91 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'package:logging/logging.dart';
+
+import 'failure_reporter.dart';
+
+/// A delegating [Logger] that records if any logs of level >= [Level.SEVERE]
+/// were seen.
+class BuildForInputLogger implements Logger {
+  final Logger _delegate;
+
+  final errorsSeen = <ErrorReport>[];
+
+  BuildForInputLogger(this._delegate);
+
+  @override
+  Level get level => _delegate.level;
+
+  @override
+  set level(Level level) => _delegate.level = level;
+
+  @override
+  Map<String, Logger> get children => _delegate.children;
+
+  @override
+  void clearListeners() => _delegate.clearListeners();
+
+  @override
+  void config(Object message, [Object error, StackTrace stackTrace]) =>
+      _delegate.config(message, error, stackTrace);
+
+  @override
+  void fine(Object message, [Object error, StackTrace stackTrace]) =>
+      _delegate.fine(message, error, stackTrace);
+
+  @override
+  void finer(Object message, [Object error, StackTrace stackTrace]) =>
+      _delegate.finer(message, error, stackTrace);
+
+  @override
+  void finest(Object message, [Object error, StackTrace stackTrace]) =>
+      _delegate.finest(message, error, stackTrace);
+
+  @override
+  String get fullName => _delegate.fullName;
+
+  @override
+  void info(Object message, [Object error, StackTrace stackTrace]) =>
+      _delegate.info(message, error, stackTrace);
+
+  @override
+  bool isLoggable(Level value) => _delegate.isLoggable(value);
+
+  @override
+  void log(Level logLevel, Object message,
+      [Object error, StackTrace stackTrace, Zone zone]) {
+    if (logLevel >= Level.SEVERE) {
+      errorsSeen.add(ErrorReport('$message', '${error ?? ''}', stackTrace));
+    }
+    _delegate.log(logLevel, message, error, stackTrace, zone);
+  }
+
+  @override
+  String get name => _delegate.name;
+
+  @override
+  Stream<LogRecord> get onRecord => _delegate.onRecord;
+
+  @override
+  Logger get parent => _delegate.parent;
+
+  @override
+  void severe(Object message, [Object error, StackTrace stackTrace]) {
+    errorsSeen.add(ErrorReport('$message', '${error ?? ''}', stackTrace));
+    _delegate.severe(message, error, stackTrace);
+  }
+
+  @override
+  void shout(Object message, [Object error, StackTrace stackTrace]) {
+    errorsSeen.add(ErrorReport('$message', '${error ?? ''}', stackTrace));
+    _delegate.shout(message, error, stackTrace);
+  }
+
+  @override
+  void warning(Object message, [Object error, StackTrace stackTrace]) =>
+      _delegate.warning(message, error, stackTrace);
+}
diff --git a/build_runner_core/lib/src/logging/failure_reporter.dart b/build_runner_core/lib/src/logging/failure_reporter.dart
new file mode 100644
index 0000000..75bb4f8
--- /dev/null
+++ b/build_runner_core/lib/src/logging/failure_reporter.dart
@@ -0,0 +1,120 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:build/build.dart';
+import 'package:logging/logging.dart';
+import 'package:path/path.dart' as p;
+
+import '../asset_graph/node.dart';
+import '../util/constants.dart';
+
+/// A tracker for the errors which have already been reported during the current
+/// build.
+///
+/// Errors that occur due to actions that are run within this build will be
+/// reported directly as they happen. When an action is skipped and remains a
+/// failure the error will not have been reported by the time we check wether
+/// the build is failed.
+///
+/// The lifetime of this class should be a single build.
+class FailureReporter {
+  /// Removes all stored errors from previous builds.
+  ///
+  /// This should be called any time the build phases change since the naming
+  /// scheme is dependent on the build phases.
+  static Future<void> cleanErrorCache() async {
+    final errorCacheDirectory = Directory(p.fromUri(errorCachePath));
+    if (await errorCacheDirectory.exists()) {
+      await errorCacheDirectory.delete(recursive: true);
+    }
+  }
+
+  /// Remove the stored error for [phaseNumber] runnon on [primaryInput].
+  ///
+  /// This should be called anytime the action is being run.
+  static Future<void> clean(int phaseNumber, AssetId primaryInput) async {
+    final errorFile =
+        File(_errorPathForPrimaryInput(phaseNumber, primaryInput));
+    if (await errorFile.exists()) {
+      await errorFile.delete();
+    }
+  }
+
+  /// A set of Strings which uniquely identify a particular build action and
+  /// it's primary input.
+  final _reportedActions = <String>{};
+
+  /// Indicate that a failure reason for the build step which would produce
+  /// [output] and all other outputs from the same build step has been printed.
+  Future<void> markReported(String actionDescription, GeneratedAssetNode output,
+      Iterable<ErrorReport> errors) async {
+    if (!_reportedActions.add(_actionKey(output))) return;
+    final errorFile =
+        await File(_errorPathForOutput(output)).create(recursive: true);
+    await errorFile.writeAsString(jsonEncode(<dynamic>[actionDescription]
+        .followedBy(errors
+            .map((e) => [e.message, e.error, e.stackTrace?.toString() ?? '']))
+        .toList()));
+  }
+
+  /// Indicate that the build steps which would produce [outputs] are failing
+  /// due to a dependency and being skipped so no actuall error will be
+  /// produced.
+  Future<void> markSkipped(Iterable<GeneratedAssetNode> outputs) =>
+      Future.wait(outputs.map((output) async {
+        if (!_reportedActions.add(_actionKey(output))) return;
+        await clean(output.phaseNumber, output.primaryInput);
+      }));
+
+  /// Log stored errors for any build steps which would output nodes in
+  /// [failingNodes] which haven't already been reported.
+  Future<void> reportErrors(Iterable<GeneratedAssetNode> failingNodes) {
+    final errorFiles = <File>[];
+    for (final failure in failingNodes) {
+      final key = _actionKey(failure);
+      if (!_reportedActions.add(key)) continue;
+      errorFiles.add(File(_errorPathForOutput(failure)));
+    }
+    return Future.wait(errorFiles.map((errorFile) async {
+      if (await errorFile.exists()) {
+        final errorReports = jsonDecode(await errorFile.readAsString());
+        final actionDescription = '${(errorReports as List).first} (cached)';
+        final logger = Logger(actionDescription);
+        for (final List error in errorReports.skip(1)) {
+          final stackTraceString = error[2] as String;
+          final stackTrace = stackTraceString.isEmpty
+              ? null
+              : StackTrace.fromString(stackTraceString);
+          logger.severe(error[0], error[1], stackTrace);
+        }
+      }
+    }));
+  }
+}
+
+/// Matches the call to [Logger.severe] except the [message] and [error] are
+/// eagerly converted to String.
+class ErrorReport {
+  final String message;
+  final String error;
+  final StackTrace stackTrace;
+  ErrorReport(this.message, this.error, this.stackTrace);
+}
+
+String _actionKey(GeneratedAssetNode node) =>
+    '${node.builderOptionsId} on ${node.primaryInput}';
+
+String _errorPathForOutput(GeneratedAssetNode output) => p.join(
+    p.fromUri(errorCachePath),
+    output.id.package,
+    '${output.phaseNumber}',
+    p.fromUri(output.primaryInput.path));
+
+String _errorPathForPrimaryInput(int phaseNumber, AssetId primaryInput) =>
+    p.join(p.fromUri(errorCachePath), primaryInput.package, '$phaseNumber',
+        p.fromUri(primaryInput.path));
diff --git a/build_runner_core/lib/src/logging/human_readable_duration.dart b/build_runner_core/lib/src/logging/human_readable_duration.dart
new file mode 100644
index 0000000..b03aee9
--- /dev/null
+++ b/build_runner_core/lib/src/logging/human_readable_duration.dart
@@ -0,0 +1,28 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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.
+
+/// Returns a human readable string for a duration.
+///
+/// Handles durations that span up to hours - this will not be a good fit for
+/// durations that are longer than days.
+///
+/// Always attempts 2 'levels' of precision. Will show hours/minutes,
+/// minutes/seconds, seconds/tenths of a second, or milliseconds depending on
+/// the largest level that needs to be displayed.
+String humanReadable(Duration duration) {
+  if (duration < const Duration(seconds: 1)) {
+    return '${duration.inMilliseconds}ms';
+  }
+  if (duration < const Duration(minutes: 1)) {
+    return '${(duration.inMilliseconds / 1000.0).toStringAsFixed(1)}s';
+  }
+  if (duration < const Duration(hours: 1)) {
+    final minutes = duration.inMinutes;
+    final remaining = duration - Duration(minutes: minutes);
+    return '${minutes}m ${remaining.inSeconds}s';
+  }
+  final hours = duration.inHours;
+  final remaining = duration - Duration(hours: hours);
+  return '${hours}h ${remaining.inMinutes}m';
+}
diff --git a/build_runner_core/lib/src/logging/logging.dart b/build_runner_core/lib/src/logging/logging.dart
new file mode 100644
index 0000000..1d93542
--- /dev/null
+++ b/build_runner_core/lib/src/logging/logging.dart
@@ -0,0 +1,48 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'package:logging/logging.dart';
+
+import 'human_readable_duration.dart';
+
+// Ensures this message does not get overwritten by later logs.
+final _logSuffix = '\n';
+
+/// Logs an asynchronous [action] with [description] before and after.
+///
+/// Returns a future that completes after the action and logging finishes.
+Future<T> logTimedAsync<T>(
+  Logger logger,
+  String description,
+  Future<T> Function() action, {
+  Level level = Level.INFO,
+}) async {
+  final watch = Stopwatch()..start();
+  logger.log(level, '$description...');
+  final result = await action();
+  watch.stop();
+  final time = '${humanReadable(watch.elapsed)}$_logSuffix';
+  logger.log(level, '$description completed, took $time');
+  return result;
+}
+
+/// Logs a synchronous [action] with [description] before and after.
+///
+/// Returns a future that completes after the action and logging finishes.
+T logTimedSync<T>(
+  Logger logger,
+  String description,
+  T Function() action, {
+  Level level = Level.INFO,
+}) {
+  final watch = Stopwatch()..start();
+  logger.log(level, '$description...');
+  final result = action();
+  watch.stop();
+  final time = '${humanReadable(watch.elapsed)}$_logSuffix';
+  logger.log(level, '$description completed, took $time');
+  return result;
+}
diff --git a/build_runner_core/lib/src/package_graph/apply_builders.dart b/build_runner_core/lib/src/package_graph/apply_builders.dart
new file mode 100644
index 0000000..a151016
--- /dev/null
+++ b/build_runner_core/lib/src/package_graph/apply_builders.dart
@@ -0,0 +1,411 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:convert';
+
+import 'package:build/build.dart';
+import 'package:build/src/builder/logging.dart';
+import 'package:build_config/build_config.dart';
+import 'package:graphs/graphs.dart';
+import 'package:logging/logging.dart';
+
+import '../generate/exceptions.dart';
+import '../generate/phase.dart';
+import '../validation/config_validation.dart';
+import 'package_graph.dart';
+import 'target_graph.dart';
+
+typedef BuildPhaseFactory = BuildPhase Function(
+    PackageNode package,
+    BuilderOptions options,
+    InputSet targetSources,
+    InputSet generateFor,
+    bool isReleaseBuild);
+
+typedef PackageFilter = bool Function(PackageNode node);
+
+/// Run a builder on all packages in the package graph.
+PackageFilter toAllPackages() => (_) => true;
+
+/// Require manual configuration to opt in to a builder.
+PackageFilter toNoneByDefault() => (_) => false;
+
+/// Run a builder on all packages with an immediate dependency on [packageName].
+PackageFilter toDependentsOf(String packageName) =>
+    (p) => p.dependencies.any((d) => d.name == packageName);
+
+/// Run a builder on a single package.
+PackageFilter toPackage(String package) => (p) => p.name == package;
+
+/// Run a builder on a collection of packages.
+PackageFilter toPackages(Set<String> packages) =>
+    (p) => packages.contains(p.name);
+
+/// Run a builders if the package matches any of [filters]
+PackageFilter toAll(Iterable<PackageFilter> filters) =>
+    (p) => filters.any((f) => f(p));
+
+PackageFilter toRoot() => (p) => p.isRoot;
+
+/// Apply [builder] to the root package.
+///
+/// Creates a `BuilderApplication` which corresponds to an empty builder key so
+/// that no other `build.yaml` based configuration will apply.
+BuilderApplication applyToRoot(Builder builder,
+        {bool isOptional = false,
+        bool hideOutput = false,
+        InputSet generateFor}) =>
+    BuilderApplication.forBuilder('', [(_) => builder], toRoot(),
+        isOptional: isOptional,
+        hideOutput: hideOutput,
+        defaultGenerateFor: generateFor);
+
+/// Apply each builder from [builderFactories] to the packages matching
+/// [filter].
+///
+/// If the builder should only run on a subset of files within a target pass
+/// globs to [defaultGenerateFor]. This can be overridden by any target which
+/// configured the builder manually.
+///
+/// If [isOptional] is true the builder will only run if one of its outputs is
+/// read by a later builder, or is used as a primary input to a later builder.
+/// If no build actions read the output of an optional action, then it will
+/// never run.
+///
+/// Any existing Builders which match a key in [appliesBuilders] will
+/// automatically be applied to any target which runs this Builder, whether
+/// because it matches [filter] or because it was enabled manually.
+BuilderApplication apply(String builderKey,
+        List<BuilderFactory> builderFactories, PackageFilter filter,
+        {bool isOptional,
+        bool hideOutput,
+        InputSet defaultGenerateFor,
+        BuilderOptions defaultOptions,
+        BuilderOptions defaultDevOptions,
+        BuilderOptions defaultReleaseOptions,
+        Iterable<String> appliesBuilders}) =>
+    BuilderApplication.forBuilder(
+      builderKey,
+      builderFactories,
+      filter,
+      isOptional: isOptional,
+      hideOutput: hideOutput,
+      defaultGenerateFor: defaultGenerateFor,
+      defaultOptions: defaultOptions,
+      defaultDevOptions: defaultDevOptions,
+      defaultReleaseOptions: defaultReleaseOptions,
+      appliesBuilders: appliesBuilders,
+    );
+
+/// Same as [apply] except it takes [PostProcessBuilderFactory]s.
+///
+/// Does not provide options for `isOptional` or `hideOutput` because they
+/// aren't configurable for these types of builders. They are never optional and
+/// always hidden.
+BuilderApplication applyPostProcess(
+        String builderKey, PostProcessBuilderFactory builderFactory,
+        {InputSet defaultGenerateFor,
+        BuilderOptions defaultOptions,
+        BuilderOptions defaultDevOptions,
+        BuilderOptions defaultReleaseOptions}) =>
+    BuilderApplication.forPostProcessBuilder(
+      builderKey,
+      builderFactory,
+      defaultGenerateFor: defaultGenerateFor,
+      defaultOptions: defaultOptions,
+      defaultDevOptions: defaultDevOptions,
+      defaultReleaseOptions: defaultReleaseOptions,
+    );
+
+/// A description of which packages need a given [Builder] or
+/// [PostProcessBuilder] applied.
+class BuilderApplication {
+  /// Factories that create [BuildPhase]s for all [Builder]s or
+  /// [PostProcessBuilder]s that should be applied.
+  final List<BuildPhaseFactory> buildPhaseFactories;
+
+  /// Determines whether a given package needs builder applied.
+  final PackageFilter filter;
+
+  /// Builder keys which, when applied to a target, will also apply this Builder
+  /// even if [filter] does not match.
+  final Iterable<String> appliesBuilders;
+
+  /// A uniqe key for this builder.
+  ///
+  /// Ignored when null or empty.
+  final String builderKey;
+
+  /// Whether genereated assets should be placed in the build cache.
+  final bool hideOutput;
+
+  const BuilderApplication._(
+    this.builderKey,
+    this.buildPhaseFactories,
+    this.filter,
+    this.hideOutput,
+    Iterable<String> appliesBuilders,
+  ) : appliesBuilders = appliesBuilders ?? const [];
+
+  factory BuilderApplication.forBuilder(
+    String builderKey,
+    List<BuilderFactory> builderFactories,
+    PackageFilter filter, {
+    bool isOptional,
+    bool hideOutput,
+    InputSet defaultGenerateFor,
+    BuilderOptions defaultOptions,
+    BuilderOptions defaultDevOptions,
+    BuilderOptions defaultReleaseOptions,
+    Iterable<String> appliesBuilders,
+  }) {
+    hideOutput ??= true;
+    var phaseFactories = builderFactories.map((builderFactory) {
+      return (PackageNode package, BuilderOptions options,
+          InputSet targetSources, InputSet generateFor, bool isReleaseBuild) {
+        generateFor ??= defaultGenerateFor;
+
+        var optionsWithDefaults = (defaultOptions ?? BuilderOptions.empty)
+            .overrideWith(
+                isReleaseBuild ? defaultReleaseOptions : defaultDevOptions)
+            .overrideWith(options);
+        if (package.isRoot) {
+          optionsWithDefaults =
+              optionsWithDefaults.overrideWith(BuilderOptions.forRoot);
+        }
+
+        final logger = Logger(builderKey);
+        final builder =
+            _scopeLogSync(() => builderFactory(optionsWithDefaults), logger);
+        if (builder == null) {
+          logger.severe(_factoryFailure(package.name, optionsWithDefaults));
+          throw CannotBuildException();
+        }
+        return InBuildPhase(builder, package.name,
+            builderKey: builderKey,
+            targetSources: targetSources,
+            generateFor: generateFor,
+            builderOptions: optionsWithDefaults,
+            hideOutput: hideOutput,
+            isOptional: isOptional);
+      };
+    }).toList();
+    return BuilderApplication._(
+        builderKey, phaseFactories, filter, hideOutput, appliesBuilders);
+  }
+
+  /// Note that these builder applications each create their own phase, but they
+  /// will all eventually be merged into a single phase.
+  factory BuilderApplication.forPostProcessBuilder(
+    String builderKey,
+    PostProcessBuilderFactory builderFactory, {
+    InputSet defaultGenerateFor,
+    BuilderOptions defaultOptions,
+    BuilderOptions defaultDevOptions,
+    BuilderOptions defaultReleaseOptions,
+  }) {
+    var phaseFactory = (PackageNode package, BuilderOptions options,
+        InputSet targetSources, InputSet generateFor, bool isReleaseBuild) {
+      generateFor ??= defaultGenerateFor;
+
+      var optionsWithDefaults = (defaultOptions ?? BuilderOptions.empty)
+          .overrideWith(
+              isReleaseBuild ? defaultReleaseOptions : defaultDevOptions)
+          .overrideWith(options);
+      if (package.isRoot) {
+        optionsWithDefaults =
+            optionsWithDefaults.overrideWith(BuilderOptions.forRoot);
+      }
+
+      final logger = Logger(builderKey);
+      final builder =
+          _scopeLogSync(() => builderFactory(optionsWithDefaults), logger);
+      if (builder == null) {
+        logger.severe(_factoryFailure(package.name, optionsWithDefaults));
+        throw CannotBuildException();
+      }
+      var builderAction = PostBuildAction(builder, package.name,
+          builderOptions: optionsWithDefaults,
+          generateFor: generateFor,
+          targetSources: targetSources);
+      return PostBuildPhase([builderAction]);
+    };
+    return BuilderApplication._(
+        builderKey, [phaseFactory], toNoneByDefault(), true, []);
+  }
+}
+
+final _logger = Logger('ApplyBuilders');
+
+/// Creates a [BuildPhase] to apply each builder in [builderApplications] to
+/// each target in [targetGraph] such that all builders are run for dependencies
+/// before moving on to later packages.
+///
+/// When there is a package cycle the builders are applied to each packages
+/// within the cycle before moving on to packages that depend on any package
+/// within the cycle.
+///
+/// Builders may be filtered, for instance to run only on package which have a
+/// dependency on some other package by choosing the appropriate
+/// [BuilderApplication].
+Future<List<BuildPhase>> createBuildPhases(
+    TargetGraph targetGraph,
+    Iterable<BuilderApplication> builderApplications,
+    Map<String, Map<String, dynamic>> builderConfigOverrides,
+    bool isReleaseMode) async {
+  validateBuilderConfig(builderApplications, targetGraph.rootPackageConfig,
+      builderConfigOverrides, _logger);
+  final globalOptions = targetGraph.rootPackageConfig.globalOptions.map(
+      (key, config) => MapEntry(
+          key,
+          _options(config?.options).overrideWith(isReleaseMode
+              ? _options(config?.releaseOptions)
+              : _options(config?.devOptions))));
+  for (final key in builderConfigOverrides.keys) {
+    final overrides = BuilderOptions(builderConfigOverrides[key]);
+    globalOptions[key] =
+        (globalOptions[key] ?? BuilderOptions.empty).overrideWith(overrides);
+  }
+
+  final cycles = stronglyConnectedComponents<TargetNode>(
+      targetGraph.allModules.values,
+      (node) => node.target.dependencies?.map((key) {
+            if (!targetGraph.allModules.containsKey(key)) {
+              _logger.severe('${node.target.key} declares a dependency on $key '
+                  'but it does not exist');
+              throw CannotBuildException();
+            }
+            return targetGraph.allModules[key];
+          })?.where((n) => n != null),
+      equals: (a, b) => a.target.key == b.target.key,
+      hashCode: (node) => node.target.key.hashCode);
+  final applyWith = _applyWith(builderApplications);
+  final allBuilders = Map<String, BuilderApplication>.fromIterable(
+      builderApplications,
+      key: (b) => (b as BuilderApplication).builderKey);
+  final expandedPhases = cycles
+      .expand((cycle) => _createBuildPhasesWithinCycle(
+          cycle,
+          builderApplications,
+          globalOptions,
+          applyWith,
+          allBuilders,
+          isReleaseMode))
+      .toList();
+
+  final inBuildPhases = expandedPhases.whereType<InBuildPhase>();
+
+  final postBuildPhases = expandedPhases.whereType<PostBuildPhase>().toList();
+  final collapsedPostBuildPhase = <PostBuildPhase>[];
+  if (postBuildPhases.isNotEmpty) {
+    collapsedPostBuildPhase.add(postBuildPhases
+        .fold<PostBuildPhase>(PostBuildPhase([]), (previous, next) {
+      previous.builderActions.addAll(next.builderActions);
+      return previous;
+    }));
+  }
+
+  return <BuildPhase>[...inBuildPhases, ...collapsedPostBuildPhase];
+}
+
+Iterable<BuildPhase> _createBuildPhasesWithinCycle(
+        Iterable<TargetNode> cycle,
+        Iterable<BuilderApplication> builderApplications,
+        Map<String, BuilderOptions> globalOptions,
+        Map<String, List<BuilderApplication>> applyWith,
+        Map<String, BuilderApplication> allBuilders,
+        bool isReleaseMode) =>
+    builderApplications.expand((builderApplication) =>
+        _createBuildPhasesForBuilderInCycle(
+            cycle,
+            builderApplication,
+            globalOptions[builderApplication.builderKey] ??
+                BuilderOptions.empty,
+            applyWith,
+            allBuilders,
+            isReleaseMode));
+
+Iterable<BuildPhase> _createBuildPhasesForBuilderInCycle(
+    Iterable<TargetNode> cycle,
+    BuilderApplication builderApplication,
+    BuilderOptions globalOptionOverrides,
+    Map<String, List<BuilderApplication>> applyWith,
+    Map<String, BuilderApplication> allBuilders,
+    bool isReleaseMode) {
+  TargetBuilderConfig targetConfig(TargetNode node) =>
+      node.target.builders[builderApplication.builderKey];
+  return builderApplication.buildPhaseFactories.expand((createPhase) => cycle
+          .where((targetNode) => _shouldApply(
+              builderApplication, targetNode, applyWith, allBuilders))
+          .map((node) {
+        final builderConfig = targetConfig(node);
+        final options = _options(builderConfig?.options)
+            .overrideWith(isReleaseMode
+                ? _options(builderConfig?.releaseOptions)
+                : _options(builderConfig?.devOptions))
+            .overrideWith(globalOptionOverrides);
+        return createPhase(node.package, options, node.target.sources,
+            builderConfig?.generateFor, isReleaseMode);
+      }));
+}
+
+bool _shouldApply(
+    BuilderApplication builderApplication,
+    TargetNode node,
+    Map<String, List<BuilderApplication>> applyWith,
+    Map<String, BuilderApplication> allBuilders) {
+  if (!(builderApplication.hideOutput &&
+          builderApplication.appliesBuilders
+              .every((b) => allBuilders[b]?.hideOutput ?? true)) &&
+      !node.package.isRoot) {
+    return false;
+  }
+  final builderConfig = node.target.builders[builderApplication.builderKey];
+  if (builderConfig?.isEnabled != null) {
+    return builderConfig.isEnabled;
+  }
+  final shouldAutoApply =
+      node.target.autoApplyBuilders && builderApplication.filter(node.package);
+  return shouldAutoApply ||
+      (applyWith[builderApplication.builderKey] ?? const []).any(
+          (anchorBuilder) =>
+              _shouldApply(anchorBuilder, node, applyWith, allBuilders));
+}
+
+/// Inverts the dependency map from 'applies builders' to 'applied with
+/// builders'.
+Map<String, List<BuilderApplication>> _applyWith(
+    Iterable<BuilderApplication> builderApplications) {
+  final applyWith = <String, List<BuilderApplication>>{};
+  for (final builderApplication in builderApplications) {
+    for (final alsoApply in builderApplication.appliesBuilders) {
+      applyWith.putIfAbsent(alsoApply, () => []).add(builderApplication);
+    }
+  }
+  return applyWith;
+}
+
+/// Runs [fn] in an error handling [Zone].
+///
+/// Any calls to [print] will be logged with `log.info`, and any errors will be
+/// logged with `log.severe`.
+T _scopeLogSync<T>(T Function() fn, Logger log) {
+  return runZoned(fn,
+      zoneSpecification:
+          ZoneSpecification(print: (self, parent, zone, message) {
+        log.info(message);
+      }),
+      zoneValues: {logKey: log},
+      onError: (Object e, StackTrace s) {
+        log.severe('', e, s);
+      });
+}
+
+String _factoryFailure(String packageName, BuilderOptions options) =>
+    'Failed to instantiate builder for $packageName with configuration:\n'
+    '${JsonEncoder.withIndent(' ').convert(options.config)}';
+
+BuilderOptions _options(Map<String, dynamic> options) =>
+    options?.isEmpty ?? true ? BuilderOptions.empty : BuilderOptions(options);
diff --git a/build_runner_core/lib/src/package_graph/package_graph.dart b/build_runner_core/lib/src/package_graph/package_graph.dart
new file mode 100644
index 0000000..2ef4a56
--- /dev/null
+++ b/build_runner_core/lib/src/package_graph/package_graph.dart
@@ -0,0 +1,250 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 'dart:io';
+
+import 'package:path/path.dart' as p;
+import 'package:yaml/yaml.dart';
+
+import '../util/constants.dart';
+
+/// The SDK package, we filter this to the core libs and dev compiler
+/// resources.
+final PackageNode _sdkPackageNode =
+    PackageNode(r'$sdk', sdkPath, DependencyType.hosted);
+
+/// A graph of the package dependencies for an application.
+class PackageGraph {
+  /// The root application package.
+  final PackageNode root;
+
+  /// All [PackageNode]s indexed by package name.
+  final Map<String, PackageNode> allPackages;
+
+  PackageGraph._(this.root, Map<String, PackageNode> allPackages)
+      : allPackages = Map.unmodifiable(
+            Map<String, PackageNode>.from(allPackages)
+              ..putIfAbsent(r'$sdk', () => _sdkPackageNode)) {
+    if (!root.isRoot) {
+      throw ArgumentError('Root node must indicate `isRoot`');
+    }
+    if (allPackages.values.where((n) => n != root).any((n) => n.isRoot)) {
+      throw ArgumentError('No nodes other than the root may indicate `isRoot`');
+    }
+  }
+
+  /// Creates a [PackageGraph] given the [root] [PackageNode].
+  factory PackageGraph.fromRoot(PackageNode root) {
+    final allPackages = <String, PackageNode>{root.name: root};
+
+    void addDeps(PackageNode package) {
+      for (var dep in package.dependencies) {
+        if (allPackages.containsKey(dep.name)) continue;
+        allPackages[dep.name] = dep;
+        addDeps(dep);
+      }
+    }
+
+    addDeps(root);
+
+    return PackageGraph._(root, allPackages);
+  }
+
+  /// Creates a [PackageGraph] for the package whose top level directory lives
+  /// at [packagePath] (no trailing slash).
+  factory PackageGraph.forPath(String packagePath) {
+    /// Read in the pubspec file and parse it as yaml.
+    final pubspec = File(p.join(packagePath, 'pubspec.yaml'));
+    if (!pubspec.existsSync()) {
+      throw StateError(
+          'Unable to generate package graph, no `pubspec.yaml` found. '
+          'This program must be ran from the root directory of your package.');
+    }
+    final rootPubspec = _pubspecForPath(packagePath);
+    final rootPackageName = rootPubspec['name'] as String;
+
+    final packageLocations = _parsePackageLocations(packagePath)
+      ..remove(rootPackageName);
+
+    final dependencyTypes = _parseDependencyTypes(packagePath);
+
+    final nodes = <String, PackageNode>{};
+    final rootNode = PackageNode(
+        rootPackageName, packagePath, DependencyType.path,
+        isRoot: true);
+    nodes[rootPackageName] = rootNode;
+    for (final packageName in packageLocations.keys) {
+      if (packageName == rootPackageName) continue;
+      nodes[packageName] = PackageNode(packageName,
+          packageLocations[packageName], dependencyTypes[packageName],
+          isRoot: false);
+    }
+
+    rootNode.dependencies
+        .addAll(_depsFromYaml(rootPubspec, isRoot: true).map((n) => nodes[n]));
+
+    final packageDependencies = _parsePackageDependencies(packageLocations);
+    for (final packageName in packageDependencies.keys) {
+      nodes[packageName]
+          .dependencies
+          .addAll(packageDependencies[packageName].map((n) => nodes[n]));
+    }
+    return PackageGraph._(rootNode, nodes);
+  }
+
+  /// Creates a [PackageGraph] for the package in which you are currently
+  /// running.
+  factory PackageGraph.forThisPackage() => PackageGraph.forPath('./');
+
+  /// Shorthand to get a package by name.
+  PackageNode operator [](String packageName) => allPackages[packageName];
+
+  @override
+  String toString() {
+    var buffer = StringBuffer();
+    for (var package in allPackages.values) {
+      buffer.writeln('$package');
+    }
+    return buffer.toString();
+  }
+}
+
+/// A node in a [PackageGraph].
+class PackageNode {
+  /// The name of the package as listed in `pubspec.yaml`.
+  final String name;
+
+  /// The type of dependency being used to pull in this package.
+  ///
+  /// May be `null`.
+  final DependencyType dependencyType;
+
+  /// All the packages that this package directly depends on.
+  final List<PackageNode> dependencies = [];
+
+  /// The absolute path of the current version of this package.
+  ///
+  /// Paths are platform dependent.
+  final String path;
+
+  /// Whether this node is the [PackageGraph.root].
+  final bool isRoot;
+
+  PackageNode(this.name, String path, this.dependencyType, {bool isRoot})
+      : path = _toAbsolute(path),
+        isRoot = isRoot ?? false;
+
+  @override
+  String toString() => '''
+  $name:
+    type: $dependencyType
+    path: $path
+    dependencies: [${dependencies.map((d) => d.name).join(', ')}]''';
+
+  /// Converts [path] to a canonical absolute path, returns `null` if given
+  /// `null`.
+  static String _toAbsolute(String path) =>
+      (path == null) ? null : p.canonicalize(path);
+}
+
+/// Parse the `.packages` file and return a Map from package name to the file
+/// location for that package.
+Map<String, String> _parsePackageLocations(String rootPackagePath) {
+  var packagesFile = File(p.join(rootPackagePath, '.packages'));
+  if (!packagesFile.existsSync()) {
+    throw StateError('Unable to generate package graph, no `.packages` found. '
+        'This program must be ran from the root directory of your package.');
+  }
+  var packageLocations = <String, String>{};
+  for (final line in packagesFile.readAsLinesSync().skip(1)) {
+    var firstColon = line.indexOf(':');
+    var name = line.substring(0, firstColon);
+    assert(line.endsWith('lib/'));
+    // Start after package_name:, and strip out trailing 'lib/'.
+    var uriString = line.substring(firstColon + 1, line.length - 4);
+    // Strip the trailing slash, if present.
+    if (uriString.endsWith('/')) {
+      uriString = uriString.substring(0, uriString.length - 1);
+    }
+    var uri = Uri.tryParse(uriString) ?? Uri.file(uriString);
+    if (!uri.isAbsolute) {
+      uri = p.toUri(p.join(rootPackagePath, uri.path));
+    }
+    packageLocations[name] = uri.toFilePath(windows: Platform.isWindows);
+  }
+  return packageLocations;
+}
+
+/// The type of dependency being used. This dictates how the package should be
+/// watched for changes.
+enum DependencyType { github, path, hosted }
+
+/// Parse the `pubspec.lock` file and return a Map from package name to the type
+/// of dependency.
+Map<String, DependencyType> _parseDependencyTypes(String rootPackagePath) {
+  final pubspecLock = File(p.join(rootPackagePath, 'pubspec.lock'));
+  if (!pubspecLock.existsSync()) {
+    throw StateError(
+        'Unable to generate package graph, no `pubspec.lock` found. '
+        'This program must be ran from the root directory of your package.');
+  }
+  final dependencyTypes = <String, DependencyType>{};
+  final dependencies = loadYaml(pubspecLock.readAsStringSync()) as YamlMap;
+  for (final packageName in dependencies['packages'].keys) {
+    final source = dependencies['packages'][packageName]['source'];
+    dependencyTypes[packageName as String] =
+        _dependencyTypeFromSource(source as String);
+  }
+  return dependencyTypes;
+}
+
+DependencyType _dependencyTypeFromSource(String source) {
+  switch (source) {
+    case 'git':
+      return DependencyType.github;
+    case 'hosted':
+      return DependencyType.hosted;
+    case 'path':
+    case 'sdk': // Until Flutter supports another type, assum same as path.
+      return DependencyType.path;
+  }
+  throw ArgumentError('Unable to determine dependency type:\n$source');
+}
+
+/// Read the pubspec for each package in [packageLocations] and finds it's
+/// dependencies.
+Map<String, Set<String>> _parsePackageDependencies(
+    Map<String, String> packageLocations) {
+  final dependencies = <String, Set<String>>{};
+  for (final packageName in packageLocations.keys) {
+    final pubspec = _pubspecForPath(packageLocations[packageName]);
+    dependencies[packageName] = _depsFromYaml(pubspec);
+  }
+  return dependencies;
+}
+
+/// Gets the deps from a yaml file, taking into account dependency_overrides.
+Set<String> _depsFromYaml(YamlMap yaml, {bool isRoot = false}) {
+  var deps = <String>{}..addAll(_stringKeys(yaml['dependencies'] as Map));
+  if (isRoot) {
+    deps
+      ..addAll(_stringKeys(yaml['dev_dependencies'] as Map))
+      ..addAll(_stringKeys(yaml['dependency_overrides'] as Map));
+  }
+  return deps;
+}
+
+Iterable<String> _stringKeys(Map m) =>
+    m == null ? const [] : m.keys.cast<String>();
+
+/// Should point to the top level directory for the package.
+YamlMap _pubspecForPath(String absolutePath) {
+  var pubspecPath = p.join(absolutePath, 'pubspec.yaml');
+  var pubspec = File(pubspecPath);
+  if (!pubspec.existsSync()) {
+    throw StateError(
+        'Unable to generate package graph, no `$pubspecPath` found.');
+  }
+  return loadYaml(pubspec.readAsStringSync()) as YamlMap;
+}
diff --git a/build_runner_core/lib/src/package_graph/target_graph.dart b/build_runner_core/lib/src/package_graph/target_graph.dart
new file mode 100644
index 0000000..d66b158
--- /dev/null
+++ b/build_runner_core/lib/src/package_graph/target_graph.dart
@@ -0,0 +1,102 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'package:build/build.dart';
+import 'package:build_config/build_config.dart';
+import 'package:glob/glob.dart';
+
+import '../generate/input_matcher.dart';
+import 'package_graph.dart';
+
+/// Like a [PackageGraph] but packages are further broken down into modules
+/// based on build config.
+class TargetGraph {
+  /// All [TargetNode]s indexed by `"$packageName:$targetName"`.
+  final Map<String, TargetNode> allModules;
+
+  /// All [TargetNode]s by package name.
+  final Map<String, List<TargetNode>> modulesByPackage;
+
+  /// The [BuildConfig] of the root package.
+  final BuildConfig rootPackageConfig;
+
+  TargetGraph._(this.allModules, this.modulesByPackage, this.rootPackageConfig);
+
+  static Future<TargetGraph> forPackageGraph(PackageGraph packageGraph,
+      {Map<String, BuildConfig> overrideBuildConfig,
+      List<String> defaultRootPackageWhitelist}) async {
+    overrideBuildConfig ??= const {};
+    final modulesByKey = <String, TargetNode>{};
+    final modulesByPackage = <String, List<TargetNode>>{};
+    BuildConfig rootPackageConfig;
+    for (final package in packageGraph.allPackages.values) {
+      final config = overrideBuildConfig[package.name] ??
+          await _packageBuildConfig(package);
+      List<String> defaultInclude;
+      if (package.isRoot) {
+        defaultInclude = defaultRootPackageWhitelist;
+        rootPackageConfig = config;
+      } else if (package.name == r'$sdk') {
+        defaultInclude = const [
+          'lib/dev_compiler/**.js',
+          'lib/_internal/**.sum',
+        ];
+      } else {
+        defaultInclude = const ['lib/**'];
+      }
+      final nodes = config.buildTargets.values.map((target) =>
+          TargetNode(target, package, defaultInclude: defaultInclude));
+      for (final node in nodes) {
+        modulesByKey[node.target.key] = node;
+        modulesByPackage.putIfAbsent(node.target.package, () => []).add(node);
+      }
+    }
+    return TargetGraph._(modulesByKey, modulesByPackage, rootPackageConfig);
+  }
+
+  /// Whether or not [id] is included in the sources of any target in the graph.
+  bool anyMatchesAsset(AssetId id) =>
+      modulesByPackage[id.package]?.any((t) => t.matchesSource(id)) ?? false;
+}
+
+class TargetNode {
+  final BuildTarget target;
+  final PackageNode package;
+
+  List<Glob> get sourceIncludes => _sourcesMatcher.includeGlobs;
+  final InputMatcher _sourcesMatcher;
+
+  TargetNode(this.target, this.package, {List<String> defaultInclude})
+      : _sourcesMatcher =
+            InputMatcher(target.sources, defaultInclude: defaultInclude);
+
+  bool excludesSource(AssetId id) => _sourcesMatcher.excludes(id);
+
+  bool matchesSource(AssetId id) => _sourcesMatcher.matches(id);
+
+  @override
+  String toString() => target.key;
+}
+
+Future<BuildConfig> _packageBuildConfig(PackageNode package) async {
+  final dependencyNames = package.dependencies.map((n) => n.name);
+  if (package.path == null) {
+    return BuildConfig.useDefault(package.name, dependencyNames);
+  }
+  try {
+    return await BuildConfig.fromBuildConfigDir(
+        package.name, dependencyNames, package.path);
+  } on ArgumentError catch (e) {
+    throw BuildConfigParseException(package.name, e);
+  }
+}
+
+class BuildConfigParseException implements Exception {
+  final String packageName;
+  final dynamic exception;
+
+  BuildConfigParseException(this.packageName, this.exception);
+}
diff --git a/build_runner_core/lib/src/performance_tracking/performance_tracking_resolvers.dart b/build_runner_core/lib/src/performance_tracking/performance_tracking_resolvers.dart
new file mode 100644
index 0000000..0155e55
--- /dev/null
+++ b/build_runner_core/lib/src/performance_tracking/performance_tracking_resolvers.dart
@@ -0,0 +1,23 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'package:build/build.dart';
+
+import '../generate/performance_tracker.dart';
+
+class PerformanceTrackingResolvers implements Resolvers {
+  final Resolvers _delegate;
+  final BuilderActionTracker _tracker;
+
+  PerformanceTrackingResolvers(this._delegate, this._tracker);
+
+  @override
+  Future<ReleasableResolver> get(BuildStep buildStep) =>
+      _tracker.trackStage('ResolverGet', () => _delegate.get(buildStep));
+
+  @override
+  void reset() => _delegate.reset();
+}
diff --git a/build_runner_core/lib/src/util/async.dart b/build_runner_core/lib/src/util/async.dart
new file mode 100644
index 0000000..75d755b
--- /dev/null
+++ b/build_runner_core/lib/src/util/async.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+/// Invokes [callback] and returns the result as soon as possible. This will
+/// happen synchronously if [value] is available.
+FutureOr<S> doAfter<T, S>(
+    FutureOr<T> value, FutureOr<S> Function(T value) callback) {
+  if (value is Future<T>) {
+    return value.then(callback);
+  } else {
+    return callback(value as T);
+  }
+}
+
+/// Converts [value] to a [Future] if it is not already.
+Future<T> toFuture<T>(FutureOr<T> value) =>
+    value is Future<T> ? value : Future.value(value);
diff --git a/build_runner_core/lib/src/util/build_dirs.dart b/build_runner_core/lib/src/util/build_dirs.dart
new file mode 100644
index 0000000..1301186
--- /dev/null
+++ b/build_runner_core/lib/src/util/build_dirs.dart
@@ -0,0 +1,38 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'package:build/build.dart';
+
+import '../generate/options.dart';
+import '../generate/phase.dart';
+
+/// Returns whether or not [id] should be built based upon [buildDirs],
+/// [phase], and optional [buildFilters].
+///
+/// The logic for this is as follows:
+///
+/// - If any [buildFilters] are supplied, then this only returns `true` if [id]
+///   explicitly matches one of the filters.
+/// - If no [buildFilters] are supplied, then the old behavior applies - all
+///   build to source builders and all files under `lib` of all packages are
+///   always built.
+/// - Regardless of the [buildFilters] setting, if [buildDirs] is supplied then
+///   `id.path` must start with one of the specified directory names.
+bool shouldBuildForDirs(AssetId id, Set<String> buildDirs,
+    Set<BuildFilter> buildFilters, BuildPhase phase) {
+  buildFilters ??= {};
+  if (buildFilters.isEmpty) {
+    if (!phase.hideOutput) return true;
+
+    if (id.path.startsWith('lib/')) return true;
+  } else {
+    if (!buildFilters.any((f) => f.matches(id))) {
+      return false;
+    }
+  }
+
+  if (buildDirs.isEmpty) return true;
+
+  return id.path.startsWith('lib/') || buildDirs.any(id.path.startsWith);
+}
diff --git a/build_runner_core/lib/src/util/clock.dart b/build_runner_core/lib/src/util/clock.dart
new file mode 100644
index 0000000..5b2db75
--- /dev/null
+++ b/build_runner_core/lib/src/util/clock.dart
@@ -0,0 +1,18 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+/// A function that returns the current [DateTime].
+typedef _Clock = DateTime Function();
+DateTime _defaultClock() => DateTime.now();
+
+/// Returns the current [DateTime].
+///
+/// May be overridden for tests using [scopeClock].
+DateTime now() => (Zone.current[_Clock] as _Clock ?? _defaultClock)();
+
+/// Runs [f], with [clock] scoped whenever [now] is called.
+T scopeClock<T>(DateTime Function() clock, T Function() f) =>
+    runZoned(f, zoneValues: {_Clock: clock});
diff --git a/build_runner_core/lib/src/util/constants.dart b/build_runner_core/lib/src/util/constants.dart
new file mode 100644
index 0000000..eb1d073
--- /dev/null
+++ b/build_runner_core/lib/src/util/constants.dart
@@ -0,0 +1,87 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 'dart:convert';
+import 'dart:io';
+
+import 'package:crypto/crypto.dart';
+import 'package:path/path.dart' as p;
+
+/// Relative path to the asset graph from the root package dir.
+final String assetGraphPath = assetGraphPathFor(_scriptPath);
+
+/// Relative path to the asset graph for a build script at [path]
+String assetGraphPathFor(String path) =>
+    '$cacheDir/${_scriptHashFor(path)}/asset_graph.json';
+
+/// Relative path to the directory which holds serialized versions of errors
+/// reported during previous builds.
+final errorCachePath =
+    p.url.join(cacheDir, _scriptHashFor(_scriptPath), 'error_cache');
+
+final String _scriptPath = Platform.script.scheme == 'file'
+    ? p.url.joinAll(
+        p.split(p.relative(Platform.script.toFilePath(), from: p.current)))
+    : Platform.script.path;
+
+/// Directory containing automatically generated build entrypoints.
+///
+/// Files in this directory must be read to do build script invalidation.
+const entryPointDir = '$cacheDir/entrypoint';
+
+/// The directory to which hidden assets will be written.
+String get generatedOutputDirectory => '$cacheDir/$_generatedOutputDirectory';
+
+/// Locks the generated directory name for the duration of this process.
+///
+/// This should be invoked before any builds start.
+void lockGeneratedOutputDirectory() => _generatedOutputDirectoryIsLocked = true;
+
+/// The default generated dir name. Can be modified with
+/// [overrideGeneratedOutputDirectory].
+String _generatedOutputDirectory = 'generated';
+
+/// Whether or not the [generatedOutputDirectory] is locked. This must be `true`
+/// before you can access [generatedOutputDirectory];
+bool _generatedOutputDirectoryIsLocked = false;
+
+/// Overrides the generated directory name.
+///
+/// This is interpreted as a relative path under the [cacheDir].
+void overrideGeneratedOutputDirectory(String path) {
+  if (_generatedOutputDirectory != 'generated') {
+    throw StateError('You can only override the generated dir once.');
+  } else if (_generatedOutputDirectoryIsLocked) {
+    throw StateError(
+        'Attempted to override the generated dir after it was locked.');
+  } else if (!p.isRelative(path)) {
+    throw StateError('Only relative paths are accepted for the generated dir '
+        'but got `$path`.');
+  }
+  _generatedOutputDirectory = path;
+}
+
+/// Relative path to the cache directory from the root package dir.
+const String cacheDir = '.dart_tool/build';
+
+/// Returns a hash for a given Dart script path.
+///
+/// Normalizes between snapshot and Dart source file paths so they give the same
+/// hash.
+String _scriptHashFor(String path) => md5
+    .convert(utf8.encode(
+        path.endsWith('.snapshot') ? path.substring(0, path.length - 9) : path))
+    .toString();
+
+/// The name of the pub binary on the current platform.
+final pubBinary = p.join(sdkBin, Platform.isWindows ? 'pub.bat' : 'pub');
+
+/// The path to the sdk bin directory on the current platform.
+final sdkBin = p.join(sdkPath, 'bin');
+
+/// The path to the sdk on the current platform.
+final sdkPath = p.dirname(p.dirname(Platform.resolvedExecutable));
+
+/// The maximum number of concurrent actions to run per build phase.
+const buildPhasePoolSize = 16;
diff --git a/build_runner_core/lib/src/util/hash.dart b/build_runner_core/lib/src/util/hash.dart
new file mode 100644
index 0000000..d8539e2
--- /dev/null
+++ b/build_runner_core/lib/src/util/hash.dart
@@ -0,0 +1,15 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS 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.
+
+int hashCombine(int hash, int value) {
+  hash = 0x1fffffff & (hash + value);
+  hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
+  return hash ^ (hash >> 6);
+}
+
+int hashComplete(int hash) {
+  hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
+  hash = hash ^ (hash >> 11);
+  return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
+}
diff --git a/build_runner_core/lib/src/util/sdk_version_match.dart b/build_runner_core/lib/src/util/sdk_version_match.dart
new file mode 100644
index 0000000..f584862
--- /dev/null
+++ b/build_runner_core/lib/src/util/sdk_version_match.dart
@@ -0,0 +1,8 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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.
+
+/// Checks whether [thisVersion] and [thatVersion] have the same semver
+/// identifier without extra platform specific information.
+bool isSameSdkVersion(String thisVersion, String thatVersion) =>
+    thisVersion?.split(' ')?.first == thatVersion?.split(' ')?.first;
diff --git a/build_runner_core/lib/src/validation/config_validation.dart b/build_runner_core/lib/src/validation/config_validation.dart
new file mode 100644
index 0000000..2ec9c7e
--- /dev/null
+++ b/build_runner_core/lib/src/validation/config_validation.dart
@@ -0,0 +1,37 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'package:build_config/build_config.dart';
+import 'package:logging/logging.dart';
+
+import '../package_graph/apply_builders.dart';
+
+/// Checks that all configuration is for valid builder keys.
+void validateBuilderConfig(
+    Iterable<BuilderApplication> builders,
+    BuildConfig rootPackageConfig,
+    Map<String, Map<String, dynamic>> builderConfigOverrides,
+    Logger logger) {
+  final builderKeys = builders.map((b) => b.builderKey).toSet();
+  for (final key in builderConfigOverrides.keys) {
+    if (!builderKeys.contains(key)) {
+      logger.warning('Overriding configuration for `$key` but this is not a '
+          'known Builder');
+    }
+  }
+  for (final target in rootPackageConfig.buildTargets.values) {
+    for (final key in target.builders.keys) {
+      if (!builderKeys.contains(key)) {
+        logger.warning('Configuring `$key` in target `${target.key}` but this '
+            'is not a known Builder');
+      }
+    }
+  }
+  for (final key in rootPackageConfig.globalOptions.keys) {
+    if (!builderKeys.contains(key)) {
+      logger.warning('Configuring `$key` in global options but this is not a '
+          'known Builder');
+    }
+  }
+}
diff --git a/build_runner_core/mono_pkg.yaml b/build_runner_core/mono_pkg.yaml
new file mode 100644
index 0000000..74ca9f7
--- /dev/null
+++ b/build_runner_core/mono_pkg.yaml
@@ -0,0 +1,17 @@
+dart:
+  - 2.6.0
+  - dev
+
+stages:
+  - analyze_and_format:
+    - group:
+      - dartfmt: sdk
+      - dartanalyzer: --fatal-infos --fatal-warnings .
+      dart: dev
+    - dartanalyzer: --fatal-warnings .
+      dart: 2.6.0
+  - unit_test:
+    - test:
+      os:
+        - linux
+        - windows
diff --git a/build_runner_core/pubspec.yaml b/build_runner_core/pubspec.yaml
new file mode 100644
index 0000000..e87a441
--- /dev/null
+++ b/build_runner_core/pubspec.yaml
@@ -0,0 +1,37 @@
+name: build_runner_core
+version: 4.4.0
+description: Core tools to write binaries that run builders.
+homepage: https://github.com/dart-lang/build/tree/master/build_runner_core
+
+environment:
+  sdk: ">=2.6.0 <3.0.0"
+
+dependencies:
+  async: ">=1.13.3 <3.0.0"
+  build: ">=1.2.0 <1.3.0"
+  build_config: ">=0.4.2 <0.4.3"
+  build_resolvers: ^1.0.0
+  collection: ^1.14.0
+  convert: ^2.0.1
+  crypto: ">=0.9.2 <3.0.0"
+  glob: ^1.1.0
+  graphs: ^0.2.0
+  json_annotation: '>=1.0.0 <4.0.0'
+  logging: ^0.11.2
+  meta: ^1.1.0
+  path: ^1.1.0
+  pedantic: ^1.0.0
+  pool: ^1.0.0
+  timing: ^0.1.1
+  watcher: ^0.9.7
+  yaml: ^2.1.0
+
+dev_dependencies:
+  build_test: ^0.10.0
+  package_resolver: ^1.0.2
+  source_gen: ^0.9.0
+  test: ^1.0.0
+  test_descriptor: ^1.0.0
+  test_process: ^1.0.0
+  _test_common:
+    path: ../_test_common
diff --git a/build_test/BUILD.gn b/build_test/BUILD.gn
new file mode 100644
index 0000000..f490fa0
--- /dev/null
+++ b/build_test/BUILD.gn
@@ -0,0 +1,32 @@
+# This file is generated by importer.py for build_test-0.10.12+1
+
+import("//build/dart/dart_library.gni")
+
+dart_library("build_test") {
+  package_name = "build_test"
+
+  # This parameter is left empty as we don't care about analysis or exporting
+  # these sources outside of the tree.
+  sources = []
+
+  disable_analysis = true
+
+  deps = [
+    "//third_party/dart-pkg/pub/pedantic",
+    "//third_party/dart-pkg/pub/matcher",
+    "//third_party/dart-pkg/pub/glob",
+    "//third_party/dart-pkg/pub/watcher",
+    "//third_party/dart-pkg/pub/test_core",
+    "//third_party/dart-pkg/pub/package_resolver",
+    "//third_party/dart-pkg/pub/path",
+    "//third_party/dart-pkg/pub/logging",
+    "//third_party/dart-pkg/pub/crypto",
+    "//third_party/dart-pkg/pub/stream_transform",
+    "//third_party/dart-pkg/pub/html",
+    "//third_party/dart-pkg/pub/build",
+    "//third_party/dart-pkg/pub/build_resolvers",
+    "//third_party/dart-pkg/pub/test",
+    "//third_party/dart-pkg/pub/async",
+    "//third_party/dart-pkg/pub/build_config",
+  ]
+}
diff --git a/build_test/CHANGELOG.md b/build_test/CHANGELOG.md
new file mode 100644
index 0000000..f0d57ab
--- /dev/null
+++ b/build_test/CHANGELOG.md
@@ -0,0 +1,426 @@
+## 0.10.12+1
+
+- Allow the latest test_core package (`0.3.x`).
+
+## 0.10.12
+
+- Fix a bug with the `resolve*` apis where they would leak unhandled async
+  errors to client code if the provided action callback threw an error.
+
+## 0.10.11
+
+- Add support for the new `$package$` placeholder.
+
+### Potentially Breaking Change
+
+- Only add the non-lib placeholders when a root package is specified
+  - Infer the root package when there is only one package in the sources
+  - This is being released as a non-breaking change because the only expected
+    use cases already would have been broken - `findAssets` calls already
+    required a root package.
+
+## 0.10.10
+
+- Allow reading of assets written from the same build step.
+  - This mirrors the latest behavior in build_runner_core.
+- Require SDK version `2.6.0` to enable extension methods.
+
+## 0.10.9+1
+
+- Fix the `DebugTestBuilder` on windows.
+- Fix `PackageAssetReader` on windows.
+
+## 0.10.9
+
+- Allow tracking of reported unused assets in `testBuilder` calls with the
+  `reportUnusedAssetsForInput(AssetId input, Iterable<AssetId> unused)`
+  callback.
+
+## 0.10.8
+
+- Allow a custom AssetReader to be passed to `testBuilder`. This will be used
+  as a fallback for any sources that don't exist in the `sourceAssets` map.
+
+## 0.10.7+3
+
+- Handle the case where the root package in a `PackageAssetReader` is a fake
+  package.
+
+## 0.10.7+2
+
+- Avoid throwing for missing files from `PackageAssetReader.canRead`.
+
+## 0.10.7+1
+
+- Allow `build_config` `0.4.x`.
+
+## 0.10.7
+
+- Support the latest version of `package:html`.
+- Only generate bootstrap scripts for supported platforms based on `TestOn`
+  annotations.
+
+## 0.10.6
+
+- Allow build_resolvers version `1.0.0`.
+
+## 0.10.5
+
+- Improve error messages for unresolvable URIs in the PackageAssetReader.
+
+## 0.10.4
+
+- Allow using `PackageAssetReader` when the current working directory is not the
+  root package directory as long as it uses a pub layout.
+
+## 0.10.3+4
+
+- Increased the upper bound for `package:analyzer` to `<0.35.0`.
+
+## 0.10.3+3
+
+- Increased the upper bound for `package:analyzer` to '<0.34.0'.
+
+## 0.10.3+2
+
+- Declare support for `package:build` version 1.0.0.
+
+## 0.10.3+1
+
+- Increased the upper bound for the sdk to `<3.0.0`.
+
+## 0.10.3
+
+- Require test version ^0.12.42 and use `TypeMatcher`.
+- Improve performance of test methods which use a `Resolver` by keeping a cached
+  instance of `AnalyzerResolvers`.
+
+## 0.10.2+4
+
+- Allow the latest build_config.
+
+## 0.10.2+3
+
+- Remove package:build_barback dependency, and use public apis from package:test
+  to directly do the bootstrapping instead of wrapping the transformer.
+
+## 0.10.2+2
+
+- Avoid looking for files from `Uri.path` paths.
+
+## 0.10.2+1
+
+- Add back an implementation of `findAssets` in `PackageAssetReader`.
+
+## 0.10.2
+
+- Added a `DebugIndexBuilder`, which by default generates a `test/index.html`
+  with links to debug tests in your `test/**_test.dart` folder, linking to the
+  generated `*_test.debug.html` files. **NOTE**: This only works for web-based
+  tests.
+- Fix `PackageAssetReader` when running with a package map pointing to a
+  "packages" directory structure, as is generated by `build_runner`. Drop
+  support for a broken `findAssets` implementation.
+
+## 0.10.1+1
+
+- Replace `BarbackResolvers` with `AnalyzerResolvers` from `build_resolvers` by
+  default.
+
+## 0.10.1
+
+- Allow overriding the `Resolvers` used for `resolve*` utilities.
+- Bug Fix: Don't call the `action` multiple times when there are multiple
+  sources passed to `resolve*`.
+
+## 0.10.0
+
+- Added automatic generation of `.debug.html` files for all tests, which can
+  be loaded in the browser to directly run tests and debug them without going
+  through the package:test runner.
+- Update to package:build version `0.12.0`.
+- Removed `CopyBuilder` in favor of `TestBuilder` which takes closures to
+  change behavior rather than adding configuration for every possible
+  modification.
+- Added support for the special placeholder `{$lib/$test/$web}` assets
+  supported by the `build_runner` and `bazel_codegen` implementations of
+  `package:build`. For an example see `test/test_builder_test.dart`. Note that
+  this is technically a **BREAKING CHANGE**, as additional inputs will be
+  matched for overzealous builders (like `TestBuilder`).
+- Added `resolverFor` as an optional parameter to `resolveSources`. By default
+  a `Resolver` is returned for the _first_ asset provided; to modify that the
+  name of another asset may be provided. This is a **BREAKING CHANGE**, as
+  previously the last asset was used.
+
+## 0.9.4
+
+- Added `InMemoryAssetReader.shareAssetCache` constructor. This is useful for the
+  case where the reader should be kept up to date as assets are written through
+  a writer.
+- Added `buildInputs` stream to `CopyBuilder` which emits an event for each
+  `BuildStep.inputId` at the top of the `build` method.
+- `CopyBuilder` automatically skips the placeholder files (any file ending in
+  `$`). This is technically breaking but should not affect any real users and is
+  not being released as a breaking change.
+- Changed `TestBootstrapBuilder` to only target `_test.dart` files.
+
+## 0.9.3
+
+- Added `resolveSources`, a way to resolve multiple libraries for testing,
+  including any combination of fake files (in-memory, created in the test) and
+  real ones (from on-disk packages):
+
+```dart
+test('multiple assets, some mock, some on disk', () async {
+  final real = 'build_test|test/_files/example_lib.dart';
+  final mock = 'build_test|test/_files/not_really_here.dart';
+  final library = await resolveSources(
+    {
+      real: useAssetReader,
+      mock: r'''
+        // This is a fake library that we're mocking.
+        library example;
+
+        // This is a real on-disk library we are using.
+        import 'example_lib.dart';
+
+        class ExamplePrime extends Example {}
+      ''',
+    },
+    (resolver) => resolver.findLibraryByName('example'),
+  );
+  final type = library.getType('ExamplePrime');
+  expect(type, isNotNull);
+  expect(type.supertype.name, 'Example');
+});
+```
+
+## 0.9.2
+
+- Add `inputExtension` argument to `CopyBuilder`. When used the builder with
+  throw if any assets are provided that don't match the input extension.
+
+## 0.9.1
+
+- Allow `build_barback` version `0.5.x`. The breaking behavior change should not
+  impact test uses that don't already have a version constraint on that package.
+
+## 0.9.0
+
+- Added the `TestBootstrapBuilder` under the `builder.dart` library. This can
+  be used to bootstrap tests similar to the `test/pub_serve` Transformer.
+  - **Known Issue**: Custom html files are not supported.
+- **Breaking**: All `AssetReader#findAssets` implementations now return a
+  `Stream<AssetId>` to match the latest `build` package.
+- **Breaking**: The `DatedValue`, `DatedString`, and `DatedBytes` apis are now
+  gone, since timestamps are no longer used by package:build. Instead the
+  `InMemoryAssetReader` and other apis take a `Map<AssetId, dynamic>`, and will
+  automatically convert any `String` values into  `List<int>` values.
+- **Breaking**: The `outputs` map of `testBuilder` works a little bit
+  differently due to the removal of `DatedValue` and `DatedString`.
+  * If a value provided is a `String`, then the asset is by default decoded
+    using UTF8, and matched against the string.
+  * If the value is a `matcher`, it will match againt the actual bytes of the
+    asset (in `List<int>` form).
+  * A new matcher was added, `decodedMatches`, which can be combined with other
+    matchers to match against the string contents. For example, to match a
+    string containing a substring, you would do
+    `decodedMatches(contains('some substring'))`.
+
+## 0.8.0
+
+- `InMemoryAssetReader`, `MultiAssetReader`, `StubAssetReader` and
+  `PackageAssetReader` now implement the `MultiPackageAssetReader` interface.
+- `testBuilder` now supports `Builder`s that call `findAssets` in non-root
+  packages.
+- Added a `GlobbingBuilder` which globs files in a package.
+- Added the `RecordingAssetReader` interface, which adds the
+  `Iterable<AssetId> get assetsRead` getter.
+- `InMemoryAssetReader` now implements `RecordingAssetReader`.
+- **Breaking**: The `MultiAssetReader` now requires all its wrapped readers to
+  implement the `MultiPackageAssetReader` interface.
+  - This should not affect most users, since all readers provided by this
+    package now implement that interface.
+
+## 0.7.1
+
+- Add `mapAssetIds` argument to `checkOutputs` for cases where the logical asset
+  location recorded by the builder does not match the written location.
+
+- Add `recordLogs`, a top-level function that invokes `scopeLog` and captures
+  the resulting `Stream<LogRecord>` for testing. Can be used with the provided
+  `anyLogOf`, `infoLogOf`, `warningLogOf`, `severeLogOf` matchers in order to
+  test a build process:
+
+```dart
+test('should log "uh oh!"', () async {
+  final logs = recordLogs(() => runBuilder());
+  expect(logs, emitsInOrder([
+    anyLogOf('uh oh!'),
+  ]);
+});
+```
+
+- Add the constructors `forPackages` and `forPackageRoot` to
+  `PackageAssetReader` - these are convenience constructors for pointing to a
+  small set of packages (fake or real) for testing purposes. For example:
+
+```dart
+test('should resolve multiple libraries', () async {
+  reader = new PackageAssetReader.forPackages({
+    'example_a': '_libs/example_a',
+    'example_b': '_libs/example_b',
+  });
+  expect(await reader.canRead(fileFromExampleLibA), isTrue);
+  expect(await reader.canRead(fileFromExampleLibB), isTrue);
+  expect(await reader.canRead(fileFromExampleLibC), isFalse);
+});
+```
+
+## 0.7.0+1
+
+- Switch to a typedef from function type syntax for compatibility with older
+  SDKs.
+
+## 0.7.0
+
+- **Breaking**: `resolveSource` and `resolveAsset` now take an `action` to
+  perform with the Resolver instance.
+
+## 0.6.4+1
+
+- Allow `package:build_barback` v0.4.x
+
+## 0.6.4
+
+- Allow `package:build` v0.10.x
+- `AssetReader` implementations always return `Future` from `canRead`
+
+## 0.6.3
+
+- Added `resolveAsset`, which is similar to `resolveSource` but specifies a
+  real asset that lives on the file system. For example, to resolve the main
+  library of `package:collection`:
+
+```dart
+var pkgCollection = new AssetId('collection', 'lib/collection.dart');
+var resolver = await resolveAsset(pkgCollection);
+// ...
+```
+
+- Supports `package:build_barback >=0.2.0 <0.4.0`.
+
+## 0.6.2
+
+- Internal version bump.
+
+## 0.6.1
+
+- Declare an output extension in `_ResolveSourceBuilder` so it is not skipped
+
+## 0.6.0
+
+- Support build 0.9.0
+  - Rename `hasInput` to `canRead` in `AssetReader` implementations
+  - Replace `declareOutputs` with `buildExtensions` in `Builder` implementations
+- **Breaking** `CopyBuilder` no longer has an `outputPackage` field since outputs
+  can only ever be in the same package as inputs.
+
+## 0.5.2
+
+- Add `MultiAssetReader` to the public API.
+
+## 0.5.1
+
+- Add `PackageAssetReader`, a standalone asset reader that uses a
+  `PackageResolver` to map an `AssetId` to a location on disk.
+- Add `resolveSource`, a top-level function that can resolve arbitrary Dart
+  source code. This can be useful in testing your own code that uses a
+  `Resolver` to do type checks.
+
+## 0.5.0
+
+- Add `findAssets` implementations to StubAssetReader an InMemoryAssetReader
+- **BREAKING**: InMemoryAssetReader constructor uses named optional parameters
+
+## 0.4.1
+
+- Make `scopeLog` visible so tests can be run with an available `log` without
+  going through runBuilder.
+
+## 0.4.0+1
+
+- Bug Fix: Correctly identify missing outputs in testBuilder
+
+## 0.4.0
+
+Updates to work with `build` version 0.7.0.
+
+### New Features
+- The `testBuilder` method now accepts `List<int>` values for both
+  `sourceAssets` and `outputs`.
+- The `checkOutputs` method is now public.
+
+### Breaking Changes
+- The `testBuilder` method now requires a `RecordingAssetWriter` instead of
+  just an `AssetWriter` for the `writer` parameter.
+- If a `Matcher` is provided as a value in `outputs`, then it will match against
+  the same value that was written. For example if your builder uses
+  `writeAsString` then it will match against that string. If you use
+  `writeAsBytes` then it will match against those bytes. It will not
+  automatically convert to/from bytes and strings.
+- Deleted the `makeAsset` and `makeAssets` methods. There is no more `Asset`
+  class so these don't really have any value any more.
+- The signature of `addAssets` has changed to
+  `void addAssets(Map<AssetId, dynamic> assets, InMemoryAssetWriter writer)`.
+  Values of the map may be either `String` or `List<int>`.
+- `InMemoryAssetReader#assets` and `InMemoryAssetWriter#assets` have changed to
+  a type of `Map<AssetId, DatedValue>` from a type of
+  `Map<AssetId, DatedString>`. `DatedValue` has both a `stringValue` and
+  `bytesValue` getter.
+- `InMemoryAssetReader` and `InMemoryAssetWriter` have been updated to implement
+  the new `AssetReader` and `AssetWriter` interfaces (see the `build` package
+  CHANGELOG for more details).
+- `InMemoryAssetReader#cacheAsset` has been changed to two separate methods,
+  `void cacheStringAsset(AssetId id, String contents)` and
+  `void cacheBytesAsset(AssetId id, List<int> bytes)`.
+- The `equalsAsset` matcher has been removed, since there is no more `Asset`
+  class.
+
+## 0.3.1
+
+- Additional capabilities in testBuilder:
+  - Filter sourceAssets to inputs with `isInput`
+  - Get log records
+  - Ignore output expectations when `outputs` is null
+  - Use a custom `writer`
+
+## 0.3.0
+
+- **BREAKING** removed testPhases utility. Tests should be using testBuilder
+- Drop dependency on build_runner package
+
+## 0.2.1
+
+- Support the package split into build/build_runner/build_barback
+- Expose additional test utilities that used to be internal to build
+
+## 0.2.0
+
+- Upgrade build package to 0.4.0
+- Delete now unnecessary `GenericBuilderTransformer` and use
+  `BuilderTransformer` in the tests.
+
+## 0.1.2
+
+- Add `logLevel` and `onLog` named args to `testPhases`. These can be used
+  to test your log messages, see `test/utils_test.dart` for an example.
+
+## 0.1.1
+
+- Allow String or Matcher for expected output values in `testPhases`.
+
+## 0.1.0
+
+- Initial version, exposes many basic utilities for testing `Builder`s using in
+  memory data structures. Most notably, the `testPhases` method.
diff --git a/build_test/LICENSE b/build_test/LICENSE
new file mode 100644
index 0000000..82e9b52
--- /dev/null
+++ b/build_test/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2016, the Dart project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of Google Inc. nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/build_test/README.md b/build_test/README.md
new file mode 100644
index 0000000..148cb82
--- /dev/null
+++ b/build_test/README.md
@@ -0,0 +1,140 @@
+<p align="center">
+  Testing utilities for users of <a href="https://pub.dev/packages/build"><code>package:build</code></a>.
+  <br>
+  <a href="https://travis-ci.org/dart-lang/build">
+    <img src="https://travis-ci.org/dart-lang/build.svg?branch=master" alt="Build Status" />
+  </a>
+  <a href="https://github.com/dart-lang/build/labels/package%3A%20build_test">
+    <img src="https://img.shields.io/github/issues-raw/dart-lang/build/package%3A%20build_test.svg" alt="Issues related to build_test" />
+  </a>
+  <a href="https://pub.dev/packages/build_test">
+    <img src="https://img.shields.io/pub/v/build_test.svg" alt="Pub Package Version" />
+  </a>
+  <a href="https://pub.dev/documentation/build_test/latest">
+    <img src="https://img.shields.io/badge/dartdocs-latest-blue.svg" alt="Latest Dartdocs" />
+  </a>
+  <a href="https://gitter.im/dart-lang/build">
+    <img src="https://badges.gitter.im/dart-lang/build.svg" alt="Join the chat on Gitter" />
+  </a>
+</p>
+
+## Installation
+
+This package is intended to only be as a [development dependency][] for users
+of [`package:build`][], and should not be used in any production code. Simply
+add to your `pubspec.yaml`:
+
+```yaml
+dev_dependencies:
+  build_test:
+```
+
+## Running tests
+
+To run tests, you should go through the `pub run build_runner test` command.
+This will compile all your tests to a temp directory and run them using
+`pub run test`. If you would like to see the output directory, you can use the
+`--output=<dir>` option to force the output to go to a specific place.
+
+### Forwarding additional args to `pub run test`
+
+It is very common to need to pass some arguments through to the eventual call
+to `pub run test`. To do this, add all those args after an empty `--` arg.
+
+For example, to run all chrome platform tests you would do
+`pub run build_runner test -- -p chrome`.
+
+## Debugging web tests
+
+This package will automatically create `*.debug.html` files next to all your
+`*_test.dart` files, which can be loaded in a browser from the normal
+development server (`pub run build_runner serve`).
+
+**Note:** In order to run the tests this way, you will need to configure them
+to be compiled (by default we only compile `*.browser_test.dart` files). You
+can do this in your build.yaml file, with something like the following:
+
+```yaml
+targets:
+  $default:
+    builders:
+      build_web_compilers|entrypoint:
+        generate_for:
+        - test/**_test.dart
+        - web/**.dart
+```
+
+You may also view an index of links to every `*.debug.html` file by navigating
+to `http://localhost:8081` (or wherever your `test` folder is being served).
+
+## Writing tests for your custom Builder
+
+In addition to assiting in running normal tests, this package provides some
+utilities for testing your custom `Builder` classes.
+
+_See the `test` folder in the `build` package for more examples_.
+
+### Run a `Builder` within a test environment
+
+Using [`testBuilder`][api:testBuilder], you can run a functional test of a
+`Builder`, including feeding specific assets, and more. It automatically
+creates an in-memory representation of various utility classes.
+
+### Exposing actual package sources to `testBuilder`
+
+You can expose real package sources to the builder in addition to your in
+memory sources, by passing a `PackageAssetReader` to the `reader` parameter:
+
+```dart
+testBuilder(yourBuilder, {}/* test assets here */,
+    reader: await PackageAssetReader.currentIsolate());
+```
+
+You can pass any custom AssetReader here, which will be used as a fallback
+for any source not defined in the source assets map.
+
+### Resolve source code for testing
+
+Using [`resolveAsset`][api:resolveAsset] and
+[`resolveSource`][api:resolveSource], you can resolve Dart source code into a
+static element model, suitable for probing and using within tests of code you
+might have written for a `Builder`:
+
+```dart
+test('should resolve a simple dart file', () async {
+  var resolver = await resolveSource(r'''
+    library example;
+
+    class Foo {}
+  ''');
+  var libExample = resolver.getLibraryByName('example');
+  expect(libExample.getType('Foo'), isNotNull);
+});
+```
+
+### Various test implementations of classes
+
+* [`FakeWatcher`][api:FakeWatcher]
+* [`InMemoryAssetReader`][api:InMemoryAssetReader]
+* [`InMemoryAssetWriter`][api:InMemoryAssetWriter]
+* [`MultiAssetReader`][api:MultiAssetReader]
+* [`PackageAssetReader`][api:PackageAssetReader]
+* [`RecordingAssetWriter`][api:RecordingAssetWriter]
+* [`StubAssetReader`][api:StubAssetReader]
+* [`StubAssetWriter`][api:StubAssetWriter]
+
+[development dependency]: https://dart.dev/tools/pub/dependencies#dev-dependencies
+[`package:build`]: https://pub.dev/packages/build
+
+[api:FakeWatcher]: https://pub.dev/documentation/build_test/latest/build_test/FakeWatcher-class.html
+[api:InMemoryAssetReader]: https://pub.dev/documentation/build_test/latest/build_test/InMemoryAssetReader-class.html
+[api:InMemoryAssetWriter]: https://pub.dev/documentation/build_test/latest/build_test/InMemoryAssetWriter-class.html
+[api:MultiAssetReader]: https://pub.dev/documentation/build_test/latest/build_test/MultiAssetReader-class.html
+[api:PackageAssetReader]: https://pub.dev/documentation/build_test/latest/build_test/PackageAssetReader-class.html
+[api:RecordingAssetWriter]: https://pub.dev/documentation/build_test/latest/build_test/RecordingAssetWriter-class.html
+[api:StubAssetReader]: https://pub.dev/documentation/build_test/latest/build_test/StubAssetReader-class.html
+[api:StubAssetWriter]: https://pub.dev/documentation/build_test/latest/build_test/StubAssetWriter-class.html
+
+[api:resolveAsset]: https://pub.dev/documentation/build_test/latest/build_test/resolveAsset.html
+[api:resolveSource]: https://pub.dev/documentation/build_test/latest/build_test/resolveSource.html
+[api:testBuilder]: https://pub.dev/documentation/build_test/latest/build_test/testBuilder.html
diff --git a/build_test/build.yaml b/build_test/build.yaml
new file mode 100644
index 0000000..2283e15
--- /dev/null
+++ b/build_test/build.yaml
@@ -0,0 +1,22 @@
+builders:
+  test_bootstrap:
+    target: "build_test"
+    import: "package:build_test/builder.dart"
+    builder_factories:
+        - "debugIndexBuilder"
+        - "debugTestBuilder"
+        - "testBootstrapBuilder"
+    build_extensions:
+      $test$:
+        - index.html
+      _test.dart:
+        - _test.dart.vm_test.dart
+        - _test.dart.browser_test.dart
+        - _test.dart.node_test.dart
+        - _test.html
+        - _test.debug.html
+    is_optional: False
+    build_to: cache
+    auto_apply: root_package
+    defaults:
+      generate_for: ["test/**"]
diff --git a/build_test/lib/build_test.dart b/build_test/lib/build_test.dart
new file mode 100644
index 0000000..e215514
--- /dev/null
+++ b/build_test/lib/build_test.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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.
+
+export 'package:build/src/builder/logging.dart' show scopeLogAsync;
+
+export 'src/assets.dart';
+export 'src/builder.dart';
+export 'src/fake_watcher.dart';
+export 'src/globbing_builder.dart';
+export 'src/in_memory_reader.dart';
+export 'src/in_memory_writer.dart';
+export 'src/matchers.dart';
+export 'src/multi_asset_reader.dart' show MultiAssetReader;
+export 'src/package_reader.dart' show PackageAssetReader;
+export 'src/record_logs.dart';
+export 'src/resolve_source.dart'
+    show useAssetReader, resolveSource, resolveSources, resolveAsset;
+export 'src/stub_reader.dart';
+export 'src/stub_writer.dart';
+export 'src/test_builder.dart';
+export 'src/written_asset_reader.dart';
diff --git a/build_test/lib/builder.dart b/build_test/lib/builder.dart
new file mode 100644
index 0000000..44d7697
--- /dev/null
+++ b/build_test/lib/builder.dart
@@ -0,0 +1,13 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'src/debug_test_builder.dart';
+import 'src/test_bootstrap_builder.dart';
+
+export 'src/debug_test_builder.dart' show DebugTestBuilder;
+export 'src/test_bootstrap_builder.dart' show TestBootstrapBuilder;
+
+DebugTestBuilder debugTestBuilder(_) => const DebugTestBuilder();
+DebugIndexBuilder debugIndexBuilder(_) => const DebugIndexBuilder();
+TestBootstrapBuilder testBootstrapBuilder(_) => TestBootstrapBuilder();
diff --git a/build_test/lib/src/assets.dart b/build_test/lib/src/assets.dart
new file mode 100644
index 0000000..53597a8
--- /dev/null
+++ b/build_test/lib/src/assets.dart
@@ -0,0 +1,32 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 'dart:convert';
+
+import 'package:build/build.dart';
+
+import 'in_memory_writer.dart';
+
+int _nextId = 0;
+AssetId makeAssetId([String assetIdString]) {
+  if (assetIdString == null) {
+    assetIdString = 'a|web/asset_$_nextId.txt';
+    _nextId++;
+  }
+  return AssetId.parse(assetIdString);
+}
+
+void addAssets(Map<AssetId, dynamic> assets, InMemoryAssetWriter writer) {
+  assets.forEach((id, value) {
+    if (value is String) {
+      writer.assets[id] = utf8.encode(value);
+    } else if (value is List<int>) {
+      writer.assets[id] = value;
+    } else {
+      throw ArgumentError(
+          '`assets` values must be of type `String` or `List<int>`, got '
+          '${value.runtimeType}.');
+    }
+  });
+}
diff --git a/build_test/lib/src/builder.dart b/build_test/lib/src/builder.dart
new file mode 100644
index 0000000..f36f6e3
--- /dev/null
+++ b/build_test/lib/src/builder.dart
@@ -0,0 +1,112 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'package:build/build.dart';
+
+/// Overridable behavior for a [Builder.build] method.
+typedef BuildBehavior = FutureOr Function(
+    BuildStep buildStep, Map<String, List<String>> buildExtensions);
+
+/// Copy the input asset to all possible output assets.
+void _defaultBehavior(
+        BuildStep buildStep, Map<String, List<String>> buildExtensions) =>
+    _copyToAll(buildStep, buildExtensions);
+
+T _identity<T>(T value) => value;
+Future<String> _readAsset(BuildStep buildStep, AssetId assetId) =>
+    buildStep.readAsString(assetId);
+
+/// Pass the input assetId through [readFrom] and duplicate the results of
+/// [read] on that asset into every matching output based on [buildExtensions].
+void _copyToAll(BuildStep buildStep, Map<String, List<String>> buildExtensions,
+    {AssetId Function(AssetId assetId) readFrom = _identity,
+    Future<String> Function(BuildStep buildStep, AssetId assetId) read =
+        _readAsset}) {
+  if (!buildExtensions.keys.any((e) => buildStep.inputId.path.endsWith(e))) {
+    throw ArgumentError('Only expected inputs with extension in '
+        '${buildExtensions.keys.toList()} but got ${buildStep.inputId}');
+  }
+  for (final inputExtension in buildExtensions.keys) {
+    if (!buildStep.inputId.path.endsWith(inputExtension)) continue;
+    for (final outputExtension in buildExtensions[inputExtension]) {
+      final newPath = _replaceSuffix(
+          buildStep.inputId.path, inputExtension, outputExtension);
+      final id = AssetId(buildStep.inputId.package, newPath);
+      buildStep.writeAsString(id, read(buildStep, readFrom(buildStep.inputId)));
+    }
+  }
+}
+
+/// A build behavior which reads [assetId] and copies it's content into every
+/// output.
+BuildBehavior copyFrom(AssetId assetId) => (buildStep, buildExtensions) =>
+    _copyToAll(buildStep, buildExtensions, readFrom: (_) => assetId);
+
+/// A build behavior which writes either 'true' or 'false' depending on whether
+/// [assetId] can be read.
+BuildBehavior writeCanRead(AssetId assetId) =>
+    (BuildStep buildStep, Map<String, List<String>> buildExtensions) =>
+        _copyToAll(buildStep, buildExtensions,
+            readFrom: (_) => assetId,
+            read: (buildStep, assetId) async =>
+                '${await buildStep.canRead(assetId)}');
+
+/// A [Builder.buildExtensions] which operats on assets ending in [from] and
+/// creates outputs with [postFix] appended as the extension.
+///
+/// If [numCopies] is greater than 1 the postFix will also get a `.0`, `.1`...
+Map<String, List<String>> appendExtension(String postFix,
+        {String from = '', int numCopies = 1}) =>
+    {
+      from: numCopies == 1
+          ? ['$from$postFix']
+          : List.generate(numCopies, (i) => '$from$postFix.$i')
+    };
+
+Map<String, List<String>> replaceExtension(String from, String to) => {
+      from: [to]
+    };
+
+/// A [Builder] whose [build] method can be replaced with a closure.
+class TestBuilder implements Builder {
+  @override
+  final Map<String, List<String>> buildExtensions;
+
+  final BuildBehavior _build;
+  final BuildBehavior _extraWork;
+
+  /// A stream of all the [BuildStep.inputId]s that are seen.
+  ///
+  /// Events are added at the start of the [build] method.
+  final _buildInputsController = StreamController<AssetId>.broadcast();
+  Stream<AssetId> get buildInputs => _buildInputsController.stream;
+
+  /// A stream of all the [BuildStep.inputId]s that are completed.
+  ///
+  /// Events are added at the end of the [build] method.
+  final _buildsCompletedController = StreamController<AssetId>.broadcast();
+  Stream<AssetId> get buildsCompleted => _buildsCompletedController.stream;
+
+  TestBuilder({
+    Map<String, List<String>> buildExtensions,
+    BuildBehavior build,
+    BuildBehavior extraWork,
+  })  : buildExtensions = buildExtensions ?? appendExtension('.copy'),
+        _build = build ?? _defaultBehavior,
+        _extraWork = extraWork;
+
+  @override
+  Future build(BuildStep buildStep) async {
+    if (!await buildStep.canRead(buildStep.inputId)) return;
+    _buildInputsController.add(buildStep.inputId);
+    await _build(buildStep, buildExtensions);
+    if (_extraWork != null) await _extraWork(buildStep, buildExtensions);
+    _buildsCompletedController.add(buildStep.inputId);
+  }
+}
+
+String _replaceSuffix(String path, String old, String replacement) =>
+    path.substring(0, path.length - old.length) + replacement;
diff --git a/build_test/lib/src/debug_test_builder.dart b/build_test/lib/src/debug_test_builder.dart
new file mode 100644
index 0000000..1d97bd9
--- /dev/null
+++ b/build_test/lib/src/debug_test_builder.dart
@@ -0,0 +1,128 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'package:build/build.dart';
+import 'package:glob/glob.dart';
+import 'package:html/dom.dart';
+import 'package:html/parser.dart';
+import 'package:path/path.dart' as p;
+
+const _inputExtension = '_test.dart';
+const _outputExtension = '_test.debug.html';
+
+/// Returns the (optional) user-provided HTML file to use as an input.
+///
+/// For example, for `test/foo_test.dart`, we look for `test/foo_test.html`.
+AssetId _customHtmlId(AssetId test) => test.changeExtension('.html');
+
+/// Returns the builder-generated HTML file for browsers to navigate to.
+AssetId _debugHtmlId(AssetId test) => test.changeExtension('.debug.html');
+
+/// Returns the JS script path for the browser for [dartTest].
+String _jsScriptPath(AssetId dartTest) => '${p.url.basename(dartTest.path)}.js';
+
+/// Generates a `*.debug.html` for every file in `test/**/*_test.dart`.
+///
+/// This is normally used in order to use Chrome (or another browser's)
+/// debugging tools (such as setting breakpoints) while running a test suite.
+class DebugTestBuilder implements Builder {
+  /// Generates a `.debug.html` for the provided `._test.dart` file.
+  static Future<void> _generateDebugHtml(
+    BuildStep buildStep,
+    AssetId dartTest,
+  ) async {
+    final customHtmlId = _customHtmlId(dartTest);
+    final jsScriptPath = _jsScriptPath(buildStep.inputId);
+    String debugHtml;
+    if (await buildStep.canRead(customHtmlId)) {
+      debugHtml = _replaceCustomHtml(
+        await buildStep.readAsString(customHtmlId),
+        jsScriptPath,
+      );
+    } else {
+      debugHtml = _createDebugHtml(jsScriptPath);
+    }
+    return buildStep.writeAsString(_debugHtmlId(dartTest), debugHtml);
+  }
+
+  /// Returns the content of [customHtml] modified to work with this package.
+  static String _replaceCustomHtml(String customHtml, String jsScriptPath) {
+    final document = parse(customHtml);
+
+    // Replace <link rel="x-dart-test"> with <script src="{jsScriptPath}">.
+    final linkTag = document.querySelector('link[rel="x-dart-test"]');
+    final scriptTag = Element.tag('script');
+    scriptTag.attributes['src'] = jsScriptPath;
+    linkTag.replaceWith(scriptTag);
+
+    // Remove the <script src="packages/test/dart.js"></script> if present.
+    document.querySelector('script[src="packages/test/dart.js"]')?.remove();
+
+    return document.outerHtml;
+  }
+
+  static String _createDebugHtml(String jsScriptPath) => ''
+      '<html>\n'
+      '  <head>\n'
+      '    <script src="$jsScriptPath"></script>\n'
+      '  </head>\n'
+      '</html>\n';
+
+  const DebugTestBuilder();
+
+  @override
+  final buildExtensions = const {
+    _inputExtension: [_outputExtension],
+  };
+
+  @override
+  Future<void> build(BuildStep buildStep) {
+    return _generateDebugHtml(buildStep, buildStep.inputId);
+  }
+}
+
+/// Generates `text/index.html`, useful for navigating and running tests.
+class DebugIndexBuilder implements Builder {
+  static final _allTests = Glob(p.url.join('test', '**$_inputExtension'));
+
+  static AssetId _outputFor(BuildStep buildStep) {
+    return AssetId(buildStep.inputId.package, p.url.join('test', 'index.html'));
+  }
+
+  static String _generateHtml(Iterable<AssetId> tests) {
+    if (tests.isEmpty) {
+      return '<strong>No tests found!</strong>';
+    }
+    final buffer = StringBuffer('    <ul>');
+    for (final test in tests) {
+      final path =
+          p.url.joinAll(p.url.split(_debugHtmlId(test).path)..removeAt(0));
+      buffer.writeln('      <li><a href="/$path">${test.path}</a></li>');
+    }
+    buffer.writeln('    </ul>');
+    return buffer.toString();
+  }
+
+  const DebugIndexBuilder();
+
+  @override
+  final buildExtensions = const {
+    r'$test$': ['index.html'],
+  };
+
+  @override
+  Future<void> build(BuildStep buildStep) async {
+    final files = await buildStep.findAssets(_allTests).toList();
+    final output = _outputFor(buildStep);
+    return buildStep.writeAsString(
+        output,
+        '<html>\n'
+        '  <body>\n'
+        '    ${_generateHtml(files)}\n'
+        '  </body>\n'
+        '</html>');
+  }
+}
diff --git a/build_test/lib/src/fake_watcher.dart b/build_test/lib/src/fake_watcher.dart
new file mode 100644
index 0000000..b41ebaa
--- /dev/null
+++ b/build_test/lib/src/fake_watcher.dart
@@ -0,0 +1,45 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'package:watcher/watcher.dart';
+
+/// A fake [DirectoryWatcher].
+///
+/// Use the static [notifyWatchers] method to add simulated events.
+class FakeWatcher implements DirectoryWatcher {
+  @override
+  String get directory => path;
+
+  @override
+  final String path;
+
+  FakeWatcher(this.path) {
+    watchers.add(this);
+  }
+
+  final _eventsController = StreamController<WatchEvent>();
+
+  @override
+  Stream<WatchEvent> get events => _eventsController.stream;
+
+  @override
+  Future get ready => Future(() {});
+
+  @override
+  bool get isReady => true;
+
+  /// All watchers.
+  static final List<FakeWatcher> watchers = <FakeWatcher>[];
+
+  /// Notify all active watchers of [event] if their [FakeWatcher#path] matches.
+  /// The path will also be adjusted to remove the path.
+  static void notifyWatchers(WatchEvent event) {
+    for (var watcher in watchers) {
+      if (event.path.startsWith(watcher.path)) {
+        watcher._eventsController.add(WatchEvent(event.type, event.path));
+      }
+    }
+  }
+}
diff --git a/build_test/lib/src/globbing_builder.dart b/build_test/lib/src/globbing_builder.dart
new file mode 100644
index 0000000..918ec1f
--- /dev/null
+++ b/build_test/lib/src/globbing_builder.dart
@@ -0,0 +1,30 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'package:build/build.dart';
+import 'package:glob/glob.dart';
+
+/// A simple builder which globs files in a package and outputs a file that
+/// lists each matching file in alphabetical order into another file, one
+/// per line.
+class GlobbingBuilder extends Builder {
+  @override
+  final buildExtensions = {
+    '.globPlaceholder': ['.matchingFiles'],
+  };
+
+  final Glob glob;
+
+  GlobbingBuilder(this.glob);
+
+  @override
+  Future build(BuildStep buildStep) async {
+    var allAssets = await buildStep.findAssets(glob).toList();
+    allAssets.sort((a, b) => a.path.compareTo(b.path));
+    await buildStep.writeAsString(
+        buildStep.inputId.changeExtension('.matchingFiles'),
+        allAssets.map((id) => id.toString()).join('\n'));
+  }
+}
diff --git a/build_test/lib/src/in_memory_reader.dart b/build_test/lib/src/in_memory_reader.dart
new file mode 100644
index 0000000..9444da1
--- /dev/null
+++ b/build_test/lib/src/in_memory_reader.dart
@@ -0,0 +1,93 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:convert';
+
+import 'package:build/build.dart';
+import 'package:glob/glob.dart';
+
+/// An [AssetReader] that records which assets have been read to [assetsRead].
+abstract class RecordingAssetReader implements AssetReader {
+  Iterable<AssetId> get assetsRead;
+}
+
+/// An implementation of [AssetReader] with primed in-memory assets.
+class InMemoryAssetReader extends AssetReader
+    implements MultiPackageAssetReader, RecordingAssetReader {
+  final Map<AssetId, List<int>> assets;
+  final String rootPackage;
+
+  @override
+  final Set<AssetId> assetsRead = <AssetId>{};
+
+  /// Create a new asset reader that contains [sourceAssets].
+  ///
+  /// Any items in [sourceAssets] which are [String]s will be converted into
+  /// a [List<int>] of bytes.
+  ///
+  /// May optionally define a [rootPackage], which is required for some APIs.
+  InMemoryAssetReader({Map<AssetId, dynamic> sourceAssets, this.rootPackage})
+      : assets = _assetsAsBytes(sourceAssets) ?? <AssetId, List<int>>{};
+
+  /// Create a new asset reader backed by [assets].
+  InMemoryAssetReader.shareAssetCache(this.assets, {this.rootPackage});
+
+  static Map<AssetId, List<int>> _assetsAsBytes(Map<AssetId, dynamic> assets) {
+    if (assets == null || assets.isEmpty) {
+      return {};
+    }
+    final output = <AssetId, List<int>>{};
+    assets.forEach((id, stringOrBytes) {
+      if (stringOrBytes is List<int>) {
+        output[id] = stringOrBytes;
+      } else if (stringOrBytes is String) {
+        output[id] = utf8.encode(stringOrBytes);
+      } else {
+        throw UnsupportedError('Invalid asset contents: $stringOrBytes.');
+      }
+    });
+    return output;
+  }
+
+  @override
+  Future<bool> canRead(AssetId id) async {
+    assetsRead.add(id);
+    return assets.containsKey(id);
+  }
+
+  @override
+  Future<List<int>> readAsBytes(AssetId id) async {
+    if (!await canRead(id)) throw AssetNotFoundException(id);
+    assetsRead.add(id);
+    return assets[id];
+  }
+
+  @override
+  Future<String> readAsString(AssetId id, {Encoding encoding = utf8}) async {
+    if (!await canRead(id)) throw AssetNotFoundException(id);
+    assetsRead.add(id);
+    return utf8.decode(assets[id]);
+  }
+
+  @override
+  Stream<AssetId> findAssets(Glob glob, {String package}) {
+    package ??= rootPackage;
+    if (package == null) {
+      throw UnsupportedError(
+          'Root package is required to use findAssets without providing an '
+          'explicit package.');
+    }
+    return Stream.fromIterable(assets.keys
+        .where((id) => id.package == package && glob.matches(id.path)));
+  }
+
+  void cacheBytesAsset(AssetId id, List<int> bytes) {
+    assets[id] = bytes;
+  }
+
+  void cacheStringAsset(AssetId id, String contents, {Encoding encoding}) {
+    encoding ??= utf8;
+    assets[id] = encoding.encode(contents);
+  }
+}
diff --git a/build_test/lib/src/in_memory_writer.dart b/build_test/lib/src/in_memory_writer.dart
new file mode 100644
index 0000000..8bb35cc
--- /dev/null
+++ b/build_test/lib/src/in_memory_writer.dart
@@ -0,0 +1,31 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:convert';
+
+import 'package:build/build.dart';
+
+/// An implementation of [AssetWriter] that records outputs to [assets].
+abstract class RecordingAssetWriter implements AssetWriter {
+  Map<AssetId, List<int>> get assets;
+}
+
+/// An implementation of [AssetWriter] that writes outputs to memory.
+class InMemoryAssetWriter implements RecordingAssetWriter {
+  @override
+  final Map<AssetId, List<int>> assets = {};
+
+  InMemoryAssetWriter();
+
+  @override
+  Future writeAsBytes(AssetId id, List<int> bytes) async {
+    assets[id] = bytes;
+  }
+
+  @override
+  Future writeAsString(AssetId id, String contents,
+      {Encoding encoding = utf8}) async {
+    assets[id] = encoding.encode(contents);
+  }
+}
diff --git a/build_test/lib/src/matchers.dart b/build_test/lib/src/matchers.dart
new file mode 100644
index 0000000..92f3c16
--- /dev/null
+++ b/build_test/lib/src/matchers.dart
@@ -0,0 +1,28 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 'dart:convert';
+
+import 'package:test/test.dart';
+
+import 'package:build/build.dart';
+
+/// Matches instance of [AssetNotFoundException].
+final assetNotFoundException = const TypeMatcher<AssetNotFoundException>();
+
+/// Matches instance of [InvalidInputException].
+final invalidInputException = const TypeMatcher<InvalidInputException>();
+
+/// Matches instance of [InvalidOutputException].
+final invalidOutputException = const TypeMatcher<InvalidOutputException>();
+
+/// Matches instance of [PackageNotFoundException].
+final packageNotFoundException = const TypeMatcher<PackageNotFoundException>();
+
+/// Decodes the value using [encoding] and matches it against [expected].
+TypeMatcher<List<int>> decodedMatches(dynamic expected, {Encoding encoding}) {
+  encoding ??= utf8;
+  return TypeMatcher<List<int>>().having(
+      (e) => encoding.decode(e), '${encoding.name} decoded bytes', expected);
+}
diff --git a/build_test/lib/src/multi_asset_reader.dart b/build_test/lib/src/multi_asset_reader.dart
new file mode 100644
index 0000000..a487263
--- /dev/null
+++ b/build_test/lib/src/multi_asset_reader.dart
@@ -0,0 +1,61 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:convert';
+
+import 'package:async/async.dart';
+import 'package:build/build.dart';
+import 'package:glob/glob.dart';
+
+/// A [MultiPackageAssetReader] that delegates to multiple other asset
+/// readers.
+///
+/// [MultiAssetReader] attempts to check every provided
+/// [MultiPackageAssetReader] to see if they are capable of reading an
+/// [AssetId], otherwise checks the next reader.
+class MultiAssetReader extends AssetReader implements MultiPackageAssetReader {
+  final List<MultiPackageAssetReader> _readers;
+
+  MultiAssetReader(this._readers);
+
+  @override
+  Future<bool> canRead(AssetId id) async {
+    for (var reader in _readers) {
+      if (await reader.canRead(id)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @override
+  Future<List<int>> readAsBytes(AssetId id) async =>
+      (await _readerWith(id)).readAsBytes(id);
+
+  @override
+  Future<String> readAsString(AssetId id, {Encoding encoding = utf8}) async =>
+      (await _readerWith(id)).readAsString(id, encoding: encoding);
+
+  /// Returns all readable assets matching [glob] under [package].
+  ///
+  /// **NOTE**: This is a combined view of all provided readers. As such it is
+  /// possible that an [AssetId] will be iterated over more than once, unlike
+  /// other implementations of [AssetReader].
+  @override
+  Stream<AssetId> findAssets(Glob glob, {String package}) => StreamGroup.merge(
+      _readers.map((reader) => reader.findAssets(glob, package: package)));
+
+  /// Returns the first [AssetReader] that contains [id].
+  ///
+  /// Otherwise throws [AssetNotFoundException].
+  Future<AssetReader> _readerWith(AssetId id) async {
+    for (var reader in _readers) {
+      if (await reader.canRead(id)) {
+        return reader;
+      }
+    }
+    throw AssetNotFoundException(id);
+  }
+}
diff --git a/build_test/lib/src/package_reader.dart b/build_test/lib/src/package_reader.dart
new file mode 100644
index 0000000..b8a7809
--- /dev/null
+++ b/build_test/lib/src/package_reader.dart
@@ -0,0 +1,139 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:build/build.dart';
+import 'package:glob/glob.dart';
+import 'package:package_resolver/package_resolver.dart';
+import 'package:path/path.dart' as p;
+import 'package:stream_transform/stream_transform.dart';
+
+/// Resolves using a [SyncPackageResolver] before reading from the file system.
+///
+/// For a simple implementation that uses the current isolate's package
+/// resolution logic (i.e. whatever you have generated in `.packages` in most
+/// cases), use [currentIsolate]:
+/// ```dart
+/// var assetReader = await PackageAssetReader.currentIsolate();
+/// ```
+class PackageAssetReader extends AssetReader
+    implements MultiPackageAssetReader {
+  final SyncPackageResolver _packageResolver;
+
+  /// What package is the originating build occurring in.
+  final String _rootPackage;
+
+  /// Wrap a [SyncPackageResolver] to identify where files are located.
+  ///
+  /// To use a normal [PackageResolver] use `asSync`:
+  /// ```
+  /// new PackageAssetReader(await packageResolver.asSync);
+  /// ```
+  PackageAssetReader(this._packageResolver, [this._rootPackage]);
+
+  /// A [PackageAssetReader] with a single [packageRoot] configured.
+  ///
+  /// It is assumed that every _directory_ in [packageRoot] is a package where
+  /// the name of the package is the name of the directory. This is similar to
+  /// the older "packages" folder paradigm for resolution.
+  factory PackageAssetReader.forPackageRoot(String packageRoot,
+      [String rootPackage]) {
+    // This purposefully doesn't use SyncPackageResolver.root, because that is
+    // assuming a symlink collection and not directories, and this factory is
+    // more useful for a user-created collection of folders for testing.
+    final directory = Directory(packageRoot);
+    final packages = <String, String>{};
+    for (final entity in directory.listSync()) {
+      if (entity is Directory) {
+        final name = p.basename(entity.path);
+        packages[name] = entity.uri.toFilePath(windows: false);
+      }
+    }
+    return PackageAssetReader.forPackages(packages, rootPackage);
+  }
+
+  /// Returns a [PackageAssetReader] with a simple [packageToPath] mapping.
+  factory PackageAssetReader.forPackages(Map<String, String> packageToPath,
+          [String rootPackage]) =>
+      PackageAssetReader(
+          SyncPackageResolver.config(packageToPath
+              .map((k, v) => MapEntry(k, Uri.parse(p.url.absolute(v, 'lib'))))),
+          rootPackage);
+
+  /// A reader that can resolve files known to the current isolate.
+  ///
+  /// A [rootPackage] should be provided for full API compatibility.
+  static Future<PackageAssetReader> currentIsolate({String rootPackage}) async {
+    var resolver = PackageResolver.current;
+    return PackageAssetReader(await resolver.asSync, rootPackage);
+  }
+
+  File _resolve(AssetId id) {
+    final uri = id.uri;
+    if (uri.isScheme('package')) {
+      final uri = _packageResolver.resolveUri(id.uri);
+      if (uri != null) {
+        return File.fromUri(uri);
+      }
+    }
+    if (id.package == _rootPackage) {
+      return File(p.canonicalize(p.join(_rootPackagePath, id.path)));
+    }
+    return null;
+  }
+
+  String get _rootPackagePath {
+    // If the root package has a pub layout, use `packagePath`.
+    final root = _packageResolver.packagePath(_rootPackage);
+    if (root != null && Directory(p.join(root, 'lib')).existsSync()) {
+      return root;
+    }
+    // Assume the cwd is the package root.
+    return p.current;
+  }
+
+  @override
+  Stream<AssetId> findAssets(Glob glob, {String package}) {
+    package ??= _rootPackage;
+    if (package == null) {
+      throw UnsupportedError(
+          'Root package must be provided to use `findAssets` without an '
+          'explicit `package`.');
+    }
+    var packageLibDir = _packageResolver.packageConfigMap[package];
+    if (packageLibDir == null) {
+      throw UnsupportedError('Unable to find package $package');
+    }
+
+    var packageFiles = Directory.fromUri(packageLibDir)
+        .list(recursive: true)
+        .whereType<File>()
+        .map((f) =>
+            p.join('lib', p.relative(f.path, from: p.fromUri(packageLibDir))));
+    if (package == _rootPackage) {
+      packageFiles = packageFiles.merge(Directory(_rootPackagePath)
+          .list(recursive: true)
+          .whereType<File>()
+          .map((f) => p.relative(f.path, from: _rootPackagePath))
+          .where((p) => !(p.startsWith('packages/') || p.startsWith('lib/'))));
+    }
+    return packageFiles.where(glob.matches).map((p) => AssetId(package, p));
+  }
+
+  @override
+  Future<bool> canRead(AssetId id) =>
+      _resolve(id)?.exists() ?? Future.value(false);
+
+  @override
+  Future<List<int>> readAsBytes(AssetId id) =>
+      _resolve(id)?.readAsBytes() ?? (throw AssetNotFoundException(id));
+
+  @override
+  Future<String> readAsString(AssetId id, {Encoding encoding = utf8}) =>
+      _resolve(id)?.readAsString(encoding: encoding) ??
+      (throw AssetNotFoundException(id));
+}
diff --git a/build_test/lib/src/record_logs.dart b/build_test/lib/src/record_logs.dart
new file mode 100644
index 0000000..b45de5b
--- /dev/null
+++ b/build_test/lib/src/record_logs.dart
@@ -0,0 +1,91 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'package:build/src/builder/logging.dart';
+import 'package:matcher/matcher.dart';
+import 'package:logging/logging.dart';
+
+/// Executes [run] with a new [Logger], returning the resulting log records.
+///
+/// The returned [Stream] is _closed_ after the [run] function is executed. If
+/// [run] returns a [Future], that future is awaited _before_ the stream is
+/// closed.
+///
+/// ```dart
+/// test('should log "uh oh!"', () async {
+///   final logs = recordLogs(() => runBuilder());
+///   expect(logs, emitsInOrder([
+///     anyLogOf('uh oh!'),
+///   ]);
+/// });
+/// ```
+Stream<LogRecord> recordLogs(dynamic Function() run, {String name = ''}) {
+  final logger = Logger(name);
+  Timer.run(() async {
+    await scopeLogAsync(() => Future.value(run()), logger);
+    logger.clearListeners();
+  });
+  return logger.onRecord;
+}
+
+/// Matches [LogRecord] of any level whose message is [messageOrMatcher].
+///
+/// ```dart
+/// anyLogOf('Hello World)';     // Exactly match 'Hello World'.
+/// anyLogOf(contains('ERROR')); // Contains the sub-string 'ERROR'.
+/// ```
+Matcher anyLogOf(dynamic messageOrMatcher) =>
+    _LogRecordMatcher(anything, messageOrMatcher);
+
+/// Matches [LogRecord] of [Level.INFO] where message is [messageOrMatcher].
+Matcher infoLogOf(dynamic messageOrMatcher) =>
+    _LogRecordMatcher(Level.INFO, messageOrMatcher);
+
+/// Matches [LogRecord] of [Level.WARNING] where message is [messageOrMatcher].
+Matcher warningLogOf(dynamic messageOrMatcher) =>
+    _LogRecordMatcher(Level.WARNING, messageOrMatcher);
+
+/// Matches [LogRecord] of [Level.SEVERE] where message is [messageOrMatcher].
+Matcher severeLogOf(dynamic messageOrMatcher) =>
+    _LogRecordMatcher(Level.SEVERE, messageOrMatcher);
+
+class _LogRecordMatcher extends Matcher {
+  final Matcher _level;
+  final Matcher _message;
+
+  factory _LogRecordMatcher(dynamic levelOr, dynamic messageOr) =>
+      _LogRecordMatcher._(levelOr is Matcher ? levelOr : equals(levelOr),
+          messageOr is Matcher ? messageOr : equals(messageOr));
+
+  _LogRecordMatcher._(this._level, this._message);
+
+  @override
+  Description describe(Description description) {
+    description.add('level: ');
+    _level.describe(description);
+    description.add(', message: ');
+    _message.describe(description);
+    return description;
+  }
+
+  @override
+  Description describeMismatch(
+      covariant LogRecord item, Description description, _, __) {
+    if (!_level.matches(item.level, {})) {
+      _level.describeMismatch(item.level, description, {}, false);
+    }
+    if (!_message.matches(item.message, {})) {
+      _message.describeMismatch(item.message, description, {}, false);
+    }
+    return description;
+  }
+
+  @override
+  bool matches(item, _) =>
+      item is LogRecord &&
+      _level.matches(item.level, {}) &&
+      _message.matches(item.message, {});
+}
diff --git a/build_test/lib/src/resolve_source.dart b/build_test/lib/src/resolve_source.dart
new file mode 100644
index 0000000..6782805
--- /dev/null
+++ b/build_test/lib/src/resolve_source.dart
@@ -0,0 +1,242 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'package:build/build.dart';
+import 'package:build_resolvers/build_resolvers.dart';
+import 'package:package_resolver/package_resolver.dart';
+import 'package:pedantic/pedantic.dart';
+
+import 'in_memory_reader.dart';
+import 'in_memory_writer.dart';
+import 'multi_asset_reader.dart';
+import 'package_reader.dart';
+
+final defaultResolvers = AnalyzerResolvers();
+
+/// Marker constant that may be used in combination with [resolveSources].
+///
+/// Use of this string means instead of using the contents of the string as the
+/// source of a given asset, instead read the file from the default or provided
+/// [AssetReader].
+const useAssetReader = '__useAssetReader__';
+
+/// A convenience method for using [resolveSources] with a single source file.
+Future<T> resolveSource<T>(
+  String inputSource,
+  FutureOr<T> Function(Resolver resolver) action, {
+  AssetId inputId,
+  PackageResolver resolver,
+  Future<Null> tearDown,
+  Resolvers resolvers,
+}) {
+  inputId ??= AssetId('_resolve_source', 'lib/_resolve_source.dart');
+  return _resolveAssets(
+    {
+      '${inputId.package}|${inputId.path}': inputSource,
+    },
+    inputId.package,
+    action,
+    resolver: resolver,
+    resolverFor: inputId,
+    tearDown: tearDown,
+    resolvers: resolvers,
+  );
+}
+
+/// Resolves and runs [action] using a created resolver for [inputs].
+///
+/// Inputs accepts the pattern of `<package>|<path>.dart`, for example:
+/// ```
+/// {
+///   'test_lib|lib/test_lib.dart': r'''
+///     // Contents of test_lib.dart go here.
+///   ''',
+/// }
+/// ```
+///
+/// You may provide [useAssetReader] as the value of any input in order to read
+/// it from the file system instead of being forced to provide it inline as a
+/// string. This is useful for mixing real and mocked assets.
+///
+/// Example use:
+/// ```
+/// import 'package:build_test/build_test.dart';
+/// import 'package:test/test.dart';
+///
+/// void main() {
+///   test('should find a Foo type', () async {
+///     var library = await resolveSources({
+///       'test_lib|lib/test_lib.dart': r'''
+///         library example;
+///
+///         class Foo {}
+///       ''',
+///     }, (resolver) => resolver.findLibraryByName('example'));
+///     expect(library.getType('Foo'), isNotNull);
+///   });
+/// }
+/// ```
+///
+/// By default the [Resolver] is unusable after [action] completes. To keep the
+/// resolver active across multiple tests (for example, use `setUpAll` and
+/// `tearDownAll`, provide a `tearDown` [Future]:
+/// ```
+/// import 'dart:async';
+/// import 'package:build/build.dart';
+/// import 'package:build_test/build_test.dart';
+/// import 'package:test/test.dart';
+///
+/// void main() {
+///   Resolver resolver;
+///   var resolverDone = new Completer<Null>();
+///
+///   setUpAll(() async {
+///     resolver = await resolveSources(
+///       {...},
+///       (resolver) => resolver,
+///       tearDown: resolverDone.future,
+///     );
+///   });
+///
+///   tearDownAll(() => resolverDone.complete());
+///
+///   test('...', () async {
+///     // Use the resolver here, and in other tests.
+///   });
+/// }
+/// ```
+///
+/// May provide [resolverFor] to return the [Resolver] for the asset provided,
+/// otherwise defaults to the first one in [inputs].
+///
+/// **NOTE**: All `package` dependencies are resolved using [PackageAssetReader]
+/// - by default, [PackageAssetReader.currentIsolate]. A custom [resolver] may
+/// be provided to map files not visible to the current package's runtime.
+Future<T> resolveSources<T>(
+  Map<String, String> inputs,
+  FutureOr<T> Function(Resolver resolver) action, {
+  PackageResolver resolver,
+  String resolverFor,
+  String rootPackage,
+  Future<Null> tearDown,
+  Resolvers resolvers,
+}) {
+  if (inputs == null || inputs.isEmpty) {
+    throw ArgumentError.value(inputs, 'inputs', 'Must be a non-empty Map');
+  }
+  return _resolveAssets(
+    inputs,
+    rootPackage ?? AssetId.parse(inputs.keys.first).package,
+    action,
+    resolver: resolver,
+    resolverFor: AssetId.parse(resolverFor ?? inputs.keys.first),
+    tearDown: tearDown,
+    resolvers: resolvers,
+  );
+}
+
+/// A convenience for using [resolveSources] with a single [inputId] from disk.
+Future<T> resolveAsset<T>(
+  AssetId inputId,
+  FutureOr<T> Function(Resolver resolver) action, {
+  PackageResolver resolver,
+  Future<Null> tearDown,
+  Resolvers resolvers,
+}) {
+  return _resolveAssets(
+    {
+      '${inputId.package}|${inputId.path}': useAssetReader,
+    },
+    inputId.package,
+    action,
+    resolver: resolver,
+    resolverFor: inputId,
+    tearDown: tearDown,
+    resolvers: resolvers,
+  );
+}
+
+/// Internal-only backing implementation of `resolve{Asset|Source(s)}`.
+///
+/// If the value of an entry of [inputs] is [useAssetReader] then the value is
+/// instead read from the file system, otherwise the provided text is used as
+/// the contents of the asset.
+Future<T> _resolveAssets<T>(
+  Map<String, String> inputs,
+  String rootPackage,
+  FutureOr<T> Function(Resolver resolver) action, {
+  PackageResolver resolver,
+  AssetId resolverFor,
+  Future<Null> tearDown,
+  Resolvers resolvers,
+}) async {
+  final syncResolver = await (resolver ?? PackageResolver.current).asSync;
+  final assetReader = PackageAssetReader(syncResolver, rootPackage);
+  final resolveBuilder = _ResolveSourceBuilder(
+    action,
+    resolverFor,
+    tearDown,
+  );
+  final inputAssets = <AssetId, String>{};
+  await Future.wait(inputs.keys.map((String rawAssetId) async {
+    final assetId = AssetId.parse(rawAssetId);
+    var assetValue = inputs[rawAssetId];
+    if (assetValue == useAssetReader) {
+      assetValue = await assetReader.readAsString(assetId);
+    }
+    inputAssets[assetId] = assetValue;
+  }));
+  final inMemory = InMemoryAssetReader(
+    sourceAssets: inputAssets,
+    rootPackage: rootPackage,
+  );
+  // We don't care about the results of this build, but we also can't await
+  // it because that would block on the `tearDown` of the `resolveBuilder`.
+  //
+  // We also dont want to leak errors as unhandled async errors so we swallow
+  // them here.
+  //
+  // Errors will still be reported through the resolver itself as well as the
+  // `onDone` future that we return.
+  unawaited(runBuilder(
+    resolveBuilder,
+    inputAssets.keys,
+    MultiAssetReader([inMemory, assetReader]),
+    InMemoryAssetWriter(),
+    resolvers ?? defaultResolvers,
+  ).catchError((_) {}));
+  return resolveBuilder.onDone.future;
+}
+
+/// A [Builder] that is only used to retrieve a [Resolver] instance.
+///
+/// It simulates what a user builder would do in order to resolve a primary
+/// input given a set of dependencies to also use. See `resolveSource`.
+class _ResolveSourceBuilder<T> implements Builder {
+  final FutureOr<T> Function(Resolver) _action;
+  final Future _tearDown;
+  final AssetId _resolverFor;
+
+  final onDone = Completer<T>();
+
+  _ResolveSourceBuilder(this._action, this._resolverFor, this._tearDown);
+
+  @override
+  Future<void> build(BuildStep buildStep) async {
+    if (_resolverFor != buildStep.inputId) return;
+    try {
+      onDone.complete(await _action(buildStep.resolver));
+    } catch (e, s) {
+      onDone.completeError(e, s);
+    }
+    await _tearDown;
+  }
+
+  @override
+  final buildExtensions = const {
+    '': ['.unused']
+  };
+}
diff --git a/build_test/lib/src/stub_reader.dart b/build_test/lib/src/stub_reader.dart
new file mode 100644
index 0000000..6c10e90
--- /dev/null
+++ b/build_test/lib/src/stub_reader.dart
@@ -0,0 +1,30 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:convert';
+
+import 'package:build/build.dart';
+import 'package:crypto/crypto.dart';
+import 'package:glob/glob.dart';
+
+/// A no-op implementation of [AssetReader].
+class StubAssetReader extends AssetReader implements MultiPackageAssetReader {
+  StubAssetReader();
+
+  @override
+  Future<bool> canRead(AssetId id) => Future.value(null);
+
+  @override
+  Future<List<int>> readAsBytes(AssetId id) => Future.value(null);
+
+  @override
+  Future<String> readAsString(AssetId id, {Encoding encoding = utf8}) =>
+      Future.value(null);
+
+  @override
+  Stream<AssetId> findAssets(Glob glob, {String package}) => null;
+
+  @override
+  Future<Digest> digest(AssetId id) => Future.value(Digest([1, 2, 3]));
+}
diff --git a/build_test/lib/src/stub_writer.dart b/build_test/lib/src/stub_writer.dart
new file mode 100644
index 0000000..8c4b5b2
--- /dev/null
+++ b/build_test/lib/src/stub_writer.dart
@@ -0,0 +1,18 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:convert';
+
+import 'package:build/build.dart';
+
+/// A no-op implementation of [AssetWriter].
+class StubAssetWriter implements AssetWriter {
+  const StubAssetWriter();
+
+  @override
+  Future writeAsBytes(_, __) => Future.value(null);
+
+  @override
+  Future writeAsString(_, __, {Encoding encoding = utf8}) => Future.value(null);
+}
diff --git a/build_test/lib/src/test_bootstrap_builder.dart b/build_test/lib/src/test_bootstrap_builder.dart
new file mode 100644
index 0000000..7c77340
--- /dev/null
+++ b/build_test/lib/src/test_bootstrap_builder.dart
@@ -0,0 +1,78 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'package:build/build.dart';
+import 'package:path/path.dart' as p;
+// ignore: deprecated_member_use
+import 'package:test_core/backend.dart';
+
+/// A [Builder] that injects bootstrapping code used by the test runner to run
+/// tests in --precompiled mode.
+///
+/// This doesn't modify existing code at all, it just adds wrapper files that
+/// can be used to load isolates or iframes.
+class TestBootstrapBuilder extends Builder {
+  @override
+  final buildExtensions = const {
+    '_test.dart': [
+      '_test.dart.vm_test.dart',
+      '_test.dart.browser_test.dart',
+      '_test.dart.node_test.dart',
+    ]
+  };
+  TestBootstrapBuilder();
+
+  @override
+  Future<void> build(BuildStep buildStep) async {
+    var id = buildStep.inputId;
+    var contents = await buildStep.readAsString(id);
+    var assetPath = id.pathSegments.first == 'lib'
+        ? p.url.join('packages', id.package, id.path)
+        : id.path;
+    var metadata = parseMetadata(
+        assetPath, contents, Runtime.builtIn.map((r) => r.name).toSet());
+
+    if (metadata.testOn.evaluate(SuitePlatform(Runtime.vm))) {
+      await buildStep.writeAsString(id.addExtension('.vm_test.dart'), '''
+          import "dart:isolate";
+
+          import "package:test/bootstrap/vm.dart";
+
+          import "${p.url.basename(id.path)}" as test;
+
+          void main(_, SendPort message) {
+            internalBootstrapVmTest(() => test.main, message);
+          }
+        ''');
+    }
+
+    var browserRuntimes = Runtime.builtIn.where((r) => r.isBrowser == true);
+    if (browserRuntimes
+        .any((r) => metadata.testOn.evaluate(SuitePlatform(r)))) {
+      await buildStep.writeAsString(id.addExtension('.browser_test.dart'), '''
+          import "package:test/bootstrap/browser.dart";
+
+          import "${p.url.basename(id.path)}" as test;
+
+          void main() {
+            internalBootstrapBrowserTest(() => test.main);
+          }
+        ''');
+    }
+
+    if (metadata.testOn.evaluate(SuitePlatform(Runtime.nodeJS))) {
+      await buildStep.writeAsString(id.addExtension('.node_test.dart'), '''
+          import "package:test/bootstrap/node.dart";
+
+          import "${p.url.basename(id.path)}" as test;
+
+          void main() {
+            internalBootstrapNodeTest(() => test.main);
+          }
+        ''');
+    }
+  }
+}
diff --git a/build_test/lib/src/test_builder.dart b/build_test/lib/src/test_builder.dart
new file mode 100644
index 0000000..31ee456
--- /dev/null
+++ b/build_test/lib/src/test_builder.dart
@@ -0,0 +1,177 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:convert';
+
+import 'package:build/build.dart';
+import 'package:logging/logging.dart';
+import 'package:test/test.dart';
+
+import 'assets.dart';
+import 'in_memory_reader.dart';
+import 'in_memory_writer.dart';
+import 'multi_asset_reader.dart';
+import 'resolve_source.dart';
+import 'written_asset_reader.dart';
+
+AssetId _passThrough(AssetId id) => id;
+
+/// Validates that [actualAssets] matches the expected [outputs].
+///
+/// The keys in [outputs] should be serialized [AssetId]s in the form
+/// `'package|path'`. The values should match the expected content for the
+/// written asset and may be a String (for `writeAsString`), a `List<int>` (for
+/// `writeAsBytes`) or a [Matcher] for a String or bytes.
+///
+/// [actualAssets] are the IDs that were recorded as written during the build.
+///
+/// Assets are checked against those that were written to [writer]. If other
+/// assets were written through the writer, but not as part of the build
+/// process, they will be ignored. Only the IDs in [actualAssets] are checked.
+///
+/// If assets are written to a location that does not match their logical
+/// association to a package pass [mapAssetIds] to translate from the logical
+/// location to the actual written location.
+void checkOutputs(
+    Map<String, /*List<int>|String|Matcher<String|List<int>>*/ dynamic> outputs,
+    Iterable<AssetId> actualAssets,
+    RecordingAssetWriter writer,
+    {AssetId Function(AssetId id) mapAssetIds = _passThrough}) {
+  var modifiableActualAssets = Set.from(actualAssets);
+  if (outputs != null) {
+    outputs.forEach((serializedId, contentsMatcher) {
+      assert(contentsMatcher is String ||
+          contentsMatcher is List<int> ||
+          contentsMatcher is Matcher);
+
+      var assetId = makeAssetId(serializedId);
+
+      // Check that the asset was produced.
+      expect(modifiableActualAssets, contains(assetId),
+          reason: 'Builder failed to write asset $assetId');
+      modifiableActualAssets.remove(assetId);
+      var actual = writer.assets[mapAssetIds(assetId)];
+      Object expected;
+      if (contentsMatcher is String) {
+        expected = utf8.decode(actual);
+      } else if (contentsMatcher is List<int>) {
+        expected = actual;
+      } else if (contentsMatcher is Matcher) {
+        expected = actual;
+      } else {
+        throw ArgumentError('Expected values for `outputs` to be of type '
+            '`String`, `List<int>`, or `Matcher`, but got `$contentsMatcher`.');
+      }
+      expect(expected, contentsMatcher,
+          reason: 'Unexpected content for $assetId in result.outputs.');
+    });
+    // Check that no extra assets were produced.
+    expect(modifiableActualAssets, isEmpty,
+        reason:
+            'Unexpected outputs found `$actualAssets`. Only expected $outputs');
+  }
+}
+
+/// Runs [builder] in a test environment.
+///
+/// The test environment supplies in-memory build [sourceAssets] to the builders
+/// under test. [outputs] may be optionally provided to verify that the builders
+/// produce the expected output. If [outputs] is omitted the only validation
+/// this method provides is that the build did not `throw`.
+///
+/// Either [generateFor] or the [isInput] callback can specify which assets
+/// should be given as inputs to the builder. These can be omitted if every
+/// asset in [sourceAssets] should be considered an input. [generateFor] is
+/// ignored if both [isInput] and [generateFor] are provided.
+///
+/// The keys in [sourceAssets] and [outputs] are paths to file assets and the
+/// values are file contents. The paths must use the following format:
+///
+///     PACKAGE_NAME|PATH_WITHIN_PACKAGE
+///
+/// Where `PACKAGE_NAME` is the name of the package, and `PATH_WITHIN_PACKAGE`
+/// is the path to a file relative to the package. `PATH_WITHIN_PACKAGE` must
+/// include `lib`, `web`, `bin` or `test`. Example: "myapp|lib/utils.dart".
+///
+/// If a [reader] is provided, then any asset not in [sourceAssets] will be
+/// read from the provided reader. This allows you to more easily provide
+/// sources of entire packages to the test, instead of mocking them out, for
+/// example, this exposes all assets available to the test itself:
+///
+///
+/// ```dart
+/// testBuilder(yourBuilder, {}/* test assets here */,
+///     reader: await PackageAssetReader.currentIsolate());
+/// ```
+///
+/// Callers may optionally provide a [writer] to stub different behavior or do
+/// more complex validation than what is possible with [outputs].
+///
+/// Callers may optionally provide an [onLog] callback to do validaiton on the
+/// logging output of the builder.
+Future testBuilder(
+    Builder builder, Map<String, /*String|List<int>*/ dynamic> sourceAssets,
+    {Set<String> generateFor,
+    bool Function(String assetId) isInput,
+    String rootPackage,
+    MultiPackageAssetReader reader,
+    RecordingAssetWriter writer,
+    Map<String, /*String|List<int>|Matcher<String|List<int>>*/ dynamic> outputs,
+    void Function(LogRecord log) onLog,
+    void Function(AssetId, Iterable<AssetId>)
+        reportUnusedAssetsForInput}) async {
+  writer ??= InMemoryAssetWriter();
+
+  var inputIds = {
+    for (var descriptor in sourceAssets.keys) makeAssetId(descriptor)
+  };
+  var allPackages = {for (var id in inputIds) id.package};
+  if (allPackages.length == 1) rootPackage ??= allPackages.first;
+
+  inputIds.addAll([
+    for (var package in allPackages) AssetId(package, r'lib/$lib$'),
+    if (rootPackage != null) ...[
+      AssetId(rootPackage, r'$package$'),
+      AssetId(rootPackage, r'test/$test$'),
+      AssetId(rootPackage, r'web/$web$'),
+    ]
+  ]);
+
+  final inMemoryReader = InMemoryAssetReader(rootPackage: rootPackage);
+
+  sourceAssets.forEach((serializedId, contents) {
+    var id = makeAssetId(serializedId);
+    if (contents is String) {
+      inMemoryReader.cacheStringAsset(id, contents);
+    } else if (contents is List<int>) {
+      inMemoryReader.cacheBytesAsset(id, contents);
+    }
+  });
+
+  isInput ??= generateFor?.contains ?? (_) => true;
+  inputIds.retainWhere((id) => isInput('$id'));
+
+  var writerSpy = AssetWriterSpy(writer);
+  var logger = Logger('testBuilder');
+  var logSubscription = logger.onRecord.listen(onLog);
+
+  for (var input in inputIds) {
+    // create another writer spy and reader for each input. This prevents writes
+    // from a previous input being readable when processing the current input.
+    final spyForStep = AssetWriterSpy(writerSpy);
+    final readerForStep = MultiAssetReader([
+      inMemoryReader,
+      if (reader != null) reader,
+      WrittenAssetReader(writer, spyForStep),
+    ]);
+
+    await runBuilder(
+        builder, {input}, readerForStep, spyForStep, defaultResolvers,
+        logger: logger, reportUnusedAssetsForInput: reportUnusedAssetsForInput);
+  }
+
+  await logSubscription.cancel();
+  var actualOutputs = writerSpy.assetsWritten;
+  checkOutputs(outputs, actualOutputs, writer);
+}
diff --git a/build_test/lib/src/written_asset_reader.dart b/build_test/lib/src/written_asset_reader.dart
new file mode 100644
index 0000000..d24dbcb
--- /dev/null
+++ b/build_test/lib/src/written_asset_reader.dart
@@ -0,0 +1,62 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS 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 'dart:convert';
+
+import 'package:build/build.dart';
+import 'package:glob/glob.dart';
+
+import 'in_memory_writer.dart';
+
+/// A [MultiPackageAssetReader] which supports reads from previous outputs.
+class WrittenAssetReader extends MultiPackageAssetReader {
+  final RecordingAssetWriter source;
+
+  /// An optional [AssetWriterSpy] to limit what's readable through this reader.
+  ///
+  /// Only assets reported as written trough this [AssetWriterSpy] can be read
+  /// from this reader. When null, all assets from [source] are available.
+  final AssetWriterSpy filterSpy;
+
+  WrittenAssetReader(this.source, [this.filterSpy]);
+
+  @override
+  Future<bool> canRead(AssetId id) {
+    var canRead = source.assets.containsKey(id);
+    if (filterSpy != null) {
+      canRead = canRead && filterSpy.assetsWritten.contains(id);
+    }
+
+    return Future.value(canRead);
+  }
+
+  @override
+  Stream<AssetId> findAssets(Glob glob, {String package}) async* {
+    var available = source.assets.keys.toSet();
+    if (filterSpy != null) {
+      available = available.intersection(filterSpy.assetsWritten.toSet());
+    }
+
+    for (var asset in available) {
+      if (!glob.matches(asset.path)) continue;
+      if (package != null && asset.package != package) continue;
+
+      yield asset;
+    }
+  }
+
+  @override
+  Future<List<int>> readAsBytes(AssetId id) {
+    if (!source.assets.containsKey(id)) {
+      throw AssetNotFoundException(id);
+    }
+    return Future.value(source.assets[id]);
+  }
+
+  @override
+  Future<String> readAsString(AssetId id, {Encoding encoding}) async {
+    encoding ??= utf8;
+    return encoding.decode(await readAsBytes(id));
+  }
+}
diff --git a/build_test/mono_pkg.yaml b/build_test/mono_pkg.yaml
new file mode 100644
index 0000000..912fda7
--- /dev/null
+++ b/build_test/mono_pkg.yaml
@@ -0,0 +1,20 @@
+dart:
+  - dev
+
+stages:
+  - analyze_and_format:
+    - group:
+        - dartfmt: sdk
+        - dartanalyzer: --fatal-infos --fatal-warnings .
+    - dartanalyzer: --fatal-warnings .
+      dart:
+        - 2.6.0
+  - unit_test:
+    - command: pub run build_runner test
+      os:
+        - linux
+        - windows
+
+cache:
+  directories:
+    - .dart_tool/build
diff --git a/build_test/pubspec.yaml b/build_test/pubspec.yaml
new file mode 100644
index 0000000..5f71f2f
--- /dev/null
+++ b/build_test/pubspec.yaml
@@ -0,0 +1,31 @@
+name: build_test
+description: Utilities for writing unit tests of Builders.
+version: 0.10.12+1
+homepage: https://github.com/dart-lang/build/tree/master/build_test
+
+environment:
+  sdk: ">=2.6.0 <3.0.0"
+
+dependencies:
+  async: ">=1.2.0 <3.0.0"
+  build: ">=1.2.0 <2.0.0"
+  build_config: ">=0.2.0 <0.5.0"
+  build_resolvers: ">=0.2.0 <2.0.0"
+  crypto: ">=0.9.2 <3.0.0"
+  glob: ^1.1.0
+  html: ">=0.9.0 <0.15.0"
+  logging: ^0.11.2
+  matcher: ^0.12.0
+  package_resolver: ^1.0.2
+  path: ^1.4.1
+  pedantic: ^1.0.0
+  stream_transform: ">=0.0.20 <2.0.0"
+  test: '>=0.12.42 <2.0.0'
+  test_core: '>=0.2.4 <0.4.0'
+  watcher: ^0.9.7
+
+dev_dependencies:
+  analyzer: ">=0.35.4 <0.40.0"
+  build_runner: ^1.3.3
+  build_vm_compilers: '>=0.1.2 <2.0.0'
+  collection: ^1.14.0
diff --git a/build_vm_compilers/BUILD.gn b/build_vm_compilers/BUILD.gn
new file mode 100644
index 0000000..7861884
--- /dev/null
+++ b/build_vm_compilers/BUILD.gn
@@ -0,0 +1,22 @@
+# This file is generated by importer.py for build_vm_compilers-1.0.4
+
+import("//build/dart/dart_library.gni")
+
+dart_library("build_vm_compilers") {
+  package_name = "build_vm_compilers"
+
+  # This parameter is left empty as we don't care about analysis or exporting
+  # these sources outside of the tree.
+  sources = []
+
+  disable_analysis = true
+
+  deps = [
+    "//third_party/dart-pkg/pub/build",
+    "//third_party/dart-pkg/pub/build_config",
+    "//third_party/dart-pkg/pub/pool",
+    "//third_party/dart-pkg/pub/path",
+    "//third_party/dart-pkg/pub/build_modules",
+    "//third_party/dart-pkg/pub/analyzer",
+  ]
+}
diff --git a/build_vm_compilers/CHANGELOG.md b/build_vm_compilers/CHANGELOG.md
new file mode 100644
index 0000000..cccd852
--- /dev/null
+++ b/build_vm_compilers/CHANGELOG.md
@@ -0,0 +1,56 @@
+## 1.0.4
+
+- Allow analyzer version `0.39.0`.
+
+## 1.0.3
+
+- Allow analyzer version `0.38.0`.
+
+## 1.0.2
+
+- Fix kernel concat ordering to be topological instead of reverse
+  topological.
+
+## 1.0.1
+
+- Allow analyzer version 0.37.0.
+
+## 1.0.0
+
+- Support build_modules 2.0.0
+  - Define our own `vm` platform and builders explicitly.
+- Skip trying to compile apps that import known incompatible libraries.
+
+## 0.1.2
+
+- Increased the upper bound for `package:analyzer` to `<0.37.0`.
+- Require Dart SDK `>=2.1.0`.
+
+## 0.1.1+5
+
+- Increased the upper bound for `package:analyzer` to `<0.36.0`.
+
+## 0.1.1+4
+
+- Increased the upper bound for `package:analyzer` to `<0.35.0`.
+
+## 0.1.1+3
+
+- Increased the upper bound for `package:analyzer` to `<0.34.0`.
+
+## 0.1.1+2
+
+Support `package:build_modules` version `1.x.x`.
+
+## 0.1.1+1
+
+Support `package:build` version `1.x.x`.
+
+## 0.1.1
+
+Support the latest build_modules.
+
+## 0.1.0
+
+Initial release, adds the modular kernel compiler for the vm platform, and the
+entrypoint builder which concatenates all the modules into a single kernel file.
diff --git a/build_vm_compilers/LICENSE b/build_vm_compilers/LICENSE
new file mode 100644
index 0000000..c4dc9ba
--- /dev/null
+++ b/build_vm_compilers/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2018, the Dart project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of Google Inc. nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/build_vm_compilers/README.md b/build_vm_compilers/README.md
new file mode 100644
index 0000000..971d656
--- /dev/null
+++ b/build_vm_compilers/README.md
@@ -0,0 +1,70 @@
+<p align="center">
+  Vm compilers for users of <a href="https://pub.dev/packages/build"><code>package:build</code></a>.
+  <br>
+  <a href="https://travis-ci.org/dart-lang/build">
+    <img src="https://travis-ci.org/dart-lang/build.svg?branch=master" alt="Build Status" />
+  </a>
+  <a href="https://github.com/dart-lang/build/labels/package%3A%20build_vm_compilers">
+    <img src="https://img.shields.io/github/issues-raw/dart-lang/build/package%3A%20build_vm_compilers.svg" alt="Issues related to build_vm_compilers" />
+  </a>
+  <a href="https://pub.dev/packages/build_vm_compilers">
+    <img src="https://img.shields.io/pub/v/build_vm_compilers.svg" alt="Pub Package Version" />
+  </a>
+  <a href="https://pub.dev/documentation/build_vm_compilers/latest">
+    <img src="https://img.shields.io/badge/dartdocs-latest-blue.svg" alt="Latest Dartdocs" />
+  </a>
+  <a href="https://gitter.im/dart-lang/build">
+    <img src="https://badges.gitter.im/dart-lang/build.svg" alt="Join the chat on Gitter" />
+  </a>
+</p>
+
+* [Installation](#installation)
+* [Usage](#usage)
+* [Configuration](#configuration)
+* [Manual Usage](#manual-usage)
+
+## Installation
+
+This package is intended to be used as a [development dependency][] for users
+of [`package:build`][] who want to run code in the Dart vm with precompiled
+kernel files. This allows you to share compilation of dependencies between
+multiple entrypoints, instead of doing a monolithic compile of each entrypoint
+like the Dart VM would normally do on each run.
+
+**Note**: If you want to use this package for running tests with
+`pub run build_runner test` you will also need a `build_test` dev dependency.
+
+## Usage
+
+This package creates a `.vm.app.dill` file corresponding to each `.dart` file
+that contains a `main` function.
+
+These files can be passed directly to the vm, instead of the dart script, and
+the vm will skip its initial parse and compile step.
+
+You can find the output either by using the `-o <dir>` option for build_runner,
+or by finding it in the generated cache directory, which is located at
+`.dart_tool/build/generated/<your-package>`.
+
+## Configuration
+
+There are no configuration options available at this time.
+
+## Custom Build Script Integration
+
+If you are using a custom build script, you will need to add the following
+builder applications to what you already have, sometime after the
+`build_modules` builder applications:
+
+```dart
+    apply('build_vm_compilers|entrypoint',
+        [vmKernelEntrypointBuilder], toRoot(),
+        hideOutput: true,
+        // These globs should match your entrypoints only.
+        defaultGenerateFor: const InputSet(
+            include: const ['bin/**', 'tool/**', 'test/**.vm_test.dart'])),
+]
+```
+
+[development dependency]: https://dart.dev/tools/pub/dependencies#dev-dependencies
+[`package:build`]: https://pub.dev/packages/build
diff --git a/build_vm_compilers/build.yaml b/build_vm_compilers/build.yaml
new file mode 100644
index 0000000..4b13b98
--- /dev/null
+++ b/build_vm_compilers/build.yaml
@@ -0,0 +1,52 @@
+builders:
+  modules:
+    import: "package:build_vm_compilers/builders.dart"
+    builder_factories:
+      - metaModuleBuilder
+      - metaModuleCleanBuilder
+      - moduleBuilder
+    build_extensions:
+      $lib$:
+        - .vm.meta_module.raw
+        - .vm.meta_module.clean
+      .dart:
+        - .vm.module
+    is_optional: True
+    auto_apply: none
+    required_inputs: [".dart", ".module.library"]
+    applies_builders: ["build_modules|module_cleanup"]
+  vm:
+    import: "package:build_vm_compilers/builders.dart"
+    builder_factories:
+      - vmKernelModuleBuilder
+    build_extensions:
+      .vm.module:
+        - .vm.dill
+    is_optional: True
+    auto_apply: all_packages
+    required_inputs:
+      - .dart
+      - .vm.module
+    applies_builders:
+      - build_vm_compilers|modules
+  entrypoint:
+    import: "package:build_vm_compilers/builders.dart"
+    builder_factories:
+      - vmKernelEntrypointBuilder
+    build_extensions:
+      .dart:
+        - .vm.app.dill
+    required_inputs:
+      - .dart
+      - .vm.dill
+      - .vm.module
+    build_to: cache
+    auto_apply: root_package
+    defaults:
+      generate_for:
+        include:
+          - bin/**
+          - tool/**
+          - test/**.dart.vm_test.dart
+          - example/**
+          - benchmark/**
diff --git a/build_vm_compilers/lib/build_vm_compilers.dart b/build_vm_compilers/lib/build_vm_compilers.dart
new file mode 100644
index 0000000..e0ea675
--- /dev/null
+++ b/build_vm_compilers/lib/build_vm_compilers.dart
@@ -0,0 +1,6 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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.
+
+export 'src/platform.dart' show vmPlatform;
+export 'src/vm_entrypoint_builder.dart' show VmEntrypointBuilder;
diff --git a/build_vm_compilers/lib/builders.dart b/build_vm_compilers/lib/builders.dart
new file mode 100644
index 0000000..6848a54
--- /dev/null
+++ b/build_vm_compilers/lib/builders.dart
@@ -0,0 +1,27 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'package:build/build.dart';
+import 'package:build_modules/build_modules.dart';
+import 'package:path/path.dart' as p;
+
+import 'src/platform.dart';
+import 'src/vm_entrypoint_builder.dart';
+
+const vmKernelModuleExtension = '.vm.dill';
+const vmKernelEntrypointExtension = '.vm.app.dill';
+
+Builder metaModuleBuilder(BuilderOptions options) =>
+    MetaModuleBuilder.forOptions(vmPlatform, options);
+Builder metaModuleCleanBuilder([_]) => MetaModuleCleanBuilder(vmPlatform);
+Builder moduleBuilder([_]) => ModuleBuilder(vmPlatform);
+
+Builder vmKernelModuleBuilder(_) => KernelBuilder(
+      summaryOnly: false,
+      sdkKernelPath: p.join('lib', '_internal', 'vm_platform_strong.dill'),
+      outputExtension: vmKernelModuleExtension,
+      platform: vmPlatform,
+    );
+
+Builder vmKernelEntrypointBuilder(_) => VmEntrypointBuilder();
diff --git a/build_vm_compilers/lib/src/platform.dart b/build_vm_compilers/lib/src/platform.dart
new file mode 100644
index 0000000..85bdf5c
--- /dev/null
+++ b/build_vm_compilers/lib/src/platform.dart
@@ -0,0 +1,23 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS 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 'package:build_modules/build_modules.dart';
+
+final vmPlatform = DartPlatform.register('vm', [
+  'async',
+  'cli',
+  'collection',
+  'convert',
+  'core',
+  'developer',
+  'io',
+  'isolate',
+  'math',
+  'mirrors',
+  'nativewrappers',
+  'profiler',
+  'typed_data',
+  'vmservice_io',
+  '_internal',
+]);
diff --git a/build_vm_compilers/lib/src/vm_entrypoint_builder.dart b/build_vm_compilers/lib/src/vm_entrypoint_builder.dart
new file mode 100644
index 0000000..03b520a
--- /dev/null
+++ b/build_vm_compilers/lib/src/vm_entrypoint_builder.dart
@@ -0,0 +1,99 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:convert';
+
+// ignore: deprecated_member_use
+import 'package:analyzer/analyzer.dart';
+import 'package:build/build.dart';
+import 'package:build_modules/build_modules.dart';
+import 'package:pool/pool.dart';
+
+import '../builders.dart';
+import 'platform.dart';
+
+/// Because we hold bytes in memory we don't want to compile to many app entry
+/// points at once.
+final _buildPool = Pool(16);
+
+/// A builder which combines several [vmKernelModuleExtension] modules into a
+/// single [vmKernelEntrypointExtension] file, which represents an entire
+/// application.
+class VmEntrypointBuilder implements Builder {
+  const VmEntrypointBuilder();
+
+  @override
+  final buildExtensions = const {
+    '.dart': [vmKernelEntrypointExtension],
+  };
+
+  @override
+  Future<void> build(BuildStep buildStep) async {
+    await _buildPool.withResource(() async {
+      var dartEntrypointId = buildStep.inputId;
+      var isAppEntrypoint = await _isAppEntryPoint(dartEntrypointId, buildStep);
+      if (!isAppEntrypoint) return;
+
+      var moduleId =
+          buildStep.inputId.changeExtension(moduleExtension(vmPlatform));
+      var module = Module.fromJson(
+          json.decode(await buildStep.readAsString(moduleId))
+              as Map<String, dynamic>);
+
+      List<Module> transitiveModules;
+      try {
+        transitiveModules = await module
+            .computeTransitiveDependencies(buildStep, throwIfUnsupported: true);
+      } on UnsupportedModules catch (e) {
+        var librariesString = (await e.exactLibraries(buildStep).toList())
+            .map((lib) => AssetId(lib.id.package,
+                lib.id.path.replaceFirst(moduleLibraryExtension, '.dart')))
+            .join('\n');
+        log.warning('''
+Skipping compiling ${buildStep.inputId} for the vm because some of its
+transitive libraries have sdk dependencies that not supported on this platform:
+
+$librariesString
+
+https://github.com/dart-lang/build/blob/master/docs/faq.md#how-can-i-resolve-skipped-compiling-warnings
+''');
+        return;
+      }
+
+      var transitiveKernelModules = [
+        module.primarySource.changeExtension(vmKernelModuleExtension)
+      ].followedBy(transitiveModules.reversed.map(
+          (m) => m.primarySource.changeExtension(vmKernelModuleExtension)));
+      var appContents = <int>[];
+      for (var dependencyId in transitiveKernelModules) {
+        appContents.addAll(await buildStep.readAsBytes(dependencyId));
+      }
+      await buildStep.writeAsBytes(
+          buildStep.inputId.changeExtension(vmKernelEntrypointExtension),
+          appContents);
+    });
+  }
+}
+
+/// Returns whether or not [dartId] is an app entrypoint (basically, whether
+/// or not it has a `main` function).
+Future<bool> _isAppEntryPoint(AssetId dartId, AssetReader reader) async {
+  assert(dartId.extension == '.dart');
+  // Skip reporting errors here, dartdevc will report them later with nicer
+  // formatting.
+  // ignore: deprecated_member_use
+  var parsed = parseCompilationUnit(await reader.readAsString(dartId),
+      suppressErrors: true);
+  // Allow two or fewer arguments so that entrypoints intended for use with
+  // [spawnUri] get counted.
+  //
+  // TODO: This misses the case where a Dart file doesn't contain main(),
+  // but has a part that does, or it exports a `main` from another library.
+  return parsed.declarations.any((node) {
+    return node is FunctionDeclaration &&
+        node.name.name == 'main' &&
+        node.functionExpression.parameters.parameters.length <= 2;
+  });
+}
diff --git a/build_vm_compilers/mono_pkg.yaml b/build_vm_compilers/mono_pkg.yaml
new file mode 100644
index 0000000..c79ba5d
--- /dev/null
+++ b/build_vm_compilers/mono_pkg.yaml
@@ -0,0 +1,16 @@
+dart:
+  - dev
+
+stages:
+  - analyze_and_format:
+    - group:
+        - dartfmt: sdk
+        - dartanalyzer: --fatal-infos --fatal-warnings .
+    - dartanalyzer: --fatal-warnings .
+      dart:
+        - 2.5.0
+  - unit_test:
+    - test:
+      os:
+        - linux
+        - windows
diff --git a/build_vm_compilers/pubspec.yaml b/build_vm_compilers/pubspec.yaml
new file mode 100644
index 0000000..f9257d2
--- /dev/null
+++ b/build_vm_compilers/pubspec.yaml
@@ -0,0 +1,23 @@
+name: build_vm_compilers
+version: 1.0.4
+description: Builder implementations wrapping Dart VM compilers.
+author: Dart Team <misc@dartlang.org>
+homepage: https://github.com/dart-lang/build/tree/master/build_vm_compilers
+
+environment:
+  sdk: ">=2.5.0 <3.0.0"
+
+dependencies:
+  analyzer: ">=0.35.0 <0.40.0"
+  build: ^1.0.0
+  build_config: ">=0.3.0 <0.5.0"
+  build_modules: ^2.0.0
+  path: ^1.6.0
+  pool: ^1.3.0
+
+dev_dependencies:
+  build_runner: ^1.0.0
+  test: ^1.0.0
+  test_descriptor: ^1.1.0
+  _test_common:
+    path: ../_test_common
diff --git a/build_web_compilers/BUILD.gn b/build_web_compilers/BUILD.gn
new file mode 100644
index 0000000..d5652c7
--- /dev/null
+++ b/build_web_compilers/BUILD.gn
@@ -0,0 +1,34 @@
+# This file is generated by importer.py for build_web_compilers-2.9.0
+
+import("//build/dart/dart_library.gni")
+
+dart_library("build_web_compilers") {
+  package_name = "build_web_compilers"
+
+  # This parameter is left empty as we don't care about analysis or exporting
+  # these sources outside of the tree.
+  sources = []
+
+  disable_analysis = true
+
+  deps = [
+    "//third_party/dart-pkg/pub/stack_trace",
+    "//third_party/dart-pkg/pub/glob",
+    "//third_party/dart-pkg/pub/analyzer",
+    "//third_party/dart-pkg/pub/js",
+    "//third_party/dart-pkg/pub/meta",
+    "//third_party/dart-pkg/pub/scratch_space",
+    "//third_party/dart-pkg/pub/collection",
+    "//third_party/dart-pkg/pub/path",
+    "//third_party/dart-pkg/pub/source_span",
+    "//third_party/dart-pkg/pub/build_modules",
+    "//third_party/dart-pkg/pub/bazel_worker",
+    "//third_party/dart-pkg/pub/pool",
+    "//third_party/dart-pkg/pub/source_maps",
+    "//third_party/dart-pkg/pub/logging",
+    "//third_party/dart-pkg/pub/crypto",
+    "//third_party/dart-pkg/pub/archive",
+    "//third_party/dart-pkg/pub/build",
+    "//third_party/dart-pkg/pub/build_config",
+  ]
+}
diff --git a/build_web_compilers/CHANGELOG.md b/build_web_compilers/CHANGELOG.md
new file mode 100644
index 0000000..774e4a0
--- /dev/null
+++ b/build_web_compilers/CHANGELOG.md
@@ -0,0 +1,544 @@
+## 2.9.0
+
+- Add support for enabling experiments through the `experiments` option on the
+  `build_web_compilers|ddc` builder. This must be configured globally.
+  - This is a list of experiment names, which translates into
+    `--enable-experiment=<name>` arguments.
+
+## 2.8.0
+
+- Enable asserts in dev mode with dart2js by default.
+
+## 2.7.2
+
+- Fix a bug with hot restart,
+  [#2586](https://github.com/dart-lang/build/issues/2586).
+
+## 2.7.1
+
+- Allow analyzer version `0.39.x`.
+
+## 2.7.0
+
+- Added an `environment` option to the `DevCompilerBuilder`.
+  - This can be configured using the `environment` option of the
+    `build_web_compilers|ddc` builder.
+  - The expected value is a `Map<String, String>` and is equivalent to
+    providing `-D<key>=<value>` command line arguments.
+  - This option should only be set globally, and will throw if it ever recieves
+    two different values. This is to ensure all modules are compiled with the
+    same environment.
+
+## 2.6.4
+
+- Deobfuscate DDC extension method stack traces.
+
+## 2.6.3
+
+- Enforce builder application ordering between the SDK JS copy builder and the
+  DDC entrypoint builder.
+
+## 2.6.2
+
+Fix the skipPlatformCheck option which was accidentally doing the opposite
+of what it claimed.
+
+## 2.6.1
+
+- Use the kernel version of `dart_sdk.js` rather than the analyzer version.
+
+## 2.6.0
+
+Add an option to globally skip the platform checks instead of only skipping
+them for a set of whitelisted packages.
+
+## 2.5.2
+
+Republish of `2.5.0` with the proper min sdk contraint.
+
+## 2.5.1
+
+### Bug fix for issue #2464
+
+Ignore the `trackUnusedInputs` option that was added in `2.5.0`.
+
+This option will be respected again in the next release which will have the
+proper minimum sdk constraint.
+
+## 2.5.0
+
+Add support for dependency pruning to the `DevCompilerBuilder`. This should
+greatly improve the invalidation semantics for builds, meaning that less code
+will be recompiled for each edit you make.
+
+This is enabled by default when using build_runner, and can be disabled using
+the `track-unused-inputs: false` option if you run into issues, so in your
+`build.yaml` it would look like this:
+
+```yaml
+targets:
+  $default:
+    build_web_compilers:ddc:
+      options:
+        track-unused-inputs: false
+```
+
+When using the builder programatically it is disabled by default and can be
+enabled by passing `trackUnusedInputs: true` to the `DevCompilerBuilder`
+constructor.
+
+## 2.4.1
+
+Make the required assets for DDC applications configurable in the
+`bootstrapDdc` method instead of hard coded. This allows custom integrations
+like flutter web to not require the same assets, or require additional custom
+assets.
+
+## 2.4.0
+
+### New Feature: Better --build-filter support for building a single test.
+
+You can now build a basic app or test in isolation by only requesting the
+`*.dart.js` file using a build filter, for example adding this argument to any
+build_runner command would build the `web/main.dart` app only:
+`--build-filter=web/main.dart.js`.
+
+For tests you will need to specify the bootstrapped test file, so:
+`--build-filter=test/hello_world.dart.browser_test.dart.js`.
+
+Previously you also had to explicitly require the SDK resources like:
+`--build-filter="package:build_web_compilers/**.js"` or similar.
+
+**Note**: If your app relies on any non-Dart generated files you will likely
+have to ask for those explicitly as well with additinal filters.
+
+## 2.3.0
+
+- Add an option to the DDC bootstrap to skip the checks around modules that have
+  imports to unsupported SDK libraries like `dart:io` when the module is from a
+  specified package. This is not used in the default build, but is available for
+  custom DDC integrations.
+
+## 2.2.3
+
+- Allow analyzer version 0.38.0.
+
+## 2.2.2
+
+- Re-publish 2.2.0 with proper minimum sdk constraint of >=2.4.0.
+
+## 2.2.1
+
+- Revert of bad 2.2.0 release (had a bad min sdk).
+
+## 2.2.0
+
+- Make `librariesPath` configurable in `DevCompilerBuilder`.
+
+## 2.1.5
+
+- Add pre-emptive support for an upcoming breaking change in ddc
+  around entrypoint naming.
+
+## 2.1.4
+
+- Allow analyzer version 0.37.0.
+
+## 2.1.3
+
+- Improve error message when `dart2js_args` is configured improperly.
+
+## 2.1.2
+
+- Fix hot restart bootstrapping logic for dart scripts that live in a
+  different directory than the html file.
+
+## 2.1.1
+
+- Prepare for source map change from dartdevc, don't modify relative paths in
+  source maps.
+- Fix hot reload bootstrap logic for app entrypoints under lib.
+
+## 2.1.0
+
+- Make `platformSdk`, `sdkKernelPath`, and `platform` configurable in
+  `DevCompilerBuilder`.
+
+## 2.0.2
+
+- Prepare for the next sdk release, which changes what the uris look like for
+  non-package sources, and breaks our existing hot restart logic.
+
+## 2.0.1
+
+- Fix issue #2269, which could cause applications to fail to properly bootstrap.
+- Skip compiling modules with ddc when the primary source isn't the primary
+  input (only shows up in non-lazy builds - essentially just tests).
+
+## 2.0.0
+
+### Major Update - Switch to use the common front end.
+
+In this release, the Dart front end for the `dev_compiler` is changing from the
+[analyzer] to the common [front end][front_end]. This should unify error
+messages and general consistency across platforms, as this was one of the last
+compilers left still using the analyzer as a front end.
+
+While this is intended to be a transparent change, it is likely that there will
+be unintended differences. Please [file issues][issue tracker] if you experience
+something that seems broken or not working as intended.
+
+### Major Update - Auto-detection of web support for applications
+
+Previously, all files with a `main` that were matched by the input globs would
+attempt to compile for the web. This caused an issue when there were non-web
+applications in the default directories, which specifically happens a lot in the
+`test` directory. Resolving this required custom globs in `build.yaml` files.
+
+Two changes were made to help handle this issue more gracefully, in a way that
+often doesn't require custom `build.yaml` files any more.
+
+- Before compiling any app, build_web_compilers will check that all its
+  transitive modules are compatible with the web (based on their `dart:`
+  imports).
+  - Today we will log a warning message for any app that isn't compatible, as
+    ultimately it is most efficient to exclude these using globs in your
+    `build.yaml` than waiting for us to detect it. This may change based on
+    feedback.
+- Changed the default glob for the `test` directory to only include the
+  `test/**.dart.browser_test.dart` files (other dirs remain unchanged).
+  - Note that as a result of this, serving the `test` dir and running tests that
+    way no longer works, as those entrypoints won't be compiled. You would need
+    to configure the `generate_for` to explicitly include `test/**_test.dart`
+    to restore that functionality (we will continue pushing on this in the
+    future though to restore similar functionality).
+  - Also note that the `build_test` package will now output
+    `<platform>_test.dart` files based on your [TestOn] annotations, so using
+    those where possible will help reduce build platform support warnings as
+    well.
+
+### Additional Notes
+
+- Update to run DDC in kernel mode, and consume kernel outlines instead of
+  analyzer summaries.
+- Skip trying to compile apps that import known incompatible libraries.
+- Increased the upper bound for `package:analyzer` to `<0.37.0`.
+- Removed support for `build_root_app_summary` configuration option.
+- Combine the `ddc_kernel` and `ddc` workers under a single name (`ddc`).
+- By default only `.dart.browser_test.dart` files will be compiled under the
+  `test` directory, instead of all `_test.dart` files.
+  - If you used the previous test debugging workflow in the browser you can
+    restore the old behavior with something like the following in your
+    build.yaml:
+
+```yaml
+targets:
+  $default:
+    builders:
+      build_web_compilers|entrypoint:
+        generate_for:
+        - test/**_test.dart
+        - web/**.dart
+```
+
+- Added the `use-incremental-compiler` option for the `build_web_compilers:ddc`
+  builder. This is enabled by default but can be disabled if running into build
+  issues by setting it to `false` globally:
+
+```yaml
+global_options:
+  build_web_compilers:ddc:
+    options:
+      use-incremental-compiler: false
+```
+
+- This package now makes its own copy of the `dart_sdk.js` and `require.js`
+  files, which it deletes during production builds.
+  - In a future build_runner release we will be deleting the entire `$sdk`
+    package to resolve some issues with build output bloat.
+  - If you are using `dartdevc` as your production compiler, you will need to
+    disable the cleanup builder in `build.yaml` (globally) like this:
+
+```yaml
+global_options:
+  build_web_compilers|sdk_js_cleanup:
+    release_options:
+      enabled: false
+```
+
+[analyzer]: https://pub.dev/packages/analyzer
+[front_end]: https://pub.dev/packages/front_end
+[issue tracker]: https://github.com/dart-lang/build/issues/new
+[TestOn]: https://pub.dev/documentation/test/latest/#restricting-tests-to-certain-platforms
+
+## 1.2.2
+
+- Allow build_config 0.4.x.
+
+## 1.2.1
+
+- Allow analyzer version 0.36.x.
+
+## 1.2.0
+
+- Add a marker to inject code before the application main method is called.
+- During a hot restart we will now clear all statics before re-invoking main.
+
+## 1.1.0
+
+- Output a `.digests` file which contains transitive digests for an entrypoint.
+
+## 1.0.2
+
+- Improved the time tracking for ddc actions by not reporting time spent waiting
+  for a worker to be available.
+
+## 1.0.1
+
+- Increased the upper bound for `package:analyzer` to `<0.36.0`.
+
+## 1.0.0
+
+- Removed the `enable_sync_async` and `ignore_cast_failures` options for the
+  `build_web_compilers|entrypoint` builder. These will no longer have any effect
+  and will give a build time warning if you try to use them.
+
+## 0.4.4+3
+
+- Increased the upper bound for `package:analyzer` to `<0.35.0`.
+
+## 0.4.4+2
+
+- Support `package:analyzer` version `0.33.x`.
+
+## 0.4.4+1
+
+- Support `package:build_modules` version `1.x.x`.
+
+## 0.4.4
+
+- Track performance of different builder stages.
+
+## 0.4.3+1
+
+- Removed dependency on cli_util.
+- Fix error in require.js error handling code
+
+## 0.4.3
+
+- Only call `window.postMessage` during initialization if the current context
+  is a `Window`.
+- Fixed an error while showing stack traces for DDC generated scripts
+  when `<base>` tag is used.
+- Value of `<base href="/.../">` tag should start and end with a `/` to be
+  used as the base url for require js.
+- Added more javascript code to dev bootstrap for hot-reloading support
+- Support the latest build_modules.
+
+## 0.4.2+2
+
+- Add magic comment marker for build_runner to know where to inject
+  live-reloading client code. This is only present when using the `dartdevc`
+  compiler. (reapplied)
+
+## 0.4.2+1
+
+- Restore `new` keyword for a working release on Dart 1 VM.
+
+## 0.4.2
+
+- Add magic comment marker for build_runner to know where to inject
+  live-reloading client code. This is only present when using the `dartdevc`
+  compiler.
+- Release broken on Dart 1 VM.
+
+## 0.4.1
+
+- Support the latest build_modules, with updated dart2js support so that it can
+  do multiple builds concurrently and will restart workers periodically to
+  mitigate the effects of dart-lang/sdk#33708.
+- Improvements to reduce the memory usage of the dart2js builder, so that
+  transitive dependency information can be garbage collected before the dart2js
+  compile is completed.
+- Increased the upper bound for the sdk to `<3.0.0`.
+
+## 0.4.0+5
+
+- Fixed an issue where subdirectories with hyphens in the name weren't
+  bootstrapped properly in dartdevc.
+
+## 0.4.0+4
+
+- Expand support for `package:build_config` to include version `0.3.x`.
+
+## 0.4.0+3
+
+- Expand support for `package:archive` to include version `2.x.x`.
+
+## 0.4.0+2
+
+- Fix a dart2 error.
+
+## 0.4.0+1
+
+- Support `package:analyzer` `0.32.0`.
+
+## 0.4.0
+
+- Changed the default for `enable_sync_async` to `true` for the
+  `build_web_compilers|entrypoint` builder.
+- Changed the default for `ignore_cast_failures` to `false` for the
+  `build_web_compilers|entrypoint` builder.
+
+## 0.3.8
+
+- Remove `.dart` sources and `.js.map` files from the output directory in
+  release mode.
+- Clean up `.tar.gz` outputs produced by `Dart2Js.`
+- Don't output extra `Dart2Js` outputs other than deferred part files.
+- Fixed a logical error in `dartdevc` compiler to detect correct base url for
+  require js.
+- Added a `enable_sync_async` option to the `build_web_compilers|entrypoint`
+  builder, which defaults to `false`.
+
+## 0.3.7+3
+
+- The `dartdevc` compiler now respects the `<base href="/....">` tags and will
+  set them as the base url for require js.
+
+## 0.3.7+2
+
+- Fix sdk stack trace folding in the browser console and package:test.
+
+## 0.3.7+1
+
+- Add missing dependency on the `pool` package.
+
+## 0.3.7
+
+- Reduce memory usage by requesting (and lazily building) lower level modules
+  first when building for an entrypoint.
+
+## 0.3.6
+
+- Add support for compiling with `dart2js` by default in release mode.
+
+## 0.3.5
+
+- Don't ignore cast failures by default. We expect most code to be clean with
+  cast failures, and the option can be manually enabled with config.
+
+## 0.3.4+2
+
+- Create `.packages` file and use the new frontend with `dart2js`.
+
+## 0.3.4+1
+
+- Use `--use-old-frontend` with `dart2js` as a stopgap until we can add support
+  for `.packages` files.
+
+## 0.3.4
+
+- Added support for dart2js deferred loading.
+- Added support for bootstrapping code in web workers with `dartdevc`.
+
+## 0.3.3
+
+- Added support for `--dump-info` and the `dart2js` compiler. If you pass that
+  argument with `dart2js_args` the `.info.json` file will be copied the output.
+
+## 0.3.2
+
+- Dart2JS now has minification (`--minify`) enabled by default, similar to how
+  it worked in `pub build`. If you are adding custom `dart2js` options, make
+  sure you still keep the `--minify` flag. Here is an example:
+
+```yaml
+targets:
+  $default:
+    builders:
+      build_web_compilers|entrypoint:
+        generate_for:
+        - web/**.dart
+        options:
+          compiler: dart2js
+          dart2js_args:
+          - --fast-startup
+          - --minify
+          - --trust-type-annotations
+          - --trust-primitives
+```
+
+## 0.3.1
+
+- Cast failures will now be ignored in dartdevc by default (these were enabled
+  in the latest sdk), and added an `ignore_cast_failures` option to the
+  `build_web_compilers|entrypoint` builder which you can set to `true` to enable
+  them.
+  - At some point in the future it is expected that the default for this will
+    flip.
+
+## 0.3.0+1
+
+- Fixed an issue with `dart2js` and the `--no-source-maps` flag.
+
+## 0.3.0
+
+### Breaking changes
+
+- Split `ModuleBuilder`, `UnlinkedSummaryBuilder` and `LinkedSummaryBuilder`
+  into a separate `build_modules` package.
+
+## 0.2.1+1
+
+- Support the latest `analyzer` package.
+
+## 0.2.1
+
+- All dart files under `test` are now compiled by default instead of only the
+  `_browser_test.dart` files (minus vm/node test bootstrap files). This means
+  the original tests can be debugged directly (prior to package:test
+  bootstrapping).
+- Updated to `package:build` version `0.12.0`.
+
+## 0.2.0
+
+### New Features
+
+- Added support for `dart2js`. This can be configured using the top level
+  `compiler` option for the `build_web_compilers|entrypoint` builder. The
+  supported options are `dartdevc` (the default) and `dart2js`. Args can be
+  passed to `dart2js` using the `dart2js_args` option. For example:
+
+```yaml
+targets:
+  <my_package>:
+    builders:
+      build_web_compilers|entrypoint:
+        options:
+          compiler: dart2js
+          dart2js_args:
+          - --minify
+```
+
+### Breaking Changes
+
+- Renamed `ddc_bootstrap` builder to `entrypoint`, the exposed class also
+  changed from `DevCompilerBootstrapBuilder` to `WebEntrypointBuilder`.
+- Renamed `jsBootstrapExtension` to `ddcBootstrapExtension` since it is only
+  required when using the dev compiler.
+
+## 0.1.1
+
+- Mark `ddc_bootstrap` builder with `build_to: cache`.
+- Publish as `build_web_compilers`
+
+## 0.1.0
+
+- Add builder factories.
+- Fixed temp dir cleanup bug on windows.
+- Enabled support for running tests in --precompiled mode.
+
+## 0.0.1
+
+- Initial release with support for building analyzer summaries and DDC modules.
diff --git a/build_web_compilers/LICENSE b/build_web_compilers/LICENSE
new file mode 100644
index 0000000..389ce98
--- /dev/null
+++ b/build_web_compilers/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2017, the Dart project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of Google Inc. nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/build_web_compilers/README.md b/build_web_compilers/README.md
new file mode 100644
index 0000000..4667d45
--- /dev/null
+++ b/build_web_compilers/README.md
@@ -0,0 +1,101 @@
+<p align="center">
+  Web compilers for users of <a href="https://pub.dev/packages/build"><code>package:build</code></a>.
+  <br>
+  <a href="https://travis-ci.org/dart-lang/build">
+    <img src="https://travis-ci.org/dart-lang/build.svg?branch=master" alt="Build Status" />
+  </a>
+  <a href="https://github.com/dart-lang/build/labels/package%3A%20build_web_compilers">
+    <img src="https://img.shields.io/github/issues-raw/dart-lang/build/package%3A%20build_web_compilers.svg" alt="Issues related to build_web_compilers" />
+  </a>
+  <a href="https://pub.dev/packages/build_web_compilers">
+    <img src="https://img.shields.io/pub/v/build_web_compilers.svg" alt="Pub Package Version" />
+  </a>
+  <a href="https://pub.dev/documentation/build_web_compilers/latest">
+    <img src="https://img.shields.io/badge/dartdocs-latest-blue.svg" alt="Latest Dartdocs" />
+  </a>
+  <a href="https://gitter.im/dart-lang/build">
+    <img src="https://badges.gitter.im/dart-lang/build.svg" alt="Join the chat on Gitter" />
+  </a>
+</p>
+
+* [Installation](#installation)
+* [Usage](#usage)
+* [Configuration](#configuration)
+* [Manual Usage](#manual-usage)
+
+## Installation
+
+This package is intended to be used as a [development dependency][] for users
+of [`package:build`][] who want to run code in a browser. Simply add the
+following to your `pubspec.yaml`:
+
+```yaml
+dev_dependencies:
+  build_web_compilers:
+```
+
+## Usage
+
+If you are using the autogenerated build script (going through
+`pub run build_runner <command>` instead of handwriting a `build.dart` file),
+then all you need is the `dev_dependency` listed above.
+
+## Configuration
+
+By default, the `dartdevc` compiler will be used, which is the Dart Development
+Compiler.
+
+If you would like to opt into `dart2js` you will need to add a `build.yaml`
+file, which should look roughly like the following:
+
+```yaml
+targets:
+  $default:
+    builders:
+      build_web_compilers|entrypoint:
+        # These are globs for the entrypoints you want to compile.
+        generate_for:
+        - test/**.browser_test.dart
+        - web/**.dart
+        options:
+          compiler: dart2js
+          # List any dart2js specific args here, or omit it.
+          dart2js_args:
+          - -O2
+```
+
+## Manual Usage
+
+If you are using a custom build script, you will need to add the following
+builder applications to what you already have, almost certainly at the end of
+the list (unless you need to post-process the js files).
+
+```dart
+[
+    apply(
+        'build_web_compilers|ddc',
+        [
+        (_) => new ModuleBuilder(),
+        (_) => new UnlinkedSummaryBuilder(),
+        (_) => new LinkedSummaryBuilder(),
+        (_) => new DevCompilerBuilder()
+        ],
+        toAllPackages(),
+        // Recommended, but not required. This makes it so only modules that are
+        // imported by entrypoints get compiled.
+        isOptional: true,
+        hideOutput: true),
+    apply('build_web_compilers|entrypoint',
+        // You can also use `WebCompiler.Dart2Js`. If you don't care about
+        // dartdevc at all you may also omit the previous builder application
+        // entirely.
+        [(_) => new WebEntrypointBuilder(WebCompiler.DartDevc)], toRoot(),
+        hideOutput: true,
+        // These globs should match your entrypoints only.
+        defaultGenerateFor: const InputSet(
+            include: const ['web/**', 'test/**.browser_test.dart'])),
+]
+```
+
+[development dependency]: https://dart.dev/tools/pub/dependencies#dev-dependencies
+[`package:build`]: https://pub.dev/packages/build
diff --git a/build_web_compilers/build.yaml b/build_web_compilers/build.yaml
new file mode 100644
index 0000000..c858018
--- /dev/null
+++ b/build_web_compilers/build.yaml
@@ -0,0 +1,149 @@
+targets:
+  $default:
+    builders:
+      build_web_compilers|entrypoint:
+        options:
+          compiler: dart2js
+          dart2js_args:
+            - -O4
+        enabled: true
+        generate_for:
+          - web/stack_trace_mapper.dart
+      build_web_compilers|_stack_trace_mapper_copy:
+        enabled: true
+      build_web_compilers|sdk_js_copy:
+        enabled: true
+      build_web_compilers|sdk_js_cleanup:
+        enabled: true
+builders:
+  sdk_js_copy:
+    import: "package:build_web_compilers/builders.dart"
+    builder_factories:
+      - sdkJsCopyBuilder
+    build_extensions:
+      $lib$:
+        - src/dev_compiler/dart_sdk.js
+        - src/dev_compiler/require.js
+    is_optional: False
+    auto_apply: none
+    applies_builders: ["build_web_compilers|sdk_js_cleanup"]
+    runs_before: ["build_web_compilers|entrypoint"]
+  dart2js_modules:
+    import: "package:build_web_compilers/builders.dart"
+    builder_factories:
+      - dart2jsMetaModuleBuilder
+      - dart2jsMetaModuleCleanBuilder
+      - dart2jsModuleBuilder
+    build_extensions:
+      $lib$:
+        - .dart2js.meta_module.raw
+        - .dart2js.meta_module.clean
+      .dart:
+        - .dart2js.module
+    is_optional: True
+    auto_apply: none
+    required_inputs: [".dart", ".module.library"]
+    applies_builders: ["build_modules|module_cleanup"]
+  ddc_modules:
+    import: "package:build_web_compilers/builders.dart"
+    builder_factories:
+      - ddcMetaModuleBuilder
+      - ddcMetaModuleCleanBuilder
+      - ddcModuleBuilder
+    build_extensions:
+      $lib$:
+        - .ddc.meta_module.raw
+        - .ddc.meta_module.clean
+      .dart:
+        - .ddc.module
+    is_optional: True
+    auto_apply: none
+    required_inputs: [".dart", ".module.library"]
+    applies_builders: ["build_modules|module_cleanup"]
+  ddc:
+    import: "package:build_web_compilers/builders.dart"
+    builder_factories:
+      - ddcKernelBuilder
+      - ddcBuilder
+    build_extensions:
+      .ddc.module:
+        - .ddc.dill
+        - .ddc.js.errors
+        - .ddc.js
+        - .ddc.js.map
+    is_optional: True
+    auto_apply: all_packages
+    required_inputs:
+      - .ddc.module
+    applies_builders:
+      - build_web_compilers|ddc_modules
+      # This isn't really the best place to apply these, but it is the only
+      # place we can (its the only builder which runs on all packages).
+      - build_web_compilers|dart2js_modules
+      - build_web_compilers|dart_source_cleanup
+  entrypoint:
+    import: "package:build_web_compilers/builders.dart"
+    builder_factories:
+      - webEntrypointBuilder
+    build_extensions:
+      .dart:
+        - .dart.bootstrap.js
+        - .dart.js
+        - .dart.js.map
+        - .dart.js.tar.gz
+        - .digests
+    required_inputs:
+      - .dart
+      - .ddc.js
+      - .ddc.module
+      - .dart2js.module
+    build_to: cache
+    auto_apply: root_package
+    defaults:
+      generate_for:
+        include:
+          - web/**
+          - test/**.dart.browser_test.dart
+          - example/**
+          - benchmark/**
+        exclude:
+          - test/**.node_test.dart
+          - test/**.vm_test.dart
+      options:
+        dart2js_args:
+          - --minify
+      dev_options:
+        dart2js_args:
+          - --enable-asserts
+      release_options:
+        compiler: dart2js
+    applies_builders:
+      - build_web_compilers|dart2js_archive_extractor
+  _stack_trace_mapper_copy:
+    import: "tool/copy_builder.dart"
+    builder_factories:
+        - copyBuilder
+    build_extensions:
+      web/stack_trace_mapper.dart.js:
+        - lib/src/dev_compiler_stack_trace/stack_trace_mapper.dart.js
+    auto_apply: none
+    build_to: source
+post_process_builders:
+  dart2js_archive_extractor:
+    import: "package:build_web_compilers/builders.dart"
+    builder_factory: dart2jsArchiveExtractor
+    defaults:
+      release_options:
+        filter_outputs: true
+  dart_source_cleanup:
+    import: "package:build_web_compilers/builders.dart"
+    builder_factory: dartSourceCleanup
+    defaults:
+      release_options:
+        enabled: true
+  sdk_js_cleanup:
+    import: "package:build_web_compilers/builders.dart"
+    builder_factory: sdkJsCleanupBuilder
+    defaults:
+      release_options:
+        enabled: true
diff --git a/build_web_compilers/dart_test.yaml b/build_web_compilers/dart_test.yaml
new file mode 100644
index 0000000..f2869e5
--- /dev/null
+++ b/build_web_compilers/dart_test.yaml
@@ -0,0 +1 @@
+timeout: 2x
diff --git a/build_web_compilers/lib/build_web_compilers.dart b/build_web_compilers/lib/build_web_compilers.dart
new file mode 100644
index 0000000..95e26a5
--- /dev/null
+++ b/build_web_compilers/lib/build_web_compilers.dart
@@ -0,0 +1,14 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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.
+
+export 'src/archive_extractor.dart' show Dart2JsArchiveExtractor;
+export 'src/dev_compiler_builder.dart'
+    show
+        DevCompilerBuilder,
+        jsModuleErrorsExtension,
+        jsModuleExtension,
+        jsSourceMapExtension;
+export 'src/platforms.dart' show dart2jsPlatform, ddcPlatform;
+export 'src/web_entrypoint_builder.dart'
+    show WebCompiler, WebEntrypointBuilder, ddcBootstrapExtension;
diff --git a/build_web_compilers/lib/builders.dart b/build_web_compilers/lib/builders.dart
new file mode 100644
index 0000000..255fa5d
--- /dev/null
+++ b/build_web_compilers/lib/builders.dart
@@ -0,0 +1,115 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'package:build/build.dart';
+import 'package:build_modules/build_modules.dart';
+import 'package:collection/collection.dart';
+import 'package:path/path.dart' as p;
+
+import 'build_web_compilers.dart';
+import 'src/common.dart';
+import 'src/platforms.dart';
+import 'src/sdk_js_copy_builder.dart';
+
+// Shared entrypoint builder
+Builder webEntrypointBuilder(BuilderOptions options) =>
+    WebEntrypointBuilder.fromOptions(options);
+
+// Ddc related builders
+Builder ddcMetaModuleBuilder(BuilderOptions options) =>
+    MetaModuleBuilder.forOptions(ddcPlatform, options);
+Builder ddcMetaModuleCleanBuilder(_) => MetaModuleCleanBuilder(ddcPlatform);
+Builder ddcModuleBuilder([_]) => ModuleBuilder(ddcPlatform);
+Builder ddcBuilder(BuilderOptions options) {
+  validateOptions(options.config, _supportedOptions, 'build_web_compilers:ddc');
+  _ensureSameDdcOptions(options);
+
+  return DevCompilerBuilder(
+    useIncrementalCompiler: _readUseIncrementalCompilerOption(options),
+    trackUnusedInputs: _readTrackInputsCompilerOption(options),
+    platform: ddcPlatform,
+    environment: _readEnvironmentOption(options),
+    experiments: _readExperimentOption(options),
+  );
+}
+
+const ddcKernelExtension = '.ddc.dill';
+Builder ddcKernelBuilder(BuilderOptions options) {
+  validateOptions(options.config, _supportedOptions, 'build_web_compilers:ddc');
+  _ensureSameDdcOptions(options);
+
+  return KernelBuilder(
+      summaryOnly: true,
+      sdkKernelPath: p.url.join('lib', '_internal', 'ddc_sdk.dill'),
+      outputExtension: ddcKernelExtension,
+      platform: ddcPlatform,
+      useIncrementalCompiler: _readUseIncrementalCompilerOption(options),
+      trackUnusedInputs: _readTrackInputsCompilerOption(options),
+      experiments: _readExperimentOption(options));
+}
+
+Builder sdkJsCopyBuilder(_) => SdkJsCopyBuilder();
+PostProcessBuilder sdkJsCleanupBuilder(BuilderOptions options) =>
+    FileDeletingBuilder(
+        ['lib/src/dev_compiler/dart_sdk.js', 'lib/src/dev_compiler/require.js'],
+        isEnabled: options.config['enabled'] as bool ?? false);
+
+// Dart2js related builders
+Builder dart2jsMetaModuleBuilder(BuilderOptions options) =>
+    MetaModuleBuilder.forOptions(dart2jsPlatform, options);
+Builder dart2jsMetaModuleCleanBuilder(_) =>
+    MetaModuleCleanBuilder(dart2jsPlatform);
+Builder dart2jsModuleBuilder([_]) => ModuleBuilder(dart2jsPlatform);
+PostProcessBuilder dart2jsArchiveExtractor(BuilderOptions options) =>
+    Dart2JsArchiveExtractor.fromOptions(options);
+
+// General purpose builders
+PostProcessBuilder dartSourceCleanup(BuilderOptions options) =>
+    (options.config['enabled'] as bool ?? false)
+        ? const FileDeletingBuilder(['.dart', '.js.map'])
+        : const FileDeletingBuilder(['.dart', '.js.map'], isEnabled: false);
+
+/// Throws if it is ever given different options.
+void _ensureSameDdcOptions(BuilderOptions options) {
+  if (_previousDdcConfig != null) {
+    if (!const MapEquality().equals(_previousDdcConfig, options.config)) {
+      throw ArgumentError(
+          'The build_web_compilers:ddc builder must have the same '
+          'configuration in all packages. Saw $_previousDdcConfig and '
+          '${options.config} which are not equal.\n\n '
+          'Please use the `global_options` section in '
+          '`build.yaml` or the `--define` flag to set global options.');
+    }
+  } else {
+    _previousDdcConfig = options.config;
+  }
+}
+
+bool _readUseIncrementalCompilerOption(BuilderOptions options) {
+  return options.config[_useIncrementalCompilerOption] as bool ?? true;
+}
+
+bool _readTrackInputsCompilerOption(BuilderOptions options) {
+  return options.config[_trackUnusedInputsCompilerOption] as bool ?? true;
+}
+
+Map<String, String> _readEnvironmentOption(BuilderOptions options) {
+  return Map.from((options.config[_environmentOption] as Map) ?? {});
+}
+
+List<String> _readExperimentOption(BuilderOptions options) {
+  return List.from((options.config[_experimentOption] as List) ?? []);
+}
+
+Map<String, dynamic> _previousDdcConfig;
+const _useIncrementalCompilerOption = 'use-incremental-compiler';
+const _trackUnusedInputsCompilerOption = 'track-unused-inputs';
+const _environmentOption = 'environment';
+const _experimentOption = 'experiments';
+const _supportedOptions = [
+  _environmentOption,
+  _experimentOption,
+  _useIncrementalCompilerOption,
+  _trackUnusedInputsCompilerOption
+];
diff --git a/build_web_compilers/lib/src/archive_extractor.dart b/build_web_compilers/lib/src/archive_extractor.dart
new file mode 100644
index 0000000..1506710
--- /dev/null
+++ b/build_web_compilers/lib/src/archive_extractor.dart
@@ -0,0 +1,40 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'package:archive/archive.dart';
+import 'package:build/build.dart';
+import 'package:path/path.dart' as p;
+
+import 'web_entrypoint_builder.dart';
+
+class Dart2JsArchiveExtractor implements PostProcessBuilder {
+  /// Whether to only output .js files.
+  ///
+  /// The default in release mode.
+  bool filterOutputs;
+
+  Dart2JsArchiveExtractor() : filterOutputs = false;
+
+  Dart2JsArchiveExtractor.fromOptions(BuilderOptions options)
+      : filterOutputs = options.config['filter_outputs'] as bool ?? false;
+
+  @override
+  final inputExtensions = const [jsEntrypointArchiveExtension];
+
+  @override
+  Future<void> build(PostProcessBuildStep buildStep) async {
+    var bytes = await buildStep.readInputAsBytes();
+    var archive = TarDecoder().decodeBytes(bytes);
+    for (var file in archive.files) {
+      if (filterOutputs && !file.name.endsWith('.js')) continue;
+      var inputId = buildStep.inputId;
+      var id = AssetId(
+          inputId.package, p.url.join(p.url.dirname(inputId.path), file.name));
+      await buildStep.writeAsBytes(id, file.content as List<int>);
+    }
+    buildStep.deletePrimaryInput();
+  }
+}
diff --git a/build_web_compilers/lib/src/common.dart b/build_web_compilers/lib/src/common.dart
new file mode 100644
index 0000000..1916bff
--- /dev/null
+++ b/build_web_compilers/lib/src/common.dart
@@ -0,0 +1,53 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:io';
+
+import 'package:build/build.dart';
+import 'package:path/path.dart' as p;
+import 'package:scratch_space/scratch_space.dart';
+import 'package:build_modules/build_modules.dart';
+
+final defaultAnalysisOptionsId =
+    AssetId('build_modules', 'lib/src/analysis_options.default.yaml');
+
+final sdkDir = p.dirname(p.dirname(Platform.resolvedExecutable));
+
+String defaultAnalysisOptionsArg(ScratchSpace scratchSpace) =>
+    '--options=${scratchSpace.fileFor(defaultAnalysisOptionsId).path}';
+
+// TODO: better solution for a .packages file, today we just create a new one
+// for every kernel build action.
+Future<File> createPackagesFile(Iterable<AssetId> allAssets) async {
+  var allPackages = allAssets.map((id) => id.package).toSet();
+  var packagesFileDir =
+      await Directory.systemTemp.createTemp('kernel_builder_');
+  var packagesFile = File(p.join(packagesFileDir.path, '.packages'));
+  await packagesFile.create();
+  await packagesFile.writeAsString(allPackages
+      .map((pkg) => '$pkg:$multiRootScheme:///packages/$pkg')
+      .join('\r\n'));
+  return packagesFile;
+}
+
+/// Validates that [config] only has the top level keys [supportedOptions].
+///
+/// Throws an [ArgumentError] if not.
+void validateOptions(Map<String, dynamic> config, List<String> supportedOptions,
+    String builderKey,
+    {List<String> deprecatedOptions}) {
+  deprecatedOptions ??= [];
+  var unsupported = config.keys.where(
+      (o) => !supportedOptions.contains(o) && !deprecatedOptions.contains(o));
+  if (unsupported.isNotEmpty) {
+    throw ArgumentError.value(unsupported.join(', '), builderKey,
+        'only $supportedOptions are supported options, but got');
+  }
+  var deprecated = config.keys.where(deprecatedOptions.contains);
+  if (deprecated.isNotEmpty) {
+    log.warning('Found deprecated options ${deprecated.join(', ')}. These no '
+        'longer have any effect and should be removed.');
+  }
+}
diff --git a/build_web_compilers/lib/src/dart2js_bootstrap.dart b/build_web_compilers/lib/src/dart2js_bootstrap.dart
new file mode 100644
index 0000000..11ba92c
--- /dev/null
+++ b/build_web_compilers/lib/src/dart2js_bootstrap.dart
@@ -0,0 +1,151 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:archive/archive.dart';
+import 'package:build/build.dart';
+import 'package:build_modules/build_modules.dart';
+import 'package:crypto/crypto.dart';
+import 'package:glob/glob.dart';
+import 'package:path/path.dart' as p;
+import 'package:scratch_space/scratch_space.dart';
+
+import 'platforms.dart';
+import 'web_entrypoint_builder.dart';
+
+/// Compiles an the primary input of [buildStep] with dart2js.
+///
+/// If [skipPlatformCheck] is `true` then all `dart:` imports will be
+/// allowed in all packages.
+Future<void> bootstrapDart2Js(BuildStep buildStep, List<String> dart2JsArgs,
+    {bool skipPlatformCheck}) async {
+  skipPlatformCheck ??= false;
+  var dartEntrypointId = buildStep.inputId;
+  var moduleId =
+      dartEntrypointId.changeExtension(moduleExtension(dart2jsPlatform));
+  var args = <String>[];
+  {
+    var module = Module.fromJson(
+        json.decode(await buildStep.readAsString(moduleId))
+            as Map<String, dynamic>);
+    List<Module> allDeps;
+    try {
+      allDeps = (await module.computeTransitiveDependencies(buildStep,
+          throwIfUnsupported: !skipPlatformCheck))
+        ..add(module);
+    } on UnsupportedModules catch (e) {
+      var librariesString = (await e.exactLibraries(buildStep).toList())
+          .map((lib) => AssetId(lib.id.package,
+              lib.id.path.replaceFirst(moduleLibraryExtension, '.dart')))
+          .join('\n');
+      log.warning('''
+Skipping compiling ${buildStep.inputId} with dart2js because some of its
+transitive libraries have sdk dependencies that not supported on this platform:
+
+$librariesString
+
+https://github.com/dart-lang/build/blob/master/docs/faq.md#how-can-i-resolve-skipped-compiling-warnings
+''');
+      return;
+    }
+
+    var scratchSpace = await buildStep.fetchResource(scratchSpaceResource);
+    var allSrcs = allDeps.expand((module) => module.sources);
+    await scratchSpace.ensureAssets(allSrcs, buildStep);
+    var packageFile =
+        await _createPackageFile(allSrcs, buildStep, scratchSpace);
+
+    var dartPath = dartEntrypointId.path.startsWith('lib/')
+        ? 'package:${dartEntrypointId.package}/'
+            '${dartEntrypointId.path.substring('lib/'.length)}'
+        : dartEntrypointId.path;
+    var jsOutputPath =
+        '${p.withoutExtension(dartPath.replaceFirst('package:', 'packages/'))}'
+        '$jsEntrypointExtension';
+    args = dart2JsArgs.toList()
+      ..addAll([
+        '--packages=$packageFile',
+        '-o$jsOutputPath',
+        dartPath,
+      ]);
+  }
+
+  var dart2js = await buildStep.fetchResource(dart2JsWorkerResource);
+  var result = await dart2js.compile(args);
+  var jsOutputId = dartEntrypointId.changeExtension(jsEntrypointExtension);
+  var jsOutputFile = scratchSpace.fileFor(jsOutputId);
+  if (result.succeeded && await jsOutputFile.exists()) {
+    log.info(result.output);
+    var rootDir = p.dirname(jsOutputFile.path);
+    var dartFile = p.basename(dartEntrypointId.path);
+    var fileGlob = Glob('$dartFile.js*');
+    var archive = Archive();
+    await for (var jsFile in fileGlob.list(root: rootDir)) {
+      if (jsFile.path.endsWith(jsEntrypointExtension) ||
+          jsFile.path.endsWith(jsEntrypointSourceMapExtension)) {
+        // These are explicitly output, and are not part of the archive.
+        continue;
+      }
+      if (jsFile is File) {
+        var fileName = p.relative(jsFile.path, from: rootDir);
+        var fileStats = await jsFile.stat();
+        archive.addFile(
+            ArchiveFile(fileName, fileStats.size, await jsFile.readAsBytes())
+              ..mode = fileStats.mode
+              ..lastModTime = fileStats.modified.millisecondsSinceEpoch);
+      }
+    }
+    if (archive.isNotEmpty) {
+      var archiveId =
+          dartEntrypointId.changeExtension(jsEntrypointArchiveExtension);
+      await buildStep.writeAsBytes(archiveId, TarEncoder().encode(archive));
+    }
+
+    // Explicitly write out the original js file and sourcemap - we can't output
+    // these as part of the archive because they already have asset nodes.
+    await scratchSpace.copyOutput(jsOutputId, buildStep);
+    var jsSourceMapId =
+        dartEntrypointId.changeExtension(jsEntrypointSourceMapExtension);
+    await _copyIfExists(jsSourceMapId, scratchSpace, buildStep);
+  } else {
+    log.severe(result.output);
+  }
+}
+
+Future<void> _copyIfExists(
+    AssetId id, ScratchSpace scratchSpace, AssetWriter writer) async {
+  var file = scratchSpace.fileFor(id);
+  if (await file.exists()) {
+    await scratchSpace.copyOutput(id, writer);
+  }
+}
+
+/// Creates a `.packages` file unique to this entrypoint at the root of the
+/// scratch space and returns it's filename.
+///
+/// Since mulitple invocations of Dart2Js will share a scratch space and we only
+/// know the set of packages involved the current entrypoint we can't construct
+/// a `.packages` file that will work for all invocations of Dart2Js so a unique
+/// file is created for every entrypoint that is run.
+///
+/// The filename is based off the MD5 hash of the asset path so that files are
+/// unique regarless of situations like `web/foo/bar.dart` vs
+/// `web/foo-bar.dart`.
+Future<String> _createPackageFile(Iterable<AssetId> inputSources,
+    BuildStep buildStep, ScratchSpace scratchSpace) async {
+  var inputUri = buildStep.inputId.uri;
+  var packageFileName =
+      '.package-${md5.convert(inputUri.toString().codeUnits)}';
+  var packagesFile =
+      scratchSpace.fileFor(AssetId(buildStep.inputId.package, packageFileName));
+  var packageNames = inputSources.map((s) => s.package).toSet();
+  var packagesFileContent =
+      packageNames.map((n) => '$n:packages/$n/').join('\n');
+  await packagesFile
+      .writeAsString('# Generated for $inputUri\n$packagesFileContent');
+  return packageFileName;
+}
diff --git a/build_web_compilers/lib/src/ddc_names.dart b/build_web_compilers/lib/src/ddc_names.dart
new file mode 100644
index 0000000..6a4e987
--- /dev/null
+++ b/build_web_compilers/lib/src/ddc_names.dart
@@ -0,0 +1,112 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'package:path/path.dart' as p;
+
+/// Transforms a path to a valid JS identifier.
+///
+/// This logic must be synchronized with [pathToJSIdentifier] in DDC at:
+/// pkg/dev_compiler/lib/src/compiler/module_builder.dart
+///
+/// For backwards compatibility, if this pattern is changed,
+/// dev_compiler_bootstrap.dart must be updated to accept both old and new
+/// patterns.
+String pathToJSIdentifier(String path) {
+  path = p.normalize(path);
+  if (path.startsWith('/') || path.startsWith('\\')) {
+    path = path.substring(1, path.length);
+  }
+  return toJSIdentifier(path
+      .replaceAll('\\', '__')
+      .replaceAll('/', '__')
+      .replaceAll('..', '__')
+      .replaceAll('-', '_'));
+}
+
+/// Escape [name] to make it into a valid identifier.
+String toJSIdentifier(String name) {
+  if (name.isEmpty) return r'$';
+
+  // Escape any invalid characters
+  StringBuffer buffer;
+  for (var i = 0; i < name.length; i++) {
+    var ch = name[i];
+    var needsEscape = ch == r'$' || _invalidCharInIdentifier.hasMatch(ch);
+    if (needsEscape && buffer == null) {
+      buffer = StringBuffer(name.substring(0, i));
+    }
+    if (buffer != null) {
+      buffer.write(needsEscape ? '\$${ch.codeUnits.join("")}' : ch);
+    }
+  }
+
+  var result = buffer != null ? '$buffer' : name;
+  // Ensure the identifier first character is not numeric and that the whole
+  // identifier is not a keyword.
+  if (result.startsWith(RegExp('[0-9]')) || invalidVariableName(result)) {
+    return '\$$result';
+  }
+  return result;
+}
+
+/// Returns true for invalid JS variable names, such as keywords.
+/// Also handles invalid variable names in strict mode, like "arguments".
+bool invalidVariableName(String keyword, {bool strictMode = true}) {
+  switch (keyword) {
+    // http://www.ecma-international.org/ecma-262/6.0/#sec-future-reserved-words
+    case 'await':
+    case 'break':
+    case 'case':
+    case 'catch':
+    case 'class':
+    case 'const':
+    case 'continue':
+    case 'debugger':
+    case 'default':
+    case 'delete':
+    case 'do':
+    case 'else':
+    case 'enum':
+    case 'export':
+    case 'extends':
+    case 'finally':
+    case 'for':
+    case 'function':
+    case 'if':
+    case 'import':
+    case 'in':
+    case 'instanceof':
+    case 'let':
+    case 'new':
+    case 'return':
+    case 'super':
+    case 'switch':
+    case 'this':
+    case 'throw':
+    case 'try':
+    case 'typeof':
+    case 'var':
+    case 'void':
+    case 'while':
+    case 'with':
+      return true;
+    case 'arguments':
+    case 'eval':
+    // http://www.ecma-international.org/ecma-262/6.0/#sec-future-reserved-words
+    // http://www.ecma-international.org/ecma-262/6.0/#sec-identifiers-static-semantics-early-errors
+    case 'implements':
+    case 'interface':
+    case 'package':
+    case 'private':
+    case 'protected':
+    case 'public':
+    case 'static':
+    case 'yield':
+      return strictMode;
+  }
+  return false;
+}
+
+// Invalid characters for identifiers, which would need to be escaped.
+final _invalidCharInIdentifier = RegExp(r'[^A-Za-z_$0-9]');
diff --git a/build_web_compilers/lib/src/dev_compiler_bootstrap.dart b/build_web_compilers/lib/src/dev_compiler_bootstrap.dart
new file mode 100644
index 0000000..2845d45
--- /dev/null
+++ b/build_web_compilers/lib/src/dev_compiler_bootstrap.dart
@@ -0,0 +1,530 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:collection';
+import 'dart:convert';
+
+import 'package:build/build.dart';
+import 'package:build_modules/build_modules.dart';
+import 'package:meta/meta.dart';
+import 'package:path/path.dart' as _p; // ignore: library_prefixes
+import 'package:pool/pool.dart';
+
+import 'ddc_names.dart';
+import 'dev_compiler_builder.dart';
+import 'platforms.dart';
+import 'web_entrypoint_builder.dart';
+
+/// Alias `_p.url` to `p`.
+_p.Context get _context => _p.url;
+
+var _modulePartialExtension = _context.withoutExtension(jsModuleExtension);
+
+/// Bootstraps a ddc application, creating the main entrypoint as well as the
+/// bootstrap and digest entrypoints.
+///
+/// If [skipPlatformCheck] is `true` then all `dart:` imports will be
+/// allowed in all packages.
+///
+/// Deprecated: If [skipPlatformCheckPackages] is provided then any dart:
+/// imports will be allowed in the specified packages.
+///
+/// If [requiredAssets] is provided then this will ensure those assets are
+/// available to the app by making them inputs of this build action.
+Future<void> bootstrapDdc(
+  BuildStep buildStep, {
+  DartPlatform platform,
+  bool skipPlatformCheck = false,
+  @deprecated Set<String> skipPlatformCheckPackages = const {},
+  Iterable<AssetId> requiredAssets,
+}) async {
+  requiredAssets ??= [];
+  skipPlatformCheck ??= false;
+  // Ensures that the sdk resources are built and available.
+  await _ensureResources(buildStep, requiredAssets);
+
+  var dartEntrypointId = buildStep.inputId;
+  var moduleId = buildStep.inputId
+      .changeExtension(moduleExtension(platform ?? ddcPlatform));
+  var module = Module.fromJson(json
+      .decode(await buildStep.readAsString(moduleId)) as Map<String, dynamic>);
+
+  // First, ensure all transitive modules are built.
+  List<AssetId> transitiveJsModules;
+  try {
+    transitiveJsModules = await _ensureTransitiveJsModules(module, buildStep,
+        skipPlatformCheck: skipPlatformCheck,
+        skipPlatformCheckPackages: skipPlatformCheckPackages);
+  } on UnsupportedModules catch (e) {
+    var librariesString = (await e.exactLibraries(buildStep).toList())
+        .map((lib) => AssetId(lib.id.package,
+            lib.id.path.replaceFirst(moduleLibraryExtension, '.dart')))
+        .join('\n');
+    log.warning('''
+Skipping compiling ${buildStep.inputId} with ddc because some of its
+transitive libraries have sdk dependencies that not supported on this platform:
+
+$librariesString
+
+https://github.com/dart-lang/build/blob/master/docs/faq.md#how-can-i-resolve-skipped-compiling-warnings
+''');
+    return;
+  }
+  var jsId = module.primarySource.changeExtension(jsModuleExtension);
+  var appModuleName = ddcModuleName(jsId);
+  var appDigestsOutput =
+      dartEntrypointId.changeExtension(digestsEntrypointExtension);
+
+  // The name of the entrypoint dart library within the entrypoint JS module.
+  //
+  // This is used to invoke `main()` from within the bootstrap script.
+  //
+  // TODO(jakemac53): Sane module name creation, this only works in the most
+  // basic of cases.
+  //
+  // See https://github.com/dart-lang/sdk/issues/27262 for the root issue
+  // which will allow us to not rely on the naming schemes that dartdevc uses
+  // internally, but instead specify our own.
+  var oldAppModuleScope = toJSIdentifier(
+      _context.withoutExtension(_context.basename(buildStep.inputId.path)));
+
+  // Like above but with a package-relative entrypoint.
+  var appModuleScope =
+      pathToJSIdentifier(_context.withoutExtension(buildStep.inputId.path));
+
+  // Map from module name to module path for custom modules.
+  var modulePaths = SplayTreeMap.of(
+      {'dart_sdk': r'packages/build_web_compilers/src/dev_compiler/dart_sdk'});
+  for (var jsId in transitiveJsModules) {
+    // Strip out the top level dir from the path for any module, and set it to
+    // `packages/` for lib modules. We set baseUrl to `/` to simplify things,
+    // and we only allow you to serve top level directories.
+    var moduleName = ddcModuleName(jsId);
+    modulePaths[moduleName] = _context.withoutExtension(
+        jsId.path.startsWith('lib')
+            ? '$moduleName$jsModuleExtension'
+            : _context.joinAll(_context.split(jsId.path).skip(1)));
+  }
+
+  var bootstrapId = dartEntrypointId.changeExtension(ddcBootstrapExtension);
+  var bootstrapModuleName = _context.withoutExtension(_context.relative(
+      bootstrapId.path,
+      from: _context.dirname(dartEntrypointId.path)));
+
+  var dartEntrypointParts = _context.split(dartEntrypointId.path);
+  var entrypointLibraryName = _context.joinAll([
+    // Convert to a package: uri for files under lib.
+    if (dartEntrypointParts.first == 'lib')
+      'package:${module.primarySource.package}',
+    // Strip top-level directory from the path.
+    ...dartEntrypointParts.skip(1),
+  ]);
+
+  var bootstrapContent =
+      StringBuffer('$_entrypointExtensionMarker\n(function() {\n')
+        ..write(_dartLoaderSetup(
+            modulePaths,
+            _p.url.relative(appDigestsOutput.path,
+                from: _p.url.dirname(bootstrapId.path))))
+        ..write(_requireJsConfig)
+        ..write(_appBootstrap(bootstrapModuleName, appModuleName,
+            appModuleScope, entrypointLibraryName,
+            oldModuleScope: oldAppModuleScope));
+
+  await buildStep.writeAsString(bootstrapId, bootstrapContent.toString());
+
+  var entrypointJsContent = _entryPointJs(bootstrapModuleName);
+  await buildStep.writeAsString(
+      dartEntrypointId.changeExtension(jsEntrypointExtension),
+      entrypointJsContent);
+
+  // Output the digests for transitive modules.
+  // These can be consumed for hot reloads.
+  var moduleDigests = <String, String>{
+    for (var jsId in transitiveJsModules)
+      _moduleDigestKey(jsId): '${await buildStep.digest(jsId)}',
+  };
+  await buildStep.writeAsString(appDigestsOutput, jsonEncode(moduleDigests));
+}
+
+String _moduleDigestKey(AssetId jsId) =>
+    '${ddcModuleName(jsId)}$jsModuleExtension';
+
+final _lazyBuildPool = Pool(16);
+
+/// Ensures that all transitive js modules for [module] are available and built.
+///
+/// Throws an [UnsupportedModules] exception if there are any
+/// unsupported modules.
+Future<List<AssetId>> _ensureTransitiveJsModules(
+    Module module, BuildStep buildStep,
+    {@required bool skipPlatformCheck,
+    @required Set<String> skipPlatformCheckPackages}) async {
+  // Collect all the modules this module depends on, plus this module.
+  var transitiveDeps = await module.computeTransitiveDependencies(buildStep,
+      throwIfUnsupported: !skipPlatformCheck,
+      // ignore: deprecated_member_use
+      skipPlatformCheckPackages: skipPlatformCheckPackages);
+
+  var jsModules = [
+    module.primarySource.changeExtension(jsModuleExtension),
+    for (var dep in transitiveDeps)
+      dep.primarySource.changeExtension(jsModuleExtension),
+  ];
+  // Check that each module is readable, and warn otherwise.
+  await Future.wait(jsModules.map((jsId) async {
+    if (await _lazyBuildPool.withResource(() => buildStep.canRead(jsId))) {
+      return;
+    }
+    var errorsId = jsId.addExtension('.errors');
+    await buildStep.canRead(errorsId);
+    log.warning('Unable to read $jsId, check your console or the '
+        '`.dart_tool/build/generated/${errorsId.package}/${errorsId.path}` '
+        'log file.');
+  }));
+  return jsModules;
+}
+
+/// Code that actually imports the [moduleName] module, and calls the
+/// `[moduleScope].main()` function on it.
+///
+/// Also performs other necessary initialization.
+String _appBootstrap(String bootstrapModuleName, String moduleName,
+        String moduleScope, String entrypointLibraryName,
+        {String oldModuleScope}) =>
+    '''
+define("$bootstrapModuleName", ["$moduleName", "dart_sdk"], function(app, dart_sdk) {
+  dart_sdk.dart.setStartAsyncSynchronously(true);
+  dart_sdk._isolate_helper.startRootIsolate(() => {}, []);
+  $_initializeTools
+  $_mainExtensionMarker
+  (app.$moduleScope || app.$oldModuleScope).main();
+  var bootstrap = {
+      hot\$onChildUpdate: function(childName, child) {
+        // Special handling for the multi-root scheme uris. We need to strip
+        // out the scheme and the top level directory, to match the source path
+        // that chrome sees.
+        if (childName.startsWith('$multiRootScheme:///')) {
+          childName = childName.substring('$multiRootScheme:///'.length);
+          var firstSlash = childName.indexOf('/');
+          if (firstSlash == -1) return false;
+          childName = childName.substring(firstSlash + 1);
+        }
+        if (childName === "$entrypointLibraryName") {
+          // Clear static caches.
+          dart_sdk.dart.hotRestart();
+          child.main();
+          return true;
+        }
+      }
+    }
+  dart_sdk.dart.trackLibraries("$bootstrapModuleName", {
+    "$bootstrapModuleName": bootstrap
+  }, '');
+  return {
+    bootstrap: bootstrap
+  };
+});
+})();
+''';
+
+/// The actual entrypoint JS file which injects all the necessary scripts to
+/// run the app.
+String _entryPointJs(String bootstrapModuleName) => '''
+(function() {
+  $_currentDirectoryScript
+  $_baseUrlScript
+
+  var mapperUri = baseUrl + "packages/build_web_compilers/src/" +
+      "dev_compiler_stack_trace/stack_trace_mapper.dart.js";
+  var requireUri = baseUrl +
+      "packages/build_web_compilers/src/dev_compiler/require.js";
+  var mainUri = _currentDirectory + "$bootstrapModuleName";
+
+  if (typeof document != 'undefined') {
+    var el = document.createElement("script");
+    el.defer = true;
+    el.async = false;
+    el.src = mapperUri;
+    document.head.appendChild(el);
+
+    el = document.createElement("script");
+    el.defer = true;
+    el.async = false;
+    el.src = requireUri;
+    el.setAttribute("data-main", mainUri);
+    document.head.appendChild(el);
+  } else {
+    importScripts(mapperUri, requireUri);
+    require.config({
+      baseUrl: baseUrl,
+    });
+    // TODO: update bootstrap code to take argument - dart-lang/build#1115
+    window = self;
+    require([mainUri + '.js']);
+  }
+})();
+''';
+
+/// JavaScript snippet to determine the directory a script was run from.
+final _currentDirectoryScript = r'''
+var _currentDirectory = (function () {
+  var _url;
+  var lines = new Error().stack.split('\n');
+  function lookupUrl() {
+    if (lines.length > 2) {
+      var match = lines[1].match(/^\s+at (.+):\d+:\d+$/);
+      // Chrome.
+      if (match) return match[1];
+      // Chrome nested eval case.
+      match = lines[1].match(/^\s+at eval [(](.+):\d+:\d+[)]$/);
+      if (match) return match[1];
+      // Edge.
+      match = lines[1].match(/^\s+at.+\((.+):\d+:\d+\)$/);
+      if (match) return match[1];
+      // Firefox.
+      match = lines[0].match(/[<][@](.+):\d+:\d+$/)
+      if (match) return match[1];
+    }
+    // Safari.
+    return lines[0].match(/(.+):\d+:\d+$/)[1];
+  }
+  _url = lookupUrl();
+  var lastSlash = _url.lastIndexOf('/');
+  if (lastSlash == -1) return _url;
+  var currentDirectory = _url.substring(0, lastSlash + 1);
+  return currentDirectory;
+})();
+''';
+
+/// Sets up `window.$dartLoader` based on [modulePaths].
+String _dartLoaderSetup(Map<String, String> modulePaths, String appDigests) =>
+    '''
+$_currentDirectoryScript
+$_baseUrlScript
+let modulePaths = ${const JsonEncoder.withIndent(" ").convert(modulePaths)};
+if(!window.\$dartLoader) {
+   window.\$dartLoader = {
+     appDigests: _currentDirectory + '$appDigests',
+     moduleIdToUrl: new Map(),
+     urlToModuleId: new Map(),
+     rootDirectories: new Array(),
+     // Used in package:build_runner/src/server/build_updates_client/hot_reload_client.dart
+     moduleParentsGraph: new Map(),
+     moduleLoadingErrorCallbacks: new Map(),
+     forceLoadModule: function (moduleName, callback, onError) {
+       // dartdevc only strips the final extension when adding modules to source
+       // maps, so we need to do the same.
+       if (moduleName.endsWith('$_modulePartialExtension')) {
+         moduleName = moduleName.substring(0, moduleName.length - ${_modulePartialExtension.length});
+       }
+       if (typeof onError != 'undefined') {
+         var errorCallbacks = \$dartLoader.moduleLoadingErrorCallbacks;
+         if (!errorCallbacks.has(moduleName)) {
+           errorCallbacks.set(moduleName, new Set());
+         }
+         errorCallbacks.get(moduleName).add(onError);
+       }
+       requirejs.undef(moduleName);
+       requirejs([moduleName], function() {
+         if (typeof onError != 'undefined') {
+           errorCallbacks.get(moduleName).delete(onError);
+         }
+         if (typeof callback != 'undefined') {
+           callback();
+         }
+       });
+     },
+     getModuleLibraries: null, // set up by _initializeTools
+   };
+}
+let customModulePaths = {};
+window.\$dartLoader.rootDirectories.push(window.location.origin + baseUrl);
+for (let moduleName of Object.getOwnPropertyNames(modulePaths)) {
+  let modulePath = modulePaths[moduleName];
+  if (modulePath != moduleName) {
+    customModulePaths[moduleName] = modulePath;
+  }
+  var src = window.location.origin + '/' + modulePath + '.js';
+  if (window.\$dartLoader.moduleIdToUrl.has(moduleName)) {
+    continue;
+  }
+  \$dartLoader.moduleIdToUrl.set(moduleName, src);
+  \$dartLoader.urlToModuleId.set(src, moduleName);
+}
+''';
+
+/// Code to initialize the dev tools formatter, stack trace mapper, and any
+/// other tools.
+///
+/// Posts a message to the window when done.
+final _initializeTools = '''
+$_baseUrlScript
+  dart_sdk._debugger.registerDevtoolsFormatter();
+  \$dartLoader.getModuleLibraries = dart_sdk.dart.getModuleLibraries;
+  if (window.\$dartStackTraceUtility && !window.\$dartStackTraceUtility.ready) {
+    window.\$dartStackTraceUtility.ready = true;
+    let dart = dart_sdk.dart;
+    window.\$dartStackTraceUtility.setSourceMapProvider(
+      function(url) {
+        url = url.replace(baseUrl, '/');
+        var module = window.\$dartLoader.urlToModuleId.get(url);
+        if (!module) return null;
+        return dart.getSourceMap(module);
+      });
+  }
+  if (typeof document != 'undefined') {
+    window.postMessage({ type: "DDC_STATE_CHANGE", state: "start" }, "*");
+  }
+''';
+
+/// Require JS config for ddc.
+///
+/// Sets the base url to `/` so that all modules can be loaded using absolute
+/// paths which simplifies a lot of scenarios.
+///
+/// Sets the timeout for loading modules to infinity (0).
+///
+/// Sets up the custom module paths.
+///
+/// Adds error handler code for require.js which requests a `.errors` file for
+/// any failed module, and logs it to the console.
+final _requireJsConfig = '''
+// Whenever we fail to load a JS module, try to request the corresponding
+// `.errors` file, and log it to the console.
+(function() {
+  var oldOnError = requirejs.onError;
+  requirejs.onError = function(e) {
+    if (e.requireModules) {
+      if (e.message) {
+        // If error occurred on loading dependencies, we need to invalidate ancessor too.
+        var ancesor = e.message.match(/needed by: (.*)/);
+        if (ancesor) {
+          e.requireModules.push(ancesor[1]);
+        }
+      }
+      for (const module of e.requireModules) {
+        var errorCallbacks = \$dartLoader.moduleLoadingErrorCallbacks.get(module);
+        if (errorCallbacks) {
+          for (const callback of errorCallbacks) callback(e);
+          errorCallbacks.clear();
+        }
+      }
+    }
+    if (e.originalError && e.originalError.srcElement) {
+      var xhr = new XMLHttpRequest();
+      xhr.onreadystatechange = function() {
+        if (this.readyState == 4) {
+          var message;
+          if (this.status == 200) {
+            message = this.responseText;
+          } else {
+            message = "Unknown error loading " + e.originalError.srcElement.src;
+          }
+          console.error(message);
+          var errorEvent = new CustomEvent(
+            'dartLoadException', { detail: message });
+          window.dispatchEvent(errorEvent);
+        }
+      };
+      xhr.open("GET", e.originalError.srcElement.src + ".errors", true);
+      xhr.send();
+    }
+    // Also handle errors the normal way.
+    if (oldOnError) oldOnError(e);
+  };
+}());
+
+$_baseUrlScript;
+
+require.config({
+    baseUrl: baseUrl,
+    waitSeconds: 0,
+    paths: customModulePaths
+});
+
+const modulesGraph = new Map();
+function getRegisteredModuleName(moduleMap) {
+  if (\$dartLoader.moduleIdToUrl.has(moduleMap.name + '$_modulePartialExtension')) {
+    return moduleMap.name + '$_modulePartialExtension';
+  }
+  return moduleMap.name;
+}
+requirejs.onResourceLoad = function (context, map, depArray) {
+  const name = getRegisteredModuleName(map);
+  const depNameArray = depArray.map(getRegisteredModuleName);
+  if (modulesGraph.has(name)) {
+    // TODO Move this logic to better place
+    var previousDeps = modulesGraph.get(name);
+    var changed = previousDeps.length != depNameArray.length;
+    changed = changed || depNameArray.some(function(depName) {
+      return !previousDeps.includes(depName);
+    });
+    if (changed) {
+      console.warn("Dependencies graph change for module '" + name + "' detected. " +
+        "Dependencies was [" + previousDeps + "], now [" +  depNameArray.map((depName) => depName) +"]. " +
+        "Page can't be hot-reloaded, firing full page reload.");
+      window.location.reload();
+    }
+  } else {
+    modulesGraph.set(name, []);
+    for (const depName of depNameArray) {
+      if (!\$dartLoader.moduleParentsGraph.has(depName)) {
+        \$dartLoader.moduleParentsGraph.set(depName, []);
+      }
+      \$dartLoader.moduleParentsGraph.get(depName).push(name);
+      modulesGraph.get(name).push(depName);
+    }
+  }
+};
+''';
+
+/// Marker comment used by tools to identify the entrypoint file,
+/// to inject custom code.
+final _entrypointExtensionMarker = '/* ENTRYPOINT_EXTENTION_MARKER */';
+
+/// Marker comment used by tools to identify the main function
+/// to inject custom code.
+final _mainExtensionMarker = '/* MAIN_EXTENSION_MARKER */';
+
+final _baseUrlScript = '''
+var baseUrl = (function () {
+  // Attempt to detect --precompiled mode for tests, and set the base url
+  // appropriately, otherwise set it to '/'.
+  var pathParts = location.pathname.split("/");
+  if (pathParts[0] == "") {
+    pathParts.shift();
+  }
+  if (pathParts.length > 1 && pathParts[1] == "test") {
+    return "/" + pathParts.slice(0, 2).join("/") + "/";
+  }
+  // Attempt to detect base url using <base href> html tag
+  // base href should start and end with "/"
+  if (typeof document !== 'undefined') {
+    var el = document.getElementsByTagName('base');
+    if (el && el[0] && el[0].getAttribute("href") && el[0].getAttribute
+    ("href").startsWith("/") && el[0].getAttribute("href").endsWith("/")){
+      return el[0].getAttribute("href");
+    }
+  }
+  // return default value
+  return "/";
+}());
+''';
+
+/// Ensures that all of [resources] are built successfully, and adds them as
+/// an input dependency to this action.
+///
+/// This also has the effect of ensuring these resources are present whenever
+/// a DDC app is built - reducing the need to explicitly list these files as
+/// build filters.
+Future<void> _ensureResources(
+    BuildStep buildStep, Iterable<AssetId> resources) async {
+  for (var resource in resources) {
+    if (!await buildStep.canRead(resource)) {
+      throw StateError('Unable to locate required sdk resource $resource');
+    }
+  }
+}
diff --git a/build_web_compilers/lib/src/dev_compiler_builder.dart b/build_web_compilers/lib/src/dev_compiler_builder.dart
new file mode 100644
index 0000000..b2809ee
--- /dev/null
+++ b/build_web_compilers/lib/src/dev_compiler_builder.dart
@@ -0,0 +1,294 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:bazel_worker/bazel_worker.dart';
+import 'package:build/build.dart';
+import 'package:build_modules/build_modules.dart';
+import 'package:meta/meta.dart';
+import 'package:path/path.dart' as p;
+import 'package:scratch_space/scratch_space.dart';
+
+import '../builders.dart';
+import 'common.dart';
+import 'errors.dart';
+
+const jsModuleErrorsExtension = '.ddc.js.errors';
+const jsModuleExtension = '.ddc.js';
+const jsSourceMapExtension = '.ddc.js.map';
+
+/// A builder which can output ddc modules!
+class DevCompilerBuilder implements Builder {
+  final bool useIncrementalCompiler;
+
+  final bool trackUnusedInputs;
+
+  final DartPlatform platform;
+
+  /// The sdk kernel file for the current platform.
+  final String sdkKernelPath;
+
+  /// The root directory of the platform's dart SDK.
+  ///
+  /// If not provided, defaults to the directory of
+  /// [Platform.resolvedExecutable].
+  ///
+  /// On flutter this is the path to the root of the flutter_patched_sdk
+  /// directory, which contains the platform kernel files.
+  final String platformSdk;
+
+  /// The absolute path to the libraries file for the current platform.
+  ///
+  /// If not provided, defaults to "lib/libraries.json" in the sdk directory.
+  final String librariesPath;
+
+  /// Environment defines to pass to ddc (as -D variables).
+  final Map<String, String> environment;
+
+  /// Experiments to pass to ddc (as --enable-experiment=<experiment> args).
+  final Iterable<String> experiments;
+
+  DevCompilerBuilder(
+      {bool useIncrementalCompiler,
+      bool trackUnusedInputs,
+      @required this.platform,
+      this.sdkKernelPath,
+      String librariesPath,
+      String platformSdk,
+      Map<String, String> environment,
+      Iterable<String> experiments})
+      : useIncrementalCompiler = useIncrementalCompiler ?? true,
+        platformSdk = platformSdk ?? sdkDir,
+        librariesPath = librariesPath ??
+            p.join(platformSdk ?? sdkDir, 'lib', 'libraries.json'),
+        trackUnusedInputs = trackUnusedInputs ?? false,
+        buildExtensions = {
+          moduleExtension(platform): [
+            jsModuleExtension,
+            jsModuleErrorsExtension,
+            jsSourceMapExtension
+          ],
+        },
+        environment = environment ?? {},
+        experiments = experiments ?? {};
+
+  @override
+  final Map<String, List<String>> buildExtensions;
+
+  @override
+  Future build(BuildStep buildStep) async {
+    var module = Module.fromJson(
+        json.decode(await buildStep.readAsString(buildStep.inputId))
+            as Map<String, dynamic>);
+    // Entrypoints always have a `.module` file for ease of looking them up,
+    // but they might not be the primary source.
+    if (module.primarySource.changeExtension(moduleExtension(platform)) !=
+        buildStep.inputId) {
+      return;
+    }
+
+    Future<void> handleError(e) async {
+      await buildStep.writeAsString(
+          module.primarySource.changeExtension(jsModuleErrorsExtension), '$e');
+      log.severe('$e');
+    }
+
+    try {
+      await _createDevCompilerModule(
+          module,
+          buildStep,
+          useIncrementalCompiler,
+          trackUnusedInputs,
+          platformSdk,
+          sdkKernelPath,
+          librariesPath,
+          environment,
+          experiments);
+    } on DartDevcCompilationException catch (e) {
+      await handleError(e);
+    } on MissingModulesException catch (e) {
+      await handleError(e);
+    }
+  }
+}
+
+/// Compile [module] with the dev compiler.
+Future<void> _createDevCompilerModule(
+    Module module,
+    BuildStep buildStep,
+    bool useIncrementalCompiler,
+    bool trackUnusedInputs,
+    String dartSdk,
+    String sdkKernelPath,
+    String librariesPath,
+    Map<String, String> environment,
+    Iterable<String> experiments,
+    {bool debugMode = true}) async {
+  var transitiveDeps = await buildStep.trackStage('CollectTransitiveDeps',
+      () => module.computeTransitiveDependencies(buildStep));
+  var transitiveKernelDeps = [
+    for (var dep in transitiveDeps)
+      dep.primarySource.changeExtension(ddcKernelExtension)
+  ];
+  var scratchSpace = await buildStep.fetchResource(scratchSpaceResource);
+
+  var allAssetIds = <AssetId>{...module.sources, ...transitiveKernelDeps};
+  await buildStep.trackStage(
+      'EnsureAssets', () => scratchSpace.ensureAssets(allAssetIds, buildStep));
+  var jsId = module.primarySource.changeExtension(jsModuleExtension);
+  var jsOutputFile = scratchSpace.fileFor(jsId);
+  var sdkSummary =
+      p.url.join(dartSdk, sdkKernelPath ?? 'lib/_internal/ddc_sdk.dill');
+
+  // Maps the inputs paths we provide to the ddc worker to asset ids, if
+  // `trackUnusedInputs` is `true`.
+  Map<String, AssetId> kernelInputPathToId;
+  // If `trackUnusedInputs` is `true`, this is the file we will use to
+  // communicate the used inputs with the ddc worker.
+  File usedInputsFile;
+
+  if (trackUnusedInputs) {
+    usedInputsFile = await File(p.join(
+            (await Directory.systemTemp.createTemp('ddk_builder_')).path,
+            'used_inputs.txt'))
+        .create();
+    kernelInputPathToId = {};
+  }
+
+  var packagesFile = await createPackagesFile(allAssetIds);
+  var request = WorkRequest()
+    ..arguments.addAll([
+      '--dart-sdk-summary=$sdkSummary',
+      '--modules=amd',
+      '--no-summarize',
+      '-o',
+      jsOutputFile.path,
+      debugMode ? '--source-map' : '--no-source-map',
+      for (var dep in transitiveDeps) _summaryArg(dep),
+      '--packages=${packagesFile.absolute.uri}',
+      '--module-name=${ddcModuleName(jsId)}',
+      '--multi-root-scheme=$multiRootScheme',
+      '--multi-root=.',
+      '--track-widget-creation',
+      '--inline-source-map',
+      '--libraries-file=${p.toUri(librariesPath)}',
+      if (useIncrementalCompiler) ...[
+        '--reuse-compiler-result',
+        '--use-incremental-compiler',
+      ],
+      if (usedInputsFile != null)
+        '--used-inputs-file=${usedInputsFile.uri.toFilePath()}',
+      for (var source in module.sources) _sourceArg(source),
+      for (var define in environment.entries) '-D${define.key}=${define.value}',
+      for (var experiment in experiments) '--enable-experiment=$experiment',
+    ])
+    ..inputs.add(Input()
+      ..path = sdkSummary
+      ..digest = [0])
+    ..inputs.addAll(await Future.wait(transitiveKernelDeps.map((dep) async {
+      var file = scratchSpace.fileFor(dep);
+      if (kernelInputPathToId != null) {
+        kernelInputPathToId[file.uri.toString()] = dep;
+      }
+      return Input()
+        ..path = file.path
+        ..digest = (await buildStep.digest(dep)).bytes;
+    })));
+
+  try {
+    var driverResource = dartdevkDriverResource;
+    var driver = await buildStep.fetchResource(driverResource);
+    var response = await driver.doWork(request,
+        trackWork: (response) =>
+            buildStep.trackStage('Compile', () => response, isExternal: true));
+
+    // TODO(jakemac53): Fix the ddc worker mode so it always sends back a bad
+    // status code if something failed. Today we just make sure there is an output
+    // JS file to verify it was successful.
+    var message = response.output
+        .replaceAll('${scratchSpace.tempDir.path}/', '')
+        .replaceAll('$multiRootScheme:///', '');
+    if (response.exitCode != EXIT_CODE_OK ||
+        !jsOutputFile.existsSync() ||
+        message.contains('Error:')) {
+      throw DartDevcCompilationException(jsId, message);
+    }
+
+    if (message.isNotEmpty) {
+      log.info('\n$message');
+    }
+
+    // Copy the output back using the buildStep.
+    await scratchSpace.copyOutput(jsId, buildStep);
+    if (debugMode) {
+      // We need to modify the sources in the sourcemap to remove the custom
+      // `multiRootScheme` that we use.
+      var sourceMapId =
+          module.primarySource.changeExtension(jsSourceMapExtension);
+      var file = scratchSpace.fileFor(sourceMapId);
+      var content = await file.readAsString();
+      var json = jsonDecode(content);
+      json['sources'] = fixSourceMapSources((json['sources'] as List).cast());
+      await buildStep.writeAsString(sourceMapId, jsonEncode(json));
+    }
+
+    // Note that we only want to do this on success, we can't trust the unused
+    // inputs if there is a failure.
+    if (usedInputsFile != null) {
+      await reportUnusedKernelInputs(
+          usedInputsFile, transitiveKernelDeps, kernelInputPathToId, buildStep);
+    }
+  } finally {
+    await packagesFile.parent.delete(recursive: true);
+    await usedInputsFile?.parent?.delete(recursive: true);
+  }
+}
+
+/// Returns the `--summary=` argument for a dependency.
+String _summaryArg(Module module) {
+  final kernelAsset = module.primarySource.changeExtension(ddcKernelExtension);
+  final moduleName =
+      ddcModuleName(module.primarySource.changeExtension(jsModuleExtension));
+  return '--summary=${scratchSpace.fileFor(kernelAsset).path}=$moduleName';
+}
+
+/// The url to compile for a source.
+///
+/// Use the package: path for files under lib and the full absolute path for
+/// other files.
+String _sourceArg(AssetId id) {
+  var uri = canonicalUriFor(id);
+  return uri.startsWith('package:') ? uri : '$multiRootScheme:///${id.path}';
+}
+
+/// Copied to `web/stack_trace_mapper.dart`, these need to be kept in sync.
+///
+/// Given a list of [uris] as [String]s from a sourcemap, fixes them up so that
+/// they make sense in a browser context.
+///
+/// - Strips the scheme from the uri
+/// - Strips the top level directory if its not `packages`
+List<String> fixSourceMapSources(List<String> uris) {
+  return uris.map((source) {
+    var uri = Uri.parse(source);
+    // We only want to rewrite multi-root scheme uris.
+    if (uri.scheme.isEmpty) return source;
+    var newSegments = uri.pathSegments.first == 'packages'
+        ? uri.pathSegments
+        : uri.pathSegments.skip(1);
+    return Uri(path: p.url.joinAll(['/'].followedBy(newSegments))).toString();
+  }).toList();
+}
+
+/// The module name according to ddc for [jsId] which represents the real js
+/// module file.
+String ddcModuleName(AssetId jsId) {
+  var jsPath = jsId.path.startsWith('lib/')
+      ? jsId.path.replaceFirst('lib/', 'packages/${jsId.package}/')
+      : jsId.path;
+  return jsPath.substring(0, jsPath.length - jsModuleExtension.length);
+}
diff --git a/build_web_compilers/lib/src/errors.dart b/build_web_compilers/lib/src/errors.dart
new file mode 100644
index 0000000..9c63746
--- /dev/null
+++ b/build_web_compilers/lib/src/errors.dart
@@ -0,0 +1,28 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'package:build/build.dart';
+
+/// An [Exception] that is thrown when a worker returns an error.
+abstract class _WorkerException implements Exception {
+  final AssetId failedAsset;
+
+  final String error;
+
+  /// A message to prepend to [toString] output.
+  String get message;
+
+  _WorkerException(this.failedAsset, this.error);
+
+  @override
+  String toString() => '$message:$failedAsset\n\n$error';
+}
+
+/// An [Exception] that is thrown when dartdevc compilation fails.
+class DartDevcCompilationException extends _WorkerException {
+  @override
+  final String message = 'Error compiling dartdevc module';
+
+  DartDevcCompilationException(AssetId jsId, String error) : super(jsId, error);
+}
diff --git a/build_web_compilers/lib/src/platforms.dart b/build_web_compilers/lib/src/platforms.dart
new file mode 100644
index 0000000..4722b46
--- /dev/null
+++ b/build_web_compilers/lib/src/platforms.dart
@@ -0,0 +1,45 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS 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 'package:build_modules/build_modules.dart';
+
+final ddcPlatform = DartPlatform.register('ddc', [
+  'async',
+  'collection',
+  'convert',
+  'core',
+  'developer',
+  'html',
+  'html_common',
+  'indexed_db',
+  'js',
+  'js_util',
+  'math',
+  'svg',
+  'typed_data',
+  'web_audio',
+  'web_gl',
+  'web_sql',
+  '_internal',
+]);
+
+final dart2jsPlatform = DartPlatform.register('dart2js', [
+  'async',
+  'collection',
+  'convert',
+  'core',
+  'developer',
+  'html',
+  'html_common',
+  'indexed_db',
+  'js',
+  'js_util',
+  'math',
+  'svg',
+  'typed_data',
+  'web_audio',
+  'web_gl',
+  'web_sql',
+  '_internal',
+]);
diff --git a/build_web_compilers/lib/src/sdk_js_copy_builder.dart b/build_web_compilers/lib/src/sdk_js_copy_builder.dart
new file mode 100644
index 0000000..a2579b1
--- /dev/null
+++ b/build_web_compilers/lib/src/sdk_js_copy_builder.dart
@@ -0,0 +1,42 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:io';
+
+import 'package:build/build.dart';
+import 'package:path/path.dart' as p;
+
+import 'common.dart';
+
+/// Copies the dart_sdk.js and require.js files from the sdk itself, into the
+/// build_web_compilers package at `lib/dart_sdk.js` and `lib/require.js`.
+class SdkJsCopyBuilder implements Builder {
+  @override
+  final buildExtensions = {
+    r'$lib$': ['src/dev_compiler/dart_sdk.js', 'src/dev_compiler/require.js']
+  };
+
+  /// Path to the dart_sdk.js file that should be used for all ddc web apps.
+  final _sdkJsLocation =
+      p.join(sdkDir, 'lib', 'dev_compiler', 'kernel', 'amd', 'dart_sdk.js');
+
+  /// Path to the require.js file that should be used for all ddc web apps.
+  final _sdkRequireJsLocation =
+      p.join(sdkDir, 'lib', 'dev_compiler', 'kernel', 'amd', 'require.js');
+
+  @override
+  FutureOr<void> build(BuildStep buildStep) async {
+    if (buildStep.inputId.package != 'build_web_compilers') {
+      throw StateError('This builder should only be applied to the '
+          'build_web_compilers package');
+    }
+    await buildStep.writeAsBytes(
+        AssetId('build_web_compilers', 'lib/src/dev_compiler/dart_sdk.js'),
+        await File(_sdkJsLocation).readAsBytes());
+    await buildStep.writeAsBytes(
+        AssetId('build_web_compilers', 'lib/src/dev_compiler/require.js'),
+        await File(_sdkRequireJsLocation).readAsBytes());
+  }
+}
diff --git a/build_web_compilers/lib/src/web_entrypoint_builder.dart b/build_web_compilers/lib/src/web_entrypoint_builder.dart
new file mode 100644
index 0000000..5c1c2a3
--- /dev/null
+++ b/build_web_compilers/lib/src/web_entrypoint_builder.dart
@@ -0,0 +1,137 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+// ignore: deprecated_member_use
+import 'package:analyzer/analyzer.dart';
+import 'package:build/build.dart';
+import 'package:build_modules/build_modules.dart';
+
+import 'common.dart';
+import 'dart2js_bootstrap.dart';
+import 'dev_compiler_bootstrap.dart';
+
+const ddcBootstrapExtension = '.dart.bootstrap.js';
+const jsEntrypointExtension = '.dart.js';
+const jsEntrypointSourceMapExtension = '.dart.js.map';
+const jsEntrypointArchiveExtension = '.dart.js.tar.gz';
+const digestsEntrypointExtension = '.digests';
+
+/// Which compiler to use when compiling web entrypoints.
+enum WebCompiler {
+  Dart2Js,
+  DartDevc,
+}
+
+/// The top level keys supported for the `options` config for the
+/// [WebEntrypointBuilder].
+const _supportedOptions = [
+  _compiler,
+  _dart2jsArgs,
+];
+
+const _compiler = 'compiler';
+const _dart2jsArgs = 'dart2js_args';
+
+/// The deprecated keys for the `options` config for the [WebEntrypointBuilder].
+const _deprecatedOptions = [
+  'enable_sync_async',
+  'ignore_cast_failures',
+];
+
+/// A builder which compiles entrypoints for the web.
+///
+/// Supports `dart2js` and `dartdevc`.
+class WebEntrypointBuilder implements Builder {
+  final WebCompiler webCompiler;
+  final List<String> dart2JsArgs;
+
+  const WebEntrypointBuilder(this.webCompiler, {this.dart2JsArgs = const []});
+
+  factory WebEntrypointBuilder.fromOptions(BuilderOptions options) {
+    validateOptions(
+        options.config, _supportedOptions, 'build_web_compilers|entrypoint',
+        deprecatedOptions: _deprecatedOptions);
+    var compilerOption = options.config[_compiler] as String ?? 'dartdevc';
+    WebCompiler compiler;
+    switch (compilerOption) {
+      case 'dartdevc':
+        compiler = WebCompiler.DartDevc;
+        break;
+      case 'dart2js':
+        compiler = WebCompiler.Dart2Js;
+        break;
+      default:
+        throw ArgumentError.value(compilerOption, _compiler,
+            'Only `dartdevc` and `dart2js` are supported.');
+    }
+
+    if (options.config[_dart2jsArgs] is! List) {
+      throw ArgumentError.value(options.config[_dart2jsArgs], _dart2jsArgs,
+          'Expected a list for $_dart2jsArgs.');
+    }
+    var dart2JsArgs = (options.config[_dart2jsArgs] as List)
+            ?.map((arg) => '$arg')
+            ?.toList() ??
+        const <String>[];
+
+    return WebEntrypointBuilder(compiler, dart2JsArgs: dart2JsArgs);
+  }
+
+  @override
+  final buildExtensions = const {
+    '.dart': [
+      ddcBootstrapExtension,
+      jsEntrypointExtension,
+      jsEntrypointSourceMapExtension,
+      jsEntrypointArchiveExtension,
+      digestsEntrypointExtension,
+    ],
+  };
+
+  @override
+  Future<void> build(BuildStep buildStep) async {
+    var dartEntrypointId = buildStep.inputId;
+    var isAppEntrypoint = await _isAppEntryPoint(dartEntrypointId, buildStep);
+    if (!isAppEntrypoint) return;
+    if (webCompiler == WebCompiler.DartDevc) {
+      try {
+        await bootstrapDdc(buildStep, requiredAssets: _ddcSdkResources);
+      } on MissingModulesException catch (e) {
+        log.severe('$e');
+      }
+    } else if (webCompiler == WebCompiler.Dart2Js) {
+      await bootstrapDart2Js(buildStep, dart2JsArgs);
+    }
+  }
+}
+
+/// Returns whether or not [dartId] is an app entrypoint (basically, whether
+/// or not it has a `main` function).
+Future<bool> _isAppEntryPoint(AssetId dartId, AssetReader reader) async {
+  assert(dartId.extension == '.dart');
+  // Skip reporting errors here, dartdevc will report them later with nicer
+  // formatting.
+  // ignore: deprecated_member_use
+  var parsed = parseCompilationUnit(await reader.readAsString(dartId),
+      suppressErrors: true);
+  // Allow two or fewer arguments so that entrypoints intended for use with
+  // [spawnUri] get counted.
+  //
+  // TODO: This misses the case where a Dart file doesn't contain main(),
+  // but has a part that does, or it exports a `main` from another library.
+  return parsed.declarations.any((node) {
+    return node is FunctionDeclaration &&
+        node.name.name == 'main' &&
+        node.functionExpression.parameters.parameters.length <= 2;
+  });
+}
+
+/// Files copied from the SDK that are required at runtime to run a DDC
+/// application.
+final _ddcSdkResources = [
+  AssetId('build_web_compilers', 'lib/src/dev_compiler/dart_sdk.js'),
+  AssetId('build_web_compilers', 'lib/src/dev_compiler/require.js'),
+];
diff --git a/build_web_compilers/mono_pkg.yaml b/build_web_compilers/mono_pkg.yaml
new file mode 100644
index 0000000..bae55f2
--- /dev/null
+++ b/build_web_compilers/mono_pkg.yaml
@@ -0,0 +1,17 @@
+dart:
+  - dev
+
+stages:
+  - analyze_and_format:
+    - group:
+      - dartfmt: sdk
+      - dartanalyzer: --fatal-infos --fatal-warnings .
+    - dartanalyzer: --fatal-warnings .
+      dart:
+        - 2.5.0
+  - unit_test:
+    - group:
+      - test:
+      os:
+        - linux
+        - windows
\ No newline at end of file
diff --git a/build_web_compilers/pubspec.yaml b/build_web_compilers/pubspec.yaml
new file mode 100644
index 0000000..40aa3d6
--- /dev/null
+++ b/build_web_compilers/pubspec.yaml
@@ -0,0 +1,36 @@
+name: build_web_compilers
+version: 2.9.0
+description: Builder implementations wrapping Dart compilers.
+homepage: https://github.com/dart-lang/build/tree/master/build_web_compilers
+
+environment:
+  sdk: ">=2.5.0 <3.0.0"
+
+dependencies:
+  analyzer: ">=0.30.0 <0.40.0"
+  archive: ^2.0.0
+  bazel_worker: ^0.1.18
+  build: ">=1.2.0 <2.0.0"
+  build_config: ">=0.3.0 <0.5.0"
+  build_modules: ^2.8.0
+  collection: ^1.0.0
+  crypto: ^2.0.0
+  glob: ^1.1.0
+  js: ^0.6.1
+  logging: ^0.11.2
+  meta: ^1.1.0
+  path: ^1.4.2
+  pool: ^1.3.0
+  scratch_space: ^0.0.2
+  source_maps: ^0.10.4
+  source_span: ^1.4.0
+  stack_trace: ^1.9.2
+
+dev_dependencies:
+  build_runner: ^1.0.0
+  build_test: ^0.10.9
+  test: ^1.0.0
+  a:
+    path: test/fixtures/a
+  b:
+    path: test/fixtures/b
diff --git a/build_web_compilers/tool/copy_builder.dart b/build_web_compilers/tool/copy_builder.dart
new file mode 100644
index 0000000..1191a9d
--- /dev/null
+++ b/build_web_compilers/tool/copy_builder.dart
@@ -0,0 +1,31 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS 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 'package:build/build.dart';
+
+/// Factory for the build script.
+Builder copyBuilder(_) => _CopyBuilder();
+
+/// Copies the [_stackTraceMapperJs] file to [_stackTraceMapperCopyJs].
+class _CopyBuilder extends Builder {
+  @override
+  final Map<String, List<String>> buildExtensions = {
+    _stackTraceMapperJs.path: [_stackTraceMapperCopyJs.path]
+  };
+
+  @override
+  void build(BuildStep buildStep) {
+    if (buildStep.inputId != _stackTraceMapperJs) {
+      throw StateError(
+          'Unexpected input for `CopyBuilder` expected only $_stackTraceMapperJs');
+    }
+    buildStep.writeAsString(
+        _stackTraceMapperCopyJs, buildStep.readAsString(_stackTraceMapperJs));
+  }
+}
+
+final _stackTraceMapperJs =
+    AssetId('build_web_compilers', 'web/stack_trace_mapper.dart.js');
+final _stackTraceMapperCopyJs = AssetId('build_web_compilers',
+    'lib/src/dev_compiler_stack_trace/stack_trace_mapper.dart.js');
diff --git a/build_web_compilers/web/source_map_stack_trace.dart b/build_web_compilers/web/source_map_stack_trace.dart
new file mode 100644
index 0000000..b231ef4
--- /dev/null
+++ b/build_web_compilers/web/source_map_stack_trace.dart
@@ -0,0 +1,133 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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.
+
+/// Standalone utility that manages loading source maps for all Dart scripts
+/// on the page compiled with DDC.
+///
+/// This is forked from the equivalent file that ships with the SDK which is
+/// intended for use within bazel only.
+
+import 'package:path/path.dart' as p;
+import 'package:source_maps/source_maps.dart';
+import 'package:stack_trace/stack_trace.dart';
+
+/// TODO(jakemac): Remove after https://github.com/dart-lang/build/issues/2219
+void main() {}
+
+/// Convert [stackTrace], a stack trace generated by DDC-compiled
+/// JavaScript, to a native-looking stack trace using [sourceMap].
+///
+/// [roots] are the paths (usually `http:` URI strings) that DDC applications
+/// are served from.  This is used to identify sdk and package URIs.
+StackTrace mapStackTrace(Mapping sourceMap, StackTrace stackTrace,
+    {List<String> roots}) {
+  if (stackTrace is Chain) {
+    return Chain(stackTrace.traces.map(
+        (trace) => Trace.from(mapStackTrace(sourceMap, trace, roots: roots))));
+  }
+
+  var trace = Trace.from(stackTrace);
+  return Trace(trace.frames.map((frame) {
+    // If there's no line information, there's no way to translate this frame.
+    // We could return it as-is, but these lines are usually not useful anyways.
+    if (frame.line == null) return null;
+
+    // If there's no column, try using the first column of the line.
+    var column = frame.column ?? 0;
+
+    // Subtract 1 because stack traces use 1-indexed lines and columns and
+    // source maps uses 0-indexed.
+    var span = sourceMap.spanFor(frame.line - 1, column - 1,
+        uri: frame.uri?.toString());
+
+    // If we can't find a source span, ignore the frame. It's probably something
+    // internal that the user doesn't care about.
+    if (span == null) return null;
+
+    var sourceUrl = span.sourceUrl.toString();
+    for (var root in roots) {
+      if (root != null && p.url.isWithin(root, sourceUrl)) {
+        var relative = p.url.relative(sourceUrl, from: root);
+        if (relative.contains('dart:')) {
+          sourceUrl = relative.substring(relative.indexOf('dart:'));
+          break;
+        }
+        var packageRoot = '$root/packages';
+        if (p.url.isWithin(packageRoot, sourceUrl)) {
+          sourceUrl = 'package:' + p.url.relative(sourceUrl, from: packageRoot);
+          break;
+        }
+      }
+    }
+
+    if (!sourceUrl.startsWith('dart:') &&
+        sourceUrl ==
+            'package:build_web_compilers/src/dev_compiler/dart_sdk.js') {
+      // This compresses the long dart_sdk URLs if SDK source maps are missing.
+      // It's no longer linkable, but neither are the properly mapped ones
+      // above.
+      sourceUrl = 'dart:sdk_internal';
+    }
+
+    return Frame(Uri.parse(sourceUrl), span.start.line + 1,
+        span.start.column + 1, _prettifyMember(frame.member));
+  }).where((frame) => frame != null))
+      .foldFrames((Frame frame) => frame.uri.scheme.contains('dart'));
+}
+
+final escapedPipe = '\$124';
+final escapedPound = '\$35';
+
+/// Reformats a JS member name to make it look more Dart-like.
+///
+/// TODO(https://github.com/dart-lang/sdk/issues/38869): Remove this logic when
+/// DDC stack trace deobfuscation is overhauled.
+String _prettifyMember(String member) {
+  var last = member.lastIndexOf('.');
+  if (last < 0) return member;
+  var suffix = member.substring(last + 1);
+  member = suffix == 'fn' ? member : suffix;
+  // We avoid unescaping the entire member here due to DDC's deduping mechanism
+  // introducing trailing $N.
+  member = member.replaceAll(escapedPipe, '|');
+  return member.contains('|') ? _prettifyExtension(member) : member;
+}
+
+/// Reformats a JS member name as an extension method invocation.
+String _prettifyExtension(String member) {
+  var isSetter = false;
+  var pipeIndex = member.indexOf('|');
+  var spaceIndex = member.indexOf(' ');
+  var poundIndex = member.indexOf('escapedPound');
+  if (spaceIndex >= 0) {
+    // Here member is a static field or static getter/setter.
+    isSetter = member.substring(0, spaceIndex) == 'set';
+    member = member.substring(spaceIndex + 1, member.length);
+  } else if (poundIndex >= 0) {
+    // Here member is a tearoff or local property getter/setter.
+    isSetter = member.substring(pipeIndex + 1, poundIndex) == 'set';
+    member = member.replaceRange(pipeIndex + 1, poundIndex + 3, '');
+  } else {
+    var body = member.substring(pipeIndex + 1, member.length);
+    if (body.startsWith('unary') || body.startsWith('\$')) {
+      // Here member's an operator, so it's safe to unescape everything lazily.
+      member = _unescape(member);
+    }
+  }
+  member = member.replaceAll('|', '.');
+  return isSetter ? '$member=' : member;
+}
+
+/// Unescapes a DDC-escaped JS identifier name.
+///
+/// Identifier names that contain illegal JS characters are escaped by DDC to a
+/// decimal representation of the symbol's UTF-16 value.
+/// Warning: this greedily escapes characters, so it can be unsafe in the event
+/// that an escaped sequence precedes a number literal in the JS name.
+String _unescape(String name) {
+  return name.replaceAllMapped(
+      RegExp(r'\$[0-9]+'),
+      (m) =>
+          String.fromCharCode(int.parse(name.substring(m.start + 1, m.end))));
+}
diff --git a/build_web_compilers/web/stack_trace_mapper.dart b/build_web_compilers/web/stack_trace_mapper.dart
new file mode 100644
index 0000000..383fef2
--- /dev/null
+++ b/build_web_compilers/web/stack_trace_mapper.dart
@@ -0,0 +1,141 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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.
+
+/// Standalone utility that manages loading source maps for all Dart scripts
+/// on the page compiled with DDC.
+///
+/// Example JavaScript usage:
+/// $dartStackTraceUtility.addLoadedListener(function() {
+///   // All Dart source maps are now loaded. It is now safe to start your
+///   // Dart application compiled with DDC.
+///   dart_library.start('your_dart_application');
+/// })
+///
+/// If $dartStackTraceUtility is set, the dart:core StackTrace class calls
+/// $dartStackTraceUtility.mapper(someJSStackTrace)
+/// to apply source maps.
+///
+/// This utility can be compiled to JavaScript using Dart2JS while the rest
+/// of the application is compiled with DDC or could be compiled with DDC.
+
+@JS()
+library stack_trace_mapper;
+
+import 'dart:convert';
+
+import 'package:js/js.dart';
+import 'package:path/path.dart' as p;
+import 'package:source_maps/source_maps.dart';
+import 'package:source_span/source_span.dart';
+import 'package:stack_trace/stack_trace.dart';
+
+import 'source_map_stack_trace.dart';
+
+/// Copied from `lib/src/dev_compiler_builder.dart`, these need to be kept in
+/// sync.
+///
+/// Given a list of [uris] as [String]s from a sourcemap, fixes them up so that
+/// they make sense in a browser context.
+///
+/// - Strips the scheme from the uri
+/// - Strips the top level directory if its not `packages`
+List<String> fixSourceMapSources(List<String> uris) {
+  return uris.map((source) {
+    var uri = Uri.parse(source);
+    // We only want to rewrite multi-root scheme uris.
+    if (uri.scheme.isEmpty) return source;
+    var newSegments = uri.pathSegments.first == 'packages'
+        ? uri.pathSegments
+        : uri.pathSegments.skip(1);
+    return Uri(path: p.url.joinAll(['/'].followedBy(newSegments))).toString();
+  }).toList();
+}
+
+typedef ReadyCallback = void Function();
+
+/// Global object DDC uses to see if a stack trace utility has been registered.
+@JS(r'$dartStackTraceUtility')
+external set dartStackTraceUtility(DartStackTraceUtility value);
+
+@JS(r'$dartLoader.rootDirectories')
+external List get rootDirectories;
+
+typedef StackTraceMapper = String Function(String stackTrace);
+typedef SourceMapProvider = dynamic Function(String modulePath);
+typedef SetSourceMapProvider = void Function(SourceMapProvider provider);
+
+@JS()
+@anonymous
+class DartStackTraceUtility {
+  external factory DartStackTraceUtility(
+      {StackTraceMapper mapper, SetSourceMapProvider setSourceMapProvider});
+}
+
+/// Source mapping that waits to parse source maps until they match the uri
+/// of a requested source map.
+///
+/// This improves startup performance compared to using MappingBundle directly.
+/// The unparsed data for the source maps must still be loaded before
+/// LazyMapping is used.
+class LazyMapping extends Mapping {
+  final _bundle = MappingBundle();
+  final SourceMapProvider _provider;
+
+  LazyMapping(this._provider);
+
+  List toJson() => _bundle.toJson();
+
+  @override
+  SourceMapSpan spanFor(int line, int column,
+      {Map<String, SourceFile> files, String uri}) {
+    if (uri == null) {
+      throw ArgumentError.notNull('uri');
+    }
+
+    if (!_bundle.containsMapping(uri)) {
+      var rawMap = _provider(uri);
+      var parsedMap = (rawMap is String ? jsonDecode(rawMap) : rawMap) as Map;
+      if (parsedMap != null) {
+        parsedMap['sources'] =
+            fixSourceMapSources((parsedMap['sources'] as List).cast());
+        var mapping = parse(jsonEncode(parsedMap)) as SingleMapping
+          ..targetUrl = uri
+          ..sourceRoot = '${p.dirname(uri)}/';
+        _bundle.addMapping(mapping);
+      }
+    }
+    var span = _bundle.spanFor(line, column, files: files, uri: uri);
+    // TODO(jacobr): we shouldn't have to filter out invalid sourceUrl entries
+    // here.
+    if (span == null || span.start.sourceUrl == null) return null;
+    var pathSegments = span.start.sourceUrl.pathSegments;
+    if (pathSegments.isNotEmpty && pathSegments.last == 'null') return null;
+    return span;
+  }
+}
+
+LazyMapping _mapping;
+
+List<String> roots = rootDirectories.map((s) => '$s').toList();
+
+String mapper(String rawStackTrace) {
+  if (_mapping == null) {
+    // This should not happen if the user has waited for the ReadyCallback
+    // to start the application.
+    throw StateError('Source maps are not done loading.');
+  }
+  var trace = Trace.parse(rawStackTrace);
+  return mapStackTrace(_mapping, trace, roots: roots).toString();
+}
+
+void setSourceMapProvider(SourceMapProvider provider) {
+  _mapping = LazyMapping(provider);
+}
+
+void main() {
+  // Register with DDC.
+  dartStackTraceUtility = DartStackTraceUtility(
+      mapper: allowInterop(mapper),
+      setSourceMapProvider: allowInterop(setSourceMapProvider));
+}
diff --git a/code_builder/.gitignore b/code_builder/.gitignore
new file mode 100644
index 0000000..feb089d
--- /dev/null
+++ b/code_builder/.gitignore
@@ -0,0 +1,5 @@
+# Files and directories created by pub
+.dart_tool
+.packages
+.pub
+pubspec.lock
diff --git a/code_builder/.travis.yml b/code_builder/.travis.yml
new file mode 100644
index 0000000..e415dd4
--- /dev/null
+++ b/code_builder/.travis.yml
@@ -0,0 +1,23 @@
+language: dart
+dart:
+  - dev
+  - 2.1.0
+
+cache:
+  directories:
+    - $HOME/.pub-cache
+
+dist: trusty
+addons:
+  chrome: stable
+
+branches:
+  only: [master]
+
+# TODO: Give up the dream of running with dartdevc until...
+# https://github.com/dart-lang/sdk/issues/31280
+
+dart_task:
+  - test: --platform vm
+  - dartanalyzer
+  - dartfmt
diff --git a/code_builder/AUTHORS b/code_builder/AUTHORS
new file mode 100644
index 0000000..609e0dc
--- /dev/null
+++ b/code_builder/AUTHORS
@@ -0,0 +1,6 @@
+# Below is a list of people and organizations that have contributed
+# to the project. Names should be added to the list like so:
+#
+#   Name/Organization <email address>
+
+Google Inc.
\ No newline at end of file
diff --git a/code_builder/BUILD.gn b/code_builder/BUILD.gn
new file mode 100644
index 0000000..a5f2e0b
--- /dev/null
+++ b/code_builder/BUILD.gn
@@ -0,0 +1,21 @@
+# This file is generated by importer.py for code_builder-3.2.1
+
+import("//build/dart/dart_library.gni")
+
+dart_library("code_builder") {
+  package_name = "code_builder"
+
+  # This parameter is left empty as we don't care about analysis or exporting
+  # these sources outside of the tree.
+  sources = []
+
+  disable_analysis = true
+
+  deps = [
+    "//third_party/dart-pkg/pub/matcher",
+    "//third_party/dart-pkg/pub/built_collection",
+    "//third_party/dart-pkg/pub/meta",
+    "//third_party/dart-pkg/pub/built_value",
+    "//third_party/dart-pkg/pub/collection",
+  ]
+}
diff --git a/code_builder/CHANGELOG.md b/code_builder/CHANGELOG.md
new file mode 100644
index 0000000..6f2abf0
--- /dev/null
+++ b/code_builder/CHANGELOG.md
@@ -0,0 +1,684 @@
+## 3.2.1
+
+* Escape newlines in String literals.
+* Introduce `Expression.or` for boolean OR.
+* Introduce `Expression.negate` for boolean NOT.
+* No longer emits redundant `,`s in `FunctionType`s.
+* Added support for `literalSet` and `literalConstSet`.
+* Depend on the latest `package:built_value`.
+
+## 3.2.0
+
+* Emit `=` instead of `:` for named parameter default values.
+* The `new` keyword will not be used in generated code.
+* The `const` keyword will be omitted when it can be inferred.
+* Add an option in `DartEmitter` to order directives.
+* `DartEmitter` added a `startConstCode` function to track the creation of
+  constant expression trees.
+* `BinaryExpression` added the `final bool isConst` field.
+
+## 3.1.3
+
+* Bump dependency on built_collection to include v4.0.0.
+
+## 3.1.2
+
+* Set max SDK version to `<3.0.0`.
+
+## 3.1.1
+
+* `Expression.asA` is now wrapped with parenthesis so that further calls may be
+  made on it as an expression.
+
+
+## 3.1.0
+
+* Added `Expression.asA` for creating explicit casts:
+
+```dart
+void main() {
+  test('should emit an explicit cast', () {
+    expect(
+      refer('foo').asA(refer('String')),
+      equalsDart('foo as String'),
+    );
+  });
+}
+```
+
+## 3.0.3
+
+* Fix a bug that caused all downstream users of `code_builder` to crash due to
+  `build_runner` trying to import our private builder (in `tool/`). Sorry for
+  the inconvenience.
+
+## 3.0.2
+
+* Require `source_gen: ^0.7.5`.
+
+## 3.0.1
+
+* Upgrade to `built_value` 5.1.0.
+* Export the `literalNum` function.
+* **BUG FIX**: `literal` supports a `Map`.
+
+## 3.0.0
+
+* Also infer `Constructor.lambda` for `factory` constructors.
+
+## 3.0.0-alpha
+
+* Using `equalsDart` no longer formats automatically with `dartfmt`.
+
+* Removed deprecated `Annotation` and `File` classes.
+
+* `Method.lambda` is inferred based on `Method.body` where possible and now
+  defaults to `null`.
+
+## 2.4.0
+
+* Add `equalTo`, `notEqualTo`, `greaterThan`, `lessThan`, `greateOrEqualTo`, and
+  `lessOrEqualTo` to `Expression`.
+
+## 2.3.0
+
+* Using `equalsDart` and expecting `dartfmt` by default is *deprecated*. This
+  requires this package to have a direct dependency on specific versions of
+  `dart_style` (and transitively `analyzer`), which is problematic just for
+  testing infrastructure. To future proof, we've exposed the `EqualsDart` class
+  with a `format` override:
+
+```dart
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'package:code_builder/code_builder.dart';
+import 'package:dart_style/dart_style.dart';
+
+final DartFormatter _dartfmt = new DartFormatter();
+String _format(String source) {
+  try {
+    return _dartfmt.format(source);
+  } on FormatException catch (_) {
+    return _dartfmt.formatStatement(source);
+  }
+}
+
+/// Should be invoked in `main()` of every test in `test/**_test.dart`.
+void useDartfmt() => EqualsDart.format = _format;
+```
+
+* Added `Expression.isA` and `Expression.isNotA`:
+
+```dart
+void main() {
+  test('should emit an is check', () {
+    expect(
+      refer('foo').isA(refer('String')),
+      equalsDart('foo is String'),
+    );
+  });
+}
+```
+
+* Deprecated `Annotation`. It is now legal to simply pass any `Expression` as
+  a metadata annotation to `Class`, `Method`, `Field,` and `Parameter`. In
+  `3.0.0`, the `Annotation` class will be completely removed:
+
+```dart
+void main() {
+  test('should create a class with a annotated constructor', () {
+    expect(
+      new Class((b) => b
+        ..name = 'Foo'
+        ..constructors.add(
+          new Constructor((b) => b..annotations.add(refer('deprecated'))))),
+      equalsDart(r'''
+        class Foo {
+          @deprecated
+          Foo();
+        }
+      '''),
+    );
+  });
+}
+```
+
+* Added inference support for `Method.lambda` and `Constructor.lambda`. If not
+  explicitly provided and the body of the function originated from an
+  `Expression` then `lambda` is inferred to be true. This is not a breaking
+  change yet, as it requires an explicit `null` value. In `3.0.0` this will be
+  the default:
+
+```dart
+void main() {
+  final animal = new Class((b) => b
+    ..name = 'Animal'
+    ..extend = refer('Organism')
+    ..methods.add(new Method.returnsVoid((b) => b
+      ..name = 'eat'
+      // In 3.0.0, this may be omitted and still is inferred.
+      ..lambda = null
+      ..body = refer('print').call([literalString('Yum!')]).code)));
+  final emitter = new DartEmitter();
+  print(new DartFormatter().format('${animal.accept(emitter)}'));
+}
+```
+
+* Added `nullSafeProperty` to `Expression` to access properties with `?.`
+* Added `conditional` to `Expression` to use the ternary operator `? : `
+* Methods taking `positionalArguments` accept `Iterable<Expression>`
+* **BUG FIX**: Parameters can take a `FunctionType` as a `type`.
+  `Reference.type` now returns a `Reference`. Note that this change is
+  technically breaking but should not impacts most clients.
+
+## 2.2.0
+
+* Imports are prefixed with `_i1` rather than `_1` which satisfies the lint
+  `lowercase_with_underscores`. While not a strictly breaking change you may
+  have to fix/regenerate golden file-like tests. We added documentation that
+  the specific prefix is not considered stable.
+
+* Added `Expression.index` for accessing the `[]` operator:
+
+```dart
+void main() {
+  test('should emit an index operator', () {
+    expect(
+      refer('bar').index(literalTrue).assignVar('foo').statement,
+      equalsDart('var foo = bar[true];'),
+    );
+  });
+
+  test('should emit an index operator set', () {
+    expect(
+      refer('bar')
+        .index(literalTrue)
+        .assign(literalFalse)
+        .assignVar('foo')
+        .statement,
+      equalsDart('var foo = bar[true] = false;'),
+    );
+  });
+}
+```
+
+* `literalList` accepts an `Iterable` argument.
+
+* Fixed an NPE when a method had a return type of a `FunctionType`:
+
+```dart
+void main() {
+  test('should create a method with a function type return type', () {
+    expect(
+      new Method((b) => b
+        ..name = 'foo'
+        ..returns = new FunctionType((b) => b
+          ..returnType = refer('String')
+          ..requiredParameters.addAll([
+            refer('int'),
+          ]))),
+      equalsDart(r'''
+        String Function(int) foo();
+      '''),
+    );
+  });
+}
+```
+
+## 2.1.0
+
+We now require the Dart 2.0-dev branch SDK (`>= 2.0.0-dev`).
+
+* Added support for raw `String` literals.
+* Automatically escapes single quotes in now-raw `String` literals.
+* Deprecated `File`, which is now a redirect to the preferred class, `Library`.
+
+This helps avoid symbol clashes when used with `dart:io`, a popular library. It
+is now safe to do the following and get full access to the `code_builder` API:
+
+```dart
+import 'dart:io';
+
+import 'package:code_builder/code_builder.dart' hide File;
+```
+
+We will remove `File` in `3.0.0`, so use `Library` instead.
+
+## 2.0.0
+
+Re-released without a direct dependency on `package:analyzer`!
+
+For users of the `1.x` branch of `code_builder`, this is a pretty big breaking
+change but ultimately is for the better - it's easier to evolve this library
+now and even add your own builders on top of the library.
+
+```dart
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'package:code_builder/code_builder.dart';
+import 'package:dart_style/dart_style.dart';
+
+void main() {
+  final animal = new Class((b) => b
+    ..name = 'Animal'
+    ..extend = refer('Organism')
+    ..methods.add(new Method.returnsVoid((b) => b
+      ..name = 'eat'
+      ..lambda = true
+      ..body = const Code('print(\'Yum\')'))));
+  final emitter = new DartEmitter();
+  print(new DartFormatter().format('${animal.accept(emitter)}'));
+}
+```
+
+...outputs...
+
+```dart
+class Animal extends Organism {
+  void eat() => print('Yum!');
+}
+```
+
+**Major changes**:
+
+* Builders now use `built_value`, and have a more consistent, friendly API.
+* Builders are now consistent - they don't any work until code is emitted.
+* It's possible to overwrite the built-in code emitting, formatting, etc by
+  providing your own visitors. See `DartEmitter` as an example of the built-in
+  visitor/emitter.
+* Most of the expression and statement level helpers were removed; in practice
+  they were difficult to write and maintain, and many users commonly asked for
+  opt-out type APIs. See the `Code` example below:
+
+```dart
+void main() {
+  var code = new Code('x + y = z');
+  code.expression;
+  code.statement;
+}
+```
+
+See the commit log, examples, and tests for full details. While we want to try
+and avoid breaking changes, suggestions, new features, and incremental updates
+are welcome!
+
+## 2.0.0-beta
+
+* Added `lazySpec` and `lazyCode` to lazily create code on visit [#145](https://github.com/dart-lang/code_builder/issues/145).
+
+* **BUG FIX**: `equalsDart` emits the failing source code [#147](https://github.com/dart-lang/code_builder/issues/147).
+* **BUG FIX**: Top-level `lambda` `Method`s no longer emit invalid code [#146](https://github.com/dart-lang/code_builder/issues/146).
+
+## 2.0.0-alpha+3
+
+* Added `Expression.annotation` and `Expression.annotationNamed`.
+* Added `Method.closure` to create an `Expression`.
+* Added `FunctionType`.
+* Added `{new|const}InstanceNamed` to `Expression` [#135](https://github.com/dart-lang/code_builder/issues/135).
+  * Also added a `typeArguments` option to all invocations.
+* Added `assign{...}` variants to `Expression` [#137](https://github.com/dart-lang/code_builder/issues/137).
+* Added `.awaited` and `.returned` to `Expression` [#138](https://github.com/dart-lang/code_builder/issues/138).
+
+* **BUG FIX**: `Block` now implements `Code` [#136](https://github.com/dart-lang/code_builder/issues/136).
+* **BUG FIX**: `new DartEmitter.scoped()` applies prefixing [#139](https://github.com/dart-lang/code_builder/issues/139).
+
+* Renamed many of the `.asFoo(...)` and `.toFoo(...)` methods to single getter:
+  * `asCode()` to `code`
+  * `asStatement()` to `statement`
+  * `toExpression()` to `expression`
+
+* Moved `{new|const}Instance{[Named]}` from `Expression` to `Reference`.
+
+## 2.0.0-alpha+2
+
+* Upgraded `build_runner` from `^0.3.0` to `>=0.4.0 <0.6.0`.
+* Upgraded `build_value{_generator}` from `^1.0.0` to `>=2.0.0 <5.0.0`.
+* Upgraded `source_gen` from `>=0.5.0 <0.7.0` to `^0.7.0`.
+
+* Added `MethodModifier` to allow emit a `Method` with `async|async*|sync*`.
+* Added `show|hide` to `Directive`.
+* Added `Directive.importDeferredAs`.
+* Added a new line character after emitting some types (class, method, etc).
+* Added `refer` as a short-hand for `new Reference(...)`.
+  * `Reference` now implements `Expression`.
+
+* Added many classes/methods for writing bodies of `Code` fluently:
+  * `Expression`
+  * `LiteralExpression`
+    * `literal`
+    * `literalNull`
+    * `literalBool`
+    * `literalTrue`
+    * `literalFalse`
+    * `literalNum`
+    * `literalString`
+    * `literalList` and `literalConstList`
+    * `literalMap` and `literalConstMap`
+  * `const Code(staticString)`
+  * `const Code.scope((allocate) => '')`
+
+* Removed `SimpleSpecVisitor` (it was unused).
+* Removed `implements Reference` from `Method` and `Field`; not a lot of value.
+
+* `SpecVisitor<T>`'s methods all have an optional `[T context]` parameter now.
+  * This makes it much easier to avoid allocating extra `StringBuffer`s.
+* `equalsDart` removes insignificant white space before comparing results.
+
+## 2.0.0-alpha+1
+
+* Removed `Reference.localScope`. Just use `Reference(symbol)` now.
+* Allow `Reference` instead of an explicit `TypeReference` in most APIs.
+  * `toType()` is performed for you as part the emitter process
+
+```dart
+final animal = new Class((b) => b
+  ..name = 'Animal'
+  // Used to need a suffix of .toType().
+  ..extend = const Reference('Organism')
+  ..methods.add(new Method.returnsVoid((b) => b
+    ..name = 'eat'
+    ..lambda = true
+    ..body = new Code((b) => b..code = 'print(\'Yum\')'))));
+```
+
+* We now support the Dart 2.0 pre-release SDKs (`<2.0.0-dev.infinity`)
+* Removed the ability to treat `Class` as a `TypeReference`.
+  * Was required for compilation to `dart2js`, which is now tested on travis.
+
+## 2.0.0-alpha
+
+* Complete re-write to not use `package:analyzer`.
+* Code generation now properly uses the _builder_ pattern (via `built_value`).
+* See examples and tests for details.
+
+## 1.0.4
+
+* Added `isInstanceOf` to `ExpressionBuilder`, which performs an `is` check:
+
+```dart
+expect(
+  reference('foo').isInstanceOf(_barType),
+  equalsSource('foo is Bar'),
+);
+```
+
+## 1.0.3
+
+* Support latest `pkg/analyzer` and `pkg/func`.
+
+## 1.0.2
+
+* Update internals to use newer analyzer API
+
+## 1.0.1
+
+* Support the latest version of `package:dart_style`.
+
+## 1.0.0
+
+First full release. At this point all changes until `2.0.0` will be backwards
+compatible (new features) or bug fixes that are not breaking. This doesn't mean
+that the entire Dart language is buildable with our API, though.
+
+**Contributions are welcome.**
+
+- Exposed `uri` in `ImportBuilder`, `ExportBuilder`, and `Part[Of]Builder`.
+
+## 1.0.0-beta+7
+
+- Added `ExpressionBuilder#ternary`.
+
+## 1.0.0-beta+6
+
+- Added `TypeDefBuilder`.
+- Added `FunctionParameterBuilder`.
+- Added `asAbstract` to various `MethodBuilder` constructors.
+
+## 1.0.0-beta+5
+
+- Re-published the package without merge conflicts.
+
+## 1.0.0-beta+4
+
+- Renamed `PartBuilder` to `PartOfBuilder`.
+- Added a new class, `PartBuilder`, to represent `part '...dart'` directives.
+- Added the `HasAnnotations` interface to all library/part/directive builders.
+- Added `asFactory` and `asConst` to `ConstructorBuilder`.
+- Added `ConstructorBuilder.redirectTo` for a redirecting factory constructor.
+- Added a `name` getter to `ReferenceBuilder`.
+- Supplying an empty constructor name (`''`) is equivalent to `null` (default).
+- Automatically encodes string literals with multiple lines as `'''`.
+- Added `asThrow` to `ExpressionBuilder`.
+- Fixed a bug that prevented `FieldBuilder` from being used at the top-level.
+
+## 1.0.0-beta+3
+
+- Added support for `genericTypes` parameter for `ExpressionBuilder#invoke`:
+
+```dart
+expect(
+  explicitThis.invoke('doThing', [literal(true)], genericTypes: [
+    lib$core.bool,
+  ]),
+  equalsSource(r'''
+    this.doThing<bool>(true)
+  '''),
+);
+```
+
+- Added a `castAs` method to `ExpressionBuilder`:
+
+```dart
+expect(
+  literal(1.0).castAs(lib$core.num),
+  equalsSource(r'''
+    1.0 as num
+  '''),
+);
+```
+
+### BREAKING CHANGES
+
+- Removed `namedNewInstance` and `namedConstInstance`, replaced with `constructor: `:
+
+```dart
+expect(
+  reference('Foo').newInstance([], constructor: 'other'),
+  equalsSource(r'''
+    new Foo.other()
+  '''),
+);
+```
+
+- Renamed `named` parameter to `namedArguments`:
+
+```dart
+expect(
+  reference('doThing').call(
+    [literal(true)],
+    namedArguments: {
+      'otherFlag': literal(false),
+    },
+  ),
+  equalsSource(r'''
+    doThing(true, otherFlag: false)
+  '''),
+);
+```
+
+## 1.0.0-beta+2
+
+### BREAKING CHANGES
+
+Avoid creating symbols that can collide with the Dart language:
+
+- `MethodModifier.async` -> `MethodModifier.asAsync`
+- `MethodModifier.asyncStar` -> `MethodModifier.asAsyncStar`
+- `MethodModifier.syncStar` -> `MethodModifier.asSyncStar`
+
+## 1.0.0-beta+1
+
+- Add support for `switch` statements
+- Add support for a raw expression and statement
+  - `new ExpressionBuilder.raw(...)`
+  - `new StatemnetBuilder.raw(...)`
+
+This should help cover any cases not covered with builders today.
+
+- Allow referring to a `ClassBuilder` and `TypeBuilder` as an expression
+- Add support for accessing the index `[]` operator on an expression
+
+### BREAKING CHANGES
+
+- Changed `ExpressionBuilder.asAssign` to always take an `ExpressionBuilder` as
+  target and removed the `value` property. Most changes are pretty simple, and
+  involve just using `reference(...)`. For example:
+
+```dart
+literal(true).asAssign(reference('flag'))
+```
+
+... emits `flag = true`.
+
+## 1.0.0-beta
+
+- Add support for `async`, `sync`, `sync*` functions
+- Add support for expression `asAwait`, `asYield`, `asYieldStar`
+- Add `toExportBuilder` and `toImportBuilder` to types and references
+- Fix an import scoping bug in `return` statements and named constructor invocations.
+- Added constructor initializer support
+- Add `while` and `do {} while` loop support
+- Add `for` and `for-in` support
+- Added a `name` getter for `ParameterBuilder`
+
+## 1.0.0-alpha+7
+
+- Make use of new analyzer API in preparation for analyzer version 0.30.
+
+## 1.0.0-alpha+6
+
+- `MethodBuilder.closure` emits properly as a top-level function
+
+## 1.0.0-alpha+5
+
+- MethodBuilder with no statements will create an empty block instead of
+  a semicolon.
+
+```dart
+// main() {}
+method('main')
+```
+
+- Fix lambdas and closures to not include a trailing semicolon when used
+  as an expression.
+
+```dart
+ // () => false
+ new MethodBuilder.closure(returns: literal(false));
+```
+
+## 1.0.0-alpha+4
+
+- Add support for latest `pkg/analyzer`.
+
+## 1.0.0-alpha+3
+
+- BREAKING CHANGE: Added generics support to `TypeBuilder`:
+
+`importFrom` becomes a _named_, not positional argument, and the named
+argument `genericTypes` is added (`Iterable<TypeBuilder>`).
+
+```dart
+// List<String>
+new TypeBuilder('List', genericTypes: [reference('String')])
+```
+
+- Added generic support to `ReferenceBuilder`:
+
+```dart
+// List<String>
+reference('List').toTyped([reference('String')])
+```
+
+- Fixed a bug where `ReferenceBuilder.buildAst` was not implemented
+- Added `and` and `or` methods to `ExpressionBuilder`:
+
+```dart
+// true || false
+literal(true).or(literal(false));
+
+// true && false
+literal(true).and(literal(false));
+```
+
+- Added support for creating closures - `MethodBuilder.closure`:
+
+```dart
+// () => true
+new MethodBuilder.closure(
+  returns: literal(true),
+  returnType: lib$core.bool,
+)
+```
+
+## 1.0.0-alpha+2
+
+- Added `returnVoid` to well, `return;`
+- Added support for top-level field assignments:
+
+```dart
+new LibraryBuilder()..addMember(literal(false).asConst('foo'))
+```
+
+- Added support for specifying a `target` when using `asAssign`:
+
+```dart
+// Outputs bank.bar = goldBar
+reference('goldBar').asAssign('bar', target: reference('bank'))
+```
+
+- Added support for the cascade operator:
+
+```dart
+// Outputs foo..doThis()..doThat()
+reference('foo').cascade((c) => <ExpressionBuilder> [
+  c.invoke('doThis', []),
+  c.invoke('doThat', []),
+]);
+```
+
+- Added support for accessing a property
+
+```dart
+// foo.bar
+reference('foo').property('bar');
+```
+
+## 1.0.0-alpha+1
+
+- Slight updates to confusing documentation.
+- Added support for null-aware assignments.
+- Added `show` and `hide` support to `ImportBuilder`
+- Added `deferred` support to `ImportBuilder`
+- Added `ExportBuilder`
+- Added `list` and `map` literals that support generic types
+
+## 1.0.0-alpha
+
+- Large refactor that makes the library more feature complete.
+
+## 0.1.1
+
+- Add concept of `Scope` and change `toAst` to support it
+
+Now your entire AST tree can be scoped and import directives
+automatically added to a `LibraryBuilder` for you if you use
+`LibraryBuilder.scope`.
+
+## 0.1.0
+
+- Initial version
diff --git a/code_builder/CONTRIBUTING.md b/code_builder/CONTRIBUTING.md
new file mode 100644
index 0000000..0dfd0f7
--- /dev/null
+++ b/code_builder/CONTRIBUTING.md
@@ -0,0 +1,33 @@
+Want to contribute? Great! First, read this page (including the small print at
+the end).
+
+### Before you contribute
+Before we can use your code, you must sign the
+[Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual)
+(CLA), which you can do online. The CLA is necessary mainly because you own the
+copyright to your changes, even after your contribution becomes part of our
+codebase, so we need your permission to use and distribute your code. We also
+need to be sure of various other things—for instance that you'll tell us if you
+know that your code infringes on other people's patents. You don't have to sign
+the CLA until after you've submitted your code for review and a member has
+approved it, but you must do it before we can put your code into our codebase.
+
+Before you start working on a larger contribution, you should get in touch with
+us first through the issue tracker with your idea so that we can help out and
+possibly guide you. Coordinating up front makes it much easier to avoid
+frustration later on.
+
+### Code reviews
+All submissions, including submissions by project members, require review.
+
+### File headers
+All files in the project must start with the following header.
+
+    // Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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.
+
+### The small print
+Contributions made by corporations are covered by a different agreement than the
+one above, the
+[Software Grant and Corporate Contributor License Agreement](https://developers.google.com/open-source/cla/corporate).
\ No newline at end of file
diff --git a/code_builder/LICENSE b/code_builder/LICENSE
new file mode 100644
index 0000000..82e9b52
--- /dev/null
+++ b/code_builder/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2016, the Dart project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of Google Inc. nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/code_builder/README.md b/code_builder/README.md
new file mode 100644
index 0000000..9d9f394
--- /dev/null
+++ b/code_builder/README.md
@@ -0,0 +1,107 @@
+[![Pub package](https://img.shields.io/pub/v/code_builder.svg)](https://pub.dartlang.org/packages/code_builder)
+[![Build status](https://travis-ci.org/dart-lang/code_builder.svg)](https://travis-ci.org/dart-lang/code_builder)
+[![Latest docs](https://img.shields.io/badge/dartdocs-latest-blue.svg)](https://www.dartdocs.org/documentation/code_builder/latest)
+[![Gitter chat](https://badges.gitter.im/dart-lang/build.svg)](https://gitter.im/dart-lang/build)
+
+A fluent, builder-based library for generating valid Dart code.
+
+## Usage
+
+`code_builder` has a narrow and user-friendly API.
+
+See the `example` and `test` folders for additional examples.
+
+For example creating a class with a method:
+
+```dart
+import 'package:code_builder/code_builder.dart';
+import 'package:dart_style/dart_style.dart';
+
+void main() {
+  final animal = Class((b) => b
+    ..name = 'Animal'
+    ..extend = refer('Organism')
+    ..methods.add(Method.returnsVoid((b) => b
+      ..name = 'eat'
+      ..body = const Code("print('Yum');"))));
+  final emitter = DartEmitter();
+  print(DartFormatter().format('${animal.accept(emitter)}'));
+}
+```
+
+Outputs:
+```dart
+class Animal extends Organism {
+  void eat() => print('Yum!');
+}
+```
+
+Have a complicated set of dependencies for your generated code?
+`code_builder` supports automatic scoping of your ASTs to automatically
+use prefixes to avoid symbol conflicts:
+
+```dart
+import 'package:code_builder/code_builder.dart';
+import 'package:dart_style/dart_style.dart';
+
+void main() {
+  final library = Library((b) => b.body.addAll([
+        Method((b) => b
+          ..body = const Code('')
+          ..name = 'doThing'
+          ..returns = refer('Thing', 'package:a/a.dart')),
+        Method((b) => b
+          ..body = const Code('')
+          ..name = 'doOther'
+          ..returns = refer('Other', 'package:b/b.dart')),
+      ]));
+  final emitter = DartEmitter(Allocator.simplePrefixing());
+  print(DartFormatter().format('${library.accept(emitter)}'));
+}
+```
+
+Outputs:
+```dart
+import 'package:a/a.dart' as _i1;
+import 'package:b/b.dart' as _i2;
+
+_i1.Thing doThing() {}
+_i2.Other doOther() {}
+```
+
+## Contributing
+
+* Read and help us document common patterns over [at the wiki][wiki].
+* Is there a *bug* in the code? [File an issue][issue].
+
+If a feature is missing (the Dart language is always evolving) or you'd like an
+easier or better way to do something, consider [opening a pull request][pull].
+You can always [file an issue][issue], but generally speaking feature requests
+will be on a best-effort basis.
+
+> **NOTE**: Due to the evolving Dart SDK the local `dartfmt` must be used to
+> format this repository. You can run it simply from the command-line:
+>
+> ```sh
+> $ pub run dart_style:format -w .
+> ```
+
+[wiki]: https://github.com/dart-lang/code_builder/wiki
+[issue]: https://github.com/dart-lang/code_builder/issues
+[pull]: https://github.com/dart-lang/code_builder/pulls
+
+### Updating generated (`.g.dart`) files
+
+> **NOTE**: There is currently a limitation in `build_runner` that requires
+> a workaround for developing this package. We expect this to be unnecessary
+> in the future.
+
+Use [`build_runner`][build_runner]:
+
+```bash
+$ mv build.disabled.yaml build.yaml
+$ pub run build_runner build --delete-conflicting-outputs
+$ mv build.yaml build.disabled.yaml
+```
+
+[build_runner]: https://pub.dartlang.org/packages/build_runner
diff --git a/code_builder/analysis_options.yaml b/code_builder/analysis_options.yaml
new file mode 100644
index 0000000..b4b37b4
--- /dev/null
+++ b/code_builder/analysis_options.yaml
@@ -0,0 +1,55 @@
+analyzer:
+  strong-mode:
+    implicit-casts: false
+    implicit-dynamic: false
+
+linter:
+  rules:
+    # Error Rules
+    - avoid_empty_else
+    - comment_references
+    - control_flow_in_finally
+    - empty_statements
+    - hash_and_equals
+    - invariant_booleans
+    - iterable_contains_unrelated_type
+    - list_remove_unrelated_type
+    - no_adjacent_strings_in_list
+    - no_duplicate_case_values
+    - test_types_in_equals
+    - throw_in_finally
+    - unrelated_type_equality_checks
+    - valid_regexps
+
+    # Style Rules
+    - annotate_overrides
+    - avoid_init_to_null
+    - avoid_return_types_on_setters
+    - camel_case_types
+    - cascade_invocations
+    - constant_identifier_names
+    - directives_ordering
+    - empty_catches
+    - empty_constructor_bodies
+    - implementation_imports
+    - library_names
+    - library_prefixes
+    - non_constant_identifier_names
+    - omit_local_variable_types
+    - only_throw_errors
+    - prefer_adjacent_string_concatenation
+    - prefer_collection_literals
+    - prefer_const_constructors
+    - prefer_contains
+    - prefer_equal_for_default_values
+    - prefer_final_fields
+    - prefer_final_locals
+    - prefer_initializing_formals
+    - prefer_interpolation_to_compose_strings
+    - prefer_is_empty
+    - prefer_is_not_empty
+    - recursive_getters
+    - slash_for_doc_comments
+    - type_init_formals
+    - unnecessary_brace_in_string_interps
+    - unnecessary_this
diff --git a/code_builder/build.disabled.yaml b/code_builder/build.disabled.yaml
new file mode 100644
index 0000000..2eed503
--- /dev/null
+++ b/code_builder/build.disabled.yaml
@@ -0,0 +1,14 @@
+targets:
+  $default:
+    builders:
+      "|_built_value":
+
+builders:
+  _built_value:
+    target: ":code_builder"
+    import: "../../../tool/src/builder.dart"
+    builder_factories:
+      - "builtValueBuilder"
+    build_extensions:
+      ".dart": [".g.dart"]
+    build_to: "source"
diff --git a/code_builder/example/example.dart b/code_builder/example/example.dart
new file mode 100644
index 0000000..577752b
--- /dev/null
+++ b/code_builder/example/example.dart
@@ -0,0 +1,54 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'package:code_builder/code_builder.dart';
+import 'package:dart_style/dart_style.dart';
+
+final _dartfmt = DartFormatter();
+
+void main() {
+  print('animalClass():\n${'=' * 40}\n${animalClass()}');
+  print('scopedLibrary():\n${'=' * 40}\n${scopedLibrary()}');
+}
+
+/// Outputs:
+///
+/// ```dart
+/// class Animal extends Organism {
+///   void eat() => print('Yum!');
+/// }
+/// ```
+String animalClass() {
+  final animal = Class((b) => b
+    ..name = 'Animal'
+    ..extend = refer('Organism')
+    ..methods.add(Method.returnsVoid((b) => b
+      ..name = 'eat'
+      ..body = refer('print').call([literalString('Yum!')]).code)));
+  return _dartfmt.format('${animal.accept(DartEmitter())}');
+}
+
+/// Outputs:
+///
+/// ```dart
+/// import 'package:a/a.dart' as _i1;
+/// import 'package:b/b.dart' as _i2;
+///
+/// _i1.Thing doThing() {}
+/// _i2.Other doOther() {}
+/// ```
+String scopedLibrary() {
+  final methods = [
+    Method((b) => b
+      ..body = const Code('')
+      ..name = 'doThing'
+      ..returns = refer('Thing', 'package:a/a.dart')),
+    Method((b) => b
+      ..body = const Code('')
+      ..name = 'doOther'
+      ..returns = refer('Other', 'package:b/b.dart')),
+  ];
+  final library = Library((b) => b.body.addAll(methods));
+  return _dartfmt.format('${library.accept(DartEmitter.scoped())}');
+}
diff --git a/code_builder/lib/code_builder.dart b/code_builder/lib/code_builder.dart
new file mode 100644
index 0000000..2be4733
--- /dev/null
+++ b/code_builder/lib/code_builder.dart
@@ -0,0 +1,52 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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.
+
+export 'src/allocator.dart' show Allocator;
+export 'src/base.dart' show lazySpec, Spec;
+export 'src/emitter.dart' show DartEmitter;
+export 'src/matchers.dart' show equalsDart, EqualsDart;
+export 'src/specs/class.dart' show Class, ClassBuilder;
+export 'src/specs/code.dart'
+    show lazyCode, Block, BlockBuilder, Code, StaticCode, ScopedCode;
+export 'src/specs/constructor.dart' show Constructor, ConstructorBuilder;
+export 'src/specs/directive.dart'
+    show Directive, DirectiveType, DirectiveBuilder;
+export 'src/specs/expression.dart'
+    show
+        ToCodeExpression,
+        BinaryExpression,
+        CodeExpression,
+        Expression,
+        ExpressionEmitter,
+        ExpressionVisitor,
+        InvokeExpression,
+        InvokeExpressionType,
+        LiteralExpression,
+        LiteralListExpression,
+        literal,
+        literalNull,
+        literalNum,
+        literalBool,
+        literalList,
+        literalConstList,
+        literalSet,
+        literalConstSet,
+        literalMap,
+        literalConstMap,
+        literalString,
+        literalTrue,
+        literalFalse;
+export 'src/specs/field.dart' show Field, FieldBuilder, FieldModifier;
+export 'src/specs/library.dart' show Library, LibraryBuilder;
+export 'src/specs/method.dart'
+    show
+        Method,
+        MethodBuilder,
+        MethodModifier,
+        MethodType,
+        Parameter,
+        ParameterBuilder;
+export 'src/specs/reference.dart' show refer, Reference;
+export 'src/specs/type_function.dart' show FunctionType, FunctionTypeBuilder;
+export 'src/specs/type_reference.dart' show TypeReference, TypeReferenceBuilder;
diff --git a/code_builder/lib/src/allocator.dart b/code_builder/lib/src/allocator.dart
new file mode 100644
index 0000000..87315d5
--- /dev/null
+++ b/code_builder/lib/src/allocator.dart
@@ -0,0 +1,98 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'specs/directive.dart';
+import 'specs/reference.dart';
+
+/// Collects references and automatically allocates prefixes and imports.
+///
+/// `Allocator` takes out the manual work of deciding whether a symbol will
+/// clash with other imports in your generated code, or what imports are needed
+/// to resolve all symbols in your generated code.
+abstract class Allocator {
+  /// An allocator that does not prefix symbols nor collects imports.
+  static const Allocator none = _NullAllocator();
+
+  /// Creates a new default allocator that applies no prefixing.
+  factory Allocator() = _Allocator;
+
+  /// Creates a new allocator that applies naive prefixing to avoid conflicts.
+  ///
+  /// This implementation is not optimized for any particular code generation
+  /// style and instead takes a conservative approach of prefixing _every_
+  /// import except references to `dart:core` (which are considered always
+  /// imported).
+  ///
+  /// The prefixes are not guaranteed to be stable and cannot be expected to
+  /// have any particular value.
+  factory Allocator.simplePrefixing() = _PrefixedAllocator;
+
+  /// Returns a reference string given a [reference] object.
+  ///
+  /// For example, a no-op implementation:
+  /// ```dart
+  /// allocate(const Reference('List', 'dart:core')); // Returns 'List'.
+  /// ```
+  ///
+  /// Where-as an implementation that prefixes imports might output:
+  /// ```dart
+  /// allocate(const Reference('Foo', 'package:foo')); // Returns '_i1.Foo'.
+  /// ```
+  String allocate(Reference reference);
+
+  /// All imports that have so far been added implicitly via [allocate].
+  Iterable<Directive> get imports;
+}
+
+class _Allocator implements Allocator {
+  final _imports = Set<String>();
+
+  @override
+  String allocate(Reference reference) {
+    if (reference.url != null) {
+      _imports.add(reference.url);
+    }
+    return reference.symbol;
+  }
+
+  @override
+  Iterable<Directive> get imports {
+    return _imports.map((u) => Directive.import(u));
+  }
+}
+
+class _NullAllocator implements Allocator {
+  const _NullAllocator();
+
+  @override
+  String allocate(Reference reference) => reference.symbol;
+
+  @override
+  Iterable<Directive> get imports => const [];
+}
+
+class _PrefixedAllocator implements Allocator {
+  static const _doNotPrefix = ['dart:core'];
+
+  final _imports = <String, int>{};
+  var _keys = 1;
+
+  @override
+  String allocate(Reference reference) {
+    final symbol = reference.symbol;
+    if (reference.url == null || _doNotPrefix.contains(reference.url)) {
+      return symbol;
+    }
+    return '_i${_imports.putIfAbsent(reference.url, _nextKey)}.$symbol';
+  }
+
+  int _nextKey() => _keys++;
+
+  @override
+  Iterable<Directive> get imports {
+    return _imports.keys.map(
+      (u) => Directive.import(u, as: '_i${_imports[u]}'),
+    );
+  }
+}
diff --git a/code_builder/lib/src/base.dart b/code_builder/lib/src/base.dart
new file mode 100644
index 0000000..529dd5b
--- /dev/null
+++ b/code_builder/lib/src/base.dart
@@ -0,0 +1,23 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'visitors.dart';
+
+abstract class Spec {
+  R accept<R>(SpecVisitor<R> visitor, [R context]);
+}
+
+/// Returns a generic [Spec] that is lazily generated when visited.
+Spec lazySpec(Spec Function() generate) => _LazySpec(generate);
+
+class _LazySpec implements Spec {
+  final Spec Function() generate;
+
+  const _LazySpec(this.generate);
+
+  @override
+  R accept<R>(SpecVisitor<R> visitor, [R context]) {
+    return generate().accept(visitor, context);
+  }
+}
diff --git a/code_builder/lib/src/emitter.dart b/code_builder/lib/src/emitter.dart
new file mode 100644
index 0000000..d7a688f
--- /dev/null
+++ b/code_builder/lib/src/emitter.dart
@@ -0,0 +1,531 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'allocator.dart';
+import 'base.dart';
+import 'specs/class.dart';
+import 'specs/code.dart';
+import 'specs/constructor.dart';
+import 'specs/directive.dart';
+import 'specs/expression.dart';
+import 'specs/field.dart';
+import 'specs/library.dart';
+import 'specs/method.dart';
+import 'specs/reference.dart';
+import 'specs/type_function.dart';
+import 'specs/type_reference.dart';
+import 'visitors.dart';
+
+/// Helper method improving on [StringSink.writeAll].
+///
+/// For every `Spec` in [elements], executing [visit].
+///
+/// If [elements] is at least 2 elements, inserts [separator] delimiting them.
+StringSink visitAll<T>(
+  Iterable<T> elements,
+  StringSink output,
+  void visit(T element), [
+  String separator = ', ',
+]) {
+  // Basically, this whole method is an improvement on
+  //   output.writeAll(specs.map((s) => s.accept(visitor));
+  //
+  // ... which would allocate more StringBuffer(s) for a one-time use.
+  if (elements.isEmpty) {
+    return output;
+  }
+  final iterator = elements.iterator..moveNext();
+  visit(iterator.current);
+  while (iterator.moveNext()) {
+    output.write(separator);
+    visit(iterator.current);
+  }
+  return output;
+}
+
+class DartEmitter extends Object
+    with CodeEmitter, ExpressionEmitter
+    implements SpecVisitor<StringSink> {
+  @override
+  final Allocator allocator;
+
+  /// If directives should be ordered while emitting.
+  ///
+  /// Ordering rules follow the guidance in
+  /// [Effective Dart](https://www.dartlang.org/guides/language/effective-dart/style#ordering)
+  /// and the
+  /// [directives_ordering](http://dart-lang.github.io/linter/lints/directives_ordering.html)
+  /// lint.
+  final bool orderDirectives;
+
+  /// Creates a new instance of [DartEmitter].
+  ///
+  /// May specify an [Allocator] to use for symbols, otherwise uses a no-op.
+  DartEmitter([this.allocator = Allocator.none, bool orderDirectives = false])
+      : orderDirectives = orderDirectives ?? false;
+
+  /// Creates a new instance of [DartEmitter] with simple automatic imports.
+  factory DartEmitter.scoped({bool orderDirectives = false}) {
+    return DartEmitter(Allocator.simplePrefixing(), orderDirectives);
+  }
+
+  static bool _isLambdaBody(Code code) =>
+      code is ToCodeExpression && !code.isStatement;
+
+  /// Whether the provided [method] is considered a lambda method.
+  static bool _isLambdaMethod(Method method) =>
+      method.lambda ?? _isLambdaBody(method.body);
+
+  /// Whether the provided [constructor] is considered a lambda method.
+  static bool _isLambdaConstructor(Constructor constructor) =>
+      constructor.lambda ??
+      constructor.factory && _isLambdaBody(constructor.body);
+
+  @override
+  visitAnnotation(Expression spec, [StringSink output]) {
+    (output ??= StringBuffer()).write('@');
+    spec.accept(this, output);
+    output.write(' ');
+    return output;
+  }
+
+  @override
+  visitClass(Class spec, [StringSink output]) {
+    output ??= StringBuffer();
+    spec.docs.forEach(output.writeln);
+    spec.annotations.forEach((a) => visitAnnotation(a, output));
+    if (spec.abstract) {
+      output.write('abstract ');
+    }
+    output.write('class ${spec.name}');
+    visitTypeParameters(spec.types.map((r) => r.type), output);
+    if (spec.extend != null) {
+      output.write(' extends ');
+      spec.extend.type.accept(this, output);
+    }
+    if (spec.mixins.isNotEmpty) {
+      output
+        ..write(' with ')
+        ..writeAll(
+            spec.mixins.map<StringSink>((m) => m.type.accept(this)), ',');
+    }
+    if (spec.implements.isNotEmpty) {
+      output
+        ..write(' implements ')
+        ..writeAll(
+            spec.implements.map<StringSink>((m) => m.type.accept(this)), ',');
+    }
+    output.write(' {');
+    spec.constructors.forEach((c) {
+      visitConstructor(c, spec.name, output);
+      output.writeln();
+    });
+    spec.fields.forEach((f) {
+      visitField(f, output);
+      output.writeln();
+    });
+    spec.methods.forEach((m) {
+      visitMethod(m, output);
+      if (_isLambdaMethod(m)) {
+        output.write(';');
+      }
+      output.writeln();
+    });
+    output.writeln(' }');
+    return output;
+  }
+
+  @override
+  visitConstructor(Constructor spec, String clazz, [StringSink output]) {
+    output ??= StringBuffer();
+    spec.docs.forEach(output.writeln);
+    spec.annotations.forEach((a) => visitAnnotation(a, output));
+    if (spec.external) {
+      output.write('external ');
+    }
+    if (spec.factory) {
+      output.write('factory ');
+    }
+    if (spec.constant) {
+      output.write('const ');
+    }
+    output.write(clazz);
+    if (spec.name != null) {
+      output..write('.')..write(spec.name);
+    }
+    output.write('(');
+    if (spec.requiredParameters.isNotEmpty) {
+      var count = 0;
+      for (final p in spec.requiredParameters) {
+        count++;
+        _visitParameter(p, output);
+        if (spec.requiredParameters.length != count ||
+            spec.optionalParameters.isNotEmpty) {
+          output.write(', ');
+        }
+      }
+    }
+    if (spec.optionalParameters.isNotEmpty) {
+      final named = spec.optionalParameters.any((p) => p.named);
+      if (named) {
+        output.write('{');
+      } else {
+        output.write('[');
+      }
+      var count = 0;
+      for (final p in spec.optionalParameters) {
+        count++;
+        _visitParameter(p, output, optional: true, named: named);
+        if (spec.optionalParameters.length != count) {
+          output.write(', ');
+        }
+      }
+      if (named) {
+        output.write('}');
+      } else {
+        output.write(']');
+      }
+    }
+    output.write(')');
+    if (spec.initializers.isNotEmpty) {
+      output.write(' : ');
+      var count = 0;
+      for (final initializer in spec.initializers) {
+        count++;
+        initializer.accept(this, output);
+        if (count != spec.initializers.length) {
+          output.write(', ');
+        }
+      }
+    }
+    if (spec.redirect != null) {
+      output.write(' = ');
+      spec.redirect.type.accept(this, output);
+      output.write(';');
+    } else if (spec.body != null) {
+      if (_isLambdaConstructor(spec)) {
+        output.write(' => ');
+        spec.body.accept(this, output);
+        output.write(';');
+      } else {
+        output.write(' { ');
+        spec.body.accept(this, output);
+        output.write(' }');
+      }
+    } else {
+      output.write(';');
+    }
+    output.writeln();
+    return output;
+  }
+
+  @override
+  visitDirective(Directive spec, [StringSink output]) {
+    output ??= StringBuffer();
+    if (spec.type == DirectiveType.import) {
+      output.write('import ');
+    } else {
+      output.write('export ');
+    }
+    output.write("'${spec.url}'");
+    if (spec.as != null) {
+      if (spec.deferred) {
+        output.write(' deferred ');
+      }
+      output.write(' as ${spec.as}');
+    }
+    if (spec.show.isNotEmpty) {
+      output
+        ..write(' show ')
+        ..writeAll(spec.show, ', ');
+    } else if (spec.hide.isNotEmpty) {
+      output
+        ..write(' hide ')
+        ..writeAll(spec.hide, ', ');
+    }
+    output.write(';');
+    return output;
+  }
+
+  @override
+  visitField(Field spec, [StringSink output]) {
+    output ??= StringBuffer();
+    spec.docs.forEach(output.writeln);
+    spec.annotations.forEach((a) => visitAnnotation(a, output));
+    if (spec.static) {
+      output.write('static ');
+    }
+    switch (spec.modifier) {
+      case FieldModifier.var$:
+        if (spec.type == null) {
+          output.write('var ');
+        }
+        break;
+      case FieldModifier.final$:
+        output.write('final ');
+        break;
+      case FieldModifier.constant:
+        output.write('const ');
+        break;
+    }
+    if (spec.type != null) {
+      spec.type.type.accept(this, output);
+      output.write(' ');
+    }
+    output.write(spec.name);
+    if (spec.assignment != null) {
+      output.write(' = ');
+      startConstCode(spec.modifier == FieldModifier.constant, () {
+        spec.assignment.accept(this, output);
+      });
+    }
+    output.writeln(';');
+    return output;
+  }
+
+  @override
+  visitLibrary(Library spec, [StringSink output]) {
+    output ??= StringBuffer();
+    // Process the body first in order to prime the allocators.
+    final body = StringBuffer();
+    for (final spec in spec.body) {
+      spec.accept(this, body);
+      if (spec is Method && _isLambdaMethod(spec)) {
+        body.write(';');
+      }
+    }
+
+    final directives = <Directive>[]
+      ..addAll(spec.directives)
+      ..addAll(allocator.imports);
+
+    if (orderDirectives) {
+      directives.sort();
+    }
+
+    Directive previous;
+    for (final directive in directives) {
+      if (_newLineBetween(orderDirectives, previous, directive)) {
+        // Note: dartfmt handles creating new lines between directives.
+        // 2 lines are written here. The first one comes after the previous
+        // directive `;`, the second is the empty line.
+        output..writeln()..writeln();
+      }
+      directive.accept(this, output);
+      previous = directive;
+    }
+    output.write(body);
+    return output;
+  }
+
+  @override
+  visitFunctionType(FunctionType spec, [StringSink output]) {
+    output ??= StringBuffer();
+    if (spec.returnType != null) {
+      spec.returnType.accept(this, output);
+      output.write(' ');
+    }
+    output.write('Function');
+    if (spec.types.isNotEmpty) {
+      output.write('<');
+      visitAll<Reference>(spec.types, output, (spec) {
+        spec.accept(this, output);
+      });
+      output.write('>');
+    }
+    output.write('(');
+    visitAll<Reference>(spec.requiredParameters, output, (spec) {
+      spec.accept(this, output);
+    });
+    if (spec.requiredParameters.isNotEmpty &&
+        (spec.optionalParameters.isNotEmpty ||
+            spec.namedParameters.isNotEmpty)) {
+      output.write(', ');
+    }
+    if (spec.optionalParameters.isNotEmpty) {
+      output.write('[');
+      visitAll<Reference>(spec.optionalParameters, output, (spec) {
+        spec.accept(this, output);
+      });
+      output.write(']');
+    } else if (spec.namedParameters.isNotEmpty) {
+      output.write('{');
+      visitAll<String>(spec.namedParameters.keys, output, (name) {
+        spec.namedParameters[name].accept(this, output);
+        output..write(' ')..write(name);
+      });
+      output.write('}');
+    }
+    return output..write(')');
+  }
+
+  @override
+  visitMethod(Method spec, [StringSink output]) {
+    output ??= StringBuffer();
+    spec.docs.forEach(output.writeln);
+    spec.annotations.forEach((a) => visitAnnotation(a, output));
+    if (spec.external) {
+      output.write('external ');
+    }
+    if (spec.static) {
+      output.write('static ');
+    }
+    if (spec.returns != null) {
+      spec.returns.accept(this, output);
+      output.write(' ');
+    }
+    if (spec.type == MethodType.getter) {
+      output..write('get ')..write(spec.name);
+    } else {
+      if (spec.type == MethodType.setter) {
+        output.write('set ');
+      }
+      if (spec.name != null) {
+        output.write(spec.name);
+      }
+      visitTypeParameters(spec.types.map((r) => r.type), output);
+      output.write('(');
+      if (spec.requiredParameters.isNotEmpty) {
+        var count = 0;
+        for (final p in spec.requiredParameters) {
+          count++;
+          _visitParameter(p, output);
+          if (spec.requiredParameters.length != count ||
+              spec.optionalParameters.isNotEmpty) {
+            output.write(', ');
+          }
+        }
+      }
+      if (spec.optionalParameters.isNotEmpty) {
+        final named = spec.optionalParameters.any((p) => p.named);
+        if (named) {
+          output.write('{');
+        } else {
+          output.write('[');
+        }
+        var count = 0;
+        for (final p in spec.optionalParameters) {
+          count++;
+          _visitParameter(p, output, optional: true, named: named);
+          if (spec.optionalParameters.length != count) {
+            output.write(', ');
+          }
+        }
+        if (named) {
+          output.write('}');
+        } else {
+          output.write(']');
+        }
+      }
+      output.write(')');
+    }
+    if (spec.body != null) {
+      if (spec.modifier != null) {
+        switch (spec.modifier) {
+          case MethodModifier.async:
+            output.write(' async ');
+            break;
+          case MethodModifier.asyncStar:
+            output.write(' async* ');
+            break;
+          case MethodModifier.syncStar:
+            output.write(' sync* ');
+            break;
+        }
+      }
+      if (_isLambdaMethod(spec)) {
+        output.write(' => ');
+      } else {
+        output.write(' { ');
+      }
+      spec.body.accept(this, output);
+      if (!_isLambdaMethod(spec)) {
+        output.write(' } ');
+      }
+    } else {
+      output.write(';');
+    }
+    return output;
+  }
+
+  // Expose as a first-class visit function only if needed.
+  void _visitParameter(
+    Parameter spec,
+    StringSink output, {
+    bool optional = false,
+    bool named = false,
+  }) {
+    spec.docs.forEach(output.writeln);
+    spec.annotations.forEach((a) => visitAnnotation(a, output));
+    if (spec.type != null) {
+      spec.type.type.accept(this, output);
+      output.write(' ');
+    }
+    if (spec.toThis) {
+      output.write('this.');
+    }
+    output.write(spec.name);
+    if (optional && spec.defaultTo != null) {
+      output.write(' = ');
+      spec.defaultTo.accept(this, output);
+    }
+  }
+
+  @override
+  visitReference(Reference spec, [StringSink output]) {
+    return (output ??= StringBuffer())..write(allocator.allocate(spec));
+  }
+
+  @override
+  visitSpec(Spec spec, [StringSink output]) => spec.accept(this, output);
+
+  @override
+  visitType(TypeReference spec, [StringSink output]) {
+    output ??= StringBuffer();
+    // Intentionally not .accept to avoid stack overflow.
+    visitReference(spec, output);
+    if (spec.bound != null) {
+      output.write(' extends ');
+      spec.bound.type.accept(this, output);
+    }
+    visitTypeParameters(spec.types.map((r) => r.type), output);
+    return output;
+  }
+
+  @override
+  visitTypeParameters(Iterable<Reference> specs, [StringSink output]) {
+    output ??= StringBuffer();
+    if (specs.isNotEmpty) {
+      output
+        ..write('<')
+        ..writeAll(specs.map<StringSink>((s) => s.accept(this)), ',')
+        ..write('>');
+    }
+    return output;
+  }
+}
+
+/// Returns `true` if:
+///
+/// * [ordered] is `true`
+/// * [a] is non-`null`
+/// * If there should be an empty line before [b] if it's emitted after [a].
+bool _newLineBetween(bool ordered, Directive a, Directive b) {
+  if (!ordered) return false;
+  if (a == null) return false;
+
+  assert(b != null);
+
+  // Put a line between imports and exports
+  if (a.type != b.type) return true;
+
+  // Within exports, don't put in extra blank lines
+  if (a.type == DirectiveType.export) {
+    assert(b.type == DirectiveType.export);
+    return false;
+  }
+
+  // Return `true` if the schemes for [a] and [b] are different
+  return !Uri.parse(a.url).isScheme(Uri.parse(b.url).scheme);
+}
diff --git a/code_builder/lib/src/matchers.dart b/code_builder/lib/src/matchers.dart
new file mode 100644
index 0000000..dda8ac8
--- /dev/null
+++ b/code_builder/lib/src/matchers.dart
@@ -0,0 +1,69 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'package:matcher/matcher.dart';
+
+import 'base.dart';
+import 'emitter.dart';
+
+/// Encodes [spec] as Dart source code.
+String _dart(Spec spec, DartEmitter emitter) =>
+    EqualsDart._format(spec.accept<StringSink>(emitter).toString());
+
+/// Returns a matcher for Dart source code.
+Matcher equalsDart(
+  String source, [
+  DartEmitter emitter,
+]) =>
+    EqualsDart._(EqualsDart._format(source), emitter ?? DartEmitter());
+
+/// Implementation detail of using the [equalsDart] matcher.
+///
+/// See [EqualsDart.format] to specify the default source code formatter.
+class EqualsDart extends Matcher {
+  /// May override to provide a function to format Dart on [equalsDart].
+  ///
+  /// By default, uses [collapseWhitespace], but it is recommended to instead
+  /// use `dart_style` (dartfmt) where possible. See `test/common.dart` for an
+  /// example.
+  static String Function(String) format = (String source) {
+    return collapseWhitespace(source);
+  };
+
+  static String _format(String source) {
+    try {
+      return format(source).trim();
+    } catch (_) {
+      // Ignored on purpose, probably not exactly valid Dart code.
+      return collapseWhitespace(source).trim();
+    }
+  }
+
+  final DartEmitter _emitter;
+  final String _source;
+
+  const EqualsDart._(this._source, this._emitter);
+
+  @override
+  Description describe(Description description) => description.add(_source);
+
+  @override
+  Description describeMismatch(
+    covariant Spec item,
+    Description mismatchDescription,
+    state,
+    verbose,
+  ) {
+    final result = _dart(item, _emitter);
+    return equals(result).describeMismatch(
+      _source,
+      mismatchDescription.add(result),
+      state,
+      verbose,
+    );
+  }
+
+  @override
+  bool matches(covariant Spec item, _) => _dart(item, _emitter) == _source;
+}
diff --git a/code_builder/lib/src/mixins/annotations.dart b/code_builder/lib/src/mixins/annotations.dart
new file mode 100644
index 0000000..f8bc948
--- /dev/null
+++ b/code_builder/lib/src/mixins/annotations.dart
@@ -0,0 +1,19 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'package:built_collection/built_collection.dart';
+
+import '../specs/expression.dart';
+
+/// A type of AST node that can have metadata [annotations].
+abstract class HasAnnotations {
+  /// Annotations as metadata on the node.
+  BuiltList<Expression> get annotations;
+}
+
+/// Compliment to the [HasAnnotations] mixin for metadata [annotations].
+abstract class HasAnnotationsBuilder {
+  /// Annotations as metadata on the node.
+  ListBuilder<Expression> annotations;
+}
diff --git a/code_builder/lib/src/mixins/dartdoc.dart b/code_builder/lib/src/mixins/dartdoc.dart
new file mode 100644
index 0000000..ae44bd1
--- /dev/null
+++ b/code_builder/lib/src/mixins/dartdoc.dart
@@ -0,0 +1,15 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'package:built_collection/built_collection.dart';
+
+abstract class HasDartDocs {
+  /// Dart docs.
+  BuiltList<String> get docs;
+}
+
+abstract class HasDartDocsBuilder {
+  /// Dart docs.
+  ListBuilder<String> docs;
+}
diff --git a/code_builder/lib/src/mixins/generics.dart b/code_builder/lib/src/mixins/generics.dart
new file mode 100644
index 0000000..04a049e
--- /dev/null
+++ b/code_builder/lib/src/mixins/generics.dart
@@ -0,0 +1,17 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'package:built_collection/built_collection.dart';
+
+import '../specs/reference.dart';
+
+abstract class HasGenerics {
+  /// Generic type parameters.
+  BuiltList<Reference> get types;
+}
+
+abstract class HasGenericsBuilder {
+  /// Generic type parameters.
+  ListBuilder<Reference> types;
+}
diff --git a/code_builder/lib/src/specs/class.dart b/code_builder/lib/src/specs/class.dart
new file mode 100644
index 0000000..3e596a6
--- /dev/null
+++ b/code_builder/lib/src/specs/class.dart
@@ -0,0 +1,94 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'package:built_value/built_value.dart';
+import 'package:built_collection/built_collection.dart';
+import 'package:meta/meta.dart';
+
+import '../base.dart';
+import '../mixins/annotations.dart';
+import '../mixins/dartdoc.dart';
+import '../mixins/generics.dart';
+import '../visitors.dart';
+import 'constructor.dart';
+import 'expression.dart';
+import 'field.dart';
+import 'method.dart';
+import 'reference.dart';
+
+part 'class.g.dart';
+
+@immutable
+abstract class Class extends Object
+    with HasAnnotations, HasDartDocs, HasGenerics
+    implements Built<Class, ClassBuilder>, Spec {
+  factory Class([void updates(ClassBuilder b)]) = _$Class;
+
+  Class._();
+
+  /// Whether the class is `abstract`.
+  bool get abstract;
+
+  @override
+  BuiltList<Expression> get annotations;
+
+  @override
+  BuiltList<String> get docs;
+
+  @nullable
+  Reference get extend;
+
+  BuiltList<Reference> get implements;
+
+  BuiltList<Reference> get mixins;
+
+  @override
+  BuiltList<Reference> get types;
+
+  BuiltList<Constructor> get constructors;
+  BuiltList<Method> get methods;
+  BuiltList<Field> get fields;
+
+  /// Name of the class.
+  String get name;
+
+  @override
+  R accept<R>(
+    SpecVisitor<R> visitor, [
+    R context,
+  ]) =>
+      visitor.visitClass(this, context);
+}
+
+abstract class ClassBuilder extends Object
+    with HasAnnotationsBuilder, HasDartDocsBuilder, HasGenericsBuilder
+    implements Builder<Class, ClassBuilder> {
+  factory ClassBuilder() = _$ClassBuilder;
+
+  ClassBuilder._();
+
+  /// Whether the class is `abstract`.
+  bool abstract = false;
+
+  @override
+  ListBuilder<Expression> annotations = ListBuilder<Expression>();
+
+  @override
+  ListBuilder<String> docs = ListBuilder<String>();
+
+  Reference extend;
+
+  ListBuilder<Reference> implements = ListBuilder<Reference>();
+  ListBuilder<Reference> mixins = ListBuilder<Reference>();
+
+  @override
+  ListBuilder<Reference> types = ListBuilder<Reference>();
+
+  ListBuilder<Constructor> constructors = ListBuilder<Constructor>();
+  ListBuilder<Method> methods = ListBuilder<Method>();
+  ListBuilder<Field> fields = ListBuilder<Field>();
+
+  /// Name of the class.
+  String name;
+}
diff --git a/code_builder/lib/src/specs/class.g.dart b/code_builder/lib/src/specs/class.g.dart
new file mode 100644
index 0000000..68cff19
--- /dev/null
+++ b/code_builder/lib/src/specs/class.g.dart
@@ -0,0 +1,353 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'class.dart';
+
+// **************************************************************************
+// BuiltValueGenerator
+// **************************************************************************
+
+// ignore_for_file: always_put_control_body_on_new_line
+// ignore_for_file: annotate_overrides
+// ignore_for_file: avoid_annotating_with_dynamic
+// ignore_for_file: avoid_catches_without_on_clauses
+// ignore_for_file: avoid_returning_this
+// ignore_for_file: lines_longer_than_80_chars
+// ignore_for_file: omit_local_variable_types
+// ignore_for_file: prefer_expression_function_bodies
+// ignore_for_file: sort_constructors_first
+
+class _$Class extends Class {
+  @override
+  final bool abstract;
+  @override
+  final BuiltList<Expression> annotations;
+  @override
+  final BuiltList<String> docs;
+  @override
+  final Reference extend;
+  @override
+  final BuiltList<Reference> implements;
+  @override
+  final BuiltList<Reference> mixins;
+  @override
+  final BuiltList<Reference> types;
+  @override
+  final BuiltList<Constructor> constructors;
+  @override
+  final BuiltList<Method> methods;
+  @override
+  final BuiltList<Field> fields;
+  @override
+  final String name;
+
+  factory _$Class([void updates(ClassBuilder b)]) =>
+      (new ClassBuilder()..update(updates)).build() as _$Class;
+
+  _$Class._(
+      {this.abstract,
+      this.annotations,
+      this.docs,
+      this.extend,
+      this.implements,
+      this.mixins,
+      this.types,
+      this.constructors,
+      this.methods,
+      this.fields,
+      this.name})
+      : super._() {
+    if (abstract == null)
+      throw new BuiltValueNullFieldError('Class', 'abstract');
+    if (annotations == null)
+      throw new BuiltValueNullFieldError('Class', 'annotations');
+    if (docs == null) throw new BuiltValueNullFieldError('Class', 'docs');
+    if (implements == null)
+      throw new BuiltValueNullFieldError('Class', 'implements');
+    if (mixins == null) throw new BuiltValueNullFieldError('Class', 'mixins');
+    if (types == null) throw new BuiltValueNullFieldError('Class', 'types');
+    if (constructors == null)
+      throw new BuiltValueNullFieldError('Class', 'constructors');
+    if (methods == null) throw new BuiltValueNullFieldError('Class', 'methods');
+    if (fields == null) throw new BuiltValueNullFieldError('Class', 'fields');
+    if (name == null) throw new BuiltValueNullFieldError('Class', 'name');
+  }
+
+  @override
+  Class rebuild(void updates(ClassBuilder b)) =>
+      (toBuilder()..update(updates)).build();
+
+  @override
+  _$ClassBuilder toBuilder() => new _$ClassBuilder()..replace(this);
+
+  @override
+  bool operator ==(dynamic other) {
+    if (identical(other, this)) return true;
+    if (other is! Class) return false;
+    return abstract == other.abstract &&
+        annotations == other.annotations &&
+        docs == other.docs &&
+        extend == other.extend &&
+        implements == other.implements &&
+        mixins == other.mixins &&
+        types == other.types &&
+        constructors == other.constructors &&
+        methods == other.methods &&
+        fields == other.fields &&
+        name == other.name;
+  }
+
+  @override
+  int get hashCode {
+    return $jf($jc(
+        $jc(
+            $jc(
+                $jc(
+                    $jc(
+                        $jc(
+                            $jc(
+                                $jc(
+                                    $jc(
+                                        $jc($jc(0, abstract.hashCode),
+                                            annotations.hashCode),
+                                        docs.hashCode),
+                                    extend.hashCode),
+                                implements.hashCode),
+                            mixins.hashCode),
+                        types.hashCode),
+                    constructors.hashCode),
+                methods.hashCode),
+            fields.hashCode),
+        name.hashCode));
+  }
+
+  @override
+  String toString() {
+    return (newBuiltValueToStringHelper('Class')
+          ..add('abstract', abstract)
+          ..add('annotations', annotations)
+          ..add('docs', docs)
+          ..add('extend', extend)
+          ..add('implements', implements)
+          ..add('mixins', mixins)
+          ..add('types', types)
+          ..add('constructors', constructors)
+          ..add('methods', methods)
+          ..add('fields', fields)
+          ..add('name', name))
+        .toString();
+  }
+}
+
+class _$ClassBuilder extends ClassBuilder {
+  _$Class _$v;
+
+  @override
+  bool get abstract {
+    _$this;
+    return super.abstract;
+  }
+
+  @override
+  set abstract(bool abstract) {
+    _$this;
+    super.abstract = abstract;
+  }
+
+  @override
+  ListBuilder<Expression> get annotations {
+    _$this;
+    return super.annotations ??= new ListBuilder<Expression>();
+  }
+
+  @override
+  set annotations(ListBuilder<Expression> annotations) {
+    _$this;
+    super.annotations = annotations;
+  }
+
+  @override
+  ListBuilder<String> get docs {
+    _$this;
+    return super.docs ??= new ListBuilder<String>();
+  }
+
+  @override
+  set docs(ListBuilder<String> docs) {
+    _$this;
+    super.docs = docs;
+  }
+
+  @override
+  Reference get extend {
+    _$this;
+    return super.extend;
+  }
+
+  @override
+  set extend(Reference extend) {
+    _$this;
+    super.extend = extend;
+  }
+
+  @override
+  ListBuilder<Reference> get implements {
+    _$this;
+    return super.implements ??= new ListBuilder<Reference>();
+  }
+
+  @override
+  set implements(ListBuilder<Reference> implements) {
+    _$this;
+    super.implements = implements;
+  }
+
+  @override
+  ListBuilder<Reference> get mixins {
+    _$this;
+    return super.mixins ??= new ListBuilder<Reference>();
+  }
+
+  @override
+  set mixins(ListBuilder<Reference> mixins) {
+    _$this;
+    super.mixins = mixins;
+  }
+
+  @override
+  ListBuilder<Reference> get types {
+    _$this;
+    return super.types ??= new ListBuilder<Reference>();
+  }
+
+  @override
+  set types(ListBuilder<Reference> types) {
+    _$this;
+    super.types = types;
+  }
+
+  @override
+  ListBuilder<Constructor> get constructors {
+    _$this;
+    return super.constructors ??= new ListBuilder<Constructor>();
+  }
+
+  @override
+  set constructors(ListBuilder<Constructor> constructors) {
+    _$this;
+    super.constructors = constructors;
+  }
+
+  @override
+  ListBuilder<Method> get methods {
+    _$this;
+    return super.methods ??= new ListBuilder<Method>();
+  }
+
+  @override
+  set methods(ListBuilder<Method> methods) {
+    _$this;
+    super.methods = methods;
+  }
+
+  @override
+  ListBuilder<Field> get fields {
+    _$this;
+    return super.fields ??= new ListBuilder<Field>();
+  }
+
+  @override
+  set fields(ListBuilder<Field> fields) {
+    _$this;
+    super.fields = fields;
+  }
+
+  @override
+  String get name {
+    _$this;
+    return super.name;
+  }
+
+  @override
+  set name(String name) {
+    _$this;
+    super.name = name;
+  }
+
+  _$ClassBuilder() : super._();
+
+  ClassBuilder get _$this {
+    if (_$v != null) {
+      super.abstract = _$v.abstract;
+      super.annotations = _$v.annotations?.toBuilder();
+      super.docs = _$v.docs?.toBuilder();
+      super.extend = _$v.extend;
+      super.implements = _$v.implements?.toBuilder();
+      super.mixins = _$v.mixins?.toBuilder();
+      super.types = _$v.types?.toBuilder();
+      super.constructors = _$v.constructors?.toBuilder();
+      super.methods = _$v.methods?.toBuilder();
+      super.fields = _$v.fields?.toBuilder();
+      super.name = _$v.name;
+      _$v = null;
+    }
+    return this;
+  }
+
+  @override
+  void replace(Class other) {
+    if (other == null) throw new ArgumentError.notNull('other');
+    _$v = other as _$Class;
+  }
+
+  @override
+  void update(void updates(ClassBuilder b)) {
+    if (updates != null) updates(this);
+  }
+
+  @override
+  _$Class build() {
+    _$Class _$result;
+    try {
+      _$result = _$v ??
+          new _$Class._(
+              abstract: abstract,
+              annotations: annotations.build(),
+              docs: docs.build(),
+              extend: extend,
+              implements: implements.build(),
+              mixins: mixins.build(),
+              types: types.build(),
+              constructors: constructors.build(),
+              methods: methods.build(),
+              fields: fields.build(),
+              name: name);
+    } catch (_) {
+      String _$failedField;
+      try {
+        _$failedField = 'annotations';
+        annotations.build();
+        _$failedField = 'docs';
+        docs.build();
+
+        _$failedField = 'implements';
+        implements.build();
+        _$failedField = 'mixins';
+        mixins.build();
+        _$failedField = 'types';
+        types.build();
+        _$failedField = 'constructors';
+        constructors.build();
+        _$failedField = 'methods';
+        methods.build();
+        _$failedField = 'fields';
+        fields.build();
+      } catch (e) {
+        throw new BuiltValueNestedFieldError(
+            'Class', _$failedField, e.toString());
+      }
+      rethrow;
+    }
+    replace(_$result);
+    return _$result;
+  }
+}
diff --git a/code_builder/lib/src/specs/code.dart b/code_builder/lib/src/specs/code.dart
new file mode 100644
index 0000000..e14c102
--- /dev/null
+++ b/code_builder/lib/src/specs/code.dart
@@ -0,0 +1,170 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'package:built_value/built_value.dart';
+import 'package:built_collection/built_collection.dart';
+import 'package:meta/meta.dart';
+
+import '../allocator.dart';
+import '../base.dart';
+import '../emitter.dart';
+import '../visitors.dart';
+
+import 'expression.dart';
+import 'reference.dart';
+
+part 'code.g.dart';
+
+/// Returns a scoped symbol to [Reference], with an import prefix if needed.
+///
+/// This is short-hand for [Allocator.allocate] in most implementations.
+typedef String Allocate(Reference reference);
+
+/// Represents arbitrary Dart code (either expressions or statements).
+///
+/// See the various constructors for details.
+abstract class Code implements Spec {
+  /// Create a simple code body based on a static string.
+  const factory Code(String code) = StaticCode._;
+
+  /// Create a code based that may use a provided [Allocator] for scoping:
+  ///
+  /// ```dart
+  /// // Emits `_i123.FooType()`, where `_i123` is the import prefix.
+  ///
+  /// Code.scope((a) {
+  ///   return '${a.allocate(fooType)}()'
+  /// });
+  /// ```
+  const factory Code.scope(
+    String Function(Allocate allocate) scope,
+  ) = ScopedCode._;
+
+  @override
+  R accept<R>(covariant CodeVisitor<R> visitor, [R context]);
+}
+
+/// Represents blocks of statements of Dart code.
+abstract class Block implements Built<Block, BlockBuilder>, Code, Spec {
+  factory Block([void updates(BlockBuilder b)]) = _$Block;
+
+  factory Block.of(Iterable<Code> statements) {
+    return Block((b) => b..statements.addAll(statements));
+  }
+
+  Block._();
+
+  @override
+  R accept<R>(covariant CodeVisitor<R> visitor, [R context]) {
+    return visitor.visitBlock(this, context);
+  }
+
+  BuiltList<Code> get statements;
+}
+
+abstract class BlockBuilder implements Builder<Block, BlockBuilder> {
+  factory BlockBuilder() = _$BlockBuilder;
+
+  BlockBuilder._();
+
+  /// Adds an [expression] to [statements].
+  ///
+  /// **NOTE**: Not all expressions are _useful_ statements.
+  void addExpression(Expression expression) {
+    statements.add(expression.statement);
+  }
+
+  ListBuilder<Code> statements = ListBuilder<Code>();
+}
+
+/// Knowledge of different types of blocks of code in Dart.
+///
+/// **INTERNAL ONLY**.
+abstract class CodeVisitor<T> implements SpecVisitor<T> {
+  T visitBlock(Block code, [T context]);
+  T visitStaticCode(StaticCode code, [T context]);
+  T visitScopedCode(ScopedCode code, [T context]);
+}
+
+/// Knowledge of how to write valid Dart code from [CodeVisitor].
+abstract class CodeEmitter implements CodeVisitor<StringSink> {
+  @protected
+  Allocator get allocator;
+
+  @override
+  visitBlock(Block block, [StringSink output]) {
+    output ??= StringBuffer();
+    return visitAll<Code>(block.statements, output, (statement) {
+      statement.accept(this, output);
+    }, '\n');
+  }
+
+  @override
+  visitStaticCode(StaticCode code, [StringSink output]) {
+    output ??= StringBuffer();
+    return output..write(code.code);
+  }
+
+  @override
+  visitScopedCode(ScopedCode code, [StringSink output]) {
+    output ??= StringBuffer();
+    return output..write(code.code(allocator.allocate));
+  }
+}
+
+/// Represents a code block that requires lazy visiting.
+class LazyCode implements Code {
+  final Spec Function(SpecVisitor) generate;
+
+  const LazyCode._(this.generate);
+
+  @override
+  R accept<R>(CodeVisitor<R> visitor, [R context]) {
+    return generate(visitor).accept(visitor, context);
+  }
+}
+
+/// Returns a generic [Code] that is lazily generated when visited.
+Code lazyCode(Code Function() generate) => _LazyCode(generate);
+
+class _LazyCode implements Code {
+  final Code Function() generate;
+
+  const _LazyCode(this.generate);
+
+  @override
+  R accept<R>(CodeVisitor<R> visitor, [R context]) {
+    return generate().accept(visitor, context);
+  }
+}
+
+/// Represents a simple, literal code block to be inserted as-is.
+class StaticCode implements Code {
+  final String code;
+
+  const StaticCode._(this.code);
+
+  @override
+  R accept<R>(CodeVisitor<R> visitor, [R context]) {
+    return visitor.visitStaticCode(this, context);
+  }
+
+  @override
+  String toString() => code;
+}
+
+/// Represents a code block that may require scoping.
+class ScopedCode implements Code {
+  final String Function(Allocate) code;
+
+  const ScopedCode._(this.code);
+
+  @override
+  R accept<R>(CodeVisitor<R> visitor, [R context]) {
+    return visitor.visitScopedCode(this, context);
+  }
+
+  @override
+  String toString() => code((ref) => ref.symbol);
+}
diff --git a/code_builder/lib/src/specs/code.g.dart b/code_builder/lib/src/specs/code.g.dart
new file mode 100644
index 0000000..5243ec8
--- /dev/null
+++ b/code_builder/lib/src/specs/code.g.dart
@@ -0,0 +1,112 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'code.dart';
+
+// **************************************************************************
+// BuiltValueGenerator
+// **************************************************************************
+
+// ignore_for_file: always_put_control_body_on_new_line
+// ignore_for_file: annotate_overrides
+// ignore_for_file: avoid_annotating_with_dynamic
+// ignore_for_file: avoid_catches_without_on_clauses
+// ignore_for_file: avoid_returning_this
+// ignore_for_file: lines_longer_than_80_chars
+// ignore_for_file: omit_local_variable_types
+// ignore_for_file: prefer_expression_function_bodies
+// ignore_for_file: sort_constructors_first
+
+class _$Block extends Block {
+  @override
+  final BuiltList<Code> statements;
+
+  factory _$Block([void updates(BlockBuilder b)]) =>
+      (new BlockBuilder()..update(updates)).build() as _$Block;
+
+  _$Block._({this.statements}) : super._() {
+    if (statements == null)
+      throw new BuiltValueNullFieldError('Block', 'statements');
+  }
+
+  @override
+  Block rebuild(void updates(BlockBuilder b)) =>
+      (toBuilder()..update(updates)).build();
+
+  @override
+  _$BlockBuilder toBuilder() => new _$BlockBuilder()..replace(this);
+
+  @override
+  bool operator ==(dynamic other) {
+    if (identical(other, this)) return true;
+    if (other is! Block) return false;
+    return statements == other.statements;
+  }
+
+  @override
+  int get hashCode {
+    return $jf($jc(0, statements.hashCode));
+  }
+
+  @override
+  String toString() {
+    return (newBuiltValueToStringHelper('Block')..add('statements', statements))
+        .toString();
+  }
+}
+
+class _$BlockBuilder extends BlockBuilder {
+  _$Block _$v;
+
+  @override
+  ListBuilder<Code> get statements {
+    _$this;
+    return super.statements ??= new ListBuilder<Code>();
+  }
+
+  @override
+  set statements(ListBuilder<Code> statements) {
+    _$this;
+    super.statements = statements;
+  }
+
+  _$BlockBuilder() : super._();
+
+  BlockBuilder get _$this {
+    if (_$v != null) {
+      super.statements = _$v.statements?.toBuilder();
+      _$v = null;
+    }
+    return this;
+  }
+
+  @override
+  void replace(Block other) {
+    if (other == null) throw new ArgumentError.notNull('other');
+    _$v = other as _$Block;
+  }
+
+  @override
+  void update(void updates(BlockBuilder b)) {
+    if (updates != null) updates(this);
+  }
+
+  @override
+  _$Block build() {
+    _$Block _$result;
+    try {
+      _$result = _$v ?? new _$Block._(statements: statements.build());
+    } catch (_) {
+      String _$failedField;
+      try {
+        _$failedField = 'statements';
+        statements.build();
+      } catch (e) {
+        throw new BuiltValueNestedFieldError(
+            'Block', _$failedField, e.toString());
+      }
+      rethrow;
+    }
+    replace(_$result);
+    return _$result;
+  }
+}
diff --git a/code_builder/lib/src/specs/constructor.dart b/code_builder/lib/src/specs/constructor.dart
new file mode 100644
index 0000000..04fcc70
--- /dev/null
+++ b/code_builder/lib/src/specs/constructor.dart
@@ -0,0 +1,110 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'package:built_value/built_value.dart';
+import 'package:built_collection/built_collection.dart';
+import 'package:meta/meta.dart';
+
+import '../mixins/annotations.dart';
+import '../mixins/dartdoc.dart';
+import 'code.dart';
+import 'expression.dart';
+import 'method.dart';
+import 'reference.dart';
+
+part 'constructor.g.dart';
+
+@immutable
+abstract class Constructor extends Object
+    with HasAnnotations, HasDartDocs
+    implements Built<Constructor, ConstructorBuilder> {
+  factory Constructor([void updates(ConstructorBuilder b)]) = _$Constructor;
+
+  Constructor._();
+
+  @override
+  BuiltList<Expression> get annotations;
+
+  @override
+  BuiltList<String> get docs;
+
+  /// Optional parameters.
+  BuiltList<Parameter> get optionalParameters;
+
+  /// Required parameters.
+  BuiltList<Parameter> get requiredParameters;
+
+  /// Constructor initializer statements.
+  BuiltList<Code> get initializers;
+
+  /// Body of the method.
+  @nullable
+  Code get body;
+
+  /// Whether the constructor should be prefixed with `external`.
+  bool get external;
+
+  /// Whether the constructor should be prefixed with `const`.
+  bool get constant;
+
+  /// Whether this constructor should be prefixed with `factory`.
+  bool get factory;
+
+  /// Whether this constructor is a simple lambda expression.
+  @nullable
+  bool get lambda;
+
+  /// Name of the constructor - optional.
+  @nullable
+  String get name;
+
+  /// If non-null, redirect to this constructor.
+  @nullable
+  Reference get redirect;
+}
+
+abstract class ConstructorBuilder extends Object
+    with HasAnnotationsBuilder, HasDartDocsBuilder
+    implements Builder<Constructor, ConstructorBuilder> {
+  factory ConstructorBuilder() = _$ConstructorBuilder;
+
+  ConstructorBuilder._();
+
+  @override
+  ListBuilder<Expression> annotations = ListBuilder<Expression>();
+
+  @override
+  ListBuilder<String> docs = ListBuilder<String>();
+
+  /// Optional parameters.
+  ListBuilder<Parameter> optionalParameters = ListBuilder<Parameter>();
+
+  /// Required parameters.
+  ListBuilder<Parameter> requiredParameters = ListBuilder<Parameter>();
+
+  /// Constructor initializer statements.
+  ListBuilder<Code> initializers = ListBuilder<Code>();
+
+  /// Body of the constructor.
+  Code body;
+
+  /// Whether the constructor should be prefixed with `const`.
+  bool constant = false;
+
+  /// Whether the constructor should be prefixed with `external`.
+  bool external = false;
+
+  /// Whether this constructor should be prefixed with `factory`.
+  bool factory = false;
+
+  /// Whether this constructor is a simple lambda expression.
+  bool lambda;
+
+  /// Name of the constructor - optional.
+  String name;
+
+  /// If non-null, redirect to this constructor.
+  @nullable
+  Reference redirect;
+}
diff --git a/code_builder/lib/src/specs/constructor.g.dart b/code_builder/lib/src/specs/constructor.g.dart
new file mode 100644
index 0000000..efc08f2
--- /dev/null
+++ b/code_builder/lib/src/specs/constructor.g.dart
@@ -0,0 +1,368 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'constructor.dart';
+
+// **************************************************************************
+// BuiltValueGenerator
+// **************************************************************************
+
+// ignore_for_file: always_put_control_body_on_new_line
+// ignore_for_file: annotate_overrides
+// ignore_for_file: avoid_annotating_with_dynamic
+// ignore_for_file: avoid_catches_without_on_clauses
+// ignore_for_file: avoid_returning_this
+// ignore_for_file: lines_longer_than_80_chars
+// ignore_for_file: omit_local_variable_types
+// ignore_for_file: prefer_expression_function_bodies
+// ignore_for_file: sort_constructors_first
+
+class _$Constructor extends Constructor {
+  @override
+  final BuiltList<Expression> annotations;
+  @override
+  final BuiltList<String> docs;
+  @override
+  final BuiltList<Parameter> optionalParameters;
+  @override
+  final BuiltList<Parameter> requiredParameters;
+  @override
+  final BuiltList<Code> initializers;
+  @override
+  final Code body;
+  @override
+  final bool external;
+  @override
+  final bool constant;
+  @override
+  final bool factory;
+  @override
+  final bool lambda;
+  @override
+  final String name;
+  @override
+  final Reference redirect;
+
+  factory _$Constructor([void updates(ConstructorBuilder b)]) =>
+      (new ConstructorBuilder()..update(updates)).build() as _$Constructor;
+
+  _$Constructor._(
+      {this.annotations,
+      this.docs,
+      this.optionalParameters,
+      this.requiredParameters,
+      this.initializers,
+      this.body,
+      this.external,
+      this.constant,
+      this.factory,
+      this.lambda,
+      this.name,
+      this.redirect})
+      : super._() {
+    if (annotations == null)
+      throw new BuiltValueNullFieldError('Constructor', 'annotations');
+    if (docs == null) throw new BuiltValueNullFieldError('Constructor', 'docs');
+    if (optionalParameters == null)
+      throw new BuiltValueNullFieldError('Constructor', 'optionalParameters');
+    if (requiredParameters == null)
+      throw new BuiltValueNullFieldError('Constructor', 'requiredParameters');
+    if (initializers == null)
+      throw new BuiltValueNullFieldError('Constructor', 'initializers');
+    if (external == null)
+      throw new BuiltValueNullFieldError('Constructor', 'external');
+    if (constant == null)
+      throw new BuiltValueNullFieldError('Constructor', 'constant');
+    if (factory == null)
+      throw new BuiltValueNullFieldError('Constructor', 'factory');
+  }
+
+  @override
+  Constructor rebuild(void updates(ConstructorBuilder b)) =>
+      (toBuilder()..update(updates)).build();
+
+  @override
+  _$ConstructorBuilder toBuilder() => new _$ConstructorBuilder()..replace(this);
+
+  @override
+  bool operator ==(dynamic other) {
+    if (identical(other, this)) return true;
+    if (other is! Constructor) return false;
+    return annotations == other.annotations &&
+        docs == other.docs &&
+        optionalParameters == other.optionalParameters &&
+        requiredParameters == other.requiredParameters &&
+        initializers == other.initializers &&
+        body == other.body &&
+        external == other.external &&
+        constant == other.constant &&
+        factory == other.factory &&
+        lambda == other.lambda &&
+        name == other.name &&
+        redirect == other.redirect;
+  }
+
+  @override
+  int get hashCode {
+    return $jf($jc(
+        $jc(
+            $jc(
+                $jc(
+                    $jc(
+                        $jc(
+                            $jc(
+                                $jc(
+                                    $jc(
+                                        $jc(
+                                            $jc($jc(0, annotations.hashCode),
+                                                docs.hashCode),
+                                            optionalParameters.hashCode),
+                                        requiredParameters.hashCode),
+                                    initializers.hashCode),
+                                body.hashCode),
+                            external.hashCode),
+                        constant.hashCode),
+                    factory.hashCode),
+                lambda.hashCode),
+            name.hashCode),
+        redirect.hashCode));
+  }
+
+  @override
+  String toString() {
+    return (newBuiltValueToStringHelper('Constructor')
+          ..add('annotations', annotations)
+          ..add('docs', docs)
+          ..add('optionalParameters', optionalParameters)
+          ..add('requiredParameters', requiredParameters)
+          ..add('initializers', initializers)
+          ..add('body', body)
+          ..add('external', external)
+          ..add('constant', constant)
+          ..add('factory', factory)
+          ..add('lambda', lambda)
+          ..add('name', name)
+          ..add('redirect', redirect))
+        .toString();
+  }
+}
+
+class _$ConstructorBuilder extends ConstructorBuilder {
+  _$Constructor _$v;
+
+  @override
+  ListBuilder<Expression> get annotations {
+    _$this;
+    return super.annotations ??= new ListBuilder<Expression>();
+  }
+
+  @override
+  set annotations(ListBuilder<Expression> annotations) {
+    _$this;
+    super.annotations = annotations;
+  }
+
+  @override
+  ListBuilder<String> get docs {
+    _$this;
+    return super.docs ??= new ListBuilder<String>();
+  }
+
+  @override
+  set docs(ListBuilder<String> docs) {
+    _$this;
+    super.docs = docs;
+  }
+
+  @override
+  ListBuilder<Parameter> get optionalParameters {
+    _$this;
+    return super.optionalParameters ??= new ListBuilder<Parameter>();
+  }
+
+  @override
+  set optionalParameters(ListBuilder<Parameter> optionalParameters) {
+    _$this;
+    super.optionalParameters = optionalParameters;
+  }
+
+  @override
+  ListBuilder<Parameter> get requiredParameters {
+    _$this;
+    return super.requiredParameters ??= new ListBuilder<Parameter>();
+  }
+
+  @override
+  set requiredParameters(ListBuilder<Parameter> requiredParameters) {
+    _$this;
+    super.requiredParameters = requiredParameters;
+  }
+
+  @override
+  ListBuilder<Code> get initializers {
+    _$this;
+    return super.initializers ??= new ListBuilder<Code>();
+  }
+
+  @override
+  set initializers(ListBuilder<Code> initializers) {
+    _$this;
+    super.initializers = initializers;
+  }
+
+  @override
+  Code get body {
+    _$this;
+    return super.body;
+  }
+
+  @override
+  set body(Code body) {
+    _$this;
+    super.body = body;
+  }
+
+  @override
+  bool get external {
+    _$this;
+    return super.external;
+  }
+
+  @override
+  set external(bool external) {
+    _$this;
+    super.external = external;
+  }
+
+  @override
+  bool get constant {
+    _$this;
+    return super.constant;
+  }
+
+  @override
+  set constant(bool constant) {
+    _$this;
+    super.constant = constant;
+  }
+
+  @override
+  bool get factory {
+    _$this;
+    return super.factory;
+  }
+
+  @override
+  set factory(bool factory) {
+    _$this;
+    super.factory = factory;
+  }
+
+  @override
+  bool get lambda {
+    _$this;
+    return super.lambda;
+  }
+
+  @override
+  set lambda(bool lambda) {
+    _$this;
+    super.lambda = lambda;
+  }
+
+  @override
+  String get name {
+    _$this;
+    return super.name;
+  }
+
+  @override
+  set name(String name) {
+    _$this;
+    super.name = name;
+  }
+
+  @override
+  Reference get redirect {
+    _$this;
+    return super.redirect;
+  }
+
+  @override
+  set redirect(Reference redirect) {
+    _$this;
+    super.redirect = redirect;
+  }
+
+  _$ConstructorBuilder() : super._();
+
+  ConstructorBuilder get _$this {
+    if (_$v != null) {
+      super.annotations = _$v.annotations?.toBuilder();
+      super.docs = _$v.docs?.toBuilder();
+      super.optionalParameters = _$v.optionalParameters?.toBuilder();
+      super.requiredParameters = _$v.requiredParameters?.toBuilder();
+      super.initializers = _$v.initializers?.toBuilder();
+      super.body = _$v.body;
+      super.external = _$v.external;
+      super.constant = _$v.constant;
+      super.factory = _$v.factory;
+      super.lambda = _$v.lambda;
+      super.name = _$v.name;
+      super.redirect = _$v.redirect;
+      _$v = null;
+    }
+    return this;
+  }
+
+  @override
+  void replace(Constructor other) {
+    if (other == null) throw new ArgumentError.notNull('other');
+    _$v = other as _$Constructor;
+  }
+
+  @override
+  void update(void updates(ConstructorBuilder b)) {
+    if (updates != null) updates(this);
+  }
+
+  @override
+  _$Constructor build() {
+    _$Constructor _$result;
+    try {
+      _$result = _$v ??
+          new _$Constructor._(
+              annotations: annotations.build(),
+              docs: docs.build(),
+              optionalParameters: optionalParameters.build(),
+              requiredParameters: requiredParameters.build(),
+              initializers: initializers.build(),
+              body: body,
+              external: external,
+              constant: constant,
+              factory: factory,
+              lambda: lambda,
+              name: name,
+              redirect: redirect);
+    } catch (_) {
+      String _$failedField;
+      try {
+        _$failedField = 'annotations';
+        annotations.build();
+        _$failedField = 'docs';
+        docs.build();
+        _$failedField = 'optionalParameters';
+        optionalParameters.build();
+        _$failedField = 'requiredParameters';
+        requiredParameters.build();
+        _$failedField = 'initializers';
+        initializers.build();
+      } catch (e) {
+        throw new BuiltValueNestedFieldError(
+            'Constructor', _$failedField, e.toString());
+      }
+      rethrow;
+    }
+    replace(_$result);
+    return _$result;
+  }
+}
diff --git a/code_builder/lib/src/specs/directive.dart b/code_builder/lib/src/specs/directive.dart
new file mode 100644
index 0000000..69e93ff
--- /dev/null
+++ b/code_builder/lib/src/specs/directive.dart
@@ -0,0 +1,148 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'package:built_value/built_value.dart';
+import 'package:collection/collection.dart';
+import 'package:meta/meta.dart';
+
+import '../base.dart';
+import '../visitors.dart';
+
+part 'directive.g.dart';
+
+@immutable
+abstract class Directive
+    implements Built<Directive, DirectiveBuilder>, Spec, Comparable<Directive> {
+  factory Directive([void updates(DirectiveBuilder b)]) = _$Directive;
+
+  factory Directive.import(
+    String url, {
+    String as,
+    List<String> show = const [],
+    List<String> hide = const [],
+  }) =>
+      Directive((builder) => builder
+        ..as = as
+        ..type = DirectiveType.import
+        ..url = url
+        ..show.addAll(show)
+        ..hide.addAll(hide));
+
+  factory Directive.importDeferredAs(
+    String url,
+    String as, {
+    List<String> show = const [],
+    List<String> hide = const [],
+  }) =>
+      Directive((builder) => builder
+        ..as = as
+        ..type = DirectiveType.import
+        ..url = url
+        ..deferred = true
+        ..show.addAll(show)
+        ..hide.addAll(hide));
+
+  factory Directive.export(
+    String url, {
+    List<String> show = const [],
+    List<String> hide = const [],
+  }) =>
+      Directive((builder) => builder
+        ..type = DirectiveType.export
+        ..url = url
+        ..show.addAll(show)
+        ..hide.addAll(hide));
+
+  Directive._();
+
+  @nullable
+  String get as;
+
+  String get url;
+
+  DirectiveType get type;
+
+  List<String> get show;
+
+  List<String> get hide;
+
+  bool get deferred;
+
+  @override
+  R accept<R>(
+    SpecVisitor<R> visitor, [
+    R context,
+  ]) =>
+      visitor.visitDirective(this, context);
+
+  @override
+  int compareTo(Directive other) => _compareDirectives(this, other);
+}
+
+abstract class DirectiveBuilder
+    implements Builder<Directive, DirectiveBuilder> {
+  factory DirectiveBuilder() = _$DirectiveBuilder;
+
+  DirectiveBuilder._();
+
+  bool deferred = false;
+
+  String as;
+
+  String url;
+
+  List<String> show = <String>[];
+
+  List<String> hide = <String>[];
+
+  DirectiveType type;
+}
+
+enum DirectiveType {
+  import,
+  export,
+}
+
+/// Sort import URIs represented by [a] and [b] to honor the
+/// "Effective Dart" ordering rules which are enforced by the
+/// `directives_ordering` lint.
+///
+/// 1. `import`s before `export`s
+/// 2. `dart:`
+/// 3. `package:`
+/// 4. relative
+int _compareDirectives(Directive a, Directive b) {
+  // NOTE: using the fact that `import` is before `export` in the
+  // `DirectiveType` enum – which allows us to compare using `indexOf`.
+  var value = DirectiveType.values
+      .indexOf(a.type)
+      .compareTo(DirectiveType.values.indexOf(b.type));
+
+  if (value == 0) {
+    final uriA = Uri.parse(a.url);
+    final uriB = Uri.parse(b.url);
+
+    if (uriA.hasScheme) {
+      if (uriB.hasScheme) {
+        // If both import URIs have schemes, compare them based on scheme
+        // `dart` will sort before `package` which is what we want
+        // schemes are case-insensitive, so compare accordingly
+        value = compareAsciiLowerCase(uriA.scheme, uriB.scheme);
+      } else {
+        value = -1;
+      }
+    } else if (uriB.hasScheme) {
+      value = 1;
+    }
+
+    // If both schemes are the same, compare based on path
+    if (value == 0) {
+      value = compareAsciiLowerCase(uriA.path, uriB.path);
+    }
+
+    assert((value == 0) == (a.url == b.url));
+  }
+
+  return value;
+}
diff --git a/code_builder/lib/src/specs/directive.g.dart b/code_builder/lib/src/specs/directive.g.dart
new file mode 100644
index 0000000..a1717b2
--- /dev/null
+++ b/code_builder/lib/src/specs/directive.g.dart
@@ -0,0 +1,203 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'directive.dart';
+
+// **************************************************************************
+// BuiltValueGenerator
+// **************************************************************************
+
+// ignore_for_file: always_put_control_body_on_new_line
+// ignore_for_file: annotate_overrides
+// ignore_for_file: avoid_annotating_with_dynamic
+// ignore_for_file: avoid_catches_without_on_clauses
+// ignore_for_file: avoid_returning_this
+// ignore_for_file: lines_longer_than_80_chars
+// ignore_for_file: omit_local_variable_types
+// ignore_for_file: prefer_expression_function_bodies
+// ignore_for_file: sort_constructors_first
+
+class _$Directive extends Directive {
+  @override
+  final String as;
+  @override
+  final String url;
+  @override
+  final DirectiveType type;
+  @override
+  final List<String> show;
+  @override
+  final List<String> hide;
+  @override
+  final bool deferred;
+
+  factory _$Directive([void updates(DirectiveBuilder b)]) =>
+      (new DirectiveBuilder()..update(updates)).build() as _$Directive;
+
+  _$Directive._(
+      {this.as, this.url, this.type, this.show, this.hide, this.deferred})
+      : super._() {
+    if (url == null) throw new BuiltValueNullFieldError('Directive', 'url');
+    if (type == null) throw new BuiltValueNullFieldError('Directive', 'type');
+    if (show == null) throw new BuiltValueNullFieldError('Directive', 'show');
+    if (hide == null) throw new BuiltValueNullFieldError('Directive', 'hide');
+    if (deferred == null)
+      throw new BuiltValueNullFieldError('Directive', 'deferred');
+  }
+
+  @override
+  Directive rebuild(void updates(DirectiveBuilder b)) =>
+      (toBuilder()..update(updates)).build();
+
+  @override
+  _$DirectiveBuilder toBuilder() => new _$DirectiveBuilder()..replace(this);
+
+  @override
+  bool operator ==(dynamic other) {
+    if (identical(other, this)) return true;
+    if (other is! Directive) return false;
+    return as == other.as &&
+        url == other.url &&
+        type == other.type &&
+        show == other.show &&
+        hide == other.hide &&
+        deferred == other.deferred;
+  }
+
+  @override
+  int get hashCode {
+    return $jf($jc(
+        $jc(
+            $jc($jc($jc($jc(0, as.hashCode), url.hashCode), type.hashCode),
+                show.hashCode),
+            hide.hashCode),
+        deferred.hashCode));
+  }
+
+  @override
+  String toString() {
+    return (newBuiltValueToStringHelper('Directive')
+          ..add('as', as)
+          ..add('url', url)
+          ..add('type', type)
+          ..add('show', show)
+          ..add('hide', hide)
+          ..add('deferred', deferred))
+        .toString();
+  }
+}
+
+class _$DirectiveBuilder extends DirectiveBuilder {
+  _$Directive _$v;
+
+  @override
+  String get as {
+    _$this;
+    return super.as;
+  }
+
+  @override
+  set as(String as) {
+    _$this;
+    super.as = as;
+  }
+
+  @override
+  String get url {
+    _$this;
+    return super.url;
+  }
+
+  @override
+  set url(String url) {
+    _$this;
+    super.url = url;
+  }
+
+  @override
+  DirectiveType get type {
+    _$this;
+    return super.type;
+  }
+
+  @override
+  set type(DirectiveType type) {
+    _$this;
+    super.type = type;
+  }
+
+  @override
+  List<String> get show {
+    _$this;
+    return super.show;
+  }
+
+  @override
+  set show(List<String> show) {
+    _$this;
+    super.show = show;
+  }
+
+  @override
+  List<String> get hide {
+    _$this;
+    return super.hide;
+  }
+
+  @override
+  set hide(List<String> hide) {
+    _$this;
+    super.hide = hide;
+  }
+
+  @override
+  bool get deferred {
+    _$this;
+    return super.deferred;
+  }
+
+  @override
+  set deferred(bool deferred) {
+    _$this;
+    super.deferred = deferred;
+  }
+
+  _$DirectiveBuilder() : super._();
+
+  DirectiveBuilder get _$this {
+    if (_$v != null) {
+      super.as = _$v.as;
+      super.url = _$v.url;
+      super.type = _$v.type;
+      super.show = _$v.show;
+      super.hide = _$v.hide;
+      super.deferred = _$v.deferred;
+      _$v = null;
+    }
+    return this;
+  }
+
+  @override
+  void replace(Directive other) {
+    if (other == null) throw new ArgumentError.notNull('other');
+    _$v = other as _$Directive;
+  }
+
+  @override
+  void update(void updates(DirectiveBuilder b)) {
+    if (updates != null) updates(this);
+  }
+
+  @override
+  _$Directive build() {
+    final _$result = _$v ??
+        new _$Directive._(
+            as: as,
+            url: url,
+            type: type,
+            show: show,
+            hide: hide,
+            deferred: deferred);
+    replace(_$result);
+    return _$result;
+  }
+}
diff --git a/code_builder/lib/src/specs/expression.dart b/code_builder/lib/src/specs/expression.dart
new file mode 100644
index 0000000..cbbf6d4
--- /dev/null
+++ b/code_builder/lib/src/specs/expression.dart
@@ -0,0 +1,573 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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.
+
+library code_builder.src.specs.expression;
+
+import 'package:meta/meta.dart';
+
+import '../base.dart';
+import '../emitter.dart';
+import '../visitors.dart';
+import 'code.dart';
+import 'method.dart';
+import 'reference.dart';
+import 'type_function.dart';
+
+part 'expression/binary.dart';
+part 'expression/closure.dart';
+part 'expression/code.dart';
+part 'expression/invoke.dart';
+part 'expression/literal.dart';
+
+/// Represents a [code] block that wraps an [Expression].
+
+/// Represents a Dart expression.
+///
+/// See various concrete implementations for details.
+abstract class Expression implements Spec {
+  const Expression();
+
+  /// An empty expression.
+  static const _empty = CodeExpression(Code(''));
+
+  @override
+  R accept<R>(covariant ExpressionVisitor<R> visitor, [R context]);
+
+  /// The expression as a valid [Code] block.
+  ///
+  /// Also see [statement].
+  Code get code => ToCodeExpression(this, false);
+
+  /// The expression as a valid [Code] block with a trailing `;`.
+  Code get statement => ToCodeExpression(this, true);
+
+  /// Returns the result of `this` `&&` [other].
+  Expression and(Expression other) {
+    return BinaryExpression._(expression, other, '&&');
+  }
+
+  /// Returns the result of `this` `||` [other].
+  Expression or(Expression other) {
+    return BinaryExpression._(expression, other, '||');
+  }
+
+  /// Returns the result of `!this`.
+  Expression negate() {
+    return BinaryExpression._(_empty, expression, '!', addSpace: false);
+  }
+
+  /// Returns the result of `this` `as` [other].
+  Expression asA(Expression other) {
+    return CodeExpression(Block.of([
+      const Code('('),
+      BinaryExpression._(
+        expression,
+        other,
+        'as',
+      ).code,
+      const Code(')')
+    ]));
+  }
+
+  /// Returns accessing the index operator (`[]`) on `this`.
+  Expression index(Expression index) {
+    return BinaryExpression._(
+      expression,
+      CodeExpression(Block.of([
+        const Code('['),
+        index.code,
+        const Code(']'),
+      ])),
+      '',
+    );
+  }
+
+  /// Returns the result of `this` `is` [other].
+  Expression isA(Expression other) {
+    return BinaryExpression._(
+      expression,
+      other,
+      'is',
+    );
+  }
+
+  /// Returns the result of `this` `is!` [other].
+  Expression isNotA(Expression other) {
+    return BinaryExpression._(
+      expression,
+      other,
+      'is!',
+    );
+  }
+
+  /// Returns the result of `this` `==` [other].
+  Expression equalTo(Expression other) {
+    return BinaryExpression._(
+      expression,
+      other,
+      '==',
+    );
+  }
+
+  /// Returns the result of `this` `!=` [other].
+  Expression notEqualTo(Expression other) {
+    return BinaryExpression._(
+      expression,
+      other,
+      '!=',
+    );
+  }
+
+  /// Returns the result of `this` `>` [other].
+  Expression greaterThan(Expression other) {
+    return BinaryExpression._(
+      expression,
+      other,
+      '>',
+    );
+  }
+
+  /// Returns the result of `this` `<` [other].
+  Expression lessThan(Expression other) {
+    return BinaryExpression._(
+      expression,
+      other,
+      '<',
+    );
+  }
+
+  /// Returns the result of `this` `>=` [other].
+  Expression greaterOrEqualTo(Expression other) {
+    return BinaryExpression._(
+      expression,
+      other,
+      '>=',
+    );
+  }
+
+  /// Returns the result of `this` `<=` [other].
+  Expression lessOrEqualTo(Expression other) {
+    return BinaryExpression._(
+      expression,
+      other,
+      '<=',
+    );
+  }
+
+  /// Returns the result of `this` `+` [other].
+  Expression operatorAdd(Expression other) {
+    return BinaryExpression._(
+      expression,
+      other,
+      '+',
+    );
+  }
+
+  /// Returns the result of `this` `-` [other].
+  Expression operatorSubstract(Expression other) {
+    return BinaryExpression._(
+      expression,
+      other,
+      '-',
+    );
+  }
+
+  /// Returns the result of `this` `/` [other].
+  Expression operatorDivide(Expression other) {
+    return BinaryExpression._(
+      expression,
+      other,
+      '/',
+    );
+  }
+
+  /// Returns the result of `this` `*` [other].
+  Expression operatorMultiply(Expression other) {
+    return BinaryExpression._(
+      expression,
+      other,
+      '*',
+    );
+  }
+
+  /// Returns the result of `this` `%` [other].
+  Expression operatorEuclideanModulo(Expression other) {
+    return BinaryExpression._(
+      expression,
+      other,
+      '%',
+    );
+  }
+
+  Expression conditional(Expression whenTrue, Expression whenFalse) {
+    return BinaryExpression._(
+      expression,
+      BinaryExpression._(whenTrue, whenFalse, ':'),
+      '?',
+    );
+  }
+
+  /// This expression preceded by `await`.
+  Expression get awaited {
+    return BinaryExpression._(
+      _empty,
+      this,
+      'await',
+    );
+  }
+
+  /// Return `{other} = {this}`.
+  Expression assign(Expression other) {
+    return BinaryExpression._(
+      this,
+      other,
+      '=',
+    );
+  }
+
+  /// Return `{other} ??= {this}`.
+  Expression assignNullAware(Expression other) {
+    return BinaryExpression._(
+      this,
+      other,
+      '??=',
+    );
+  }
+
+  /// Return `var {name} = {this}`.
+  Expression assignVar(String name, [Reference type]) {
+    return BinaryExpression._(
+      type == null
+          ? LiteralExpression._('var $name')
+          : BinaryExpression._(
+              type.expression,
+              LiteralExpression._(name),
+              '',
+            ),
+      this,
+      '=',
+    );
+  }
+
+  /// Return `final {name} = {this}`.
+  Expression assignFinal(String name, [Reference type]) {
+    return BinaryExpression._(
+      type == null
+          ? const LiteralExpression._('final')
+          : BinaryExpression._(
+              const LiteralExpression._('final'),
+              type.expression,
+              '',
+            ),
+      this,
+      '$name =',
+    );
+  }
+
+  /// Return `const {name} = {this}`.
+  Expression assignConst(String name, [Reference type]) {
+    return BinaryExpression._(
+      type == null
+          ? const LiteralExpression._('const')
+          : BinaryExpression._(
+              const LiteralExpression._('const'),
+              type.expression,
+              '',
+            ),
+      this,
+      '$name =',
+      isConst: true,
+    );
+  }
+
+  /// Call this expression as a method.
+  Expression call(
+    Iterable<Expression> positionalArguments, [
+    Map<String, Expression> namedArguments = const {},
+    List<Reference> typeArguments = const [],
+  ]) {
+    return InvokeExpression._(
+      this,
+      positionalArguments.toList(),
+      namedArguments,
+      typeArguments,
+    );
+  }
+
+  /// Returns an expression accessing `.<name>` on this expression.
+  Expression property(String name) {
+    return BinaryExpression._(
+      this,
+      LiteralExpression._(name),
+      '.',
+      addSpace: false,
+    );
+  }
+
+  /// Returns an expression accessing `?.<name>` on this expression.
+  Expression nullSafeProperty(String name) {
+    return BinaryExpression._(
+      this,
+      LiteralExpression._(name),
+      '?.',
+      addSpace: false,
+    );
+  }
+
+  /// This expression preceded by `return`.
+  Expression get returned {
+    return BinaryExpression._(
+      const LiteralExpression._('return'),
+      this,
+      '',
+    );
+  }
+
+  /// May be overridden to support other types implementing [Expression].
+  @visibleForOverriding
+  Expression get expression => this;
+}
+
+/// Creates `typedef {name} =`.
+Code createTypeDef(String name, FunctionType type) => BinaryExpression._(
+        LiteralExpression._('typedef $name'), type.expression, '=')
+    .statement;
+
+class ToCodeExpression implements Code {
+  final Expression code;
+
+  /// Whether this code should be considered a _statement_.
+  final bool isStatement;
+
+  @visibleForTesting
+  const ToCodeExpression(this.code, [this.isStatement = false]);
+
+  @override
+  R accept<R>(CodeVisitor<R> visitor, [R context]) {
+    return (visitor as ExpressionVisitor<R>)
+        .visitToCodeExpression(this, context);
+  }
+
+  @override
+  String toString() => code.toString();
+}
+
+/// Knowledge of different types of expressions in Dart.
+///
+/// **INTERNAL ONLY**.
+abstract class ExpressionVisitor<T> implements SpecVisitor<T> {
+  T visitToCodeExpression(ToCodeExpression code, [T context]);
+  T visitBinaryExpression(BinaryExpression expression, [T context]);
+  T visitClosureExpression(ClosureExpression expression, [T context]);
+  T visitCodeExpression(CodeExpression expression, [T context]);
+  T visitInvokeExpression(InvokeExpression expression, [T context]);
+  T visitLiteralExpression(LiteralExpression expression, [T context]);
+  T visitLiteralListExpression(LiteralListExpression expression, [T context]);
+  T visitLiteralSetExpression(LiteralSetExpression expression, [T context]);
+  T visitLiteralMapExpression(LiteralMapExpression expression, [T context]);
+}
+
+/// Knowledge of how to write valid Dart code from [ExpressionVisitor].
+///
+/// **INTERNAL ONLY**.
+abstract class ExpressionEmitter implements ExpressionVisitor<StringSink> {
+  @override
+  visitToCodeExpression(ToCodeExpression expression, [StringSink output]) {
+    output ??= StringBuffer();
+    expression.code.accept(this, output);
+    if (expression.isStatement) {
+      output.write(';');
+    }
+    return output;
+  }
+
+  @override
+  visitBinaryExpression(BinaryExpression expression, [StringSink output]) {
+    output ??= StringBuffer();
+    expression.left.accept(this, output);
+    if (expression.addSpace) {
+      output.write(' ');
+    }
+    output.write(expression.operator);
+    if (expression.addSpace) {
+      output.write(' ');
+    }
+    startConstCode(expression.isConst, () {
+      expression.right.accept(this, output);
+    });
+    return output;
+  }
+
+  @override
+  visitClosureExpression(ClosureExpression expression, [StringSink output]) {
+    output ??= StringBuffer();
+    return expression.method.accept(this, output);
+  }
+
+  @override
+  visitCodeExpression(CodeExpression expression, [StringSink output]) {
+    output ??= StringBuffer();
+    final visitor = this as CodeVisitor<StringSink>;
+    return expression.code.accept(visitor, output);
+  }
+
+  @override
+  visitInvokeExpression(InvokeExpression expression, [StringSink output]) {
+    output ??= StringBuffer();
+    return _writeConstExpression(
+        output, expression.type == InvokeExpressionType.constInstance, () {
+      expression.target.accept(this, output);
+      if (expression.name != null) {
+        output..write('.')..write(expression.name);
+      }
+      if (expression.typeArguments.isNotEmpty) {
+        output.write('<');
+        visitAll<Reference>(expression.typeArguments, output, (type) {
+          type.accept(this, output);
+        });
+        output.write('>');
+      }
+      output.write('(');
+      visitAll<Spec>(expression.positionalArguments, output, (spec) {
+        spec.accept(this, output);
+      });
+      if (expression.positionalArguments.isNotEmpty &&
+          expression.namedArguments.isNotEmpty) {
+        output.write(', ');
+      }
+      visitAll<String>(expression.namedArguments.keys, output, (name) {
+        output..write(name)..write(': ');
+        expression.namedArguments[name].accept(this, output);
+      });
+      return output..write(')');
+    });
+  }
+
+  @override
+  visitLiteralExpression(LiteralExpression expression, [StringSink output]) {
+    output ??= StringBuffer();
+    return output..write(expression.literal);
+  }
+
+  void _acceptLiteral(Object literalOrSpec, StringSink output) {
+    if (literalOrSpec is Spec) {
+      literalOrSpec.accept(this, output);
+      return;
+    }
+    literal(literalOrSpec).accept(this, output);
+  }
+
+  bool _withInConstExpression = false;
+
+  @override
+  visitLiteralListExpression(
+    LiteralListExpression expression, [
+    StringSink output,
+  ]) {
+    output ??= StringBuffer();
+
+    return _writeConstExpression(output, expression.isConst, () {
+      if (expression.type != null) {
+        output.write('<');
+        expression.type.accept(this, output);
+        output.write('>');
+      }
+      output.write('[');
+      visitAll<Object>(expression.values, output, (value) {
+        _acceptLiteral(value, output);
+      });
+      return output..write(']');
+    });
+  }
+
+  @override
+  visitLiteralSetExpression(
+    LiteralSetExpression expression, [
+    StringSink output,
+  ]) {
+    output ??= StringBuffer();
+
+    return _writeConstExpression(output, expression.isConst, () {
+      if (expression.type != null) {
+        output.write('<');
+        expression.type.accept(this, output);
+        output.write('>');
+      }
+      output.write('{');
+      visitAll<Object>(expression.values, output, (value) {
+        _acceptLiteral(value, output);
+      });
+      return output..write('}');
+    });
+  }
+
+  @override
+  visitLiteralMapExpression(
+    LiteralMapExpression expression, [
+    StringSink output,
+  ]) {
+    output ??= StringBuffer();
+    return _writeConstExpression(output, expression.isConst, () {
+      if (expression.keyType != null) {
+        output.write('<');
+        expression.keyType.accept(this, output);
+        output.write(', ');
+        if (expression.valueType == null) {
+          const Reference('dynamic', 'dart:core').accept(this, output);
+        } else {
+          expression.valueType.accept(this, output);
+        }
+        output.write('>');
+      }
+      output.write('{');
+      visitAll<Object>(expression.values.keys, output, (key) {
+        final value = expression.values[key];
+        _acceptLiteral(key, output);
+        output.write(': ');
+        _acceptLiteral(value, output);
+      });
+      return output..write('}');
+    });
+  }
+
+  /// Executes [visit] within a context which may alter the output if [isConst]
+  /// is `true`.
+  ///
+  /// This allows constant expressions to omit the `const` keyword if they
+  /// are already within a constant expression.
+  void startConstCode(
+    bool isConst,
+    Null Function() visit,
+  ) {
+    final previousConstContext = _withInConstExpression;
+    if (isConst) {
+      _withInConstExpression = true;
+    }
+
+    visit();
+    _withInConstExpression = previousConstContext;
+  }
+
+  /// Similar to [startConstCode], but handles writing `"const "` if [isConst]
+  /// is `true` and the invocation is not nested under other invocations where
+  /// [isConst] is true.
+  StringSink _writeConstExpression(
+    StringSink sink,
+    bool isConst,
+    StringSink Function() visitExpression,
+  ) {
+    final previousConstContext = _withInConstExpression;
+    if (isConst) {
+      if (!_withInConstExpression) {
+        sink.write('const ');
+      }
+      _withInConstExpression = true;
+    }
+
+    final returnedSink = visitExpression();
+    assert(identical(returnedSink, sink));
+    _withInConstExpression = previousConstContext;
+    return sink;
+  }
+}
diff --git a/code_builder/lib/src/specs/expression/binary.dart b/code_builder/lib/src/specs/expression/binary.dart
new file mode 100644
index 0000000..1f7e76e
--- /dev/null
+++ b/code_builder/lib/src/specs/expression/binary.dart
@@ -0,0 +1,27 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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.
+
+part of code_builder.src.specs.expression;
+
+/// Represents two expressions ([left] and [right]) and an [operator].
+class BinaryExpression extends Expression {
+  final Expression left;
+  final Expression right;
+  final String operator;
+  final bool addSpace;
+  final bool isConst;
+
+  const BinaryExpression._(
+    this.left,
+    this.right,
+    this.operator, {
+    this.addSpace = true,
+    this.isConst = false,
+  });
+
+  @override
+  R accept<R>(ExpressionVisitor<R> visitor, [R context]) {
+    return visitor.visitBinaryExpression(this, context);
+  }
+}
diff --git a/code_builder/lib/src/specs/expression/closure.dart b/code_builder/lib/src/specs/expression/closure.dart
new file mode 100644
index 0000000..1af2760
--- /dev/null
+++ b/code_builder/lib/src/specs/expression/closure.dart
@@ -0,0 +1,24 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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.
+
+part of code_builder.src.specs.expression;
+
+Expression toClosure(Method method) {
+  final withoutTypes = method.rebuild((b) {
+    b.returns = null;
+    b.types.clear();
+  });
+  return ClosureExpression._(withoutTypes);
+}
+
+class ClosureExpression extends Expression {
+  final Method method;
+
+  const ClosureExpression._(this.method);
+
+  @override
+  R accept<R>(ExpressionVisitor<R> visitor, [R context]) {
+    return visitor.visitClosureExpression(this, context);
+  }
+}
diff --git a/code_builder/lib/src/specs/expression/code.dart b/code_builder/lib/src/specs/expression/code.dart
new file mode 100644
index 0000000..7465448
--- /dev/null
+++ b/code_builder/lib/src/specs/expression/code.dart
@@ -0,0 +1,19 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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.
+
+part of code_builder.src.specs.expression;
+
+/// Represents a [Code] block as an [Expression].
+class CodeExpression extends Expression {
+  @override
+  final Code code;
+
+  /// **INTERNAL ONLY**: Used to wrap [Code] as an [Expression].
+  const CodeExpression(this.code);
+
+  @override
+  R accept<R>(ExpressionVisitor<R> visitor, [R context]) {
+    return visitor.visitCodeExpression(this, context);
+  }
+}
diff --git a/code_builder/lib/src/specs/expression/invoke.dart b/code_builder/lib/src/specs/expression/invoke.dart
new file mode 100644
index 0000000..3ac586f
--- /dev/null
+++ b/code_builder/lib/src/specs/expression/invoke.dart
@@ -0,0 +1,57 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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.
+
+part of code_builder.src.specs.expression;
+
+/// Represents invoking [target] as a method with arguments.
+class InvokeExpression extends Expression {
+  /// Target of the method invocation.
+  final Expression target;
+
+  /// Optional; type of invocation.
+  final InvokeExpressionType type;
+
+  final List<Expression> positionalArguments;
+  final Map<String, Expression> namedArguments;
+  final List<Reference> typeArguments;
+  final String name;
+
+  const InvokeExpression._(
+    this.target,
+    this.positionalArguments, [
+    this.namedArguments = const {},
+    this.typeArguments,
+    this.name,
+  ]) : type = null;
+
+  const InvokeExpression.newOf(
+    this.target,
+    this.positionalArguments, [
+    this.namedArguments = const {},
+    this.typeArguments,
+    this.name,
+  ]) : type = InvokeExpressionType.newInstance;
+
+  const InvokeExpression.constOf(
+    this.target,
+    this.positionalArguments, [
+    this.namedArguments = const {},
+    this.typeArguments,
+    this.name,
+  ]) : type = InvokeExpressionType.constInstance;
+
+  @override
+  R accept<R>(ExpressionVisitor<R> visitor, [R context]) {
+    return visitor.visitInvokeExpression(this, context);
+  }
+
+  @override
+  String toString() =>
+      '${type ?? ''} $target($positionalArguments, $namedArguments)';
+}
+
+enum InvokeExpressionType {
+  newInstance,
+  constInstance,
+}
diff --git a/code_builder/lib/src/specs/expression/literal.dart b/code_builder/lib/src/specs/expression/literal.dart
new file mode 100644
index 0000000..3021fa1
--- /dev/null
+++ b/code_builder/lib/src/specs/expression/literal.dart
@@ -0,0 +1,182 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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.
+
+part of code_builder.src.specs.expression;
+
+/// Converts a runtime Dart [literal] value into an [Expression].
+///
+/// Unsupported inputs invoke the [onError] callback.
+Expression literal(Object literal, {Expression onError(Object value)}) {
+  if (literal is bool) {
+    return literalBool(literal);
+  }
+  if (literal is num) {
+    return literalNum(literal);
+  }
+  if (literal is String) {
+    return literalString(literal);
+  }
+  if (literal is List) {
+    return literalList(literal);
+  }
+  if (literal is Set) {
+    return literalSet(literal);
+  }
+  if (literal is Map) {
+    return literalMap(literal);
+  }
+  if (literal == null) {
+    return literalNull;
+  }
+  if (onError != null) {
+    return onError(literal);
+  }
+  throw UnsupportedError('Not a supported literal type: $literal.');
+}
+
+/// Represents the literal value `true`.
+const Expression literalTrue = LiteralExpression._('true');
+
+/// Represents the literal value `false`.
+const Expression literalFalse = LiteralExpression._('false');
+
+/// Create a literal expression from a boolean [value].
+Expression literalBool(bool value) => value ? literalTrue : literalFalse;
+
+/// Represents the literal value `null`.
+const Expression literalNull = LiteralExpression._('null');
+
+/// Create a literal expression from a number [value].
+Expression literalNum(num value) => LiteralExpression._('$value');
+
+/// Create a literal expression from a string [value].
+///
+/// **NOTE**: The string is always formatted `'<value>'`.
+///
+/// If [raw] is `true`, creates a raw String formatted `r'<value>'` and the
+/// value may not contain a single quote.
+/// Escapes single quotes and newlines in the value.
+Expression literalString(String value, {bool raw = false}) {
+  if (raw && value.contains('\'')) {
+    throw ArgumentError('Cannot include a single quote in a raw string');
+  }
+  final escaped = value.replaceAll('\'', '\\\'').replaceAll('\n', '\\n');
+  return LiteralExpression._("${raw ? 'r' : ''}'$escaped'");
+}
+
+/// Creates a literal list expression from [values].
+LiteralListExpression literalList(Iterable<Object> values, [Reference type]) {
+  return LiteralListExpression._(false, values.toList(), type);
+}
+
+/// Creates a literal `const` list expression from [values].
+LiteralListExpression literalConstList(List<Object> values, [Reference type]) {
+  return LiteralListExpression._(true, values, type);
+}
+
+/// Creates a literal set expression from [values].
+LiteralSetExpression literalSet(Iterable<Object> values, [Reference type]) {
+  return LiteralSetExpression._(false, values.toSet(), type);
+}
+
+/// Creates a literal `const` set expression from [values].
+LiteralSetExpression literalConstSet(Set<Object> values, [Reference type]) {
+  return LiteralSetExpression._(true, values, type);
+}
+
+/// Create a literal map expression from [values].
+LiteralMapExpression literalMap(
+  Map<Object, Object> values, [
+  Reference keyType,
+  Reference valueType,
+]) {
+  return LiteralMapExpression._(false, values, keyType, valueType);
+}
+
+/// Create a literal `const` map expression from [values].
+LiteralMapExpression literalConstMap(
+  Map<Object, Object> values, [
+  Reference keyType,
+  Reference valueType,
+]) {
+  return LiteralMapExpression._(true, values, keyType, valueType);
+}
+
+/// Represents a literal value in Dart source code.
+///
+/// For example, `LiteralExpression('null')` should emit `null`.
+///
+/// Some common literals and helpers are available as methods/fields:
+/// * [literal]
+/// * [literalBool] and [literalTrue], [literalFalse]
+/// * [literalNull]
+/// * [literalList] and [literalConstList]
+/// * [literalSet] and [literalConstSet]
+class LiteralExpression extends Expression {
+  final String literal;
+
+  const LiteralExpression._(this.literal);
+
+  @override
+  R accept<R>(ExpressionVisitor<R> visitor, [R context]) {
+    return visitor.visitLiteralExpression(this, context);
+  }
+
+  @override
+  String toString() => literal;
+}
+
+class LiteralListExpression extends Expression {
+  final bool isConst;
+  final List<Object> values;
+  final Reference type;
+
+  const LiteralListExpression._(this.isConst, this.values, this.type);
+
+  @override
+  R accept<R>(ExpressionVisitor<R> visitor, [R context]) {
+    return visitor.visitLiteralListExpression(this, context);
+  }
+
+  @override
+  String toString() => '[${values.map(literal).join(', ')}]';
+}
+
+class LiteralSetExpression extends Expression {
+  final bool isConst;
+  final Set<Object> values;
+  final Reference type;
+
+  const LiteralSetExpression._(this.isConst, this.values, this.type);
+
+  @override
+  R accept<R>(ExpressionVisitor<R> visitor, [R context]) {
+    return visitor.visitLiteralSetExpression(this, context);
+  }
+
+  @override
+  String toString() => '{${values.map(literal).join(', ')}}';
+}
+
+class LiteralMapExpression extends Expression {
+  final bool isConst;
+  final Map<Object, Object> values;
+  final Reference keyType;
+  final Reference valueType;
+
+  const LiteralMapExpression._(
+    this.isConst,
+    this.values,
+    this.keyType,
+    this.valueType,
+  );
+
+  @override
+  R accept<R>(ExpressionVisitor<R> visitor, [R context]) {
+    return visitor.visitLiteralMapExpression(this, context);
+  }
+
+  @override
+  String toString() => '{$values}';
+}
diff --git a/code_builder/lib/src/specs/field.dart b/code_builder/lib/src/specs/field.dart
new file mode 100644
index 0000000..81c5640
--- /dev/null
+++ b/code_builder/lib/src/specs/field.dart
@@ -0,0 +1,91 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'package:built_collection/built_collection.dart';
+import 'package:built_value/built_value.dart';
+import 'package:meta/meta.dart';
+
+import '../base.dart';
+import '../mixins/annotations.dart';
+import '../mixins/dartdoc.dart';
+import '../visitors.dart';
+import 'code.dart';
+import 'expression.dart';
+import 'reference.dart';
+
+part 'field.g.dart';
+
+@immutable
+abstract class Field extends Object
+    with HasAnnotations, HasDartDocs
+    implements Built<Field, FieldBuilder>, Spec {
+  factory Field([void updates(FieldBuilder b)]) = _$Field;
+
+  Field._();
+
+  @override
+  BuiltList<Expression> get annotations;
+
+  @override
+  BuiltList<String> get docs;
+
+  /// Field assignment, if any.
+  @nullable
+  Code get assignment;
+
+  /// Whether this field should be prefixed with `static`.
+  ///
+  /// This is only valid within classes.
+  bool get static;
+
+  /// Name of the field.
+  String get name;
+
+  @nullable
+  Reference get type;
+
+  FieldModifier get modifier;
+
+  @override
+  R accept<R>(
+    SpecVisitor<R> visitor, [
+    R context,
+  ]) =>
+      visitor.visitField(this, context);
+}
+
+enum FieldModifier {
+  var$,
+  final$,
+  constant,
+}
+
+abstract class FieldBuilder extends Object
+    with HasAnnotationsBuilder, HasDartDocsBuilder
+    implements Builder<Field, FieldBuilder> {
+  factory FieldBuilder() = _$FieldBuilder;
+
+  FieldBuilder._();
+
+  @override
+  ListBuilder<Expression> annotations = ListBuilder<Expression>();
+
+  @override
+  ListBuilder<String> docs = ListBuilder<String>();
+
+  /// Field assignment, if any.
+  Code assignment;
+
+  /// Whether this field should be prefixed with `static`.
+  ///
+  /// This is only valid within classes.
+  bool static = false;
+
+  /// Name of the field.
+  String name;
+
+  Reference type;
+
+  FieldModifier modifier = FieldModifier.var$;
+}
diff --git a/code_builder/lib/src/specs/field.g.dart b/code_builder/lib/src/specs/field.g.dart
new file mode 100644
index 0000000..3bef453
--- /dev/null
+++ b/code_builder/lib/src/specs/field.g.dart
@@ -0,0 +1,247 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'field.dart';
+
+// **************************************************************************
+// BuiltValueGenerator
+// **************************************************************************
+
+// ignore_for_file: always_put_control_body_on_new_line
+// ignore_for_file: annotate_overrides
+// ignore_for_file: avoid_annotating_with_dynamic
+// ignore_for_file: avoid_catches_without_on_clauses
+// ignore_for_file: avoid_returning_this
+// ignore_for_file: lines_longer_than_80_chars
+// ignore_for_file: omit_local_variable_types
+// ignore_for_file: prefer_expression_function_bodies
+// ignore_for_file: sort_constructors_first
+
+class _$Field extends Field {
+  @override
+  final BuiltList<Expression> annotations;
+  @override
+  final BuiltList<String> docs;
+  @override
+  final Code assignment;
+  @override
+  final bool static;
+  @override
+  final String name;
+  @override
+  final Reference type;
+  @override
+  final FieldModifier modifier;
+
+  factory _$Field([void updates(FieldBuilder b)]) =>
+      (new FieldBuilder()..update(updates)).build() as _$Field;
+
+  _$Field._(
+      {this.annotations,
+      this.docs,
+      this.assignment,
+      this.static,
+      this.name,
+      this.type,
+      this.modifier})
+      : super._() {
+    if (annotations == null)
+      throw new BuiltValueNullFieldError('Field', 'annotations');
+    if (docs == null) throw new BuiltValueNullFieldError('Field', 'docs');
+    if (static == null) throw new BuiltValueNullFieldError('Field', 'static');
+    if (name == null) throw new BuiltValueNullFieldError('Field', 'name');
+    if (modifier == null)
+      throw new BuiltValueNullFieldError('Field', 'modifier');
+  }
+
+  @override
+  Field rebuild(void updates(FieldBuilder b)) =>
+      (toBuilder()..update(updates)).build();
+
+  @override
+  _$FieldBuilder toBuilder() => new _$FieldBuilder()..replace(this);
+
+  @override
+  bool operator ==(dynamic other) {
+    if (identical(other, this)) return true;
+    if (other is! Field) return false;
+    return annotations == other.annotations &&
+        docs == other.docs &&
+        assignment == other.assignment &&
+        static == other.static &&
+        name == other.name &&
+        type == other.type &&
+        modifier == other.modifier;
+  }
+
+  @override
+  int get hashCode {
+    return $jf($jc(
+        $jc(
+            $jc(
+                $jc(
+                    $jc($jc($jc(0, annotations.hashCode), docs.hashCode),
+                        assignment.hashCode),
+                    static.hashCode),
+                name.hashCode),
+            type.hashCode),
+        modifier.hashCode));
+  }
+
+  @override
+  String toString() {
+    return (newBuiltValueToStringHelper('Field')
+          ..add('annotations', annotations)
+          ..add('docs', docs)
+          ..add('assignment', assignment)
+          ..add('static', static)
+          ..add('name', name)
+          ..add('type', type)
+          ..add('modifier', modifier))
+        .toString();
+  }
+}
+
+class _$FieldBuilder extends FieldBuilder {
+  _$Field _$v;
+
+  @override
+  ListBuilder<Expression> get annotations {
+    _$this;
+    return super.annotations ??= new ListBuilder<Expression>();
+  }
+
+  @override
+  set annotations(ListBuilder<Expression> annotations) {
+    _$this;
+    super.annotations = annotations;
+  }
+
+  @override
+  ListBuilder<String> get docs {
+    _$this;
+    return super.docs ??= new ListBuilder<String>();
+  }
+
+  @override
+  set docs(ListBuilder<String> docs) {
+    _$this;
+    super.docs = docs;
+  }
+
+  @override
+  Code get assignment {
+    _$this;
+    return super.assignment;
+  }
+
+  @override
+  set assignment(Code assignment) {
+    _$this;
+    super.assignment = assignment;
+  }
+
+  @override
+  bool get static {
+    _$this;
+    return super.static;
+  }
+
+  @override
+  set static(bool static) {
+    _$this;
+    super.static = static;
+  }
+
+  @override
+  String get name {
+    _$this;
+    return super.name;
+  }
+
+  @override
+  set name(String name) {
+    _$this;
+    super.name = name;
+  }
+
+  @override
+  Reference get type {
+    _$this;
+    return super.type;
+  }
+
+  @override
+  set type(Reference type) {
+    _$this;
+    super.type = type;
+  }
+
+  @override
+  FieldModifier get modifier {
+    _$this;
+    return super.modifier;
+  }
+
+  @override
+  set modifier(FieldModifier modifier) {
+    _$this;
+    super.modifier = modifier;
+  }
+
+  _$FieldBuilder() : super._();
+
+  FieldBuilder get _$this {
+    if (_$v != null) {
+      super.annotations = _$v.annotations?.toBuilder();
+      super.docs = _$v.docs?.toBuilder();
+      super.assignment = _$v.assignment;
+      super.static = _$v.static;
+      super.name = _$v.name;
+      super.type = _$v.type;
+      super.modifier = _$v.modifier;
+      _$v = null;
+    }
+    return this;
+  }
+
+  @override
+  void replace(Field other) {
+    if (other == null) throw new ArgumentError.notNull('other');
+    _$v = other as _$Field;
+  }
+
+  @override
+  void update(void updates(FieldBuilder b)) {
+    if (updates != null) updates(this);
+  }
+
+  @override
+  _$Field build() {
+    _$Field _$result;
+    try {
+      _$result = _$v ??
+          new _$Field._(
+              annotations: annotations.build(),
+              docs: docs.build(),
+              assignment: assignment,
+              static: static,
+              name: name,
+              type: type,
+              modifier: modifier);
+    } catch (_) {
+      String _$failedField;
+      try {
+        _$failedField = 'annotations';
+        annotations.build();
+        _$failedField = 'docs';
+        docs.build();
+      } catch (e) {
+        throw new BuiltValueNestedFieldError(
+            'Field', _$failedField, e.toString());
+      }
+      rethrow;
+    }
+    replace(_$result);
+    return _$result;
+  }
+}
diff --git a/code_builder/lib/src/specs/library.dart b/code_builder/lib/src/specs/library.dart
new file mode 100644
index 0000000..f476828
--- /dev/null
+++ b/code_builder/lib/src/specs/library.dart
@@ -0,0 +1,37 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'package:built_collection/built_collection.dart';
+import 'package:built_value/built_value.dart';
+import 'package:meta/meta.dart';
+
+import '../base.dart';
+import '../visitors.dart';
+import 'directive.dart';
+
+part 'library.g.dart';
+
+@immutable
+abstract class Library implements Built<Library, LibraryBuilder>, Spec {
+  factory Library([void updates(LibraryBuilder b)]) = _$Library;
+  Library._();
+
+  BuiltList<Directive> get directives;
+  BuiltList<Spec> get body;
+
+  @override
+  R accept<R>(
+    SpecVisitor<R> visitor, [
+    R context,
+  ]) =>
+      visitor.visitLibrary(this, context);
+}
+
+abstract class LibraryBuilder implements Builder<Library, LibraryBuilder> {
+  factory LibraryBuilder() = _$LibraryBuilder;
+  LibraryBuilder._();
+
+  ListBuilder<Spec> body = ListBuilder<Spec>();
+  ListBuilder<Directive> directives = ListBuilder<Directive>();
+}
diff --git a/code_builder/lib/src/specs/library.g.dart b/code_builder/lib/src/specs/library.g.dart
new file mode 100644
index 0000000..9935040
--- /dev/null
+++ b/code_builder/lib/src/specs/library.g.dart
@@ -0,0 +1,133 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'library.dart';
+
+// **************************************************************************
+// BuiltValueGenerator
+// **************************************************************************
+
+// ignore_for_file: always_put_control_body_on_new_line
+// ignore_for_file: annotate_overrides
+// ignore_for_file: avoid_annotating_with_dynamic
+// ignore_for_file: avoid_catches_without_on_clauses
+// ignore_for_file: avoid_returning_this
+// ignore_for_file: lines_longer_than_80_chars
+// ignore_for_file: omit_local_variable_types
+// ignore_for_file: prefer_expression_function_bodies
+// ignore_for_file: sort_constructors_first
+
+class _$Library extends Library {
+  @override
+  final BuiltList<Directive> directives;
+  @override
+  final BuiltList<Spec> body;
+
+  factory _$Library([void updates(LibraryBuilder b)]) =>
+      (new LibraryBuilder()..update(updates)).build() as _$Library;
+
+  _$Library._({this.directives, this.body}) : super._() {
+    if (directives == null)
+      throw new BuiltValueNullFieldError('Library', 'directives');
+    if (body == null) throw new BuiltValueNullFieldError('Library', 'body');
+  }
+
+  @override
+  Library rebuild(void updates(LibraryBuilder b)) =>
+      (toBuilder()..update(updates)).build();
+
+  @override
+  _$LibraryBuilder toBuilder() => new _$LibraryBuilder()..replace(this);
+
+  @override
+  bool operator ==(dynamic other) {
+    if (identical(other, this)) return true;
+    if (other is! Library) return false;
+    return directives == other.directives && body == other.body;
+  }
+
+  @override
+  int get hashCode {
+    return $jf($jc($jc(0, directives.hashCode), body.hashCode));
+  }
+
+  @override
+  String toString() {
+    return (newBuiltValueToStringHelper('Library')
+          ..add('directives', directives)
+          ..add('body', body))
+        .toString();
+  }
+}
+
+class _$LibraryBuilder extends LibraryBuilder {
+  _$Library _$v;
+
+  @override
+  ListBuilder<Directive> get directives {
+    _$this;
+    return super.directives ??= new ListBuilder<Directive>();
+  }
+
+  @override
+  set directives(ListBuilder<Directive> directives) {
+    _$this;
+    super.directives = directives;
+  }
+
+  @override
+  ListBuilder<Spec> get body {
+    _$this;
+    return super.body ??= new ListBuilder<Spec>();
+  }
+
+  @override
+  set body(ListBuilder<Spec> body) {
+    _$this;
+    super.body = body;
+  }
+
+  _$LibraryBuilder() : super._();
+
+  LibraryBuilder get _$this {
+    if (_$v != null) {
+      super.directives = _$v.directives?.toBuilder();
+      super.body = _$v.body?.toBuilder();
+      _$v = null;
+    }
+    return this;
+  }
+
+  @override
+  void replace(Library other) {
+    if (other == null) throw new ArgumentError.notNull('other');
+    _$v = other as _$Library;
+  }
+
+  @override
+  void update(void updates(LibraryBuilder b)) {
+    if (updates != null) updates(this);
+  }
+
+  @override
+  _$Library build() {
+    _$Library _$result;
+    try {
+      _$result = _$v ??
+          new _$Library._(directives: directives.build(), body: body.build());
+    } catch (_) {
+      String _$failedField;
+      try {
+        _$failedField = 'directives';
+        directives.build();
+        _$failedField = 'body';
+        body.build();
+      } catch (e) {
+        throw new BuiltValueNestedFieldError(
+            'Library', _$failedField, e.toString());
+      }
+      rethrow;
+    }
+    replace(_$result);
+    return _$result;
+  }
+}
diff --git a/code_builder/lib/src/specs/method.dart b/code_builder/lib/src/specs/method.dart
new file mode 100644
index 0000000..5fecfe3
--- /dev/null
+++ b/code_builder/lib/src/specs/method.dart
@@ -0,0 +1,229 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'package:built_value/built_value.dart';
+import 'package:built_collection/built_collection.dart';
+import 'package:meta/meta.dart';
+
+import '../base.dart';
+import '../mixins/annotations.dart';
+import '../mixins/dartdoc.dart';
+import '../mixins/generics.dart';
+import '../visitors.dart';
+import 'code.dart';
+import 'expression.dart';
+import 'reference.dart';
+
+part 'method.g.dart';
+
+final Reference _$void = const Reference('void');
+
+@immutable
+abstract class Method extends Object
+    with HasAnnotations, HasGenerics, HasDartDocs
+    implements Built<Method, MethodBuilder>, Spec {
+  factory Method([void updates(MethodBuilder b)]) = _$Method;
+
+  factory Method.returnsVoid([void updates(MethodBuilder b)]) {
+    return Method((b) {
+      if (updates != null) {
+        updates(b);
+      }
+      b.returns = _$void;
+    });
+  }
+
+  Method._();
+
+  @override
+  BuiltList<Expression> get annotations;
+
+  @override
+  BuiltList<String> get docs;
+
+  @override
+  BuiltList<Reference> get types;
+
+  /// Optional parameters.
+  BuiltList<Parameter> get optionalParameters;
+
+  /// Required parameters.
+  BuiltList<Parameter> get requiredParameters;
+
+  /// Body of the method.
+  @nullable
+  Code get body;
+
+  /// Whether the method should be prefixed with `external`.
+  bool get external;
+
+  /// Whether this method is a simple lambda expression.
+  ///
+  /// May be `null` to be inferred based on the value of [body].
+  @nullable
+  bool get lambda;
+
+  /// Whether this method should be prefixed with `static`.
+  ///
+  /// This is only valid within classes.
+  bool get static;
+
+  /// Name of the method or function.
+  ///
+  /// May be `null` when being used as a [closure].
+  @nullable
+  String get name;
+
+  /// Whether this is a getter or setter.
+  @nullable
+  MethodType get type;
+
+  /// Whether this method is `async`, `async*`, or `sync*`.
+  @nullable
+  MethodModifier get modifier;
+
+  @nullable
+  Reference get returns;
+
+  @override
+  R accept<R>(
+    SpecVisitor<R> visitor, [
+    R context,
+  ]) =>
+      visitor.visitMethod(this, context);
+
+  /// This method as a closure.
+  Expression get closure => toClosure(this);
+}
+
+abstract class MethodBuilder extends Object
+    with HasAnnotationsBuilder, HasGenericsBuilder, HasDartDocsBuilder
+    implements Builder<Method, MethodBuilder> {
+  factory MethodBuilder() = _$MethodBuilder;
+
+  MethodBuilder._();
+
+  @override
+  ListBuilder<Expression> annotations = ListBuilder<Expression>();
+
+  @override
+  ListBuilder<String> docs = ListBuilder<String>();
+
+  @override
+  ListBuilder<Reference> types = ListBuilder<Reference>();
+
+  /// Optional parameters.
+  ListBuilder<Parameter> optionalParameters = ListBuilder<Parameter>();
+
+  /// Required parameters.
+  ListBuilder<Parameter> requiredParameters = ListBuilder<Parameter>();
+
+  /// Body of the method.
+  Code body;
+
+  /// Whether the method should be prefixed with `external`.
+  bool external = false;
+
+  /// Whether this method is a simple lambda expression.
+  ///
+  /// If not specified this is inferred from the [body].
+  bool lambda;
+
+  /// Whether this method should be prefixed with `static`.
+  ///
+  /// This is only valid within classes.
+  bool static = false;
+
+  /// Name of the method or function.
+  String name;
+
+  /// Whether this is a getter or setter.
+  MethodType type;
+
+  /// Whether this method is `async`, `async*`, or `sync*`.
+  MethodModifier modifier;
+
+  Reference returns;
+}
+
+enum MethodType {
+  getter,
+  setter,
+}
+
+enum MethodModifier {
+  async,
+  asyncStar,
+  syncStar,
+}
+
+abstract class Parameter extends Object
+    with HasAnnotations, HasGenerics, HasDartDocs
+    implements Built<Parameter, ParameterBuilder> {
+  factory Parameter([void updates(ParameterBuilder b)]) = _$Parameter;
+
+  Parameter._();
+
+  /// If not `null`, a default assignment if the parameter is optional.
+  @nullable
+  Code get defaultTo;
+
+  /// Name of the parameter.
+  String get name;
+
+  /// Whether this parameter should be named, if optional.
+  bool get named;
+
+  /// Whether this parameter should be field formal (i.e. `this.`).
+  ///
+  /// This is only valid on constructors;
+  bool get toThis;
+
+  @override
+  BuiltList<Expression> get annotations;
+
+  @override
+  BuiltList<String> get docs;
+
+  @override
+  BuiltList<Reference> get types;
+
+  /// Type of the parameter;
+  @nullable
+  Reference get type;
+}
+
+abstract class ParameterBuilder extends Object
+    with HasAnnotationsBuilder, HasGenericsBuilder, HasDartDocsBuilder
+    implements Builder<Parameter, ParameterBuilder> {
+  factory ParameterBuilder() = _$ParameterBuilder;
+
+  ParameterBuilder._();
+
+  /// If not `null`, a default assignment if the parameter is optional.
+  Code defaultTo;
+
+  /// Name of the parameter.
+  String name;
+
+  /// Whether this parameter should be named, if optional.
+  bool named = false;
+
+  /// Whether this parameter should be field formal (i.e. `this.`).
+  ///
+  /// This is only valid on constructors;
+  bool toThis = false;
+
+  @override
+  ListBuilder<Expression> annotations = ListBuilder<Expression>();
+
+  @override
+  ListBuilder<String> docs = ListBuilder<String>();
+
+  @override
+  ListBuilder<Reference> types = ListBuilder<Reference>();
+
+  /// Type of the parameter;
+  Reference type;
+}
diff --git a/code_builder/lib/src/specs/method.g.dart b/code_builder/lib/src/specs/method.g.dart
new file mode 100644
index 0000000..b6b862a
--- /dev/null
+++ b/code_builder/lib/src/specs/method.g.dart
@@ -0,0 +1,641 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'method.dart';
+
+// **************************************************************************
+// BuiltValueGenerator
+// **************************************************************************
+
+// ignore_for_file: always_put_control_body_on_new_line
+// ignore_for_file: annotate_overrides
+// ignore_for_file: avoid_annotating_with_dynamic
+// ignore_for_file: avoid_catches_without_on_clauses
+// ignore_for_file: avoid_returning_this
+// ignore_for_file: lines_longer_than_80_chars
+// ignore_for_file: omit_local_variable_types
+// ignore_for_file: prefer_expression_function_bodies
+// ignore_for_file: sort_constructors_first
+
+class _$Method extends Method {
+  @override
+  final BuiltList<Expression> annotations;
+  @override
+  final BuiltList<String> docs;
+  @override
+  final BuiltList<Reference> types;
+  @override
+  final BuiltList<Parameter> optionalParameters;
+  @override
+  final BuiltList<Parameter> requiredParameters;
+  @override
+  final Code body;
+  @override
+  final bool external;
+  @override
+  final bool lambda;
+  @override
+  final bool static;
+  @override
+  final String name;
+  @override
+  final MethodType type;
+  @override
+  final MethodModifier modifier;
+  @override
+  final Reference returns;
+
+  factory _$Method([void updates(MethodBuilder b)]) =>
+      (new MethodBuilder()..update(updates)).build() as _$Method;
+
+  _$Method._(
+      {this.annotations,
+      this.docs,
+      this.types,
+      this.optionalParameters,
+      this.requiredParameters,
+      this.body,
+      this.external,
+      this.lambda,
+      this.static,
+      this.name,
+      this.type,
+      this.modifier,
+      this.returns})
+      : super._() {
+    if (annotations == null)
+      throw new BuiltValueNullFieldError('Method', 'annotations');
+    if (docs == null) throw new BuiltValueNullFieldError('Method', 'docs');
+    if (types == null) throw new BuiltValueNullFieldError('Method', 'types');
+    if (optionalParameters == null)
+      throw new BuiltValueNullFieldError('Method', 'optionalParameters');
+    if (requiredParameters == null)
+      throw new BuiltValueNullFieldError('Method', 'requiredParameters');
+    if (external == null)
+      throw new BuiltValueNullFieldError('Method', 'external');
+    if (static == null) throw new BuiltValueNullFieldError('Method', 'static');
+  }
+
+  @override
+  Method rebuild(void updates(MethodBuilder b)) =>
+      (toBuilder()..update(updates)).build();
+
+  @override
+  _$MethodBuilder toBuilder() => new _$MethodBuilder()..replace(this);
+
+  @override
+  bool operator ==(dynamic other) {
+    if (identical(other, this)) return true;
+    if (other is! Method) return false;
+    return annotations == other.annotations &&
+        docs == other.docs &&
+        types == other.types &&
+        optionalParameters == other.optionalParameters &&
+        requiredParameters == other.requiredParameters &&
+        body == other.body &&
+        external == other.external &&
+        lambda == other.lambda &&
+        static == other.static &&
+        name == other.name &&
+        type == other.type &&
+        modifier == other.modifier &&
+        returns == other.returns;
+  }
+
+  @override
+  int get hashCode {
+    return $jf($jc(
+        $jc(
+            $jc(
+                $jc(
+                    $jc(
+                        $jc(
+                            $jc(
+                                $jc(
+                                    $jc(
+                                        $jc(
+                                            $jc(
+                                                $jc(
+                                                    $jc(0,
+                                                        annotations.hashCode),
+                                                    docs.hashCode),
+                                                types.hashCode),
+                                            optionalParameters.hashCode),
+                                        requiredParameters.hashCode),
+                                    body.hashCode),
+                                external.hashCode),
+                            lambda.hashCode),
+                        static.hashCode),
+                    name.hashCode),
+                type.hashCode),
+            modifier.hashCode),
+        returns.hashCode));
+  }
+
+  @override
+  String toString() {
+    return (newBuiltValueToStringHelper('Method')
+          ..add('annotations', annotations)
+          ..add('docs', docs)
+          ..add('types', types)
+          ..add('optionalParameters', optionalParameters)
+          ..add('requiredParameters', requiredParameters)
+          ..add('body', body)
+          ..add('external', external)
+          ..add('lambda', lambda)
+          ..add('static', static)
+          ..add('name', name)
+          ..add('type', type)
+          ..add('modifier', modifier)
+          ..add('returns', returns))
+        .toString();
+  }
+}
+
+class _$MethodBuilder extends MethodBuilder {
+  _$Method _$v;
+
+  @override
+  ListBuilder<Expression> get annotations {
+    _$this;
+    return super.annotations ??= new ListBuilder<Expression>();
+  }
+
+  @override
+  set annotations(ListBuilder<Expression> annotations) {
+    _$this;
+    super.annotations = annotations;
+  }
+
+  @override
+  ListBuilder<String> get docs {
+    _$this;
+    return super.docs ??= new ListBuilder<String>();
+  }
+
+  @override
+  set docs(ListBuilder<String> docs) {
+    _$this;
+    super.docs = docs;
+  }
+
+  @override
+  ListBuilder<Reference> get types {
+    _$this;
+    return super.types ??= new ListBuilder<Reference>();
+  }
+
+  @override
+  set types(ListBuilder<Reference> types) {
+    _$this;
+    super.types = types;
+  }
+
+  @override
+  ListBuilder<Parameter> get optionalParameters {
+    _$this;
+    return super.optionalParameters ??= new ListBuilder<Parameter>();
+  }
+
+  @override
+  set optionalParameters(ListBuilder<Parameter> optionalParameters) {
+    _$this;
+    super.optionalParameters = optionalParameters;
+  }
+
+  @override
+  ListBuilder<Parameter> get requiredParameters {
+    _$this;
+    return super.requiredParameters ??= new ListBuilder<Parameter>();
+  }
+
+  @override
+  set requiredParameters(ListBuilder<Parameter> requiredParameters) {
+    _$this;
+    super.requiredParameters = requiredParameters;
+  }
+
+  @override
+  Code get body {
+    _$this;
+    return super.body;
+  }
+
+  @override
+  set body(Code body) {
+    _$this;
+    super.body = body;
+  }
+
+  @override
+  bool get external {
+    _$this;
+    return super.external;
+  }
+
+  @override
+  set external(bool external) {
+    _$this;
+    super.external = external;
+  }
+
+  @override
+  bool get lambda {
+    _$this;
+    return super.lambda;
+  }
+
+  @override
+  set lambda(bool lambda) {
+    _$this;
+    super.lambda = lambda;
+  }
+
+  @override
+  bool get static {
+    _$this;
+    return super.static;
+  }
+
+  @override
+  set static(bool static) {
+    _$this;
+    super.static = static;
+  }
+
+  @override
+  String get name {
+    _$this;
+    return super.name;
+  }
+
+  @override
+  set name(String name) {
+    _$this;
+    super.name = name;
+  }
+
+  @override
+  MethodType get type {
+    _$this;
+    return super.type;
+  }
+
+  @override
+  set type(MethodType type) {
+    _$this;
+    super.type = type;
+  }
+
+  @override
+  MethodModifier get modifier {
+    _$this;
+    return super.modifier;
+  }
+
+  @override
+  set modifier(MethodModifier modifier) {
+    _$this;
+    super.modifier = modifier;
+  }
+
+  @override
+  Reference get returns {
+    _$this;
+    return super.returns;
+  }
+
+  @override
+  set returns(Reference returns) {
+    _$this;
+    super.returns = returns;
+  }
+
+  _$MethodBuilder() : super._();
+
+  MethodBuilder get _$this {
+    if (_$v != null) {
+      super.annotations = _$v.annotations?.toBuilder();
+      super.docs = _$v.docs?.toBuilder();
+      super.types = _$v.types?.toBuilder();
+      super.optionalParameters = _$v.optionalParameters?.toBuilder();
+      super.requiredParameters = _$v.requiredParameters?.toBuilder();
+      super.body = _$v.body;
+      super.external = _$v.external;
+      super.lambda = _$v.lambda;
+      super.static = _$v.static;
+      super.name = _$v.name;
+      super.type = _$v.type;
+      super.modifier = _$v.modifier;
+      super.returns = _$v.returns;
+      _$v = null;
+    }
+    return this;
+  }
+
+  @override
+  void replace(Method other) {
+    if (other == null) throw new ArgumentError.notNull('other');
+    _$v = other as _$Method;
+  }
+
+  @override
+  void update(void updates(MethodBuilder b)) {
+    if (updates != null) updates(this);
+  }
+
+  @override
+  _$Method build() {
+    _$Method _$result;
+    try {
+      _$result = _$v ??
+          new _$Method._(
+              annotations: annotations.build(),
+              docs: docs.build(),
+              types: types.build(),
+              optionalParameters: optionalParameters.build(),
+              requiredParameters: requiredParameters.build(),
+              body: body,
+              external: external,
+              lambda: lambda,
+              static: static,
+              name: name,
+              type: type,
+              modifier: modifier,
+              returns: returns);
+    } catch (_) {
+      String _$failedField;
+      try {
+        _$failedField = 'annotations';
+        annotations.build();
+        _$failedField = 'docs';
+        docs.build();
+        _$failedField = 'types';
+        types.build();
+        _$failedField = 'optionalParameters';
+        optionalParameters.build();
+        _$failedField = 'requiredParameters';
+        requiredParameters.build();
+      } catch (e) {
+        throw new BuiltValueNestedFieldError(
+            'Method', _$failedField, e.toString());
+      }
+      rethrow;
+    }
+    replace(_$result);
+    return _$result;
+  }
+}
+
+class _$Parameter extends Parameter {
+  @override
+  final Code defaultTo;
+  @override
+  final String name;
+  @override
+  final bool named;
+  @override
+  final bool toThis;
+  @override
+  final BuiltList<Expression> annotations;
+  @override
+  final BuiltList<String> docs;
+  @override
+  final BuiltList<Reference> types;
+  @override
+  final Reference type;
+
+  factory _$Parameter([void updates(ParameterBuilder b)]) =>
+      (new ParameterBuilder()..update(updates)).build() as _$Parameter;
+
+  _$Parameter._(
+      {this.defaultTo,
+      this.name,
+      this.named,
+      this.toThis,
+      this.annotations,
+      this.docs,
+      this.types,
+      this.type})
+      : super._() {
+    if (name == null) throw new BuiltValueNullFieldError('Parameter', 'name');
+    if (named == null) throw new BuiltValueNullFieldError('Parameter', 'named');
+    if (toThis == null)
+      throw new BuiltValueNullFieldError('Parameter', 'toThis');
+    if (annotations == null)
+      throw new BuiltValueNullFieldError('Parameter', 'annotations');
+    if (docs == null) throw new BuiltValueNullFieldError('Parameter', 'docs');
+    if (types == null) throw new BuiltValueNullFieldError('Parameter', 'types');
+  }
+
+  @override
+  Parameter rebuild(void updates(ParameterBuilder b)) =>
+      (toBuilder()..update(updates)).build();
+
+  @override
+  _$ParameterBuilder toBuilder() => new _$ParameterBuilder()..replace(this);
+
+  @override
+  bool operator ==(dynamic other) {
+    if (identical(other, this)) return true;
+    if (other is! Parameter) return false;
+    return defaultTo == other.defaultTo &&
+        name == other.name &&
+        named == other.named &&
+        toThis == other.toThis &&
+        annotations == other.annotations &&
+        docs == other.docs &&
+        types == other.types &&
+        type == other.type;
+  }
+
+  @override
+  int get hashCode {
+    return $jf($jc(
+        $jc(
+            $jc(
+                $jc(
+                    $jc(
+                        $jc($jc($jc(0, defaultTo.hashCode), name.hashCode),
+                            named.hashCode),
+                        toThis.hashCode),
+                    annotations.hashCode),
+                docs.hashCode),
+            types.hashCode),
+        type.hashCode));
+  }
+
+  @override
+  String toString() {
+    return (newBuiltValueToStringHelper('Parameter')
+          ..add('defaultTo', defaultTo)
+          ..add('name', name)
+          ..add('named', named)
+          ..add('toThis', toThis)
+          ..add('annotations', annotations)
+          ..add('docs', docs)
+          ..add('types', types)
+          ..add('type', type))
+        .toString();
+  }
+}
+
+class _$ParameterBuilder extends ParameterBuilder {
+  _$Parameter _$v;
+
+  @override
+  Code get defaultTo {
+    _$this;
+    return super.defaultTo;
+  }
+
+  @override
+  set defaultTo(Code defaultTo) {
+    _$this;
+    super.defaultTo = defaultTo;
+  }
+
+  @override
+  String get name {
+    _$this;
+    return super.name;
+  }
+
+  @override
+  set name(String name) {
+    _$this;
+    super.name = name;
+  }
+
+  @override
+  bool get named {
+    _$this;
+    return super.named;
+  }
+
+  @override
+  set named(bool named) {
+    _$this;
+    super.named = named;
+  }
+
+  @override
+  bool get toThis {
+    _$this;
+    return super.toThis;
+  }
+
+  @override
+  set toThis(bool toThis) {
+    _$this;
+    super.toThis = toThis;
+  }
+
+  @override
+  ListBuilder<Expression> get annotations {
+    _$this;
+    return super.annotations ??= new ListBuilder<Expression>();
+  }
+
+  @override
+  set annotations(ListBuilder<Expression> annotations) {
+    _$this;
+    super.annotations = annotations;
+  }
+
+  @override
+  ListBuilder<String> get docs {
+    _$this;
+    return super.docs ??= new ListBuilder<String>();
+  }
+
+  @override
+  set docs(ListBuilder<String> docs) {
+    _$this;
+    super.docs = docs;
+  }
+
+  @override
+  ListBuilder<Reference> get types {
+    _$this;
+    return super.types ??= new ListBuilder<Reference>();
+  }
+
+  @override
+  set types(ListBuilder<Reference> types) {
+    _$this;
+    super.types = types;
+  }
+
+  @override
+  Reference get type {
+    _$this;
+    return super.type;
+  }
+
+  @override
+  set type(Reference type) {
+    _$this;
+    super.type = type;
+  }
+
+  _$ParameterBuilder() : super._();
+
+  ParameterBuilder get _$this {
+    if (_$v != null) {
+      super.defaultTo = _$v.defaultTo;
+      super.name = _$v.name;
+      super.named = _$v.named;
+      super.toThis = _$v.toThis;
+      super.annotations = _$v.annotations?.toBuilder();
+      super.docs = _$v.docs?.toBuilder();
+      super.types = _$v.types?.toBuilder();
+      super.type = _$v.type;
+      _$v = null;
+    }
+    return this;
+  }
+
+  @override
+  void replace(Parameter other) {
+    if (other == null) throw new ArgumentError.notNull('other');
+    _$v = other as _$Parameter;
+  }
+
+  @override
+  void update(void updates(ParameterBuilder b)) {
+    if (updates != null) updates(this);
+  }
+
+  @override
+  _$Parameter build() {
+    _$Parameter _$result;
+    try {
+      _$result = _$v ??
+          new _$Parameter._(
+              defaultTo: defaultTo,
+              name: name,
+              named: named,
+              toThis: toThis,
+              annotations: annotations.build(),
+              docs: docs.build(),
+              types: types.build(),
+              type: type);
+    } catch (_) {
+      String _$failedField;
+      try {
+        _$failedField = 'annotations';
+        annotations.build();
+        _$failedField = 'docs';
+        docs.build();
+        _$failedField = 'types';
+        types.build();
+      } catch (e) {
+        throw new BuiltValueNestedFieldError(
+            'Parameter', _$failedField, e.toString());
+      }
+      rethrow;
+    }
+    replace(_$result);
+    return _$result;
+  }
+}
diff --git a/code_builder/lib/src/specs/reference.dart b/code_builder/lib/src/specs/reference.dart
new file mode 100644
index 0000000..102d330
--- /dev/null
+++ b/code_builder/lib/src/specs/reference.dart
@@ -0,0 +1,129 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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.
+
+library code_builder.src.specs.reference;
+
+import 'package:built_value/built_value.dart';
+import 'package:meta/meta.dart';
+
+import '../base.dart';
+import '../visitors.dart';
+import 'code.dart';
+import 'expression.dart';
+import 'type_reference.dart';
+
+/// Short-hand for `Reference(symbol, url)`.
+Reference refer(String symbol, [String url]) {
+  return Reference(symbol, url);
+}
+
+/// A reference to [symbol], such as a class, or top-level method or field.
+///
+/// References can be collected and collated in order to automatically generate
+/// `import` statements for all used symbols.
+@immutable
+class Reference extends Expression implements Spec {
+  /// Relative, `package:` or `dart:` URL of the library.
+  ///
+  /// May be omitted (`null`) in order to express "same library".
+  final String url;
+
+  /// Name of the class, method, or field.
+  final String symbol;
+
+  /// Create a reference to [symbol] in [url].
+  const Reference(this.symbol, [this.url]);
+
+  @override
+  R accept<R>(
+    SpecVisitor<R> visitor, [
+    R context,
+  ]) =>
+      visitor.visitReference(this, context);
+
+  @override
+  int get hashCode => '$url#$symbol'.hashCode;
+
+  @override
+  bool operator ==(Object o) =>
+      o is Reference && o.url == url && o.symbol == symbol;
+
+  /// Returns a new instance of this expression.
+  Expression newInstance(
+    Iterable<Expression> positionalArguments, [
+    Map<String, Expression> namedArguments = const {},
+    List<Reference> typeArguments = const [],
+  ]) {
+    return InvokeExpression.newOf(
+      this,
+      positionalArguments.toList(),
+      namedArguments,
+      typeArguments,
+      null,
+    );
+  }
+
+  /// Returns a new instance of this expression with a named constructor.
+  Expression newInstanceNamed(
+    String name,
+    Iterable<Expression> positionalArguments, [
+    Map<String, Expression> namedArguments = const {},
+    List<Reference> typeArguments = const [],
+  ]) {
+    return InvokeExpression.newOf(
+      this,
+      positionalArguments.toList(),
+      namedArguments,
+      typeArguments,
+      name,
+    );
+  }
+
+  /// Returns a const instance of this expression.
+  Expression constInstance(
+    Iterable<Expression> positionalArguments, [
+    Map<String, Expression> namedArguments = const {},
+    List<Reference> typeArguments = const [],
+  ]) {
+    return InvokeExpression.constOf(
+      this,
+      positionalArguments.toList(),
+      namedArguments,
+      typeArguments,
+      null,
+    );
+  }
+
+  /// Returns a const instance of this expression with a named constructor.
+  Expression constInstanceNamed(
+    String name,
+    Iterable<Expression> positionalArguments, [
+    Map<String, Expression> namedArguments = const {},
+    List<Reference> typeArguments = const [],
+  ]) {
+    return InvokeExpression.constOf(
+      this,
+      positionalArguments.toList(),
+      namedArguments,
+      typeArguments,
+      name,
+    );
+  }
+
+  @override
+  Expression get expression {
+    return CodeExpression(Code.scope((a) => a(this)));
+  }
+
+  @override
+  String toString() => (newBuiltValueToStringHelper('Reference')
+        ..add('url', url)
+        ..add('symbol', symbol))
+      .toString();
+
+  /// Returns as a [TypeReference], which allows adding generic type parameters.
+  Reference get type => TypeReference((b) => b
+    ..url = url
+    ..symbol = symbol);
+}
diff --git a/code_builder/lib/src/specs/type_function.dart b/code_builder/lib/src/specs/type_function.dart
new file mode 100644
index 0000000..b7b9014
--- /dev/null
+++ b/code_builder/lib/src/specs/type_function.dart
@@ -0,0 +1,116 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'package:built_value/built_value.dart';
+import 'package:built_collection/built_collection.dart';
+import 'package:meta/meta.dart';
+
+import '../base.dart';
+import '../mixins/generics.dart';
+import '../visitors.dart';
+import 'code.dart';
+import 'expression.dart';
+import 'reference.dart';
+
+part 'type_function.g.dart';
+
+@immutable
+abstract class FunctionType extends Expression
+    with HasGenerics
+    implements Built<FunctionType, FunctionTypeBuilder>, Reference, Spec {
+  factory FunctionType([
+    void updates(FunctionTypeBuilder b),
+  ]) = _$FunctionType;
+
+  FunctionType._();
+
+  @override
+  R accept<R>(
+    SpecVisitor<R> visitor, [
+    R context,
+  ]) =>
+      visitor.visitFunctionType(this, context);
+
+  /// Return type.
+  @nullable
+  Reference get returnType;
+
+  @override
+  BuiltList<Reference> get types;
+
+  /// Required positional arguments to this function type.
+  BuiltList<Reference> get requiredParameters;
+
+  /// Optional positional arguments to this function type.
+  BuiltList<Reference> get optionalParameters;
+
+  /// Named optional arguments to this function type.
+  BuiltMap<String, Reference> get namedParameters;
+
+  @override
+  String get url => null;
+
+  @override
+  String get symbol => null;
+
+  @override
+  Reference get type => this;
+
+  @override
+  Expression newInstance(
+    Iterable<Expression> positionalArguments, [
+    Map<String, Expression> namedArguments = const {},
+    List<Reference> typeArguments = const [],
+  ]) =>
+      throw UnsupportedError('Cannot instantiate a function type.');
+
+  @override
+  Expression newInstanceNamed(
+    String name,
+    Iterable<Expression> positionalArguments, [
+    Map<String, Expression> namedArguments = const {},
+    List<Reference> typeArguments = const [],
+  ]) =>
+      throw UnsupportedError('Cannot instantiate a function type.');
+
+  @override
+  Expression constInstance(
+    Iterable<Expression> positionalArguments, [
+    Map<String, Expression> namedArguments = const {},
+    List<Reference> typeArguments = const [],
+  ]) =>
+      throw UnsupportedError('Cannot "const" a function type.');
+
+  @override
+  Expression constInstanceNamed(
+    String name,
+    Iterable<Expression> positionalArguments, [
+    Map<String, Expression> namedArguments = const {},
+    List<Reference> typeArguments = const [],
+  ]) =>
+      throw UnsupportedError('Cannot "const" a function type.');
+
+  /// A typedef assignment to this type.
+  Code toTypeDef(String name) => createTypeDef(name, this);
+}
+
+abstract class FunctionTypeBuilder extends Object
+    with HasGenericsBuilder
+    implements Builder<FunctionType, FunctionTypeBuilder> {
+  factory FunctionTypeBuilder() = _$FunctionTypeBuilder;
+
+  FunctionTypeBuilder._();
+
+  Reference returnType;
+
+  @override
+  ListBuilder<Reference> types = ListBuilder<Reference>();
+
+  ListBuilder<Reference> requiredParameters = ListBuilder<Reference>();
+
+  ListBuilder<Reference> optionalParameters = ListBuilder<Reference>();
+
+  MapBuilder<String, Reference> namedParameters =
+      MapBuilder<String, Reference>();
+}
diff --git a/code_builder/lib/src/specs/type_function.g.dart b/code_builder/lib/src/specs/type_function.g.dart
new file mode 100644
index 0000000..b89237f
--- /dev/null
+++ b/code_builder/lib/src/specs/type_function.g.dart
@@ -0,0 +1,211 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'type_function.dart';
+
+// **************************************************************************
+// BuiltValueGenerator
+// **************************************************************************
+
+// ignore_for_file: always_put_control_body_on_new_line
+// ignore_for_file: annotate_overrides
+// ignore_for_file: avoid_annotating_with_dynamic
+// ignore_for_file: avoid_catches_without_on_clauses
+// ignore_for_file: avoid_returning_this
+// ignore_for_file: lines_longer_than_80_chars
+// ignore_for_file: omit_local_variable_types
+// ignore_for_file: prefer_expression_function_bodies
+// ignore_for_file: sort_constructors_first
+
+class _$FunctionType extends FunctionType {
+  @override
+  final Reference returnType;
+  @override
+  final BuiltList<Reference> types;
+  @override
+  final BuiltList<Reference> requiredParameters;
+  @override
+  final BuiltList<Reference> optionalParameters;
+  @override
+  final BuiltMap<String, Reference> namedParameters;
+
+  factory _$FunctionType([void updates(FunctionTypeBuilder b)]) =>
+      (new FunctionTypeBuilder()..update(updates)).build() as _$FunctionType;
+
+  _$FunctionType._(
+      {this.returnType,
+      this.types,
+      this.requiredParameters,
+      this.optionalParameters,
+      this.namedParameters})
+      : super._() {
+    if (types == null)
+      throw new BuiltValueNullFieldError('FunctionType', 'types');
+    if (requiredParameters == null)
+      throw new BuiltValueNullFieldError('FunctionType', 'requiredParameters');
+    if (optionalParameters == null)
+      throw new BuiltValueNullFieldError('FunctionType', 'optionalParameters');
+    if (namedParameters == null)
+      throw new BuiltValueNullFieldError('FunctionType', 'namedParameters');
+  }
+
+  @override
+  FunctionType rebuild(void updates(FunctionTypeBuilder b)) =>
+      (toBuilder()..update(updates)).build();
+
+  @override
+  _$FunctionTypeBuilder toBuilder() =>
+      new _$FunctionTypeBuilder()..replace(this);
+
+  @override
+  bool operator ==(dynamic other) {
+    if (identical(other, this)) return true;
+    if (other is! FunctionType) return false;
+    return returnType == other.returnType &&
+        types == other.types &&
+        requiredParameters == other.requiredParameters &&
+        optionalParameters == other.optionalParameters &&
+        namedParameters == other.namedParameters;
+  }
+
+  @override
+  int get hashCode {
+    return $jf($jc(
+        $jc(
+            $jc($jc($jc(0, returnType.hashCode), types.hashCode),
+                requiredParameters.hashCode),
+            optionalParameters.hashCode),
+        namedParameters.hashCode));
+  }
+
+  @override
+  String toString() {
+    return (newBuiltValueToStringHelper('FunctionType')
+          ..add('returnType', returnType)
+          ..add('types', types)
+          ..add('requiredParameters', requiredParameters)
+          ..add('optionalParameters', optionalParameters)
+          ..add('namedParameters', namedParameters))
+        .toString();
+  }
+}
+
+class _$FunctionTypeBuilder extends FunctionTypeBuilder {
+  _$FunctionType _$v;
+
+  @override
+  Reference get returnType {
+    _$this;
+    return super.returnType;
+  }
+
+  @override
+  set returnType(Reference returnType) {
+    _$this;
+    super.returnType = returnType;
+  }
+
+  @override
+  ListBuilder<Reference> get types {
+    _$this;
+    return super.types ??= new ListBuilder<Reference>();
+  }
+
+  @override
+  set types(ListBuilder<Reference> types) {
+    _$this;
+    super.types = types;
+  }
+
+  @override
+  ListBuilder<Reference> get requiredParameters {
+    _$this;
+    return super.requiredParameters ??= new ListBuilder<Reference>();
+  }
+
+  @override
+  set requiredParameters(ListBuilder<Reference> requiredParameters) {
+    _$this;
+    super.requiredParameters = requiredParameters;
+  }
+
+  @override
+  ListBuilder<Reference> get optionalParameters {
+    _$this;
+    return super.optionalParameters ??= new ListBuilder<Reference>();
+  }
+
+  @override
+  set optionalParameters(ListBuilder<Reference> optionalParameters) {
+    _$this;
+    super.optionalParameters = optionalParameters;
+  }
+
+  @override
+  MapBuilder<String, Reference> get namedParameters {
+    _$this;
+    return super.namedParameters ??= new MapBuilder<String, Reference>();
+  }
+
+  @override
+  set namedParameters(MapBuilder<String, Reference> namedParameters) {
+    _$this;
+    super.namedParameters = namedParameters;
+  }
+
+  _$FunctionTypeBuilder() : super._();
+
+  FunctionTypeBuilder get _$this {
+    if (_$v != null) {
+      super.returnType = _$v.returnType;
+      super.types = _$v.types?.toBuilder();
+      super.requiredParameters = _$v.requiredParameters?.toBuilder();
+      super.optionalParameters = _$v.optionalParameters?.toBuilder();
+      super.namedParameters = _$v.namedParameters?.toBuilder();
+      _$v = null;
+    }
+    return this;
+  }
+
+  @override
+  void replace(FunctionType other) {
+    if (other == null) throw new ArgumentError.notNull('other');
+    _$v = other as _$FunctionType;
+  }
+
+  @override
+  void update(void updates(FunctionTypeBuilder b)) {
+    if (updates != null) updates(this);
+  }
+
+  @override
+  _$FunctionType build() {
+    _$FunctionType _$result;
+    try {
+      _$result = _$v ??
+          new _$FunctionType._(
+              returnType: returnType,
+              types: types.build(),
+              requiredParameters: requiredParameters.build(),
+              optionalParameters: optionalParameters.build(),
+              namedParameters: namedParameters.build());
+    } catch (_) {
+      String _$failedField;
+      try {
+        _$failedField = 'types';
+        types.build();
+        _$failedField = 'requiredParameters';
+        requiredParameters.build();
+        _$failedField = 'optionalParameters';
+        optionalParameters.build();
+        _$failedField = 'namedParameters';
+        namedParameters.build();
+      } catch (e) {
+        throw new BuiltValueNestedFieldError(
+            'FunctionType', _$failedField, e.toString());
+      }
+      rethrow;
+    }
+    replace(_$result);
+    return _$result;
+  }
+}
diff --git a/code_builder/lib/src/specs/type_reference.dart b/code_builder/lib/src/specs/type_reference.dart
new file mode 100644
index 0000000..1f864f9
--- /dev/null
+++ b/code_builder/lib/src/specs/type_reference.dart
@@ -0,0 +1,136 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'package:built_value/built_value.dart';
+import 'package:built_collection/built_collection.dart';
+import 'package:meta/meta.dart';
+
+import '../base.dart';
+import '../mixins/generics.dart';
+import '../visitors.dart';
+import 'code.dart';
+import 'expression.dart';
+import 'reference.dart';
+
+part 'type_reference.g.dart';
+
+@immutable
+abstract class TypeReference extends Expression
+    with HasGenerics
+    implements Built<TypeReference, TypeReferenceBuilder>, Reference, Spec {
+  factory TypeReference([
+    void updates(TypeReferenceBuilder b),
+  ]) = _$TypeReference;
+
+  TypeReference._();
+
+  @override
+  String get symbol;
+
+  @override
+  @nullable
+  String get url;
+
+  /// Optional bound generic.
+  @nullable
+  Reference get bound;
+
+  @override
+  BuiltList<Reference> get types;
+
+  @override
+  R accept<R>(
+    SpecVisitor<R> visitor, [
+    R context,
+  ]) =>
+      visitor.visitType(this, context);
+
+  @override
+  Expression get expression {
+    return CodeExpression(Code.scope((a) => a(this)));
+  }
+
+  @override
+  TypeReference get type => this;
+
+  @override
+  Expression newInstance(
+    Iterable<Expression> positionalArguments, [
+    Map<String, Expression> namedArguments = const {},
+    List<Reference> typeArguments = const [],
+  ]) {
+    return InvokeExpression.newOf(
+      this,
+      positionalArguments.toList(),
+      namedArguments,
+      typeArguments,
+      null,
+    );
+  }
+
+  @override
+  Expression newInstanceNamed(
+    String name,
+    Iterable<Expression> positionalArguments, [
+    Map<String, Expression> namedArguments = const {},
+    List<Reference> typeArguments = const [],
+  ]) {
+    return InvokeExpression.newOf(
+      this,
+      positionalArguments.toList(),
+      namedArguments,
+      typeArguments,
+      name,
+    );
+  }
+
+  @override
+  Expression constInstance(
+    Iterable<Expression> positionalArguments, [
+    Map<String, Expression> namedArguments = const {},
+    List<Reference> typeArguments = const [],
+  ]) {
+    return InvokeExpression.constOf(
+      this,
+      positionalArguments.toList(),
+      namedArguments,
+      typeArguments,
+      null,
+    );
+  }
+
+  @override
+  Expression constInstanceNamed(
+    String name,
+    Iterable<Expression> positionalArguments, [
+    Map<String, Expression> namedArguments = const {},
+    List<Reference> typeArguments = const [],
+  ]) {
+    return InvokeExpression.constOf(
+      this,
+      positionalArguments.toList(),
+      namedArguments,
+      typeArguments,
+      name,
+    );
+  }
+}
+
+abstract class TypeReferenceBuilder extends Object
+    with HasGenericsBuilder
+    implements Builder<TypeReference, TypeReferenceBuilder> {
+  factory TypeReferenceBuilder() = _$TypeReferenceBuilder;
+
+  TypeReferenceBuilder._();
+
+  String symbol;
+
+  String url;
+
+  /// Optional bound generic.
+  Reference bound;
+
+  @override
+  ListBuilder<Reference> types = ListBuilder<Reference>();
+}
diff --git a/code_builder/lib/src/specs/type_reference.g.dart b/code_builder/lib/src/specs/type_reference.g.dart
new file mode 100644
index 0000000..bcd362f
--- /dev/null
+++ b/code_builder/lib/src/specs/type_reference.g.dart
@@ -0,0 +1,172 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'type_reference.dart';
+
+// **************************************************************************
+// BuiltValueGenerator
+// **************************************************************************
+
+// ignore_for_file: always_put_control_body_on_new_line
+// ignore_for_file: annotate_overrides
+// ignore_for_file: avoid_annotating_with_dynamic
+// ignore_for_file: avoid_catches_without_on_clauses
+// ignore_for_file: avoid_returning_this
+// ignore_for_file: lines_longer_than_80_chars
+// ignore_for_file: omit_local_variable_types
+// ignore_for_file: prefer_expression_function_bodies
+// ignore_for_file: sort_constructors_first
+
+class _$TypeReference extends TypeReference {
+  @override
+  final String symbol;
+  @override
+  final String url;
+  @override
+  final Reference bound;
+  @override
+  final BuiltList<Reference> types;
+
+  factory _$TypeReference([void updates(TypeReferenceBuilder b)]) =>
+      (new TypeReferenceBuilder()..update(updates)).build() as _$TypeReference;
+
+  _$TypeReference._({this.symbol, this.url, this.bound, this.types})
+      : super._() {
+    if (symbol == null)
+      throw new BuiltValueNullFieldError('TypeReference', 'symbol');
+    if (types == null)
+      throw new BuiltValueNullFieldError('TypeReference', 'types');
+  }
+
+  @override
+  TypeReference rebuild(void updates(TypeReferenceBuilder b)) =>
+      (toBuilder()..update(updates)).build();
+
+  @override
+  _$TypeReferenceBuilder toBuilder() =>
+      new _$TypeReferenceBuilder()..replace(this);
+
+  @override
+  bool operator ==(dynamic other) {
+    if (identical(other, this)) return true;
+    if (other is! TypeReference) return false;
+    return symbol == other.symbol &&
+        url == other.url &&
+        bound == other.bound &&
+        types == other.types;
+  }
+
+  @override
+  int get hashCode {
+    return $jf($jc(
+        $jc($jc($jc(0, symbol.hashCode), url.hashCode), bound.hashCode),
+        types.hashCode));
+  }
+
+  @override
+  String toString() {
+    return (newBuiltValueToStringHelper('TypeReference')
+          ..add('symbol', symbol)
+          ..add('url', url)
+          ..add('bound', bound)
+          ..add('types', types))
+        .toString();
+  }
+}
+
+class _$TypeReferenceBuilder extends TypeReferenceBuilder {
+  _$TypeReference _$v;
+
+  @override
+  String get symbol {
+    _$this;
+    return super.symbol;
+  }
+
+  @override
+  set symbol(String symbol) {
+    _$this;
+    super.symbol = symbol;
+  }
+
+  @override
+  String get url {
+    _$this;
+    return super.url;
+  }
+
+  @override
+  set url(String url) {
+    _$this;
+    super.url = url;
+  }
+
+  @override
+  Reference get bound {
+    _$this;
+    return super.bound;
+  }
+
+  @override
+  set bound(Reference bound) {
+    _$this;
+    super.bound = bound;
+  }
+
+  @override
+  ListBuilder<Reference> get types {
+    _$this;
+    return super.types ??= new ListBuilder<Reference>();
+  }
+
+  @override
+  set types(ListBuilder<Reference> types) {
+    _$this;
+    super.types = types;
+  }
+
+  _$TypeReferenceBuilder() : super._();
+
+  TypeReferenceBuilder get _$this {
+    if (_$v != null) {
+      super.symbol = _$v.symbol;
+      super.url = _$v.url;
+      super.bound = _$v.bound;
+      super.types = _$v.types?.toBuilder();
+      _$v = null;
+    }
+    return this;
+  }
+
+  @override
+  void replace(TypeReference other) {
+    if (other == null) throw new ArgumentError.notNull('other');
+    _$v = other as _$TypeReference;
+  }
+
+  @override
+  void update(void updates(TypeReferenceBuilder b)) {
+    if (updates != null) updates(this);
+  }
+
+  @override
+  _$TypeReference build() {
+    _$TypeReference _$result;
+    try {
+      _$result = _$v ??
+          new _$TypeReference._(
+              symbol: symbol, url: url, bound: bound, types: types.build());
+    } catch (_) {
+      String _$failedField;
+      try {
+        _$failedField = 'types';
+        types.build();
+      } catch (e) {
+        throw new BuiltValueNestedFieldError(
+            'TypeReference', _$failedField, e.toString());
+      }
+      rethrow;
+    }
+    replace(_$result);
+    return _$result;
+  }
+}
diff --git a/code_builder/lib/src/visitors.dart b/code_builder/lib/src/visitors.dart
new file mode 100644
index 0000000..62b47bc
--- /dev/null
+++ b/code_builder/lib/src/visitors.dart
@@ -0,0 +1,46 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'package:meta/meta.dart';
+
+import 'base.dart';
+import 'specs/class.dart';
+import 'specs/constructor.dart';
+import 'specs/directive.dart';
+import 'specs/expression.dart';
+import 'specs/field.dart';
+import 'specs/library.dart';
+import 'specs/method.dart';
+import 'specs/reference.dart';
+import 'specs/type_function.dart';
+import 'specs/type_reference.dart';
+
+@optionalTypeArgs
+abstract class SpecVisitor<T> {
+  const SpecVisitor._();
+
+  T visitAnnotation(Expression spec, [T context]);
+
+  T visitClass(Class spec, [T context]);
+
+  T visitConstructor(Constructor spec, String clazz, [T context]);
+
+  T visitDirective(Directive spec, [T context]);
+
+  T visitField(Field spec, [T context]);
+
+  T visitLibrary(Library spec, [T context]);
+
+  T visitFunctionType(FunctionType spec, [T context]);
+
+  T visitMethod(Method spec, [T context]);
+
+  T visitReference(Reference spec, [T context]);
+
+  T visitSpec(Spec spec, [T context]);
+
+  T visitType(TypeReference spec, [T context]);
+
+  T visitTypeParameters(Iterable<Reference> specs, [T context]);
+}
diff --git a/code_builder/pubspec.yaml b/code_builder/pubspec.yaml
new file mode 100644
index 0000000..60bcd09
--- /dev/null
+++ b/code_builder/pubspec.yaml
@@ -0,0 +1,28 @@
+name: code_builder
+version: 3.2.1
+
+description: >-
+  A fluent, builder-based library for generating valid Dart code
+author: Dart Team <misc@dartlang.org>
+homepage: https://github.com/dart-lang/code_builder
+
+environment:
+  sdk: '>=2.1.0 <3.0.0'
+
+dependencies:
+  built_collection: '>=3.0.0 <5.0.0'
+  built_value: ^7.0.0
+  collection: ^1.14.0
+  matcher: ^0.12.0
+  meta: ^1.0.5
+
+dev_dependencies:
+  build: ^1.0.0
+  build_runner: ^1.1.0
+  built_value_generator: ^7.0.0
+  dart_style: ^1.0.0
+  source_gen: ^0.9.0
+  test: ^1.3.0
+
+#dependency_overrides:
+#  built_value: ^7.0.0
diff --git a/code_builder/tool/src/builder.dart b/code_builder/tool/src/builder.dart
new file mode 100644
index 0000000..ff95814
--- /dev/null
+++ b/code_builder/tool/src/builder.dart
@@ -0,0 +1,14 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'package:build/build.dart';
+import 'package:built_value_generator/built_value_generator.dart';
+import 'package:source_gen/source_gen.dart';
+
+/// Returns a [Builder] to generate `.g.dart` files for `built_value`.
+Builder builtValueBuilder(BuilderOptions _) {
+  return PartBuilder([
+    const BuiltValueGenerator(),
+  ], '.g.dart');
+}
diff --git a/completion/.travis.yml b/completion/.travis.yml
index f8a21dd..7e4cc44 100644
--- a/completion/.travis.yml
+++ b/completion/.travis.yml
@@ -2,17 +2,11 @@
 
 dart:
   - dev
-  - 2.3.0
 
 dart_task:
   - test
-  - dartanalyzer: --fatal-infos --fatal-warnings .
-
-matrix:
-  include:
-  # Only validate formatting using the dev release
-  - dart: dev
-    dart_task: dartfmt
+  - dartfmt
+  - dartanalyzer
 
 # Only building master means that we don't run two builds for each pull request.
 branches:
diff --git a/completion/BUILD.gn b/completion/BUILD.gn
index c6d945e..f1d8965 100644
--- a/completion/BUILD.gn
+++ b/completion/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by importer.py for completion-0.2.2
+# This file is generated by importer.py for completion-0.2.1+1
 
 import("//build/dart/dart_library.gni")
 
diff --git a/completion/CHANGELOG.md b/completion/CHANGELOG.md
index 0f8ca1c..7fa074d 100644
--- a/completion/CHANGELOG.md
+++ b/completion/CHANGELOG.md
@@ -1,7 +1,3 @@
-## 0.2.2
-
-* Increase minimum Dart SDK to `2.3.0`.
-
 ## 0.2.1+1
 
 * Small fix to error handler.
diff --git a/completion/analysis_options.yaml b/completion/analysis_options.yaml
index c1d96c4..47f9b74 100644
--- a/completion/analysis_options.yaml
+++ b/completion/analysis_options.yaml
@@ -1,70 +1,63 @@
-include: package:pedantic/analysis_options.yaml
-
 analyzer:
   strong-mode:
     implicit-casts: false
-
+  errors:
+    unused_element: error
+    unused_import: error
+    unused_local_variable: error
+    dead_code: error
+    override_on_non_overriding_method: error
 linter:
   rules:
-    - avoid_catching_errors
+    - annotate_overrides
+    - avoid_empty_else
     - avoid_function_literals_in_foreach_calls
-    - avoid_private_typedef_functions
-    - avoid_redundant_argument_values
-    - avoid_renaming_method_parameters
+    - avoid_init_to_null
+    - avoid_null_checks_in_equality_operators
+    - avoid_return_types_on_setters
     - avoid_returning_null
-    - avoid_returning_null_for_void
     - avoid_unused_constructor_parameters
-    - avoid_void_async
     - await_only_futures
     - camel_case_types
     - cancel_subscriptions
-    - cascade_invocations
     - comment_references
     - constant_identifier_names
     - control_flow_in_finally
     - directives_ordering
+    - empty_catches
+    - empty_constructor_bodies
     - empty_statements
-    - file_names
     - hash_and_equals
     - implementation_imports
-    - invariant_booleans
     - iterable_contains_unrelated_type
-    - join_return_with_assignment
-    - lines_longer_than_80_chars
+    - library_names
+    - library_prefixes
     - list_remove_unrelated_type
-    - literal_only_boolean_expressions
-    - missing_whitespace_between_adjacent_strings
-    - no_adjacent_strings_in_list
-    - no_runtimeType_toString
+    - no_duplicate_case_values
     - non_constant_identifier_names
     - only_throw_errors
     - overridden_fields
     - package_api_docs
     - package_names
     - package_prefixed_library_names
-    - prefer_asserts_in_initializer_lists
-    - prefer_const_constructors
-    - prefer_const_declarations
-    - prefer_expression_function_bodies
-    - prefer_final_locals
-    - prefer_function_declarations_over_variables
-    - prefer_initializing_formals
-    - prefer_inlined_adds
-    - prefer_interpolation_to_compose_strings
-    - prefer_is_not_operator
-    - prefer_null_aware_operators
-    - prefer_relative_imports
+    - prefer_conditional_assignment
+    - prefer_final_fields
+    - prefer_is_empty
+    - prefer_is_not_empty
+    - prefer_single_quotes
     - prefer_typing_uninitialized_variables
-    - prefer_void_to_null
-    - sort_pub_dependencies
+    - recursive_getters
+    - slash_for_doc_comments
+    - super_goes_last
     - test_types_in_equals
     - throw_in_finally
+    - type_init_formals
+    - unawaited_futures
     - unnecessary_brace_in_string_interps
     - unnecessary_getters_setters
     - unnecessary_lambdas
     - unnecessary_null_aware_assignments
-    - unnecessary_overrides
-    - unnecessary_parenthesis
     - unnecessary_statements
-    - unnecessary_string_interpolations
-    - void_checks
+    - unused_catch_clause
+    - unrelated_type_equality_checks
+    - valid_regexps
diff --git a/completion/example/example-completion.sh b/completion/example/hello-completion.sh
similarity index 69%
rename from completion/example/example-completion.sh
rename to completion/example/hello-completion.sh
index 3dee89a..91e272f 100644
--- a/completion/example/example-completion.sh
+++ b/completion/example/hello-completion.sh
@@ -11,32 +11,32 @@
 #
 #    /usr/local/etc/bash_completion.d/
 
-###-begin-example.dart-completion-###
+###-begin-hello.dart-completion-###
 
 if type complete &>/dev/null; then
-  __example_dart_completion() {
+  __hello_dart_completion() {
     local si="$IFS"
     IFS=$'\n' COMPREPLY=($(COMP_CWORD="$COMP_CWORD" \
                            COMP_LINE="$COMP_LINE" \
                            COMP_POINT="$COMP_POINT" \
-                           example.dart completion -- "${COMP_WORDS[@]}" \
+                           hello.dart completion -- "${COMP_WORDS[@]}" \
                            2>/dev/null)) || return $?
     IFS="$si"
   }
-  complete -F __example_dart_completion example.dart
+  complete -F __hello_dart_completion hello.dart
 elif type compdef &>/dev/null; then
-  __example_dart_completion() {
+  __hello_dart_completion() {
     si=$IFS
     compadd -- $(COMP_CWORD=$((CURRENT-1)) \
                  COMP_LINE=$BUFFER \
                  COMP_POINT=0 \
-                 example.dart completion -- "${words[@]}" \
+                 hello.dart completion -- "${words[@]}" \
                  2>/dev/null)
     IFS=$si
   }
-  compdef __example_dart_completion example.dart
+  compdef __hello_dart_completion hello.dart
 elif type compctl &>/dev/null; then
-  __example_dart_completion() {
+  __hello_dart_completion() {
     local cword line point words si
     read -Ac words
     read -cn cword
@@ -47,14 +47,14 @@
     IFS=$'\n' reply=($(COMP_CWORD="$cword" \
                        COMP_LINE="$line" \
                        COMP_POINT="$point" \
-                       example.dart completion -- "${words[@]}" \
+                       hello.dart completion -- "${words[@]}" \
                        2>/dev/null)) || return $?
     IFS="$si"
   }
-  compctl -K __example_dart_completion example.dart
+  compctl -K __hello_dart_completion hello.dart
 fi
 
-###-end-example.dart-completion-###
+###-end-hello.dart-completion-###
 
-## Generated 2018-12-06 13:41:53.261614Z
+## Generated 2018-04-24 15:45:52.542816Z
 ## By /Users/kevmoo/source/github/completion.dart/bin/shell_completion_generator.dart
diff --git a/completion/example/example.dart b/completion/example/hello.dart
similarity index 94%
rename from completion/example/example.dart
rename to completion/example/hello.dart
index c20909a..81ce1de 100755
--- a/completion/example/example.dart
+++ b/completion/example/hello.dart
@@ -1,12 +1,11 @@
 #!/usr/bin/env dart
 import 'dart:io';
-
 import 'package:args/args.dart';
 import 'package:completion/completion.dart';
 
 import '../test/completion_tests_args.dart';
 
-void main(List<String> args) {
+main(List<String> args) {
   final argParser = getHelloSampleParser();
 
   ArgResults argResult;
@@ -46,7 +45,7 @@
           print(subCommandParser.usage);
           return;
         } else {
-          throw StateError(
+          throw new StateError(
               'no clue what that subCammand is: ${subSubCommand.name}');
         }
       }
@@ -68,7 +67,7 @@
 
   final greeting = argResult['friendly'] as bool ? 'Hiya' : 'Hello';
 
-  final salutationVal = argResult['salutation'] as String;
+  final String salutationVal = argResult['salutation'];
   final salutation = salutationVal == null ? '' : '$salutationVal ';
 
   var message = '$greeting, $salutation$name';
diff --git a/completion/example/example_completion_init.sh b/completion/example/hello_completion_init.sh
similarity index 73%
rename from completion/example/example_completion_init.sh
rename to completion/example/hello_completion_init.sh
index 873f19c..1bb2642 100644
--- a/completion/example/example_completion_init.sh
+++ b/completion/example/hello_completion_init.sh
@@ -5,8 +5,8 @@
 #  source hello_completion_init.sh
 #
 
-APP_NAME=example.dart
-COMPLETION_NAME=example-completion.sh
+APP_NAME=hello.dart
+COMPETION_NAME=hello-completion.sh
 
 APP_DIR=$( cd $( dirname "${BASH_SOURCE[0]}" ) && pwd )
 
@@ -18,15 +18,15 @@
 
 if [ ! -f $APP_DIR/$COMPETION_NAME ]
 then
-  echo $COMPLETION_NAME does not exist in the expected directory
+  echo $COMPETION_NAME does not exist in the expected directory
   exit 1
 fi
 
 echo Initializing your environment to run the $APP_NAME completion sample
 echo
 
-echo 'sourcing' $COMPLETION_NAME to enable command completion
-source $APP_DIR/$COMPLETION_NAME
+echo 'sourcing' $COMPETION_NAME to enable command completion
+source $APP_DIR/$COMPETION_NAME
 
 echo
 echo Adding $APP_NAME directory \($APP_DIR\) to PATH environment
diff --git a/completion/lib/completion.dart b/completion/lib/completion.dart
index 4ebc244..55c627a 100644
--- a/completion/lib/completion.dart
+++ b/completion/lib/completion.dart
@@ -13,9 +13,10 @@
         logFile}) {
   tryCompletion(
     mainArgs,
-    (List<String> args, String compLine, int compPoint) =>
-        getArgsCompletions(parser, args, compLine, compPoint),
-    // ignore: deprecated_member_use_from_same_package,deprecated_member_use
+    (List<String> args, String compLine, int compPoint) {
+      return getArgsCompletions(parser, args, compLine, compPoint);
+    },
+    // ignore: deprecated_member_use
     logFile: logFile,
   );
   return parser.parse(mainArgs);
diff --git a/completion/lib/src/bot.dart b/completion/lib/src/bot.dart
new file mode 100644
index 0000000..c77a072
--- /dev/null
+++ b/completion/lib/src/bot.dart
@@ -0,0 +1,55 @@
+class Tuple<T1, T2> {
+  final T1 item1;
+  final T2 item2;
+
+  const Tuple(this.item1, this.item2);
+
+  @override
+  bool operator ==(other) {
+    return other is Tuple && item1 == other.item1 && item2 == other.item2;
+  }
+
+  @override
+  String toString() => '{item1: $item1, item2: $item2}';
+
+  @override
+  int get hashCode => Util.getHashCode([item1, item2]);
+
+  dynamic toJson() => {'item1': item1, 'item2': item2};
+}
+
+class Util {
+  static int getHashCode(Iterable source) {
+    requireArgumentNotNull(source, 'source');
+
+    int hash = 0;
+    for (final h in source) {
+      int next = h == null ? 0 : h.hashCode;
+      hash = 0x1fffffff & (hash + next);
+      hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
+      hash ^= hash >> 6;
+    }
+    hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
+    hash ^= hash >> 11;
+    return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
+  }
+}
+
+void require(bool truth, [String message]) {
+  if (!truth) {
+    throw new Exception(message);
+  }
+}
+
+void requireArgumentNotNull(argument, String argName) {
+  _metaRequireArgumentNotNullOrEmpty(argName);
+  if (argument == null) {
+    throw new ArgumentError.notNull(argName);
+  }
+}
+
+void _metaRequireArgumentNotNullOrEmpty(String argName) {
+  if (argName == null || argName.isEmpty) {
+    throw new UnsupportedError("That's just sad. Give me a good argName");
+  }
+}
diff --git a/completion/lib/src/generate.dart b/completion/lib/src/generate.dart
index 7fb0304..e371b2b 100644
--- a/completion/lib/src/generate.dart
+++ b/completion/lib/src/generate.dart
@@ -12,7 +12,7 @@
  * Can contain letters, numbers, '_', '-', '.'
  * Must end with letter or number
  */
-final _binNameMatch = RegExp(r'^[a-zA-Z0-9]((\w|-|\.)*[a-zA-Z0-9])?$');
+final _binNameMatch = new RegExp(r'^[a-zA-Z0-9]((\w|-|\.)*[a-zA-Z0-9])?$');
 
 /*
  * Format for unified bash and zsh completion script:
@@ -26,32 +26,32 @@
 
 String generateCompletionScript(List<String> binaryNames) {
   if (binaryNames.isEmpty) {
-    throw ArgumentError('Provide the name of at least of one command');
+    throw new ArgumentError('Provide the name of at least of one command');
   }
 
   for (final binName in binaryNames) {
     if (!_binNameMatch.hasMatch(binName)) {
       final msg = 'The provided name - "$binName" - is invalid\n'
           'It must match regex: ${_binNameMatch.pattern}';
-      throw StateError(msg);
+      throw new StateError(msg);
     }
   }
 
-  final buffer = StringBuffer();
+  var buffer = new StringBuffer();
 
   final prefix =
       LineSplitter.split(_prefix).map((l) => '# $l'.trim()).join('\n');
-  buffer
-    ..writeln(prefix)
-    // empty line
-    ..writeln('');
+  buffer.writeln(prefix);
+
+  // empty line
+  buffer.writeln('');
 
   for (final binName in binaryNames) {
     buffer.writeln(_printBinName(binName));
   }
 
   final detailLines = [
-    'Generated ${DateTime.now().toUtc()}',
+    'Generated ${new DateTime.now().toUtc()}',
   ];
 
   if (Platform.script.scheme == 'file') {
@@ -68,11 +68,14 @@
 }
 
 String _printBinName(String binName) {
-  final templateContents = _template.replaceAll(_binNameReplacement, binName);
+  var templateContents = _template.replaceAll(_binNameReplacement, binName);
 
   var funcName = binName.replaceAll('.', '_');
   funcName = '__${funcName}_completion';
-  return templateContents.replaceAll(_funcNameReplacement, funcName);
+  templateContents =
+      templateContents.replaceAll(_funcNameReplacement, funcName);
+
+  return templateContents;
 }
 
 const _prefix = '''
diff --git a/completion/lib/src/get_args_completions.dart b/completion/lib/src/get_args_completions.dart
index 4f00923..d593e88 100644
--- a/completion/lib/src/get_args_completions.dart
+++ b/completion/lib/src/get_args_completions.dart
@@ -1,5 +1,6 @@
 import 'package:args/args.dart';
 
+import 'bot.dart';
 import 'util.dart';
 
 /*
@@ -7,24 +8,20 @@
  *       then tabbing into an app just completes to that one command. Weird?
  */
 
-List<String> getArgsCompletions(
-  ArgParser parser,
-  List<String> providedArgs,
-  String compLine,
-  int compPoint,
-) {
+List<String> getArgsCompletions(ArgParser parser, List<String> providedArgs,
+    String compLine, int compPoint) {
   assert(parser != null);
   assert(providedArgs != null);
   // all arg entries: no empty items, no null items, all pre-trimmed
-  for (var i = 0; i < providedArgs.length; i++) {
+  for (int i = 0; i < providedArgs.length; i++) {
     final arg = providedArgs[i];
     final msg = 'Arg at index $i with value "$arg" ';
-    if (arg.trim() != arg) throw StateError('$msg has whitespace');
+    requireArgumentNotNull(arg, '$msg is null');
+    require(arg.trim() == arg, '$msg has whitespace');
 
     if (i < (providedArgs.length - 1)) {
-      if (arg.isEmpty) {
-        throw StateError('$msg – Only the last arg can be an empty string');
-      }
+      require(
+          arg.isNotEmpty, '$msg – Only the last arg can be an empty string');
     }
   }
 
@@ -63,14 +60,14 @@
 
   // a set of options in use (minus, potentially, the last one)
   // all non-null, all unique
-  final optionsDefinedInArgs = alignedArgsOptions
+  var optionsDefinedInArgs = alignedArgsOptions
       .take(alignedArgsOptions.length - 1)
       .where((o) => o != null)
       .toSet();
   sublog('defined options: ${optionsDefinedInArgs.map((o) => o.name).toSet()}');
 
-  final parserOptionCompletions = List<String>.unmodifiable(
-      _parserOptionCompletions(parser, optionsDefinedInArgs));
+  var parserOptionCompletions = new List<String>.unmodifiable(
+      _getParserOptionCompletions(parser, optionsDefinedInArgs));
 
   /*
    * KNOWN: at least one item in providedArgs last and first are now safe
@@ -82,9 +79,9 @@
    * If it does, we can use the result to determine what we should do next
    */
 
-  final subsetTuple = _validSubset(parser, providedArgs);
-  final validSubSet = subsetTuple.subset;
-  final subsetResult = subsetTuple.result;
+  final subsetTuple = _getValidSubset(parser, providedArgs);
+  final validSubSet = subsetTuple.item1;
+  final subsetResult = subsetTuple.item2;
 
   sublog('valid subset: ${helpfulToString(validSubSet)}');
 
@@ -163,7 +160,7 @@
         assert(!option.isFlag);
         sublog('completing option "${option.name}"');
 
-        final optionValue = providedArgs[providedArgs.length - 1];
+        final String optionValue = providedArgs[providedArgs.length - 1];
 
         return option.allowed
             .where((String v) => v.startsWith(optionValue))
@@ -251,7 +248,7 @@
   return null;
 }
 
-Iterable<String> _parserOptionCompletions(
+Iterable<String> _getParserOptionCompletions(
     ArgParser parser, Set<Option> existingOptions) {
   assert(
       existingOptions.every((option) => parser.options.containsValue(option)));
@@ -259,10 +256,11 @@
   return parser.options.values
       .where((opt) =>
           !existingOptions.contains(opt) || opt.type == OptionType.multiple)
-      .expand(_argsOptionCompletions);
+      .expand(_getArgsOptionCompletions);
 }
 
-_Tuple _validSubset(ArgParser parser, List<String> providedArgs) {
+Tuple<List<String>, ArgResults> _getValidSubset(
+    ArgParser parser, List<String> providedArgs) {
   /* start with all of the args, loop through parsing them,
    * removing one every time
    *
@@ -287,17 +285,19 @@
     validSubSet.removeLast();
   }
 
-  return _Tuple(validSubSet, subsetResult);
+  return new Tuple(validSubSet, subsetResult);
 }
 
-List<String> _argsOptionCompletions(Option option) => <String>[
-      '--${option.name}',
-      if (option.negatable) '--no-${option.name}',
-    ]..sort();
+List<String> _getArgsOptionCompletions(Option option) {
+  final items = new List<String>();
 
-class _Tuple {
-  final List<String> subset;
-  final ArgResults result;
+  items.add('--${option.name}');
 
-  const _Tuple(this.subset, this.result);
+  if (option.negatable) {
+    items.add('--no-${option.name}');
+  }
+
+  items.sort();
+
+  return items;
 }
diff --git a/completion/lib/src/try_completion.dart b/completion/lib/src/try_completion.dart
index 8db4cca..0c3449c 100644
--- a/completion/lib/src/try_completion.dart
+++ b/completion/lib/src/try_completion.dart
@@ -3,6 +3,7 @@
 import 'package:logging/logging.dart';
 import 'package:path/path.dart' as p;
 
+import 'bot.dart';
 import 'util.dart';
 
 /// The string 'completion' used to denote that arguments provided to an app are
@@ -16,12 +17,11 @@
 
 void tryCompletion(
     List<String> args,
-    List<String> Function(List<String> args, String compLine, int compPoint)
-        completer,
+    List<String> completer(List<String> args, String compLine, int compPoint),
     {@Deprecated('Useful for testing, but do not release with this set.')
         logFile}) {
   if (logFile != null) {
-    final logFile = File('_completion.log');
+    var logFile = new File('_completion.log');
 
     void logLine(String content) {
       logFile.writeAsStringSync('$content\n', mode: FileMode.writeOnlyAppend);
@@ -30,14 +30,12 @@
     logLine(' *' * 50);
 
     Logger.root.onRecord.listen((e) {
-      final loggerName = e.loggerName.split('.');
+      var loggerName = e.loggerName.split('.');
       if (loggerName.isNotEmpty && loggerName.first == 'completion') {
         loggerName.removeAt(0);
         assert(e.level == Level.INFO);
         logLine(
-          '${loggerName.join('.').padLeft(Tag.longestTagLength)}  '
-          '${e.message}',
-        );
+            '${loggerName.join('.').padLeft(Tag.longestTagLength)}  ${e.message}');
       }
     });
   }
@@ -45,8 +43,7 @@
   String scriptName;
   try {
     scriptName = p.basename(Platform.script.toFilePath());
-  } on UnsupportedError // ignore: avoid_catching_errors
-  {
+  } on UnsupportedError {
     scriptName = '<unknown>';
   }
 
@@ -59,24 +56,20 @@
 
       final env = Platform.environment;
 
-      // There are 3 interesting env parameters passed by the completion logic
+      // There are 3 interesting env paramaters passed by the completion logic
       // COMP_LINE:  the full contents of the completion
       final compLine = env['COMP_LINE'];
-      if (compLine == null) {
-        throw StateError('Environment variable COMP_LINE must be set');
-      }
+      require(compLine != null, 'Environment variable COMP_LINE must be set');
 
       // COMP_CWORD: number of words. Also might be nice
       // COMP_POINT: where the cursor is on the completion line
       final compPointValue = env[_compPointVar];
-      if (compPointValue == null || compPointValue.isEmpty) {
-        throw StateError(
-            'Environment variable $_compPointVar must be set and non-empty');
-      }
+      require(compPointValue != null && compPointValue.isNotEmpty,
+          'Environment variable $_compPointVar must be set and non-empty');
       final compPoint = int.tryParse(compPointValue);
 
       if (compPoint == null) {
-        throw FormatException('Could not parse $_compPointVar value '
+        throw new FormatException('Could not parse $_compPointVar value '
             '"$compPointValue" into an integer');
       }
 
diff --git a/completion/lib/src/util.dart b/completion/lib/src/util.dart
index 98b5209..4512df1 100644
--- a/completion/lib/src/util.dart
+++ b/completion/lib/src/util.dart
@@ -1,7 +1,7 @@
 import 'package:logging/logging.dart' as logging;
 
 class Tag {
-  static const getArgsCompletions = Tag._('getArgsCompletions');
+  static const getArgsCompletions = const Tag._('getArgsCompletions');
 
   final String name;
 
@@ -27,7 +27,9 @@
 
   final loggerName = startArgs.join('.');
 
-  logging.Logger(loggerName)..info(safe);
+  final logger = new logging.Logger(loggerName);
+
+  logger.info(safe);
 }
 
 String helpfulToString(Object input) {
diff --git a/completion/pubspec.yaml b/completion/pubspec.yaml
index 3599f7b..f70ef9e 100644
--- a/completion/pubspec.yaml
+++ b/completion/pubspec.yaml
@@ -1,10 +1,11 @@
 name: completion
-version: 0.2.2
+version: 0.2.1+1
+author: Kevin Moore <github@j832.com>
 description: A packaged to add shell command completion to your Dart application
 homepage: https://github.com/kevmoo/completion.dart
 
 environment:
-  sdk: '>=2.3.0 <3.0.0'
+  sdk: '>=2.0.0-dev.54.0 <3.0.0'
 
 dependencies:
   args: ^1.4.0
@@ -12,7 +13,6 @@
   path: ^1.0.0
 
 dev_dependencies:
-  pedantic: ^1.0.0
   test: ^1.2.0
   test_process: ^1.0.1
 
diff --git a/connectivity/BUILD.gn b/connectivity/BUILD.gn
index 559c427..567cd56 100644
--- a/connectivity/BUILD.gn
+++ b/connectivity/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by importer.py for connectivity-0.4.8+1
+# This file is generated by importer.py for connectivity-0.4.6+2
 
 import("//build/dart/dart_library.gni")
 
@@ -12,9 +12,7 @@
   disable_analysis = true
 
   deps = [
-    "//third_party/dart-pkg/pub/connectivity_platform_interface",
     "//third_party/dart-pkg/pub/meta",
     "//third_party/dart-pkg/git/flutter/packages/flutter",
-    "//third_party/dart-pkg/pub/connectivity_macos",
   ]
 }
diff --git a/connectivity/CHANGELOG.md b/connectivity/CHANGELOG.md
index b297f38..57741ee 100644
--- a/connectivity/CHANGELOG.md
+++ b/connectivity/CHANGELOG.md
@@ -1,15 +1,3 @@
-## 0.4.8+1
-
-* Make the pedantic dev_dependency explicit.
-
-## 0.4.8
-
-* Adds macOS as an endorsed platform.
-
-## 0.4.7
-
-* Migrate the plugin to use the ConnectivityPlatform.instance defined in the connectivity_platform_interface package.
-
 ## 0.4.6+2
 
 * Migrate deprecated BinaryMessages to ServicesBinding.instance.defaultBinaryMessenger.
diff --git a/connectivity/connectivity_macos/CHANGELOG.md b/connectivity/connectivity_macos/CHANGELOG.md
new file mode 100644
index 0000000..c34dc60
--- /dev/null
+++ b/connectivity/connectivity_macos/CHANGELOG.md
@@ -0,0 +1,7 @@
+## 0.0.2+1
+
+* Add CHANGELOG.
+
+## 0.0.1
+
+* Initial open source release.
\ No newline at end of file
diff --git a/connectivity_macos/LICENSE b/connectivity/connectivity_macos/LICENSE
similarity index 100%
rename from connectivity_macos/LICENSE
rename to connectivity/connectivity_macos/LICENSE
diff --git a/connectivity/connectivity_macos/README.md b/connectivity/connectivity_macos/README.md
new file mode 100644
index 0000000..ee34d1d
--- /dev/null
+++ b/connectivity/connectivity_macos/README.md
@@ -0,0 +1,30 @@
+# connectivity_macos
+
+The macos implementation of [`connectivity`].
+
+## Usage
+
+### Import the package
+
+To use this plugin in your Flutter Web app, simply add it as a dependency in
+your `pubspec.yaml` alongside the base `connectivity` plugin.
+
+_(This is only temporary: in the future we hope to make this package an
+"endorsed" implementation of `connectivity`, so that it is automatically
+included in your Flutter macos app when you depend on `package:connectivity_macos`.)_
+
+This is what the above means to your `pubspec.yaml`:
+
+```yaml
+...
+dependencies:
+  ...
+  connectivity: ^0.4.6
+  connectivity_macos: ^0.0.1
+  ...
+```
+
+### Use the plugin
+
+Once you have the `connectivity_macos` dependency in your pubspec, you should
+be able to use `package:connectivity` as normal.
diff --git a/connectivity_macos/android/README.md b/connectivity/connectivity_macos/android/README.md
similarity index 100%
rename from connectivity_macos/android/README.md
rename to connectivity/connectivity_macos/android/README.md
diff --git a/connectivity_macos/android/build.gradle b/connectivity/connectivity_macos/android/build.gradle
similarity index 100%
rename from connectivity_macos/android/build.gradle
rename to connectivity/connectivity_macos/android/build.gradle
diff --git a/connectivity_macos/android/gradle.properties b/connectivity/connectivity_macos/android/gradle.properties
similarity index 100%
rename from connectivity_macos/android/gradle.properties
rename to connectivity/connectivity_macos/android/gradle.properties
diff --git a/connectivity_macos/android/gradle/wrapper/gradle-wrapper.properties b/connectivity/connectivity_macos/android/gradle/wrapper/gradle-wrapper.properties
similarity index 100%
rename from connectivity_macos/android/gradle/wrapper/gradle-wrapper.properties
rename to connectivity/connectivity_macos/android/gradle/wrapper/gradle-wrapper.properties
diff --git a/connectivity_macos/android/settings.gradle b/connectivity/connectivity_macos/android/settings.gradle
similarity index 100%
rename from connectivity_macos/android/settings.gradle
rename to connectivity/connectivity_macos/android/settings.gradle
diff --git a/connectivity_macos/android/src/main/AndroidManifest.xml b/connectivity/connectivity_macos/android/src/main/AndroidManifest.xml
similarity index 100%
rename from connectivity_macos/android/src/main/AndroidManifest.xml
rename to connectivity/connectivity_macos/android/src/main/AndroidManifest.xml
diff --git a/connectivity_macos/android/src/main/java/com/example/connectivity/ConnectivityPlugin.java b/connectivity/connectivity_macos/android/src/main/java/com/example/connectivity/ConnectivityPlugin.java
similarity index 100%
rename from connectivity_macos/android/src/main/java/com/example/connectivity/ConnectivityPlugin.java
rename to connectivity/connectivity_macos/android/src/main/java/com/example/connectivity/ConnectivityPlugin.java
diff --git a/connectivity_macos/ios/connectivity_macos.podspec b/connectivity/connectivity_macos/ios/connectivity_macos.podspec
similarity index 100%
rename from connectivity_macos/ios/connectivity_macos.podspec
rename to connectivity/connectivity_macos/ios/connectivity_macos.podspec
diff --git a/connectivity_macos/macos/Classes/ConnectivityPlugin.swift b/connectivity/connectivity_macos/macos/Classes/ConnectivityPlugin.swift
similarity index 100%
rename from connectivity_macos/macos/Classes/ConnectivityPlugin.swift
rename to connectivity/connectivity_macos/macos/Classes/ConnectivityPlugin.swift
diff --git a/connectivity_macos/macos/Classes/IPHelper.h b/connectivity/connectivity_macos/macos/Classes/IPHelper.h
similarity index 100%
rename from connectivity_macos/macos/Classes/IPHelper.h
rename to connectivity/connectivity_macos/macos/Classes/IPHelper.h
diff --git a/connectivity_macos/macos/connectivity_macos.podspec b/connectivity/connectivity_macos/macos/connectivity_macos.podspec
similarity index 100%
rename from connectivity_macos/macos/connectivity_macos.podspec
rename to connectivity/connectivity_macos/macos/connectivity_macos.podspec
diff --git a/connectivity_macos/pubspec.yaml b/connectivity/connectivity_macos/pubspec.yaml
similarity index 87%
rename from connectivity_macos/pubspec.yaml
rename to connectivity/connectivity_macos/pubspec.yaml
index 70ba247..f82189f 100644
--- a/connectivity_macos/pubspec.yaml
+++ b/connectivity/connectivity_macos/pubspec.yaml
@@ -1,6 +1,6 @@
 name: connectivity_macos
 description: macOS implementation of the connectivity plugin.
-version: 0.1.0+1
+version: 0.0.2+1
 homepage: https://github.com/flutter/plugins/tree/master/packages/connectivity/connectivity_macos
 
 flutter:
@@ -16,6 +16,3 @@
 dependencies:
   flutter:
     sdk: flutter
-
-dev_dependencies:
-  pedantic: ^1.8.0
diff --git a/connectivity/example/lib/main.dart b/connectivity/example/lib/main.dart
index 4ad3097..0d9c282 100644
--- a/connectivity/example/lib/main.dart
+++ b/connectivity/example/lib/main.dart
@@ -15,7 +15,7 @@
 // Sets a platform override for desktop to avoid exceptions. See
 // https://flutter.dev/desktop#target-platform-override for more info.
 void _enablePlatformOverrideForDesktop() {
-  if (!kIsWeb && (Platform.isWindows || Platform.isLinux)) {
+  if (!kIsWeb && (Platform.isMacOS || Platform.isWindows || Platform.isLinux)) {
     debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia;
   }
 }
diff --git a/connectivity/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/connectivity/example/macos/Runner.xcworkspace/contents.xcworkspacedata
deleted file mode 100644
index 21a3cc1..0000000
--- a/connectivity/example/macos/Runner.xcworkspace/contents.xcworkspacedata
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<Workspace
-   version = "1.0">
-   <FileRef
-      location = "group:Runner.xcodeproj">
-   </FileRef>
-   <FileRef
-      location = "group:Pods/Pods.xcodeproj">
-   </FileRef>
-</Workspace>
diff --git a/connectivity/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/connectivity/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
deleted file mode 100644
index 18d9810..0000000
--- a/connectivity/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-	<key>IDEDidComputeMac32BitWarning</key>
-	<true/>
-</dict>
-</plist>
diff --git a/connectivity/example/pubspec.yaml b/connectivity/example/pubspec.yaml
index a16e604..c4c58a1 100644
--- a/connectivity/example/pubspec.yaml
+++ b/connectivity/example/pubspec.yaml
@@ -6,13 +6,14 @@
     sdk: flutter
   connectivity:
     path: ../
+  connectivity_macos:
+    path: ../connectivity_macos
 
 dev_dependencies:
   flutter_driver:
     sdk: flutter
   test: any
   e2e: ^0.2.0
-  pedantic: ^1.8.0
 
 flutter:
   uses-material-design: true
diff --git a/connectivity/example/test_driver/test/connectivity_e2e.dart b/connectivity/example/test_driver/test/connectivity_e2e.dart
deleted file mode 100644
index 10c4bda..0000000
--- a/connectivity/example/test_driver/test/connectivity_e2e.dart
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:io';
-import 'package:e2e/e2e.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:connectivity/connectivity.dart';
-
-void main() {
-  E2EWidgetsFlutterBinding.ensureInitialized();
-
-  group('Connectivity test driver', () {
-    Connectivity _connectivity;
-
-    setUpAll(() async {
-      _connectivity = Connectivity();
-    });
-
-    testWidgets('test connectivity result', (WidgetTester tester) async {
-      final ConnectivityResult result = await _connectivity.checkConnectivity();
-      expect(result, isNotNull);
-      switch (result) {
-        case ConnectivityResult.wifi:
-          expect(_connectivity.getWifiName(), completes);
-          expect(_connectivity.getWifiBSSID(), completes);
-          expect((await _connectivity.getWifiIP()), isNotNull);
-          break;
-        default:
-          break;
-      }
-    });
-
-    testWidgets('test location methods, iOS only', (WidgetTester tester) async {
-      if (Platform.isIOS) {
-        expect((await _connectivity.getLocationServiceAuthorization()),
-            LocationAuthorizationStatus.notDetermined);
-      }
-    });
-  });
-}
diff --git a/connectivity/lib/connectivity.dart b/connectivity/lib/connectivity.dart
index a5d9f25..ad9fae3 100644
--- a/connectivity/lib/connectivity.dart
+++ b/connectivity/lib/connectivity.dart
@@ -3,13 +3,22 @@
 // found in the LICENSE file.
 
 import 'dart:async';
+import 'dart:io';
 
 import 'package:flutter/services.dart';
-import 'package:connectivity_platform_interface/connectivity_platform_interface.dart';
+import 'package:meta/meta.dart';
 
-// Export enums from the platform_interface so plugin users can use them directly.
-export 'package:connectivity_platform_interface/connectivity_platform_interface.dart'
-    show ConnectivityResult, LocationAuthorizationStatus;
+/// Connection status check result.
+enum ConnectivityResult {
+  /// WiFi: Device connected via Wi-Fi
+  wifi,
+
+  /// Mobile: Device connected to cellular network
+  mobile,
+
+  /// None: Device not connected to any network
+  none
+}
 
 /// Discover network connectivity configurations: Distinguish between WI-FI and cellular, check WI-FI status and more.
 class Connectivity {
@@ -18,7 +27,7 @@
   /// [Connectivity] is designed to work as a singleton.
   // When a second instance is created, the first instance will not be able to listen to the
   // EventChannel because it is overridden. Forcing the class to be a singleton class can prevent
-  // misuse of creating a second instance from a programmer.
+  // misusage of creating a second instance from a programmer.
   factory Connectivity() {
     if (_singleton == null) {
       _singleton = Connectivity._();
@@ -30,11 +39,28 @@
 
   static Connectivity _singleton;
 
-  static ConnectivityPlatform get _platform => ConnectivityPlatform.instance;
+  Stream<ConnectivityResult> _onConnectivityChanged;
+
+  /// Exposed for testing purposes and should not be used by users of the plugin.
+  @visibleForTesting
+  static const MethodChannel methodChannel = MethodChannel(
+    'plugins.flutter.io/connectivity',
+  );
+
+  /// Exposed for testing purposes and should not be used by users of the plugin.
+  @visibleForTesting
+  static const EventChannel eventChannel = EventChannel(
+    'plugins.flutter.io/connectivity_status',
+  );
 
   /// Fires whenever the connectivity state changes.
   Stream<ConnectivityResult> get onConnectivityChanged {
-    return _platform.onConnectivityChanged;
+    if (_onConnectivityChanged == null) {
+      _onConnectivityChanged = eventChannel
+          .receiveBroadcastStream()
+          .map((dynamic event) => _parseConnectivityResult(event));
+    }
+    return _onConnectivityChanged;
   }
 
   /// Checks the connection status of the device.
@@ -43,8 +69,9 @@
   /// make a network request. It only gives you the radio status.
   ///
   /// Instead listen for connectivity changes via [onConnectivityChanged] stream.
-  Future<ConnectivityResult> checkConnectivity() {
-    return _platform.checkConnectivity();
+  Future<ConnectivityResult> checkConnectivity() async {
+    final String result = await methodChannel.invokeMethod<String>('check');
+    return _parseConnectivityResult(result);
   }
 
   /// Obtains the wifi name (SSID) of the connected network
@@ -53,8 +80,12 @@
   ///
   /// From android 8.0 onwards the GPS must be ON (high accuracy)
   /// in order to be able to obtain the SSID.
-  Future<String> getWifiName() {
-    return _platform.getWifiName();
+  Future<String> getWifiName() async {
+    String wifiName = await methodChannel.invokeMethod<String>('wifiName');
+    // as Android might return <unknown ssid>, uniforming result
+    // our iOS implementation will return null
+    if (wifiName == '<unknown ssid>') wifiName = null;
+    return wifiName;
   }
 
   /// Obtains the wifi BSSID of the connected network.
@@ -63,13 +94,13 @@
   ///
   /// From Android 8.0 onwards the GPS must be ON (high accuracy)
   /// in order to be able to obtain the BSSID.
-  Future<String> getWifiBSSID() {
-    return _platform.getWifiBSSID();
+  Future<String> getWifiBSSID() async {
+    return await methodChannel.invokeMethod<String>('wifiBSSID');
   }
 
   /// Obtains the IP address of the connected wifi network
-  Future<String> getWifiIP() {
-    return _platform.getWifiIP();
+  Future<String> getWifiIP() async {
+    return await methodChannel.invokeMethod<String>('wifiIPAddress');
   }
 
   /// Request to authorize the location service (Only on iOS).
@@ -120,12 +151,14 @@
   /// Ideally, a location service authorization should only be requested if the current authorization status is not determined.
   ///
   /// See also [getLocationServiceAuthorization] to obtain current location service status.
-  Future<LocationAuthorizationStatus> requestLocationServiceAuthorization({
-    bool requestAlwaysLocationUsage = false,
-  }) {
-    return _platform.requestLocationServiceAuthorization(
-      requestAlwaysLocationUsage: requestAlwaysLocationUsage,
-    );
+  Future<LocationAuthorizationStatus> requestLocationServiceAuthorization(
+      {bool requestAlwaysLocationUsage = false}) async {
+    //Just `assert(Platform.isIOS)` will prevent us from doing dart side unit testing.
+    assert(!Platform.isAndroid);
+    final String result = await methodChannel.invokeMethod<String>(
+        'requestLocationServiceAuthorization',
+        <bool>[requestAlwaysLocationUsage]);
+    return _convertLocationStatusString(result);
   }
 
   /// Get the current location service authorization (Only on iOS).
@@ -164,7 +197,61 @@
   /// ```
   ///
   /// See also [requestLocationServiceAuthorization] for requesting a location service authorization.
-  Future<LocationAuthorizationStatus> getLocationServiceAuthorization() {
-    return _platform.getLocationServiceAuthorization();
+  Future<LocationAuthorizationStatus> getLocationServiceAuthorization() async {
+    //Just `assert(Platform.isIOS)` will prevent us from doing dart side unit testing.
+    assert(!Platform.isAndroid);
+    final String result = await methodChannel
+        .invokeMethod<String>('getLocationServiceAuthorization');
+    return _convertLocationStatusString(result);
   }
+
+  LocationAuthorizationStatus _convertLocationStatusString(String result) {
+    switch (result) {
+      case 'notDetermined':
+        return LocationAuthorizationStatus.notDetermined;
+      case 'restricted':
+        return LocationAuthorizationStatus.restricted;
+      case 'denied':
+        return LocationAuthorizationStatus.denied;
+      case 'authorizedAlways':
+        return LocationAuthorizationStatus.authorizedAlways;
+      case 'authorizedWhenInUse':
+        return LocationAuthorizationStatus.authorizedWhenInUse;
+      default:
+        return LocationAuthorizationStatus.unknown;
+    }
+  }
+}
+
+ConnectivityResult _parseConnectivityResult(String state) {
+  switch (state) {
+    case 'wifi':
+      return ConnectivityResult.wifi;
+    case 'mobile':
+      return ConnectivityResult.mobile;
+    case 'none':
+    default:
+      return ConnectivityResult.none;
+  }
+}
+
+/// The status of the location service authorization.
+enum LocationAuthorizationStatus {
+  /// The authorization of the location service is not determined.
+  notDetermined,
+
+  /// This app is not authorized to use location.
+  restricted,
+
+  /// User explicitly denied the location service.
+  denied,
+
+  /// User authorized the app to access the location at any time.
+  authorizedAlways,
+
+  /// User authorized the app to access the location when the app is visible to them.
+  authorizedWhenInUse,
+
+  /// Status unknown.
+  unknown
 }
diff --git a/connectivity/macos/connectivity.podspec b/connectivity/macos/connectivity.podspec
deleted file mode 100644
index ea544df..0000000
--- a/connectivity/macos/connectivity.podspec
+++ /dev/null
@@ -1,22 +0,0 @@
-#
-# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
-#
-Pod::Spec.new do |s|
-  s.name             = 'connectivity'
-  s.version          = '0.0.1'
-  s.summary          = 'No-op implementation of the macos connectivity to avoid build issues on macos'
-  s.description      = <<-DESC
-  No-op implementation of the connectivity plugin to avoid build issues on macos.
-  https://github.com/flutter/flutter/issues/46618
-                       DESC
-  s.homepage         = 'https://github.com/flutter/plugins/tree/master/packages/connectivity'
-  s.license          = { :file => '../LICENSE' }
-  s.author           = { 'Flutter Team' => 'flutter-dev@googlegroups.com' }
-  s.source           = { :path => '.' }
-  s.source_files = 'Classes/**/*'
-  s.public_header_files = 'Classes/**/*.h'
-
-  s.platform = :osx
-  s.osx.deployment_target = '10.11'
-end
-
diff --git a/connectivity/pubspec.yaml b/connectivity/pubspec.yaml
index ce90f75..91ba068 100644
--- a/connectivity/pubspec.yaml
+++ b/connectivity/pubspec.yaml
@@ -1,8 +1,8 @@
 name: connectivity
 description: Flutter plugin for discovering the state of the network (WiFi &
   mobile/cellular) connectivity on Android and iOS.
-homepage: https://github.com/flutter/plugins/tree/master/packages/connectivity/connectivity
-version: 0.4.8+1
+homepage: https://github.com/flutter/plugins/tree/master/packages/connectivity
+version: 0.4.6+2
 
 flutter:
   plugin:
@@ -12,15 +12,11 @@
         pluginClass: ConnectivityPlugin
       ios:
         pluginClass: FLTConnectivityPlugin
-      macos:
-        default_package: connectivity_macos
 
 dependencies:
   flutter:
     sdk: flutter
   meta: "^1.0.5"
-  connectivity_platform_interface: ^1.0.2
-  connectivity_macos: ^0.1.0
 
 dev_dependencies:
   flutter_test:
@@ -29,9 +25,6 @@
     sdk: flutter
   test: any
   e2e: ^0.2.0
-  mockito: ^4.1.1
-  plugin_platform_interface: ^1.0.0
-  pedantic: ^1.8.0
 
 environment:
   sdk: ">=2.0.0-dev.28.0 <3.0.0"
diff --git a/connectivity_macos/BUILD.gn b/connectivity_macos/BUILD.gn
deleted file mode 100644
index 07cb715..0000000
--- a/connectivity_macos/BUILD.gn
+++ /dev/null
@@ -1,17 +0,0 @@
-# This file is generated by importer.py for connectivity_macos-0.1.0+1
-
-import("//build/dart/dart_library.gni")
-
-dart_library("connectivity_macos") {
-  package_name = "connectivity_macos"
-
-  # This parameter is left empty as we don't care about analysis or exporting
-  # these sources outside of the tree.
-  sources = []
-
-  disable_analysis = true
-
-  deps = [
-    "//third_party/dart-pkg/git/flutter/packages/flutter",
-  ]
-}
diff --git a/connectivity_macos/CHANGELOG.md b/connectivity_macos/CHANGELOG.md
deleted file mode 100644
index 43db2ff..0000000
--- a/connectivity_macos/CHANGELOG.md
+++ /dev/null
@@ -1,16 +0,0 @@
-## 0.1.0+1
-
-* Make the pedantic dev_dependency explicit.
-
-## 0.1.0
-
-* Adds an example app to trigger CI tests. Bumped the MINOR version to
-avoid compatibility issues once this packages is endorsed.
-
-## 0.0.2+1
-
-* Add CHANGELOG.
-
-## 0.0.1
-
-* Initial open source release.
\ No newline at end of file
diff --git a/connectivity_macos/README.md b/connectivity_macos/README.md
deleted file mode 100644
index d63803a..0000000
--- a/connectivity_macos/README.md
+++ /dev/null
@@ -1,25 +0,0 @@
-# connectivity_macos
-
-The macos implementation of [`connectivity`].
-
-## Usage
-
-### Import the package
-
-
-This package has been endorsed, meaning that you only need to add `connectivity`
-as a dependency in your `pubspec.yaml`. It will be automatically included in your app
-when you depend on `package:connectivity`.
-
-This is what the above means to your `pubspec.yaml`:
-
-```yaml
-...
-dependencies:
-  ...
-  connectivity: ^0.4.8
-  ...
-```
-
-Refer to the `connectivity` [documentation](https://github.com/flutter/plugins/tree/master/packages/connectivity/connectivity) for more details.
-
diff --git a/connectivity_macos/example/android/app/build.gradle b/connectivity_macos/example/android/app/build.gradle
deleted file mode 100644
index 5d1f138..0000000
--- a/connectivity_macos/example/android/app/build.gradle
+++ /dev/null
@@ -1,58 +0,0 @@
-def localProperties = new Properties()
-def localPropertiesFile = rootProject.file('local.properties')
-if (localPropertiesFile.exists()) {
-    localPropertiesFile.withReader('UTF-8') { reader ->
-        localProperties.load(reader)
-    }
-}
-
-def flutterRoot = localProperties.getProperty('flutter.sdk')
-if (flutterRoot == null) {
-    throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
-}
-
-def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
-if (flutterVersionCode == null) {
-    flutterVersionCode = '1'
-}
-
-def flutterVersionName = localProperties.getProperty('flutter.versionName')
-if (flutterVersionName == null) {
-    flutterVersionName = '1.0'
-}
-
-apply plugin: 'com.android.application'
-apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
-
-android {
-    compileSdkVersion 28
-
-    lintOptions {
-        disable 'InvalidPackage'
-    }
-
-    defaultConfig {
-        applicationId "io.flutter.plugins.connectivityexample"
-        minSdkVersion 16
-        targetSdkVersion 28
-        versionCode flutterVersionCode.toInteger()
-        versionName flutterVersionName
-        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
-    }
-
-    buildTypes {
-        release {
-            signingConfig signingConfigs.debug
-        }
-    }
-}
-
-flutter {
-    source '../..'
-}
-
-dependencies {
-    testImplementation 'junit:junit:4.12'
-    androidTestImplementation 'androidx.test:runner:1.1.1'
-    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
-}
diff --git a/connectivity_macos/example/android/app/gradle/wrapper/gradle-wrapper.properties b/connectivity_macos/example/android/app/gradle/wrapper/gradle-wrapper.properties
deleted file mode 100644
index 9a4163a..0000000
--- a/connectivity_macos/example/android/app/gradle/wrapper/gradle-wrapper.properties
+++ /dev/null
@@ -1,5 +0,0 @@
-distributionBase=GRADLE_USER_HOME
-distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
-zipStoreBase=GRADLE_USER_HOME
-zipStorePath=wrapper/dists
diff --git a/connectivity_macos/example/android/app/src/main/AndroidManifest.xml b/connectivity_macos/example/android/app/src/main/AndroidManifest.xml
deleted file mode 100644
index 3bf2ca0..0000000
--- a/connectivity_macos/example/android/app/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="io.flutter.plugins.connectivityexample">
-
-    <uses-permission android:name="android.permission.INTERNET"/>
-
-    <application android:name="io.flutter.app.FlutterApplication" android:label="connectivity_example" android:icon="@mipmap/ic_launcher">
-        <activity android:name=".EmbeddingV1Activity"
-            android:launchMode="singleTop"
-            android:theme="@android:style/Theme.Black.NoTitleBar"
-            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
-            android:hardwareAccelerated="true"
-            android:exported="true"
-            android:windowSoftInputMode="adjustResize">
-        </activity>
-        <activity android:name=".MainActivity"
-            android:theme="@android:style/Theme.Black.NoTitleBar"
-            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
-            android:hardwareAccelerated="true"
-            android:windowSoftInputMode="adjustResize">
-
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-        </activity>
-    </application>
-</manifest>
diff --git a/connectivity_macos/example/android/app/src/main/java/io/flutter/plugins/connectivityexample/EmbeddingV1Activity.java b/connectivity_macos/example/android/app/src/main/java/io/flutter/plugins/connectivityexample/EmbeddingV1Activity.java
deleted file mode 100644
index 587b623..0000000
--- a/connectivity_macos/example/android/app/src/main/java/io/flutter/plugins/connectivityexample/EmbeddingV1Activity.java
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package io.flutter.plugins.connectivityexample;
-
-import android.os.Bundle;
-import io.flutter.app.FlutterActivity;
-import io.flutter.plugins.GeneratedPluginRegistrant;
-
-public class EmbeddingV1Activity extends FlutterActivity {
-  @Override
-  protected void onCreate(Bundle savedInstanceState) {
-    super.onCreate(savedInstanceState);
-    GeneratedPluginRegistrant.registerWith(this);
-  }
-}
diff --git a/connectivity_macos/example/android/app/src/main/java/io/flutter/plugins/connectivityexample/EmbeddingV1ActivityTest.java b/connectivity_macos/example/android/app/src/main/java/io/flutter/plugins/connectivityexample/EmbeddingV1ActivityTest.java
deleted file mode 100644
index a347553..0000000
--- a/connectivity_macos/example/android/app/src/main/java/io/flutter/plugins/connectivityexample/EmbeddingV1ActivityTest.java
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package io.flutter.plugins.connectivityexample;
-
-import androidx.test.rule.ActivityTestRule;
-import dev.flutter.plugins.e2e.FlutterRunner;
-import org.junit.Rule;
-import org.junit.runner.RunWith;
-
-@RunWith(FlutterRunner.class)
-public class EmbeddingV1ActivityTest {
-  @Rule
-  public ActivityTestRule<EmbeddingV1Activity> rule =
-      new ActivityTestRule<>(EmbeddingV1Activity.class);
-}
diff --git a/connectivity_macos/example/android/app/src/main/java/io/flutter/plugins/connectivityexample/MainActivity.java b/connectivity_macos/example/android/app/src/main/java/io/flutter/plugins/connectivityexample/MainActivity.java
deleted file mode 100644
index b0deb61..0000000
--- a/connectivity_macos/example/android/app/src/main/java/io/flutter/plugins/connectivityexample/MainActivity.java
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package io.flutter.plugins.connectivityexample;
-
-import io.flutter.embedding.android.FlutterActivity;
-import io.flutter.embedding.engine.FlutterEngine;
-import io.flutter.plugins.connectivity.ConnectivityPlugin;
-
-public class MainActivity extends FlutterActivity {
-
-  @Override
-  public void configureFlutterEngine(FlutterEngine flutterEngine) {
-    super.configureFlutterEngine(flutterEngine);
-    flutterEngine.getPlugins().add(new ConnectivityPlugin());
-  }
-}
diff --git a/connectivity_macos/example/android/app/src/main/java/io/flutter/plugins/connectivityexample/MainActivityTest.java b/connectivity_macos/example/android/app/src/main/java/io/flutter/plugins/connectivityexample/MainActivityTest.java
deleted file mode 100644
index 0c33d6a..0000000
--- a/connectivity_macos/example/android/app/src/main/java/io/flutter/plugins/connectivityexample/MainActivityTest.java
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package io.flutter.plugins.connectivityexample;
-
-import androidx.test.rule.ActivityTestRule;
-import dev.flutter.plugins.e2e.FlutterRunner;
-import org.junit.Rule;
-import org.junit.runner.RunWith;
-
-@RunWith(FlutterRunner.class)
-public class MainActivityTest {
-  @Rule public ActivityTestRule<MainActivity> rule = new ActivityTestRule<>(MainActivity.class);
-}
diff --git a/connectivity_macos/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/connectivity_macos/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
deleted file mode 100644
index db77bb4..0000000
--- a/connectivity_macos/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/connectivity_macos/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/connectivity_macos/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
deleted file mode 100644
index 17987b7..0000000
--- a/connectivity_macos/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/connectivity_macos/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/connectivity_macos/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
deleted file mode 100644
index 09d4391..0000000
--- a/connectivity_macos/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/connectivity_macos/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/connectivity_macos/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
deleted file mode 100644
index d5f1c8d..0000000
--- a/connectivity_macos/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/connectivity_macos/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/connectivity_macos/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
deleted file mode 100644
index 4d6372e..0000000
--- a/connectivity_macos/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/connectivity_macos/example/android/build.gradle b/connectivity_macos/example/android/build.gradle
deleted file mode 100644
index 541636c..0000000
--- a/connectivity_macos/example/android/build.gradle
+++ /dev/null
@@ -1,29 +0,0 @@
-buildscript {
-    repositories {
-        google()
-        jcenter()
-    }
-
-    dependencies {
-        classpath 'com.android.tools.build:gradle:3.3.0'
-    }
-}
-
-allprojects {
-    repositories {
-        google()
-        jcenter()
-    }
-}
-
-rootProject.buildDir = '../build'
-subprojects {
-    project.buildDir = "${rootProject.buildDir}/${project.name}"
-}
-subprojects {
-    project.evaluationDependsOn(':app')
-}
-
-task clean(type: Delete) {
-    delete rootProject.buildDir
-}
diff --git a/connectivity_macos/example/android/gradle.properties b/connectivity_macos/example/android/gradle.properties
deleted file mode 100644
index a673820..0000000
--- a/connectivity_macos/example/android/gradle.properties
+++ /dev/null
@@ -1,4 +0,0 @@
-org.gradle.jvmargs=-Xmx1536M
-android.useAndroidX=true
-android.enableJetifier=true
-android.enableR8=true
diff --git a/connectivity_macos/example/android/gradle/wrapper/gradle-wrapper.properties b/connectivity_macos/example/android/gradle/wrapper/gradle-wrapper.properties
deleted file mode 100644
index 019065d..0000000
--- a/connectivity_macos/example/android/gradle/wrapper/gradle-wrapper.properties
+++ /dev/null
@@ -1,5 +0,0 @@
-distributionBase=GRADLE_USER_HOME
-distributionPath=wrapper/dists
-zipStoreBase=GRADLE_USER_HOME
-zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
diff --git a/connectivity_macos/example/android/settings.gradle b/connectivity_macos/example/android/settings.gradle
deleted file mode 100644
index a159ea7..0000000
--- a/connectivity_macos/example/android/settings.gradle
+++ /dev/null
@@ -1,15 +0,0 @@
-include ':app'
-
-def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
-
-def plugins = new Properties()
-def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
-if (pluginsFile.exists()) {
-    pluginsFile.withInputStream { stream -> plugins.load(stream) }
-}
-
-plugins.each { name, path ->
-    def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
-        include ":$name"
-        project(":$name").projectDir = pluginDirectory
-}
diff --git a/connectivity_macos/example/ios/Flutter/AppFrameworkInfo.plist b/connectivity_macos/example/ios/Flutter/AppFrameworkInfo.plist
deleted file mode 100644
index 6c2de80..0000000
--- a/connectivity_macos/example/ios/Flutter/AppFrameworkInfo.plist
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-  <key>CFBundleDevelopmentRegion</key>
-  <string>en</string>
-  <key>CFBundleExecutable</key>
-  <string>App</string>
-  <key>CFBundleIdentifier</key>
-  <string>io.flutter.flutter.app</string>
-  <key>CFBundleInfoDictionaryVersion</key>
-  <string>6.0</string>
-  <key>CFBundleName</key>
-  <string>App</string>
-  <key>CFBundlePackageType</key>
-  <string>FMWK</string>
-  <key>CFBundleShortVersionString</key>
-  <string>1.0</string>
-  <key>CFBundleSignature</key>
-  <string>????</string>
-  <key>CFBundleVersion</key>
-  <string>1.0</string>
-  <key>UIRequiredDeviceCapabilities</key>
-  <array>
-    <string>arm64</string>
-  </array>
-  <key>MinimumOSVersion</key>
-  <string>8.0</string>
-</dict>
-</plist>
diff --git a/connectivity_macos/example/ios/Flutter/Debug.xcconfig b/connectivity_macos/example/ios/Flutter/Debug.xcconfig
deleted file mode 100644
index e8efba1..0000000
--- a/connectivity_macos/example/ios/Flutter/Debug.xcconfig
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
-#include "Generated.xcconfig"
diff --git a/connectivity_macos/example/ios/Flutter/Release.xcconfig b/connectivity_macos/example/ios/Flutter/Release.xcconfig
deleted file mode 100644
index 399e934..0000000
--- a/connectivity_macos/example/ios/Flutter/Release.xcconfig
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
-#include "Generated.xcconfig"
diff --git a/connectivity_macos/example/ios/Runner.xcodeproj/project.pbxproj b/connectivity_macos/example/ios/Runner.xcodeproj/project.pbxproj
deleted file mode 100644
index e497d09..0000000
--- a/connectivity_macos/example/ios/Runner.xcodeproj/project.pbxproj
+++ /dev/null
@@ -1,490 +0,0 @@
-// !$*UTF8*$!
-{
-	archiveVersion = 1;
-	classes = {
-	};
-	objectVersion = 46;
-	objects = {
-
-/* Begin PBXBuildFile section */
-		1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
-		3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
-		3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
-		3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
-		9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
-		9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
-		978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
-		97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
-		97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
-		97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
-		97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
-		EB0BA966000B5C35B13186D7 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C80D49AFD183103034E444C2 /* libPods-Runner.a */; };
-/* End PBXBuildFile section */
-
-/* Begin PBXCopyFilesBuildPhase section */
-		9705A1C41CF9048500538489 /* Embed Frameworks */ = {
-			isa = PBXCopyFilesBuildPhase;
-			buildActionMask = 2147483647;
-			dstPath = "";
-			dstSubfolderSpec = 10;
-			files = (
-				3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
-				9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
-			);
-			name = "Embed Frameworks";
-			runOnlyForDeploymentPostprocessing = 0;
-		};
-/* End PBXCopyFilesBuildPhase section */
-
-/* Begin PBXFileReference section */
-		1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
-		1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
-		3173C764DD180BE02EB51E47 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
-		3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
-		3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; };
-		69D903F0A9A7C636EE803AF8 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
-		7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
-		7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
-		7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
-		9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
-		9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
-		9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = "<group>"; };
-		97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
-		97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
-		97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
-		97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
-		97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
-		97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
-		C80D49AFD183103034E444C2 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
-/* End PBXFileReference section */
-
-/* Begin PBXFrameworksBuildPhase section */
-		97C146EB1CF9000F007C117D /* Frameworks */ = {
-			isa = PBXFrameworksBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-				9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
-				3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
-				EB0BA966000B5C35B13186D7 /* libPods-Runner.a in Frameworks */,
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-		};
-/* End PBXFrameworksBuildPhase section */
-
-/* Begin PBXGroup section */
-		89F516DEFCBF79E39D2885C2 /* Frameworks */ = {
-			isa = PBXGroup;
-			children = (
-				C80D49AFD183103034E444C2 /* libPods-Runner.a */,
-			);
-			name = Frameworks;
-			sourceTree = "<group>";
-		};
-		8ECC1C323F60D5498EEC2315 /* Pods */ = {
-			isa = PBXGroup;
-			children = (
-				69D903F0A9A7C636EE803AF8 /* Pods-Runner.debug.xcconfig */,
-				3173C764DD180BE02EB51E47 /* Pods-Runner.release.xcconfig */,
-			);
-			name = Pods;
-			sourceTree = "<group>";
-		};
-		9740EEB11CF90186004384FC /* Flutter */ = {
-			isa = PBXGroup;
-			children = (
-				3B80C3931E831B6300D905FE /* App.framework */,
-				3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
-				9740EEBA1CF902C7004384FC /* Flutter.framework */,
-				9740EEB21CF90195004384FC /* Debug.xcconfig */,
-				7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
-				9740EEB31CF90195004384FC /* Generated.xcconfig */,
-			);
-			name = Flutter;
-			sourceTree = "<group>";
-		};
-		97C146E51CF9000F007C117D = {
-			isa = PBXGroup;
-			children = (
-				9740EEB11CF90186004384FC /* Flutter */,
-				97C146F01CF9000F007C117D /* Runner */,
-				97C146EF1CF9000F007C117D /* Products */,
-				8ECC1C323F60D5498EEC2315 /* Pods */,
-				89F516DEFCBF79E39D2885C2 /* Frameworks */,
-			);
-			sourceTree = "<group>";
-		};
-		97C146EF1CF9000F007C117D /* Products */ = {
-			isa = PBXGroup;
-			children = (
-				97C146EE1CF9000F007C117D /* Runner.app */,
-			);
-			name = Products;
-			sourceTree = "<group>";
-		};
-		97C146F01CF9000F007C117D /* Runner */ = {
-			isa = PBXGroup;
-			children = (
-				7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */,
-				7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */,
-				97C146FA1CF9000F007C117D /* Main.storyboard */,
-				97C146FD1CF9000F007C117D /* Assets.xcassets */,
-				97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
-				97C147021CF9000F007C117D /* Info.plist */,
-				97C146F11CF9000F007C117D /* Supporting Files */,
-				1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
-				1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
-			);
-			path = Runner;
-			sourceTree = "<group>";
-		};
-		97C146F11CF9000F007C117D /* Supporting Files */ = {
-			isa = PBXGroup;
-			children = (
-				97C146F21CF9000F007C117D /* main.m */,
-			);
-			name = "Supporting Files";
-			sourceTree = "<group>";
-		};
-/* End PBXGroup section */
-
-/* Begin PBXNativeTarget section */
-		97C146ED1CF9000F007C117D /* Runner */ = {
-			isa = PBXNativeTarget;
-			buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
-			buildPhases = (
-				3BAF367E8BACBC7576CEE653 /* [CP] Check Pods Manifest.lock */,
-				9740EEB61CF901F6004384FC /* Run Script */,
-				97C146EA1CF9000F007C117D /* Sources */,
-				97C146EB1CF9000F007C117D /* Frameworks */,
-				97C146EC1CF9000F007C117D /* Resources */,
-				9705A1C41CF9048500538489 /* Embed Frameworks */,
-				3B06AD1E1E4923F5004D2608 /* Thin Binary */,
-				6A2F146AD353BE7A0C3E797E /* [CP] Embed Pods Frameworks */,
-			);
-			buildRules = (
-			);
-			dependencies = (
-			);
-			name = Runner;
-			productName = Runner;
-			productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
-			productType = "com.apple.product-type.application";
-		};
-/* End PBXNativeTarget section */
-
-/* Begin PBXProject section */
-		97C146E61CF9000F007C117D /* Project object */ = {
-			isa = PBXProject;
-			attributes = {
-				LastUpgradeCheck = 1100;
-				ORGANIZATIONNAME = "The Chromium Authors";
-				TargetAttributes = {
-					97C146ED1CF9000F007C117D = {
-						CreatedOnToolsVersion = 7.3.1;
-					};
-				};
-			};
-			buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
-			compatibilityVersion = "Xcode 3.2";
-			developmentRegion = en;
-			hasScannedForEncodings = 0;
-			knownRegions = (
-				en,
-				Base,
-			);
-			mainGroup = 97C146E51CF9000F007C117D;
-			productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
-			projectDirPath = "";
-			projectRoot = "";
-			targets = (
-				97C146ED1CF9000F007C117D /* Runner */,
-			);
-		};
-/* End PBXProject section */
-
-/* Begin PBXResourcesBuildPhase section */
-		97C146EC1CF9000F007C117D /* Resources */ = {
-			isa = PBXResourcesBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-				97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
-				3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
-				97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
-				97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-		};
-/* End PBXResourcesBuildPhase section */
-
-/* Begin PBXShellScriptBuildPhase section */
-		3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputPaths = (
-			);
-			name = "Thin Binary";
-			outputPaths = (
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
-		};
-		3BAF367E8BACBC7576CEE653 /* [CP] Check Pods Manifest.lock */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputPaths = (
-				"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
-				"${PODS_ROOT}/Manifest.lock",
-			);
-			name = "[CP] Check Pods Manifest.lock";
-			outputPaths = (
-				"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
-			showEnvVarsInLog = 0;
-		};
-		6A2F146AD353BE7A0C3E797E /* [CP] Embed Pods Frameworks */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputPaths = (
-			);
-			name = "[CP] Embed Pods Frameworks";
-			outputPaths = (
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
-			showEnvVarsInLog = 0;
-		};
-		9740EEB61CF901F6004384FC /* Run Script */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputPaths = (
-			);
-			name = "Run Script";
-			outputPaths = (
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
-		};
-/* End PBXShellScriptBuildPhase section */
-
-/* Begin PBXSourcesBuildPhase section */
-		97C146EA1CF9000F007C117D /* Sources */ = {
-			isa = PBXSourcesBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-				978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */,
-				97C146F31CF9000F007C117D /* main.m in Sources */,
-				1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-		};
-/* End PBXSourcesBuildPhase section */
-
-/* Begin PBXVariantGroup section */
-		97C146FA1CF9000F007C117D /* Main.storyboard */ = {
-			isa = PBXVariantGroup;
-			children = (
-				97C146FB1CF9000F007C117D /* Base */,
-			);
-			name = Main.storyboard;
-			sourceTree = "<group>";
-		};
-		97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
-			isa = PBXVariantGroup;
-			children = (
-				97C147001CF9000F007C117D /* Base */,
-			);
-			name = LaunchScreen.storyboard;
-			sourceTree = "<group>";
-		};
-/* End PBXVariantGroup section */
-
-/* Begin XCBuildConfiguration section */
-		97C147031CF9000F007C117D /* Debug */ = {
-			isa = XCBuildConfiguration;
-			baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
-			buildSettings = {
-				ALWAYS_SEARCH_USER_PATHS = NO;
-				CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
-				CLANG_ANALYZER_NONNULL = YES;
-				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
-				CLANG_CXX_LIBRARY = "libc++";
-				CLANG_ENABLE_MODULES = YES;
-				CLANG_ENABLE_OBJC_ARC = YES;
-				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
-				CLANG_WARN_BOOL_CONVERSION = YES;
-				CLANG_WARN_COMMA = YES;
-				CLANG_WARN_CONSTANT_CONVERSION = YES;
-				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
-				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
-				CLANG_WARN_EMPTY_BODY = YES;
-				CLANG_WARN_ENUM_CONVERSION = YES;
-				CLANG_WARN_INFINITE_RECURSION = YES;
-				CLANG_WARN_INT_CONVERSION = YES;
-				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
-				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
-				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
-				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
-				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
-				CLANG_WARN_STRICT_PROTOTYPES = YES;
-				CLANG_WARN_SUSPICIOUS_MOVE = YES;
-				CLANG_WARN_UNREACHABLE_CODE = YES;
-				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
-				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
-				COPY_PHASE_STRIP = NO;
-				DEBUG_INFORMATION_FORMAT = dwarf;
-				ENABLE_STRICT_OBJC_MSGSEND = YES;
-				ENABLE_TESTABILITY = YES;
-				GCC_C_LANGUAGE_STANDARD = gnu99;
-				GCC_DYNAMIC_NO_PIC = NO;
-				GCC_NO_COMMON_BLOCKS = YES;
-				GCC_OPTIMIZATION_LEVEL = 0;
-				GCC_PREPROCESSOR_DEFINITIONS = (
-					"DEBUG=1",
-					"$(inherited)",
-				);
-				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
-				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
-				GCC_WARN_UNDECLARED_SELECTOR = YES;
-				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
-				GCC_WARN_UNUSED_FUNCTION = YES;
-				GCC_WARN_UNUSED_VARIABLE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 8.0;
-				MTL_ENABLE_DEBUG_INFO = YES;
-				ONLY_ACTIVE_ARCH = YES;
-				SDKROOT = iphoneos;
-				TARGETED_DEVICE_FAMILY = "1,2";
-			};
-			name = Debug;
-		};
-		97C147041CF9000F007C117D /* Release */ = {
-			isa = XCBuildConfiguration;
-			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
-			buildSettings = {
-				ALWAYS_SEARCH_USER_PATHS = NO;
-				CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
-				CLANG_ANALYZER_NONNULL = YES;
-				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
-				CLANG_CXX_LIBRARY = "libc++";
-				CLANG_ENABLE_MODULES = YES;
-				CLANG_ENABLE_OBJC_ARC = YES;
-				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
-				CLANG_WARN_BOOL_CONVERSION = YES;
-				CLANG_WARN_COMMA = YES;
-				CLANG_WARN_CONSTANT_CONVERSION = YES;
-				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
-				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
-				CLANG_WARN_EMPTY_BODY = YES;
-				CLANG_WARN_ENUM_CONVERSION = YES;
-				CLANG_WARN_INFINITE_RECURSION = YES;
-				CLANG_WARN_INT_CONVERSION = YES;
-				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
-				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
-				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
-				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
-				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
-				CLANG_WARN_STRICT_PROTOTYPES = YES;
-				CLANG_WARN_SUSPICIOUS_MOVE = YES;
-				CLANG_WARN_UNREACHABLE_CODE = YES;
-				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
-				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
-				COPY_PHASE_STRIP = NO;
-				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
-				ENABLE_NS_ASSERTIONS = NO;
-				ENABLE_STRICT_OBJC_MSGSEND = YES;
-				GCC_C_LANGUAGE_STANDARD = gnu99;
-				GCC_NO_COMMON_BLOCKS = YES;
-				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
-				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
-				GCC_WARN_UNDECLARED_SELECTOR = YES;
-				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
-				GCC_WARN_UNUSED_FUNCTION = YES;
-				GCC_WARN_UNUSED_VARIABLE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 8.0;
-				MTL_ENABLE_DEBUG_INFO = NO;
-				SDKROOT = iphoneos;
-				TARGETED_DEVICE_FAMILY = "1,2";
-				VALIDATE_PRODUCT = YES;
-			};
-			name = Release;
-		};
-		97C147061CF9000F007C117D /* Debug */ = {
-			isa = XCBuildConfiguration;
-			baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
-			buildSettings = {
-				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
-				ENABLE_BITCODE = NO;
-				FRAMEWORK_SEARCH_PATHS = (
-					"$(inherited)",
-					"$(PROJECT_DIR)/Flutter",
-				);
-				INFOPLIST_FILE = Runner/Info.plist;
-				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
-				LIBRARY_SEARCH_PATHS = (
-					"$(inherited)",
-					"$(PROJECT_DIR)/Flutter",
-				);
-				PRODUCT_BUNDLE_IDENTIFIER = io.flutter.plugins.connectivityExample;
-				PRODUCT_NAME = "$(TARGET_NAME)";
-			};
-			name = Debug;
-		};
-		97C147071CF9000F007C117D /* Release */ = {
-			isa = XCBuildConfiguration;
-			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
-			buildSettings = {
-				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
-				ENABLE_BITCODE = NO;
-				FRAMEWORK_SEARCH_PATHS = (
-					"$(inherited)",
-					"$(PROJECT_DIR)/Flutter",
-				);
-				INFOPLIST_FILE = Runner/Info.plist;
-				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
-				LIBRARY_SEARCH_PATHS = (
-					"$(inherited)",
-					"$(PROJECT_DIR)/Flutter",
-				);
-				PRODUCT_BUNDLE_IDENTIFIER = io.flutter.plugins.connectivityExample;
-				PRODUCT_NAME = "$(TARGET_NAME)";
-			};
-			name = Release;
-		};
-/* End XCBuildConfiguration section */
-
-/* Begin XCConfigurationList section */
-		97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
-			isa = XCConfigurationList;
-			buildConfigurations = (
-				97C147031CF9000F007C117D /* Debug */,
-				97C147041CF9000F007C117D /* Release */,
-			);
-			defaultConfigurationIsVisible = 0;
-			defaultConfigurationName = Release;
-		};
-		97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
-			isa = XCConfigurationList;
-			buildConfigurations = (
-				97C147061CF9000F007C117D /* Debug */,
-				97C147071CF9000F007C117D /* Release */,
-			);
-			defaultConfigurationIsVisible = 0;
-			defaultConfigurationName = Release;
-		};
-/* End XCConfigurationList section */
-	};
-	rootObject = 97C146E61CF9000F007C117D /* Project object */;
-}
diff --git a/connectivity_macos/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/connectivity_macos/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
deleted file mode 100644
index 1d526a1..0000000
--- a/connectivity_macos/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<Workspace
-   version = "1.0">
-   <FileRef
-      location = "group:Runner.xcodeproj">
-   </FileRef>
-</Workspace>
diff --git a/connectivity_macos/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/connectivity_macos/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
deleted file mode 100644
index 3bb3697..0000000
--- a/connectivity_macos/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ /dev/null
@@ -1,87 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<Scheme
-   LastUpgradeVersion = "1100"
-   version = "1.3">
-   <BuildAction
-      parallelizeBuildables = "YES"
-      buildImplicitDependencies = "YES">
-      <BuildActionEntries>
-         <BuildActionEntry
-            buildForTesting = "YES"
-            buildForRunning = "YES"
-            buildForProfiling = "YES"
-            buildForArchiving = "YES"
-            buildForAnalyzing = "YES">
-            <BuildableReference
-               BuildableIdentifier = "primary"
-               BlueprintIdentifier = "97C146ED1CF9000F007C117D"
-               BuildableName = "Runner.app"
-               BlueprintName = "Runner"
-               ReferencedContainer = "container:Runner.xcodeproj">
-            </BuildableReference>
-         </BuildActionEntry>
-      </BuildActionEntries>
-   </BuildAction>
-   <TestAction
-      buildConfiguration = "Debug"
-      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
-      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
-      shouldUseLaunchSchemeArgsEnv = "YES">
-      <MacroExpansion>
-         <BuildableReference
-            BuildableIdentifier = "primary"
-            BlueprintIdentifier = "97C146ED1CF9000F007C117D"
-            BuildableName = "Runner.app"
-            BlueprintName = "Runner"
-            ReferencedContainer = "container:Runner.xcodeproj">
-         </BuildableReference>
-      </MacroExpansion>
-      <Testables>
-      </Testables>
-   </TestAction>
-   <LaunchAction
-      buildConfiguration = "Debug"
-      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
-      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
-      launchStyle = "0"
-      useCustomWorkingDirectory = "NO"
-      ignoresPersistentStateOnLaunch = "NO"
-      debugDocumentVersioning = "YES"
-      debugServiceExtension = "internal"
-      allowLocationSimulation = "YES">
-      <BuildableProductRunnable
-         runnableDebuggingMode = "0">
-         <BuildableReference
-            BuildableIdentifier = "primary"
-            BlueprintIdentifier = "97C146ED1CF9000F007C117D"
-            BuildableName = "Runner.app"
-            BlueprintName = "Runner"
-            ReferencedContainer = "container:Runner.xcodeproj">
-         </BuildableReference>
-      </BuildableProductRunnable>
-   </LaunchAction>
-   <ProfileAction
-      buildConfiguration = "Release"
-      shouldUseLaunchSchemeArgsEnv = "YES"
-      savedToolIdentifier = ""
-      useCustomWorkingDirectory = "NO"
-      debugDocumentVersioning = "YES">
-      <BuildableProductRunnable
-         runnableDebuggingMode = "0">
-         <BuildableReference
-            BuildableIdentifier = "primary"
-            BlueprintIdentifier = "97C146ED1CF9000F007C117D"
-            BuildableName = "Runner.app"
-            BlueprintName = "Runner"
-            ReferencedContainer = "container:Runner.xcodeproj">
-         </BuildableReference>
-      </BuildableProductRunnable>
-   </ProfileAction>
-   <AnalyzeAction
-      buildConfiguration = "Debug">
-   </AnalyzeAction>
-   <ArchiveAction
-      buildConfiguration = "Release"
-      revealArchiveInOrganizer = "YES">
-   </ArchiveAction>
-</Scheme>
diff --git a/connectivity_macos/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/connectivity_macos/example/ios/Runner.xcworkspace/contents.xcworkspacedata
deleted file mode 100644
index 21a3cc1..0000000
--- a/connectivity_macos/example/ios/Runner.xcworkspace/contents.xcworkspacedata
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<Workspace
-   version = "1.0">
-   <FileRef
-      location = "group:Runner.xcodeproj">
-   </FileRef>
-   <FileRef
-      location = "group:Pods/Pods.xcodeproj">
-   </FileRef>
-</Workspace>
diff --git a/connectivity_macos/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/connectivity_macos/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
deleted file mode 100644
index 949b678..0000000
--- a/connectivity_macos/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-	<key>BuildSystemType</key>
-	<string>Original</string>
-</dict>
-</plist>
diff --git a/connectivity_macos/example/ios/Runner/AppDelegate.h b/connectivity_macos/example/ios/Runner/AppDelegate.h
deleted file mode 100644
index d9e18e9..0000000
--- a/connectivity_macos/example/ios/Runner/AppDelegate.h
+++ /dev/null
@@ -1,10 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import <Flutter/Flutter.h>
-#import <UIKit/UIKit.h>
-
-@interface AppDelegate : FlutterAppDelegate
-
-@end
diff --git a/connectivity_macos/example/ios/Runner/AppDelegate.m b/connectivity_macos/example/ios/Runner/AppDelegate.m
deleted file mode 100644
index f086757..0000000
--- a/connectivity_macos/example/ios/Runner/AppDelegate.m
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "AppDelegate.h"
-#include "GeneratedPluginRegistrant.h"
-
-@implementation AppDelegate
-
-- (BOOL)application:(UIApplication *)application
-    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
-  [GeneratedPluginRegistrant registerWithRegistry:self];
-  // Override point for customization after application launch.
-  return [super application:application didFinishLaunchingWithOptions:launchOptions];
-}
-
-@end
diff --git a/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
deleted file mode 100644
index d22f10b..0000000
--- a/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
+++ /dev/null
@@ -1,116 +0,0 @@
-{
-  "images" : [
-    {
-      "size" : "20x20",
-      "idiom" : "iphone",
-      "filename" : "Icon-App-20x20@2x.png",
-      "scale" : "2x"
-    },
-    {
-      "size" : "20x20",
-      "idiom" : "iphone",
-      "filename" : "Icon-App-20x20@3x.png",
-      "scale" : "3x"
-    },
-    {
-      "size" : "29x29",
-      "idiom" : "iphone",
-      "filename" : "Icon-App-29x29@1x.png",
-      "scale" : "1x"
-    },
-    {
-      "size" : "29x29",
-      "idiom" : "iphone",
-      "filename" : "Icon-App-29x29@2x.png",
-      "scale" : "2x"
-    },
-    {
-      "size" : "29x29",
-      "idiom" : "iphone",
-      "filename" : "Icon-App-29x29@3x.png",
-      "scale" : "3x"
-    },
-    {
-      "size" : "40x40",
-      "idiom" : "iphone",
-      "filename" : "Icon-App-40x40@2x.png",
-      "scale" : "2x"
-    },
-    {
-      "size" : "40x40",
-      "idiom" : "iphone",
-      "filename" : "Icon-App-40x40@3x.png",
-      "scale" : "3x"
-    },
-    {
-      "size" : "60x60",
-      "idiom" : "iphone",
-      "filename" : "Icon-App-60x60@2x.png",
-      "scale" : "2x"
-    },
-    {
-      "size" : "60x60",
-      "idiom" : "iphone",
-      "filename" : "Icon-App-60x60@3x.png",
-      "scale" : "3x"
-    },
-    {
-      "size" : "20x20",
-      "idiom" : "ipad",
-      "filename" : "Icon-App-20x20@1x.png",
-      "scale" : "1x"
-    },
-    {
-      "size" : "20x20",
-      "idiom" : "ipad",
-      "filename" : "Icon-App-20x20@2x.png",
-      "scale" : "2x"
-    },
-    {
-      "size" : "29x29",
-      "idiom" : "ipad",
-      "filename" : "Icon-App-29x29@1x.png",
-      "scale" : "1x"
-    },
-    {
-      "size" : "29x29",
-      "idiom" : "ipad",
-      "filename" : "Icon-App-29x29@2x.png",
-      "scale" : "2x"
-    },
-    {
-      "size" : "40x40",
-      "idiom" : "ipad",
-      "filename" : "Icon-App-40x40@1x.png",
-      "scale" : "1x"
-    },
-    {
-      "size" : "40x40",
-      "idiom" : "ipad",
-      "filename" : "Icon-App-40x40@2x.png",
-      "scale" : "2x"
-    },
-    {
-      "size" : "76x76",
-      "idiom" : "ipad",
-      "filename" : "Icon-App-76x76@1x.png",
-      "scale" : "1x"
-    },
-    {
-      "size" : "76x76",
-      "idiom" : "ipad",
-      "filename" : "Icon-App-76x76@2x.png",
-      "scale" : "2x"
-    },
-    {
-      "size" : "83.5x83.5",
-      "idiom" : "ipad",
-      "filename" : "Icon-App-83.5x83.5@2x.png",
-      "scale" : "2x"
-    }
-  ],
-  "info" : {
-    "version" : 1,
-    "author" : "xcode"
-  }
-}
diff --git a/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
deleted file mode 100644
index 28c6bf0..0000000
--- a/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
+++ /dev/null
Binary files differ
diff --git a/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
deleted file mode 100644
index 2ccbfd9..0000000
--- a/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
+++ /dev/null
Binary files differ
diff --git a/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
deleted file mode 100644
index f091b6b..0000000
--- a/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
+++ /dev/null
Binary files differ
diff --git a/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
deleted file mode 100644
index 4cde121..0000000
--- a/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
+++ /dev/null
Binary files differ
diff --git a/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
deleted file mode 100644
index d0ef06e..0000000
--- a/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
+++ /dev/null
Binary files differ
diff --git a/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
deleted file mode 100644
index dcdc230..0000000
--- a/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
+++ /dev/null
Binary files differ
diff --git a/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
deleted file mode 100644
index 2ccbfd9..0000000
--- a/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
+++ /dev/null
Binary files differ
diff --git a/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
deleted file mode 100644
index c8f9ed8..0000000
--- a/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
+++ /dev/null
Binary files differ
diff --git a/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
deleted file mode 100644
index a6d6b86..0000000
--- a/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
+++ /dev/null
Binary files differ
diff --git a/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
deleted file mode 100644
index a6d6b86..0000000
--- a/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
+++ /dev/null
Binary files differ
diff --git a/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
deleted file mode 100644
index 75b2d16..0000000
--- a/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
+++ /dev/null
Binary files differ
diff --git a/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
deleted file mode 100644
index c4df70d..0000000
--- a/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
+++ /dev/null
Binary files differ
diff --git a/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
deleted file mode 100644
index 6a84f41..0000000
--- a/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
+++ /dev/null
Binary files differ
diff --git a/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
deleted file mode 100644
index d0e1f58..0000000
--- a/connectivity_macos/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
+++ /dev/null
Binary files differ
diff --git a/connectivity_macos/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/connectivity_macos/example/ios/Runner/Base.lproj/LaunchScreen.storyboard
deleted file mode 100644
index ebf48f6..0000000
--- a/connectivity_macos/example/ios/Runner/Base.lproj/LaunchScreen.storyboard
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
-    <dependencies>
-        <deployment identifier="iOS"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
-    </dependencies>
-    <scenes>
-        <!--View Controller-->
-        <scene sceneID="EHf-IW-A2E">
-            <objects>
-                <viewController id="01J-lp-oVM" sceneMemberID="viewController">
-                    <layoutGuides>
-                        <viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
-                        <viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
-                    </layoutGuides>
-                    <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
-                        <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
-                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
-                        <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
-                    </view>
-                </viewController>
-                <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
-            </objects>
-            <point key="canvasLocation" x="53" y="375"/>
-        </scene>
-    </scenes>
-</document>
diff --git a/connectivity_macos/example/ios/Runner/Base.lproj/Main.storyboard b/connectivity_macos/example/ios/Runner/Base.lproj/Main.storyboard
deleted file mode 100644
index f3c2851..0000000
--- a/connectivity_macos/example/ios/Runner/Base.lproj/Main.storyboard
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
-    <dependencies>
-        <deployment identifier="iOS"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
-    </dependencies>
-    <scenes>
-        <!--Flutter View Controller-->
-        <scene sceneID="tne-QT-ifu">
-            <objects>
-                <viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
-                    <layoutGuides>
-                        <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
-                        <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
-                    </layoutGuides>
-                    <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
-                        <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
-                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
-                        <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
-                    </view>
-                </viewController>
-                <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
-            </objects>
-        </scene>
-    </scenes>
-</document>
diff --git a/connectivity_macos/example/ios/Runner/Info.plist b/connectivity_macos/example/ios/Runner/Info.plist
deleted file mode 100644
index babbd80..0000000
--- a/connectivity_macos/example/ios/Runner/Info.plist
+++ /dev/null
@@ -1,53 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-	<key>CFBundleDevelopmentRegion</key>
-	<string>en</string>
-	<key>CFBundleExecutable</key>
-	<string>$(EXECUTABLE_NAME)</string>
-	<key>CFBundleIdentifier</key>
-	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
-	<key>CFBundleInfoDictionaryVersion</key>
-	<string>6.0</string>
-	<key>CFBundleName</key>
-	<string>connectivity_example</string>
-	<key>CFBundlePackageType</key>
-	<string>APPL</string>
-	<key>CFBundleShortVersionString</key>
-	<string>1.0</string>
-	<key>CFBundleSignature</key>
-	<string>????</string>
-	<key>CFBundleVersion</key>
-	<string>1</string>
-	<key>LSRequiresIPhoneOS</key>
-	<true/>
-	<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
-	<string>This app requires accessing your location information all the time to get wi-fi information.</string>
-	<key>NSLocationWhenInUseUsageDescription</key>
-	<string>This app requires accessing your location information when the app is in foreground to get wi-fi information.</string>
-	<key>UILaunchStoryboardName</key>
-	<string>LaunchScreen</string>
-	<key>UIMainStoryboardFile</key>
-	<string>Main</string>
-	<key>UIRequiredDeviceCapabilities</key>
-	<array>
-		<string>arm64</string>
-	</array>
-	<key>UISupportedInterfaceOrientations</key>
-	<array>
-		<string>UIInterfaceOrientationPortrait</string>
-		<string>UIInterfaceOrientationLandscapeLeft</string>
-		<string>UIInterfaceOrientationLandscapeRight</string>
-	</array>
-	<key>UISupportedInterfaceOrientations~ipad</key>
-	<array>
-		<string>UIInterfaceOrientationPortrait</string>
-		<string>UIInterfaceOrientationPortraitUpsideDown</string>
-		<string>UIInterfaceOrientationLandscapeLeft</string>
-		<string>UIInterfaceOrientationLandscapeRight</string>
-	</array>
-	<key>UIViewControllerBasedStatusBarAppearance</key>
-	<false/>
-</dict>
-</plist>
diff --git a/connectivity_macos/example/ios/Runner/Runner.entitlements b/connectivity_macos/example/ios/Runner/Runner.entitlements
deleted file mode 100644
index ba21fbd..0000000
--- a/connectivity_macos/example/ios/Runner/Runner.entitlements
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-	<key>com.apple.developer.networking.wifi-info</key>
-	<true/>
-</dict>
-</plist>
diff --git a/connectivity_macos/example/ios/Runner/main.m b/connectivity_macos/example/ios/Runner/main.m
deleted file mode 100644
index bec320c..0000000
--- a/connectivity_macos/example/ios/Runner/main.m
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import <Flutter/Flutter.h>
-#import <UIKit/UIKit.h>
-#import "AppDelegate.h"
-
-int main(int argc, char* argv[]) {
-  @autoreleasepool {
-    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
-  }
-}
diff --git a/connectivity_macos/example/lib/main.dart b/connectivity_macos/example/lib/main.dart
deleted file mode 100644
index 4ad3097..0000000
--- a/connectivity_macos/example/lib/main.dart
+++ /dev/null
@@ -1,172 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// ignore_for_file: public_member_api_docs
-
-import 'dart:async';
-import 'dart:io';
-
-import 'package:connectivity/connectivity.dart';
-import 'package:flutter/foundation.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
-
-// Sets a platform override for desktop to avoid exceptions. See
-// https://flutter.dev/desktop#target-platform-override for more info.
-void _enablePlatformOverrideForDesktop() {
-  if (!kIsWeb && (Platform.isWindows || Platform.isLinux)) {
-    debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia;
-  }
-}
-
-void main() {
-  _enablePlatformOverrideForDesktop();
-  runApp(MyApp());
-}
-
-class MyApp extends StatelessWidget {
-  // This widget is the root of your application.
-  @override
-  Widget build(BuildContext context) {
-    return MaterialApp(
-      title: 'Flutter Demo',
-      theme: ThemeData(
-        primarySwatch: Colors.blue,
-      ),
-      home: MyHomePage(title: 'Flutter Demo Home Page'),
-    );
-  }
-}
-
-class MyHomePage extends StatefulWidget {
-  MyHomePage({Key key, this.title}) : super(key: key);
-
-  final String title;
-
-  @override
-  _MyHomePageState createState() => _MyHomePageState();
-}
-
-class _MyHomePageState extends State<MyHomePage> {
-  String _connectionStatus = 'Unknown';
-  final Connectivity _connectivity = Connectivity();
-  StreamSubscription<ConnectivityResult> _connectivitySubscription;
-
-  @override
-  void initState() {
-    super.initState();
-    initConnectivity();
-    _connectivitySubscription =
-        _connectivity.onConnectivityChanged.listen(_updateConnectionStatus);
-  }
-
-  @override
-  void dispose() {
-    _connectivitySubscription.cancel();
-    super.dispose();
-  }
-
-  // Platform messages are asynchronous, so we initialize in an async method.
-  Future<void> initConnectivity() async {
-    ConnectivityResult result;
-    // Platform messages may fail, so we use a try/catch PlatformException.
-    try {
-      result = await _connectivity.checkConnectivity();
-    } on PlatformException catch (e) {
-      print(e.toString());
-    }
-
-    // If the widget was removed from the tree while the asynchronous platform
-    // message was in flight, we want to discard the reply rather than calling
-    // setState to update our non-existent appearance.
-    if (!mounted) {
-      return Future.value(null);
-    }
-
-    return _updateConnectionStatus(result);
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    return Scaffold(
-      appBar: AppBar(
-        title: const Text('Plugin example app'),
-      ),
-      body: Center(child: Text('Connection Status: $_connectionStatus')),
-    );
-  }
-
-  Future<void> _updateConnectionStatus(ConnectivityResult result) async {
-    switch (result) {
-      case ConnectivityResult.wifi:
-        String wifiName, wifiBSSID, wifiIP;
-
-        try {
-          if (Platform.isIOS) {
-            LocationAuthorizationStatus status =
-                await _connectivity.getLocationServiceAuthorization();
-            if (status == LocationAuthorizationStatus.notDetermined) {
-              status =
-                  await _connectivity.requestLocationServiceAuthorization();
-            }
-            if (status == LocationAuthorizationStatus.authorizedAlways ||
-                status == LocationAuthorizationStatus.authorizedWhenInUse) {
-              wifiName = await _connectivity.getWifiName();
-            } else {
-              wifiName = await _connectivity.getWifiName();
-            }
-          } else {
-            wifiName = await _connectivity.getWifiName();
-          }
-        } on PlatformException catch (e) {
-          print(e.toString());
-          wifiName = "Failed to get Wifi Name";
-        }
-
-        try {
-          if (Platform.isIOS) {
-            LocationAuthorizationStatus status =
-                await _connectivity.getLocationServiceAuthorization();
-            if (status == LocationAuthorizationStatus.notDetermined) {
-              status =
-                  await _connectivity.requestLocationServiceAuthorization();
-            }
-            if (status == LocationAuthorizationStatus.authorizedAlways ||
-                status == LocationAuthorizationStatus.authorizedWhenInUse) {
-              wifiBSSID = await _connectivity.getWifiBSSID();
-            } else {
-              wifiBSSID = await _connectivity.getWifiBSSID();
-            }
-          } else {
-            wifiBSSID = await _connectivity.getWifiBSSID();
-          }
-        } on PlatformException catch (e) {
-          print(e.toString());
-          wifiBSSID = "Failed to get Wifi BSSID";
-        }
-
-        try {
-          wifiIP = await _connectivity.getWifiIP();
-        } on PlatformException catch (e) {
-          print(e.toString());
-          wifiIP = "Failed to get Wifi IP";
-        }
-
-        setState(() {
-          _connectionStatus = '$result\n'
-              'Wifi Name: $wifiName\n'
-              'Wifi BSSID: $wifiBSSID\n'
-              'Wifi IP: $wifiIP\n';
-        });
-        break;
-      case ConnectivityResult.mobile:
-      case ConnectivityResult.none:
-        setState(() => _connectionStatus = result.toString());
-        break;
-      default:
-        setState(() => _connectionStatus = 'Failed to get connectivity.');
-        break;
-    }
-  }
-}
diff --git a/connectivity_macos/example/macos/Flutter/Flutter-Debug.xcconfig b/connectivity_macos/example/macos/Flutter/Flutter-Debug.xcconfig
deleted file mode 100644
index 785633d..0000000
--- a/connectivity_macos/example/macos/Flutter/Flutter-Debug.xcconfig
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
-#include "ephemeral/Flutter-Generated.xcconfig"
diff --git a/connectivity_macos/example/macos/Flutter/Flutter-Release.xcconfig b/connectivity_macos/example/macos/Flutter/Flutter-Release.xcconfig
deleted file mode 100644
index 5fba960..0000000
--- a/connectivity_macos/example/macos/Flutter/Flutter-Release.xcconfig
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
-#include "ephemeral/Flutter-Generated.xcconfig"
diff --git a/connectivity_macos/example/macos/Runner.xcodeproj/project.pbxproj b/connectivity_macos/example/macos/Runner.xcodeproj/project.pbxproj
deleted file mode 100644
index 0e24134..0000000
--- a/connectivity_macos/example/macos/Runner.xcodeproj/project.pbxproj
+++ /dev/null
@@ -1,654 +0,0 @@
-// !$*UTF8*$!
-{
-	archiveVersion = 1;
-	classes = {
-	};
-	objectVersion = 51;
-	objects = {
-
-/* Begin PBXAggregateTarget section */
-		33CC111A2044C6BA0003C045 /* Flutter Assemble */ = {
-			isa = PBXAggregateTarget;
-			buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */;
-			buildPhases = (
-				33CC111E2044C6BF0003C045 /* ShellScript */,
-			);
-			dependencies = (
-			);
-			name = "Flutter Assemble";
-			productName = FLX;
-		};
-/* End PBXAggregateTarget section */
-
-/* Begin PBXBuildFile section */
-		335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
-		33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
-		33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
-		33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
-		33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
-		33D1A10422148B71006C7A3E /* FlutterMacOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 33D1A10322148B71006C7A3E /* FlutterMacOS.framework */; };
-		33D1A10522148B93006C7A3E /* FlutterMacOS.framework in Bundle Framework */ = {isa = PBXBuildFile; fileRef = 33D1A10322148B71006C7A3E /* FlutterMacOS.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
-		D73912F022F37F9E000D13A0 /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D73912EF22F37F9E000D13A0 /* App.framework */; };
-		D73912F222F3801D000D13A0 /* App.framework in Bundle Framework */ = {isa = PBXBuildFile; fileRef = D73912EF22F37F9E000D13A0 /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
-		EA473EC5F2038B17A2FE4D78 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 748ADDF1719804343BB18004 /* Pods_Runner.framework */; };
-/* End PBXBuildFile section */
-
-/* Begin PBXContainerItemProxy section */
-		33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = {
-			isa = PBXContainerItemProxy;
-			containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
-			proxyType = 1;
-			remoteGlobalIDString = 33CC111A2044C6BA0003C045;
-			remoteInfo = FLX;
-		};
-/* End PBXContainerItemProxy section */
-
-/* Begin PBXCopyFilesBuildPhase section */
-		33CC110E2044A8840003C045 /* Bundle Framework */ = {
-			isa = PBXCopyFilesBuildPhase;
-			buildActionMask = 2147483647;
-			dstPath = "";
-			dstSubfolderSpec = 10;
-			files = (
-				D73912F222F3801D000D13A0 /* App.framework in Bundle Framework */,
-				33D1A10522148B93006C7A3E /* FlutterMacOS.framework in Bundle Framework */,
-			);
-			name = "Bundle Framework";
-			runOnlyForDeploymentPostprocessing = 0;
-		};
-/* End PBXCopyFilesBuildPhase section */
-
-/* Begin PBXFileReference section */
-		333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
-		335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
-		33CC10ED2044A3C60003C045 /* connectivity_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = connectivity_example.app; sourceTree = BUILT_PRODUCTS_DIR; };
-		33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
-		33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
-		33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
-		33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = "<group>"; };
-		33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = "<group>"; };
-		33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = "<group>"; };
-		33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = "<group>"; };
-		33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = "<group>"; };
-		33D1A10322148B71006C7A3E /* FlutterMacOS.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FlutterMacOS.framework; path = Flutter/ephemeral/FlutterMacOS.framework; sourceTree = SOURCE_ROOT; };
-		33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
-		33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
-		33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
-		748ADDF1719804343BB18004 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
-		7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
-		80418F0A2F74D683C63A4D0A /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
-		9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
-		AA19B00394637215A825CF5E /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
-		D73912EF22F37F9E000D13A0 /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/ephemeral/App.framework; sourceTree = SOURCE_ROOT; };
-		E960ED3977AF6DF197F74FFA /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
-/* End PBXFileReference section */
-
-/* Begin PBXFrameworksBuildPhase section */
-		33CC10EA2044A3C60003C045 /* Frameworks */ = {
-			isa = PBXFrameworksBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-				D73912F022F37F9E000D13A0 /* App.framework in Frameworks */,
-				33D1A10422148B71006C7A3E /* FlutterMacOS.framework in Frameworks */,
-				EA473EC5F2038B17A2FE4D78 /* Pods_Runner.framework in Frameworks */,
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-		};
-/* End PBXFrameworksBuildPhase section */
-
-/* Begin PBXGroup section */
-		33BA886A226E78AF003329D5 /* Configs */ = {
-			isa = PBXGroup;
-			children = (
-				33E5194F232828860026EE4D /* AppInfo.xcconfig */,
-				9740EEB21CF90195004384FC /* Debug.xcconfig */,
-				7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
-				333000ED22D3DE5D00554162 /* Warnings.xcconfig */,
-			);
-			path = Configs;
-			sourceTree = "<group>";
-		};
-		33CC10E42044A3C60003C045 = {
-			isa = PBXGroup;
-			children = (
-				33FAB671232836740065AC1E /* Runner */,
-				33CEB47122A05771004F2AC0 /* Flutter */,
-				33CC10EE2044A3C60003C045 /* Products */,
-				D73912EC22F37F3D000D13A0 /* Frameworks */,
-				D42EAEE5849744148CC78D83 /* Pods */,
-			);
-			sourceTree = "<group>";
-		};
-		33CC10EE2044A3C60003C045 /* Products */ = {
-			isa = PBXGroup;
-			children = (
-				33CC10ED2044A3C60003C045 /* connectivity_example.app */,
-			);
-			name = Products;
-			sourceTree = "<group>";
-		};
-		33CC11242044D66E0003C045 /* Resources */ = {
-			isa = PBXGroup;
-			children = (
-				33CC10F22044A3C60003C045 /* Assets.xcassets */,
-				33CC10F42044A3C60003C045 /* MainMenu.xib */,
-				33CC10F72044A3C60003C045 /* Info.plist */,
-			);
-			name = Resources;
-			path = ..;
-			sourceTree = "<group>";
-		};
-		33CEB47122A05771004F2AC0 /* Flutter */ = {
-			isa = PBXGroup;
-			children = (
-				335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */,
-				33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */,
-				33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */,
-				33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */,
-				D73912EF22F37F9E000D13A0 /* App.framework */,
-				33D1A10322148B71006C7A3E /* FlutterMacOS.framework */,
-			);
-			path = Flutter;
-			sourceTree = "<group>";
-		};
-		33FAB671232836740065AC1E /* Runner */ = {
-			isa = PBXGroup;
-			children = (
-				33CC10F02044A3C60003C045 /* AppDelegate.swift */,
-				33CC11122044BFA00003C045 /* MainFlutterWindow.swift */,
-				33E51913231747F40026EE4D /* DebugProfile.entitlements */,
-				33E51914231749380026EE4D /* Release.entitlements */,
-				33CC11242044D66E0003C045 /* Resources */,
-				33BA886A226E78AF003329D5 /* Configs */,
-			);
-			path = Runner;
-			sourceTree = "<group>";
-		};
-		D42EAEE5849744148CC78D83 /* Pods */ = {
-			isa = PBXGroup;
-			children = (
-				80418F0A2F74D683C63A4D0A /* Pods-Runner.debug.xcconfig */,
-				E960ED3977AF6DF197F74FFA /* Pods-Runner.release.xcconfig */,
-				AA19B00394637215A825CF5E /* Pods-Runner.profile.xcconfig */,
-			);
-			name = Pods;
-			path = Pods;
-			sourceTree = "<group>";
-		};
-		D73912EC22F37F3D000D13A0 /* Frameworks */ = {
-			isa = PBXGroup;
-			children = (
-				748ADDF1719804343BB18004 /* Pods_Runner.framework */,
-			);
-			name = Frameworks;
-			sourceTree = "<group>";
-		};
-/* End PBXGroup section */
-
-/* Begin PBXNativeTarget section */
-		33CC10EC2044A3C60003C045 /* Runner */ = {
-			isa = PBXNativeTarget;
-			buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
-			buildPhases = (
-				B24477CAB9D5BDFC8F3553DA /* [CP] Check Pods Manifest.lock */,
-				33CC10E92044A3C60003C045 /* Sources */,
-				33CC10EA2044A3C60003C045 /* Frameworks */,
-				33CC10EB2044A3C60003C045 /* Resources */,
-				33CC110E2044A8840003C045 /* Bundle Framework */,
-				3399D490228B24CF009A79C7 /* ShellScript */,
-				84A8D21305B2F01D093A8F9C /* [CP] Embed Pods Frameworks */,
-			);
-			buildRules = (
-			);
-			dependencies = (
-				33CC11202044C79F0003C045 /* PBXTargetDependency */,
-			);
-			name = Runner;
-			productName = Runner;
-			productReference = 33CC10ED2044A3C60003C045 /* connectivity_example.app */;
-			productType = "com.apple.product-type.application";
-		};
-/* End PBXNativeTarget section */
-
-/* Begin PBXProject section */
-		33CC10E52044A3C60003C045 /* Project object */ = {
-			isa = PBXProject;
-			attributes = {
-				LastSwiftUpdateCheck = 0920;
-				LastUpgradeCheck = 0930;
-				ORGANIZATIONNAME = "Google LLC";
-				TargetAttributes = {
-					33CC10EC2044A3C60003C045 = {
-						CreatedOnToolsVersion = 9.2;
-						LastSwiftMigration = 1100;
-						ProvisioningStyle = Automatic;
-						SystemCapabilities = {
-							com.apple.Sandbox = {
-								enabled = 1;
-							};
-						};
-					};
-					33CC111A2044C6BA0003C045 = {
-						CreatedOnToolsVersion = 9.2;
-						ProvisioningStyle = Manual;
-					};
-				};
-			};
-			buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */;
-			compatibilityVersion = "Xcode 8.0";
-			developmentRegion = en;
-			hasScannedForEncodings = 0;
-			knownRegions = (
-				en,
-				Base,
-			);
-			mainGroup = 33CC10E42044A3C60003C045;
-			productRefGroup = 33CC10EE2044A3C60003C045 /* Products */;
-			projectDirPath = "";
-			projectRoot = "";
-			targets = (
-				33CC10EC2044A3C60003C045 /* Runner */,
-				33CC111A2044C6BA0003C045 /* Flutter Assemble */,
-			);
-		};
-/* End PBXProject section */
-
-/* Begin PBXResourcesBuildPhase section */
-		33CC10EB2044A3C60003C045 /* Resources */ = {
-			isa = PBXResourcesBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-				33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */,
-				33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */,
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-		};
-/* End PBXResourcesBuildPhase section */
-
-/* Begin PBXShellScriptBuildPhase section */
-		3399D490228B24CF009A79C7 /* ShellScript */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputFileListPaths = (
-			);
-			inputPaths = (
-			);
-			outputFileListPaths = (
-			);
-			outputPaths = (
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename\n";
-		};
-		33CC111E2044C6BF0003C045 /* ShellScript */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputFileListPaths = (
-				Flutter/ephemeral/FlutterInputs.xcfilelist,
-			);
-			inputPaths = (
-				Flutter/ephemeral/tripwire,
-			);
-			outputFileListPaths = (
-				Flutter/ephemeral/FlutterOutputs.xcfilelist,
-			);
-			outputPaths = (
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh\ntouch Flutter/ephemeral/tripwire\n";
-		};
-		84A8D21305B2F01D093A8F9C /* [CP] Embed Pods Frameworks */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputFileListPaths = (
-			);
-			name = "[CP] Embed Pods Frameworks";
-			outputFileListPaths = (
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
-			showEnvVarsInLog = 0;
-		};
-		B24477CAB9D5BDFC8F3553DA /* [CP] Check Pods Manifest.lock */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputFileListPaths = (
-			);
-			inputPaths = (
-				"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
-				"${PODS_ROOT}/Manifest.lock",
-			);
-			name = "[CP] Check Pods Manifest.lock";
-			outputFileListPaths = (
-			);
-			outputPaths = (
-				"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
-			showEnvVarsInLog = 0;
-		};
-/* End PBXShellScriptBuildPhase section */
-
-/* Begin PBXSourcesBuildPhase section */
-		33CC10E92044A3C60003C045 /* Sources */ = {
-			isa = PBXSourcesBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-				33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */,
-				33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */,
-				335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */,
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-		};
-/* End PBXSourcesBuildPhase section */
-
-/* Begin PBXTargetDependency section */
-		33CC11202044C79F0003C045 /* PBXTargetDependency */ = {
-			isa = PBXTargetDependency;
-			target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */;
-			targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */;
-		};
-/* End PBXTargetDependency section */
-
-/* Begin PBXVariantGroup section */
-		33CC10F42044A3C60003C045 /* MainMenu.xib */ = {
-			isa = PBXVariantGroup;
-			children = (
-				33CC10F52044A3C60003C045 /* Base */,
-			);
-			name = MainMenu.xib;
-			path = Runner;
-			sourceTree = "<group>";
-		};
-/* End PBXVariantGroup section */
-
-/* Begin XCBuildConfiguration section */
-		338D0CE9231458BD00FA5F75 /* Profile */ = {
-			isa = XCBuildConfiguration;
-			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
-			buildSettings = {
-				ALWAYS_SEARCH_USER_PATHS = NO;
-				CLANG_ANALYZER_NONNULL = YES;
-				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
-				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
-				CLANG_CXX_LIBRARY = "libc++";
-				CLANG_ENABLE_MODULES = YES;
-				CLANG_ENABLE_OBJC_ARC = YES;
-				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
-				CLANG_WARN_BOOL_CONVERSION = YES;
-				CLANG_WARN_CONSTANT_CONVERSION = YES;
-				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
-				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
-				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
-				CLANG_WARN_EMPTY_BODY = YES;
-				CLANG_WARN_ENUM_CONVERSION = YES;
-				CLANG_WARN_INFINITE_RECURSION = YES;
-				CLANG_WARN_INT_CONVERSION = YES;
-				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
-				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
-				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
-				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
-				CLANG_WARN_SUSPICIOUS_MOVE = YES;
-				CODE_SIGN_IDENTITY = "-";
-				COPY_PHASE_STRIP = NO;
-				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
-				ENABLE_NS_ASSERTIONS = NO;
-				ENABLE_STRICT_OBJC_MSGSEND = YES;
-				GCC_C_LANGUAGE_STANDARD = gnu11;
-				GCC_NO_COMMON_BLOCKS = YES;
-				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
-				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
-				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
-				GCC_WARN_UNUSED_FUNCTION = YES;
-				GCC_WARN_UNUSED_VARIABLE = YES;
-				MACOSX_DEPLOYMENT_TARGET = 10.11;
-				MTL_ENABLE_DEBUG_INFO = NO;
-				SDKROOT = macosx;
-				SWIFT_COMPILATION_MODE = wholemodule;
-				SWIFT_OPTIMIZATION_LEVEL = "-O";
-			};
-			name = Profile;
-		};
-		338D0CEA231458BD00FA5F75 /* Profile */ = {
-			isa = XCBuildConfiguration;
-			baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
-			buildSettings = {
-				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
-				CLANG_ENABLE_MODULES = YES;
-				CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
-				CODE_SIGN_STYLE = Automatic;
-				COMBINE_HIDPI_IMAGES = YES;
-				FRAMEWORK_SEARCH_PATHS = (
-					"$(inherited)",
-					"$(PROJECT_DIR)/Flutter/ephemeral",
-				);
-				INFOPLIST_FILE = Runner/Info.plist;
-				LD_RUNPATH_SEARCH_PATHS = (
-					"$(inherited)",
-					"@executable_path/../Frameworks",
-				);
-				PROVISIONING_PROFILE_SPECIFIER = "";
-				SWIFT_VERSION = 5.0;
-			};
-			name = Profile;
-		};
-		338D0CEB231458BD00FA5F75 /* Profile */ = {
-			isa = XCBuildConfiguration;
-			buildSettings = {
-				CODE_SIGN_STYLE = Manual;
-				PRODUCT_NAME = "$(TARGET_NAME)";
-			};
-			name = Profile;
-		};
-		33CC10F92044A3C60003C045 /* Debug */ = {
-			isa = XCBuildConfiguration;
-			baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
-			buildSettings = {
-				ALWAYS_SEARCH_USER_PATHS = NO;
-				CLANG_ANALYZER_NONNULL = YES;
-				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
-				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
-				CLANG_CXX_LIBRARY = "libc++";
-				CLANG_ENABLE_MODULES = YES;
-				CLANG_ENABLE_OBJC_ARC = YES;
-				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
-				CLANG_WARN_BOOL_CONVERSION = YES;
-				CLANG_WARN_CONSTANT_CONVERSION = YES;
-				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
-				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
-				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
-				CLANG_WARN_EMPTY_BODY = YES;
-				CLANG_WARN_ENUM_CONVERSION = YES;
-				CLANG_WARN_INFINITE_RECURSION = YES;
-				CLANG_WARN_INT_CONVERSION = YES;
-				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
-				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
-				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
-				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
-				CLANG_WARN_SUSPICIOUS_MOVE = YES;
-				CODE_SIGN_IDENTITY = "-";
-				COPY_PHASE_STRIP = NO;
-				DEBUG_INFORMATION_FORMAT = dwarf;
-				ENABLE_STRICT_OBJC_MSGSEND = YES;
-				ENABLE_TESTABILITY = YES;
-				GCC_C_LANGUAGE_STANDARD = gnu11;
-				GCC_DYNAMIC_NO_PIC = NO;
-				GCC_NO_COMMON_BLOCKS = YES;
-				GCC_OPTIMIZATION_LEVEL = 0;
-				GCC_PREPROCESSOR_DEFINITIONS = (
-					"DEBUG=1",
-					"$(inherited)",
-				);
-				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
-				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
-				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
-				GCC_WARN_UNUSED_FUNCTION = YES;
-				GCC_WARN_UNUSED_VARIABLE = YES;
-				MACOSX_DEPLOYMENT_TARGET = 10.11;
-				MTL_ENABLE_DEBUG_INFO = YES;
-				ONLY_ACTIVE_ARCH = YES;
-				SDKROOT = macosx;
-				SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
-				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
-			};
-			name = Debug;
-		};
-		33CC10FA2044A3C60003C045 /* Release */ = {
-			isa = XCBuildConfiguration;
-			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
-			buildSettings = {
-				ALWAYS_SEARCH_USER_PATHS = NO;
-				CLANG_ANALYZER_NONNULL = YES;
-				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
-				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
-				CLANG_CXX_LIBRARY = "libc++";
-				CLANG_ENABLE_MODULES = YES;
-				CLANG_ENABLE_OBJC_ARC = YES;
-				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
-				CLANG_WARN_BOOL_CONVERSION = YES;
-				CLANG_WARN_CONSTANT_CONVERSION = YES;
-				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
-				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
-				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
-				CLANG_WARN_EMPTY_BODY = YES;
-				CLANG_WARN_ENUM_CONVERSION = YES;
-				CLANG_WARN_INFINITE_RECURSION = YES;
-				CLANG_WARN_INT_CONVERSION = YES;
-				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
-				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
-				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
-				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
-				CLANG_WARN_SUSPICIOUS_MOVE = YES;
-				CODE_SIGN_IDENTITY = "-";
-				COPY_PHASE_STRIP = NO;
-				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
-				ENABLE_NS_ASSERTIONS = NO;
-				ENABLE_STRICT_OBJC_MSGSEND = YES;
-				GCC_C_LANGUAGE_STANDARD = gnu11;
-				GCC_NO_COMMON_BLOCKS = YES;
-				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
-				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
-				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
-				GCC_WARN_UNUSED_FUNCTION = YES;
-				GCC_WARN_UNUSED_VARIABLE = YES;
-				MACOSX_DEPLOYMENT_TARGET = 10.11;
-				MTL_ENABLE_DEBUG_INFO = NO;
-				SDKROOT = macosx;
-				SWIFT_COMPILATION_MODE = wholemodule;
-				SWIFT_OPTIMIZATION_LEVEL = "-O";
-			};
-			name = Release;
-		};
-		33CC10FC2044A3C60003C045 /* Debug */ = {
-			isa = XCBuildConfiguration;
-			baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
-			buildSettings = {
-				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
-				CLANG_ENABLE_MODULES = YES;
-				CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
-				CODE_SIGN_STYLE = Automatic;
-				COMBINE_HIDPI_IMAGES = YES;
-				FRAMEWORK_SEARCH_PATHS = (
-					"$(inherited)",
-					"$(PROJECT_DIR)/Flutter/ephemeral",
-				);
-				INFOPLIST_FILE = Runner/Info.plist;
-				LD_RUNPATH_SEARCH_PATHS = (
-					"$(inherited)",
-					"@executable_path/../Frameworks",
-				);
-				PROVISIONING_PROFILE_SPECIFIER = "";
-				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
-				SWIFT_VERSION = 5.0;
-			};
-			name = Debug;
-		};
-		33CC10FD2044A3C60003C045 /* Release */ = {
-			isa = XCBuildConfiguration;
-			baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
-			buildSettings = {
-				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
-				CLANG_ENABLE_MODULES = YES;
-				CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
-				CODE_SIGN_STYLE = Automatic;
-				COMBINE_HIDPI_IMAGES = YES;
-				FRAMEWORK_SEARCH_PATHS = (
-					"$(inherited)",
-					"$(PROJECT_DIR)/Flutter/ephemeral",
-				);
-				INFOPLIST_FILE = Runner/Info.plist;
-				LD_RUNPATH_SEARCH_PATHS = (
-					"$(inherited)",
-					"@executable_path/../Frameworks",
-				);
-				PROVISIONING_PROFILE_SPECIFIER = "";
-				SWIFT_VERSION = 5.0;
-			};
-			name = Release;
-		};
-		33CC111C2044C6BA0003C045 /* Debug */ = {
-			isa = XCBuildConfiguration;
-			buildSettings = {
-				CODE_SIGN_STYLE = Manual;
-				PRODUCT_NAME = "$(TARGET_NAME)";
-			};
-			name = Debug;
-		};
-		33CC111D2044C6BA0003C045 /* Release */ = {
-			isa = XCBuildConfiguration;
-			buildSettings = {
-				CODE_SIGN_STYLE = Automatic;
-				PRODUCT_NAME = "$(TARGET_NAME)";
-			};
-			name = Release;
-		};
-/* End XCBuildConfiguration section */
-
-/* Begin XCConfigurationList section */
-		33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = {
-			isa = XCConfigurationList;
-			buildConfigurations = (
-				33CC10F92044A3C60003C045 /* Debug */,
-				33CC10FA2044A3C60003C045 /* Release */,
-				338D0CE9231458BD00FA5F75 /* Profile */,
-			);
-			defaultConfigurationIsVisible = 0;
-			defaultConfigurationName = Release;
-		};
-		33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = {
-			isa = XCConfigurationList;
-			buildConfigurations = (
-				33CC10FC2044A3C60003C045 /* Debug */,
-				33CC10FD2044A3C60003C045 /* Release */,
-				338D0CEA231458BD00FA5F75 /* Profile */,
-			);
-			defaultConfigurationIsVisible = 0;
-			defaultConfigurationName = Release;
-		};
-		33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = {
-			isa = XCConfigurationList;
-			buildConfigurations = (
-				33CC111C2044C6BA0003C045 /* Debug */,
-				33CC111D2044C6BA0003C045 /* Release */,
-				338D0CEB231458BD00FA5F75 /* Profile */,
-			);
-			defaultConfigurationIsVisible = 0;
-			defaultConfigurationName = Release;
-		};
-/* End XCConfigurationList section */
-	};
-	rootObject = 33CC10E52044A3C60003C045 /* Project object */;
-}
diff --git a/connectivity_macos/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/connectivity_macos/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
deleted file mode 100644
index 2a7d3e7..0000000
--- a/connectivity_macos/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ /dev/null
@@ -1,101 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<Scheme
-   LastUpgradeVersion = "1000"
-   version = "1.3">
-   <BuildAction
-      parallelizeBuildables = "YES"
-      buildImplicitDependencies = "YES">
-      <BuildActionEntries>
-         <BuildActionEntry
-            buildForTesting = "YES"
-            buildForRunning = "YES"
-            buildForProfiling = "YES"
-            buildForArchiving = "YES"
-            buildForAnalyzing = "YES">
-            <BuildableReference
-               BuildableIdentifier = "primary"
-               BlueprintIdentifier = "33CC10EC2044A3C60003C045"
-               BuildableName = "connectivity_example.app"
-               BlueprintName = "Runner"
-               ReferencedContainer = "container:Runner.xcodeproj">
-            </BuildableReference>
-         </BuildActionEntry>
-      </BuildActionEntries>
-   </BuildAction>
-   <TestAction
-      buildConfiguration = "Debug"
-      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
-      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
-      shouldUseLaunchSchemeArgsEnv = "YES">
-      <Testables>
-         <TestableReference
-            skipped = "NO">
-            <BuildableReference
-               BuildableIdentifier = "primary"
-               BlueprintIdentifier = "00380F9121DF178D00097171"
-               BuildableName = "RunnerUITests.xctest"
-               BlueprintName = "RunnerUITests"
-               ReferencedContainer = "container:Runner.xcodeproj">
-            </BuildableReference>
-         </TestableReference>
-      </Testables>
-      <MacroExpansion>
-         <BuildableReference
-            BuildableIdentifier = "primary"
-            BlueprintIdentifier = "33CC10EC2044A3C60003C045"
-            BuildableName = "connectivity_example.app"
-            BlueprintName = "Runner"
-            ReferencedContainer = "container:Runner.xcodeproj">
-         </BuildableReference>
-      </MacroExpansion>
-      <AdditionalOptions>
-      </AdditionalOptions>
-   </TestAction>
-   <LaunchAction
-      buildConfiguration = "Debug"
-      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
-      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
-      launchStyle = "0"
-      useCustomWorkingDirectory = "NO"
-      ignoresPersistentStateOnLaunch = "NO"
-      debugDocumentVersioning = "YES"
-      debugServiceExtension = "internal"
-      allowLocationSimulation = "YES">
-      <BuildableProductRunnable
-         runnableDebuggingMode = "0">
-         <BuildableReference
-            BuildableIdentifier = "primary"
-            BlueprintIdentifier = "33CC10EC2044A3C60003C045"
-            BuildableName = "connectivity_example.app"
-            BlueprintName = "Runner"
-            ReferencedContainer = "container:Runner.xcodeproj">
-         </BuildableReference>
-      </BuildableProductRunnable>
-      <AdditionalOptions>
-      </AdditionalOptions>
-   </LaunchAction>
-   <ProfileAction
-      buildConfiguration = "Release"
-      shouldUseLaunchSchemeArgsEnv = "YES"
-      savedToolIdentifier = ""
-      useCustomWorkingDirectory = "NO"
-      debugDocumentVersioning = "YES">
-      <BuildableProductRunnable
-         runnableDebuggingMode = "0">
-         <BuildableReference
-            BuildableIdentifier = "primary"
-            BlueprintIdentifier = "33CC10EC2044A3C60003C045"
-            BuildableName = "connectivity_example.app"
-            BlueprintName = "Runner"
-            ReferencedContainer = "container:Runner.xcodeproj">
-         </BuildableReference>
-      </BuildableProductRunnable>
-   </ProfileAction>
-   <AnalyzeAction
-      buildConfiguration = "Debug">
-   </AnalyzeAction>
-   <ArchiveAction
-      buildConfiguration = "Release"
-      revealArchiveInOrganizer = "YES">
-   </ArchiveAction>
-</Scheme>
diff --git a/connectivity_macos/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/connectivity_macos/example/macos/Runner.xcworkspace/contents.xcworkspacedata
deleted file mode 100644
index 21a3cc1..0000000
--- a/connectivity_macos/example/macos/Runner.xcworkspace/contents.xcworkspacedata
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<Workspace
-   version = "1.0">
-   <FileRef
-      location = "group:Runner.xcodeproj">
-   </FileRef>
-   <FileRef
-      location = "group:Pods/Pods.xcodeproj">
-   </FileRef>
-</Workspace>
diff --git a/connectivity_macos/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/connectivity_macos/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
deleted file mode 100644
index 18d9810..0000000
--- a/connectivity_macos/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-	<key>IDEDidComputeMac32BitWarning</key>
-	<true/>
-</dict>
-</plist>
diff --git a/connectivity_macos/example/macos/Runner/AppDelegate.swift b/connectivity_macos/example/macos/Runner/AppDelegate.swift
deleted file mode 100644
index d53ef64..0000000
--- a/connectivity_macos/example/macos/Runner/AppDelegate.swift
+++ /dev/null
@@ -1,9 +0,0 @@
-import Cocoa
-import FlutterMacOS
-
-@NSApplicationMain
-class AppDelegate: FlutterAppDelegate {
-  override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
-    return true
-  }
-}
diff --git a/connectivity_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/connectivity_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
deleted file mode 100644
index a2ec33f..0000000
--- a/connectivity_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
+++ /dev/null
@@ -1,68 +0,0 @@
-{
-  "images" : [
-    {
-      "size" : "16x16",
-      "idiom" : "mac",
-      "filename" : "app_icon_16.png",
-      "scale" : "1x"
-    },
-    {
-      "size" : "16x16",
-      "idiom" : "mac",
-      "filename" : "app_icon_32.png",
-      "scale" : "2x"
-    },
-    {
-      "size" : "32x32",
-      "idiom" : "mac",
-      "filename" : "app_icon_32.png",
-      "scale" : "1x"
-    },
-    {
-      "size" : "32x32",
-      "idiom" : "mac",
-      "filename" : "app_icon_64.png",
-      "scale" : "2x"
-    },
-    {
-      "size" : "128x128",
-      "idiom" : "mac",
-      "filename" : "app_icon_128.png",
-      "scale" : "1x"
-    },
-    {
-      "size" : "128x128",
-      "idiom" : "mac",
-      "filename" : "app_icon_256.png",
-      "scale" : "2x"
-    },
-    {
-      "size" : "256x256",
-      "idiom" : "mac",
-      "filename" : "app_icon_256.png",
-      "scale" : "1x"
-    },
-    {
-      "size" : "256x256",
-      "idiom" : "mac",
-      "filename" : "app_icon_512.png",
-      "scale" : "2x"
-    },
-    {
-      "size" : "512x512",
-      "idiom" : "mac",
-      "filename" : "app_icon_512.png",
-      "scale" : "1x"
-    },
-    {
-      "size" : "512x512",
-      "idiom" : "mac",
-      "filename" : "app_icon_1024.png",
-      "scale" : "2x"
-    }
-  ],
-  "info" : {
-    "version" : 1,
-    "author" : "xcode"
-  }
-}
diff --git a/connectivity_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/connectivity_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
deleted file mode 100644
index 3c4935a..0000000
--- a/connectivity_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
+++ /dev/null
Binary files differ
diff --git a/connectivity_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/connectivity_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png
deleted file mode 100644
index ed4cc16..0000000
--- a/connectivity_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png
+++ /dev/null
Binary files differ
diff --git a/connectivity_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/connectivity_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
deleted file mode 100644
index 483be61..0000000
--- a/connectivity_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
+++ /dev/null
Binary files differ
diff --git a/connectivity_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/connectivity_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
deleted file mode 100644
index bcbf36d..0000000
--- a/connectivity_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
+++ /dev/null
Binary files differ
diff --git a/connectivity_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/connectivity_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
deleted file mode 100644
index 9c0a652..0000000
--- a/connectivity_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
+++ /dev/null
Binary files differ
diff --git a/connectivity_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/connectivity_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
deleted file mode 100644
index e71a726..0000000
--- a/connectivity_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
+++ /dev/null
Binary files differ
diff --git a/connectivity_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/connectivity_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
deleted file mode 100644
index 8a31fe2..0000000
--- a/connectivity_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
+++ /dev/null
Binary files differ
diff --git a/connectivity_macos/example/macos/Runner/Base.lproj/MainMenu.xib b/connectivity_macos/example/macos/Runner/Base.lproj/MainMenu.xib
deleted file mode 100644
index 537341a..0000000
--- a/connectivity_macos/example/macos/Runner/Base.lproj/MainMenu.xib
+++ /dev/null
@@ -1,339 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
-    <dependencies>
-        <deployment identifier="macosx"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
-        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
-    </dependencies>
-    <objects>
-        <customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
-            <connections>
-                <outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
-            </connections>
-        </customObject>
-        <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
-        <customObject id="-3" userLabel="Application" customClass="NSObject"/>
-        <customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Runner" customModuleProvider="target">
-            <connections>
-                <outlet property="applicationMenu" destination="uQy-DD-JDr" id="XBo-yE-nKs"/>
-                <outlet property="mainFlutterWindow" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/>
-            </connections>
-        </customObject>
-        <customObject id="YLy-65-1bz" customClass="NSFontManager"/>
-        <menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
-            <items>
-                <menuItem title="APP_NAME" id="1Xt-HY-uBw">
-                    <modifierMask key="keyEquivalentModifierMask"/>
-                    <menu key="submenu" title="APP_NAME" systemMenu="apple" id="uQy-DD-JDr">
-                        <items>
-                            <menuItem title="About APP_NAME" id="5kV-Vb-QxS">
-                                <modifierMask key="keyEquivalentModifierMask"/>
-                                <connections>
-                                    <action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
-                                </connections>
-                            </menuItem>
-                            <menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
-                            <menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
-                            <menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
-                            <menuItem title="Services" id="NMo-om-nkz">
-                                <modifierMask key="keyEquivalentModifierMask"/>
-                                <menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
-                            </menuItem>
-                            <menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
-                            <menuItem title="Hide APP_NAME" keyEquivalent="h" id="Olw-nP-bQN">
-                                <connections>
-                                    <action selector="hide:" target="-1" id="PnN-Uc-m68"/>
-                                </connections>
-                            </menuItem>
-                            <menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
-                                <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
-                                <connections>
-                                    <action selector="hideOtherApplications:" target="-1" id="VT4-aY-XCT"/>
-                                </connections>
-                            </menuItem>
-                            <menuItem title="Show All" id="Kd2-mp-pUS">
-                                <modifierMask key="keyEquivalentModifierMask"/>
-                                <connections>
-                                    <action selector="unhideAllApplications:" target="-1" id="Dhg-Le-xox"/>
-                                </connections>
-                            </menuItem>
-                            <menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
-                            <menuItem title="Quit APP_NAME" keyEquivalent="q" id="4sb-4s-VLi">
-                                <connections>
-                                    <action selector="terminate:" target="-1" id="Te7-pn-YzF"/>
-                                </connections>
-                            </menuItem>
-                        </items>
-                    </menu>
-                </menuItem>
-                <menuItem title="Edit" id="5QF-Oa-p0T">
-                    <modifierMask key="keyEquivalentModifierMask"/>
-                    <menu key="submenu" title="Edit" id="W48-6f-4Dl">
-                        <items>
-                            <menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
-                                <connections>
-                                    <action selector="undo:" target="-1" id="M6e-cu-g7V"/>
-                                </connections>
-                            </menuItem>
-                            <menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
-                                <connections>
-                                    <action selector="redo:" target="-1" id="oIA-Rs-6OD"/>
-                                </connections>
-                            </menuItem>
-                            <menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
-                            <menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
-                                <connections>
-                                    <action selector="cut:" target="-1" id="YJe-68-I9s"/>
-                                </connections>
-                            </menuItem>
-                            <menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
-                                <connections>
-                                    <action selector="copy:" target="-1" id="G1f-GL-Joy"/>
-                                </connections>
-                            </menuItem>
-                            <menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
-                                <connections>
-                                    <action selector="paste:" target="-1" id="UvS-8e-Qdg"/>
-                                </connections>
-                            </menuItem>
-                            <menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
-                                <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
-                                <connections>
-                                    <action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/>
-                                </connections>
-                            </menuItem>
-                            <menuItem title="Delete" id="pa3-QI-u2k">
-                                <modifierMask key="keyEquivalentModifierMask"/>
-                                <connections>
-                                    <action selector="delete:" target="-1" id="0Mk-Ml-PaM"/>
-                                </connections>
-                            </menuItem>
-                            <menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
-                                <connections>
-                                    <action selector="selectAll:" target="-1" id="VNm-Mi-diN"/>
-                                </connections>
-                            </menuItem>
-                            <menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
-                            <menuItem title="Find" id="4EN-yA-p0u">
-                                <modifierMask key="keyEquivalentModifierMask"/>
-                                <menu key="submenu" title="Find" id="1b7-l0-nxx">
-                                    <items>
-                                        <menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
-                                            <connections>
-                                                <action selector="performFindPanelAction:" target="-1" id="cD7-Qs-BN4"/>
-                                            </connections>
-                                        </menuItem>
-                                        <menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
-                                            <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
-                                            <connections>
-                                                <action selector="performFindPanelAction:" target="-1" id="WD3-Gg-5AJ"/>
-                                            </connections>
-                                        </menuItem>
-                                        <menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
-                                            <connections>
-                                                <action selector="performFindPanelAction:" target="-1" id="NDo-RZ-v9R"/>
-                                            </connections>
-                                        </menuItem>
-                                        <menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
-                                            <connections>
-                                                <action selector="performFindPanelAction:" target="-1" id="HOh-sY-3ay"/>
-                                            </connections>
-                                        </menuItem>
-                                        <menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
-                                            <connections>
-                                                <action selector="performFindPanelAction:" target="-1" id="U76-nv-p5D"/>
-                                            </connections>
-                                        </menuItem>
-                                        <menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
-                                            <connections>
-                                                <action selector="centerSelectionInVisibleArea:" target="-1" id="IOG-6D-g5B"/>
-                                            </connections>
-                                        </menuItem>
-                                    </items>
-                                </menu>
-                            </menuItem>
-                            <menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
-                                <modifierMask key="keyEquivalentModifierMask"/>
-                                <menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
-                                    <items>
-                                        <menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
-                                            <connections>
-                                                <action selector="showGuessPanel:" target="-1" id="vFj-Ks-hy3"/>
-                                            </connections>
-                                        </menuItem>
-                                        <menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
-                                            <connections>
-                                                <action selector="checkSpelling:" target="-1" id="fz7-VC-reM"/>
-                                            </connections>
-                                        </menuItem>
-                                        <menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
-                                        <menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
-                                            <modifierMask key="keyEquivalentModifierMask"/>
-                                            <connections>
-                                                <action selector="toggleContinuousSpellChecking:" target="-1" id="7w6-Qz-0kB"/>
-                                            </connections>
-                                        </menuItem>
-                                        <menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
-                                            <modifierMask key="keyEquivalentModifierMask"/>
-                                            <connections>
-                                                <action selector="toggleGrammarChecking:" target="-1" id="muD-Qn-j4w"/>
-                                            </connections>
-                                        </menuItem>
-                                        <menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
-                                            <modifierMask key="keyEquivalentModifierMask"/>
-                                            <connections>
-                                                <action selector="toggleAutomaticSpellingCorrection:" target="-1" id="2lM-Qi-WAP"/>
-                                            </connections>
-                                        </menuItem>
-                                    </items>
-                                </menu>
-                            </menuItem>
-                            <menuItem title="Substitutions" id="9ic-FL-obx">
-                                <modifierMask key="keyEquivalentModifierMask"/>
-                                <menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
-                                    <items>
-                                        <menuItem title="Show Substitutions" id="z6F-FW-3nz">
-                                            <modifierMask key="keyEquivalentModifierMask"/>
-                                            <connections>
-                                                <action selector="orderFrontSubstitutionsPanel:" target="-1" id="oku-mr-iSq"/>
-                                            </connections>
-                                        </menuItem>
-                                        <menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
-                                        <menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
-                                            <modifierMask key="keyEquivalentModifierMask"/>
-                                            <connections>
-                                                <action selector="toggleSmartInsertDelete:" target="-1" id="3IJ-Se-DZD"/>
-                                            </connections>
-                                        </menuItem>
-                                        <menuItem title="Smart Quotes" id="hQb-2v-fYv">
-                                            <modifierMask key="keyEquivalentModifierMask"/>
-                                            <connections>
-                                                <action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="ptq-xd-QOA"/>
-                                            </connections>
-                                        </menuItem>
-                                        <menuItem title="Smart Dashes" id="rgM-f4-ycn">
-                                            <modifierMask key="keyEquivalentModifierMask"/>
-                                            <connections>
-                                                <action selector="toggleAutomaticDashSubstitution:" target="-1" id="oCt-pO-9gS"/>
-                                            </connections>
-                                        </menuItem>
-                                        <menuItem title="Smart Links" id="cwL-P1-jid">
-                                            <modifierMask key="keyEquivalentModifierMask"/>
-                                            <connections>
-                                                <action selector="toggleAutomaticLinkDetection:" target="-1" id="Gip-E3-Fov"/>
-                                            </connections>
-                                        </menuItem>
-                                        <menuItem title="Data Detectors" id="tRr-pd-1PS">
-                                            <modifierMask key="keyEquivalentModifierMask"/>
-                                            <connections>
-                                                <action selector="toggleAutomaticDataDetection:" target="-1" id="R1I-Nq-Kbl"/>
-                                            </connections>
-                                        </menuItem>
-                                        <menuItem title="Text Replacement" id="HFQ-gK-NFA">
-                                            <modifierMask key="keyEquivalentModifierMask"/>
-                                            <connections>
-                                                <action selector="toggleAutomaticTextReplacement:" target="-1" id="DvP-Fe-Py6"/>
-                                            </connections>
-                                        </menuItem>
-                                    </items>
-                                </menu>
-                            </menuItem>
-                            <menuItem title="Transformations" id="2oI-Rn-ZJC">
-                                <modifierMask key="keyEquivalentModifierMask"/>
-                                <menu key="submenu" title="Transformations" id="c8a-y6-VQd">
-                                    <items>
-                                        <menuItem title="Make Upper Case" id="vmV-6d-7jI">
-                                            <modifierMask key="keyEquivalentModifierMask"/>
-                                            <connections>
-                                                <action selector="uppercaseWord:" target="-1" id="sPh-Tk-edu"/>
-                                            </connections>
-                                        </menuItem>
-                                        <menuItem title="Make Lower Case" id="d9M-CD-aMd">
-                                            <modifierMask key="keyEquivalentModifierMask"/>
-                                            <connections>
-                                                <action selector="lowercaseWord:" target="-1" id="iUZ-b5-hil"/>
-                                            </connections>
-                                        </menuItem>
-                                        <menuItem title="Capitalize" id="UEZ-Bs-lqG">
-                                            <modifierMask key="keyEquivalentModifierMask"/>
-                                            <connections>
-                                                <action selector="capitalizeWord:" target="-1" id="26H-TL-nsh"/>
-                                            </connections>
-                                        </menuItem>
-                                    </items>
-                                </menu>
-                            </menuItem>
-                            <menuItem title="Speech" id="xrE-MZ-jX0">
-                                <modifierMask key="keyEquivalentModifierMask"/>
-                                <menu key="submenu" title="Speech" id="3rS-ZA-NoH">
-                                    <items>
-                                        <menuItem title="Start Speaking" id="Ynk-f8-cLZ">
-                                            <modifierMask key="keyEquivalentModifierMask"/>
-                                            <connections>
-                                                <action selector="startSpeaking:" target="-1" id="654-Ng-kyl"/>
-                                            </connections>
-                                        </menuItem>
-                                        <menuItem title="Stop Speaking" id="Oyz-dy-DGm">
-                                            <modifierMask key="keyEquivalentModifierMask"/>
-                                            <connections>
-                                                <action selector="stopSpeaking:" target="-1" id="dX8-6p-jy9"/>
-                                            </connections>
-                                        </menuItem>
-                                    </items>
-                                </menu>
-                            </menuItem>
-                        </items>
-                    </menu>
-                </menuItem>
-                <menuItem title="View" id="H8h-7b-M4v">
-                    <modifierMask key="keyEquivalentModifierMask"/>
-                    <menu key="submenu" title="View" id="HyV-fh-RgO">
-                        <items>
-                            <menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa">
-                                <modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
-                                <connections>
-                                    <action selector="toggleFullScreen:" target="-1" id="dU3-MA-1Rq"/>
-                                </connections>
-                            </menuItem>
-                        </items>
-                    </menu>
-                </menuItem>
-                <menuItem title="Window" id="aUF-d1-5bR">
-                    <modifierMask key="keyEquivalentModifierMask"/>
-                    <menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
-                        <items>
-                            <menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV">
-                                <connections>
-                                    <action selector="performMiniaturize:" target="-1" id="VwT-WD-YPe"/>
-                                </connections>
-                            </menuItem>
-                            <menuItem title="Zoom" id="R4o-n2-Eq4">
-                                <modifierMask key="keyEquivalentModifierMask"/>
-                                <connections>
-                                    <action selector="performZoom:" target="-1" id="DIl-cC-cCs"/>
-                                </connections>
-                            </menuItem>
-                            <menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
-                            <menuItem title="Bring All to Front" id="LE2-aR-0XJ">
-                                <modifierMask key="keyEquivalentModifierMask"/>
-                                <connections>
-                                    <action selector="arrangeInFront:" target="-1" id="DRN-fu-gQh"/>
-                                </connections>
-                            </menuItem>
-                        </items>
-                    </menu>
-                </menuItem>
-            </items>
-            <point key="canvasLocation" x="142" y="-258"/>
-        </menu>
-        <window title="APP_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MainFlutterWindow" customModule="Runner" customModuleProvider="target">
-            <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
-            <rect key="contentRect" x="335" y="390" width="800" height="600"/>
-            <rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/>
-            <view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
-                <rect key="frame" x="0.0" y="0.0" width="800" height="600"/>
-                <autoresizingMask key="autoresizingMask"/>
-            </view>
-        </window>
-    </objects>
-</document>
diff --git a/connectivity_macos/example/macos/Runner/Configs/AppInfo.xcconfig b/connectivity_macos/example/macos/Runner/Configs/AppInfo.xcconfig
deleted file mode 100644
index a951488..0000000
--- a/connectivity_macos/example/macos/Runner/Configs/AppInfo.xcconfig
+++ /dev/null
@@ -1,14 +0,0 @@
-// Application-level settings for the Runner target.
-//
-// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the
-// future. If not, the values below would default to using the project name when this becomes a
-// 'flutter create' template.
-
-// The application's name. By default this is also the title of the Flutter window.
-PRODUCT_NAME = connectivity_example
-
-// The application's bundle identifier
-PRODUCT_BUNDLE_IDENTIFIER = io.flutter.plugins.connectivityExample
-
-// The copyright displayed in application information
-PRODUCT_COPYRIGHT = Copyright © 2019 io.flutter.plugins. All rights reserved.
diff --git a/connectivity_macos/example/macos/Runner/Configs/Debug.xcconfig b/connectivity_macos/example/macos/Runner/Configs/Debug.xcconfig
deleted file mode 100644
index 36b0fd9..0000000
--- a/connectivity_macos/example/macos/Runner/Configs/Debug.xcconfig
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "../../Flutter/Flutter-Debug.xcconfig"
-#include "Warnings.xcconfig"
diff --git a/connectivity_macos/example/macos/Runner/Configs/Release.xcconfig b/connectivity_macos/example/macos/Runner/Configs/Release.xcconfig
deleted file mode 100644
index dff4f49..0000000
--- a/connectivity_macos/example/macos/Runner/Configs/Release.xcconfig
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "../../Flutter/Flutter-Release.xcconfig"
-#include "Warnings.xcconfig"
diff --git a/connectivity_macos/example/macos/Runner/Configs/Warnings.xcconfig b/connectivity_macos/example/macos/Runner/Configs/Warnings.xcconfig
deleted file mode 100644
index 42bcbf4..0000000
--- a/connectivity_macos/example/macos/Runner/Configs/Warnings.xcconfig
+++ /dev/null
@@ -1,13 +0,0 @@
-WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings
-GCC_WARN_UNDECLARED_SELECTOR = YES
-CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES
-CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE
-CLANG_WARN__DUPLICATE_METHOD_MATCH = YES
-CLANG_WARN_PRAGMA_PACK = YES
-CLANG_WARN_STRICT_PROTOTYPES = YES
-CLANG_WARN_COMMA = YES
-GCC_WARN_STRICT_SELECTOR_MATCH = YES
-CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES
-CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES
-GCC_WARN_SHADOW = YES
-CLANG_WARN_UNREACHABLE_CODE = YES
diff --git a/connectivity_macos/example/macos/Runner/DebugProfile.entitlements b/connectivity_macos/example/macos/Runner/DebugProfile.entitlements
deleted file mode 100644
index dddb8a3..0000000
--- a/connectivity_macos/example/macos/Runner/DebugProfile.entitlements
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-	<key>com.apple.security.app-sandbox</key>
-	<true/>
-	<key>com.apple.security.cs.allow-jit</key>
-	<true/>
-	<key>com.apple.security.network.server</key>
-	<true/>
-</dict>
-</plist>
diff --git a/connectivity_macos/example/macos/Runner/Info.plist b/connectivity_macos/example/macos/Runner/Info.plist
deleted file mode 100644
index 4789daa..0000000
--- a/connectivity_macos/example/macos/Runner/Info.plist
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-	<key>CFBundleDevelopmentRegion</key>
-	<string>$(DEVELOPMENT_LANGUAGE)</string>
-	<key>CFBundleExecutable</key>
-	<string>$(EXECUTABLE_NAME)</string>
-	<key>CFBundleIconFile</key>
-	<string></string>
-	<key>CFBundleIdentifier</key>
-	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
-	<key>CFBundleInfoDictionaryVersion</key>
-	<string>6.0</string>
-	<key>CFBundleName</key>
-	<string>$(PRODUCT_NAME)</string>
-	<key>CFBundlePackageType</key>
-	<string>APPL</string>
-	<key>CFBundleShortVersionString</key>
-	<string>$(FLUTTER_BUILD_NAME)</string>
-	<key>CFBundleVersion</key>
-	<string>$(FLUTTER_BUILD_NUMBER)</string>
-	<key>LSMinimumSystemVersion</key>
-	<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
-	<key>NSHumanReadableCopyright</key>
-	<string>$(PRODUCT_COPYRIGHT)</string>
-	<key>NSMainNibFile</key>
-	<string>MainMenu</string>
-	<key>NSPrincipalClass</key>
-	<string>NSApplication</string>
-</dict>
-</plist>
diff --git a/connectivity_macos/example/macos/Runner/MainFlutterWindow.swift b/connectivity_macos/example/macos/Runner/MainFlutterWindow.swift
deleted file mode 100644
index 2722837..0000000
--- a/connectivity_macos/example/macos/Runner/MainFlutterWindow.swift
+++ /dev/null
@@ -1,15 +0,0 @@
-import Cocoa
-import FlutterMacOS
-
-class MainFlutterWindow: NSWindow {
-  override func awakeFromNib() {
-    let flutterViewController = FlutterViewController.init()
-    let windowFrame = self.frame
-    self.contentViewController = flutterViewController
-    self.setFrame(windowFrame, display: true)
-
-    RegisterGeneratedPlugins(registry: flutterViewController)
-
-    super.awakeFromNib()
-  }
-}
diff --git a/connectivity_macos/example/macos/Runner/Release.entitlements b/connectivity_macos/example/macos/Runner/Release.entitlements
deleted file mode 100644
index 852fa1a..0000000
--- a/connectivity_macos/example/macos/Runner/Release.entitlements
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-	<key>com.apple.security.app-sandbox</key>
-	<true/>
-</dict>
-</plist>
diff --git a/connectivity_macos/example/pubspec.yaml b/connectivity_macos/example/pubspec.yaml
deleted file mode 100644
index 8772500..0000000
--- a/connectivity_macos/example/pubspec.yaml
+++ /dev/null
@@ -1,19 +0,0 @@
-name: connectivity_example
-description: Demonstrates how to use the connectivity plugin.
-
-dependencies:
-  flutter:
-    sdk: flutter
-  connectivity: any
-  connectivity_macos:
-    path: ../
-
-dev_dependencies:
-  flutter_driver:
-    sdk: flutter
-  test: any
-  e2e: ^0.2.0
-  pedantic: ^1.8.0
-
-flutter:
-  uses-material-design: true
diff --git a/connectivity_macos/example/test_driver/test/connectivity_e2e.dart b/connectivity_macos/example/test_driver/test/connectivity_e2e.dart
deleted file mode 100644
index 10c4bda..0000000
--- a/connectivity_macos/example/test_driver/test/connectivity_e2e.dart
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:io';
-import 'package:e2e/e2e.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:connectivity/connectivity.dart';
-
-void main() {
-  E2EWidgetsFlutterBinding.ensureInitialized();
-
-  group('Connectivity test driver', () {
-    Connectivity _connectivity;
-
-    setUpAll(() async {
-      _connectivity = Connectivity();
-    });
-
-    testWidgets('test connectivity result', (WidgetTester tester) async {
-      final ConnectivityResult result = await _connectivity.checkConnectivity();
-      expect(result, isNotNull);
-      switch (result) {
-        case ConnectivityResult.wifi:
-          expect(_connectivity.getWifiName(), completes);
-          expect(_connectivity.getWifiBSSID(), completes);
-          expect((await _connectivity.getWifiIP()), isNotNull);
-          break;
-        default:
-          break;
-      }
-    });
-
-    testWidgets('test location methods, iOS only', (WidgetTester tester) async {
-      if (Platform.isIOS) {
-        expect((await _connectivity.getLocationServiceAuthorization()),
-            LocationAuthorizationStatus.notDetermined);
-      }
-    });
-  });
-}
diff --git a/connectivity_macos/example/test_driver/test/connectivity_e2e_test.dart b/connectivity_macos/example/test_driver/test/connectivity_e2e_test.dart
deleted file mode 100644
index 84b7ae6..0000000
--- a/connectivity_macos/example/test_driver/test/connectivity_e2e_test.dart
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:io';
-import 'package:flutter_driver/flutter_driver.dart';
-
-Future<void> main() async {
-  final FlutterDriver driver = await FlutterDriver.connect();
-  final String result =
-      await driver.requestData(null, timeout: const Duration(minutes: 1));
-  await driver.close();
-  exit(result == 'pass' ? 0 : 1);
-}
diff --git a/connectivity_macos/lib/connectivity_macos.dart b/connectivity_macos/lib/connectivity_macos.dart
deleted file mode 100644
index 7be7b14..0000000
--- a/connectivity_macos/lib/connectivity_macos.dart
+++ /dev/null
@@ -1,3 +0,0 @@
-// Analyze will fail if there is no main.dart file. This file should
-// be removed once an example app has been added to connectivity_macos.
-// https://github.com/flutter/flutter/issues/51007
diff --git a/connectivity_platform_interface/BUILD.gn b/connectivity_platform_interface/BUILD.gn
deleted file mode 100644
index 5541f94..0000000
--- a/connectivity_platform_interface/BUILD.gn
+++ /dev/null
@@ -1,19 +0,0 @@
-# This file is generated by importer.py for connectivity_platform_interface-1.0.3
-
-import("//build/dart/dart_library.gni")
-
-dart_library("connectivity_platform_interface") {
-  package_name = "connectivity_platform_interface"
-
-  # This parameter is left empty as we don't care about analysis or exporting
-  # these sources outside of the tree.
-  sources = []
-
-  disable_analysis = true
-
-  deps = [
-    "//third_party/dart-pkg/pub/plugin_platform_interface",
-    "//third_party/dart-pkg/pub/meta",
-    "//third_party/dart-pkg/git/flutter/packages/flutter",
-  ]
-}
diff --git a/connectivity_platform_interface/CHANGELOG.md b/connectivity_platform_interface/CHANGELOG.md
deleted file mode 100644
index d249985..0000000
--- a/connectivity_platform_interface/CHANGELOG.md
+++ /dev/null
@@ -1,19 +0,0 @@
-## 1.0.3
-
-* Make the pedantic dev_dependency explicit.
-
-## 1.0.2
-
-* Bring ConnectivityResult and LocationAuthorizationStatus enums from the core package.
-* Use the above Enums as return values for ConnectivityPlatformInterface methods.
-* Modify the MethodChannel implementation so it returns the right types.
-* Bring all utility methods, asserts and other logic that is only needed on the MethodChannel implementation from the core package.
-* Bring MethodChannel unit tests from core package.
-
-## 1.0.1
-
-* Fix README.md link.
-
-## 1.0.0
-
-* Initial release.
diff --git a/connectivity_platform_interface/LICENSE b/connectivity_platform_interface/LICENSE
deleted file mode 100644
index 0c91662..0000000
--- a/connectivity_platform_interface/LICENSE
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-//    * Redistributions of source code must retain the above copyright
-// notice, this list of conditions and the following disclaimer.
-//    * Redistributions in binary form must reproduce the above
-// copyright notice, this list of conditions and the following disclaimer
-// in the documentation and/or other materials provided with the
-// distribution.
-//    * Neither the name of Google Inc. nor the names of its
-// contributors may be used to endorse or promote products derived from
-// this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/connectivity_platform_interface/README.md b/connectivity_platform_interface/README.md
deleted file mode 100644
index 76b3931..0000000
--- a/connectivity_platform_interface/README.md
+++ /dev/null
@@ -1,26 +0,0 @@
-# connectivity_platform_interface
-
-A common platform interface for the [`connectivity`][1] plugin.
-
-This interface allows platform-specific implementations of the `connectivity`
-plugin, as well as the plugin itself, to ensure they are supporting the
-same interface.
-
-# Usage
-
-To implement a new platform-specific implementation of `connectivity`, extend
-[`ConnectivityPlatform`][2] with an implementation that performs the
-platform-specific behavior, and when you register your plugin, set the default
-`ConnectivityPlatform` by calling
-`ConnectivityPlatform.instance = MyPlatformConnectivity()`.
-
-# Note on breaking changes
-
-Strongly prefer non-breaking changes (such as adding a method to the interface)
-over breaking changes for this package.
-
-See https://flutter.dev/go/platform-interface-breaking-changes for a discussion
-on why a less-clean interface is preferable to a breaking change.
-
-[1]: ../
-[2]: lib/connectivity_platform_interface.dart
diff --git a/connectivity_platform_interface/lib/connectivity_platform_interface.dart b/connectivity_platform_interface/lib/connectivity_platform_interface.dart
deleted file mode 100644
index cfd9cf6..0000000
--- a/connectivity_platform_interface/lib/connectivity_platform_interface.dart
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:async';
-
-import 'package:plugin_platform_interface/plugin_platform_interface.dart';
-
-import 'src/enums.dart';
-import 'src/method_channel_connectivity.dart';
-
-export 'src/enums.dart';
-
-/// The interface that implementations of connectivity must implement.
-///
-/// Platform implementations should extend this class rather than implement it as `Connectivity`
-/// does not consider newly added methods to be breaking changes. Extending this class
-/// (using `extends`) ensures that the subclass will get the default implementation, while
-/// platform implementations that `implements` this interface will be broken by newly added
-/// [ConnectivityPlatform] methods.
-abstract class ConnectivityPlatform extends PlatformInterface {
-  /// Constructs a ConnectivityPlatform.
-  ConnectivityPlatform() : super(token: _token);
-
-  static final Object _token = Object();
-
-  static ConnectivityPlatform _instance = MethodChannelConnectivity();
-
-  /// The default instance of [ConnectivityPlatform] to use.
-  ///
-  /// Defaults to [MethodChannelConnectivity].
-  static ConnectivityPlatform get instance => _instance;
-
-  /// Platform-specific plugins should set this with their own platform-specific
-  /// class that extends [ConnectivityPlatform] when they register themselves.
-  static set instance(ConnectivityPlatform instance) {
-    PlatformInterface.verifyToken(instance, _token);
-    _instance = instance;
-  }
-
-  /// Checks the connection status of the device.
-  Future<ConnectivityResult> checkConnectivity() {
-    throw UnimplementedError('checkConnectivity() has not been implemented.');
-  }
-
-  /// Returns a Stream of ConnectivityResults changes.
-  Stream<ConnectivityResult> get onConnectivityChanged {
-    throw UnimplementedError(
-        'get onConnectivityChanged has not been implemented.');
-  }
-
-  /// Obtains the wifi name (SSID) of the connected network
-  Future<String> getWifiName() {
-    throw UnimplementedError('getWifiName() has not been implemented.');
-  }
-
-  /// Obtains the wifi BSSID of the connected network.
-  Future<String> getWifiBSSID() {
-    throw UnimplementedError('getWifiBSSID() has not been implemented.');
-  }
-
-  /// Obtains the IP address of the connected wifi network
-  Future<String> getWifiIP() {
-    throw UnimplementedError('getWifiIP() has not been implemented.');
-  }
-
-  /// Request to authorize the location service (Only on iOS).
-  Future<LocationAuthorizationStatus> requestLocationServiceAuthorization(
-      {bool requestAlwaysLocationUsage = false}) {
-    throw UnimplementedError(
-        'requestLocationServiceAuthorization() has not been implemented.');
-  }
-
-  /// Get the current location service authorization (Only on iOS).
-  Future<LocationAuthorizationStatus> getLocationServiceAuthorization() {
-    throw UnimplementedError(
-        'getLocationServiceAuthorization() has not been implemented.');
-  }
-}
diff --git a/connectivity_platform_interface/lib/src/enums.dart b/connectivity_platform_interface/lib/src/enums.dart
deleted file mode 100644
index 9d8cef9..0000000
--- a/connectivity_platform_interface/lib/src/enums.dart
+++ /dev/null
@@ -1,32 +0,0 @@
-/// Connection status check result.
-enum ConnectivityResult {
-  /// WiFi: Device connected via Wi-Fi
-  wifi,
-
-  /// Mobile: Device connected to cellular network
-  mobile,
-
-  /// None: Device not connected to any network
-  none
-}
-
-/// The status of the location service authorization.
-enum LocationAuthorizationStatus {
-  /// The authorization of the location service is not determined.
-  notDetermined,
-
-  /// This app is not authorized to use location.
-  restricted,
-
-  /// User explicitly denied the location service.
-  denied,
-
-  /// User authorized the app to access the location at any time.
-  authorizedAlways,
-
-  /// User authorized the app to access the location when the app is visible to them.
-  authorizedWhenInUse,
-
-  /// Status unknown.
-  unknown
-}
diff --git a/connectivity_platform_interface/lib/src/method_channel_connectivity.dart b/connectivity_platform_interface/lib/src/method_channel_connectivity.dart
deleted file mode 100644
index 7a64115..0000000
--- a/connectivity_platform_interface/lib/src/method_channel_connectivity.dart
+++ /dev/null
@@ -1,88 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:async';
-import 'dart:io' show Platform;
-
-import 'package:connectivity_platform_interface/connectivity_platform_interface.dart';
-import 'package:flutter/services.dart';
-import 'package:meta/meta.dart';
-
-import 'utils.dart';
-
-/// An implementation of [ConnectivityPlatform] that uses method channels.
-class MethodChannelConnectivity extends ConnectivityPlatform {
-  /// The method channel used to interact with the native platform.
-  @visibleForTesting
-  MethodChannel methodChannel =
-      MethodChannel('plugins.flutter.io/connectivity');
-
-  /// The event channel used to receive ConnectivityResult changes from the native platform.
-  @visibleForTesting
-  EventChannel eventChannel =
-      EventChannel('plugins.flutter.io/connectivity_status');
-
-  Stream<ConnectivityResult> _onConnectivityChanged;
-
-  /// Fires whenever the connectivity state changes.
-  Stream<ConnectivityResult> get onConnectivityChanged {
-    if (_onConnectivityChanged == null) {
-      _onConnectivityChanged = eventChannel
-          .receiveBroadcastStream()
-          .map((dynamic result) => result.toString())
-          .map(parseConnectivityResult);
-    }
-    return _onConnectivityChanged;
-  }
-
-  @override
-  Future<ConnectivityResult> checkConnectivity() {
-    return methodChannel
-        .invokeMethod<String>('check')
-        .then(parseConnectivityResult);
-  }
-
-  @override
-  Future<String> getWifiName() async {
-    String wifiName = await methodChannel.invokeMethod<String>('wifiName');
-    // as Android might return <unknown ssid>, uniforming result
-    // our iOS implementation will return null
-    if (wifiName == '<unknown ssid>') {
-      wifiName = null;
-    }
-    return wifiName;
-  }
-
-  @override
-  Future<String> getWifiBSSID() {
-    return methodChannel.invokeMethod<String>('wifiBSSID');
-  }
-
-  @override
-  Future<String> getWifiIP() {
-    return methodChannel.invokeMethod<String>('wifiIPAddress');
-  }
-
-  @override
-  Future<LocationAuthorizationStatus> requestLocationServiceAuthorization({
-    bool requestAlwaysLocationUsage = false,
-  }) {
-    // `assert(Platform.isIOS)` will prevent us from doing dart side unit testing.
-    // TODO: These should noop for non-Android, instead of throwing, so people don't need to rely on dart:io for this.
-    assert(!Platform.isAndroid);
-    return methodChannel.invokeMethod<String>(
-        'requestLocationServiceAuthorization', <bool>[
-      requestAlwaysLocationUsage
-    ]).then(parseLocationAuthorizationStatus);
-  }
-
-  @override
-  Future<LocationAuthorizationStatus> getLocationServiceAuthorization() {
-    // `assert(Platform.isIOS)` will prevent us from doing dart side unit testing.
-    assert(!Platform.isAndroid);
-    return methodChannel
-        .invokeMethod<String>('getLocationServiceAuthorization')
-        .then(parseLocationAuthorizationStatus);
-  }
-}
diff --git a/connectivity_platform_interface/lib/src/utils.dart b/connectivity_platform_interface/lib/src/utils.dart
deleted file mode 100644
index 2ae22e1..0000000
--- a/connectivity_platform_interface/lib/src/utils.dart
+++ /dev/null
@@ -1,32 +0,0 @@
-import 'package:connectivity_platform_interface/connectivity_platform_interface.dart';
-
-/// Convert a String to a ConnectivityResult value.
-ConnectivityResult parseConnectivityResult(String state) {
-  switch (state) {
-    case 'wifi':
-      return ConnectivityResult.wifi;
-    case 'mobile':
-      return ConnectivityResult.mobile;
-    case 'none':
-    default:
-      return ConnectivityResult.none;
-  }
-}
-
-/// Convert a String to a LocationAuthorizationStatus value.
-LocationAuthorizationStatus parseLocationAuthorizationStatus(String result) {
-  switch (result) {
-    case 'notDetermined':
-      return LocationAuthorizationStatus.notDetermined;
-    case 'restricted':
-      return LocationAuthorizationStatus.restricted;
-    case 'denied':
-      return LocationAuthorizationStatus.denied;
-    case 'authorizedAlways':
-      return LocationAuthorizationStatus.authorizedAlways;
-    case 'authorizedWhenInUse':
-      return LocationAuthorizationStatus.authorizedWhenInUse;
-    default:
-      return LocationAuthorizationStatus.unknown;
-  }
-}
diff --git a/connectivity_platform_interface/pubspec.yaml b/connectivity_platform_interface/pubspec.yaml
deleted file mode 100644
index 78f9473..0000000
--- a/connectivity_platform_interface/pubspec.yaml
+++ /dev/null
@@ -1,21 +0,0 @@
-name: connectivity_platform_interface
-description: A common platform interface for the connectivity plugin.
-homepage: https://github.com/flutter/plugins/tree/master/packages/connectivity/connectivity_platform_interface
-# NOTE: We strongly prefer non-breaking changes, even at the expense of a
-# less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes
-version: 1.0.3
-
-dependencies:
-  flutter:
-    sdk: flutter
-  meta: ^1.0.5
-  plugin_platform_interface: ^1.0.1
-
-dev_dependencies:
-  flutter_test:
-    sdk: flutter
-  pedantic: ^1.8.0
-
-environment:
-  sdk: ">=2.0.0-dev.28.0 <3.0.0"
-  flutter: ">=1.10.0 <2.0.0"
diff --git a/coverage/.travis.yml b/coverage/.travis.yml
index 5cccfe5..9ce0790 100644
--- a/coverage/.travis.yml
+++ b/coverage/.travis.yml
@@ -1,7 +1,7 @@
 language: dart
 
 dart:
-  - 2.7.0
+  - 2.6.0
   - dev
 
 install:
diff --git a/coverage/BUILD.gn b/coverage/BUILD.gn
index 9b05ac8..e7f6d83 100644
--- a/coverage/BUILD.gn
+++ b/coverage/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by importer.py for coverage-0.13.8
+# This file is generated by importer.py for coverage-0.13.6
 
 import("//build/dart/dart_library.gni")
 
diff --git a/coverage/CHANGELOG.md b/coverage/CHANGELOG.md
index c3d28d2..b97c52b 100644
--- a/coverage/CHANGELOG.md
+++ b/coverage/CHANGELOG.md
@@ -1,14 +1,3 @@
-## 0.13.8 - 2020-03-02
-
-* Update to package_config `1.9.0` which supports package_config.json
-  files and should be forwards compatible with `2.0.0`.
-* Deprecate the `packageRoot` argument on `Resolver`.
-
-## 0.13.7 - 2020-02-28
-
-* Loosen the dependency on the `vm_service` package from `>=1.0.0 <3.0.0` to
-`>=1.0.0 <4.0.0`.
-
 ## 0.13.6 - 2020-02-10
 
 * Now consider all `.json` files for the `format_coverage` command.
diff --git a/coverage/bin/format_coverage.dart b/coverage/bin/format_coverage.dart
index 3e2b659..0b15899 100644
--- a/coverage/bin/format_coverage.dart
+++ b/coverage/bin/format_coverage.dart
@@ -54,7 +54,6 @@
       ? BazelResolver(workspacePath: env.bazelWorkspace)
       : Resolver(
           packagesPath: env.packagesPath,
-          // ignore_for_file: deprecated_member_use_from_same_package
           packageRoot: env.pkgRoot,
           sdkRoot: env.sdkRoot);
   final loader = Loader();
diff --git a/coverage/lib/src/collect.dart b/coverage/lib/src/collect.dart
index 768e2f8..f44b030 100644
--- a/coverage/lib/src/collect.dart
+++ b/coverage/lib/src/collect.dart
@@ -51,10 +51,7 @@
       final options = const CompressionOptions(enabled: false);
       final socket = await WebSocket.connect('$uri', compression: options);
       final controller = StreamController<String>();
-      socket.listen((data) => controller.add(data as String), onDone: () {
-        controller.close();
-        service.dispose();
-      });
+      socket.listen((data) => controller.add(data as String));
       service = VmService(
           controller.stream, (String message) => socket.add(message),
           log: StdoutLog(), disposeHandler: () => socket.close());
@@ -89,7 +86,7 @@
     if (isolateIds != null && !isolateIds.contains(isolateRef.id)) continue;
     if (scopedOutput.isNotEmpty) {
       final scripts = await service.getScripts(isolateRef.id);
-      for (ScriptRef script in scripts.scripts) {
+      for (var script in scripts.scripts) {
         final uri = Uri.parse(script.uri);
         if (uri.scheme != 'package') continue;
         final scope = uri.path.split('/').first;
@@ -97,7 +94,7 @@
         if (!scopedOutput.contains(scope)) continue;
         final scriptReport = await service.getSourceReport(
             isolateRef.id, <String>[SourceReportKind.kCoverage],
-            forceCompile: true, scriptId: script.id) as SourceReport;
+            forceCompile: true, scriptId: script.id);
         final coverage = await _getCoverageJson(
             service, isolateRef, scriptReport, includeDart);
         allCoverage.addAll(coverage);
@@ -107,7 +104,7 @@
         isolateRef.id,
         <String>[SourceReportKind.kCoverage],
         forceCompile: true,
-      ) as SourceReport;
+      );
       final coverage = await _getCoverageJson(
           service, isolateRef, isolateReport, includeDart);
       allCoverage.addAll(coverage);
@@ -118,22 +115,11 @@
 
 Future _resumeIsolates(VmService service) async {
   final vm = await service.getVM();
-  final futures = <Future>[];
   for (var isolateRef in vm.isolates) {
-    // Guard against sync as well as async errors: sync - when we are writing
-    // message to the socket, the socket might be closed; async - when we are
-    // waiting for the response, the socket again closes.
-    futures.add(Future.sync(() async {
-      final isolate = await service.getIsolate(isolateRef.id) as Isolate;
-      if (isolate.pauseEvent.kind != EventKind.kResume) {
-        await service.resume(isolateRef.id);
-      }
-    }));
-  }
-  try {
-    await Future.wait(futures);
-  } catch (_) {
-    // Ignore resume isolate failures
+    final isolate = await service.getIsolate(isolateRef.id) as Isolate;
+    if (isolate.pauseEvent.kind != EventKind.kResume) {
+      await service.resume(isolateRef.id);
+    }
   }
 }
 
diff --git a/coverage/lib/src/resolver.dart b/coverage/lib/src/resolver.dart
index 03ae6ef..316c66d 100644
--- a/coverage/lib/src/resolver.dart
+++ b/coverage/lib/src/resolver.dart
@@ -3,16 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:convert';
 import 'dart:io';
 
-import 'package:package_config/package_config.dart';
+import 'package:package_config/packages_file.dart' as packages_file;
 import 'package:path/path.dart' as p;
 
 /// [Resolver] resolves imports with respect to a given environment.
 class Resolver {
-  // ignore_for_file: deprecated_member_use_from_same_package
-  Resolver({String packagesPath, @deprecated this.packageRoot, this.sdkRoot})
+  Resolver({String packagesPath, this.packageRoot, this.sdkRoot})
       : packagesPath = packagesPath,
         _packages = packagesPath != null ? _parsePackages(packagesPath) : null;
 
@@ -91,32 +89,8 @@
   }
 
   static Map<String, Uri> _parsePackages(String packagesPath) {
-    final content = File(packagesPath).readAsStringSync();
-    try {
-      final parsed =
-          PackageConfig.parseString(content, Uri.base.resolve(packagesPath));
-      return {
-        for (var package in parsed.packages)
-          package.name: package.packageUriRoot
-      };
-    } on FormatException catch (_) {
-      // It was probably an old style .packages file
-      final lines = LineSplitter.split(content);
-      final packageMap = <String, Uri>{};
-      for (var line in lines) {
-        if (line.startsWith('#')) continue;
-        final firstColon = line.indexOf(':');
-        if (firstColon == -1) {
-          throw FormatException(
-              'Unexpected package config format, expected an old style '
-              '.packages file or new style package_config.json file.',
-              content);
-        }
-        packageMap[line.substring(0, firstColon)] =
-            Uri.parse(line.substring(firstColon + 1, line.length));
-      }
-      return packageMap;
-    }
+    final source = File(packagesPath).readAsBytesSync();
+    return packages_file.parse(source, Uri.file(packagesPath));
   }
 }
 
diff --git a/coverage/pubspec.yaml b/coverage/pubspec.yaml
index 5401403..6757599 100644
--- a/coverage/pubspec.yaml
+++ b/coverage/pubspec.yaml
@@ -1,24 +1,23 @@
 name: coverage
-version: 0.13.8
+version: 0.13.6
 description: Coverage data manipulation and formatting
 homepage: https://github.com/dart-lang/coverage
 
 environment:
-  sdk: '>=2.7.0 <3.0.0'
+  sdk: '>=2.6.0 <3.0.0'
 
 dependencies:
-  args: ^1.4.0
+  args: '>=1.4.0 <2.0.0'
   logging: '>=0.9.0 <0.12.0'
-  package_config: ^1.9.0
+  package_config: '>=0.1.5 <2.0.0'
   path: '>=0.9.0 <2.0.0'
   source_maps: ^0.10.8
   stack_trace: ^1.3.0
-  vm_service: '>=1.0.0 <4.0.0'
+  vm_service: '>=1.0.0 <3.0.0'
 
 dev_dependencies:
   pedantic: ^1.0.0
   test: ^1.0.0
-  test_descriptor: ^1.2.0
 
 executables:
   collect_coverage:
diff --git a/device_info/BUILD.gn b/device_info/BUILD.gn
index ff67c9a..f5a595b 100644
--- a/device_info/BUILD.gn
+++ b/device_info/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by importer.py for device_info-0.4.1+5
+# This file is generated by importer.py for device_info-0.4.1+4
 
 import("//build/dart/dart_library.gni")
 
diff --git a/device_info/CHANGELOG.md b/device_info/CHANGELOG.md
index 00dcd29..13e0bae 100644
--- a/device_info/CHANGELOG.md
+++ b/device_info/CHANGELOG.md
@@ -1,7 +1,3 @@
-## 0.4.1+5
-
-* Make the pedantic dev_dependency explicit.
-
 ## 0.4.1+4
 
 * Remove the deprecated `author:` field from pubspec.yaml
diff --git a/device_info/example/pubspec.yaml b/device_info/example/pubspec.yaml
index 89bf430..148b7b9 100644
--- a/device_info/example/pubspec.yaml
+++ b/device_info/example/pubspec.yaml
@@ -11,7 +11,6 @@
   flutter_driver:
     sdk: flutter
   e2e: ^0.2.0
-  pedantic: ^1.8.0
 
 flutter:
   uses-material-design: true
diff --git a/device_info/pubspec.yaml b/device_info/pubspec.yaml
index b862a7d..452d690 100644
--- a/device_info/pubspec.yaml
+++ b/device_info/pubspec.yaml
@@ -2,7 +2,7 @@
 description: Flutter plugin providing detailed information about the device
   (make, model, etc.), and Android or iOS version the app is running on.
 homepage: https://github.com/flutter/plugins/tree/master/packages/device_info
-version: 0.4.1+5
+version: 0.4.1+4
 
 flutter:
   plugin:
@@ -22,7 +22,6 @@
   flutter_test:
     sdk: flutter
   e2e: ^0.2.0
-  pedantic: ^1.8.0
 
 environment:
   sdk: ">=2.0.0-dev.28.0 <3.0.0"
diff --git a/graphs/.gitignore b/graphs/.gitignore
new file mode 100644
index 0000000..ddfdca1
--- /dev/null
+++ b/graphs/.gitignore
@@ -0,0 +1,5 @@
+.dart_tool/
+.packages
+.pub/
+build/
+pubspec.lock
diff --git a/graphs/.travis.yml b/graphs/.travis.yml
new file mode 100644
index 0000000..3c0743f
--- /dev/null
+++ b/graphs/.travis.yml
@@ -0,0 +1,23 @@
+language: dart
+
+dart:
+  - dev
+  - stable
+
+dart_task:
+  - test
+  - test -p chrome,firefox
+  - dartfmt
+  - dartanalyzer: --fatal-infos --fatal-warnings .
+
+matrix:
+  exclude:
+    - dart: stable
+      dart_task: dartfmt
+
+branches:
+  only: [master]
+
+cache:
+  directories:
+    - $HOME/.pub-cache
diff --git a/mustache_template/BUILD.gn b/graphs/BUILD.gn
similarity index 60%
rename from mustache_template/BUILD.gn
rename to graphs/BUILD.gn
index 0f80569..1258e65 100644
--- a/mustache_template/BUILD.gn
+++ b/graphs/BUILD.gn
@@ -1,9 +1,9 @@
-# This file is generated by importer.py for mustache_template-1.0.0+1
+# This file is generated by importer.py for graphs-0.2.0
 
 import("//build/dart/dart_library.gni")
 
-dart_library("mustache_template") {
-  package_name = "mustache_template"
+dart_library("graphs") {
+  package_name = "graphs"
 
   # This parameter is left empty as we don't care about analysis or exporting
   # these sources outside of the tree.
diff --git a/graphs/CHANGELOG.md b/graphs/CHANGELOG.md
new file mode 100644
index 0000000..78722c4
--- /dev/null
+++ b/graphs/CHANGELOG.md
@@ -0,0 +1,36 @@
+# 0.2.0-dev
+
+- **BREAKING** `shortestPath`, `shortestPaths` and `stronglyConnectedComponents`
+  now have one generic parameter and have replaced the `key` parameter with
+  optional params: `{bool equals(T key1, T key2), int hashCode(T key)}`.
+  This follows the pattern used in `dart:collection` classes `HashMap` and 
+  `LinkedHashMap`. It improves the usability and performance of the case where
+  the source values are directly usable in a hash data structure.
+
+# 0.1.3+1
+
+- Fixed a bug with non-identity `key` in `shortestPath` and `shortestPaths`.
+
+# 0.1.3
+
+- Added `shortestPath` and `shortestPaths` functions.
+- Use `HashMap` and `HashSet` from `dart:collection` for
+  `stronglyConnectedComponents`. Improves runtime performance.
+
+# 0.1.2+1
+
+- Allow using non-dev Dart 2 SDK.
+
+# 0.1.2
+
+- `crawlAsync` surfaces exceptions while crawling through the result stream
+  rather than as uncaught asynchronous errors.
+
+# 0.1.1
+
+- `crawlAsync` will now ignore nodes that are resolved to `null`.
+
+# 0.1.0
+
+- Initial release with an implementation of `stronglyConnectedComponents` and
+  `crawlAsync`.
diff --git a/graphs/CONTRIBUTING.md b/graphs/CONTRIBUTING.md
new file mode 100644
index 0000000..286d61c
--- /dev/null
+++ b/graphs/CONTRIBUTING.md
@@ -0,0 +1,33 @@
+Want to contribute? Great! First, read this page (including the small print at
+the end).
+
+### Before you contribute
+Before we can use your code, you must sign the
+[Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual)
+(CLA), which you can do online. The CLA is necessary mainly because you own the
+copyright to your changes, even after your contribution becomes part of our
+codebase, so we need your permission to use and distribute your code. We also
+need to be sure of various other things—for instance that you'll tell us if you
+know that your code infringes on other people's patents. You don't have to sign
+the CLA until after you've submitted your code for review and a member has
+approved it, but you must do it before we can put your code into our codebase.
+
+Before you start working on a larger contribution, you should get in touch with
+us first through the issue tracker with your idea so that we can help out and
+possibly guide you. Coordinating up front makes it much easier to avoid
+frustration later on.
+
+### Code reviews
+All submissions, including submissions by project members, require review.
+
+### File headers
+All files in the project must start with the following header.
+
+    // Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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.
+
+### The small print
+Contributions made by corporations are covered by a different agreement than the
+one above, the
+[Software Grant and Corporate Contributor License Agreement](https://developers.google.com/open-source/cla/corporate).
diff --git a/graphs/LICENSE b/graphs/LICENSE
new file mode 100644
index 0000000..389ce98
--- /dev/null
+++ b/graphs/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2017, the Dart project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of Google Inc. nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/graphs/README.md b/graphs/README.md
new file mode 100644
index 0000000..0edaf92
--- /dev/null
+++ b/graphs/README.md
@@ -0,0 +1,41 @@
+# [![Build Status](https://travis-ci.org/dart-lang/graphs.svg?branch=master)](https://travis-ci.org/dart-lang/graphs)
+
+Graph algorithms which do not specify a particular approach for representing a
+Graph.
+
+Functions in this package will take arguments that provide the mechanism for
+traversing the graph. For example two common approaches for representing a
+graph:
+
+```dart
+class Graph {
+  Map<Node, List<Node>> nodes;
+}
+class Node {
+  // Interesting data
+}
+```
+
+```dart
+class Graph {
+  Node root;
+}
+class Node {
+  List<Node> edges;
+  // Interesting data
+}
+```
+
+Any representation can be adapted to the needs of the algorithm:
+
+- Some algorithms need to associate data with each node in the graph. If the
+  node type `T` does not correctly or efficiently implement `hashCode` or `==`,
+  you may provide optional `equals` and/or `hashCode` functions are parameters.
+- Algorithms which need to traverse the graph take a `edges` function which
+  provides the reachable nodes.
+  - `(node) => graph[node]`
+  - `(node) => node.edges`
+
+
+Graphs which are resolved asynchronously will have similar functions which
+return `FutureOr`.
diff --git a/graphs/analysis_options.yaml b/graphs/analysis_options.yaml
new file mode 100644
index 0000000..6e159f3
--- /dev/null
+++ b/graphs/analysis_options.yaml
@@ -0,0 +1,66 @@
+include: package:pedantic/analysis_options.yaml
+analyzer:
+  strong-mode:
+    implicit-casts: false
+  errors:
+    unused_element: error
+    unused_import: error
+    unused_local_variable: error
+    dead_code: error
+linter:
+  rules:
+    - annotate_overrides
+    - avoid_function_literals_in_foreach_calls
+    - avoid_init_to_null
+    - avoid_null_checks_in_equality_operators
+    - avoid_relative_lib_imports
+    - avoid_returning_null
+    - avoid_unused_constructor_parameters
+    - await_only_futures
+    - camel_case_types
+    - cancel_subscriptions
+    - comment_references
+    - constant_identifier_names
+    - control_flow_in_finally
+    - directives_ordering
+    - empty_catches
+    - empty_constructor_bodies
+    - empty_statements
+    - hash_and_equals
+    - implementation_imports
+    - invariant_booleans
+    - iterable_contains_unrelated_type
+    - library_names
+    - library_prefixes
+    - list_remove_unrelated_type
+    - no_adjacent_strings_in_list
+    - non_constant_identifier_names
+    - omit_local_variable_types
+    - only_throw_errors
+    - overridden_fields
+    - package_api_docs
+    - package_names
+    - package_prefixed_library_names
+    - prefer_adjacent_string_concatenation
+    - prefer_collection_literals
+    - prefer_conditional_assignment
+    - prefer_const_constructors
+    - prefer_final_fields
+    - prefer_initializing_formals
+    - prefer_interpolation_to_compose_strings
+    - prefer_single_quotes
+    - prefer_typing_uninitialized_variables
+    - slash_for_doc_comments
+    - test_types_in_equals
+    - super_goes_last
+    - test_types_in_equals
+    - throw_in_finally
+    - type_init_formals
+    - unnecessary_brace_in_string_interps
+    - unnecessary_const
+    - unnecessary_getters_setters
+    - unnecessary_lambdas
+    - unnecessary_new
+    - unnecessary_null_aware_assignments
+    - unnecessary_statements
+    - unnecessary_this
diff --git a/graphs/benchmark/connected_components_benchmark.dart b/graphs/benchmark/connected_components_benchmark.dart
new file mode 100644
index 0000000..c1c83d7
--- /dev/null
+++ b/graphs/benchmark/connected_components_benchmark.dart
@@ -0,0 +1,49 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS 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 'dart:collection';
+import 'dart:math' show Random;
+
+import 'package:graphs/graphs.dart';
+
+void main() {
+  final _rnd = Random(0);
+  final size = 2000;
+  final graph = HashMap<int, List<int>>();
+
+  for (var i = 0; i < size * 3; i++) {
+    final toList = graph.putIfAbsent(_rnd.nextInt(size), () => <int>[]);
+
+    final toValue = _rnd.nextInt(size);
+    if (!toList.contains(toValue)) {
+      toList.add(toValue);
+    }
+  }
+
+  var maxCount = 0;
+  var maxIteration = 0;
+
+  final duration = const Duration(milliseconds: 100);
+
+  for (var i = 1;; i++) {
+    var count = 0;
+    final watch = Stopwatch()..start();
+    while (watch.elapsed < duration) {
+      count++;
+      final length =
+          stronglyConnectedComponents(graph.keys, (e) => graph[e] ?? []).length;
+      assert(length == 244, '$length');
+    }
+
+    if (count > maxCount) {
+      maxCount = count;
+      maxIteration = i;
+    }
+
+    if (maxIteration == i || (i - maxIteration) % 20 == 0) {
+      print('max iterations in ${duration.inMilliseconds}ms: $maxCount\t'
+          'after $maxIteration of $i iterations');
+    }
+  }
+}
diff --git a/graphs/benchmark/shortest_path_benchmark.dart b/graphs/benchmark/shortest_path_benchmark.dart
new file mode 100644
index 0000000..813c18b
--- /dev/null
+++ b/graphs/benchmark/shortest_path_benchmark.dart
@@ -0,0 +1,51 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS 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 'dart:collection';
+import 'dart:math' show Random;
+
+import 'package:graphs/graphs.dart';
+
+void main() {
+  final _rnd = Random(1);
+  final size = 1000;
+  final graph = HashMap<int, List<int>>();
+
+  for (var i = 0; i < size * 5; i++) {
+    final toList = graph.putIfAbsent(_rnd.nextInt(size), () => <int>[]);
+
+    final toValue = _rnd.nextInt(size);
+    if (!toList.contains(toValue)) {
+      toList.add(toValue);
+    }
+  }
+
+  int minTicks;
+  var maxIteration = 0;
+
+  final testOutput =
+      shortestPath(0, size - 1, (e) => graph[e] ?? []).toString();
+  print(testOutput);
+  assert(testOutput == '[258, 252, 819, 999]');
+
+  final watch = Stopwatch();
+  for (var i = 1;; i++) {
+    watch
+      ..reset()
+      ..start();
+    final length = shortestPath(0, size - 1, (e) => graph[e] ?? []).length;
+    watch.stop();
+    assert(length == 4, '$length');
+
+    if (minTicks == null || watch.elapsedTicks < minTicks) {
+      minTicks = watch.elapsedTicks;
+      maxIteration = i;
+    }
+
+    if (maxIteration == i || (i - maxIteration) % 100000 == 0) {
+      print('min ticks for one run: $minTicks\t'
+          'after $maxIteration of $i iterations');
+    }
+  }
+}
diff --git a/graphs/example/crawl_async_example.dart b/graphs/example/crawl_async_example.dart
new file mode 100644
index 0000000..959b894
--- /dev/null
+++ b/graphs/example/crawl_async_example.dart
@@ -0,0 +1,82 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:isolate';
+
+import 'package:analyzer/dart/analysis/analysis_context.dart';
+import 'package:analyzer/dart/analysis/context_builder.dart';
+import 'package:analyzer/dart/analysis/context_locator.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:graphs/graphs.dart';
+import 'package:path/path.dart' as p;
+
+/// Print a transitive set of imported URIs where libraries are read
+/// asynchronously.
+Future<Null> main() async {
+  var allImports = await crawlAsync(
+          [Uri.parse('package:graphs/graphs.dart')], read, findImports)
+      .toList();
+  print(allImports.map((s) => s.uri).toList());
+}
+
+AnalysisContext _analysisContext;
+
+Future<AnalysisContext> get analysisContext async {
+  if (_analysisContext == null) {
+    var libUri = Uri.parse('package:graphs/');
+    var libPath = await pathForUri(libUri);
+    var packagePath = p.dirname(libPath);
+
+    var roots = ContextLocator().locateRoots(includedPaths: [packagePath]);
+    if (roots.length != 1) {
+      throw StateError('Expected to find exactly one context root, got $roots');
+    }
+
+    _analysisContext = ContextBuilder().createContext(contextRoot: roots[0]);
+  }
+
+  return _analysisContext;
+}
+
+Future<Iterable<Uri>> findImports(Uri from, Source source) async {
+  return source.unit.directives
+      .whereType<UriBasedDirective>()
+      .map((d) => d.uri.stringValue)
+      .where((uri) => !uri.startsWith('dart:'))
+      .map((import) => resolveImport(import, from));
+}
+
+Future<CompilationUnit> parseUri(Uri uri) async {
+  var path = await pathForUri(uri);
+  var analysisSession = (await analysisContext).currentSession;
+  var parseResult = analysisSession.getParsedUnit(path);
+  return parseResult.unit;
+}
+
+Future<String> pathForUri(Uri uri) async {
+  var fileUri = await Isolate.resolvePackageUri(uri);
+  if (fileUri == null || !fileUri.isScheme('file')) {
+    throw StateError('Expected to resolve $uri to a file URI, got $fileUri');
+  }
+  return p.fromUri(fileUri);
+}
+
+Future<Source> read(Uri uri) async => Source(uri, await parseUri(uri));
+
+Uri resolveImport(String import, Uri from) {
+  if (import.startsWith('package:')) return Uri.parse(import);
+  assert(from.scheme == 'package');
+  final package = from.pathSegments.first;
+  final fromPath = p.joinAll(from.pathSegments.skip(1));
+  final path = p.normalize(p.join(p.dirname(fromPath), import));
+  return Uri.parse('package:${p.join(package, path)}');
+}
+
+class Source {
+  final Uri uri;
+  final CompilationUnit unit;
+
+  Source(this.uri, this.unit);
+}
diff --git a/graphs/example/example.dart b/graphs/example/example.dart
new file mode 100644
index 0000000..47aca61
--- /dev/null
+++ b/graphs/example/example.dart
@@ -0,0 +1,47 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'package:graphs/graphs.dart';
+
+/// A representation of a directed graph.
+///
+/// Data is stored on the [Node] class.
+class Graph {
+  final Map<Node, List<Node>> nodes;
+
+  Graph(this.nodes);
+}
+
+class Node {
+  final String id;
+  final int data;
+
+  Node(this.id, this.data);
+
+  @override
+  bool operator ==(Object other) => other is Node && other.id == id;
+
+  @override
+  int get hashCode => id.hashCode;
+
+  @override
+  String toString() => '<$id -> $data>';
+}
+
+void main() {
+  var nodeA = Node('A', 1);
+  var nodeB = Node('B', 2);
+  var nodeC = Node('C', 3);
+  var nodeD = Node('D', 4);
+  var graph = Graph({
+    nodeA: [nodeB, nodeC],
+    nodeB: [nodeC, nodeD],
+    nodeC: [nodeB, nodeD]
+  });
+
+  var components = stronglyConnectedComponents<Node>(
+      graph.nodes.keys, (node) => graph.nodes[node]);
+
+  print(components);
+}
diff --git a/graphs/lib/graphs.dart b/graphs/lib/graphs.dart
new file mode 100644
index 0000000..a5de89c
--- /dev/null
+++ b/graphs/lib/graphs.dart
@@ -0,0 +1,8 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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.
+
+export 'src/crawl_async.dart' show crawlAsync;
+export 'src/shortest_path.dart' show shortestPath, shortestPaths;
+export 'src/strongly_connected_components.dart'
+    show stronglyConnectedComponents;
diff --git a/graphs/lib/src/crawl_async.dart b/graphs/lib/src/crawl_async.dart
new file mode 100644
index 0000000..319a61e
--- /dev/null
+++ b/graphs/lib/src/crawl_async.dart
@@ -0,0 +1,83 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:collection';
+
+final _empty = Future<Null>.value(null);
+
+/// Finds and returns every node in a graph who's nodes and edges are
+/// asynchronously resolved.
+///
+/// Cycles are allowed. If this is an undirected graph the [edges] function
+/// may be symmetric. In this case the [roots] may be any node in each connected
+/// graph.
+///
+/// [V] is the type of values in the graph nodes. [K] must be a type suitable
+/// for using as a Map or Set key. [edges] should return the next reachable
+/// nodes.
+///
+/// There are no ordering guarantees. This is useful for ensuring some work is
+/// performed at every node in an asynchronous graph, but does not give
+/// guarantees that the work is done in topological order.
+///
+/// If [readNode] returns null for any key it will be ignored from the rest of
+/// the graph. If missing nodes are important they should be tracked within the
+/// [readNode] callback.
+///
+/// If either [readNode] or [edges] throws the error will be forwarded
+/// through the result stream and no further nodes will be crawled, though some
+/// work may have already been started.
+Stream<V> crawlAsync<K, V>(Iterable<K> roots, FutureOr<V> Function(K) readNode,
+    FutureOr<Iterable<K>> Function(K, V) edges) {
+  final crawl = _CrawlAsync(roots, readNode, edges)..run();
+  return crawl.result.stream;
+}
+
+class _CrawlAsync<K, V> {
+  final result = StreamController<V>();
+
+  final FutureOr<V> Function(K) readNode;
+  final FutureOr<Iterable<K>> Function(K, V) edges;
+  final Iterable<K> roots;
+
+  final _seen = HashSet<K>();
+
+  _CrawlAsync(this.roots, this.readNode, this.edges);
+
+  /// Add all nodes in the graph to [result] and return a Future which fires
+  /// after all nodes have been seen.
+  Future<Null> run() async {
+    try {
+      await Future.wait(roots.map(_visit), eagerError: true);
+      await result.close();
+    } catch (e, st) {
+      result.addError(e, st);
+      await result.close();
+    }
+  }
+
+  /// Resolve the node at [key] and output it, then start crawling all of it's
+  /// edges.
+  Future<Null> _crawlFrom(K key) async {
+    var value = await readNode(key);
+    if (value == null) return;
+    if (result.isClosed) return;
+    result.add(value);
+    var next = await edges(key, value) ?? const [];
+    await Future.wait(next.map(_visit), eagerError: true);
+  }
+
+  /// Synchronously record that [key] is being handled then start work on the
+  /// node for [key].
+  ///
+  /// The returned Future will complete only after the work for [key] and all
+  /// transitively reachable nodes has either been finished, or will be finished
+  /// by some other Future in [_seen].
+  Future<Null> _visit(K key) {
+    if (_seen.contains(key)) return _empty;
+    _seen.add(key);
+    return _crawlFrom(key);
+  }
+}
diff --git a/graphs/lib/src/shortest_path.dart b/graphs/lib/src/shortest_path.dart
new file mode 100644
index 0000000..f295e5d
--- /dev/null
+++ b/graphs/lib/src/shortest_path.dart
@@ -0,0 +1,137 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS 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 'dart:collection';
+
+/// Returns the shortest path from [start] to [target] given the directed
+/// edges of a graph provided by [edges].
+///
+/// If [start] `==` [target], an empty [List] is returned and [edges] is never
+/// called.
+///
+/// [start], [target] and all values returned by [edges] must not be `null`.
+/// If asserts are enabled, an [AssertionError] is raised if these conditions
+/// are not met. If asserts are not enabled, violations result in undefined
+/// behavior.
+///
+/// If [equals] is provided, it is used to compare nodes in the graph. If
+/// [equals] is omitted, the node's own [Object.==] is used instead.
+///
+/// Similarly, if [hashCode] is provided, it is used to produce a hash value
+/// for nodes to efficiently calculate the return value. If it is omitted, the
+/// key's own [Object.hashCode] is used.
+///
+/// If you supply one of [equals] or [hashCode], you should generally also to
+/// supply the other.
+List<T> shortestPath<T>(
+  T start,
+  T target,
+  Iterable<T> Function(T) edges, {
+  bool equals(T key1, T key2),
+  int hashCode(T key),
+}) =>
+    _shortestPaths<T>(
+      start,
+      edges,
+      target: target,
+      equals: equals,
+      hashCode: hashCode,
+    )[target];
+
+/// Returns a [Map] of the shortest paths from [start] to all of the nodes in
+/// the directed graph defined by [edges].
+///
+/// All return values will contain the key [start] with an empty [List] value.
+///
+/// [start] and all values returned by [edges] must not be `null`.
+/// If asserts are enabled, an [AssertionError] is raised if these conditions
+/// are not met. If asserts are not enabled, violations result in undefined
+/// behavior.
+///
+/// If [equals] is provided, it is used to compare nodes in the graph. If
+/// [equals] is omitted, the node's own [Object.==] is used instead.
+///
+/// Similarly, if [hashCode] is provided, it is used to produce a hash value
+/// for nodes to efficiently calculate the return value. If it is omitted, the
+/// key's own [Object.hashCode] is used.
+///
+/// If you supply one of [equals] or [hashCode], you should generally also to
+/// supply the other.
+Map<T, List<T>> shortestPaths<T>(
+  T start,
+  Iterable<T> Function(T) edges, {
+  bool equals(T key1, T key2),
+  int hashCode(T key),
+}) =>
+    _shortestPaths<T>(
+      start,
+      edges,
+      equals: equals,
+      hashCode: hashCode,
+    );
+
+Map<T, List<T>> _shortestPaths<T>(
+  T start,
+  Iterable<T> Function(T) edges, {
+  T target,
+  bool equals(T key1, T key2),
+  int hashCode(T key),
+}) {
+  assert(start != null, '`start` cannot be null');
+  assert(edges != null, '`edges` cannot be null');
+
+  final distances = HashMap<T, List<T>>(equals: equals, hashCode: hashCode);
+  distances[start] = List(0);
+
+  equals ??= _defaultEquals;
+  if (equals(start, target)) {
+    return distances;
+  }
+
+  final toVisit = ListQueue<T>()..add(start);
+
+  List<T> bestOption;
+
+  while (toVisit.isNotEmpty) {
+    final current = toVisit.removeFirst();
+    final currentPath = distances[current];
+    final currentPathLength = currentPath.length;
+
+    if (bestOption != null && (currentPathLength + 1) >= bestOption.length) {
+      // Skip any existing `toVisit` items that have no chance of being
+      // better than bestOption (if it exists)
+      continue;
+    }
+
+    for (var edge in edges(current)) {
+      assert(edge != null, '`edges` cannot return null values.');
+      final existingPath = distances[edge];
+
+      assert(existingPath == null ||
+          existingPath.length <= (currentPathLength + 1));
+
+      if (existingPath == null) {
+        final newOption = List<T>(currentPathLength + 1)
+          ..setRange(0, currentPathLength, currentPath)
+          ..[currentPathLength] = edge;
+
+        if (equals(edge, target)) {
+          assert(bestOption == null || bestOption.length > newOption.length);
+          bestOption = newOption;
+        }
+
+        distances[edge] = newOption;
+        if (bestOption == null || bestOption.length > newOption.length) {
+          // Only add a node to visit if it might be a better path to the
+          // target node
+          toVisit.add(edge);
+        }
+      }
+    }
+  }
+
+  return distances;
+}
+
+bool _defaultEquals(a, b) => a == b;
diff --git a/graphs/lib/src/strongly_connected_components.dart b/graphs/lib/src/strongly_connected_components.dart
new file mode 100644
index 0000000..19519a8
--- /dev/null
+++ b/graphs/lib/src/strongly_connected_components.dart
@@ -0,0 +1,81 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:collection';
+import 'dart:math' show min;
+
+/// Finds the strongly connected components of a directed graph using Tarjan's
+/// algorithm.
+///
+/// The result will be a valid reverse topological order ordering of the
+/// strongly connected components. Components further from a root will appear in
+/// the result before the components which they are connected to.
+///
+/// Nodes within a strongly connected component have no ordering guarantees,
+/// except that if the first value in [nodes] is a valid root, and is contained
+/// in a cycle, it will be the last element of that cycle.
+///
+/// [nodes] must contain at least a root of every tree in the graph if there are
+/// disjoint subgraphs but it may contain all nodes in the graph if the roots
+/// are not known.
+///
+/// If [equals] is provided, it is used to compare nodes in the graph. If
+/// [equals] is omitted, the node's own [Object.==] is used instead.
+///
+/// Similarly, if [hashCode] is provided, it is used to produce a hash value
+/// for nodes to efficiently calculate the return value. If it is omitted, the
+/// key's own [Object.hashCode] is used.
+///
+/// If you supply one of [equals] or [hashCode], you should generally also to
+/// supply the other.
+List<List<T>> stronglyConnectedComponents<T>(
+  Iterable<T> nodes,
+  Iterable<T> Function(T) edges, {
+  bool equals(T key1, T key2),
+  int hashCode(T key),
+}) {
+  final result = <List<T>>[];
+  final lowLinks = HashMap<T, int>(equals: equals, hashCode: hashCode);
+  final indexes = HashMap<T, int>(equals: equals, hashCode: hashCode);
+  final onStack = HashSet<T>(equals: equals, hashCode: hashCode);
+
+  equals ??= _defaultEquals;
+
+  var index = 0;
+  var lastVisited = Queue<T>();
+
+  void strongConnect(T node) {
+    indexes[node] = index;
+    lowLinks[node] = index;
+    index++;
+    lastVisited.addLast(node);
+    onStack.add(node);
+    // ignore: omit_local_variable_types
+    for (final T next in edges(node) ?? const []) {
+      if (!indexes.containsKey(next)) {
+        strongConnect(next);
+        lowLinks[node] = min(lowLinks[node], lowLinks[next]);
+      } else if (onStack.contains(next)) {
+        lowLinks[node] = min(lowLinks[node], indexes[next]);
+      }
+    }
+    if (lowLinks[node] == indexes[node]) {
+      final component = <T>[];
+      T next;
+      do {
+        next = lastVisited.removeLast();
+        onStack.remove(next);
+        component.add(next);
+      } while (!equals(next, node));
+      result.add(component);
+    }
+  }
+
+  for (final node in nodes) {
+    if (!indexes.containsKey(node)) strongConnect(node);
+  }
+  return result;
+}
+
+bool _defaultEquals(a, b) => a == b;
diff --git a/graphs/pubspec.yaml b/graphs/pubspec.yaml
new file mode 100644
index 0000000..12c5ff4
--- /dev/null
+++ b/graphs/pubspec.yaml
@@ -0,0 +1,16 @@
+name: graphs
+version: 0.2.0
+description: Graph algorithms that operate on graphs in any representation
+author: Dart Team <misc@dartlang.org>
+homepage: https://github.com/dart-lang/graphs
+
+environment:
+  sdk: '>=2.0.0 <3.0.0'
+
+dev_dependencies:
+  pedantic: ^1.3.0
+  test: ^1.5.1
+
+  # For examples
+  analyzer: ^0.34.0
+  path: ^1.1.0
diff --git a/mustache_template/.gitignore b/mustache/.gitignore
similarity index 100%
rename from mustache_template/.gitignore
rename to mustache/.gitignore
diff --git a/mustache_template/.travis.yml b/mustache/.travis.yml
similarity index 100%
rename from mustache_template/.travis.yml
rename to mustache/.travis.yml
diff --git a/mustache_template/BUILD.gn b/mustache/BUILD.gn
similarity index 60%
copy from mustache_template/BUILD.gn
copy to mustache/BUILD.gn
index 0f80569..2e8c791 100644
--- a/mustache_template/BUILD.gn
+++ b/mustache/BUILD.gn
@@ -1,9 +1,9 @@
-# This file is generated by importer.py for mustache_template-1.0.0+1
+# This file is generated by importer.py for mustache-1.1.1
 
 import("//build/dart/dart_library.gni")
 
-dart_library("mustache_template") {
-  package_name = "mustache_template"
+dart_library("mustache") {
+  package_name = "mustache"
 
   # This parameter is left empty as we don't care about analysis or exporting
   # these sources outside of the tree.
diff --git a/mustache_template/CHANGELOG.md b/mustache/CHANGELOG.md
similarity index 87%
rename from mustache_template/CHANGELOG.md
rename to mustache/CHANGELOG.md
index 28d3573..3a8f600 100644
--- a/mustache_template/CHANGELOG.md
+++ b/mustache/CHANGELOG.md
@@ -1,16 +1,5 @@
 # CHANGELOG
 
-## 1.0.0+1
-
-* Fixed regression where lookups from list did not work. Removed failing tests
-  that depend on reflection.
-
-## 1.0.0
-
- * Forked from original repo. Support for mirrors removed.
-
-## Fork
-
 ## 1.1.1
 
  * Fixed error "boolean expression must not be null". Thanks Nico.
diff --git a/mustache_template/LICENSE b/mustache/LICENSE
similarity index 100%
rename from mustache_template/LICENSE
rename to mustache/LICENSE
diff --git a/mustache_template/README.md b/mustache/README.md
similarity index 88%
rename from mustache_template/README.md
rename to mustache/README.md
index 37a3a14..c2223d7 100644
--- a/mustache_template/README.md
+++ b/mustache/README.md
@@ -2,6 +2,8 @@
 
 A Dart library to parse and render [mustache templates](https://mustache.github.io/).
 
+[![Build Status](https://api.travis-ci.org/xxgreg/mustache.svg?branch=master)](https://travis-ci.org/xxgreg/mustache) [![Coverage Status](https://coveralls.io/repos/xxgreg/mustache/badge.svg)](https://coveralls.io/r/xxgreg/mustache)
+
 See the [mustache manual](http://mustache.github.com/mustache.5.html) for detailed usage information.
 
 This library passes all [mustache specification](https://github.com/mustache/spec/tree/master/specs) tests.
@@ -38,6 +40,10 @@
 
 By default all output from `{{variable}}` tags is html escaped, this behaviour can be changed by passing htmlEscapeValues : false to the Template constructor. You can also use a `{{{triple mustache}}}` tag, or a unescaped variable tag `{{&unescaped}}`, the output from these tags is not escaped.
 
+## Dart2js
+
+This library uses mirrors. When compiling with dart2js you will need to pass the experimental mirrors flag. You also need to mark any objects which will be rendered with the @mustache annotation. There is also another version of this library available which doesn't use mirrors.
+
 ## Differences between strict mode and lenient mode.
 
 ### Strict mode (default)
diff --git a/mustache/example/basic.dart b/mustache/example/basic.dart
new file mode 100644
index 0000000..89a293c
--- /dev/null
+++ b/mustache/example/basic.dart
@@ -0,0 +1,22 @@
+import 'package:mustache/mustache.dart';
+
+main() {
+  var source = '''
+    {{# names }}
+          <div>{{ lastname }}, {{ firstname }}</div>
+    {{/ names }}
+    {{^ names }}
+      <div>No names.</div>
+    {{/ names }}
+    {{! I am a comment. }}
+  ''';
+
+  var template = new Template(source, name: 'template-filename.html');
+
+  var output = template.renderString({'names': [
+      {'firstname': 'Greg', 'lastname': 'Lowe'},
+      {'firstname': 'Bob', 'lastname': 'Johnson'}
+  ]});
+
+  print(output);
+}
\ No newline at end of file
diff --git a/mustache/example/lambdas.dart b/mustache/example/lambdas.dart
new file mode 100644
index 0000000..b7a9ff2
--- /dev/null
+++ b/mustache/example/lambdas.dart
@@ -0,0 +1,33 @@
+import 'package:mustache/mustache.dart';
+
+main() {
+  var t = new Template('{{ foo }}');
+  Function lambda = (_) => 'bar';
+  var output = t.renderString({'foo': lambda}); // bar
+  print(output);
+
+  t = new Template('{{# foo }}hidden{{/ foo }}');
+  lambda = (_) => 'shown';
+  output = t.renderString({'foo': lambda}); // shown
+  print(output);
+
+  t = new Template('{{# foo }}oi{{/ foo }}');
+  lambda = (LambdaContext ctx) => '<b>${ctx.renderString().toUpperCase()}</b>';
+  output = t.renderString({'foo': lambda}); // <b>OI</b>
+  print(output);
+
+  t = new Template('{{# foo }}{{bar}}{{/ foo }}');
+  lambda = (LambdaContext ctx) => '<b>${ctx.renderString().toUpperCase()}</b>';
+  output = t.renderString({'foo': lambda, 'bar': 'pub'}); // <b>PUB</b>
+  print(output);
+
+  t = new Template('{{# foo }}{{bar}}{{/ foo }}');
+  lambda = (LambdaContext ctx) => '<b>${ctx.renderString().toUpperCase()}</b>';
+  output = t.renderString({'foo': lambda, 'bar': 'pub'}); // <b>PUB</b>
+  print(output);
+
+  t = new Template('{{# foo }}{{bar}}{{/ foo }}');
+  lambda = (LambdaContext ctx) => ctx.renderSource(ctx.source + '{{cmd}}');
+  output = t.renderString({'foo': lambda, 'bar': 'pub', 'cmd': 'build'}); // pub build
+  print(output);
+}
diff --git a/mustache/example/nested_paths.dart b/mustache/example/nested_paths.dart
new file mode 100644
index 0000000..5472ce7
--- /dev/null
+++ b/mustache/example/nested_paths.dart
@@ -0,0 +1,7 @@
+import 'package:mustache/mustache.dart';
+
+main() {
+  var template = new Template('{{ author.name }}');
+  var output = template.renderString({'author': {'name': 'Greg Lowe'}});
+  print(output);
+}
diff --git a/mustache/example/partials.dart b/mustache/example/partials.dart
new file mode 100644
index 0000000..a0c105f
--- /dev/null
+++ b/mustache/example/partials.dart
@@ -0,0 +1,16 @@
+import 'package:mustache/mustache.dart';
+
+main() {
+  var partial = new Template('{{ foo }}', name: 'partial');
+
+  var resolver = (String name) {
+     if (name == 'partial-name') { // Name of partial tag.
+       return partial;
+     }
+  };
+
+  var t = new Template('{{> partial-name }}', partialResolver: resolver);
+
+  var output = t.renderString({'foo': 'bar'}); // bar
+  print(output);
+}
diff --git a/mustache_template/lib/mustache.dart b/mustache/lib/mustache.dart
similarity index 84%
rename from mustache_template/lib/mustache.dart
rename to mustache/lib/mustache.dart
index 687459c..945c9d2 100644
--- a/mustache_template/lib/mustache.dart
+++ b/mustache/lib/mustache.dart
@@ -1,5 +1,14 @@
+/// [Mustache template documentation](http://mustache.github.com/mustache.5.html)
+
+library mustache;
+
 import 'src/template.dart' as t;
 
+/// Use new Template(source) instead.
+@deprecated
+Template parse(String source, {bool lenient: false}) =>
+    new Template(source, lenient: lenient);
+
 /// A Template can be efficiently rendered multiple times with different
 /// values.
 abstract class Template {
@@ -28,9 +37,9 @@
   void render(values, StringSink sink);
 }
 
-typedef PartialResolver = Template Function(String);
+typedef Template PartialResolver(String templateName);
 
-typedef LambdaFunction = Object Function(LambdaContext context);
+typedef Object LambdaFunction(LambdaContext context);
 
 /// Passed as an argument to a mustache lambda function. The methods on
 /// this object may only be called before the lambda function returns. If a
@@ -60,6 +69,13 @@
   Object lookup(String variableName);
 }
 
+const MustacheMirrorsUsedAnnotation mustache =
+    const MustacheMirrorsUsedAnnotation();
+
+class MustacheMirrorsUsedAnnotation {
+  const MustacheMirrorsUsedAnnotation();
+}
+
 /// [TemplateException] is used to obtain the line and column numbers
 /// of the token which caused parse or render to fail.
 abstract class TemplateException implements Exception {
@@ -85,4 +101,6 @@
   /// A short source substring of the source at the point the problem occurred
   /// with parsing or rendering.
   String get context;
+
+  String toString();
 }
diff --git a/mustache_template/lib/src/lambda_context.dart b/mustache/lib/src/lambda_context.dart
similarity index 70%
rename from mustache_template/lib/src/lambda_context.dart
rename to mustache/lib/src/lambda_context.dart
index 31d0a96..e233248 100644
--- a/mustache_template/lib/src/lambda_context.dart
+++ b/mustache/lib/src/lambda_context.dart
@@ -1,4 +1,6 @@
-import 'package:mustache_template/mustache.dart' as m;
+library mustache.lambda_context;
+
+import 'package:mustache/mustache.dart' as m;
 
 import 'node.dart';
 import 'parser.dart' as parser;
@@ -22,45 +24,41 @@
   }
 
   TemplateException _error(String msg) {
-    return TemplateException(
+    return new TemplateException(
         msg, _renderer.templateName, _renderer.source, _node.start);
   }
 
-  @override
+  /// Render the current section tag in the current context and return the
+  /// result as a string.
   String renderString({Object value}) {
     _checkClosed();
-    if (_node is! SectionNode) {
-      _error(
-          'LambdaContext.renderString() can only be called on section tags.');
-    }
-    var sink = StringBuffer();
+    if (_node is! SectionNode) _error(
+        'LambdaContext.renderString() can only be called on section tags.');
+    var sink = new StringBuffer();
     _renderSubtree(sink, value);
     return sink.toString();
   }
 
   void _renderSubtree(StringSink sink, Object value) {
-    var renderer = Renderer.subtree(_renderer, sink);
+    var renderer = new Renderer.subtree(_renderer, sink);
     SectionNode section = _node;
     if (value != null) renderer.push(value);
     renderer.render(section.children);
   }
 
-  @override
   void render({Object value}) {
     _checkClosed();
-    if (_node is! SectionNode) {
-      _error('LambdaContext.render() can only be called on section tags.');
-    }
+    if (_node is! SectionNode) _error(
+        'LambdaContext.render() can only be called on section tags.');
     _renderSubtree(_renderer.sink, value);
   }
 
-  @override
   void write(Object object) {
     _checkClosed();
     _renderer.write(object);
   }
 
-  @override
+  /// Get the unevaluated template source for the current section tag.
   String get source {
     _checkClosed();
 
@@ -79,10 +77,10 @@
     return _renderer.source.substring(node.contentStart, node.contentEnd);
   }
 
-  @override
+  /// Evaluate the string as a mustache template using the current context.
   String renderSource(String source, {Object value}) {
     _checkClosed();
-    var sink = StringBuffer();
+    var sink = new StringBuffer();
 
     // Lambdas used for sections should parse with the current delimiters.
     var delimiters = '{{ }}';
@@ -94,8 +92,8 @@
     var nodes = parser.parse(
         source, _renderer.lenient, _renderer.templateName, delimiters);
 
-    var renderer =
-        Renderer.lambda(_renderer, source, _renderer.indent, sink, delimiters);
+    var renderer = new Renderer.lambda(
+        _renderer, source, _renderer.indent, sink, delimiters);
 
     if (value != null) renderer.push(value);
     renderer.render(nodes);
@@ -103,7 +101,7 @@
     return sink.toString();
   }
 
-  @override
+  /// Lookup the value of a variable in the current context.
   Object lookup(String variableName) {
     _checkClosed();
     return _renderer.resolveValue(variableName);
diff --git a/mustache_template/lib/src/node.dart b/mustache/lib/src/node.dart
similarity index 84%
rename from mustache_template/lib/src/node.dart
rename to mustache/lib/src/node.dart
index 0c52012..046baff 100644
--- a/mustache_template/lib/src/node.dart
+++ b/mustache/lib/src/node.dart
@@ -1,3 +1,5 @@
+library mustache.node;
+
 abstract class Node {
   Node(this.start, this.end);
 
@@ -23,7 +25,6 @@
 
   final String text;
 
-  @override
   String toString() => '(TextNode "$_debugText" $start $end)';
 
   String get _debugText {
@@ -31,27 +32,24 @@
     return t.length < 50 ? t : t.substring(0, 48) + '...';
   }
 
-  @override
   void accept(Visitor visitor) => visitor.visitText(this);
 }
 
 class VariableNode extends Node {
-  VariableNode(this.name, int start, int end, {this.escape = true})
+  VariableNode(this.name, int start, int end, {this.escape: true})
       : super(start, end);
 
   final String name;
   final bool escape;
 
-  @override
   void accept(Visitor visitor) => visitor.visitVariable(this);
 
-  @override
   String toString() => '(VariableNode "$name" escape: $escape $start $end)';
 }
 
 class SectionNode extends Node {
   SectionNode(this.name, int start, int end, this.delimiters,
-      {this.inverse = false})
+      {this.inverse: false})
       : contentStart = end,
         super(start, end);
 
@@ -62,16 +60,13 @@
   int contentEnd; // Set in parser when close tag is parsed.
   final List<Node> children = <Node>[];
 
-  @override
   void accept(Visitor visitor) => visitor.visitSection(this);
 
-  @override
   void visitChildren(Visitor visitor) {
     children.forEach((node) => node.accept(visitor));
   }
 
-  @override
-  String toString() => '(SectionNode $name inverse: $inverse $start $end)';
+  toString() => '(SectionNode $name inverse: $inverse $start $end)';
 }
 
 class PartialNode extends Node {
@@ -83,9 +78,7 @@
   // it's content can be correctly indented.
   final String indent;
 
-  @override
   void accept(Visitor visitor) => visitor.visitPartial(this);
 
-  @override
-  String toString() => '(PartialNode $name $start $end "$indent")';
+  toString() => '(PartialNode $name $start $end "$indent")';
 }
diff --git a/mustache_template/lib/src/parser.dart b/mustache/lib/src/parser.dart
similarity index 80%
rename from mustache_template/lib/src/parser.dart
rename to mustache/lib/src/parser.dart
index 867cce6..5ebb0c0 100644
--- a/mustache_template/lib/src/parser.dart
+++ b/mustache/lib/src/parser.dart
@@ -1,3 +1,5 @@
+library mustache.parser;
+
 import 'node.dart';
 import 'scanner.dart';
 import 'template_exception.dart';
@@ -5,7 +7,7 @@
 
 List<Node> parse(
     String source, bool lenient, String templateName, String delimiters) {
-  var parser = Parser(source, templateName, delimiters, lenient: lenient);
+  var parser = new Parser(source, templateName, delimiters, lenient: lenient);
   return parser.parse();
 }
 
@@ -21,25 +23,26 @@
   const TagType(this.name);
   final String name;
 
-  static const TagType openSection = TagType('openSection');
-  static const TagType openInverseSection = TagType('openInverseSection');
-  static const TagType closeSection = TagType('closeSection');
-  static const TagType variable = TagType('variable');
-  static const TagType tripleMustache = TagType('tripleMustache');
-  static const TagType unescapedVariable = TagType('unescapedVariable');
-  static const TagType partial = TagType('partial');
-  static const TagType comment = TagType('comment');
-  static const TagType changeDelimiter = TagType('changeDelimiter');
+  static const TagType openSection = const TagType('openSection');
+  static const TagType openInverseSection = const TagType('openInverseSection');
+  static const TagType closeSection = const TagType('closeSection');
+  static const TagType variable = const TagType('variable');
+  static const TagType tripleMustache = const TagType('tripleMustache');
+  static const TagType unescapedVariable = const TagType('unescapedVariable');
+  static const TagType partial = const TagType('partial');
+  static const TagType comment = const TagType('comment');
+  static const TagType changeDelimiter = const TagType('changeDelimiter');
 }
 
 class Parser {
   Parser(String source, String templateName, String delimiters,
-      {lenient = false})
+      {lenient: false})
       : _source = source,
         _templateName = templateName,
         _delimiters = delimiters,
         _lenient = lenient,
-        _scanner = Scanner(source, templateName, delimiters);
+        _scanner =
+            new Scanner(source, templateName, delimiters);
 
   final String _source;
   final bool _lenient;
@@ -55,7 +58,7 @@
     _tokens = _scanner.scan();
     _currentDelimiters = _delimiters;
     _stack.clear();
-    _stack.add(SectionNode('root', 0, 0, _delimiters));
+    _stack.add(new SectionNode('root', 0, 0, _delimiters));
 
     // Handle a standalone tag on first line, including special case where the
     // first line is empty.
@@ -88,12 +91,12 @@
           break;
 
         default:
-          throw Exception('Unreachable code.');
+          throw new Exception('Unreachable code.');
       }
     }
 
     if (_stack.length != 1) {
-      throw TemplateException("Unclosed tag: '${_stack.last.name}'.",
+      throw new TemplateException("Unclosed tag: '${_stack.last.name}'.",
           _templateName, _source, _stack.last.start);
     }
 
@@ -105,7 +108,7 @@
 
   // Returns null on EOF.
   Token _read() {
-    Token t;
+    var t = null;
     if (_offset < _tokens.length) {
       t = _tokens[_offset];
       _offset++;
@@ -122,7 +125,7 @@
     return token;
   }
 
-  Token _readIf(TokenType type, {eofOk = false}) {
+  Token _readIf(TokenType type, {eofOk: false}) {
     var token = _peek();
     if (!eofOk && token == null) throw _errorEof();
     return token != null && token.type == type ? _read() : null;
@@ -132,7 +135,7 @@
       _error('Unexpected end of input.', _source.length - 1);
 
   TemplateException _error(String msg, int offset) =>
-      TemplateException(msg, _templateName, _source, offset);
+      new TemplateException(msg, _templateName, _source, offset);
 
   // Add a text node to top most section on the stack and merge consecutive
   // text nodes together.
@@ -141,10 +144,10 @@
         .contains(token.type));
     var children = _stack.last.children;
     if (children.isEmpty || children.last is! TextNode) {
-      children.add(TextNode(token.value, token.start, token.end));
+      children.add(new TextNode(token.value, token.start, token.end));
     } else {
       var last = children.removeLast() as TextNode;
-      var node = TextNode(last.text + token.value, last.start, token.end);
+      var node = new TextNode(last.text + token.value, last.start, token.end);
       children.add(node);
     }
   }
@@ -164,8 +167,8 @@
       // {{/...}}
       case TagType.closeSection:
         if (tag.name != _stack.last.name) {
-          throw TemplateException(
-              'Mismatched tag, expected: '
+          throw new TemplateException(
+              "Mismatched tag, expected: "
               "'${_stack.last.name}', was: '${tag.name}'",
               _templateName,
               _source,
@@ -189,7 +192,7 @@
         break;
 
       default:
-        throw Exception('Unreachable code.');
+        throw new Exception('Unreachable code.');
     }
   }
 
@@ -219,7 +222,7 @@
       var tagNode = _createNodeFromTag(tag, partialIndent: indent);
       var followingWhitespace = _readIf(TokenType.whitespace, eofOk: true);
 
-      const standaloneTypes = [
+      const standaloneTypes = const [
         TagType.openSection,
         TagType.closeSection,
         TagType.openInverseSection,
@@ -246,9 +249,9 @@
     }
   }
 
-  final RegExp _validIdentifier = RegExp(r'^[0-9a-zA-Z\_\-\.]+$');
+  final RegExp _validIdentifier = new RegExp(r'^[0-9a-zA-Z\_\-\.]+$');
 
-  static const _tagTypeMap = {
+  static const _tagTypeMap = const {
     '#': TagType.openSection,
     '^': TagType.openInverseSection,
     '/': TagType.closeSection,
@@ -272,7 +275,7 @@
 
       // Change delimiter tags are already parsed by the scanner.
       // So just create a tag and return it.
-      return Tag(TagType.changeDelimiter, t.value, t.start, t.end);
+      return new Tag(TagType.changeDelimiter, t.value, t.start, t.end);
     }
 
     // Start parsing a typical tag.
@@ -328,33 +331,31 @@
 
     var close = _expect(TokenType.closeDelimiter);
 
-    return Tag(tagType, name, open.start, close.end);
+    return new Tag(tagType, name, open.start, close.end);
   }
 
-  Node _createNodeFromTag(Tag tag, {String partialIndent = ''}) {
+  Node _createNodeFromTag(Tag tag, {String partialIndent: ''}) {
     // Handle EOF case.
-    if (tag == null) {
-      return null;
-    }
+    if (tag == null) return null;
 
-    Node node;
+    Node node = null;
     switch (tag.type) {
       case TagType.openSection:
       case TagType.openInverseSection:
-        var inverse = tag.type == TagType.openInverseSection;
-        node = SectionNode(tag.name, tag.start, tag.end, _currentDelimiters,
+        bool inverse = tag.type == TagType.openInverseSection;
+        node = new SectionNode(tag.name, tag.start, tag.end, _currentDelimiters,
             inverse: inverse);
         break;
 
       case TagType.variable:
       case TagType.unescapedVariable:
       case TagType.tripleMustache:
-        var escape = tag.type == TagType.variable;
-        node = VariableNode(tag.name, tag.start, tag.end, escape: escape);
+        bool escape = tag.type == TagType.variable;
+        node = new VariableNode(tag.name, tag.start, tag.end, escape: escape);
         break;
 
       case TagType.partial:
-        node = PartialNode(tag.name, tag.start, tag.end, partialIndent);
+        node = new PartialNode(tag.name, tag.start, tag.end, partialIndent);
         break;
 
       case TagType.closeSection:
@@ -364,7 +365,7 @@
         break;
 
       default:
-        throw Exception('Unreachable code');
+        throw new Exception('Unreachable code');
     }
     return node;
   }
diff --git a/mustache_template/lib/src/renderer.dart b/mustache/lib/src/renderer.dart
similarity index 80%
rename from mustache_template/lib/src/renderer.dart
rename to mustache/lib/src/renderer.dart
index 74ca3cf..bccd7e4 100644
--- a/mustache_template/lib/src/renderer.dart
+++ b/mustache/lib/src/renderer.dart
@@ -1,16 +1,22 @@
-import 'package:mustache_template/mustache.dart' as m;
+library mustache.renderer;
+
+@MirrorsUsed(metaTargets: const [m.MustacheMirrorsUsedAnnotation])
+import 'dart:mirrors';
+import 'package:mustache/mustache.dart' as m;
 import 'lambda_context.dart';
 import 'node.dart';
 import 'template.dart';
 import 'template_exception.dart';
 
-const Object noSuchProperty = Object();
-final RegExp _integerTag = RegExp(r'^[0-9]+$');
+final RegExp _validTag = new RegExp(r'^[0-9a-zA-Z\_\-\.]+$');
+final RegExp _integerTag = new RegExp(r'^[0-9]+$');
+
+const Object noSuchProperty = const Object();
 
 class Renderer extends Visitor {
   Renderer(this.sink, List stack, this.lenient, this.htmlEscapeValues,
       this.partialResolver, this.templateName, this.indent, this.source)
-      : _stack = List.from(stack);
+      : _stack = new List.from(stack);
 
   Renderer.partial(Renderer ctx, Template partial, String indent)
       : this(
@@ -66,8 +72,7 @@
     }
   }
 
-  @override
-  void visitText(TextNode node, {bool lastNode = false}) {
+  void visitText(TextNode node, {bool lastNode: false}) {
     if (node.text == '') return;
     if (indent == null || indent == '') {
       write(node.text);
@@ -81,21 +86,19 @@
     }
   }
 
-  @override
   void visitVariable(VariableNode node) {
     var value = resolveValue(node.name);
 
     if (value is Function) {
-      var context = LambdaContext(node, this);
+      var context = new LambdaContext(node, this);
       Function valueFunction = value;
       value = valueFunction(context);
       context.close();
     }
 
     if (value == noSuchProperty) {
-      if (!lenient) {
+      if (!lenient)
         throw error('Value was missing for variable tag: ${node.name}.', node);
-      }
     } else {
       var valueString = (value == null) ? '' : value.toString();
       var output = !node.escape || !htmlEscapeValues
@@ -105,13 +108,11 @@
     }
   }
 
-  @override
   void visitSection(SectionNode node) {
-    if (node.inverse) {
+    if (node.inverse)
       _renderInvSection(node);
-    } else {
+    else
       _renderSection(node);
-    }
   }
 
   //TODO can probably combine Inv and Normal to shorten.
@@ -131,11 +132,10 @@
       // Do nothing.
 
     } else if (value == noSuchProperty) {
-      if (!lenient) {
+      if (!lenient)
         throw error('Value was missing for section tag: ${node.name}.', node);
-      }
     } else if (value is Function) {
-      var context = LambdaContext(node, this);
+      var context = new LambdaContext(node, this);
       var output = value(context);
       context.close();
       if (output != null) write(output);
@@ -185,13 +185,12 @@
     pop();
   }
 
-  @override
   void visitPartial(PartialNode node) {
     var partialName = node.name;
     Template template =
         partialResolver == null ? null : partialResolver(partialName);
     if (template != null) {
-      var renderer = Renderer.partial(this, template, node.indent);
+      var renderer = new Renderer.partial(this, template, node.indent);
       var nodes = getTemplateNodes(template);
       renderer.render(nodes);
     } else if (lenient) {
@@ -215,7 +214,7 @@
         break;
       }
     }
-    for (var i = 1; i < parts.length; i++) {
+    for (int i = 1; i < parts.length; i++) {
       if (object == null || object == noSuchProperty) {
         return noSuchProperty;
       }
@@ -228,20 +227,35 @@
   // which contains the key name, this is object[name]. For other
   // objects, this is object.name or object.name(). If no property
   // by the given name exists, this method returns noSuchProperty.
-  Object _getNamedProperty(dynamic object, dynamic name) {
+  _getNamedProperty(object, name) {
     if (object is Map && object.containsKey(name)) return object[name];
 
-    if (object is List && _integerTag.hasMatch(name)) {
+    if (object is List && _integerTag.hasMatch(name))
       return object[int.parse(name)];
-    }
 
-    return noSuchProperty;
+    if (lenient && !_validTag.hasMatch(name)) return noSuchProperty;
+
+    var instance = reflect(object);
+    var field = instance.type.instanceMembers[new Symbol(name)];
+    if (field == null) return noSuchProperty;
+
+    var invocation = null;
+    if ((field is VariableMirror) ||
+        ((field is MethodMirror) && (field.isGetter))) {
+      invocation = instance.getField(field.simpleName);
+    } else if ((field is MethodMirror) && (field.parameters.where((p) => !p.isOptional).length == 0)) {
+      invocation = instance.invoke(field.simpleName, []);
+    }
+    if (invocation == null) {
+      return noSuchProperty;
+    }
+    return invocation.reflectee;
   }
 
   m.TemplateException error(String message, Node node) =>
-      TemplateException(message, templateName, source, node.start);
+      new TemplateException(message, templateName, source, node.start);
 
-  static const Map<int, String> _htmlEscapeMap = {
+  static const Map<int, String> _htmlEscapeMap = const {
     _AMP: '&amp;',
     _LT: '&lt;',
     _GT: '&gt;',
@@ -251,10 +265,10 @@
   };
 
   String _htmlEscape(String s) {
-    var buffer = StringBuffer();
-    var startIndex = 0;
-    var i = 0;
-    for (var c in s.runes) {
+    var buffer = new StringBuffer();
+    int startIndex = 0;
+    int i = 0;
+    for (int c in s.runes) {
       if (c == _AMP ||
           c == _LT ||
           c == _GT ||
diff --git a/mustache_template/lib/src/scanner.dart b/mustache/lib/src/scanner.dart
similarity index 84%
rename from mustache_template/lib/src/scanner.dart
rename to mustache/lib/src/scanner.dart
index 992566a..2a668b5 100644
--- a/mustache_template/lib/src/scanner.dart
+++ b/mustache/lib/src/scanner.dart
@@ -1,3 +1,5 @@
+library mustache.scanner;
+
 import 'token.dart';
 import 'template_exception.dart';
 
@@ -24,7 +26,7 @@
       _closeDelimiterInner = delimiters.codeUnits[3];
       _closeDelimiter = delimiters.codeUnits[4];
     } else {
-      throw TemplateException(
+      throw new TemplateException(
           'Invalid delimiter string $delimiters', null, null, null);
     }
   }
@@ -36,7 +38,7 @@
   int _offset = 0;
   int _c = 0;
 
-  final List<Token> _tokens = <Token>[];
+  final List<Token> _tokens = new List<Token>();
 
   // These can be changed by the change delimiter tag.
   int _openDelimiter;
@@ -45,21 +47,21 @@
   int _closeDelimiter;
 
   List<Token> scan() {
-    for (var c = _peek(); c != _EOF; c = _peek()) {
+    for (int c = _peek(); c != _EOF; c = _peek()) {
       // Scan text tokens.
       if (c != _openDelimiter) {
         _scanText();
         continue;
       }
 
-      var start = _offset;
+      int start = _offset;
 
       // Read first open delimiter character.
       _read();
 
       // If only a single delimiter character then create a text token.
       if (_openDelimiterInner != null && _peek() != _openDelimiterInner) {
-        var value = String.fromCharCode(_openDelimiter);
+        var value = new String.fromCharCode(_openDelimiter);
         _append(TokenType.text, value, start, _offset);
         continue;
       }
@@ -77,14 +79,14 @@
       } else {
         // Check to see if this is a change delimiter tag. {{= | | =}}
         // Need to skip whitespace and check for "=".
-        var wsStart = _offset;
+        int wsStart = _offset;
         var ws = _readWhile(_isWhitespace);
 
         if (_peek() == _EQUAL) {
           _parseChangeDelimiterTag(start);
         } else {
           // Scan standard mustache tag.
-          var value = String.fromCharCodes(_openDelimiterInner == null
+          var value = new String.fromCharCodes(_openDelimiterInner == null
               ? [_openDelimiter]
               : [_openDelimiter, _openDelimiterInner]);
 
@@ -103,34 +105,33 @@
   int _peek() => _c;
 
   int _read() {
-    var c = _c;
+    int c = _c;
     _offset++;
     _c = _itr.moveNext() ? _itr.current : _EOF;
     return c;
   }
 
-  String _readWhile(bool Function(int charCode) test) {
+  String _readWhile(bool test(int charCode)) {
     if (_c == _EOF) return '';
-    var start = _offset;
+    int start = _offset;
     while (_peek() != _EOF && test(_peek())) {
       _read();
     }
-    var end = _peek() == _EOF ? _source.length : _offset;
+    int end = _peek() == _EOF ? _source.length : _offset;
     return _source.substring(start, end);
   }
 
-  void _expect(int expectedCharCode) {
-    var c = _read();
+  _expect(int expectedCharCode) {
+    int c = _read();
 
     if (c == _EOF) {
-      throw TemplateException(
+      throw new TemplateException(
           'Unexpected end of input', _templateName, _source, _offset - 1);
-    }
-    if (c != expectedCharCode) {
-      throw TemplateException(
+    } else if (c != expectedCharCode) {
+      throw new TemplateException(
           'Unexpected character, '
-          'expected: ${String.fromCharCode(expectedCharCode)}, '
-          'was: ${String.fromCharCode(c)}',
+          'expected: ${new String.fromCharCode(expectedCharCode)}, '
+          'was: ${new String.fromCharCode(c)}',
           _templateName,
           _source,
           _offset - 1);
@@ -138,7 +139,7 @@
   }
 
   void _append(TokenType type, String value, int start, int end) =>
-      _tokens.add(Token(type, value, start, end));
+      _tokens.add(new Token(type, value, start, end));
 
   bool _isWhitespace(int c) =>
       const [_SPACE, _TAB, _NEWLINE, _RETURN].contains(c);
@@ -147,11 +148,11 @@
   // tokens for whitespace at the begining of a line. This is because the
   // mustache spec requires special handing of whitespace.
   void _scanText() {
-    var start = 0;
+    int start = 0;
     TokenType token;
     String value;
 
-    for (var c = _peek(); c != _EOF && c != _openDelimiter; c = _peek()) {
+    for (int c = _peek(); c != _EOF && c != _openDelimiter; c = _peek()) {
       start = _offset;
 
       switch (c) {
@@ -198,7 +199,7 @@
         (_closeDelimiterInner == null && c == _closeDelimiter) ||
         (_closeDelimiterInner != null && c == _closeDelimiterInner);
 
-    for (var c = _peek(); c != _EOF && !isCloseDelimiter(c); c = _peek()) {
+    for (int c = _peek(); c != _EOF && !isCloseDelimiter(c); c = _peek()) {
       start = _offset;
 
       switch (c) {
@@ -210,7 +211,7 @@
         case _EXCLAIM:
           _read();
           token = TokenType.sigil;
-          value = String.fromCharCode(c);
+          value = new String.fromCharCode(c);
           break;
 
         case _SPACE:
@@ -254,12 +255,12 @@
   // Scan close delimiter token.
   void _scanCloseDelimiter() {
     if (_peek() != _EOF) {
-      var start = _offset;
+      int start = _offset;
 
       if (_closeDelimiterInner != null) _expect(_closeDelimiterInner);
       _expect(_closeDelimiter);
 
-      var value = String.fromCharCodes(_closeDelimiterInner == null
+      String value = new String.fromCharCodes(_closeDelimiterInner == null
           ? [_closeDelimiter]
           : [_closeDelimiterInner, _closeDelimiter]);
 
@@ -270,7 +271,7 @@
   // Scan close triple mustache delimiter token.
   void _scanCloseTripleMustache() {
     if (_peek() != _EOF) {
-      var start = _offset;
+      int start = _offset;
 
       _expect(_CLOSE_MUSTACHE);
       _expect(_CLOSE_MUSTACHE);
@@ -306,9 +307,8 @@
 
     c = _read();
 
-    if (_isWhitespace(c) || c == _EQUAL) {
+    if (_isWhitespace(c) || c == _EQUAL)
       throw _error('Incorrect change delimiter tag.');
-    }
 
     if (_isWhitespace(_peek()) || _peek() == _EQUAL) {
       _closeDelimiterInner = null;
@@ -328,7 +328,7 @@
     _expect(delimiter);
 
     // Create delimiter string.
-    var buffer = StringBuffer();
+    var buffer = new StringBuffer();
     buffer.writeCharCode(_openDelimiter);
     if (_openDelimiterInner != null) buffer.writeCharCode(_openDelimiterInner);
     buffer.write(' ');
@@ -342,7 +342,7 @@
   }
 
   TemplateException _error(String message) {
-    return TemplateException(message, _templateName, _source, _offset);
+    return new TemplateException(message, _templateName, _source, _offset);
   }
 }
 
@@ -352,13 +352,26 @@
 const int _RETURN = 13;
 const int _SPACE = 32;
 const int _EXCLAIM = 33;
+const int _QUOTE = 34;
+const int _APOS = 39;
 const int _HASH = 35;
 const int _AMP = 38;
 const int _PERIOD = 46;
 const int _FORWARD_SLASH = 47;
+const int _LT = 60;
 const int _EQUAL = 61;
 const int _GT = 62;
 const int _CARET = 94;
 
 const int _OPEN_MUSTACHE = 123;
 const int _CLOSE_MUSTACHE = 125;
+
+const int _A = 65;
+const int _Z = 90;
+const int _a = 97;
+const int _z = 122;
+const int _0 = 48;
+const int _9 = 57;
+
+const int _UNDERSCORE = 95;
+const int _MINUS = 45;
diff --git a/mustache_template/lib/src/template.dart b/mustache/lib/src/template.dart
similarity index 70%
rename from mustache_template/lib/src/template.dart
rename to mustache/lib/src/template.dart
index a38cc97..2a33b28 100644
--- a/mustache_template/lib/src/template.dart
+++ b/mustache/lib/src/template.dart
@@ -1,15 +1,17 @@
-import 'package:mustache_template/mustache.dart' as m;

+library mustache.template;

+

+import 'package:mustache/mustache.dart' as m;

 import 'node.dart';

 import 'parser.dart' as parser;

 import 'renderer.dart';

 

 class Template implements m.Template {

   Template.fromSource(String source,

-      {bool lenient = false,

-      bool htmlEscapeValues = true,

+      {bool lenient: false,

+      bool htmlEscapeValues: true,

       String name,

       m.PartialResolver partialResolver,

-      String delimiters = '{{ }}'})

+      String delimiters: "{{ }}"})

       : source = source,

         _nodes = parser.parse(source, lenient, name, delimiters),

         _lenient = lenient,

@@ -17,7 +19,6 @@
         _name = name,

         _partialResolver = partialResolver;

 

-  @override

   final String source;

   final List<Node> _nodes;

   final bool _lenient;

@@ -25,23 +26,20 @@
   final String _name;

   final m.PartialResolver _partialResolver;

 

-  @override

   String get name => _name;

 

-  @override

   String renderString(values) {

-    var buf = StringBuffer();

+    var buf = new StringBuffer();

     render(values, buf);

     return buf.toString();

   }

 

-  @override

   void render(values, StringSink sink) {

-    var renderer = Renderer(sink, [values], _lenient, _htmlEscapeValues,

+    var renderer = new Renderer(sink, [values], _lenient, _htmlEscapeValues,

         _partialResolver, _name, '', source);

     renderer.render(_nodes);

   }

 }

 

 // Expose getter for nodes internally within this package.

-List<Node> getTemplateNodes(Template template) => template._nodes;

+getTemplateNodes(Template template) => template._nodes;

diff --git a/mustache_template/lib/src/template_exception.dart b/mustache/lib/src/template_exception.dart
similarity index 70%
rename from mustache_template/lib/src/template_exception.dart
rename to mustache/lib/src/template_exception.dart
index d067bdd..c1b24ce 100644
--- a/mustache_template/lib/src/template_exception.dart
+++ b/mustache/lib/src/template_exception.dart
@@ -1,15 +1,13 @@
-import 'package:mustache_template/mustache.dart' as m;
+library mustache.template_exception;
+
+import 'package:mustache/mustache.dart' as m;
 
 class TemplateException implements m.TemplateException {
   TemplateException(this.message, this.templateName, this.source, this.offset);
 
-  @override
   final String message;
-  @override
   final String templateName;
-  @override
   final String source;
-  @override
   final int offset;
 
   bool _isUpdated = false;
@@ -17,25 +15,21 @@
   int _column;
   String _context;
 
-  @override
   int get line {
     _update();
     return _line;
   }
 
-  @override
   int get column {
     _update();
     return _column;
   }
 
-  @override
   String get context {
     _update();
     return _context;
   }
 
-  @override
   String toString() {
     var list = [];
     if (templateName != null) list.add(templateName);
@@ -55,14 +49,14 @@
         (offset < 0 || offset > source.length)) return;
 
     // Find line and character column.
-    var lineNum = 1;
-    var lineStart = 0;
-    var lastWasCR = false;
-    for (var i = 0; i < offset; i++) {
-      var char = source.codeUnitAt(i);
+    int lineNum = 1;
+    int lineStart = 0;
+    bool lastWasCR = false;
+    for (int i = 0; i < offset; i++) {
+      int char = source.codeUnitAt(i);
       if (char == 0x0a) {
         if (lineStart != i || !lastWasCR) {
-          lineNum += 1;
+          lineNum++;
         }
         lineStart = i + 1;
         lastWasCR = false;
@@ -77,38 +71,38 @@
     _column = offset - lineStart + 1;
 
     // Find context.
-    var lineEnd = source.length;
-    for (var i = offset; i < source.length; i++) {
-      var char = source.codeUnitAt(i);
+    int lineEnd = source.length;
+    for (int i = offset; i < source.length; i++) {
+      int char = source.codeUnitAt(i);
       if (char == 0x0a || char == 0x0d) {
         lineEnd = i;
         break;
       }
     }
-    var length = lineEnd - lineStart;
-    var start = lineStart;
-    var end = lineEnd;
-    var prefix = '';
-    var postfix = '';
+    int length = lineEnd - lineStart;
+    int start = lineStart;
+    int end = lineEnd;
+    String prefix = "";
+    String postfix = "";
     if (length > 78) {
       // Can't show entire line. Try to anchor at the nearest end, if
       // one is within reach.
-      var index = offset - lineStart;
+      int index = offset - lineStart;
       if (index < 75) {
         end = start + 75;
-        postfix = '...';
+        postfix = "...";
       } else if (end - offset < 75) {
         start = end - 75;
-        prefix = '...';
+        prefix = "...";
       } else {
         // Neither end is near, just pick an area around the offset.
         start = offset - 36;
         end = offset + 36;
-        prefix = postfix = '...';
+        prefix = postfix = "...";
       }
     }
-    var slice = source.substring(start, end);
-    var markOffset = offset - start + prefix.length;
+    String slice = source.substring(start, end);
+    int markOffset = offset - start + prefix.length;
 
     _context = "$prefix$slice$postfix\n${" " * markOffset}^\n";
   }
diff --git a/mustache/lib/src/token.dart b/mustache/lib/src/token.dart
new file mode 100644
index 0000000..e4d7b59
--- /dev/null
+++ b/mustache/lib/src/token.dart
@@ -0,0 +1,35 @@
+library mustache.token;
+
+class TokenType {
+  const TokenType(this.name);
+
+  final String name;
+
+  String toString() => '(TokenType $name)';
+
+  static const TokenType text = const TokenType('text');
+  static const TokenType openDelimiter = const TokenType('openDelimiter');
+  static const TokenType closeDelimiter = const TokenType('closeDelimiter');
+
+  // A sigil is the word commonly used to describe the special character at the
+  // start of mustache tag i.e. #, ^ or /.
+  static const TokenType sigil = const TokenType('sigil');
+  static const TokenType identifier = const TokenType('identifier');
+  static const TokenType dot = const TokenType('dot');
+
+  static const TokenType changeDelimiter = const TokenType('changeDelimiter');
+  static const TokenType whitespace = const TokenType('whitespace');
+  static const TokenType lineEnd = const TokenType('lineEnd');
+}
+
+class Token {
+  Token(this.type, this.value, this.start, this.end);
+
+  final TokenType type;
+  final String value;
+
+  final int start;
+  final int end;
+
+  String toString() => "(Token ${type.name} \"$value\" $start $end)";
+}
diff --git a/mustache/pubspec.yaml b/mustache/pubspec.yaml
new file mode 100644
index 0000000..ed01ed6
--- /dev/null
+++ b/mustache/pubspec.yaml
@@ -0,0 +1,11 @@
+name: mustache
+version: 1.1.1
+author: Greg Lowe <greg@vis.net.nz>
+description: Mustache template library
+homepage: https://github.com/xxgreg/mustache
+environment:
+  sdk: '>=2.0.0 <3.0.0'
+dev_dependencies:
+  test: '>=0.12.0 <2.0.0'
+analyzer:
+  strong-mode: true
diff --git a/mustache_template/tool/travis.sh b/mustache/tool/travis.sh
similarity index 100%
rename from mustache_template/tool/travis.sh
rename to mustache/tool/travis.sh
diff --git a/mustache_template/analysis_options.yaml b/mustache_template/analysis_options.yaml
deleted file mode 100644
index 108d105..0000000
--- a/mustache_template/analysis_options.yaml
+++ /dev/null
@@ -1 +0,0 @@
-include: package:pedantic/analysis_options.yaml
diff --git a/mustache_template/lib/mustache_template.dart b/mustache_template/lib/mustache_template.dart
deleted file mode 100644
index 299de3a..0000000
--- a/mustache_template/lib/mustache_template.dart
+++ /dev/null
@@ -1 +0,0 @@
-export 'mustache.dart';
diff --git a/mustache_template/lib/src/token.dart b/mustache_template/lib/src/token.dart
deleted file mode 100644
index d31361e..0000000
--- a/mustache_template/lib/src/token.dart
+++ /dev/null
@@ -1,35 +0,0 @@
-class TokenType {
-  const TokenType(this.name);
-
-  final String name;
-
-  @override
-  String toString() => '(TokenType $name)';
-
-  static const TokenType text = TokenType('text');
-  static const TokenType openDelimiter = TokenType('openDelimiter');
-  static const TokenType closeDelimiter = TokenType('closeDelimiter');
-
-  // A sigil is the word commonly used to describe the special character at the
-  // start of mustache tag i.e. #, ^ or /.
-  static const TokenType sigil = TokenType('sigil');
-  static const TokenType identifier = TokenType('identifier');
-  static const TokenType dot = TokenType('dot');
-
-  static const TokenType changeDelimiter = TokenType('changeDelimiter');
-  static const TokenType whitespace = TokenType('whitespace');
-  static const TokenType lineEnd = TokenType('lineEnd');
-}
-
-class Token {
-  Token(this.type, this.value, this.start, this.end);
-
-  final TokenType type;
-  final String value;
-
-  final int start;
-  final int end;
-
-  @override
-  String toString() => '(Token ${type.name} \"$value\" $start $end)';
-}
diff --git a/mustache_template/pubspec.yaml b/mustache_template/pubspec.yaml
deleted file mode 100644
index 216a1e4..0000000
--- a/mustache_template/pubspec.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-name: mustache_template
-version: 1.0.0+1
-description: A mustache template library that supports dart2js and dart2native
-homepage: https://github.com/jonahwilliams/mustache
-
-environment:
-  sdk: '>=2.0.0 <3.0.0'
-
-dev_dependencies:
-  test: '>=0.12.0 <2.0.0'
-  pedantic: 1.9.0
diff --git a/package_config/.travis.yml b/package_config/.travis.yml
index 09fc296..655bf3d 100644
--- a/package_config/.travis.yml
+++ b/package_config/.travis.yml
@@ -7,11 +7,6 @@
   - dartfmt
   - dartanalyzer: --fatal-warnings .
 
-matrix:
-  include:
-  - dart: dev
-    script: pub run build_runner test -- -p chrome
-
 # Only building master means that we don't run two builds for each pull request.
 branches:
   only: [master]
diff --git a/package_config/BUILD.gn b/package_config/BUILD.gn
index 75d009c..d100e3f 100644
--- a/package_config/BUILD.gn
+++ b/package_config/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by importer.py for package_config-1.9.1
+# This file is generated by importer.py for package_config-1.1.0
 
 import("//build/dart/dart_library.gni")
 
diff --git a/package_config/CHANGELOG.md b/package_config/CHANGELOG.md
index 1cd45d0..db4bb00 100644
--- a/package_config/CHANGELOG.md
+++ b/package_config/CHANGELOG.md
@@ -1,15 +1,3 @@
-## 1.9.1
-
-- Remove accidental transitive import of `dart:io` from entrypoints that are
-  supposed to be cross-platform compatible.
-
-## 1.9.0
-
-- Based on new JSON file format with more content.
-- This version includes all the new functionality intended for a 2.0.0
-  version, as well as the, now deprecated, version 1 functionality.
-  When we release 2.0.0, the deprectated functionality will be removed.
-
 ## 1.1.0
 
 - Allow parsing files with default-package entries and metadata.
diff --git a/package_config/CONTRIBUTING.md b/package_config/CONTRIBUTING.md
index 8423ff9..6f5e0ea 100644
--- a/package_config/CONTRIBUTING.md
+++ b/package_config/CONTRIBUTING.md
@@ -23,7 +23,7 @@
 ### File headers
 All files in the project must start with the following header.
 
-    // Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
+    // Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS 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.
 
diff --git a/package_config/LICENSE b/package_config/LICENSE
index f75d7c2..de31e1a 100644
--- a/package_config/LICENSE
+++ b/package_config/LICENSE
@@ -1,4 +1,4 @@
-Copyright 2019, the Dart project authors. All rights reserved.
+Copyright 2015, the Dart project authors. All rights reserved.
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are
 met:
diff --git a/package_config/README.md b/package_config/README.md
index b47a682..9428220 100644
--- a/package_config/README.md
+++ b/package_config/README.md
@@ -1,18 +1,13 @@
-[![Build Status](https://travis-ci.org/dart-lang/package_config.svg?branch=master)](https://travis-ci.org/dart-lang/package_config)
-[![pub package](https://img.shields.io/pub/v/package_config.svg)](https://pub.dev/packages/package_config)
+# package_config
 
-Support for working with **Package Configuration** files as described
-in the Package Configuration v2 [design document](https://github.com/dart-lang/language/blob/master/accepted/future-releases/language-versioning/package-config-file-v2.md).
+Support for working with **Package Resolution Configuration** files as described 
+in this [DEP](https://github.com/lrhn/dep-pkgspec/blob/master/DEP-pkgspec.md), 
+under review [here](https://github.com/dart-lang/dart_enhancement_proposals/issues/5).
 
-The primary libraries are
-* `package_config.dart`:
-    Defines the `PackageConfig` class and other types needed to use
-    package configurations.
+[![Build Status](https://travis-ci.org/dart-lang/package_config.svg?branch=master)](https://travis-ci.org/dart-lang/package_config) [![pub package](https://img.shields.io/pub/v/package_config.svg)](https://pub.dartlang.org/packages/package_config) 
 
-* `package_config_discovery.dart`:
-    Provides functions for reading configurations from files,
-    and writing them back out.
+## Features and bugs
 
-The package includes deprecated backwards compatible functionality to
-work with the `.packages` file. This functionality will not be maintained,
-and will be removed in a future version of this package.
+Please file feature requests and bugs at the [issue tracker][tracker].
+
+[tracker]: https://github.com/dart-lang/package_config/issues
diff --git a/package_config/analysis_options.yaml b/package_config/analysis_options.yaml
deleted file mode 100644
index 66639ec..0000000
--- a/package_config/analysis_options.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-# Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS 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.
-
-include: package:pedantic/analysis_options.1.9.0.yaml
-analyzer:
-  errors:
-    annotate_overrides: ignore
-    curly_braces_in_flow_control_structures: ignore
-    prefer_single_quotes: ignore
-    use_function_type_syntax_for_parameters: ignore
diff --git a/package_config/lib/discovery.dart b/package_config/lib/discovery.dart
index a2f53c0..57584b6 100644
--- a/package_config/lib/discovery.dart
+++ b/package_config/lib/discovery.dart
@@ -2,7 +2,6 @@
 // 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.
 
-@Deprecated("Use the package_config.json based API")
 library package_config.discovery;
 
 import "dart:async";
@@ -31,11 +30,13 @@
 Future<Packages> loadPackagesFile(Uri packagesFile,
     {Future<List<int>> loader(Uri uri)}) async {
   Packages parseBytes(List<int> bytes) {
-    return MapPackages(pkgfile.parse(bytes, packagesFile));
+    Map<String, Uri> packageMap = pkgfile.parse(bytes, packagesFile);
+    return new MapPackages(packageMap);
   }
 
   if (packagesFile.scheme == "file") {
-    return parseBytes(await File.fromUri(packagesFile).readAsBytes());
+    File file = new File.fromUri(packagesFile);
+    return parseBytes(await file.readAsBytes());
   }
   if (loader == null) {
     return parseBytes(await _httpGet(packagesFile));
@@ -53,12 +54,13 @@
 /// for example one specified using a `--package-root` comand-line parameter.
 Packages getPackagesDirectory(Uri packagesDir) {
   if (packagesDir.scheme == "file") {
-    return FilePackagesDirectoryPackages(Directory.fromUri(packagesDir));
+    Directory directory = new Directory.fromUri(packagesDir);
+    return new FilePackagesDirectoryPackages(directory);
   }
   if (!packagesDir.path.endsWith('/')) {
     packagesDir = packagesDir.replace(path: packagesDir.path + '/');
   }
-  return NonFilePackagesDirectoryPackages(packagesDir);
+  return new NonFilePackagesDirectoryPackages(packagesDir);
 }
 
 /// Discover the package configuration for a Dart script.
@@ -92,13 +94,13 @@
 Future<Packages> findPackages(Uri baseUri,
     {Future<List<int>> loader(Uri unsupportedUri)}) {
   if (baseUri.scheme == "file") {
-    return Future<Packages>.sync(() => findPackagesFromFile(baseUri));
+    return new Future<Packages>.sync(() => findPackagesFromFile(baseUri));
   } else if (loader != null) {
     return findPackagesFromNonFile(baseUri, loader: loader);
   } else if (baseUri.scheme == "http" || baseUri.scheme == "https") {
     return findPackagesFromNonFile(baseUri, loader: _httpGet);
   } else {
-    return Future<Packages>.value(Packages.noPackages);
+    return new Future<Packages>.value(Packages.noPackages);
   }
 }
 
@@ -112,15 +114,15 @@
 /// Returns a [File] object of a `.packages` file if one is found, or a
 /// [Directory] object for the `packages/` directory if that is found.
 FileSystemEntity _findPackagesFile(String workingDirectory) {
-  var dir = Directory(workingDirectory);
+  var dir = new Directory(workingDirectory);
   if (!dir.isAbsolute) dir = dir.absolute;
   if (!dir.existsSync()) {
-    throw ArgumentError.value(
+    throw new ArgumentError.value(
         workingDirectory, "workingDirectory", "Directory does not exist.");
   }
   File checkForConfigFile(Directory directory) {
     assert(directory.isAbsolute);
-    var file = File(path.join(directory.path, ".packages"));
+    var file = new File(path.join(directory.path, ".packages"));
     if (file.existsSync()) return file;
     return null;
   }
@@ -129,7 +131,7 @@
   var packagesCfgFile = checkForConfigFile(dir);
   if (packagesCfgFile != null) return packagesCfgFile;
   // Check for $cwd/packages/
-  var packagesDir = Directory(path.join(dir.path, "packages"));
+  var packagesDir = new Directory(path.join(dir.path, "packages"));
   if (packagesDir.existsSync()) return packagesDir;
   // Check for cwd(/..)+/.packages
   var parentDir = dir.parent;
@@ -154,20 +156,21 @@
 /// file, and stops if it finds it.
 /// Otherwise it gives up and returns [Packages.noPackages].
 Packages findPackagesFromFile(Uri fileBaseUri) {
-  var baseDirectoryUri = fileBaseUri;
+  Uri baseDirectoryUri = fileBaseUri;
   if (!fileBaseUri.path.endsWith('/')) {
     baseDirectoryUri = baseDirectoryUri.resolve(".");
   }
-  var baseDirectoryPath = baseDirectoryUri.toFilePath();
-  var location = _findPackagesFile(baseDirectoryPath);
+  String baseDirectoryPath = baseDirectoryUri.toFilePath();
+  FileSystemEntity location = _findPackagesFile(baseDirectoryPath);
   if (location == null) return Packages.noPackages;
   if (location is File) {
-    var fileBytes = location.readAsBytesSync();
-    var map = pkgfile.parse(fileBytes, Uri.file(location.path));
-    return MapPackages(map);
+    List<int> fileBytes = location.readAsBytesSync();
+    Map<String, Uri> map =
+        pkgfile.parse(fileBytes, new Uri.file(location.path));
+    return new MapPackages(map);
   }
   assert(location is Directory);
-  return FilePackagesDirectoryPackages(location);
+  return new FilePackagesDirectoryPackages(location);
 }
 
 /// Finds a package resolution strategy for a Dart script.
@@ -189,37 +192,37 @@
 /// UTF-8 encoded.
 Future<Packages> findPackagesFromNonFile(Uri nonFileUri,
     {Future<List<int>> loader(Uri name)}) async {
-  loader ??= _httpGet;
-  var packagesFileUri = nonFileUri.resolve(".packages");
+  if (loader == null) loader = _httpGet;
+  Uri packagesFileUri = nonFileUri.resolve(".packages");
 
   try {
-    var fileBytes = await loader(packagesFileUri);
-    var map = pkgfile.parse(fileBytes, packagesFileUri);
-    return MapPackages(map);
+    List<int> fileBytes = await loader(packagesFileUri);
+    Map<String, Uri> map = pkgfile.parse(fileBytes, packagesFileUri);
+    return new MapPackages(map);
   } catch (_) {
     // Didn't manage to load ".packages". Assume a "packages/" directory.
-    var packagesDirectoryUri = nonFileUri.resolve("packages/");
-    return NonFilePackagesDirectoryPackages(packagesDirectoryUri);
+    Uri packagesDirectoryUri = nonFileUri.resolve("packages/");
+    return new NonFilePackagesDirectoryPackages(packagesDirectoryUri);
   }
 }
 
 /// Fetches a file over http.
 Future<List<int>> _httpGet(Uri uri) async {
-  var client = HttpClient();
-  var request = await client.getUrl(uri);
-  var response = await request.close();
+  HttpClient client = new HttpClient();
+  HttpClientRequest request = await client.getUrl(uri);
+  HttpClientResponse response = await request.close();
   if (response.statusCode != HttpStatus.ok) {
-    throw HttpException('${response.statusCode} ${response.reasonPhrase}',
+    throw new HttpException('${response.statusCode} ${response.reasonPhrase}',
         uri: uri);
   }
-  var splitContent = await response.toList();
-  var totalLength = 0;
+  List<List<int>> splitContent = await response.toList();
+  int totalLength = 0;
   for (var list in splitContent) {
     totalLength += list.length;
   }
-  var result = Uint8List(totalLength);
-  var offset = 0;
-  for (var contentPart in splitContent) {
+  Uint8List result = new Uint8List(totalLength);
+  int offset = 0;
+  for (List<int> contentPart in splitContent) {
     result.setRange(offset, offset + contentPart.length, contentPart);
     offset += contentPart.length;
   }
diff --git a/package_config/lib/discovery_analysis.dart b/package_config/lib/discovery_analysis.dart
index 2af0729..d623303 100644
--- a/package_config/lib/discovery_analysis.dart
+++ b/package_config/lib/discovery_analysis.dart
@@ -10,7 +10,6 @@
 /// but more efficiently and with some heuristics for directories that
 /// wouldn't otherwise have a package resolution strategy, or that are
 /// determined to be "package directories" themselves.
-@Deprecated("Use the package_config.json based API")
 library package_config.discovery_analysis;
 
 import "dart:collection" show HashMap;
@@ -58,7 +57,7 @@
   /// directory of `directory`. If there is, its corresponding `Packages` object
   /// should be provided as `root`.
   static PackageContext findAll(Directory directory,
-      {Packages root = Packages.noPackages}) {
+      {Packages root: Packages.noPackages}) {
     if (!directory.existsSync()) {
       throw ArgumentError("Directory not found: $directory");
     }
@@ -66,13 +65,14 @@
     void findRoots(Directory directory) {
       Packages packages;
       List<PackageContext> oldContexts;
-      var packagesFile = File(path.join(directory.path, ".packages"));
+      File packagesFile = File(path.join(directory.path, ".packages"));
       if (packagesFile.existsSync()) {
         packages = _loadPackagesFile(packagesFile);
         oldContexts = contexts;
         contexts = [];
       } else {
-        var packagesDir = Directory(path.join(directory.path, "packages"));
+        Directory packagesDir =
+            Directory(path.join(directory.path, "packages"));
         if (packagesDir.existsSync()) {
           packages = FilePackagesDirectoryPackages(packagesDir);
           oldContexts = contexts;
@@ -110,7 +110,7 @@
 
   Map<Directory, Packages> asMap() {
     var result = HashMap<Directory, Packages>();
-    void recurse(_PackageContext current) {
+    recurse(_PackageContext current) {
       result[current.directory] = current.packages;
       for (var child in current.children) {
         recurse(child);
@@ -122,19 +122,19 @@
   }
 
   PackageContext operator [](Directory directory) {
-    var path = directory.path;
+    String path = directory.path;
     if (!path.startsWith(this.directory.path)) {
       throw ArgumentError("Not inside $path: $directory");
     }
-    var current = this;
+    _PackageContext current = this;
     // The current path is know to agree with directory until deltaIndex.
-    var deltaIndex = current.directory.path.length;
+    int deltaIndex = current.directory.path.length;
     List children = current.children;
-    var i = 0;
+    int i = 0;
     while (i < children.length) {
       // TODO(lrn): Sort children and use binary search.
       _PackageContext child = children[i];
-      var childPath = child.directory.path;
+      String childPath = child.directory.path;
       if (_stringsAgree(path, childPath, deltaIndex, childPath.length)) {
         deltaIndex = childPath.length;
         if (deltaIndex == path.length) {
@@ -152,7 +152,7 @@
 
   static bool _stringsAgree(String a, String b, int start, int end) {
     if (a.length < end || b.length < end) return false;
-    for (var i = start; i < end; i++) {
+    for (int i = start; i < end; i++) {
       if (a.codeUnitAt(i) != b.codeUnitAt(i)) return false;
     }
     return true;
diff --git a/package_config/lib/package_config.dart b/package_config/lib/package_config.dart
deleted file mode 100644
index bca865d..0000000
--- a/package_config/lib/package_config.dart
+++ /dev/null
@@ -1,174 +0,0 @@
-// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS 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.
-
-/// A package configuration is a way to assign file paths to package URIs,
-/// and vice-versa,
-library package_config.package_config_discovery;
-
-import "dart:io" show File, Directory;
-import "dart:typed_data" show Uint8List;
-
-import "src/discovery.dart" as discover;
-import "src/errors.dart" show throwError;
-import "src/package_config.dart";
-import "src/package_config_io.dart";
-
-export "package_config_types.dart";
-
-/// Reads a specific package configuration file.
-///
-/// The file must exist and be readable.
-/// It must be either a valid `package_config.json` file
-/// or a valid `.packages` file.
-/// It is considered a `package_config.json` file if its first character
-/// is a `{`.
-///
-/// If the file is a `.packages` file and [preferNewest] is true, the default,
-/// also checks if there is a `.dart_tool/package_config.json` file next to the original file,
-/// and if so, loads that instead.
-/// If [preferNewest] is set to false, a directly specified `.packages` file
-/// is loaded even if there is an available `package_config.json` file.
-/// The caller can determine this from the [PackageConfig.version]
-/// being 1 and look for a `package_config.json` file themselves.
-///
-/// If [onError] is provided, the configuration file parsing will report errors
-/// by calling that function, and then try to recover.
-/// The returned package configuration is a *best effort* attempt to create
-/// a valid configuration from the invalid configuration file.
-/// If no [onError] is provided, errors are thrown immediately.
-Future<PackageConfig> loadPackageConfig(File file,
-        {bool preferNewest = true, void onError(Object error)}) =>
-    readAnyConfigFile(file, preferNewest, onError ?? throwError);
-
-/// Reads a specific package configuration URI.
-///
-/// The file of the URI must exist and be readable.
-/// It must be either a valid `package_config.json` file
-/// or a valid `.packages` file.
-/// It is considered a `package_config.json` file if its first
-/// non-whitespace character is a `{`.
-///
-/// If [preferNewest] is true, the default, and the file is a `.packages` file,
-/// first checks if there is a `.dart_tool/package_config.json` file
-/// next to the original file, and if so, loads that instead.
-/// The [file] *must not* be a `package:` URI.
-/// If [preferNewest] is set to false, a directly specified `.packages` file
-/// is loaded even if there is an available `package_config.json` file.
-/// The caller can determine this from the [PackageConfig.version]
-/// being 1 and look for a `package_config.json` file themselves.
-///
-/// If [loader] is provided, URIs are loaded using that function.
-/// The future returned by the loader must complete with a [Uint8List]
-/// containing the entire file content encoded as UTF-8,
-/// or with `null` if the file does not exist.
-/// The loader may throw at its own discretion, for situations where
-/// it determines that an error might be need user attention,
-/// but it is always allowed to return `null`.
-/// This function makes no attempt to catch such errors.
-/// As such, it may throw any error that [loader] throws.
-///
-/// If no [loader] is supplied, a default loader is used which
-/// only accepts `file:`,  `http:` and `https:` URIs,
-/// and which uses the platform file system and HTTP requests to
-/// fetch file content. The default loader never throws because
-/// of an I/O issue, as long as the location URIs are valid.
-/// As such, it does not distinguish between a file not existing,
-/// and it being temporarily locked or unreachable.
-///
-/// If [onError] is provided, the configuration file parsing will report errors
-/// by calling that function, and then try to recover.
-/// The returned package configuration is a *best effort* attempt to create
-/// a valid configuration from the invalid configuration file.
-/// If no [onError] is provided, errors are thrown immediately.
-Future<PackageConfig> loadPackageConfigUri(Uri file,
-        {Future<Uint8List /*?*/ > loader(Uri uri) /*?*/,
-        bool preferNewest = true,
-        void onError(Object error)}) =>
-    readAnyConfigFileUri(file, loader, onError ?? throwError, preferNewest);
-
-/// Finds a package configuration relative to [directory].
-///
-/// If [directory] contains a package configuration,
-/// either a `.dart_tool/package_config.json` file or,
-/// if not, a `.packages`, then that file is loaded.
-///
-/// If no file is found in the current directory,
-/// then the parent directories are checked recursively,
-/// all the way to the root directory, to check if those contains
-/// a package configuration.
-/// If [recurse] is set to [false], this parent directory check is not
-/// performed.
-///
-/// If [onError] is provided, the configuration file parsing will report errors
-/// by calling that function, and then try to recover.
-/// The returned package configuration is a *best effort* attempt to create
-/// a valid configuration from the invalid configuration file.
-/// If no [onError] is provided, errors are thrown immediately.
-///
-/// Returns `null` if no configuration file is found.
-Future<PackageConfig> findPackageConfig(Directory directory,
-        {bool recurse = true, void onError(Object error)}) =>
-    discover.findPackageConfig(directory, recurse, onError ?? throwError);
-
-/// Finds a package configuration relative to [location].
-///
-/// If [location] contains a package configuration,
-/// either a `.dart_tool/package_config.json` file or,
-/// if not, a `.packages`, then that file is loaded.
-/// The [location] URI *must not* be a `package:` URI.
-/// It should be a hierarchical URI which is supported
-/// by [loader].
-///
-/// If no file is found in the current directory,
-/// then the parent directories are checked recursively,
-/// all the way to the root directory, to check if those contains
-/// a package configuration.
-/// If [recurse] is set to [false], this parent directory check is not
-/// performed.
-///
-/// If [loader] is provided, URIs are loaded using that function.
-/// The future returned by the loader must complete with a [Uint8List]
-/// containing the entire file content,
-/// or with `null` if the file does not exist.
-/// The loader may throw at its own discretion, for situations where
-/// it determines that an error might be need user attention,
-/// but it is always allowed to return `null`.
-/// This function makes no attempt to catch such errors.
-///
-/// If no [loader] is supplied, a default loader is used which
-/// only accepts `file:`,  `http:` and `https:` URIs,
-/// and which uses the platform file system and HTTP requests to
-/// fetch file content. The default loader never throws because
-/// of an I/O issue, as long as the location URIs are valid.
-/// As such, it does not distinguish between a file not existing,
-/// and it being temporarily locked or unreachable.
-///
-/// If [onError] is provided, the configuration file parsing will report errors
-/// by calling that function, and then try to recover.
-/// The returned package configuration is a *best effort* attempt to create
-/// a valid configuration from the invalid configuration file.
-/// If no [onError] is provided, errors are thrown immediately.
-///
-/// Returns `null` if no configuration file is found.
-Future<PackageConfig> findPackageConfigUri(Uri location,
-        {bool recurse = true,
-        Future<Uint8List /*?*/ > loader(Uri uri),
-        void onError(Object error)}) =>
-    discover.findPackageConfigUri(
-        location, loader, onError ?? throwError, recurse);
-
-/// Writes a package configuration to the provided directory.
-///
-/// Writes `.dart_tool/package_config.json` relative to [directory].
-/// If the `.dart_tool/` directory does not exist, it is created.
-/// If it cannot be created, this operation fails.
-///
-/// Also writes a `.packages` file in [directory].
-/// This will stop happening eventually as the `.packages` file becomes
-/// discontinued.
-/// A comment is generated if `[PackageConfig.extraData]` contains a
-/// `"generator"` entry.
-Future<void> savePackageConfig(
-        PackageConfig configuration, Directory directory) =>
-    writePackageConfigJsonFile(configuration, directory);
diff --git a/package_config/lib/package_config_types.dart b/package_config/lib/package_config_types.dart
deleted file mode 100644
index f0637b1..0000000
--- a/package_config/lib/package_config_types.dart
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS 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.
-
-/// A package configuration is a way to assign file paths to package URIs,
-/// and vice-versa,
-library package_config.package_config;
-
-export "src/package_config.dart"
-    show PackageConfig, Package, LanguageVersion, InvalidLanguageVersion;
-export "src/errors.dart" show PackageConfigError;
diff --git a/package_config/lib/packages.dart b/package_config/lib/packages.dart
index 203f32f..886fbc8 100644
--- a/package_config/lib/packages.dart
+++ b/package_config/lib/packages.dart
@@ -2,7 +2,6 @@
 // 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.
 
-@Deprecated("Use the package_config.json based API")
 library package_config.packages;
 
 import "src/packages_impl.dart";
@@ -17,13 +16,12 @@
 /// in which case [packages] and [asMap] will throw if used.
 /// One such case is if the packages are resolved relative to a
 /// `packages/` directory available over HTTP.
-@Deprecated("Use the package_config.json based API")
 abstract class Packages {
   /// A [Packages] resolver containing no packages.
   ///
   /// This constant object is returned by [find] above if no
   /// package resolution strategy is found.
-  static const Packages noPackages = NoPackages();
+  static const Packages noPackages = const NoPackages();
 
   /// Resolve a package URI into a non-package URI.
   ///
diff --git a/package_config/lib/packages_file.dart b/package_config/lib/packages_file.dart
index ef0b0b3..284d8e9 100644
--- a/package_config/lib/packages_file.dart
+++ b/package_config/lib/packages_file.dart
@@ -2,7 +2,6 @@
 // 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.
 
-@Deprecated("Use the package_config.json based API")
 library package_config.packages_file;
 
 import "package:charcode/ascii.dart";
@@ -32,14 +31,14 @@
 /// If default package is allowed, the map maps the empty string to the default package's name.
 Map<String, Uri> parse(List<int> source, Uri baseLocation,
     {bool allowDefaultPackage = false}) {
-  var index = 0;
-  var result = <String, Uri>{};
+  int index = 0;
+  Map<String, Uri> result = <String, Uri>{};
   while (index < source.length) {
-    var isComment = false;
-    var start = index;
-    var separatorIndex = -1;
-    var end = source.length;
-    var char = source[index++];
+    bool isComment = false;
+    int start = index;
+    int separatorIndex = -1;
+    int end = source.length;
+    int char = source[index++];
     if (char == $cr || char == $lf) {
       continue;
     }
@@ -63,13 +62,14 @@
     if (separatorIndex < 0) {
       throw FormatException("No ':' on line", source, index - 1);
     }
-    var packageName = String.fromCharCodes(source, start, separatorIndex);
+    var packageName = new String.fromCharCodes(source, start, separatorIndex);
     if (packageName.isEmpty
         ? !allowDefaultPackage
         : !isValidPackageName(packageName)) {
       throw FormatException("Not a valid package name", packageName, 0);
     }
-    var packageValue = String.fromCharCodes(source, separatorIndex + 1, end);
+    var packageValue =
+        new String.fromCharCodes(source, separatorIndex + 1, end);
     Uri packageLocation;
     if (packageName.isEmpty) {
       if (!isValidPackageName(packageValue)) {
@@ -106,17 +106,12 @@
 /// If [baseUri] is provided, package locations will be made relative
 /// to the base URI, if possible, before writing.
 ///
-/// If [allowDefaultPackage] is `true`, the [packageMapping] may contain an
-/// empty string mapping to the _default package name_.
-///
 /// All the keys of [packageMapping] must be valid package names,
 /// and the values must be URIs that do not have the `package:` scheme.
 void write(StringSink output, Map<String, Uri> packageMapping,
-    {Uri baseUri, String comment, bool allowDefaultPackage = false}) {
-  ArgumentError.checkNotNull(allowDefaultPackage, 'allowDefaultPackage');
-
+    {Uri baseUri, String comment}) {
   if (baseUri != null && !baseUri.isAbsolute) {
-    throw ArgumentError.value(baseUri, "baseUri", "Must be absolute");
+    throw new ArgumentError.value(baseUri, "baseUri", "Must be absolute");
   }
 
   if (comment != null) {
@@ -128,32 +123,17 @@
     }
   } else {
     output.write("# generated by package:package_config at ");
-    output.write(DateTime.now());
+    output.write(new DateTime.now());
     output.writeln();
   }
 
   packageMapping.forEach((String packageName, Uri uri) {
-    // If [packageName] is empty then [uri] is the _default package name_.
-    if (allowDefaultPackage && packageName.isEmpty) {
-      final defaultPackageName = uri.toString();
-      if (!isValidPackageName(defaultPackageName)) {
-        throw ArgumentError.value(
-          defaultPackageName,
-          'defaultPackageName',
-          '"$defaultPackageName" is not a valid package name',
-        );
-      }
-      output.write(':');
-      output.write(defaultPackageName);
-      output.writeln();
-      return;
-    }
     // Validate packageName.
     if (!isValidPackageName(packageName)) {
-      throw ArgumentError('"$packageName" is not a valid package name');
+      throw new ArgumentError('"$packageName" is not a valid package name');
     }
     if (uri.scheme == "package") {
-      throw ArgumentError.value(
+      throw new ArgumentError.value(
           "Package location must not be a package: URI", uri.toString());
     }
     output.write(packageName);
@@ -162,10 +142,10 @@
     if (baseUri != null) {
       uri = _relativize(uri, baseUri);
     }
-    if (!uri.path.endsWith('/')) {
-      uri = uri.replace(path: uri.path + '/');
-    }
     output.write(uri);
+    if (!uri.path.endsWith('/')) {
+      output.write('/');
+    }
     output.writeln();
   });
 }
@@ -178,7 +158,7 @@
 Uri _relativize(Uri uri, Uri baseUri) {
   assert(baseUri.isAbsolute);
   if (uri.hasQuery || uri.hasFragment) {
-    uri = Uri(
+    uri = new Uri(
         scheme: uri.scheme,
         userInfo: uri.hasAuthority ? uri.userInfo : null,
         host: uri.hasAuthority ? uri.host : null,
@@ -204,14 +184,14 @@
   }
 
   baseUri = baseUri.normalizePath();
-  var base = baseUri.pathSegments.toList();
+  List<String> base = baseUri.pathSegments.toList();
   if (base.isNotEmpty) {
-    base = List<String>.from(base)..removeLast();
+    base = new List<String>.from(base)..removeLast();
   }
   uri = uri.normalizePath();
-  var target = uri.pathSegments.toList();
+  List<String> target = uri.pathSegments.toList();
   if (target.isNotEmpty && target.last.isEmpty) target.removeLast();
-  var index = 0;
+  int index = 0;
   while (index < base.length && index < target.length) {
     if (base[index] != target[index]) {
       break;
@@ -220,11 +200,11 @@
   }
   if (index == base.length) {
     if (index == target.length) {
-      return Uri(path: "./");
+      return new Uri(path: "./");
     }
-    return Uri(path: target.skip(index).join('/'));
+    return new Uri(path: target.skip(index).join('/'));
   } else if (index > 0) {
-    return Uri(
+    return new Uri(
         path: '../' * (base.length - index) + target.skip(index).join('/'));
   } else {
     return uri;
diff --git a/package_config/lib/src/discovery.dart b/package_config/lib/src/discovery.dart
deleted file mode 100644
index 8ac6a01..0000000
--- a/package_config/lib/src/discovery.dart
+++ /dev/null
@@ -1,130 +0,0 @@
-// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS 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 "dart:io";
-import 'dart:typed_data';
-
-import 'package_config_io.dart';
-
-import "errors.dart";
-import "package_config_impl.dart";
-import "package_config_json.dart";
-import "packages_file.dart" as packages_file;
-import "util_io.dart" show defaultLoader, pathJoin;
-
-final Uri packageConfigJsonPath = Uri(path: ".dart_tool/package_config.json");
-final Uri dotPackagesPath = Uri(path: ".packages");
-final Uri currentPath = Uri(path: ".");
-final Uri parentPath = Uri(path: "..");
-
-/// Discover the package configuration for a Dart script.
-///
-/// The [baseDirectory] points to the directory of the Dart script.
-/// A package resolution strategy is found by going through the following steps,
-/// and stopping when something is found.
-///
-/// * Check if a `.dart_tool/package_config.json` file exists in the directory.
-/// * Check if a `.packages` file exists in the directory.
-/// * Repeat these checks for the parent directories until reaching the
-///   root directory if [recursive] is true.
-///
-/// If any of these tests succeed, a `PackageConfig` class is returned.
-/// Returns `null` if no configuration was found. If a configuration
-/// is needed, then the caller can supply [PackageConfig.empty].
-Future<PackageConfig /*?*/ > findPackageConfig(
-    Directory baseDirectory, bool recursive, void onError(Object error)) async {
-  var directory = baseDirectory;
-  if (!directory.isAbsolute) directory = directory.absolute;
-  if (!await directory.exists()) {
-    return null;
-  }
-  do {
-    // Check for $cwd/.packages
-    var packageConfig = await findPackagConfigInDirectory(directory, onError);
-    if (packageConfig != null) return packageConfig;
-    if (!recursive) break;
-    // Check in parent directories.
-    var parentDirectory = directory.parent;
-    if (parentDirectory.path == directory.path) break;
-    directory = parentDirectory;
-  } while (true);
-  return null;
-}
-
-/// Similar to [findPackageConfig] but based on a URI.
-Future<PackageConfig /*?*/ > findPackageConfigUri(
-    Uri location,
-    Future<Uint8List /*?*/ > loader(Uri uri) /*?*/,
-    void onError(Object error) /*?*/,
-    bool recursive) async {
-  if (location.isScheme("package")) {
-    onError(PackageConfigArgumentError(
-        location, "location", "Must not be a package: URI"));
-    return null;
-  }
-  if (loader == null) {
-    if (location.isScheme("file")) {
-      return findPackageConfig(
-          Directory.fromUri(location.resolveUri(currentPath)),
-          recursive,
-          onError);
-    }
-    loader = defaultLoader;
-  }
-  if (!location.path.endsWith("/")) location = location.resolveUri(currentPath);
-  while (true) {
-    var file = location.resolveUri(packageConfigJsonPath);
-    var bytes = await loader(file);
-    if (bytes != null) {
-      return parsePackageConfigBytes(bytes, file, onError);
-    }
-    file = location.resolveUri(dotPackagesPath);
-    bytes = await loader(file);
-    if (bytes != null) {
-      return packages_file.parse(bytes, file, onError);
-    }
-    if (!recursive) break;
-    var parent = location.resolveUri(parentPath);
-    if (parent == location) break;
-    location = parent;
-  }
-  return null;
-}
-
-/// Finds a `.packages` or `.dart_tool/package_config.json` file in [directory].
-///
-/// Loads the file, if it is there, and returns the resulting [PackageConfig].
-/// Returns `null` if the file isn't there.
-/// Reports a [FormatException] if a file is there but the content is not valid.
-/// If the file exists, but fails to be read, the file system error is reported.
-///
-/// If [onError] is supplied, parsing errors are reported using that, and
-/// a best-effort attempt is made to return a package configuration.
-/// This may be the empty package configuration.
-Future<PackageConfig /*?*/ > findPackagConfigInDirectory(
-    Directory directory, void onError(Object error)) async {
-  var packageConfigFile = await checkForPackageConfigJsonFile(directory);
-  if (packageConfigFile != null) {
-    return await readPackageConfigJsonFile(packageConfigFile, onError);
-  }
-  packageConfigFile = await checkForDotPackagesFile(directory);
-  if (packageConfigFile != null) {
-    return await readDotPackagesFile(packageConfigFile, onError);
-  }
-  return null;
-}
-
-Future<File> /*?*/ checkForPackageConfigJsonFile(Directory directory) async {
-  assert(directory.isAbsolute);
-  var file =
-      File(pathJoin(directory.path, ".dart_tool", "package_config.json"));
-  if (await file.exists()) return file;
-  return null;
-}
-
-Future<File /*?*/ > checkForDotPackagesFile(Directory directory) async {
-  var file = File(pathJoin(directory.path, ".packages"));
-  if (await file.exists()) return file;
-  return null;
-}
diff --git a/package_config/lib/src/errors.dart b/package_config/lib/src/errors.dart
deleted file mode 100644
index c973617..0000000
--- a/package_config/lib/src/errors.dart
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS 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.
-
-/// General superclass of most errors and exceptions thrown by this package.
-///
-/// Only covers errors thrown while parsing package configuration files.
-/// Programming errors and I/O exceptions are not covered.
-abstract class PackageConfigError {
-  PackageConfigError._();
-}
-
-class PackageConfigArgumentError extends ArgumentError
-    implements PackageConfigError {
-  PackageConfigArgumentError(Object /*?*/ value, String name, String message)
-      : super.value(value, name, message);
-
-  PackageConfigArgumentError.from(ArgumentError error)
-      : super.value(error.invalidValue, error.name, error.message);
-}
-
-class PackageConfigFormatException extends FormatException
-    implements PackageConfigError {
-  PackageConfigFormatException(String message, Object /*?*/ source,
-      [int /*?*/ offset])
-      : super(message, source, offset);
-
-  PackageConfigFormatException.from(FormatException exception)
-      : super(exception.message, exception.source, exception.offset);
-}
-
-/// The default `onError` handler.
-void /*Never*/ throwError(Object error) => throw error;
diff --git a/package_config/lib/src/package_config.dart b/package_config/lib/src/package_config.dart
deleted file mode 100644
index 364df75..0000000
--- a/package_config/lib/src/package_config.dart
+++ /dev/null
@@ -1,375 +0,0 @@
-// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS 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 'dart:typed_data';
-
-import 'errors.dart';
-import "package_config_impl.dart";
-import 'package_config_json.dart';
-
-/// A package configuration.
-///
-/// Associates configuration data to packages and files in packages.
-///
-/// More members may be added to this class in the future,
-/// so classes outside of this package must not implement [PackageConfig]
-/// or any subclass of it.
-abstract class PackageConfig {
-  /// The largest configuration version currently recognized.
-  static const int maxVersion = 2;
-
-  /// An empty package configuration.
-  ///
-  /// A package configuration with no available packages.
-  /// Is used as a default value where a package configuration
-  /// is expected, but none have been specified or found.
-  static const PackageConfig empty = SimplePackageConfig.empty();
-
-  /// Creats a package configuration with the provided available [packages].
-  ///
-  /// The packages must be valid packages (valid package name, valid
-  /// absolute directory URIs, valid language version, if any),
-  /// and there must not be two packages with the same name.
-  ///
-  /// The package's root ([Package.rootUri]) and package-root
-  /// ([Package.packageUriRoot]) paths must satisfy a number of constraints
-  /// We say that one path (which we know ends with a `/` charater)
-  /// is inside another path, if the latter path is a prefix of the former path,
-  /// including the two paths being the same.
-  ///
-  /// * No package's root must be the same as another package's root.
-  /// * The package-root of a package must be inside the pacakge's root.
-  /// * If one package's package-root is inside another package's root,
-  ///   then the latter package's package root must not be inside the former
-  ///   package's root. (No getting between a package and its package root!)
-  ///   This also disallows a package's root being the same as another
-  ///   package's package root.
-  ///
-  /// If supplied, the [extraData] will be available as the
-  /// [PackageConfig.extraData] of the created configuration.
-  ///
-  /// The version of the resulting configuration is always [maxVersion].
-  factory PackageConfig(Iterable<Package> packages, {dynamic extraData}) =>
-      SimplePackageConfig(maxVersion, packages, extraData);
-
-  /// Parses a package configuration file.
-  ///
-  /// The [bytes] must be an UTF-8 encoded JSON object
-  /// containing a valid package configuration.
-  ///
-  /// The [baseUri] is used as the base for resolving relative
-  /// URI references in the configuration file. If the configuration
-  /// has been read from a file, the [baseUri] can be the URI of that
-  /// file, or of the directory it occurs in.
-  ///
-  /// If [onError] is provided, errors found during parsing or building
-  /// the configuration are reported by calling [onError] instead of
-  /// throwing, and parser makes a *best effort* attempt to continue
-  /// despite the error. The input must still be valid JSON.
-  /// The result may be a [PackageConfig.empty] if there is no way to
-  /// extract useful information from the bytes.
-  static PackageConfig parseBytes(Uint8List bytes, Uri baseUri,
-          {void onError(Object error)}) =>
-      parsePackageConfigBytes(bytes, baseUri, onError ?? throwError);
-
-  /// Parses a package configuration file.
-  ///
-  /// The [configuration] must be a JSON object
-  /// containing a valid package configuration.
-  ///
-  /// The [baseUri] is used as the base for resolving relative
-  /// URI references in the configuration file. If the configuration
-  /// has been read from a file, the [baseUri] can be the URI of that
-  /// file, or of the directory it occurs in.
-  ///
-  /// If [onError] is provided, errors found during parsing or building
-  /// the configuration are reported by calling [onError] instead of
-  /// throwing, and parser makes a *best effort* attempt to continue
-  /// despite the error. The input must still be valid JSON.
-  /// The result may be a [PackageConfig.empty] if there is no way to
-  /// extract useful information from the bytes.
-  static PackageConfig parseString(String configuration, Uri baseUri,
-          {void onError(Object error)}) =>
-      parsePackageConfigString(configuration, baseUri, onError ?? throwError);
-
-  /// Parses the JSON data of a package configuration file.
-  ///
-  /// The [configuration] must be a JSON-like Dart data structure,
-  /// like the one provided by parsing JSON text using `dart:convert`,
-  /// containing a valid package configuration.
-  ///
-  /// The [baseUri] is used as the base for resolving relative
-  /// URI references in the configuration file. If the configuration
-  /// has been read from a file, the [baseUri] can be the URI of that
-  /// file, or of the directory it occurs in.
-  ///
-  /// If [onError] is provided, errors found during parsing or building
-  /// the configuration are reported by calling [onError] instead of
-  /// throwing, and parser makes a *best effort* attempt to continue
-  /// despite the error. The input must still be valid JSON.
-  /// The result may be a [PackageConfig.empty] if there is no way to
-  /// extract useful information from the bytes.
-  static PackageConfig parseJson(dynamic jsonData, Uri baseUri,
-          {void onError(Object error)}) =>
-      parsePackageConfigJson(jsonData, baseUri, onError ?? throwError);
-
-  /// Writes a configuration file for this configuration on [output].
-  ///
-  /// If [baseUri] is provided, URI references in the generated file
-  /// will be made relative to [baseUri] where possible.
-  static void writeBytes(PackageConfig configuration, Sink<Uint8List> output,
-      [Uri /*?*/ baseUri]) {
-    writePackageConfigJsonUtf8(configuration, baseUri, output);
-  }
-
-  /// Writes a configuration JSON text for this configuration on [output].
-  ///
-  /// If [baseUri] is provided, URI references in the generated file
-  /// will be made relative to [baseUri] where possible.
-  static void writeString(PackageConfig configuration, StringSink output,
-      [Uri /*?*/ baseUri]) {
-    writePackageConfigJsonString(configuration, baseUri, output);
-  }
-
-  /// Converts a configuration to a JSON-like data structure.
-  ///
-  /// If [baseUri] is provided, URI references in the generated data
-  /// will be made relative to [baseUri] where possible.
-  static Map<String, dynamic> toJson(PackageConfig configuration,
-          [Uri /*?*/ baseUri]) =>
-      packageConfigToJson(configuration, baseUri);
-
-  /// The configuration version number.
-  ///
-  /// Currently this is 1 or 2, where
-  /// * Version one is the `.packages` file format and
-  /// * Version two is the first `package_config.json` format.
-  ///
-  /// Instances of this class supports both, and the version
-  /// is only useful for detecting which kind of file the configuration
-  /// was read from.
-  int get version;
-
-  /// All the available packages of this configuration.
-  ///
-  /// No two of these packages have the same name,
-  /// and no two [Package.root] directories overlap.
-  Iterable<Package> get packages;
-
-  /// Look up a package by name.
-  ///
-  /// Returns the [Package] fron [packages] with [packageName] as
-  /// [Package.name]. Returns `null` if the package is not available in the
-  /// current configuration.
-  Package /*?*/ operator [](String packageName);
-
-  /// Provides the associated package for a specific [file] (or directory).
-  ///
-  /// Returns a [Package] which contains the [file]'s path, if any.
-  /// That is, the [Package.rootUri] directory is a parent directory
-  /// of the [file]'s location.
-  ///
-  /// Returns `null` if the file does not belong to any package.
-  Package /*?*/ packageOf(Uri file);
-
-  /// Resolves a `package:` URI to a non-package URI
-  ///
-  /// The [packageUri] must be a valid package URI. That means:
-  /// * A URI with `package` as scheme,
-  /// * with no authority part (`package://...`),
-  /// * with a path starting with a valid package name followed by a slash, and
-  /// * with no query or fragment part.
-  ///
-  /// Throws an [ArgumentError] (which also implements [PackageConfigError])
-  /// if the package URI is not valid.
-  ///
-  /// Returns `null` if the package name of [packageUri] is not available
-  /// in this package configuration.
-  /// Returns the remaining path of the package URI resolved relative to the
-  /// [Package.packageUriRoot] of the corresponding package.
-  Uri /*?*/ resolve(Uri packageUri);
-
-  /// The package URI which resolves to [nonPackageUri].
-  ///
-  /// The [nonPackageUri] must not have any query or fragment part,
-  /// and it must not have `package` as scheme.
-  /// Throws an [ArgumentError] (which also implements [PackageConfigError])
-  /// if the non-package URI is not valid.
-  ///
-  /// Returns a package URI which [resolve] will convert to [nonPackageUri],
-  /// if any such URI exists. Returns `null` if no such package URI exists.
-  Uri /*?*/ toPackageUri(Uri nonPackageUri);
-
-  /// Extra data associated with the package configuration.
-  ///
-  /// The data may be in any format, depending on who introduced it.
-  /// The standard `packjage_config.json` file storage will only store
-  /// JSON-like list/map data structures.
-  dynamic get extraData;
-}
-
-/// Configuration data for a single package.
-abstract class Package {
-  /// Creates a package with the provided properties.
-  ///
-  /// The [name] must be a valid package name.
-  /// The [root] must be an absolute directory URI, meaning an absolute URI
-  /// with no query or fragment path and a path starting and ending with `/`.
-  /// The [packageUriRoot], if provided, must be either an absolute
-  /// directory URI or a relative URI reference which is then resolved
-  /// relative to [root]. It must then also be a subdirectory of [root],
-  /// or the same directory.
-  /// If [languageVersion] is supplied, it must be a valid Dart language
-  /// version, which means two decimal integer literals separated by a `.`,
-  /// where the integer literals have no leading zeros unless they are
-  /// a single zero digit.
-  /// If [extraData] is supplied, it will be available as the
-  /// [Package.extraData] of the created package.
-  factory Package(String name, Uri root,
-          {Uri /*?*/ packageUriRoot,
-          LanguageVersion /*?*/ languageVersion,
-          dynamic extraData}) =>
-      SimplePackage.validate(
-          name, root, packageUriRoot, languageVersion, extraData, throwError);
-
-  /// The package-name of the package.
-  String get name;
-
-  /// The location of the root of the package.
-  ///
-  /// Is always an absolute URI with no query or fragment parts,
-  /// and with a path ending in `/`.
-  ///
-  /// All files in the [rootUri] directory are considered
-  /// part of the package for purposes where that that matters.
-  Uri get root;
-
-  /// The root of the files available through `package:` URIs.
-  ///
-  /// A `package:` URI with [name] as the package name is
-  /// resolved relative to this location.
-  ///
-  /// Is always an absolute URI with no query or fragment part
-  /// with a path ending in `/`,
-  /// and with a location which is a subdirectory
-  /// of the [root], or the same as the [root].
-  Uri get packageUriRoot;
-
-  /// The default language version associated with this package.
-  ///
-  /// Each package may have a default language version associated,
-  /// which is the language version used to parse and compile
-  /// Dart files in the package.
-  /// A package version is defined by two non-negative numbers,
-  /// the *major* and *minor* version numbers.
-  LanguageVersion /*?*/ get languageVersion;
-
-  /// Extra data associated with the specific package.
-  ///
-  /// The data may be in any format, depending on who introduced it.
-  /// The standard `packjage_config.json` file storage will only store
-  /// JSON-like list/map data structures.
-  dynamic get extraData;
-}
-
-/// A language version.
-///
-/// A language version is represented by two non-negative integers,
-/// the [major] and [minor] version numbers.
-///
-/// If errors during parsing are handled using an `onError` handler,
-/// then an *invalid* language version may be represented by an
-/// [InvalidLanguageVersion] object.
-abstract class LanguageVersion implements Comparable<LanguageVersion> {
-  /// The maximal value allowed by [major] and [minor] values;
-  static const int maxValue = 0x7FFFFFFF;
-  factory LanguageVersion(int major, int minor) {
-    RangeError.checkValueInInterval(major, 0, maxValue, "major");
-    RangeError.checkValueInInterval(minor, 0, maxValue, "major");
-    return SimpleLanguageVersion(major, minor, null);
-  }
-
-  /// Parses a language version string.
-  ///
-  /// A valid language version string has the form
-  ///
-  /// > *decimalNumber* `.` *decimalNumber*
-  ///
-  /// where a *decimalNumber* is a non-empty sequence of decimal digits
-  /// with no unnecessary leading zeros (the decimal number only starts
-  /// with a zero digit if that digit is the entire number).
-  /// No spaces are allowed in the string.
-  ///
-  /// If the [source] is valid then it is parsed into a valid
-  /// [LanguageVersion] object.
-  /// If not, then the [onError] is called with a [FormatException].
-  /// If [onError] is not supplied, it defaults to throwing the exception.
-  /// If the call does not throw, then an [InvalidLanguageVersion] is returned
-  /// containing the original [source].
-  static LanguageVersion parse(String source, {void onError(Object error)}) =>
-      parseLanguageVersion(source, onError ?? throwError);
-
-  /// The major language version.
-  ///
-  /// A non-negative integer less than 2<sup>31</sup>.
-  ///
-  /// The value is negative for objects representing *invalid* language
-  /// versions ([InvalidLanguageVersion]).
-  int get major;
-
-  /// The minor language version.
-  ///
-  /// A non-negative integer less than 2<sup>31</sup>.
-  ///
-  /// The value is negative for objects representing *invalid* language
-  /// versions ([InvalidLanguageVersion]).
-  int get minor;
-
-  /// Compares language versions.
-  ///
-  /// Two language versions are considered equal if they have the
-  /// same major and minor version numbers.
-  ///
-  /// A language version is greater then another if the former's major version
-  /// is greater than the latter's major version, or if they have
-  /// the same major version and the former's minor version is greater than
-  /// the latter's.
-  int compareTo(LanguageVersion other);
-
-  /// Valid language versions with the same [major] and [minor] values are
-  /// equal.
-  ///
-  /// Invalid language versions ([InvalidLanguageVersion]) are not equal to
-  /// any other object.
-  bool operator ==(Object other);
-
-  int get hashCode;
-
-  /// A string representation of the language version.
-  ///
-  /// A valid language version is represented as
-  /// `"${version.major}.${version.minor}"`.
-  String toString();
-}
-
-/// An *invalid* language version.
-///
-/// Stored in a [Package] when the orginal language version string
-/// was invalid and a `onError` handler was passed to the parser
-/// which did not throw on an error.
-abstract class InvalidLanguageVersion implements LanguageVersion {
-  /// The value -1 for an invalid language version.
-  int get major;
-
-  /// The value -1 for an invalid language version.
-  int get minor;
-
-  /// An invalid language version is only equal to itself.
-  bool operator ==(Object other);
-
-  int get hashCode;
-
-  /// The original invalid version string.
-  String toString();
-}
diff --git a/package_config/lib/src/package_config_impl.dart b/package_config/lib/src/package_config_impl.dart
deleted file mode 100644
index f68a9ea..0000000
--- a/package_config/lib/src/package_config_impl.dart
+++ /dev/null
@@ -1,519 +0,0 @@
-// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS 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 'errors.dart';
-import "package_config.dart";
-import "util.dart";
-
-export "package_config.dart";
-
-// Implementations of the main data types exposed by the API of this package.
-
-class SimplePackageConfig implements PackageConfig {
-  final int version;
-  final Map<String, Package> _packages;
-  final PackageTree _packageTree;
-  final dynamic extraData;
-
-  factory SimplePackageConfig(int version, Iterable<Package> packages,
-      [dynamic extraData, void onError(Object error)]) {
-    onError ??= throwError;
-    var validVersion = _validateVersion(version, onError);
-    var sortedPackages = [...packages]..sort(_compareRoot);
-    var packageTree = _validatePackages(packages, sortedPackages, onError);
-    return SimplePackageConfig._(validVersion, packageTree,
-        {for (var p in packageTree.allPackages) p.name: p}, extraData);
-  }
-
-  SimplePackageConfig._(
-      this.version, this._packageTree, this._packages, this.extraData);
-
-  /// Creates empty configuration.
-  ///
-  /// The empty configuration can be used in cases where no configuration is
-  /// found, but code expects a non-null configuration.
-  const SimplePackageConfig.empty()
-      : version = 1,
-        _packageTree = const EmptyPackageTree(),
-        _packages = const <String, Package>{},
-        extraData = null;
-
-  static int _validateVersion(int version, void onError(Object error)) {
-    if (version < 0 || version > PackageConfig.maxVersion) {
-      onError(PackageConfigArgumentError(version, "version",
-          "Must be in the range 1 to ${PackageConfig.maxVersion}"));
-      return 2; // The minimal version supporting a SimplePackageConfig.
-    }
-    return version;
-  }
-
-  static PackageTree _validatePackages(Iterable<Package> originalPackages,
-      List<Package> packages, void onError(Object error)) {
-    var packageNames = <String>{};
-    var tree = MutablePackageTree();
-    for (var originalPackage in packages) {
-      if (originalPackage == null) {
-        onError(ArgumentError.notNull("element of packages"));
-        continue;
-      }
-      SimplePackage package;
-      if (originalPackage is! SimplePackage) {
-        // SimplePackage validates these properties.
-        package = SimplePackage.validate(
-            originalPackage.name,
-            originalPackage.root,
-            originalPackage.packageUriRoot,
-            originalPackage.languageVersion,
-            originalPackage.extraData, (error) {
-          if (error is PackageConfigArgumentError) {
-            onError(PackageConfigArgumentError(packages, "packages",
-                "Package ${package.name}: ${error.message}"));
-          } else {
-            onError(error);
-          }
-        });
-        if (package == null) continue;
-      } else {
-        package = originalPackage;
-      }
-      var name = package.name;
-      if (packageNames.contains(name)) {
-        onError(PackageConfigArgumentError(
-            name, "packages", "Duplicate package name"));
-        continue;
-      }
-      packageNames.add(name);
-      tree.add(0, package, (error) {
-        if (error is ConflictException) {
-          // There is a conflict with an existing package.
-          var existingPackage = error.existingPackage;
-          if (error.isRootConflict) {
-            onError(PackageConfigArgumentError(
-                originalPackages,
-                "packages",
-                "Packages ${package.name} and ${existingPackage.name} "
-                    "have the same root directory: ${package.root}.\n"));
-          } else {
-            assert(error.isPackageRootConflict);
-            // Package is inside the package URI root of the existing package.
-            onError(PackageConfigArgumentError(
-                originalPackages,
-                "packages",
-                "Package ${package.name} is inside the package URI root of "
-                    "package ${existingPackage.name}.\n"
-                    "${existingPackage.name} URI root: "
-                    "${existingPackage.packageUriRoot}\n"
-                    "${package.name} root: ${package.root}\n"));
-          }
-        } else {
-          // Any other error.
-          onError(error);
-        }
-      });
-    }
-    return tree;
-  }
-
-  Iterable<Package> get packages => _packages.values;
-
-  Package /*?*/ operator [](String packageName) => _packages[packageName];
-
-  /// Provides the associated package for a specific [file] (or directory).
-  ///
-  /// Returns a [Package] which contains the [file]'s path.
-  /// That is, the [Package.rootUri] directory is a parent directory
-  /// of the [file]'s location.
-  /// Returns `null` if the file does not belong to any package.
-  Package /*?*/ packageOf(Uri file) => _packageTree.packageOf(file);
-
-  Uri /*?*/ resolve(Uri packageUri) {
-    var packageName = checkValidPackageUri(packageUri, "packageUri");
-    return _packages[packageName]?.packageUriRoot?.resolveUri(
-        Uri(path: packageUri.path.substring(packageName.length + 1)));
-  }
-
-  Uri /*?*/ toPackageUri(Uri nonPackageUri) {
-    if (nonPackageUri.isScheme("package")) {
-      throw PackageConfigArgumentError(
-          nonPackageUri, "nonPackageUri", "Must not be a package URI");
-    }
-    if (nonPackageUri.hasQuery || nonPackageUri.hasFragment) {
-      throw PackageConfigArgumentError(nonPackageUri, "nonPackageUri",
-          "Must not have query or fragment part");
-    }
-    // Find package that file belongs to.
-    var package = _packageTree.packageOf(nonPackageUri);
-    if (package == null) return null;
-    // Check if it is inside the package URI root.
-    var path = nonPackageUri.toString();
-    var root = package.packageUriRoot.toString();
-    if (_beginsWith(package.root.toString().length, root, path)) {
-      var rest = path.substring(root.length);
-      return Uri(scheme: "package", path: "${package.name}/$rest");
-    }
-    return null;
-  }
-}
-
-/// Configuration data for a single package.
-class SimplePackage implements Package {
-  final String name;
-  final Uri root;
-  final Uri packageUriRoot;
-  final LanguageVersion /*?*/ languageVersion;
-  final dynamic extraData;
-
-  SimplePackage._(this.name, this.root, this.packageUriRoot,
-      this.languageVersion, this.extraData);
-
-  /// Creates a [SimplePackage] with the provided content.
-  ///
-  /// The provided arguments must be valid.
-  ///
-  /// If the arguments are invalid then the error is reported by
-  /// calling [onError], then the erroneous entry is ignored.
-  ///
-  /// If [onError] is provided, the user is expected to be able to handle
-  /// errors themselves. An invalid [languageVersion] string
-  /// will be replaced with the string `"invalid"`. This allows
-  /// users to detect the difference between an absent version and
-  /// an invalid one.
-  ///
-  /// Returns `null` if the input is invalid and an approximately valid package
-  /// cannot be salvaged from the input.
-  static SimplePackage /*?*/ validate(
-      String name,
-      Uri root,
-      Uri packageUriRoot,
-      LanguageVersion /*?*/ languageVersion,
-      dynamic extraData,
-      void onError(Object error)) {
-    var fatalError = false;
-    var invalidIndex = checkPackageName(name);
-    if (invalidIndex >= 0) {
-      onError(PackageConfigFormatException(
-          "Not a valid package name", name, invalidIndex));
-      fatalError = true;
-    }
-    if (root.isScheme("package")) {
-      onError(PackageConfigArgumentError(
-          "$root", "root", "Must not be a package URI"));
-      fatalError = true;
-    } else if (!isAbsoluteDirectoryUri(root)) {
-      onError(PackageConfigArgumentError(
-          "$root",
-          "root",
-          "In package $name: Not an absolute URI with no query or fragment "
-              "with a path ending in /"));
-      // Try to recover. If the URI has a scheme,
-      // then ensure that the path ends with `/`.
-      if (!root.hasScheme) {
-        fatalError = true;
-      } else if (!root.path.endsWith("/")) {
-        root = root.replace(path: root.path + "/");
-      }
-    }
-    if (!fatalError) {
-      if (!isAbsoluteDirectoryUri(packageUriRoot)) {
-        onError(PackageConfigArgumentError(
-            packageUriRoot,
-            "packageUriRoot",
-            "In package $name: Not an absolute URI with no query or fragment "
-                "with a path ending in /"));
-        packageUriRoot = root;
-      } else if (!isUriPrefix(root, packageUriRoot)) {
-        onError(PackageConfigArgumentError(packageUriRoot, "packageUriRoot",
-            "The package URI root is not below the package root"));
-        packageUriRoot = root;
-      }
-    }
-    if (fatalError) return null;
-    return SimplePackage._(
-        name, root, packageUriRoot, languageVersion, extraData);
-  }
-}
-
-/// Checks whether [version] is a valid Dart language version string.
-///
-/// The format is (as RegExp) `^(0|[1-9]\d+)\.(0|[1-9]\d+)$`.
-///
-/// Reports a format exception on [onError] if not, or if the numbers
-/// are too large (at most 32-bit signed integers).
-LanguageVersion parseLanguageVersion(
-    String source, void onError(Object error)) {
-  var index = 0;
-  // Reads a positive decimal numeral. Returns the value of the numeral,
-  // or a negative number in case of an error.
-  // Starts at [index] and increments the index to the position after
-  // the numeral.
-  // It is an error if the numeral value is greater than 0x7FFFFFFFF.
-  // It is a recoverable error if the numeral starts with leading zeros.
-  int readNumeral() {
-    const maxValue = 0x7FFFFFFF;
-    if (index == source.length) {
-      onError(PackageConfigFormatException("Missing number", source, index));
-      return -1;
-    }
-    var start = index;
-
-    var char = source.codeUnitAt(index);
-    var digit = char ^ 0x30;
-    if (digit > 9) {
-      onError(PackageConfigFormatException("Missing number", source, index));
-      return -1;
-    }
-    var firstDigit = digit;
-    var value = 0;
-    do {
-      value = value * 10 + digit;
-      if (value > maxValue) {
-        onError(
-            PackageConfigFormatException("Number too large", source, start));
-        return -1;
-      }
-      index++;
-      if (index == source.length) break;
-      char = source.codeUnitAt(index);
-      digit = char ^ 0x30;
-    } while (digit <= 9);
-    if (firstDigit == 0 && index > start + 1) {
-      onError(PackageConfigFormatException(
-          "Leading zero not allowed", source, start));
-    }
-    return value;
-  }
-
-  var major = readNumeral();
-  if (major < 0) {
-    return SimpleInvalidLanguageVersion(source);
-  }
-  if (index == source.length || source.codeUnitAt(index) != $dot) {
-    onError(PackageConfigFormatException("Missing '.'", source, index));
-    return SimpleInvalidLanguageVersion(source);
-  }
-  index++;
-  var minor = readNumeral();
-  if (minor < 0) {
-    return SimpleInvalidLanguageVersion(source);
-  }
-  if (index != source.length) {
-    onError(PackageConfigFormatException(
-        "Unexpected trailing character", source, index));
-    return SimpleInvalidLanguageVersion(source);
-  }
-  return SimpleLanguageVersion(major, minor, source);
-}
-
-abstract class _SimpleLanguageVersionBase implements LanguageVersion {
-  int compareTo(LanguageVersion other) {
-    var result = major.compareTo(other.major);
-    if (result != 0) return result;
-    return minor.compareTo(other.minor);
-  }
-}
-
-class SimpleLanguageVersion extends _SimpleLanguageVersionBase {
-  final int major;
-  final int minor;
-  String /*?*/ _source;
-  SimpleLanguageVersion(this.major, this.minor, this._source);
-
-  bool operator ==(Object other) =>
-      other is LanguageVersion && major == other.major && minor == other.minor;
-
-  int get hashCode => (major * 17 ^ minor * 37) & 0x3FFFFFFF;
-
-  String toString() => _source ??= "$major.$minor";
-}
-
-class SimpleInvalidLanguageVersion extends _SimpleLanguageVersionBase
-    implements InvalidLanguageVersion {
-  final String _source;
-  SimpleInvalidLanguageVersion(this._source);
-  int get major => -1;
-  int get minor => -1;
-
-  String toString() => _source;
-}
-
-abstract class PackageTree {
-  Iterable<Package> get allPackages;
-  SimplePackage /*?*/ packageOf(Uri file);
-}
-
-/// Packages of a package configuration ordered by root path.
-///
-/// A package has a root path and a package root path, where the latter
-/// contains the files exposed by `package:` URIs.
-///
-/// A package is said to be inside another package if the root path URI of
-/// the latter is a prefix of the root path URI of the former.
-///
-/// No two packages of a package may have the same root path, so this
-/// path prefix ordering defines a tree-like partial ordering on packages
-/// of a configuration.
-///
-/// The package root path of a package must not be inside another package's
-/// root path.
-/// Entire other packages are allowed inside a package's root or
-/// package root path.
-///
-/// The package tree contains an ordered mapping of unrelated packages
-/// (represented by their name) to their immediately nested packages' names.
-class MutablePackageTree implements PackageTree {
-  /// A list of packages that are not nested inside each other.
-  final List<SimplePackage> packages = [];
-
-  /// The tree of the immediately nested packages inside each package.
-  ///
-  /// Indexed by [Package.name].
-  /// If a package has no nested packages (which is most often the case),
-  /// there is no tree object associated with it.
-  Map<String, MutablePackageTree /*?*/ > /*?*/ _packageChildren;
-
-  Iterable<Package> get allPackages sync* {
-    for (var package in packages) yield package;
-    if (_packageChildren != null) {
-      for (var tree in _packageChildren.values) yield* tree.allPackages;
-    }
-  }
-
-  /// Tries to (add) `package` to the tree.
-  ///
-  /// Reports a [ConflictException] if the added package conflicts with an
-  /// existing package.
-  /// It conflicts if its root or package root is the same as another
-  /// package's root or package root, or is between the two.
-  ///
-  /// If a conflict is detected between [package] and a previous package,
-  /// then [onError] is called with a [ConflictException] object
-  /// and the [package] is not added to the tree.
-  ///
-  /// The packages are added in order of their root path.
-  /// It is never necessary to insert a node between two existing levels.
-  void add(int start, SimplePackage package, void onError(Object error)) {
-    var path = package.root.toString();
-    for (var treePackage in packages) {
-      // Check is package is inside treePackage.
-      var treePackagePath = treePackage.root.toString();
-      assert(treePackagePath.length > start);
-      assert(path.startsWith(treePackagePath.substring(0, start)));
-      if (_beginsWith(start, treePackagePath, path)) {
-        // Package *is* inside treePackage.
-        var treePackagePathLength = treePackagePath.length;
-        if (path.length == treePackagePathLength) {
-          // Has same root. Do not add package.
-          onError(ConflictException.root(package, treePackage));
-          return;
-        }
-        var treePackageUriRoot = treePackage.packageUriRoot.toString();
-        if (_beginsWith(treePackagePathLength, path, treePackageUriRoot)) {
-          // The treePackage's package root is inside package, which is inside
-          // the treePackage. This is not allowed.
-          onError(ConflictException.packageRoot(package, treePackage));
-          return;
-        }
-        _treeOf(treePackage).add(treePackagePathLength, package, onError);
-        return;
-      }
-    }
-    packages.add(package);
-  }
-
-  SimplePackage /*?*/ packageOf(Uri file) {
-    return findPackageOf(0, file.toString());
-  }
-
-  /// Finds package containing [path] in this tree.
-  ///
-  /// Returns `null` if no such package is found.
-  ///
-  /// Assumes the first [start] characters of path agrees with all
-  /// the packages at this level of the tree.
-  SimplePackage /*?*/ findPackageOf(int start, String path) {
-    for (var childPackage in packages) {
-      var childPath = childPackage.root.toString();
-      if (_beginsWith(start, childPath, path)) {
-        // The [package] is inside [childPackage].
-        var childPathLength = childPath.length;
-        if (path.length == childPathLength) return childPackage;
-        var uriRoot = childPackage.packageUriRoot.toString();
-        // Is [package] is inside the URI root of [childPackage].
-        if (uriRoot.length == childPathLength ||
-            _beginsWith(childPathLength, uriRoot, path)) {
-          return childPackage;
-        }
-        // Otherwise add [package] as child of [childPackage].
-        // TODO(lrn): When NNBD comes, convert to:
-        // return _packageChildren?[childPackage.name]
-        //     ?.packageOf(childPathLength, path) ?? childPackage;
-        if (_packageChildren == null) return childPackage;
-        var childTree = _packageChildren[childPackage.name];
-        if (childTree == null) return childPackage;
-        return childTree.findPackageOf(childPathLength, path) ?? childPackage;
-      }
-    }
-    return null;
-  }
-
-  /// Returns the [PackageTree] of the children of [package].
-  ///
-  /// Ensures that the object is allocated if necessary.
-  MutablePackageTree _treeOf(SimplePackage package) {
-    var children = _packageChildren ??= {};
-    return children[package.name] ??= MutablePackageTree();
-  }
-}
-
-class EmptyPackageTree implements PackageTree {
-  const EmptyPackageTree();
-
-  Iterable<Package> get allPackages => const Iterable<Package>.empty();
-
-  SimplePackage packageOf(Uri file) => null;
-}
-
-/// Checks whether [longerPath] begins with [parentPath].
-///
-/// Skips checking the [start] first characters which are assumed to
-/// already have been matched.
-bool _beginsWith(int start, String parentPath, String longerPath) {
-  if (longerPath.length < parentPath.length) return false;
-  for (var i = start; i < parentPath.length; i++) {
-    if (longerPath.codeUnitAt(i) != parentPath.codeUnitAt(i)) return false;
-  }
-  return true;
-}
-
-/// Conflict between packages added to the same configuration.
-///
-/// The [package] conflicts with [existingPackage] if it has
-/// the same root path ([isRootConflict]) or the package URI root path
-/// of [existingPackage] is inside the root path of [package]
-/// ([isPackageRootConflict]).
-class ConflictException {
-  /// The existing package that [package] conflicts with.
-  final SimplePackage existingPackage;
-
-  /// The package that could not be added without a conflict.
-  final SimplePackage package;
-
-  /// Whether the conflict is with the package URI root of [existingPackage].
-  final bool isPackageRootConflict;
-
-  /// Creates a root conflict between [package] and [existingPackage].
-  ConflictException.root(this.package, this.existingPackage)
-      : isPackageRootConflict = false;
-
-  /// Creates a package root conflict between [package] and [existingPackage].
-  ConflictException.packageRoot(this.package, this.existingPackage)
-      : isPackageRootConflict = true;
-
-  /// WHether the conflict is with the root URI of [existingPackage].
-  bool get isRootConflict => !isPackageRootConflict;
-}
-
-/// Used for sorting packages by root path.
-int _compareRoot(Package p1, Package p2) =>
-    p1.root.toString().compareTo(p2.root.toString());
diff --git a/package_config/lib/src/package_config_io.dart b/package_config/lib/src/package_config_io.dart
deleted file mode 100644
index 954be6b..0000000
--- a/package_config/lib/src/package_config_io.dart
+++ /dev/null
@@ -1,156 +0,0 @@
-// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS 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.
-
-// dart:io dependent functionality for reading and writing configuration files.
-
-import "dart:convert";
-import "dart:io";
-import "dart:typed_data";
-
-import "discovery.dart" show packageConfigJsonPath;
-import "errors.dart";
-import "package_config_impl.dart";
-import "package_config_json.dart";
-import "packages_file.dart" as packages_file;
-import "util.dart";
-import "util_io.dart";
-
-/// Reads a package configuration file.
-///
-/// Detects whether the [file] is a version one `.packages` file or
-/// a version two `package_config.json` file.
-///
-/// If the [file] is a `.packages` file and [preferNewest] is true,
-/// first checks whether there is an adjacent `.dart_tool/package_config.json`
-/// file, and if so, reads that instead.
-/// If [preferNewset] is false, the specified file is loaded even if it is
-/// a `.packages` file and there is an available `package_config.json` file.
-///
-/// The file must exist and be a normal file.
-Future<PackageConfig> readAnyConfigFile(
-    File file, bool preferNewest, void onError(Object error)) async {
-  Uint8List bytes;
-  try {
-    bytes = await file.readAsBytes();
-  } catch (e) {
-    onError(e);
-    return const SimplePackageConfig.empty();
-  }
-  var firstChar = firstNonWhitespaceChar(bytes);
-  if (firstChar != $lbrace) {
-    // Definitely not a JSON object, probably a .packages.
-    if (preferNewest) {
-      var alternateFile = File(
-          pathJoin(dirName(file.path), ".dart_tool", "package_config.json"));
-      if (alternateFile.existsSync()) {
-        Uint8List /*?*/ bytes;
-        try {
-          bytes = await alternateFile.readAsBytes();
-        } catch (e) {
-          onError(e);
-          return const SimplePackageConfig.empty();
-        }
-        if (bytes != null) {
-          return parsePackageConfigBytes(bytes, alternateFile.uri, onError);
-        }
-      }
-    }
-    return packages_file.parse(bytes, file.uri, onError);
-  }
-  return parsePackageConfigBytes(bytes, file.uri, onError);
-}
-
-/// Like [readAnyConfigFile] but uses a URI and an optional loader.
-Future<PackageConfig> readAnyConfigFileUri(
-    Uri file,
-    Future<Uint8List /*?*/ > loader(Uri uri) /*?*/,
-    void onError(Object error),
-    bool preferNewest) async {
-  if (file.isScheme("package")) {
-    throw PackageConfigArgumentError(
-        file, "file", "Must not be a package: URI");
-  }
-  if (loader == null) {
-    if (file.isScheme("file")) {
-      return readAnyConfigFile(File.fromUri(file), preferNewest, onError);
-    }
-    loader = defaultLoader;
-  }
-  Uint8List bytes;
-  try {
-    bytes = await loader(file);
-  } catch (e) {
-    onError(e);
-    return const SimplePackageConfig.empty();
-  }
-  if (bytes == null) {
-    onError(PackageConfigArgumentError(
-        file.toString(), "file", "File cannot be read"));
-    return const SimplePackageConfig.empty();
-  }
-  var firstChar = firstNonWhitespaceChar(bytes);
-  if (firstChar != $lbrace) {
-    // Definitely not a JSON object, probably a .packages.
-    if (preferNewest) {
-      // Check if there is a package_config.json file.
-      var alternateFile = file.resolveUri(packageConfigJsonPath);
-      Uint8List alternateBytes;
-      try {
-        alternateBytes = await loader(alternateFile);
-      } catch (e) {
-        onError(e);
-        return const SimplePackageConfig.empty();
-      }
-      if (alternateBytes != null) {
-        return parsePackageConfigBytes(alternateBytes, alternateFile, onError);
-      }
-    }
-    return packages_file.parse(bytes, file, onError);
-  }
-  return parsePackageConfigBytes(bytes, file, onError);
-}
-
-Future<PackageConfig> readPackageConfigJsonFile(
-    File file, void onError(Object error)) async {
-  Uint8List bytes;
-  try {
-    bytes = await file.readAsBytes();
-  } catch (error) {
-    onError(error);
-    return const SimplePackageConfig.empty();
-  }
-  return parsePackageConfigBytes(bytes, file.uri, onError);
-}
-
-Future<PackageConfig> readDotPackagesFile(
-    File file, void onError(Object error)) async {
-  Uint8List bytes;
-  try {
-    bytes = await file.readAsBytes();
-  } catch (error) {
-    onError(error);
-    return const SimplePackageConfig.empty();
-  }
-  return packages_file.parse(bytes, file.uri, onError);
-}
-
-Future<void> writePackageConfigJsonFile(
-    PackageConfig config, Directory targetDirectory) async {
-  // Write .dart_tool/package_config.json first.
-  var file =
-      File(pathJoin(targetDirectory.path, ".dart_tool", "package_config.json"));
-  var baseUri = file.uri;
-  var sink = file.openWrite(encoding: utf8);
-  writePackageConfigJsonUtf8(config, baseUri, sink);
-  var doneJson = sink.close();
-
-  // Write .packages too.
-  file = File(pathJoin(targetDirectory.path, ".packages"));
-  baseUri = file.uri;
-  sink = file.openWrite(encoding: utf8);
-  writeDotPackages(config, baseUri, sink);
-  var donePackages = sink.close();
-
-  await Future.wait([doneJson, donePackages]);
-}
diff --git a/package_config/lib/src/package_config_json.dart b/package_config/lib/src/package_config_json.dart
deleted file mode 100644
index b9b3416..0000000
--- a/package_config/lib/src/package_config_json.dart
+++ /dev/null
@@ -1,315 +0,0 @@
-// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS 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.
-
-// Parsing and serialization of package configurations.
-
-import "dart:convert";
-import "dart:typed_data";
-
-import "errors.dart";
-import "package_config_impl.dart";
-import "packages_file.dart" as packages_file;
-import "util.dart";
-
-const String _configVersionKey = "configVersion";
-const String _packagesKey = "packages";
-const List<String> _topNames = [_configVersionKey, _packagesKey];
-const String _nameKey = "name";
-const String _rootUriKey = "rootUri";
-const String _packageUriKey = "packageUri";
-const String _languageVersionKey = "languageVersion";
-const List<String> _packageNames = [
-  _nameKey,
-  _rootUriKey,
-  _packageUriKey,
-  _languageVersionKey
-];
-
-const String _generatedKey = "generated";
-const String _generatorKey = "generator";
-const String _generatorVersionKey = "generatorVersion";
-
-final _jsonUtf8Decoder = json.fuse(utf8).decoder;
-
-PackageConfig parsePackageConfigBytes(
-    Uint8List bytes, Uri file, void onError(Object error)) {
-  // TODO(lrn): Make this simpler. Maybe parse directly from bytes.
-  var jsonObject;
-  try {
-    jsonObject = _jsonUtf8Decoder.convert(bytes);
-  } on FormatException catch (e) {
-    onError(PackageConfigFormatException(e.message, e.source, e.offset));
-    return const SimplePackageConfig.empty();
-  }
-  return parsePackageConfigJson(jsonObject, file, onError);
-}
-
-PackageConfig parsePackageConfigString(
-    String source, Uri file, void onError(Object error)) {
-  var jsonObject;
-  try {
-    jsonObject = jsonDecode(source);
-  } on FormatException catch (e) {
-    onError(PackageConfigFormatException(e.message, e.source, e.offset));
-    return const SimplePackageConfig.empty();
-  }
-  return parsePackageConfigJson(jsonObject, file, onError);
-}
-
-/// Creates a [PackageConfig] from a parsed JSON-like object structure.
-///
-/// The [json] argument must be a JSON object (`Map<String, dynamic>`)
-/// containing a `"configVersion"` entry with an integer value in the range
-/// 1 to [PackageConfig.maxVersion],
-/// and with a `"packages"` entry which is a JSON array (`List<dynamic>`)
-/// containing JSON objects which each has the following properties:
-///
-/// * `"name"`: The package name as a string.
-/// * `"rootUri"`: The root of the package as a URI stored as a string.
-/// * `"packageUri"`: Optionally the root of for `package:` URI resolution
-///     for the package, as a relative URI below the root URI
-///     stored as a string.
-/// * `"languageVersion"`: Optionally a language version string which is a
-///     an integer numeral, a decimal point (`.`) and another integer numeral,
-///     where the integer numeral cannot have a sign, and can only have a
-///     leading zero if the entire numeral is a single zero.
-///
-/// All other properties are stored in [extraData].
-///
-/// The [baseLocation] is used as base URI to resolve the "rootUri"
-/// URI referencestring.
-PackageConfig parsePackageConfigJson(
-    dynamic json, Uri baseLocation, void onError(Object error)) {
-  if (!baseLocation.hasScheme || baseLocation.isScheme("package")) {
-    throw PackageConfigArgumentError(baseLocation.toString(), "baseLocation",
-        "Must be an absolute non-package: URI");
-  }
-
-  if (!baseLocation.path.endsWith("/")) {
-    baseLocation = baseLocation.resolveUri(Uri(path: "."));
-  }
-
-  String typeName<T>() {
-    if (0 is T) return "int";
-    if ("" is T) return "string";
-    if (const [] is T) return "array";
-    return "object";
-  }
-
-  T checkType<T>(dynamic value, String name, [String /*?*/ packageName]) {
-    if (value is T) return value;
-    // The only types we are called with are [int], [String], [List<dynamic>]
-    // and Map<String, dynamic>. Recognize which to give a better error message.
-    var message =
-        "$name${packageName != null ? " of package $packageName" : ""}"
-        " is not a JSON ${typeName<T>()}";
-    onError(PackageConfigFormatException(message, value));
-    return null;
-  }
-
-  Package /*?*/ parsePackage(Map<String, dynamic> entry) {
-    String /*?*/ name;
-    String /*?*/ rootUri;
-    String /*?*/ packageUri;
-    String /*?*/ languageVersion;
-    Map<String, dynamic> /*?*/ extraData;
-    var hasName = false;
-    var hasRoot = false;
-    var hasVersion = false;
-    entry.forEach((key, value) {
-      switch (key) {
-        case _nameKey:
-          hasName = true;
-          name = checkType<String>(value, _nameKey);
-          break;
-        case _rootUriKey:
-          hasRoot = true;
-          rootUri = checkType<String>(value, _rootUriKey, name);
-          break;
-        case _packageUriKey:
-          packageUri = checkType<String>(value, _packageUriKey, name);
-          break;
-        case _languageVersionKey:
-          hasVersion = true;
-          languageVersion = checkType<String>(value, _languageVersionKey, name);
-          break;
-        default:
-          (extraData ??= {})[key] = value;
-          break;
-      }
-    });
-    if (!hasName) {
-      onError(PackageConfigFormatException("Missing name entry", entry));
-    }
-    if (!hasRoot) {
-      onError(PackageConfigFormatException("Missing rootUri entry", entry));
-    }
-    if (name == null || rootUri == null) return null;
-    var root = baseLocation.resolve(rootUri);
-    if (!root.path.endsWith("/")) root = root.replace(path: root.path + "/");
-    var packageRoot = root;
-    if (packageUri != null) packageRoot = root.resolve(packageUri);
-    if (!packageRoot.path.endsWith("/")) {
-      packageRoot = packageRoot.replace(path: packageRoot.path + "/");
-    }
-
-    LanguageVersion /*?*/ version;
-    if (languageVersion != null) {
-      version = parseLanguageVersion(languageVersion, onError);
-    } else if (hasVersion) {
-      version = SimpleInvalidLanguageVersion("invalid");
-    }
-
-    return SimplePackage.validate(name, root, packageRoot, version, extraData,
-        (error) {
-      if (error is ArgumentError) {
-        onError(
-            PackageConfigFormatException(error.message, error.invalidValue));
-      } else {
-        onError(error);
-      }
-    });
-  }
-
-  var map = checkType<Map<String, dynamic>>(json, "value");
-  if (map == null) return const SimplePackageConfig.empty();
-  Map<String, dynamic> /*?*/ extraData;
-  List<Package> /*?*/ packageList;
-  int /*?*/ configVersion;
-  map.forEach((key, value) {
-    switch (key) {
-      case _configVersionKey:
-        configVersion = checkType<int>(value, _configVersionKey) ?? 2;
-        break;
-      case _packagesKey:
-        var packageArray = checkType<List<dynamic>>(value, _packagesKey) ?? [];
-        var packages = <Package>[];
-        for (var package in packageArray) {
-          var packageMap =
-              checkType<Map<String, dynamic>>(package, "package entry");
-          if (packageMap != null) {
-            var entry = parsePackage(packageMap);
-            if (entry != null) {
-              packages.add(entry);
-            }
-          }
-        }
-        packageList = packages;
-        break;
-      default:
-        (extraData ??= {})[key] = value;
-        break;
-    }
-  });
-  if (configVersion == null) {
-    onError(PackageConfigFormatException("Missing configVersion entry", json));
-    configVersion = 2;
-  }
-  if (packageList == null) {
-    onError(PackageConfigFormatException("Missing packages list", json));
-    packageList = [];
-  }
-  return SimplePackageConfig(configVersion, packageList, extraData, (error) {
-    if (error is ArgumentError) {
-      onError(PackageConfigFormatException(error.message, error.invalidValue));
-    } else {
-      onError(error);
-    }
-  });
-}
-
-final _jsonUtf8Encoder = JsonUtf8Encoder("  ");
-
-void writePackageConfigJsonUtf8(
-    PackageConfig config, Uri baseUri, Sink<List<int>> output) {
-  // Can be optimized.
-  var data = packageConfigToJson(config, baseUri);
-  output.add(_jsonUtf8Encoder.convert(data) as Uint8List);
-}
-
-void writePackageConfigJsonString(
-    PackageConfig config, Uri baseUri, StringSink output) {
-  // Can be optimized.
-  var data = packageConfigToJson(config, baseUri);
-  output.write(JsonEncoder.withIndent("  ").convert(data) as Uint8List);
-}
-
-Map<String, dynamic> packageConfigToJson(PackageConfig config, Uri baseUri) =>
-    <String, dynamic>{
-      ...?_extractExtraData(config.extraData, _topNames),
-      _configVersionKey: PackageConfig.maxVersion,
-      _packagesKey: [
-        for (var package in config.packages)
-          <String, dynamic>{
-            _nameKey: package.name,
-            _rootUriKey: relativizeUri(package.root, baseUri).toString(),
-            if (package.root != package.packageUriRoot)
-              _packageUriKey:
-                  relativizeUri(package.packageUriRoot, package.root)
-                      .toString(),
-            if (package.languageVersion != null &&
-                package.languageVersion is! InvalidLanguageVersion)
-              _languageVersionKey: package.languageVersion.toString(),
-            ...?_extractExtraData(package.extraData, _packageNames),
-          }
-      ],
-    };
-
-void writeDotPackages(PackageConfig config, Uri baseUri, StringSink output) {
-  var extraData = config.extraData;
-  // Write .packages too.
-  String /*?*/ comment;
-  if (extraData != null) {
-    String /*?*/ generator = extraData[_generatorKey];
-    if (generator != null) {
-      String /*?*/ generated = extraData[_generatedKey];
-      String /*?*/ generatorVersion = extraData[_generatorVersionKey];
-      comment = "Generated by $generator"
-          "${generatorVersion != null ? " $generatorVersion" : ""}"
-          "${generated != null ? " on $generated" : ""}.";
-    }
-  }
-  packages_file.write(output, config, baseUri: baseUri, comment: comment);
-  return;
-}
-
-/// If "extraData" is a JSON map, then return it, otherwise return null.
-///
-/// If the value contains any of the [reservedNames] for the current context,
-/// entries with that name in the extra data are dropped.
-Map<String, dynamic> /*?*/ _extractExtraData(
-    dynamic data, Iterable<String> reservedNames) {
-  if (data is Map<String, dynamic>) {
-    if (data.isEmpty) return null;
-    for (var name in reservedNames) {
-      if (data.containsKey(name)) {
-        data = {
-          for (var key in data.keys)
-            if (!reservedNames.contains(key)) key: data[key]
-        };
-        if (data.isEmpty) return null;
-        for (var value in data.values) {
-          if (!_validateJson(value)) return null;
-        }
-      }
-    }
-    return data;
-  }
-  return null;
-}
-
-/// Checks that the object is a valid JSON-like data structure.
-bool _validateJson(dynamic object) {
-  if (object == null || true == object || false == object) return true;
-  if (object is num || object is String) return true;
-  if (object is List<dynamic>) {
-    for (var element in object) if (!_validateJson(element)) return false;
-    return true;
-  }
-  if (object is Map<String, dynamic>) {
-    for (var value in object.values) if (!_validateJson(value)) return false;
-    return true;
-  }
-  return false;
-}
diff --git a/package_config/lib/src/packages_file.dart b/package_config/lib/src/packages_file.dart
deleted file mode 100644
index e65e7e8..0000000
--- a/package_config/lib/src/packages_file.dart
+++ /dev/null
@@ -1,185 +0,0 @@
-// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS 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 "package_config_impl.dart";
-
-import "util.dart";
-import "errors.dart";
-
-/// Parses a `.packages` file into a [PackageConfig].
-///
-/// The [source] is the byte content of a `.packages` file, assumed to be
-/// UTF-8 encoded. In practice, all significant parts of the file must be ASCII,
-/// so Latin-1 or Windows-1252 encoding will also work fine.
-///
-/// If the file content is available as a string, its [String.codeUnits] can
-/// be used as the `source` argument of this function.
-///
-/// The [baseLocation] is used as a base URI to resolve all relative
-/// URI references against.
-/// If the content was read from a file, `baseLocation` should be the
-/// location of that file.
-///
-/// Returns a simple package configuration where each package's
-/// [Package.packageUriRoot] is the same as its [Package.root]
-/// and it has no [Package.languageVersion].
-PackageConfig parse(
-    List<int> source, Uri baseLocation, void onError(Object error)) {
-  if (baseLocation.isScheme("package")) {
-    onError(PackageConfigArgumentError(
-        baseLocation, "baseLocation", "Must not be a package: URI"));
-    return PackageConfig.empty;
-  }
-  var index = 0;
-  var packages = <Package>[];
-  var packageNames = <String>{};
-  while (index < source.length) {
-    var ignoreLine = false;
-    var start = index;
-    var separatorIndex = -1;
-    var end = source.length;
-    var char = source[index++];
-    if (char == $cr || char == $lf) {
-      continue;
-    }
-    if (char == $colon) {
-      onError(PackageConfigFormatException(
-          "Missing package name", source, index - 1));
-      ignoreLine = true; // Ignore if package name is invalid.
-    } else {
-      ignoreLine = char == $hash; // Ignore if comment.
-    }
-    var queryStart = -1;
-    var fragmentStart = -1;
-    while (index < source.length) {
-      char = source[index++];
-      if (char == $colon && separatorIndex < 0) {
-        separatorIndex = index - 1;
-      } else if (char == $cr || char == $lf) {
-        end = index - 1;
-        break;
-      } else if (char == $question && queryStart < 0 && fragmentStart < 0) {
-        queryStart = index - 1;
-      } else if (char == $hash && fragmentStart < 0) {
-        fragmentStart = index - 1;
-      }
-    }
-    if (ignoreLine) continue;
-    if (separatorIndex < 0) {
-      onError(
-          PackageConfigFormatException("No ':' on line", source, index - 1));
-      continue;
-    }
-    var packageName = String.fromCharCodes(source, start, separatorIndex);
-    var invalidIndex = checkPackageName(packageName);
-    if (invalidIndex >= 0) {
-      onError(PackageConfigFormatException(
-          "Not a valid package name", source, start + invalidIndex));
-      continue;
-    }
-    if (queryStart >= 0) {
-      onError(PackageConfigFormatException(
-          "Location URI must not have query", source, queryStart));
-      end = queryStart;
-    } else if (fragmentStart >= 0) {
-      onError(PackageConfigFormatException(
-          "Location URI must not have fragment", source, fragmentStart));
-      end = fragmentStart;
-    }
-    var packageValue = String.fromCharCodes(source, separatorIndex + 1, end);
-    Uri packageLocation;
-    try {
-      packageLocation = baseLocation.resolve(packageValue);
-    } on FormatException catch (e) {
-      onError(PackageConfigFormatException.from(e));
-      continue;
-    }
-    if (packageLocation.isScheme("package")) {
-      onError(PackageConfigFormatException(
-          "Package URI as location for package", source, separatorIndex + 1));
-      continue;
-    }
-    if (!packageLocation.path.endsWith('/')) {
-      packageLocation =
-          packageLocation.replace(path: packageLocation.path + "/");
-    }
-    if (packageNames.contains(packageName)) {
-      onError(PackageConfigFormatException(
-          "Same package name occured more than once", source, start));
-      continue;
-    }
-    var package = SimplePackage.validate(
-        packageName, packageLocation, packageLocation, null, null, (error) {
-      if (error is ArgumentError) {
-        onError(PackageConfigFormatException(error.message, source));
-      } else {
-        onError(error);
-      }
-    });
-    if (package != null) {
-      packages.add(package);
-      packageNames.add(packageName);
-    }
-  }
-  return SimplePackageConfig(1, packages, null, onError);
-}
-
-/// Writes the configuration to a [StringSink].
-///
-/// If [comment] is provided, the output will contain this comment
-/// with `# ` in front of each line.
-/// Lines are defined as ending in line feed (`'\n'`). If the final
-/// line of the comment doesn't end in a line feed, one will be added.
-///
-/// If [baseUri] is provided, package locations will be made relative
-/// to the base URI, if possible, before writing.
-///
-/// If [allowDefaultPackage] is `true`, the [packageMapping] may contain an
-/// empty string mapping to the _default package name_.
-///
-/// All the keys of [packageMapping] must be valid package names,
-/// and the values must be URIs that do not have the `package:` scheme.
-void write(StringSink output, PackageConfig config,
-    {Uri baseUri, String comment}) {
-  if (baseUri != null && !baseUri.isAbsolute) {
-    throw PackageConfigArgumentError(baseUri, "baseUri", "Must be absolute");
-  }
-
-  if (comment != null) {
-    var lines = comment.split('\n');
-    if (lines.last.isEmpty) lines.removeLast();
-    for (var commentLine in lines) {
-      output.write('# ');
-      output.writeln(commentLine);
-    }
-  } else {
-    output.write("# generated by package:package_config at ");
-    output.write(DateTime.now());
-    output.writeln();
-  }
-  for (var package in config.packages) {
-    var packageName = package.name;
-    var uri = package.packageUriRoot;
-    // Validate packageName.
-    if (!isValidPackageName(packageName)) {
-      throw PackageConfigArgumentError(
-          config, "config", '"$packageName" is not a valid package name');
-    }
-    if (uri.scheme == "package") {
-      throw PackageConfigArgumentError(
-          config, "config", "Package location must not be a package URI: $uri");
-    }
-    output.write(packageName);
-    output.write(':');
-    // If baseUri is provided, make the URI relative to baseUri.
-    if (baseUri != null) {
-      uri = relativizeUri(uri, baseUri);
-    }
-    if (!uri.path.endsWith('/')) {
-      uri = uri.replace(path: uri.path + '/');
-    }
-    output.write(uri);
-    output.writeln();
-  }
-}
diff --git a/package_config/lib/src/packages_impl.dart b/package_config/lib/src/packages_impl.dart
index 19f1039..817002f 100644
--- a/package_config/lib/src/packages_impl.dart
+++ b/package_config/lib/src/packages_impl.dart
@@ -5,7 +5,6 @@
 /// Implementations of [Packages] that may be used in either server or browser
 /// based applications. For implementations that can only run in the browser,
 /// see [package_config.packages_io_impl].
-@Deprecated("Use the package_config.json based API")
 library package_config.packages_impl;
 
 import "dart:collection" show UnmodifiableMapView;
@@ -18,13 +17,13 @@
   const NoPackages();
 
   Uri resolve(Uri packageUri, {Uri notFound(Uri packageUri)}) {
-    var packageName = checkValidPackageUri(packageUri, "packageUri");
+    String packageName = checkValidPackageUri(packageUri);
     if (notFound != null) return notFound(packageUri);
-    throw ArgumentError.value(
+    throw new ArgumentError.value(
         packageUri, "packageUri", 'No package named "$packageName"');
   }
 
-  Iterable<String> get packages => Iterable<String>.empty();
+  Iterable<String> get packages => new Iterable<String>.empty();
 
   Map<String, Uri> asMap() => const <String, Uri>{};
 
@@ -42,14 +41,14 @@
 abstract class PackagesBase implements Packages {
   Uri resolve(Uri packageUri, {Uri notFound(Uri packageUri)}) {
     packageUri = packageUri.normalizePath();
-    var packageName = checkValidPackageUri(packageUri, "packageUri");
-    var packageBase = getBase(packageName);
+    String packageName = checkValidPackageUri(packageUri);
+    Uri packageBase = getBase(packageName);
     if (packageBase == null) {
       if (notFound != null) return notFound(packageUri);
-      throw ArgumentError.value(
+      throw new ArgumentError.value(
           packageUri, "packageUri", 'No package named "$packageName"');
     }
-    var packagePath = packageUri.path.substring(packageName.length + 1);
+    String packagePath = packageUri.path.substring(packageName.length + 1);
     return packageBase.resolve(packagePath);
   }
 
@@ -76,13 +75,13 @@
 
   Iterable<String> get packages => _mapping.keys;
 
-  Map<String, Uri> asMap() => UnmodifiableMapView<String, Uri>(_mapping);
+  Map<String, Uri> asMap() => new UnmodifiableMapView<String, Uri>(_mapping);
 
   String get defaultPackageName => _mapping[""]?.toString();
 
   String packageMetadata(String packageName, String key) {
     if (packageName.isEmpty) return null;
-    var uri = _mapping[packageName];
+    Uri uri = _mapping[packageName];
     if (uri == null || !uri.hasFragment) return null;
     // This can be optimized, either by caching the map or by
     // parsing incrementally instead of parsing the entire fragment.
@@ -113,7 +112,7 @@
   Uri getBase(String packageName) => _packageBase.resolve("$packageName/");
 
   Error _failListingPackages() {
-    return UnsupportedError(
+    return new UnsupportedError(
         "Cannot list packages for a ${_packageBase.scheme}: "
         "based package root");
   }
diff --git a/package_config/lib/src/packages_io_impl.dart b/package_config/lib/src/packages_io_impl.dart
index c623f4d..9eba9ce 100644
--- a/package_config/lib/src/packages_io_impl.dart
+++ b/package_config/lib/src/packages_io_impl.dart
@@ -4,15 +4,14 @@
 
 /// Implementations of [Packages] that can only be used in server based
 /// applications.
-@Deprecated("Use the package_config.json based API")
 library package_config.packages_io_impl;
 
 import "dart:collection" show UnmodifiableMapView;
 import "dart:io" show Directory;
 
-import "packages_impl.dart";
+import "package:path/path.dart" as path;
 
-import "util_io.dart";
+import "packages_impl.dart";
 
 /// A [Packages] implementation based on a local directory.
 class FilePackagesDirectoryPackages extends PackagesBase {
@@ -23,15 +22,15 @@
 
   Uri getBase(String packageName) {
     return _packageToBaseUriMap.putIfAbsent(packageName, () {
-      return Uri.file(pathJoin(_packageDir.path, packageName, '.'));
+      return new Uri.file(path.join(_packageDir.path, packageName, '.'));
     });
   }
 
   Iterable<String> _listPackageNames() {
     return _packageDir
         .listSync()
-        .whereType<Directory>()
-        .map((e) => fileName(e.path));
+        .where((e) => e is Directory)
+        .map((e) => path.basename(e.path));
   }
 
   Iterable<String> get packages => _listPackageNames();
@@ -41,6 +40,6 @@
     for (var packageName in _listPackageNames()) {
       result[packageName] = getBase(packageName);
     }
-    return UnmodifiableMapView<String, Uri>(result);
+    return new UnmodifiableMapView<String, Uri>(result);
   }
 }
diff --git a/package_config/lib/src/util.dart b/package_config/lib/src/util.dart
index 50b140f..f1e1afd 100644
--- a/package_config/lib/src/util.dart
+++ b/package_config/lib/src/util.dart
@@ -1,11 +1,11 @@
-// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS 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.
 
 /// Utility methods used by more than one library in the package.
 library package_config.util;
 
-import "errors.dart";
+import "package:charcode/ascii.dart";
 
 // All ASCII characters that are valid in a package name, with space
 // for all the invalid ones (including space).
@@ -15,7 +15,7 @@
 
 /// Tests whether something is a valid Dart package name.
 bool isValidPackageName(String string) {
-  return checkPackageName(string) < 0;
+  return _findInvalidCharacter(string) < 0;
 }
 
 /// Check if a string is a valid package name.
@@ -26,10 +26,10 @@
 /// Returns `-1` if the string is valid.
 /// Otherwise returns the index of the first invalid character,
 /// or `string.length` if the string contains no non-'.' character.
-int checkPackageName(String string) {
+int _findInvalidCharacter(String string) {
   // Becomes non-zero if any non-'.' character is encountered.
-  var nonDot = 0;
-  for (var i = 0; i < string.length; i++) {
+  int nonDot = 0;
+  for (int i = 0; i < string.length; i++) {
     var c = string.codeUnitAt(i);
     if (c > 0x7f || _validPackageNameCharacters.codeUnitAt(c) <= $space) {
       return i;
@@ -40,199 +40,58 @@
   return -1;
 }
 
-/// Validate that a [Uri] is a valid `package:` URI.
-///
-/// Used to validate user input.
-///
-/// Returns the package name extracted from the package URI,
-/// which is the path segment between `package:` and the first `/`.
-String checkValidPackageUri(Uri packageUri, String name) {
+/// Validate that a Uri is a valid package:URI.
+String checkValidPackageUri(Uri packageUri) {
   if (packageUri.scheme != "package") {
-    throw PackageConfigArgumentError(packageUri, name, "Not a package: URI");
+    throw new ArgumentError.value(
+        packageUri, "packageUri", "Not a package: URI");
   }
   if (packageUri.hasAuthority) {
-    throw PackageConfigArgumentError(
-        packageUri, name, "Package URIs must not have a host part");
+    throw new ArgumentError.value(
+        packageUri, "packageUri", "Package URIs must not have a host part");
   }
   if (packageUri.hasQuery) {
     // A query makes no sense if resolved to a file: URI.
-    throw PackageConfigArgumentError(
-        packageUri, name, "Package URIs must not have a query part");
+    throw new ArgumentError.value(
+        packageUri, "packageUri", "Package URIs must not have a query part");
   }
   if (packageUri.hasFragment) {
     // We could leave the fragment after the URL when resolving,
     // but it would be odd if "package:foo/foo.dart#1" and
     // "package:foo/foo.dart#2" were considered different libraries.
     // Keep the syntax open in case we ever get multiple libraries in one file.
-    throw PackageConfigArgumentError(
-        packageUri, name, "Package URIs must not have a fragment part");
+    throw new ArgumentError.value(
+        packageUri, "packageUri", "Package URIs must not have a fragment part");
   }
   if (packageUri.path.startsWith('/')) {
-    throw PackageConfigArgumentError(
-        packageUri, name, "Package URIs must not start with a '/'");
+    throw new ArgumentError.value(
+        packageUri, "packageUri", "Package URIs must not start with a '/'");
   }
-  var firstSlash = packageUri.path.indexOf('/');
+  int firstSlash = packageUri.path.indexOf('/');
   if (firstSlash == -1) {
-    throw PackageConfigArgumentError(packageUri, name,
+    throw new ArgumentError.value(packageUri, "packageUri",
         "Package URIs must start with the package name followed by a '/'");
   }
-  var packageName = packageUri.path.substring(0, firstSlash);
-  var badIndex = checkPackageName(packageName);
+  String packageName = packageUri.path.substring(0, firstSlash);
+  int badIndex = _findInvalidCharacter(packageName);
   if (badIndex >= 0) {
     if (packageName.isEmpty) {
-      throw PackageConfigArgumentError(
-          packageUri, name, "Package names mus be non-empty");
+      throw new ArgumentError.value(
+          packageUri, "packageUri", "Package names mus be non-empty");
     }
     if (badIndex == packageName.length) {
-      throw PackageConfigArgumentError(packageUri, name,
+      throw new ArgumentError.value(packageUri, "packageUri",
           "Package names must contain at least one non-'.' character");
     }
     assert(badIndex < packageName.length);
-    var badCharCode = packageName.codeUnitAt(badIndex);
+    int badCharCode = packageName.codeUnitAt(badIndex);
     var badChar = "U+" + badCharCode.toRadixString(16).padLeft(4, '0');
     if (badCharCode >= 0x20 && badCharCode <= 0x7e) {
       // Printable character.
       badChar = "'${packageName[badIndex]}' ($badChar)";
     }
-    throw PackageConfigArgumentError(
-        packageUri, name, "Package names must not contain $badChar");
+    throw new ArgumentError.value(
+        packageUri, "packageUri", "Package names must not contain $badChar");
   }
   return packageName;
 }
-
-/// Checks whether URI is just an absolute directory.
-///
-/// * It must have a scheme.
-/// * It must not have a query or fragment.
-/// * The path must end with `/`.
-bool isAbsoluteDirectoryUri(Uri uri) {
-  if (uri.hasQuery) return false;
-  if (uri.hasFragment) return false;
-  if (!uri.hasScheme) return false;
-  var path = uri.path;
-  if (!path.endsWith("/")) return false;
-  return true;
-}
-
-/// Whether the former URI is a prefix of the latter.
-bool isUriPrefix(Uri prefix, Uri path) {
-  assert(!prefix.hasFragment);
-  assert(!prefix.hasQuery);
-  assert(!path.hasQuery);
-  assert(!path.hasFragment);
-  assert(prefix.path.endsWith('/'));
-  return path.toString().startsWith(prefix.toString());
-}
-
-/// Finds the first non-JSON-whitespace character in a file.
-///
-/// Used to heuristically detect whether a file is a JSON file or an .ini file.
-int firstNonWhitespaceChar(List<int> bytes) {
-  for (var i = 0; i < bytes.length; i++) {
-    var char = bytes[i];
-    if (char != 0x20 && char != 0x09 && char != 0x0a && char != 0x0d) {
-      return char;
-    }
-  }
-  return -1;
-}
-
-/// Attempts to return a relative path-only URI for [uri].
-///
-/// First removes any query or fragment part from [uri].
-///
-/// If [uri] is already relative (has no scheme), it's returned as-is.
-/// If that is not desired, the caller can pass `baseUri.resolveUri(uri)`
-/// as the [uri] instead.
-///
-/// If the [uri] has a scheme or authority part which differs from
-/// the [baseUri], or if there is no overlap in the paths of the
-/// two URIs at all, the [uri] is returned as-is.
-///
-/// Otherwise the result is a path-only URI which satsifies
-/// `baseUri.resolveUri(result) == uri`,
-///
-/// The `baseUri` must be absolute.
-Uri relativizeUri(Uri uri, Uri /*?*/ baseUri) {
-  if (baseUri == null) return uri;
-  assert(baseUri.isAbsolute);
-  if (uri.hasQuery || uri.hasFragment) {
-    uri = Uri(
-        scheme: uri.scheme,
-        userInfo: uri.hasAuthority ? uri.userInfo : null,
-        host: uri.hasAuthority ? uri.host : null,
-        port: uri.hasAuthority ? uri.port : null,
-        path: uri.path);
-  }
-
-  // Already relative. We assume the caller knows what they are doing.
-  if (!uri.isAbsolute) return uri;
-
-  if (baseUri.scheme != uri.scheme) {
-    return uri;
-  }
-
-  // If authority differs, we could remove the scheme, but it's not worth it.
-  if (uri.hasAuthority != baseUri.hasAuthority) return uri;
-  if (uri.hasAuthority) {
-    if (uri.userInfo != baseUri.userInfo ||
-        uri.host.toLowerCase() != baseUri.host.toLowerCase() ||
-        uri.port != baseUri.port) {
-      return uri;
-    }
-  }
-
-  baseUri = baseUri.normalizePath();
-  var base = [...baseUri.pathSegments];
-  if (base.isNotEmpty) base.removeLast();
-  uri = uri.normalizePath();
-  var target = [...uri.pathSegments];
-  if (target.isNotEmpty && target.last.isEmpty) target.removeLast();
-  var index = 0;
-  while (index < base.length && index < target.length) {
-    if (base[index] != target[index]) {
-      break;
-    }
-    index++;
-  }
-  if (index == base.length) {
-    if (index == target.length) {
-      return Uri(path: "./");
-    }
-    return Uri(path: target.skip(index).join('/'));
-  } else if (index > 0) {
-    var buffer = StringBuffer();
-    for (var n = base.length - index; n > 0; --n) {
-      buffer.write("../");
-    }
-    buffer.writeAll(target.skip(index), "/");
-    return Uri(path: buffer.toString());
-  } else {
-    return uri;
-  }
-}
-
-// Character constants used by this package.
-/// "Line feed" control character.
-const int $lf = 0x0a;
-
-/// "Carriage return" control character.
-const int $cr = 0x0d;
-
-/// Space character.
-const int $space = 0x20;
-
-/// Character `#`.
-const int $hash = 0x23;
-
-/// Character `.`.
-const int $dot = 0x2e;
-
-/// Character `:`.
-const int $colon = 0x3a;
-
-/// Character `?`.
-const int $question = 0x3f;
-
-/// Character `{`.
-const int $lbrace = 0x7b;
diff --git a/package_config/lib/src/util_io.dart b/package_config/lib/src/util_io.dart
deleted file mode 100644
index 7f21a8d..0000000
--- a/package_config/lib/src/util_io.dart
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS 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.
-
-/// Utility methods requiring dart:io and used by more than one library in the
-/// package.
-library package_config.util_io;
-
-import 'dart:io';
-import 'dart:typed_data';
-
-Future<Uint8List> defaultLoader(Uri uri) async {
-  if (uri.isScheme("file")) {
-    var file = File.fromUri(uri);
-    try {
-      return file.readAsBytes();
-    } catch (_) {
-      return null;
-    }
-  }
-  if (uri.isScheme("http") || uri.isScheme("https")) {
-    return _httpGet(uri);
-  }
-  throw UnsupportedError("Default URI unsupported scheme: $uri");
-}
-
-Future<Uint8List /*?*/ > _httpGet(Uri uri) async {
-  assert(uri.isScheme("http") || uri.isScheme("https"));
-  var client = HttpClient();
-  var request = await client.getUrl(uri);
-  var response = await request.close();
-  if (response.statusCode != HttpStatus.ok) {
-    return null;
-  }
-  var splitContent = await response.toList();
-  var totalLength = 0;
-  if (splitContent.length == 1) {
-    var part = splitContent[0];
-    if (part is Uint8List) {
-      return part;
-    }
-  }
-  for (var list in splitContent) {
-    totalLength += list.length;
-  }
-  var result = Uint8List(totalLength);
-  var offset = 0;
-  for (Uint8List contentPart in splitContent) {
-    result.setRange(offset, offset + contentPart.length, contentPart);
-    offset += contentPart.length;
-  }
-  return result;
-}
-
-/// The file name of a path.
-///
-/// The file name is everything after the last occurrence of
-/// [Platform.pathSeparator], or the entire string if no
-/// path separator occurs in the string.
-String fileName(String path) {
-  var separator = Platform.pathSeparator;
-  var lastSeparator = path.lastIndexOf(separator);
-  if (lastSeparator < 0) return path;
-  return path.substring(lastSeparator + separator.length);
-}
-
-/// The directory name of a path.
-///
-/// The directory name is everything before the last occurrence of
-/// [Platform.pathSeparator], or the empty string if no
-/// path separator occurs in the string.
-String dirName(String path) {
-  var separator = Platform.pathSeparator;
-  var lastSeparator = path.lastIndexOf(separator);
-  if (lastSeparator < 0) return "";
-  return path.substring(0, lastSeparator);
-}
-
-/// Join path parts with the [Platform.pathSeparator].
-///
-/// If a part ends with a path separator, then no extra separator is
-/// inserted.
-String pathJoin(String part1, String part2, [String part3]) {
-  var separator = Platform.pathSeparator;
-  var separator1 = part1.endsWith(separator) ? "" : separator;
-  if (part3 == null) {
-    return "$part1$separator1$part2";
-  }
-  var separator2 = part2.endsWith(separator) ? "" : separator;
-  return "$part1$separator1$part2$separator2$part3";
-}
-
-/// Join an unknown number of path parts with [Platform.pathSeparator].
-///
-/// If a part ends with a path separator, then no extra separator is
-/// inserted.
-String pathJoinAll(Iterable<String> parts) {
-  var buffer = StringBuffer();
-  var separator = "";
-  for (var part in parts) {
-    buffer..write(separator)..write(part);
-    separator =
-        part.endsWith(Platform.pathSeparator) ? "" : Platform.pathSeparator;
-  }
-  return buffer.toString();
-}
diff --git a/package_config/pubspec.yaml b/package_config/pubspec.yaml
index 5c05891..b51932e 100644
--- a/package_config/pubspec.yaml
+++ b/package_config/pubspec.yaml
@@ -1,20 +1,15 @@
 name: package_config
-version: 1.9.1
-description: Support for working with Package Configuration files.
+version: 1.1.0
+description: Support for working with Package Resolution config files.
+author: Dart Team <misc@dartlang.org>
 homepage: https://github.com/dart-lang/package_config
 
 environment:
-  sdk: '>=2.7.0 <3.0.0'
+  sdk: '>=2.0.0-dev <3.0.0'
 
 dependencies:
-  path: ^1.6.4
   charcode: ^1.1.0
+  path: ^1.0.0
 
 dev_dependencies:
-  test: ^1.6.4
-  matcher: ^0.12.5
-  pedantic: ^1.8.0
-
-  build_runner: ^1.0.0
-  build_web_compilers: ^2.0.0
-  build_test: ^0.10.0
+  test: ^1.3.0
diff --git a/pedantic/BUILD.gn b/pedantic/BUILD.gn
index 0c47148..c7ef7f4 100644
--- a/pedantic/BUILD.gn
+++ b/pedantic/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by importer.py for pedantic-1.9.0
+# This file is generated by importer.py for pedantic-1.8.0+1
 
 import("//build/dart/dart_library.gni")
 
diff --git a/pedantic/CHANGELOG.md b/pedantic/CHANGELOG.md
index a15b3d0..05399aa 100644
--- a/pedantic/CHANGELOG.md
+++ b/pedantic/CHANGELOG.md
@@ -1,47 +1,4 @@
-## 1.9.0
-
-- Enforce 17 new lint rules:
-
-  - [`always_declare_return_types`]
-  - [`always_require_non_null_named_parameters`]
-  - [`annotate_overrides`]
-  - [`avoid_null_checks_in_equality_operators`]
-  - [`camel_case_extensions`]
-  - [`omit_local_variable_types`]
-  - [`prefer_adjacent_string_concatenation`]
-  - [`prefer_collection_literals`]
-  - [`prefer_conditional_assignment`]
-  - [`prefer_final_fields`]
-  - [`prefer_for_elements_to_map_fromIterable`]
-  - [`prefer_generic_function_type_aliases`]
-  - [`prefer_if_null_operators`]
-  - [`prefer_single_quotes`]
-  - [`prefer_spread_collections`]
-  - [`unnecessary_this`]
-  - [`use_function_type_syntax_for_parameters`]
-
-- Mark a number of lints unused, see `README.md` for details.
-
-[`always_declare_return_types`]: https://dart-lang.github.io/linter/lints/always_declare_return_types.html
-[`always_require_non_null_named_parameters`]: https://dart-lang.github.io/linter/lints/always_require_non_null_named_parameters.html
-[`annotate_overrides`]: https://dart-lang.github.io/linter/lints/annotate_overrides.html
-[`avoid_null_checks_in_equality_operators`]: https://dart-lang.github.io/linter/lints/avoid_null_checks_in_equality_operators.html
-[`camel_case_extensions`]: https://dart-lang.github.io/linter/lints/camel_case_extensions.html
-[`omit_local_variable_types`]: https://dart-lang.github.io/linter/lints/omit_local_variable_types.html
-[`prefer_adjacent_string_concatenation`]: https://dart-lang.github.io/linter/lints/prefer_adjacent_string_concatenation.html
-[`prefer_collection_literals`]: https://dart-lang.github.io/linter/lints/prefer_collection_literals.html
-[`prefer_conditional_assignment`]: https://dart-lang.github.io/linter/lints/prefer_conditional_assignment.html
-[`prefer_final_fields`]: https://dart-lang.github.io/linter/lints/prefer_final_fields.html
-[`prefer_for_elements_to_map_fromIterable`]: https://dart-lang.github.io/linter/lints/prefer_for_elements_to_map_fromIterable.html
-[`prefer_generic_function_type_aliases`]: https://dart-lang.github.io/linter/lints/prefer_generic_function_type_aliases.html
-[`prefer_if_null_operators`]: https://dart-lang.github.io/linter/lints/prefer_if_null_operators.html
-[`prefer_single_quotes`]: https://dart-lang.github.io/linter/lints/prefer_single_quotes.html
-[`prefer_spread_collections`]: https://dart-lang.github.io/linter/lints/prefer_spread_collections.html
-[`unnecessary_this`]: https://dart-lang.github.io/linter/lints/unnecessary_this.html
-[`use_function_type_syntax_for_parameters`]: https://dart-lang.github.io/linter/lints/use_function_type_syntax_for_parameters.html
-
 ## 1.8.0
-
 - Enforce three new lint rules:
 
   - [`prefer_iterable_whereType`]
diff --git a/pedantic/README.md b/pedantic/README.md
index df8f6de..3d97288 100644
--- a/pedantic/README.md
+++ b/pedantic/README.md
@@ -27,7 +27,8 @@
    - The `TODO` hint is a permanent exception.
    - Deprecation hints are a permanent exception. Deprecations are handled
      separately on a case by case basis.
-   - `unused_element`, `unused_field` and `unused_local_variable` are allowed.
+   - `unnecessary_no_such_method`, `unused_element`, `unused_field` and
+     `unused_local_variable` are allowed.
    - When a new SDK version adds new errors, warnings or hints, we either clean
      up everything before switching SDK version or maintain a whitelist of
      allowed violations so it can be gradually cleaned up.
@@ -79,174 +80,26 @@
 violates Effective Dart "DO format your code using dartfmt". See note about
 Flutter SDK style below.
 
-`always_put_required_named_parameters_first`
-does not allow for other logical orderings of parameters, such as matching the
-class field order or alphabetical.
-
 `always_specify_types`
 violates Effective Dart "AVOID type annotating initialized local variables"
 and others. See note about Flutter SDK style below.
 
-`avoid_annotating_with_dynamic`
-violates Effective Dart "PREFER annotating with `dynamic` instead of letting
-inference fail".
-
 `avoid_as`
-does not reflect common usage. See note about Flutter SDK style below.
-
-`avoid_catches_without_on_clauses`
-is too strict, catching anything is useful and common even if not always the
-most correct thing to do.
-
-`avoid_catching_errors`
-is too strict, the distinction between `Error` and `Exception` is followed
-closely in the SDK but is impractical to follow everywhere.
-
-`avoid_classes_with_only_static_members`
-is too strict, Effective Dart explicitly calls out some cases (constants,
-enum-like types) where it makes sense to violate this lint.
-
-`avoid_double_and_int_checks`
-only applies to web, but there is currently no mechanism for enabling a lint
-on web code only.
-
-`avoid_equals_and_hashcode_on_mutable_classes`
-would require the `@immutable` annotation to be consistently and correctly
-used everywhere.
-
-`avoid_field_initializers_in_const_classes`
-does not offer a clear readability benefit.
-
-`avoid_js_rounded_ints`
-only applies to web, but there is currently no mechanism for enabling a lint
-on web code only.
-
-`avoid_print`
-is too strict, it's okay to `print` in some code.
-
-`avoid_returning_null`
-will be obsoleted by NNBD.
-
-`avoid_returning_this`
-has occasional false positives, and adds little value as the cascade operator
-for fluent interfaces in Dart is already very popular.
-
-`avoid_slow_async_io`
-gives wrong advice if the underlying filesystem has unusual properties, for
-example a network mount.
-
-`avoid_void_async`
-prevents a valid style where an asynchronous method has a `void` return type
-to indicate that nobody should `await` for the result.
-
-`cancel_subscriptions`
-has false positives when you use a utility method or class to call `cancel`.
-
-`close_sinks`
-has false positives when you use a utility method or class to call `close`.
-
-`constant_identifier_names`
-is too strict as it does not support the exceptions allowed in Effective Dart
-for use of ALL_CAPS.
+does not reflect standard usage. See note about Flutter SDK style below.
 
 `control_flow_in_finally`
 does not offer enough value: people are unlikely to do this by accident,
 and there are occasional valid uses.
 
-`directives_ordering`
-would enforce a slightly different ordering to that used by IntelliJ and other
-tools using the analyzer.
-
 `empty_statements`
 is superfluous, enforcing use of `dartfmt` is sufficient to make empty
  statements obvious.
 
-`flutter_style_todos`
-is for Flutter SDK internal use, see note about Flutter SDK style below.
-
-`invariant_booleans`
-is experimental.
-
-`join_return_with_assignment`
-does not reflect common usage.
-
-`one_member_abstracts`
-is too strict, classes might implement more than one such abstract class and
-there is no equivalent way to do this using functions.
-
-`parameter_assignments`
-does not reflect common usage, and will cause particular problems with NNBD
-code.
-
-`prefer_asserts_in_initializer_lists`
-does not reflect common usage.
-
-`prefer_asserts_with_message`
-is too strict; some asserts do not benefit from documentation.
-
 `prefer_bool_in_asserts`
  is obsolete in Dart 2; bool is required in asserts.
 
-`prefer_const_constructors`
-would add a lot of noise to code now that `new` is optional.
-
-`prefer_const_constructors_in_immutables`
-does not often apply as `@immutable` is not widely used.
-
-`prefer_const_literals_to_create_immutables`
-is too strict, requiring `const` everywhere adds noise.
-
-`prefer_constructors_over_static_methods`
-is too strict, in some cases static methods are better.
-
-`prefer_double_quotes`
-does not reflect common usage.
-
-`prefer_expression_function_bodies`
-is too strict, braces look better for long expressions.
-
-`prefer_final_in_for_each`
-does not reflect common usage.
-
 `prefer_final_locals`
-does not reflect common usage.
-
-`prefer_foreach`
-is too strict; `forEach` is not always an improvement.
-
-`prefer_int_literals`
-does not reflect common usage.
-
-`prefer_typing_uninitialized_variables`
-will be obsoleted by NNBD, which comes with type inference for uninitialized
-variables.
-
-`literal_only_boolean_expressions`
-does not offer enough value: such expressions are easily spotted and so hard
-to add by accident.
-
-`no_adjacent_strings_in_list`
-does not offer enough value: adjacent strings in lists are easily spotted
-when the code is formatted with `dartfmt`.
-
-`sort_constructors_first`
-does not reflect common usage.
-
-`sort_unnamed_constructors_first`
-is too strict, people are free to choose the best constructor ordering.
-
-`test_types_in_equals`
-does not offer enough value: there are plenty of other mistakes possible in
-`operator ==` implementations. It's better to use codegen.
-
-`unnecessary_null_aware_assignments`
-does not offer enough value: this is hard to do by mistake, and harmless.
-
-`use_setters_to_change_properties`
-is too strict: it can't detect when something is conceptually a property.
-
-`use_to_and_if_as_applicable`
-is too strict: it can't detect when the style rule actually applies.
+does not reflect standard usage.
 
 `throw_in_finally`
 does not offer enough value: people are unlikely to do this by accident,
diff --git a/pedantic/lib/analysis_options.1.9.0.yaml b/pedantic/lib/analysis_options.1.9.0.yaml
deleted file mode 100644
index 909283b..0000000
--- a/pedantic/lib/analysis_options.1.9.0.yaml
+++ /dev/null
@@ -1,54 +0,0 @@
-# Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS 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.
-#
-# Google internally enforced rules. See README.md for more information,
-# including a list of lints that are intentionally _not_ enforced.
-
-linter:
-  rules:
-    - always_declare_return_types
-    - always_require_non_null_named_parameters
-    - annotate_overrides
-    - avoid_empty_else
-    - avoid_init_to_null
-    - avoid_null_checks_in_equality_operators
-    - avoid_relative_lib_imports
-    - avoid_return_types_on_setters
-    - avoid_shadowing_type_parameters
-    - avoid_types_as_parameter_names
-    - camel_case_extensions
-    - curly_braces_in_flow_control_structures
-    - empty_catches
-    - empty_constructor_bodies
-    - library_names
-    - library_prefixes
-    - no_duplicate_case_values
-    - null_closures
-    - omit_local_variable_types
-    - prefer_adjacent_string_concatenation
-    - prefer_collection_literals
-    - prefer_conditional_assignment
-    - prefer_contains
-    - prefer_equal_for_default_values
-    - prefer_final_fields
-    - prefer_for_elements_to_map_fromIterable
-    - prefer_generic_function_type_aliases
-    - prefer_if_null_operators
-    - prefer_is_empty
-    - prefer_is_not_empty
-    - prefer_iterable_whereType
-    - prefer_single_quotes
-    - prefer_spread_collections
-    - recursive_getters
-    - slash_for_doc_comments
-    - type_init_formals
-    - unawaited_futures
-    - unnecessary_const
-    - unnecessary_new
-    - unnecessary_null_in_if_null_operators
-    - unnecessary_this
-    - unrelated_type_equality_checks
-    - use_function_type_syntax_for_parameters
-    - use_rethrow_when_possible
-    - valid_regexps
diff --git a/pedantic/lib/analysis_options.yaml b/pedantic/lib/analysis_options.yaml
index cb7021e..c8cc699 100644
--- a/pedantic/lib/analysis_options.yaml
+++ b/pedantic/lib/analysis_options.yaml
@@ -10,4 +10,4 @@
 # whenever a new version of `package:pedantic` is released. To avoid this,
 # specify a specific version of `analysis_options.yaml` instead.
 
-include: package:pedantic/analysis_options.1.9.0.yaml
+include: package:pedantic/analysis_options.1.8.0.yaml
diff --git a/pedantic/pubspec.yaml b/pedantic/pubspec.yaml
index 71d8bde..278d457 100644
--- a/pedantic/pubspec.yaml
+++ b/pedantic/pubspec.yaml
@@ -1,5 +1,5 @@
 name: pedantic
-version: 1.9.0
+version: 1.8.0+1
 description: How to get the most value from Dart static analysis.
 author: Dart Team <misc@dartlang.org>
 homepage: https://github.com/dart-lang/pedantic
diff --git a/plugin_platform_interface/BUILD.gn b/plugin_platform_interface/BUILD.gn
index 9ea1fb5..7bd3695 100644
--- a/plugin_platform_interface/BUILD.gn
+++ b/plugin_platform_interface/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by importer.py for plugin_platform_interface-1.0.2
+# This file is generated by importer.py for plugin_platform_interface-1.0.1
 
 import("//build/dart/dart_library.gni")
 
diff --git a/plugin_platform_interface/CHANGELOG.md b/plugin_platform_interface/CHANGELOG.md
index 1c240ea..9fa28ec 100644
--- a/plugin_platform_interface/CHANGELOG.md
+++ b/plugin_platform_interface/CHANGELOG.md
@@ -1,7 +1,3 @@
-## 1.0.2
-
-* Make the pedantic dev_dependency explicit.
-
 ## 1.0.1
 
 * Fixed a bug that made all platform interfaces appear as mocks in release builds (https://github.com/flutter/flutter/issues/46941).
diff --git a/plugin_platform_interface/pubspec.yaml b/plugin_platform_interface/pubspec.yaml
index b5f263c..4adfe6a 100644
--- a/plugin_platform_interface/pubspec.yaml
+++ b/plugin_platform_interface/pubspec.yaml
@@ -12,7 +12,7 @@
 # be done when absolutely necessary and after the ecosystem has already migrated to 1.X.Y version
 # that is forward compatible with 2.0.0 (ideally the ecosystem have migrated to depend on:
 # `plugin_platform_interface: >=1.X.Y <3.0.0`).
-version: 1.0.2
+version: 1.0.1
 
 homepage: https://github.com/flutter/plugins/plugin_platform_interface
 
@@ -25,4 +25,3 @@
 dev_dependencies:
   mockito: ^4.1.1
   test: ^1.9.4
-  pedantic: ^1.8.0
diff --git a/scratch_space/BUILD.gn b/scratch_space/BUILD.gn
new file mode 100644
index 0000000..959a1c8
--- /dev/null
+++ b/scratch_space/BUILD.gn
@@ -0,0 +1,21 @@
+# This file is generated by importer.py for scratch_space-0.0.4+2
+
+import("//build/dart/dart_library.gni")
+
+dart_library("scratch_space") {
+  package_name = "scratch_space"
+
+  # This parameter is left empty as we don't care about analysis or exporting
+  # these sources outside of the tree.
+  sources = []
+
+  disable_analysis = true
+
+  deps = [
+    "//third_party/dart-pkg/pub/path",
+    "//third_party/dart-pkg/pub/pedantic",
+    "//third_party/dart-pkg/pub/build",
+    "//third_party/dart-pkg/pub/pool",
+    "//third_party/dart-pkg/pub/crypto",
+  ]
+}
diff --git a/scratch_space/CHANGELOG.md b/scratch_space/CHANGELOG.md
new file mode 100644
index 0000000..c36f90f
--- /dev/null
+++ b/scratch_space/CHANGELOG.md
@@ -0,0 +1,56 @@
+## 0.0.4+2
+
+- Fix a race condition bug where `ensureAssets` would complete before all
+  pending writes were completed. If the next build was scheduled before these
+  writes finished then they would get the old result.
+
+## 0.0.4+1
+
+- Fix `ScratchSpace.fileFor` on windows to normalize the paths so they dont
+  contain mixed separators.
+
+## 0.0.4
+
+- Add `requireContent` argument to `copyOutput` to allow asserting that a file
+  produced in a scratch space is not empty.
+
+## 0.0.3+2
+
+- Declare support for `package:build` version `1.x.x`.
+
+## 0.0.3+1
+
+- Increased the upper bound for the sdk to `<3.0.0`.
+
+## 0.0.3
+
+- Use digests to improve `ensureAssets` performance.
+  - Scratch spaces can now be used across builds without cleaning them up, and
+    will check digests and update assets as needed.
+
+## 0.0.2+1
+
+- Fix a bug where failing to read an asset in serve mode could get the build
+  stuck.
+
+## 0.0.2
+
+- Allow creating files at the root of the scratch space.
+
+## 0.0.1+3
+
+- Allow package:build 0.12.0
+
+## 0.0.1+2
+
+- Allow package:build 0.11.0
+
+## 0.0.1+1
+
+- Fix a deadlock issue around the file descriptor pool, only take control of a
+  resource right before actually touching disk instead of also encapsulating
+  the `readAsBytes` call from the wrapped `AssetReader`.
+
+## 0.0.1
+
+- Initial release, adds the `ScratchSpace` class.
diff --git a/scratch_space/LICENSE b/scratch_space/LICENSE
new file mode 100644
index 0000000..389ce98
--- /dev/null
+++ b/scratch_space/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2017, the Dart project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of Google Inc. nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/scratch_space/README.md b/scratch_space/README.md
new file mode 100644
index 0000000..341b8ee
--- /dev/null
+++ b/scratch_space/README.md
@@ -0,0 +1,78 @@
+[![Build Status](https://travis-ci.org/dart-lang/build.svg?branch=master)](https://travis-ci.org/dart-lang/build)
+[![Pub Package](https://img.shields.io/pub/v/scratch_space.svg)](https://pub.dev/packages/scratch_space)
+
+A [`ScratchSpace`][dartdoc:ScratchSpace] is a thin wrapper around a temporary
+directory. The constructor takes zero arguments, so making one is as simple as
+doing `new ScratchSpace()`.
+
+In general, you should wrap a `ScratchSpace` in a `Resource`, so that you can
+re-use the scratch space across build steps in an individual build. This is
+safe to do since you are not allowed to overwrite files within a build.
+
+This should look something like the following:
+
+```
+final myScratchSpaceResource =
+    new Resource(() => new ScratchSpace(), dispose: (old) => old.delete());
+```
+
+And then you can get access to it through the `BuildStep#fetchResource` api:
+
+```
+class MyBuilder extends Builder {
+  Future build(BuildStep buildStep) async {
+    var scratchSpace = await buildStep.fetchResource(myScratchSpaceResource);
+  }
+}
+```
+
+### Adding assets to a `ScratchSpace`
+
+To add assets to the `ScratchSpace`, you use the `ensureAssets` method, which
+takes an `Iterable<AssetId>` and an `AssetReader` (for which you should
+generally pass your `BuildStep` which implements that interface).
+
+You must always call this method with all assets that your external executable
+might need in order to set up the proper dependencies and ensure hermetic
+builds.
+
+**Note:** It is important to note that the `ScratchSpace` does not guarantee
+that the version of a file within it is the most updated version, only that
+some version of it exists. For this reason you should create a new
+`ScratchSpace` for each build using the `Resource` class as recommended above.
+
+### Deleting a `ScratchSpace`
+
+When you are done with a `ScratchSpace` you should call `delete` on it to make
+sure it gets cleaned up, otherwise you can end up filling up the users tmp
+directory.
+
+**Note:** You cannot delete individual assets from a `ScratchSpace` today, you
+can only delete the entire thing. If you have a use case for deleting
+individual files you can [file an issue][tracker].
+
+### Getting the actual File objects for a `ScratchSpace`
+
+When invoking an external binary, you probably need to tell it where to look
+for files. There are a few fields/methods to help you do this:
+
+  * `String get packagesDir`: The `packages` directory in the `ScratchSpace`.
+  * `String get tmpDir`: The root temp directory for the `ScratchSpace`.
+  * `File fileFor(AssetId id)`: The File object for `id`.
+
+### Copying back outputs from the temp directory
+
+After running your executable, you most likely have some outputs that you
+need to notify the build system about. To do this you can use the `copyOutput`
+method, which takes an `AssetId` and `AssetWriter` (for which you should
+generally pass your `BuildStep` which implements that interface).
+
+This will copy the asset referenced by the `AssetId` from the temp directory
+back to your actual output directory.
+
+## Feature requests and bugs
+
+Please file feature requests and bugs at the [issue tracker][tracker].
+
+[tracker]: https://github.com/dart-lang/build/issues
+[dartdoc:ScratchSpace]: https://pub.dev/documentation/scratch_space/latest/scratch_space/ScratchSpace-class.html
diff --git a/scratch_space/lib/scratch_space.dart b/scratch_space/lib/scratch_space.dart
new file mode 100644
index 0000000..95c4c8e
--- /dev/null
+++ b/scratch_space/lib/scratch_space.dart
@@ -0,0 +1,6 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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.
+
+export 'src/scratch_space.dart'
+    show ScratchSpace, canonicalUriFor, EmptyOutputException;
diff --git a/scratch_space/lib/src/scratch_space.dart b/scratch_space/lib/src/scratch_space.dart
new file mode 100644
index 0000000..d467cb3
--- /dev/null
+++ b/scratch_space/lib/src/scratch_space.dart
@@ -0,0 +1,160 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+import 'dart:io';
+
+import 'package:build/build.dart';
+import 'package:crypto/crypto.dart';
+import 'package:path/path.dart' as p;
+import 'package:pedantic/pedantic.dart';
+import 'package:pool/pool.dart';
+
+import 'util.dart';
+
+/// Pool for async file writes, we don't want to use too many file handles.
+final _descriptorPool = Pool(32);
+
+/// An on-disk temporary environment for running executables that don't have
+/// a standard Dart library API.
+class ScratchSpace {
+  /// Whether or not this scratch space still exists.
+  bool exists = true;
+
+  /// The `packages` directory under the temp directory.
+  final Directory packagesDir;
+
+  /// The temp directory at the root of this [ScratchSpace].
+  final Directory tempDir;
+
+  // Assets which have a file created but are still being written to.
+  final _pendingWrites = <AssetId, Future<void>>{};
+
+  final _digests = <AssetId, Digest>{};
+
+  ScratchSpace._(this.tempDir)
+      : packagesDir = Directory(p.join(tempDir.path, 'packages'));
+
+  factory ScratchSpace() {
+    var tempDir = Directory(Directory.systemTemp
+        .createTempSync('scratch_space')
+        .resolveSymbolicLinksSync());
+    return ScratchSpace._(tempDir);
+  }
+
+  /// Copies [id] from the tmp dir and writes it back using the [writer].
+  ///
+  /// Note that [BuildStep] implements [AssetWriter] and that is typically
+  /// what you will want to pass in.
+  ///
+  /// This must be called for all outputs which you want to be included as a
+  /// part of the actual build (any other outputs will be deleted with the
+  /// tmp dir and won't be available to other [Builder]s).
+  ///
+  /// If [requireContent] is true and the file is empty an
+  /// [EmptyOutputException] is thrown.
+  Future copyOutput(AssetId id, AssetWriter writer,
+      {bool requireContent = false}) async {
+    var file = fileFor(id);
+    var bytes = await _descriptorPool.withResource(file.readAsBytes);
+    if (requireContent && bytes.isEmpty) throw EmptyOutputException(id);
+    await writer.writeAsBytes(id, bytes);
+  }
+
+  /// Deletes the temp directory for this environment.
+  ///
+  /// This class is no longer valid once the directory is deleted, you must
+  /// create a new [ScratchSpace].
+  Future delete() async {
+    if (!exists) {
+      throw StateError(
+          'Tried to delete a ScratchSpace which was already deleted');
+    }
+    exists = false;
+    _digests.clear();
+    if (_pendingWrites.isNotEmpty) {
+      try {
+        await Future.wait(_pendingWrites.values);
+      } catch (_) {
+        // Ignore any errors, we are essentially just draining this queue
+        // of pending writes but don't care about the result.
+      }
+    }
+    return tempDir.delete(recursive: true);
+  }
+
+  /// Copies [assetIds] to [tempDir] if they don't exist, using [reader] to
+  /// read assets and mark dependencies.
+  ///
+  /// Note that [BuildStep] implements [AssetReader] and that is typically
+  /// what you will want to pass in.
+  ///
+  /// Any asset that is under a `lib` dir will be output under a `packages`
+  /// directory corresponding to its package, and any other assets are output
+  /// directly under the temp dir using their unmodified path.
+  Future ensureAssets(Iterable<AssetId> assetIds, AssetReader reader) {
+    if (!exists) {
+      throw StateError('Tried to use a deleted ScratchSpace!');
+    }
+
+    var futures = assetIds.map((id) async {
+      var digest = await reader.digest(id);
+      var existing = _digests[id];
+      if (digest == existing) {
+        await _pendingWrites[id];
+        return;
+      }
+      _digests[id] = digest;
+
+      try {
+        await _pendingWrites.putIfAbsent(
+            id,
+            () => _descriptorPool.withResource(() async {
+                  var file = fileFor(id);
+                  if (await file.exists()) {
+                    await file.delete();
+                  }
+                  await file.create(recursive: true);
+                  await file.writeAsBytes(await reader.readAsBytes(id));
+                }));
+      } finally {
+        unawaited(_pendingWrites.remove(id));
+      }
+    }).toList();
+
+    return Future.wait(futures);
+  }
+
+  /// Returns the actual [File] in this environment corresponding to [id].
+  ///
+  /// The returned [File] may or may not already exist. Call [ensureAssets]
+  /// with [id] to make sure it is actually present.
+  File fileFor(AssetId id) =>
+      File(p.join(tempDir.path, p.normalize(_relativePathFor(id))));
+}
+
+/// Returns a canonical uri for [id].
+///
+/// If [id] is under a `lib` directory then this returns a `package:` uri,
+/// otherwise it just returns [id#path].
+String canonicalUriFor(AssetId id) {
+  if (topLevelDir(id.path) == 'lib') {
+    var packagePath =
+        p.url.join(id.package, p.url.joinAll(p.url.split(id.path).skip(1)));
+    return 'package:$packagePath';
+  } else {
+    return id.path;
+  }
+}
+
+/// The path relative to the root of the environment for a given [id].
+String _relativePathFor(AssetId id) =>
+    canonicalUriFor(id).replaceFirst('package:', 'packages/');
+
+/// An indication that an output file which was expect to be non-empty had no
+/// content.
+class EmptyOutputException implements Exception {
+  final AssetId id;
+  EmptyOutputException(this.id);
+}
diff --git a/scratch_space/lib/src/util.dart b/scratch_space/lib/src/util.dart
new file mode 100644
index 0000000..ae8f1b7
--- /dev/null
+++ b/scratch_space/lib/src/util.dart
@@ -0,0 +1,17 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'package:path/path.dart' as p;
+
+/// Returns the top level directory in [uri].
+///
+/// Throws an [ArgumentError] if [uri] reaches above the top level directory.
+String topLevelDir(String uri) {
+  var parts = p.url.split(p.url.normalize(uri));
+  if (parts.first == '..') {
+    throw ArgumentError('Cannot compute top level dir for path `$uri` '
+        'which reaches outside the root directory.');
+  }
+  return parts.length == 1 ? null : parts.first;
+}
diff --git a/scratch_space/mono_pkg.yaml b/scratch_space/mono_pkg.yaml
new file mode 100644
index 0000000..0271728
--- /dev/null
+++ b/scratch_space/mono_pkg.yaml
@@ -0,0 +1,20 @@
+dart:
+  - dev
+
+stages:
+  - analyze_and_format:
+    - group:
+        - dartfmt: sdk
+        - dartanalyzer: --fatal-infos --fatal-warnings .
+    - dartanalyzer: --fatal-warnings .
+      dart:
+        - 2.3.0
+  - unit_test:
+    - command: pub run build_runner test
+      os:
+        - linux
+        - windows
+
+cache:
+  directories:
+    - .dart_tool/build
diff --git a/scratch_space/pubspec.yaml b/scratch_space/pubspec.yaml
new file mode 100644
index 0000000..11326df
--- /dev/null
+++ b/scratch_space/pubspec.yaml
@@ -0,0 +1,20 @@
+name: scratch_space
+version: 0.0.4+2
+description: A tool to manage running external executables within package:build
+homepage: https://github.com/dart-lang/build/tree/master/scratch_space
+
+environment:
+  sdk: ">=2.3.0 <3.0.0"
+
+dependencies:
+  build: ">=0.10.0 <2.0.0"
+  crypto: ">=2.0.3 <3.0.0"
+  path: ^1.1.0
+  pedantic: ^1.0.0
+  pool: ^1.0.0
+
+dev_dependencies:
+  build_runner: ^1.0.0
+  build_test: ^0.10.0
+  build_vm_compilers: ">=0.1.0 <2.0.0"
+  test: ^1.0.0
diff --git a/source_map_stack_trace/.travis.yml b/source_map_stack_trace/.travis.yml
index de32a3b..3895dfc 100644
--- a/source_map_stack_trace/.travis.yml
+++ b/source_map_stack_trace/.travis.yml
@@ -5,7 +5,7 @@
 
 dart:
   - dev
-  - 2.7.0
+  - stable
 
 dart_task:
   - test: -p chrome,vm
@@ -16,8 +16,7 @@
     - dart: dev
       dart_task: dartfmt
     - dart: dev
-      dart_task:
-        dartanalizer: --fatal-warnings --fatal-infos .
+      dart_task: analyzer
 
 # Only building master means that we don't run two builds for each pull request.
 branches:
diff --git a/source_map_stack_trace/BUILD.gn b/source_map_stack_trace/BUILD.gn
index d58f4e1..4d50473 100644
--- a/source_map_stack_trace/BUILD.gn
+++ b/source_map_stack_trace/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by importer.py for source_map_stack_trace-2.0.0
+# This file is generated by importer.py for source_map_stack_trace-1.1.5
 
 import("//build/dart/dart_library.gni")
 
@@ -13,6 +13,7 @@
 
   deps = [
     "//third_party/dart-pkg/pub/source_maps",
+    "//third_party/dart-pkg/pub/package_resolver",
     "//third_party/dart-pkg/pub/path",
     "//third_party/dart-pkg/pub/stack_trace",
   ]
diff --git a/source_map_stack_trace/CHANGELOG.md b/source_map_stack_trace/CHANGELOG.md
index 5bfef35..4acc71e 100644
--- a/source_map_stack_trace/CHANGELOG.md
+++ b/source_map_stack_trace/CHANGELOG.md
@@ -1,14 +1,3 @@
-## 2.0.0
-
-### Breaking Changes
-
-* Removed dependency on `package_resolver` and changed the apis to accept a
-  `Map<String, Uri>` which maps package names to the base uri to resolve the
-  `package:` uris for those packages.
-* The `sdkRoot` argument must be an `Uri`. Use `Uri.parse` for use
-  cases previously passing a `String`.
-* The deprecated `packageRoot` argument has been removed.
-
 ## 1.1.5
 
 * Set max SDK version to `<3.0.0`.
diff --git a/source_map_stack_trace/analysis_options.yaml b/source_map_stack_trace/analysis_options.yaml
deleted file mode 100644
index 76c449a..0000000
--- a/source_map_stack_trace/analysis_options.yaml
+++ /dev/null
@@ -1,6 +0,0 @@
-include: package:pedantic/analysis_options.yaml
-analyzer:
-  strong-mode:
-    implicit-casts: false
-  language:
-    strict-raw-types: true
diff --git a/source_map_stack_trace/codereview.settings b/source_map_stack_trace/codereview.settings
new file mode 100644
index 0000000..78c567f
--- /dev/null
+++ b/source_map_stack_trace/codereview.settings
@@ -0,0 +1,3 @@
+CODE_REVIEW_SERVER: https://codereview.chromium.org/
+VIEW_VC: https://github.com/dart-lang/source_map_stack_trace/commit/
+CC_LIST: reviews@dartlang.org
\ No newline at end of file
diff --git a/source_map_stack_trace/lib/source_map_stack_trace.dart b/source_map_stack_trace/lib/source_map_stack_trace.dart
index fd1226a..d0252e0 100644
--- a/source_map_stack_trace/lib/source_map_stack_trace.dart
+++ b/source_map_stack_trace/lib/source_map_stack_trace.dart
@@ -2,6 +2,7 @@
 // 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 'package:package_resolver/package_resolver.dart';
 import 'package:path/path.dart' as p;
 import 'package:source_maps/source_maps.dart';
 import 'package:stack_trace/stack_trace.dart';
@@ -12,31 +13,53 @@
 /// [minified] indicates whether or not the dart2js code was minified. If it
 /// hasn't, this tries to clean up the stack frame member names.
 ///
-/// The [packageMap] maps package names to the base uri used to resolve the
-/// `package:` uris for those packages. It is used to  it's used to reconstruct
-/// `package:` URIs for stack frames that come from packages.
+/// If [packageResolver] is passed, it's used to reconstruct `package:` URIs for
+/// stack frames that come from packages.
 ///
-/// [sdkRoot] is the URI surfaced in the stack traces for SDK libraries.
-/// If it's passed, stack frames from the SDK will have `dart:` URLs.
+/// [sdkRoot] is the URI (usually a `file:` URI) for the SDK containing dart2js.
+/// It can be a [String] or a [Uri]. If it's passed, stack frames from the SDK
+/// will have `dart:` URLs.
+///
+/// [packageRoot] is deprecated and shouldn't be used in new code. This throws
+/// an [ArgumentError] if [packageRoot] and [packageResolver] are both passed.
 StackTrace mapStackTrace(Mapping sourceMap, StackTrace stackTrace,
-    {bool minified = false, Map<String, Uri> packageMap, Uri sdkRoot}) {
+    {bool minified: false,
+    SyncPackageResolver packageResolver,
+    sdkRoot,
+    @Deprecated("Use the packageResolver parameter instead.") packageRoot}) {
+  if (packageRoot != null) {
+    if (packageResolver != null) {
+      throw new ArgumentError(
+          "packageResolver and packageRoot may not both be passed.");
+    }
+
+    packageResolver = new SyncPackageResolver.root(packageRoot);
+  }
+
   if (stackTrace is Chain) {
-    return Chain(stackTrace.traces.map((trace) {
-      return Trace.from(mapStackTrace(sourceMap, trace,
-          minified: minified, packageMap: packageMap, sdkRoot: sdkRoot));
+    return new Chain(stackTrace.traces.map((trace) {
+      return new Trace.from(mapStackTrace(sourceMap, trace,
+          minified: minified,
+          packageResolver: packageResolver,
+          sdkRoot: sdkRoot));
     }));
   }
 
-  var sdkLib = sdkRoot == null ? null : '$sdkRoot/lib';
+  if (sdkRoot != null && sdkRoot is! String && sdkRoot is! Uri) {
+    throw new ArgumentError(
+        'sdkRoot must be a String or a Uri, was "$sdkRoot".');
+  }
 
-  var trace = Trace.from(stackTrace);
-  return Trace(trace.frames.map((frame) {
+  var sdkLib = sdkRoot == null ? null : "$sdkRoot/lib";
+
+  var trace = new Trace.from(stackTrace);
+  return new Trace(trace.frames.map((frame) {
     // If there's no line information, there's no way to translate this frame.
     // We could return it as-is, but these lines are usually not useful anyways.
     if (frame.line == null) return null;
 
     // If there's no column, try using the first column of the line.
-    var column = frame.column ?? 0;
+    var column = frame.column == null ? 0 : frame.column;
 
     // Subtract 1 because stack traces use 1-indexed lines and columns and
     // source maps uses 0-indexed.
@@ -49,19 +72,26 @@
 
     var sourceUrl = span.sourceUrl.toString();
     if (sdkRoot != null && p.url.isWithin(sdkLib, sourceUrl)) {
-      sourceUrl = 'dart:' + p.url.relative(sourceUrl, from: sdkLib);
-    } else if (packageMap != null) {
-      for (var package in packageMap.keys) {
-        var packageUrl = packageMap[package].toString();
-        if (!p.url.isWithin(packageUrl, sourceUrl)) continue;
+      sourceUrl = "dart:" + p.url.relative(sourceUrl, from: sdkLib);
+    } else if (packageResolver != null) {
+      if (packageResolver.packageRoot != null &&
+          p.url.isWithin(packageResolver.packageRoot.toString(), sourceUrl)) {
+        sourceUrl = "package:" +
+            p.url.relative(sourceUrl,
+                from: packageResolver.packageRoot.toString());
+      } else if (packageResolver.packageConfigMap != null) {
+        for (var package in packageResolver.packageConfigMap.keys) {
+          var packageUrl = packageResolver.packageConfigMap[package].toString();
+          if (!p.url.isWithin(packageUrl, sourceUrl)) continue;
 
-        sourceUrl =
-            'package:$package/' + p.url.relative(sourceUrl, from: packageUrl);
-        break;
+          sourceUrl =
+              "package:$package/" + p.url.relative(sourceUrl, from: packageUrl);
+          break;
+        }
       }
     }
 
-    return Frame(
+    return new Frame(
         Uri.parse(sourceUrl),
         span.start.line + 1,
         span.start.column + 1,
@@ -78,26 +108,27 @@
 String _prettifyMember(String member) {
   return member
       // Get rid of the noise that Firefox sometimes adds.
-      .replaceAll(RegExp(r'/?<$'), '')
+      .replaceAll(new RegExp(r"/?<$"), "")
       // Get rid of arity indicators and named arguments.
-      .replaceAll(RegExp(r'\$\d+(\$[a-zA-Z_0-9]+)*$'), '')
+      .replaceAll(new RegExp(r"\$\d+(\$[a-zA-Z_0-9]+)*$"), "")
       // Convert closures to <fn>.
       .replaceAllMapped(
-          RegExp(r'(_+)closure\d*\.call$'),
+          new RegExp(r"(_+)closure\d*\.call$"),
           // The number of underscores before "closure" indicates how nested it
           // is.
-          (match) => '.<fn>' * match[1].length)
+          (match) => ".<fn>" * match[1].length)
       // Get rid of explicitly-generated calls.
-      .replaceAll(RegExp(r'\.call$'), '')
+      .replaceAll(new RegExp(r"\.call$"), "")
       // Get rid of the top-level method prefix.
-      .replaceAll(RegExp(r'^dart\.'), '')
+      .replaceAll(new RegExp(r"^dart\."), "")
       // Get rid of library namespaces.
-      .replaceAll(RegExp(r'[a-zA-Z_0-9]+\$'), '')
+      .replaceAll(new RegExp(r"[a-zA-Z_0-9]+\$"), "")
       // Get rid of the static method prefix. The class name also exists in the
       // invocation, so we're not getting rid of any information.
-      .replaceAll(RegExp(r'^[a-zA-Z_0-9]+.(static|dart).'), '')
+      .replaceAll(new RegExp(r"^[a-zA-Z_0-9]+.(static|dart)."), "")
       // Convert underscores after identifiers to dots. This runs the risk of
       // incorrectly converting members that contain underscores, but those are
       // contrary to the style guide anyway.
-      .replaceAllMapped(RegExp(r'([a-zA-Z0-9]+)_'), (match) => match[1] + '.');
+      .replaceAllMapped(
+          new RegExp(r"([a-zA-Z0-9]+)_"), (match) => match[1] + ".");
 }
diff --git a/source_map_stack_trace/pubspec.yaml b/source_map_stack_trace/pubspec.yaml
index b998d38..77cf804 100644
--- a/source_map_stack_trace/pubspec.yaml
+++ b/source_map_stack_trace/pubspec.yaml
@@ -1,30 +1,18 @@
 name: source_map_stack_trace
-version: 2.0.0
+version: 1.1.5
+
 description: A package for applying source maps to stack traces.
+author: Dart Team <misc@dartlang.org>
 homepage: https://github.com/dart-lang/source_map_stack_trace
 
 environment:
-  sdk: '>=2.7.0 <3.0.0'
+  sdk: '>=1.8.0 <3.0.0'
 
 dependencies:
+  package_resolver: ^1.0.0
   path: ^1.0.0
   stack_trace: ^1.0.0
   source_maps: ^0.10.2
 
 dev_dependencies:
-  source_span: ^1.6.0
-  test: ^1.12.0
-  pedantic: ^1.0.0
-
-dependency_overrides:
-  # Required to get a valid pub solve until package:test updates
-  #test_core:
-  #  git:
-  #    url: https://github.com/dart-lang/test.git
-  #    ref: drop-package-resolver
-  #    path: pkgs/test_core
-  #test:
-  #  git:
-  #    url: https://github.com/dart-lang/test.git
-  #    ref: drop-package-resolver
-  #    path: pkgs/test
+  test: '>=0.12.0 <2.0.0'
diff --git a/sse/BUILD.gn b/sse/BUILD.gn
index d993492..a691b9f 100644
--- a/sse/BUILD.gn
+++ b/sse/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by importer.py for sse-3.2.1
+# This file is generated by importer.py for sse-3.1.2
 
 import("//build/dart/dart_library.gni")
 
diff --git a/sse/CHANGELOG.md b/sse/CHANGELOG.md
index 5135fb4..b5fb859 100644
--- a/sse/CHANGELOG.md
+++ b/sse/CHANGELOG.md
@@ -1,13 +1,3 @@
-## 3.2.1
-
-- Fix an issue where `keepAlive` would only allow a single reconnection.
-
-## 3.2.0
-
-- Re-expose `isInKeepAlivePeriod` flag on `SseConnection`. This flag will be
-  `true` when a connection has been dropped and is in the keep-alive period
-  waiting for a client to reconnect.
-
 ## 3.1.2
 
 - Fix an issue where the `SseClient` would not send a `done` event when there
diff --git a/sse/lib/src/server/sse_handler.dart b/sse/lib/src/server/sse_handler.dart
index 1749f46..9c3d91e 100644
--- a/sse/lib/src/server/sse_handler.dart
+++ b/sse/lib/src/server/sse_handler.dart
@@ -37,7 +37,7 @@
   Timer _keepAliveTimer;
 
   /// Whether this connection is currently in the KeepAlive timeout period.
-  bool get isInKeepAlivePeriod => _keepAliveTimer?.isActive ?? false;
+  bool get _isInKeepAlivePeriod => _keepAliveTimer?.isActive ?? false;
 
   final _closedCompleter = Completer<void>();
 
@@ -60,7 +60,7 @@
     while (await outgoingStreamQueue.hasNext) {
       // If we're in a KeepAlive timeout, there's nowhere to send messages so
       // wait a short period and check again.
-      if (isInKeepAlivePeriod) {
+      if (_isInKeepAlivePeriod) {
         await Future.delayed(const Duration(milliseconds: 200));
         continue;
       }
@@ -105,7 +105,7 @@
     if (_keepAlive == null) {
       // Close immediately if we're not keeping alive.
       _close();
-    } else if (!isInKeepAlivePeriod) {
+    } else if (!_isInKeepAlivePeriod) {
       // Otherwise if we didn't already have an active timer, set a timer to
       // close after the timeout period. If the connection comes back, this will
       // be cancelled and all messages left in the queue tried again.
@@ -155,7 +155,7 @@
       // Check if we already have a connection for this ID that is in the process
       // of timing out (in which case we can reconnect it transparently).
       if (_connections[clientId] != null &&
-          _connections[clientId].isInKeepAlivePeriod) {
+          _connections[clientId]._isInKeepAlivePeriod) {
         _connections[clientId]._acceptReconnection(sink);
       } else {
         var connection = SseConnection(sink, keepAlive: _keepAlive);
@@ -163,15 +163,16 @@
         unawaited(connection._closedCompleter.future.then((_) {
           _connections.remove(clientId);
         }));
+        // Remove connection when it is remotely closed or the stream is
+        // cancelled.
+        channel.stream.listen((_) {
+          // SSE is unidirectional. Responses are handled through POST requests.
+        }, onDone: () {
+          connection._handleDisconnect();
+        });
+
         _connectionController.add(connection);
       }
-      // Remove connection when it is remotely closed or the stream is
-      // cancelled.
-      channel.stream.listen((_) {
-        // SSE is unidirectional. Responses are handled through POST requests.
-      }, onDone: () {
-        _connections[clientId]?._handleDisconnect();
-      });
     });
     return shelf.Response.notFound('');
   }
diff --git a/sse/pubspec.yaml b/sse/pubspec.yaml
index 1381d61..86d3c13 100644
--- a/sse/pubspec.yaml
+++ b/sse/pubspec.yaml
@@ -1,5 +1,5 @@
 name: sse
-version: 3.2.1
+version: 3.1.2
 homepage: https://github.com/dart-lang/sse
 description: >-
   Provides client and server functionality for setting up bi-directional
diff --git a/stream_transform/.travis.yml b/stream_transform/.travis.yml
index 596c0c6..d69c3f7 100644
--- a/stream_transform/.travis.yml
+++ b/stream_transform/.travis.yml
@@ -3,21 +3,12 @@
   only: [master]
 dart:
   - dev
-  - 2.6.0
+  # 2.2.0
 cache:
   directories:
     - $HOME/.pub-cache
 dart_task:
-  - test --test-randomize-ordering-seed=random
-  - test -p chrome --test-randomize-ordering-seed=random
-
-matrix:
-  include:
-    - dart: dev
-      dart_task: dartfmt
-    - dart: dev
-      dart_task:
-        dartanalyzer: --fatal-warnings --fatal-infos .
-    - dart: 2.6.0
-      dart_task:
-        dartanalyzer: --fatal-warnings .
+  - test
+  - test -p chrome
+  - dartfmt
+  - dartanalyzer: --fatal-warnings --fatal-infos .
diff --git a/stream_transform/BUILD.gn b/stream_transform/BUILD.gn
index 4ad82b3..fcf871b 100644
--- a/stream_transform/BUILD.gn
+++ b/stream_transform/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by importer.py for stream_transform-1.2.0
+# This file is generated by importer.py for stream_transform-1.1.0
 
 import("//build/dart/dart_library.gni")
 
diff --git a/stream_transform/CHANGELOG.md b/stream_transform/CHANGELOG.md
index f4cf395..686f919 100644
--- a/stream_transform/CHANGELOG.md
+++ b/stream_transform/CHANGELOG.md
@@ -1,14 +1,3 @@
-## 1.2.0
-
--  Add support for emitting the "leading" event in `debounce`.
-
-## 1.1.1
-
--   Fix a bug in `asyncMapSample`, `buffer`, `combineLatest`,
-    `combineLatestAll`, `merge`, and `mergeAll` which would cause an exception
-    when cancelling a subscription after using the transformer if the original
-    stream(s) returned `null` from cancelling their subscriptions.
-
 ## 1.1.0
 
 -   Add `concurrentAsyncExpand` to interleave events emitted by multiple sub
diff --git a/stream_transform/lib/src/aggregate_sample.dart b/stream_transform/lib/src/aggregate_sample.dart
index 88fc490..a069af7 100644
--- a/stream_transform/lib/src/aggregate_sample.dart
+++ b/stream_transform/lib/src/aggregate_sample.dart
@@ -32,13 +32,13 @@
     StreamSubscription<S> valueSub;
     StreamSubscription<void> triggerSub;
 
-    void emit() {
+    emit() {
       controller.add(currentResults);
       currentResults = null;
       waitingForTrigger = true;
     }
 
-    void onValue(S value) {
+    onValue(S value) {
       currentResults = _aggregate(value, currentResults);
 
       if (!waitingForTrigger) emit();
@@ -49,7 +49,7 @@
       }
     }
 
-    void onValuesDone() {
+    onValuesDone() {
       isValueDone = true;
       if (currentResults == null) {
         triggerSub?.cancel();
@@ -57,7 +57,7 @@
       }
     }
 
-    void onTrigger(_) {
+    onTrigger(_) {
       waitingForTrigger = false;
 
       if (currentResults != null) emit();
@@ -68,7 +68,7 @@
       }
     }
 
-    void onTriggerDone() {
+    onTriggerDone() {
       isTriggerDone = true;
       if (waitingForTrigger) {
         valueSub?.cancel();
@@ -107,10 +107,8 @@
         } else {
           triggerSub.pause();
         }
-        var cancels =
-            toCancel.map((s) => s.cancel()).where((f) => f != null).toList();
-        if (cancels.isEmpty) return null;
-        return Future.wait(cancels).then((_) => null);
+        if (toCancel.isEmpty) return null;
+        return Future.wait(toCancel.map((s) => s.cancel()));
       };
     };
     return controller.stream;
diff --git a/stream_transform/lib/src/async_map.dart b/stream_transform/lib/src/async_map.dart
index c5b160b..f002667 100644
--- a/stream_transform/lib/src/async_map.dart
+++ b/stream_transform/lib/src/async_map.dart
@@ -89,7 +89,7 @@
   ///
   /// The result stream will not close until the source stream closes and all
   /// pending conversions have finished.
-  Stream<S> concurrentAsyncMap<S>(FutureOr<S> Function(T) convert) {
+  Stream<S> concurrentAsyncMap<S>(FutureOr<S> convert(T event)) {
     var valuesWaiting = 0;
     var sourceDone = false;
     return transform(fromHandlers(handleData: (element, sink) {
@@ -116,7 +116,7 @@
 /// rather than once per listener, and [then] is called after completing the
 /// work.
 StreamTransformer<S, T> _asyncMapThen<S, T>(
-    Future<T> Function(S) convert, void Function(void) then) {
+    Future<T> convert(S event), void Function(void) then) {
   Future<void> pendingEvent;
   return fromHandlers(handleData: (event, sink) {
     pendingEvent =
diff --git a/stream_transform/lib/src/combine_latest.dart b/stream_transform/lib/src/combine_latest.dart
index 303b16a..7fee05d 100644
--- a/stream_transform/lib/src/combine_latest.dart
+++ b/stream_transform/lib/src/combine_latest.dart
@@ -175,11 +175,11 @@
           };
       }
       controller.onCancel = () {
-        var cancels = [sourceSubscription.cancel(), otherSubscription.cancel()]
-            .where((f) => f != null);
+        var cancelSource = sourceSubscription.cancel();
+        var cancelOther = otherSubscription.cancel();
         sourceSubscription = null;
         otherSubscription = null;
-        return Future.wait(cancels).then((_) => null);
+        return Future.wait([cancelSource, cancelOther]);
       };
     };
     return controller.stream;
@@ -249,12 +249,8 @@
           };
       }
       controller.onCancel = () {
-        var cancels = subscriptions
-            .map((s) => s.cancel())
-            .where((f) => f != null)
-            .toList();
-        if (cancels.isEmpty) return null;
-        return Future.wait(cancels).then((_) => null);
+        if (subscriptions.isEmpty) return null;
+        return Future.wait(subscriptions.map((s) => s.cancel()));
       };
     };
     return controller.stream;
diff --git a/stream_transform/lib/src/concatenate.dart b/stream_transform/lib/src/concatenate.dart
index 05c977f..f9ba747 100644
--- a/stream_transform/lib/src/concatenate.dart
+++ b/stream_transform/lib/src/concatenate.dart
@@ -75,17 +75,17 @@
 
     Function currentDoneHandler;
 
-    void listen() {
+    listen() {
       subscription = currentStream.listen(controller.add,
           onError: controller.addError, onDone: () => currentDoneHandler());
     }
 
-    void onSecondDone() {
+    onSecondDone() {
       secondDone = true;
       controller.close();
     }
 
-    void onFirstDone() {
+    onFirstDone() {
       firstDone = true;
       currentStream = next;
       currentDoneHandler = onSecondDone;
diff --git a/stream_transform/lib/src/merge.dart b/stream_transform/lib/src/merge.dart
index f654d35..99d19c9 100644
--- a/stream_transform/lib/src/merge.dart
+++ b/stream_transform/lib/src/merge.dart
@@ -124,12 +124,8 @@
           };
       }
       controller.onCancel = () {
-        var cancels = subscriptions
-            .map((s) => s.cancel())
-            .where((f) => f != null)
-            .toList();
-        if (cancels.isEmpty) return null;
-        return Future.wait(cancels).then((_) => null);
+        if (subscriptions.isEmpty) return null;
+        return Future.wait(subscriptions.map((s) => s.cancel()));
       };
     };
     return controller.stream;
@@ -176,12 +172,7 @@
           };
       }
       controller.onCancel = () {
-        var cancels = subscriptions
-            .map((s) => s.cancel())
-            .where((f) => f != null)
-            .toList();
-        if (cancels.isEmpty) return null;
-        return Future.wait(cancels).then((_) => null);
+        return Future.wait(subscriptions.map((s) => s.cancel()));
       };
     };
     return controller.stream;
diff --git a/stream_transform/lib/src/rate_limit.dart b/stream_transform/lib/src/rate_limit.dart
index 6341c3e..db6d7df 100644
--- a/stream_transform/lib/src/rate_limit.dart
+++ b/stream_transform/lib/src/rate_limit.dart
@@ -9,55 +9,30 @@
 
 /// Utilities to rate limit events.
 ///
-/// - [debounce] - emit the the _first_ or _last_ event of a series of closely
-///   spaced events.
-/// - [debounceBuffer] - emit _all_ events at the _end_ of a series of closely
-///   spaced events.
+/// - [debounce] - emit the _first_ event at the _end_ of the period.
+/// - [debounceBuffer] - emit _all_ events at the _end_ of the period.
 /// - [throttle] - emit the _first_ event at the _beginning_ of the period.
 /// - [audit] - emit the _last_ event at the _end_ of the period.
 /// - [buffer] - emit _all_ events on a _trigger_.
 extension RateLimit<T> on Stream<T> {
-  /// Returns a Stream which suppresses events with less inter-event spacing
-  /// than [duration].
+  /// Returns a Stream which only emits when the source stream does not emit for
+  /// [duration].
   ///
-  /// Events which are emitted with less than [duration] elapsed between them
-  /// are considered to be part of the same "series". If [leading] is `true`,
-  /// the first event of this series is emitted immediately. If [trailing] is
-  /// `true` the last event of this series is emitted with a delay of at least
-  /// [duration]. By default only trailing events are emitted, both arguments
-  /// must be specified with `leading: true, trailing: false` to emit only
-  /// leading events.
+  /// Values will always be delayed by at least [duration], and values which
+  /// come within this time will replace the old values, only the most
+  /// recent value will be emitted.
   ///
   /// If the source stream is a broadcast stream, the result will be as well.
   /// Errors are forwarded immediately.
   ///
-  /// If there is a trailing event waiting during the debounce period when the
-  /// source stream closes the returned stream will wait to emit it following
-  /// the debounce period before closing. If there is no pending debounced event
+  /// If there is an event waiting during the debounce period when the source
+  /// stream closes the returned stream will wait to emit it following the
+  /// debounce period before closing. If there is no pending debounced event
   /// when the source stream closes the returned stream will close immediately.
   ///
-  /// For example:
-  ///
-  ///     source.debouce(Duration(seconds: 1));
-  ///
-  ///     source: 1-2-3---4---5-6-|
-  ///     result: ------3---4-----6|
-  ///
-  ///     source.debouce(Duration(seconds: 1), leading: true, trailing: false);
-  ///
-  ///     source: 1-2-3---4---5-6-|
-  ///     result: 1-------4---5---|
-  ///
-  ///     source.debouce(Duration(seconds: 1), leading: true);
-  ///
-  ///     source: 1-2-3---4---5-6-|
-  ///     result: 1-----3-4---5---6|
-  ///
   /// To collect values emitted during the debounce period see [debounceBuffer].
-  Stream<T> debounce(Duration duration,
-          {bool leading = false, bool trailing = true}) =>
-      transform(_debounceAggregate(duration, _dropPrevious,
-          leading: leading, trailing: trailing));
+  Stream<T> debounce(Duration duration) =>
+      transform(_debounceAggregate(duration, _dropPrevious));
 
   /// Returns a Stream which collects values until the source stream does not
   /// emit for [duration] then emits the collected values.
@@ -76,8 +51,7 @@
   /// To keep only the most recent event during the debounce perios see
   /// [debounce].
   Stream<List<T>> debounceBuffer(Duration duration) =>
-      transform(_debounceAggregate(duration, _collectToList,
-          leading: false, trailing: true));
+      transform(_debounceAggregate(duration, _collectToList));
 
   /// Returns a stream which only emits once per [duration], at the beginning of
   /// the period.
@@ -172,34 +146,25 @@
 /// Creates a StreamTransformer which aggregates values until the source stream
 /// does not emit for [duration], then emits the aggregated values.
 StreamTransformer<T, R> _debounceAggregate<T, R>(
-    Duration duration, R Function(T element, R soFar) collect,
-    {bool leading, bool trailing}) {
+    Duration duration, R collect(T element, R soFar)) {
   Timer timer;
   R soFar;
   var shouldClose = false;
-  var emittedLatestAsLeading = false;
   return fromHandlers(handleData: (T value, EventSink<R> sink) {
     timer?.cancel();
-    soFar = collect(value, soFar);
-    if (timer == null && leading) {
-      emittedLatestAsLeading = true;
-      sink.add(soFar);
-    } else {
-      emittedLatestAsLeading = false;
-    }
     timer = Timer(duration, () {
-      if (trailing && !emittedLatestAsLeading) sink.add(soFar);
+      sink.add(soFar);
       if (shouldClose) {
         sink.close();
       }
       soFar = null;
       timer = null;
     });
+    soFar = collect(value, soFar);
   }, handleDone: (EventSink<R> sink) {
-    if (soFar != null && trailing) {
+    if (soFar != null) {
       shouldClose = true;
     } else {
-      timer?.cancel();
       sink.close();
     }
   });
diff --git a/stream_transform/lib/src/scan.dart b/stream_transform/lib/src/scan.dart
index 4e022f5..b31b9e7 100644
--- a/stream_transform/lib/src/scan.dart
+++ b/stream_transform/lib/src/scan.dart
@@ -14,7 +14,7 @@
   /// called for elements in order, and the result stream always maintains the
   /// same order as the original.
   Stream<S> scan<S>(
-      S initialValue, FutureOr<S> Function(S soFar, T element) combine) {
+      S initialValue, FutureOr<S> combine(S previousValue, T element)) {
     var accumulated = initialValue;
     return asyncMap((value) {
       var result = combine(accumulated, value);
diff --git a/stream_transform/lib/src/switch.dart b/stream_transform/lib/src/switch.dart
index 5125d57..2e2aeba 100644
--- a/stream_transform/lib/src/switch.dart
+++ b/stream_transform/lib/src/switch.dart
@@ -15,7 +15,7 @@
   ///
   /// If the source stream is a broadcast stream, the result stream will be as
   /// well, regardless of the types of the streams produced by [convert].
-  Stream<S> switchMap<S>(Stream<S> Function(T) convert) {
+  Stream<S> switchMap<S>(Stream<S> convert(T event)) {
     return map(convert).switchLatest();
   }
 }
@@ -41,11 +41,15 @@
         ? StreamController<T>.broadcast(sync: true)
         : StreamController<T>(sync: true);
 
+    StreamSubscription<Stream<T>> outerSubscription;
+
     controller.onListen = () {
+      assert(outerSubscription == null);
+
       StreamSubscription<T> innerSubscription;
       var outerStreamDone = false;
 
-      final outerSubscription = outer.listen(
+      outerSubscription = outer.listen(
           (innerStream) {
             innerSubscription?.cancel();
             innerSubscription = innerStream.listen(controller.add,
@@ -71,12 +75,15 @@
           };
       }
       controller.onCancel = () {
-        var cancels = [
-          if (!outerStreamDone) outerSubscription.cancel(),
-          if (innerSubscription != null) innerSubscription.cancel(),
-        ].where((f) => f != null);
-        if (cancels.isEmpty) return null;
-        return Future.wait(cancels).then((_) => null);
+        var toCancel = <StreamSubscription<void>>[];
+        if (!outerStreamDone) toCancel.add(outerSubscription);
+        if (innerSubscription != null) {
+          toCancel.add(innerSubscription);
+        }
+        outerSubscription = null;
+        innerSubscription = null;
+        if (toCancel.isEmpty) return null;
+        return Future.wait(toCancel.map((s) => s.cancel()));
       };
     };
     return controller.stream;
diff --git a/stream_transform/lib/src/where.dart b/stream_transform/lib/src/where.dart
index 8917cb4..7026923 100644
--- a/stream_transform/lib/src/where.dart
+++ b/stream_transform/lib/src/where.dart
@@ -17,7 +17,7 @@
   /// [S] should be a subtype of the stream's generic type, otherwise nothing of
   /// type [S] could possibly be emitted, however there is no static or runtime
   /// checking that this is the case.
-  Stream<S> whereType<S>() => where((e) => e is S).cast<S>();
+  Stream<S> whereType<S>() => transform(_WhereType<S>());
 
   /// Like [where] but allows the [test] to return a [Future].
   ///
@@ -34,7 +34,7 @@
   ///
   /// The result stream will not close until the source stream closes and all
   /// pending [test] calls have finished.
-  Stream<T> asyncWhere(FutureOr<bool> Function(T) test) {
+  Stream<T> asyncWhere(FutureOr<bool> test(T element)) {
     var valuesWaiting = 0;
     var sourceDone = false;
     return transform(fromHandlers(handleData: (element, sink) {
@@ -54,3 +54,36 @@
     }));
   }
 }
+
+class _WhereType<R> extends StreamTransformerBase<Null, R> {
+  @override
+  Stream<R> bind(Stream<Object> source) {
+    var controller = source.isBroadcast
+        ? StreamController<R>.broadcast(sync: true)
+        : StreamController<R>(sync: true);
+
+    StreamSubscription<Object> subscription;
+    controller.onListen = () {
+      assert(subscription == null);
+      subscription = source.listen(
+          (value) {
+            if (value is R) controller.add(value);
+          },
+          onError: controller.addError,
+          onDone: () {
+            subscription = null;
+            controller.close();
+          });
+      if (!source.isBroadcast) {
+        controller
+          ..onPause = subscription.pause
+          ..onResume = subscription.resume;
+      }
+      controller.onCancel = () {
+        subscription?.cancel();
+        subscription = null;
+      };
+    };
+    return controller.stream;
+  }
+}
diff --git a/stream_transform/pubspec.yaml b/stream_transform/pubspec.yaml
index c336b81..9e263e2 100644
--- a/stream_transform/pubspec.yaml
+++ b/stream_transform/pubspec.yaml
@@ -1,12 +1,12 @@
 name: stream_transform
 description: A collection of utilities to transform and manipulate streams.
+author: Dart Team <misc@dartlang.org>
 homepage: https://www.github.com/dart-lang/stream_transform
-version: 1.2.0
+version: 1.1.0
 
 environment:
   sdk: ">=2.6.0 <3.0.0"
 
 dev_dependencies:
-  async: ^2.0.0
   pedantic: ^1.5.0
   test: ^1.0.0
diff --git a/sync_http/BUILD.gn b/sync_http/BUILD.gn
index a0c7317..ebe62d4 100644
--- a/sync_http/BUILD.gn
+++ b/sync_http/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by importer.py for sync_http-0.2.0
+# This file is generated by importer.py for sync_http-0.1.4
 
 import("//build/dart/dart_library.gni")
 
diff --git a/sync_http/CHANGELOG.md b/sync_http/CHANGELOG.md
index 840953a..67c7d12 100644
--- a/sync_http/CHANGELOG.md
+++ b/sync_http/CHANGELOG.md
@@ -1,11 +1,3 @@
-## v0.2.0
-
-* Preparation for [HttpHeaders change]. Update signature of `add()`
-  and `set()` to match new signature of `HttpHeaders`. The
-  parameter is not yet forwarded and will not behave as expected.
-
-  [HttpHeaders change]: https://github.com/dart-lang/sdk/issues/39657
-
 ## v0.1.4
 
 * Fixed issue where query parameters were not being sent as part of requests.
diff --git a/sync_http/codereview.settings b/sync_http/codereview.settings
new file mode 100644
index 0000000..181f5e6
--- /dev/null
+++ b/sync_http/codereview.settings
@@ -0,0 +1,4 @@
+# This file is used by gcl to get repository specific information.
+CODE_REVIEW_SERVER: http://codereview.chromium.org
+VIEW_VC: https://github.com/dart-lang/sync_http/commit/
+CC_LIST: reviews@dartlang.org
diff --git a/sync_http/lib/src/sync_http.dart b/sync_http/lib/src/sync_http.dart
index 77464a8..fbc0f10 100644
--- a/sync_http/lib/src/sync_http.dart
+++ b/sync_http/lib/src/sync_http.dart
@@ -149,7 +149,7 @@
 
   /// Add [value] to the list of values associated with header [name].
   @override
-  void add(String name, Object value, {bool preserveHeaderCase = false}) {
+  void add(String name, Object value) {
     switch (name) {
       case HttpHeaders.acceptCharsetHeader:
       case HttpHeaders.acceptEncodingHeader:
@@ -222,9 +222,9 @@
 
   /// Replace values associated with key [name] with [value].
   @override
-  void set(String name, Object value, {bool preserveHeaderCase = false}) {
+  void set(String name, Object value) {
     removeAll(name);
-    add(name, value, preserveHeaderCase: preserveHeaderCase);
+    add(name, value);
   }
 
   /// Returns the values associated with key [name], if it exists, otherwise
@@ -449,7 +449,7 @@
   List<String> operator [](String name) => _headers[name];
 
   @override
-  void add(String name, Object value, {bool preserveHeaderCase = false}) {
+  void add(String name, Object value) {
     throw new UnsupportedError('Response headers are immutable');
   }
 
@@ -586,7 +586,7 @@
   }
 
   @override
-  void set(String name, Object value, {bool preserveHeaderCase = false}) {
+  void set(String name, Object value) {
     throw new UnsupportedError('Response headers are immutable');
   }
 
diff --git a/sync_http/pubspec.yaml b/sync_http/pubspec.yaml
index 52041ff..a590f77 100644
--- a/sync_http/pubspec.yaml
+++ b/sync_http/pubspec.yaml
@@ -1,5 +1,5 @@
 name: sync_http
-version: 0.2.0
+version: 0.1.4
 author: Dart Team <misc@dartlang.org>
 description: Synchronous HTTP client for Dart.
 homepage: https://github.com/dart-lang/sync_http
diff --git a/test/BUILD.gn b/test/BUILD.gn
index bc4297c..64a5ad5 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by importer.py for test-1.14.1
+# This file is generated by importer.py for test-1.9.4
 
 import("//build/dart/dart_library.gni")
 
@@ -14,8 +14,6 @@
   deps = [
     "//third_party/dart-pkg/pub/stack_trace",
     "//third_party/dart-pkg/pub/pedantic",
-    "//third_party/dart-pkg/pub/package_config",
-    "//third_party/dart-pkg/pub/http",
     "//third_party/dart-pkg/pub/shelf_web_socket",
     "//third_party/dart-pkg/pub/typed_data",
     "//third_party/dart-pkg/pub/shelf",
@@ -27,17 +25,16 @@
     "//third_party/dart-pkg/pub/test_api",
     "//third_party/dart-pkg/pub/io",
     "//third_party/dart-pkg/pub/test_core",
-    "//third_party/dart-pkg/pub/coverage",
-    "//third_party/dart-pkg/pub/source_span",
+    "//third_party/dart-pkg/pub/path",
     "//third_party/dart-pkg/pub/stream_channel",
     "//third_party/dart-pkg/pub/js",
-    "//third_party/dart-pkg/pub/webkit_inspection_protocol",
     "//third_party/dart-pkg/pub/pool",
     "//third_party/dart-pkg/pub/shelf_static",
-    "//third_party/dart-pkg/pub/web_socket_channel",
     "//third_party/dart-pkg/pub/yaml",
+    "//third_party/dart-pkg/pub/source_span",
+    "//third_party/dart-pkg/pub/package_resolver",
+    "//third_party/dart-pkg/pub/web_socket_channel",
     "//third_party/dart-pkg/pub/boolean_selector",
     "//third_party/dart-pkg/pub/async",
-    "//third_party/dart-pkg/pub/path",
   ]
 }
diff --git a/test/CHANGELOG.md b/test/CHANGELOG.md
index ef24aed..00a3112 100644
--- a/test/CHANGELOG.md
+++ b/test/CHANGELOG.md
@@ -1,49 +1,3 @@
-## 1.14.1
-
-* Allow the latest shelf_packages_handler.
-
-## 1.14.0
-
-* Drop the `package_resolver` dependency for the `package_config` dependency
-  which is lower level.
-
-## 1.13.0
-
-* Enable asserts in code running through `spawnHybrid` APIs.
-* Exit with a non-zero code if no tests were ran, whether due to skips or having
-  no tests defined.
-* Fix the stack trace labels in SDK code for `dart2js` compiled tests.
-* Cancel any StreamQueue that is created as a part of a stream matcher once it
-  is done matching.
-  * This fixes a bug where using a matcher on a custom stream controller and
-    then awaiting the `close()` method on that controller would hang.
-* Avoid causing the test runner to hang if there is a timeout during a
-  `tearDown` callback following a failing test case.
-
-## 1.12.0
-
-* Bump minimum SDK to `2.4.0` for safer usage of for-loop elements.
-* Deprecate `PhantomJS` and provide warning when used. Support for `PhantomJS`
-  will be removed in version `2.0.0`.
-* Support coverage collection for the Chrome platform. See `README.md` for usage
-  details.
-
-## 1.11.1
-
-* Allow `test_api` `0.2.13` to work around a bug in the SDK version `2.3.0`.
-
-## 1.11.0
-
-* Add `file_reporters` configuration option and `--file-reporter` CLI option to
-  allow specifying a separate reporter that writes to a file instead of stdout.
-
-## 1.10.0
-
-* Add `customHtmlTemplateFile` configuration option to allow sharing an
-  html template between tests
-* Depend on the latest `package:test_core`.
-* Depend on the latest `package:test_api`.
-
 ## 1.9.4
 
 * Extend the timeout for synthetic tests, e.g. `tearDownAll`.
diff --git a/test/README.md b/test/README.md
index 63ea8be..5bd7e1d 100644
--- a/test/README.md
+++ b/test/README.md
@@ -10,7 +10,6 @@
 * [Asynchronous Tests](#asynchronous-tests)
   * [Stream Matchers](#stream-matchers)
 * [Running Tests With Custom HTML](#running-tests-with-custom-html)
-  * [Providing a custom HTML template](#providing-a-custom-html-template)
 * [Configuring Tests](#configuring-tests)
   * [Skipping Tests](#skipping-tests)
   * [Timeouts](#timeouts)
@@ -20,8 +19,8 @@
 * [Debugging](#debugging)
 * [Browser/VM Hybrid Tests](#browservm-hybrid-tests)
 * [Support for Other Packages](#support-for-other-packages)
-  * [`build_runner`](#build_runner)
   * [`term_glyph`](#term_glyph)
+  * [`barback`](#barback)
 * [Further Reading](#further-reading)
 
 ## Writing Tests
@@ -33,17 +32,17 @@
 [`expect()`]: https://pub.dev/documentation/test_api/latest/test_api/expect.html
 
 ```dart
-import 'package:test/test.dart';
+import "package:test/test.dart";
 
 void main() {
-  test('String.split() splits the string on the delimiter', () {
-    var string = 'foo,bar,baz';
-    expect(string.split(','), equals(['foo', 'bar', 'baz']));
+  test("String.split() splits the string on the delimiter", () {
+    var string = "foo,bar,baz";
+    expect(string.split(","), equals(["foo", "bar", "baz"]));
   });
 
-  test('String.trim() removes surrounding whitespace', () {
-    var string = '  foo ';
-    expect(string.trim(), equals('foo'));
+  test("String.trim() removes surrounding whitespace", () {
+    var string = "  foo ";
+    expect(string.trim(), equals("foo"));
   });
 }
 ```
@@ -54,28 +53,28 @@
 [`group()`]: https://pub.dev/documentation/test_api/latest/test_api/group.html
 
 ```dart
-import 'package:test/test.dart';
+import "package:test/test.dart";
 
 void main() {
-  group('String', () {
-    test('.split() splits the string on the delimiter', () {
-      var string = 'foo,bar,baz';
-      expect(string.split(','), equals(['foo', 'bar', 'baz']));
+  group("String", () {
+    test(".split() splits the string on the delimiter", () {
+      var string = "foo,bar,baz";
+      expect(string.split(","), equals(["foo", "bar", "baz"]));
     });
 
-    test('.trim() removes surrounding whitespace', () {
-      var string = '  foo ';
-      expect(string.trim(), equals('foo'));
+    test(".trim() removes surrounding whitespace", () {
+      var string = "  foo ";
+      expect(string.trim(), equals("foo"));
     });
   });
 
-  group('int', () {
-    test('.remainder() returns the remainder of division', () {
+  group("int", () {
+    test(".remainder() returns the remainder of division", () {
       expect(11.remainder(3), equals(2));
     });
 
-    test('.toRadixString() returns a hex string', () {
-      expect(11.toRadixString(16), equals('b'));
+    test(".toRadixString() returns a hex string", () {
+      expect(11.toRadixString(16), equals("b"));
     });
   });
 }
@@ -87,14 +86,14 @@
 [`matcher`]: https://pub.dev/documentation/matcher/latest/matcher/matcher-library.html
 
 ```dart
-import 'package:test/test.dart';
+import "package:test/test.dart";
 
 void main() {
-  test('.split() splits the string on the delimiter', () {
-    expect('foo,bar,baz', allOf([
-      contains('foo'),
-      isNot(startsWith('bar')),
-      endsWith('baz')
+  test(".split() splits the string on the delimiter", () {
+    expect("foo,bar,baz", allOf([
+      contains("foo"),
+      isNot(startsWith("bar")),
+      endsWith("baz")
     ]));
   });
 }
@@ -106,14 +105,14 @@
 fails, to ensure that it has a chance to clean up after itself.
 
 ```dart
-import 'package:test/test.dart';
+import "package:test/test.dart";
 
 void main() {
   HttpServer server;
   Uri url;
   setUp(() async {
     server = await HttpServer.bind('localhost', 0);
-    url = Uri.parse('http://${server.address.host}:${server.port}');
+    url = Uri.parse("http://${server.address.host}:${server.port}");
   });
 
   tearDown(() async {
@@ -171,22 +170,9 @@
 pub run test --total-shards 3 --shard-index 2 path/to/test.dart
 ```
 
-### Shuffling Tests
-Test order can be shuffled with the `--test-randomize-ordering-seed` argument.
-This allows you to shuffle your tests with a specific seed (deterministic) or
-a random seed for each run. For example, consider the following test runs:
-
-```bash
-pub run test --test-randomize-ordering-seed=12345
-pub run test --test-randomize-ordering-seed=random
-```
-
-Setting `--test-randomize-ordering-seed=0` will have the same effect as not
-specifying it at all, meaning the test order will remain as-is.
-
 ### Collecting Code Coverage
 To collect code coverage, you can run tests with the `--coverage <directory>`
-argument. The directory specified can be an absolute or relative path.
+argument. The directory specified can be an absolute or relative path. 
 If a directory does not exist at the path specified, a directory will be
 created. If a directory does exist, files may be overwritten with the latest
 coverage data, if they conflict.
@@ -196,8 +182,7 @@
 The files can then be formatted using the `package:coverage`
 `format_coverage` executable.
 
-Coverage gathering is currently only implemented for tests run on the Dart VM or
-Chrome.
+Coverage gathering is currently only implemented for tests run in the Dart VM.
 
 ### Restricting Tests to Certain Platforms
 
@@ -209,11 +194,11 @@
 `import` declarations:
 
 ```dart
-@TestOn('vm')
+@TestOn("vm")
 
-import 'dart:io';
+import "dart:io";
 
-import 'package:test/test.dart';
+import "package:test/test.dart";
 
 void main() {
   // ...
@@ -288,7 +273,7 @@
   equivalent to `!windows`.
 
 For example, if you wanted to run a test on every browser but Chrome, you would
-write `@TestOn('browser && !chrome')`.
+write `@TestOn("browser && !chrome")`.
 
 ### Running Tests on Node.js
 
@@ -304,7 +289,7 @@
 The test runner looks for an executable named `node` (on Mac OS or Linux) or
 `node.exe` (on Windows) on your system path. When compiling Node.js tests, it
 passes `-Dnode=true`, so tests can determine whether they're running on Node
-using [`const bool.fromEnvironment('node')`][bool.fromEnvironment]. It also sets
+using [`const bool.fromEnvironment("node")`][bool.fromEnvironment]. It also sets
 `--server-mode`, which will tell the compiler that `dart:html` is not available.
 
 [bool.fromEnvironment]: https://api.dart.dev/stable/dart-core/bool/bool.fromEnvironment.html
@@ -318,13 +303,13 @@
 won't consider the test finished until the returned `Future` completes.
 
 ```dart
-import 'dart:async';
+import "dart:async";
 
-import 'package:test/test.dart';
+import "package:test/test.dart";
 
 void main() {
-  test('Future.value() returns the value', () async {
-    var value = await Future.value(10);
+  test("new Future.value() returns the value", () async {
+    var value = await new Future.value(10);
     expect(value, equals(10));
   });
 }
@@ -338,13 +323,13 @@
 [`completion()`]: https://pub.dev/documentation/test_api/latest/test_api/completion.html
 
 ```dart
-import 'dart:async';
+import "dart:async";
 
-import 'package:test/test.dart';
+import "package:test/test.dart";
 
 void main() {
-  test('Future.value() returns the value', () {
-    expect(Future.value(10), completion(equals(10)));
+  test("new Future.value() returns the value", () {
+    expect(new Future.value(10), completion(equals(10)));
   });
 }
 ```
@@ -356,14 +341,14 @@
 [`throwsA()`]: https://pub.dev/documentation/test_api/latest/test_api/throwsA.html
 
 ```dart
-import 'dart:async';
+import "dart:async";
 
-import 'package:test/test.dart';
+import "package:test/test.dart";
 
 void main() {
-  test('Future.error() throws the error', () {
-    expect(Future.error('oh no'), throwsA(equals('oh no')));
-    expect(Future.error(StateError('bad state')), throwsStateError);
+  test("new Future.error() throws the error", () {
+    expect(new Future.error("oh no"), throwsA(equals("oh no")));
+    expect(new Future.error(new StateError("bad state")), throwsStateError);
   });
 }
 ```
@@ -374,13 +359,13 @@
 from finishing until the function is called the requisite number of times.
 
 ```dart
-import 'dart:async';
+import "dart:async";
 
-import 'package:test/test.dart';
+import "package:test/test.dart";
 
 void main() {
-  test('Stream.fromIterable() emits the values in the iterable', () {
-    var stream = Stream.fromIterable([1, 2, 3]);
+  test("Stream.fromIterable() emits the values in the iterable", () {
+    var stream = new Stream.fromIterable([1, 2, 3]);
 
     stream.listen(expectAsync1((number) {
       expect(number, inInclusiveRange(1, 3));
@@ -401,29 +386,29 @@
 [Stream]: https://api.dart.dev/stable/dart-async/Stream-class.html
 
 ```dart
-import 'dart:async';
+import "dart:async";
 
-import 'package:test/test.dart';
+import "package:test/test.dart";
 
 void main() {
-  test('process emits status messages', () {
+  test("process emits status messages", () {
     // Dummy data to mimic something that might be emitted by a process.
-    var stdoutLines = Stream.fromIterable([
-      'Ready.',
-      'Loading took 150ms.',
-      'Succeeded!'
+    var stdoutLines = new Stream.fromIterable([
+      "Ready.",
+      "Loading took 150ms.",
+      "Succeeded!"
     ]);
 
     expect(stdoutLines, emitsInOrder([
       // Values match individual events.
-      'Ready.',
+      "Ready.",
 
       // Matchers also run against individual events.
-      startsWith('Loading took'),
+      startsWith("Loading took"),
 
       // Stream matchers can be nested. This asserts that one of two events are
       // emitted after the "Loading took" line.
-      emitsAnyOf(['Succeeded!', 'Failed!']),
+      emitsAnyOf(["Succeeded!", "Failed!"]),
 
       // By default, more events are allowed after the matcher finishes
       // matching. This asserts instead that the stream emits a done event and
@@ -444,29 +429,29 @@
 [`StreamQueue`]: https://pub.dev/documentation/async/latest/async/StreamQueue-class.html
 
 ```dart
-import 'dart:async';
+import "dart:async";
 
-import 'package:async/async.dart';
-import 'package:test/test.dart';
+import "package:async/async.dart";
+import "package:test/test.dart";
 
 void main() {
-  test('process emits a WebSocket URL', () async {
+  test("process emits a WebSocket URL", () async {
     // Wrap the Stream in a StreamQueue so that we can request events.
-    var stdout = StreamQueue(Stream.fromIterable([
-      'WebSocket URL:',
-      'ws://localhost:1234/',
-      'Waiting for connection...'
+    var stdout = new StreamQueue(new Stream.fromIterable([
+      "WebSocket URL:",
+      "ws://localhost:1234/",
+      "Waiting for connection..."
     ]));
 
     // Ignore lines from the process until it's about to emit the URL.
-    await expectLater(stdout, emitsThrough('WebSocket URL:'));
+    await expect(stdout, emitsThrough("WebSocket URL:"));
 
     // Parse the next line as a URL.
     var url = Uri.parse(await stdout.next);
     expect(url.host, equals('localhost'));
 
     // You can match against the same StreamQueue multiple times.
-    await expectLater(stdout, emits('Waiting for connection...'));
+    await expect(stdout, emits("Waiting for connection..."));
   });
 }
 ```
@@ -488,7 +473,8 @@
 * [`neverEmits()`] matches a stream that finishes *without* matching an inner
   matcher.
 
-You can also define your own custom stream matchers with [`StreamMatcher()`].
+You can also define your own custom stream matchers by calling
+[`new StreamMatcher()`].
 
 [`emits()`]: https://pub.dev/documentation/test_api/latest/test_api/emits.html
 [`emitsError()`]: https://pub.dev/documentation/test_api/latest/test_api/emitsError.html
@@ -499,7 +485,7 @@
 [`emitsInOrder()`]: https://pub.dev/documentation/test_api/latest/test_api/emitsInOrder.html
 [`emitsInAnyOrder()`]: https://pub.dev/documentation/test_api/latest/test_api/emitsInAnyOrder.html
 [`neverEmits()`]: https://pub.dev/documentation/test_api/latest/test_api/neverEmits.html
-[`StreamMatcher()`]: https://pub.dev/documentation/test_api/latest/test_api/StreamMatcher-class.html
+[`new StreamMatcher()`]: https://pub.dev/documentation/test_api/latest/test_api/StreamMatcher-class.html
 
 ## Running Tests With Custom HTML
 
@@ -507,9 +493,7 @@
 tests. However, tests that need custom HTML can create their own files. These
 files have three requirements:
 
-* They must have the same name as the test, with `.dart` replaced by `.html`. You can also
-  provide a configuration path to an html file if you want it to be reused across all tests.
-  See [Providing a custom HTML template](#providing-a-custom-html-template) below.
+* They must have the same name as the test, with `.dart` replaced by `.html`.
 
 * They must contain a `link` tag with `rel="x-dart-test"` and an `href`
   attribute pointing to the test script.
@@ -534,40 +518,6 @@
 </html>
 ```
 
-### Providing a custom HTML template
-
-If you want to share the same HTML file across all tests, you can provide a
-`custom-html-template-path` configuration option to your configuration file.
-This file should follow the rules above, except that instead of the link tag
-add exactly one `{{testScript}}` in the place where you want the template processor to insert it.
-
-You can also optionally use any number of `{{testName}}` placeholders which will be replaced by the test filename.
-
-The template can't be named like any test file, as that would clash with using the
-custom HTML mechanics. In such a case, an error will be thrown.
-
-For example:
-
-```yaml
-custom-html-template-path: html_template.html.tpl
-```
-
-```html
-<!doctype html>
-<!-- html_template.html.tpl -->
-<html>
-  <head>
-    <title>{{testName}} Test</title>
-    {{testScript}}
-    <script src="packages/test/dart.js"></script>
-  </head>
-  <body>
-    // ...
-  </body>
-</html>
-```
-
-
 ## Configuring Tests
 
 ### Skipping Tests
@@ -584,9 +534,9 @@
 To skip a test suite, put a `@Skip` annotation at the top of the file:
 
 ```dart
-@Skip('currently failing (see issue 1234)')
+@Skip("currently failing (see issue 1234)")
 
-import 'package:test/test.dart';
+import "package:test/test.dart";
 
 void main() {
   // ...
@@ -600,16 +550,16 @@
 can be either `true` or a String describing why the test is skipped. For example:
 
 ```dart
-import 'package:test/test.dart';
+import "package:test/test.dart";
 
 void main() {
-  group('complicated algorithm tests', () {
+  group("complicated algorithm tests", () {
     // ...
   }, skip: "the algorithm isn't quite right");
 
-  test('error-checking test', () {
+  test("error-checking test", () {
     // ...
-  }, skip: 'TODO: add error-checking.');
+  }, skip: "TODO: add error-checking.");
 }
 ```
 
@@ -622,7 +572,7 @@
 ```dart
 @Timeout(const Duration(seconds: 45))
 
-import 'package:test/test.dart';
+import "package:test/test.dart";
 
 void main() {
   // ...
@@ -637,16 +587,16 @@
 parameter takes a `Timeout` object just like the annotation. For example:
 
 ```dart
-import 'package:test/test.dart';
+import "package:test/test.dart";
 
 void main() {
-  group('slow tests', () {
+  group("slow tests", () {
     // ...
 
-    test('even slower test', () {
+    test("even slower test", () {
       // ...
-    }, timeout: Timeout.factor(2))
-  }, timeout: Timeout(Duration(minutes: 1)));
+    }, timeout: new Timeout.factor(2))
+  }, timeout: new Timeout(new Duration(minutes: 1)));
 }
 ```
 
@@ -665,16 +615,16 @@
 ```dart
 @OnPlatform(const {
   // Give Windows some extra wiggle-room before timing out.
-  'windows': const Timeout.factor(2)
+  "windows": const Timeout.factor(2)
 })
 
-import 'package:test/test.dart';
+import "package:test/test.dart";
 
 void main() {
-  test('do a thing', () {
+  test("do a thing", () {
     // ...
   }, onPlatform: {
-    'safari': Skip('Safari is currently broken (see #1234)')
+    "safari": new Skip("Safari is currently broken (see #1234)")
   });
 }
 ```
@@ -705,18 +655,18 @@
 parameter to `test()` and `group()`. For example:
 
 ```dart
-@Tags(const ['browser'])
+@Tags(const ["browser"])
 
-import 'package:test/test.dart';
+import "package:test/test.dart";
 
 void main() {
-  test('successfully launches Chrome', () {
+  test("successfully launches Chrome", () {
     // ...
-  }, tags: 'chrome');
+  }, tags: "chrome");
 
-  test('launches two browsers at once', () {
+  test("launches two browsers at once", () {
     // ...
-  }, tags: ['chrome', 'firefox']);
+  }, tags: ["chrome", "firefox"]);
 }
 ```
 
@@ -766,7 +716,7 @@
 
 Tests can be debugged interactively using platforms' built-in development tools.
 Tests running on browsers can use those browsers' development consoles to inspect
-the document, set breakpoints, and step through code. Those running on the Dart
+the document, set breakpoints, and step through code. Those running on the Dart 
 VM use [the Dart Observatory][observatory]'s .
 
 [observatory]: https://dart-lang.github.io/observatory/
@@ -813,9 +763,9 @@
 
 // The library loaded by spawnHybridUri() can import any packages that your
 // package depends on, including those that only work on the VM.
-import 'package:shelf/shelf_io.dart' as io;
-import 'package:shelf_web_socket/shelf_web_socket.dart';
-import 'package:stream_channel/stream_channel.dart';
+import "package:shelf/shelf_io.dart" as io;
+import "package:shelf_web_socket/shelf_web_socket.dart";
+import "package:stream_channel/stream_channel.dart";
 
 // Once the hybrid isolate starts, it will call the special function
 // hybridMain() with a StreamChannel that's connected to the channel
@@ -823,7 +773,7 @@
 hybridMain(StreamChannel channel) async {
   // Start a WebSocket server that just sends "hello!" to its clients.
   var server = await io.serve(webSocketHandler((webSocket) {
-    webSocket.sink.add('hello!');
+    webSocket.sink.add("hello!");
   }), 'localhost', 0);
 
   // Send the port number of the WebSocket server to the browser test, so
@@ -834,24 +784,24 @@
 
 // ## test/web_socket_test.dart
 
-@TestOn('browser')
+@TestOn("browser")
 
-import 'dart:html';
+import "dart:html";
 
-import 'package:test/test.dart';
+import "package:test/test.dart";
 
 void main() {
-  test('connects to a server-side WebSocket', () async {
+  test("connects to a server-side WebSocket", () async {
     // Each spawnHybrid function returns a StreamChannel that communicates with
     // the hybrid isolate. You can close this channel to kill the isolate.
-    var channel = spawnHybridUri('web_socket_server.dart');
+    var channel = spawnHybridUri("web_socket_server.dart");
 
     // Get the port for the WebSocket server from the hybrid isolate.
     var port = await channel.stream.first;
 
-    var socket = WebSocket('ws://localhost:$port');
+    var socket = new WebSocket('ws://localhost:$port');
     var message = await socket.onMessage.first;
-    expect(message.data, equals('hello!'));
+    expect(message.data, equals("hello!"));
   });
 }
 ```
diff --git a/test/doc/configuration.md b/test/doc/configuration.md
index 7a7cabb..25514de 100644
--- a/test/doc/configuration.md
+++ b/test/doc/configuration.md
@@ -47,7 +47,6 @@
   * [`pub_serve`](#pub_serve)
   * [`reporter`](#reporter)
   * [`fold_stack_frames`](#fold_stack_frames)
-  * [`custom_html_template_path`](#custom_html_template_path)
 * [Configuring Tags](#configuring-tags)
   * [`tags`](#tags)
   * [`add_tags`](#add_tags)
@@ -436,19 +435,6 @@
 This field is not supported in the
 [global configuration file](#global-configuration).
 
-### `file_reporters`
-
-This field specifies additional reporters to use that will write their output to
-a file rather than stdout. It should be a map of reporter names to filepaths.
-
-```yaml
-file_reporters:
-  json: reports/tests.json
-```
-
-This field is not supported in the
-[global configuration file](#global-configuration).
-
 ### `fold_stack_frames`
 
 This field controls which packages' stack frames will be folded away
@@ -477,11 +463,6 @@
 test/sample_test.dart 5:27  main.<fn>
 ```
 
-### `custom_html_template_path`
-
-This field specifies the path of the HTML template file to be used for tests run in an HTML environment.
-Any HTML file that is named the same as the test and in the same directory will take precedence over the template.
-For more information about the usage of this option see [Providing a custom HTML template](https://github.com/dart-lang/test/blob/master/README.md#providing-a-custom-html-template)
 
 ## Configuring Tags
 
diff --git a/test/doc/json_reporter.md b/test/doc/json_reporter.md
index 50348e7..669eebe 100644
--- a/test/doc/json_reporter.md
+++ b/test/doc/json_reporter.md
@@ -19,11 +19,6 @@
 
     pub run test --reporter json <path-to-test-file>
 
-You may also use the `--file-reporter` option to enable the JSON reporter such
-that it writes to a file instead of stdout.
-
-    pub run test --file-reporter json:reports/tests.json <path-to-test-file>
-
 The JSON stream will be emitted via standard output. It will be a stream of JSON
 objects, separated by newlines.
 
diff --git a/test/doc/package_config.md b/test/doc/package_config.md
index 7a7cabb..25514de 100644
--- a/test/doc/package_config.md
+++ b/test/doc/package_config.md
@@ -47,7 +47,6 @@
   * [`pub_serve`](#pub_serve)
   * [`reporter`](#reporter)
   * [`fold_stack_frames`](#fold_stack_frames)
-  * [`custom_html_template_path`](#custom_html_template_path)
 * [Configuring Tags](#configuring-tags)
   * [`tags`](#tags)
   * [`add_tags`](#add_tags)
@@ -436,19 +435,6 @@
 This field is not supported in the
 [global configuration file](#global-configuration).
 
-### `file_reporters`
-
-This field specifies additional reporters to use that will write their output to
-a file rather than stdout. It should be a map of reporter names to filepaths.
-
-```yaml
-file_reporters:
-  json: reports/tests.json
-```
-
-This field is not supported in the
-[global configuration file](#global-configuration).
-
 ### `fold_stack_frames`
 
 This field controls which packages' stack frames will be folded away
@@ -477,11 +463,6 @@
 test/sample_test.dart 5:27  main.<fn>
 ```
 
-### `custom_html_template_path`
-
-This field specifies the path of the HTML template file to be used for tests run in an HTML environment.
-Any HTML file that is named the same as the test and in the same directory will take precedence over the template.
-For more information about the usage of this option see [Providing a custom HTML template](https://github.com/dart-lang/test/blob/master/README.md#providing-a-custom-html-template)
 
 ## Configuring Tags
 
diff --git a/test/lib/src/bootstrap/browser.dart b/test/lib/src/bootstrap/browser.dart
index 171840e..2a7e730 100644
--- a/test/lib/src/bootstrap/browser.dart
+++ b/test/lib/src/bootstrap/browser.dart
@@ -8,11 +8,11 @@
 import '../runner/browser/post_message_channel.dart';
 
 /// Bootstraps a browser test to communicate with the test runner.
-void internalBootstrapBrowserTest(Function Function() getMain) {
+void internalBootstrapBrowserTest(Function getMain()) {
   var channel =
       serializeSuite(getMain, hidePrints: false, beforeLoad: () async {
     var serialized =
-        await suiteChannel('test.browser.mapper').stream.first as Map;
+        await suiteChannel("test.browser.mapper").stream.first as Map;
     if (serialized == null) return;
     setStackTraceMapper(JSStackTraceMapper.deserialize(serialized));
   });
diff --git a/test/lib/src/bootstrap/node.dart b/test/lib/src/bootstrap/node.dart
index ca002ba..e1fe100 100644
--- a/test/lib/src/bootstrap/node.dart
+++ b/test/lib/src/bootstrap/node.dart
@@ -8,9 +8,9 @@
 import 'package:test_core/src/util/stack_trace_mapper.dart'; // ignore: implementation_imports
 
 /// Bootstraps a browser test to communicate with the test runner.
-void internalBootstrapNodeTest(Function Function() getMain) {
+void internalBootstrapNodeTest(Function getMain()) {
   var channel = serializeSuite(getMain, beforeLoad: () async {
-    var serialized = await suiteChannel('test.node.mapper').stream.first;
+    var serialized = await suiteChannel("test.node.mapper").stream.first;
     if (serialized == null || serialized is! Map) return;
     setStackTraceMapper(JSStackTraceMapper.deserialize(serialized as Map));
   });
diff --git a/test/lib/src/executable.dart b/test/lib/src/executable.dart
index 75b2d58..e8313f8 100644
--- a/test/lib/src/executable.dart
+++ b/test/lib/src/executable.dart
@@ -9,7 +9,7 @@
 import 'runner/node/platform.dart';
 import 'runner/browser/platform.dart';
 
-void main(List<String> args) async {
+main(List<String> args) async {
   registerPlatformPlugin([Runtime.nodeJS], () => NodePlatform());
   registerPlatformPlugin([
     Runtime.chrome,
diff --git a/test/lib/src/runner/browser/browser.dart b/test/lib/src/runner/browser/browser.dart
index fa816db..db7e7ec 100644
--- a/test/lib/src/runner/browser/browser.dart
+++ b/test/lib/src/runner/browser/browser.dart
@@ -6,6 +6,7 @@
 import 'dart:convert';
 import 'dart:io';
 
+import 'package:pedantic/pedantic.dart';
 import 'package:stack_trace/stack_trace.dart';
 import 'package:typed_data/typed_data.dart';
 
@@ -60,7 +61,7 @@
   /// which asynchronously returns the browser process. Any errors in
   /// [startBrowser] (even those raised asynchronously after it returns) are
   /// piped to [onExit] and will cause the browser to be killed.
-  Browser(Future<Process> Function() startBrowser) {
+  Browser(Future<Process> startBrowser()) {
     // Don't return a Future here because there's no need for the caller to wait
     // for the process to actually start. They should just wait for the HTTP
     // request instead.
@@ -69,7 +70,7 @@
       _processCompleter.complete(process);
 
       var output = Uint8Buffer();
-      void drainOutput(Stream<List<int>> stream) {
+      drainOutput(Stream<List<int>> stream) {
         try {
           _ioSubscriptions
               .add(stream.listen(output.addAll, cancelOnError: true));
@@ -98,9 +99,9 @@
 
       if (!_closed && exitCode != 0) {
         var outputString = utf8.decode(output);
-        var message = '$name failed with exit code $exitCode.';
+        var message = "$name failed with exit code $exitCode.";
         if (outputString.isNotEmpty) {
-          message += '\nStandard output:\n$outputString';
+          message += "\nStandard output:\n$outputString";
         }
 
         throw ApplicationException(message);
@@ -114,11 +115,11 @@
       // Make sure the process dies even if the error wasn't fatal.
       _process.then((process) => process.kill());
 
-      stackTrace ??= Trace.current();
+      if (stackTrace == null) stackTrace = Trace.current();
       if (_onExitCompleter.isCompleted) return;
       _onExitCompleter.completeError(
           ApplicationException(
-              'Failed to run $name: ${getErrorMessage(error)}.'),
+              "Failed to run $name: ${getErrorMessage(error)}."),
           stackTrace);
     });
   }
diff --git a/test/lib/src/runner/browser/browser_manager.dart b/test/lib/src/runner/browser/browser_manager.dart
index 2339908..91ed9e6 100644
--- a/test/lib/src/runner/browser/browser_manager.dart
+++ b/test/lib/src/runner/browser/browser_manager.dart
@@ -8,15 +8,17 @@
 import 'package:async/async.dart';
 import 'package:pool/pool.dart';
 import 'package:stream_channel/stream_channel.dart';
+import 'package:web_socket_channel/web_socket_channel.dart';
+
 import 'package:test_api/src/backend/runtime.dart'; // ignore: implementation_imports
 import 'package:test_api/src/util/stack_trace_mapper.dart'; // ignore: implementation_imports
-import 'package:test_core/src/runner/application_exception.dart'; // ignore: implementation_imports
-import 'package:test_core/src/runner/environment.dart'; // ignore: implementation_imports
-import 'package:test_core/src/runner/plugin/platform_helpers.dart'; // ignore: implementation_imports
 import 'package:test_core/src/runner/runner_suite.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/environment.dart'; // ignore: implementation_imports
 import 'package:test_core/src/runner/suite.dart'; // ignore: implementation_imports
+
+import 'package:test_core/src/runner/application_exception.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/plugin/platform_helpers.dart'; // ignore: implementation_imports
 import 'package:test_core/src/util/io.dart'; // ignore: implementation_imports
-import 'package:web_socket_channel/web_socket_channel.dart';
 
 import '../executable_settings.dart';
 import 'browser.dart';
@@ -78,7 +80,7 @@
   ///
   /// These are used to mark suites as debugging or not based on the browser's
   /// pings.
-  final _controllers = <RunnerSuiteController>{};
+  final _controllers = Set<RunnerSuiteController>();
 
   // A timer that's reset whenever we receive a message from the browser.
   //
@@ -107,7 +109,7 @@
     // TODO(nweiz): Gracefully handle the browser being killed before the
     // tests complete.
     browser.onExit.then((_) {
-      throw ApplicationException('${runtime.name} exited before connecting.');
+      throw ApplicationException("${runtime.name} exited before connecting.");
     }).catchError((error, StackTrace stackTrace) {
       if (completer.isCompleted) return;
       completer.completeError(error, stackTrace);
@@ -125,7 +127,7 @@
     return completer.future.timeout(Duration(seconds: 30), onTimeout: () {
       browser.close();
       throw ApplicationException(
-          'Timed out waiting for ${runtime.name} to connect.');
+          "Timed out waiting for ${runtime.name} to connect.");
     });
   }
 
@@ -147,7 +149,7 @@
       case Runtime.internetExplorer:
         return InternetExplorer(url, settings: settings);
       default:
-        throw ArgumentError('$browser is not a browser.');
+        throw ArgumentError("$browser is not a browser.");
     }
   }
 
@@ -205,16 +207,16 @@
       {StackTraceMapper mapper}) async {
     url = url.replace(
         fragment: Uri.encodeFull(jsonEncode({
-      'metadata': suiteConfig.metadata.serialize(),
-      'browser': _runtime.identifier
+      "metadata": suiteConfig.metadata.serialize(),
+      "browser": _runtime.identifier
     })));
 
     var suiteID = _suiteID++;
     RunnerSuiteController controller;
-    void closeIframe() {
+    closeIframe() {
       if (_closed) return;
       _controllers.remove(controller);
-      _channel.sink.add({'command': 'closeSuite', 'id': suiteID});
+      _channel.sink.add({"command": "closeSuite", "id": suiteID});
     }
 
     // The virtual channel will be closed when the suite is closed, in which
@@ -229,26 +231,17 @@
 
     return await _pool.withResource<RunnerSuite>(() async {
       _channel.sink.add({
-        'command': 'loadSuite',
-        'url': url.toString(),
-        'id': suiteID,
-        'channel': suiteChannelID
+        "command": "loadSuite",
+        "url": url.toString(),
+        "id": suiteID,
+        "channel": suiteChannelID
       });
 
       try {
-        controller = deserializeSuite(
-            path,
-            currentPlatform(_runtime),
-            suiteConfig,
-            await _environment,
-            suiteChannel,
-            message, gatherCoverage: () async {
-          var browser = _browser;
-          if (browser is Chrome) return browser.gatherCoverage();
-          return {};
-        });
+        controller = deserializeSuite(path, currentPlatform(_runtime),
+            suiteConfig, await _environment, suiteChannel, message);
 
-        controller.channel('test.browser.mapper').sink.add(mapper?.serialize());
+        controller.channel("test.browser.mapper").sink.add(mapper?.serialize());
 
         _controllers.add(controller);
         return await controller.suite;
@@ -264,7 +257,7 @@
     if (_pauseCompleter != null) return _pauseCompleter.operation;
 
     _pauseCompleter = CancelableCompleter(onCancel: () {
-      _channel.sink.add({'command': 'resume'});
+      _channel.sink.add({"command": "resume"});
       _pauseCompleter = null;
     });
 
@@ -272,22 +265,22 @@
       _pauseCompleter = null;
     });
 
-    _channel.sink.add({'command': 'displayPause'});
+    _channel.sink.add({"command": "displayPause"});
 
     return _pauseCompleter.operation;
   }
 
   /// The callback for handling messages received from the host page.
   void _onMessage(Map message) {
-    switch (message['command'] as String) {
-      case 'ping':
+    switch (message["command"] as String) {
+      case "ping":
         break;
 
-      case 'restart':
+      case "restart":
         _onRestartController.add(null);
         break;
 
-      case 'resume':
+      case "resume":
         if (_pauseCompleter != null) _pauseCompleter.complete();
         break;
 
@@ -317,21 +310,16 @@
 class _BrowserEnvironment implements Environment {
   final BrowserManager _manager;
 
-  @override
   final supportsDebugging = true;
 
-  @override
   final Uri observatoryUrl;
 
-  @override
   final Uri remoteDebuggerUrl;
 
-  @override
   final Stream onRestart;
 
   _BrowserEnvironment(this._manager, this.observatoryUrl,
       this.remoteDebuggerUrl, this.onRestart);
 
-  @override
   CancelableOperation displayPause() => _manager._displayPause();
 }
diff --git a/test/lib/src/runner/browser/chrome.dart b/test/lib/src/runner/browser/chrome.dart
index fbecad2..81c5861 100644
--- a/test/lib/src/runner/browser/chrome.dart
+++ b/test/lib/src/runner/browser/chrome.dart
@@ -3,21 +3,17 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:convert';
 import 'dart:io';
 
-import 'package:coverage/coverage.dart';
-import 'package:http/http.dart' as http;
-import 'package:path/path.dart' as p;
 import 'package:pedantic/pedantic.dart';
 import 'package:test_api/src/backend/runtime.dart'; // ignore: implementation_imports
 import 'package:test_core/src/util/io.dart'; // ignore: implementation_imports
-import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
 
 import '../executable_settings.dart';
 import 'browser.dart';
 import 'default_settings.dart';
 
+// TODO(nweiz): move this into its own package?
 /// A class for running an instance of Chrome.
 ///
 /// Most of the communication with the browser is expected to happen via HTTP,
@@ -26,45 +22,38 @@
 ///
 /// Any errors starting or running the process are reported through [onExit].
 class Chrome extends Browser {
-  @override
-  final name = 'Chrome';
+  final name = "Chrome";
 
-  @override
   final Future<Uri> remoteDebuggerUrl;
 
-  final Future<WipConnection> _tabConnection;
-  final Map<String, String> _idToUrl;
-
   /// Starts a new instance of Chrome open to the given [url], which may be a
   /// [Uri] or a [String].
   factory Chrome(Uri url, {ExecutableSettings settings, bool debug = false}) {
     settings ??= defaultSettings[Runtime.chrome];
     var remoteDebuggerCompleter = Completer<Uri>.sync();
-    var connectionCompleter = Completer<WipConnection>();
-    var idToUrl = <String, String>{};
     return Chrome._(() async {
       var tryPort = ([int port]) async {
         var dir = createTempDir();
         var args = [
-          '--user-data-dir=$dir',
+          "--user-data-dir=$dir",
           url.toString(),
-          '--disable-extensions',
-          '--disable-popup-blocking',
-          '--bwsi',
-          '--no-first-run',
-          '--no-default-browser-check',
-          '--disable-default-apps',
-          '--disable-translate',
-          '--disable-dev-shm-usage',
+          "--disable-extensions",
+          "--disable-popup-blocking",
+          "--bwsi",
+          "--no-first-run",
+          "--no-default-browser-check",
+          "--disable-default-apps",
+          "--disable-translate",
+          "--disable-dev-shm-usage",
         ];
 
         if (!debug && settings.headless) {
           args.addAll([
-            '--headless',
-            '--disable-gpu',
+            "--headless",
+            "--disable-gpu",
             // We don't actually connect to the remote debugger, but Chrome will
             // close as soon as the page is loaded if we don't turn it on.
-            '--remote-debugging-port=0'
+            "--remote-debugging-port=0"
           ]);
         }
 
@@ -75,15 +64,13 @@
         // but without a reliable and fast way to tell if it succeeded that
         // doesn't provide us much. It's very unlikely that this port will fail,
         // though.
-        if (port != null) args.add('--remote-debugging-port=$port');
+        if (port != null) args.add("--remote-debugging-port=$port");
 
         var process = await Process.start(settings.executable, args);
 
         if (port != null) {
           remoteDebuggerCompleter.complete(
-              getRemoteDebuggerUrl(Uri.parse('http://localhost:$port')));
-
-          connectionCompleter.complete(_connect(process, port, idToUrl, url));
+              getRemoteDebuggerUrl(Uri.parse("http://localhost:$port")));
         } else {
           remoteDebuggerCompleter.complete(null);
         }
@@ -96,96 +83,9 @@
 
       if (!debug) return tryPort();
       return getUnusedPort<Process>(tryPort);
-    }, remoteDebuggerCompleter.future, connectionCompleter.future, idToUrl);
+    }, remoteDebuggerCompleter.future);
   }
 
-  /// Returns a Dart based hit-map containing coverage report, suitable for use
-  /// with `package:coverage`.
-  Future<Map<String, dynamic>> gatherCoverage() async {
-    var tabConnection = await _tabConnection;
-    var response = await tabConnection.debugger.connection
-        .sendCommand('Profiler.takePreciseCoverage', {});
-    var result = response.result['result'];
-    var coverage = await parseChromeCoverage(
-      (result as List).cast(),
-      _sourceProvider,
-      _sourceMapProvider,
-      _sourceUriProvider,
-    );
-    return coverage;
-  }
-
-  Chrome._(Future<Process> Function() startBrowser, this.remoteDebuggerUrl,
-      this._tabConnection, this._idToUrl)
+  Chrome._(Future<Process> startBrowser(), this.remoteDebuggerUrl)
       : super(startBrowser);
-
-  Future<Uri> _sourceUriProvider(String sourceUrl, String scriptId) async {
-    var script = _idToUrl[scriptId];
-    if (script == null) return null;
-    var uri = Uri.parse(script);
-    var path = p.join(
-        p.joinAll(uri.pathSegments.sublist(1, uri.pathSegments.length - 1)),
-        sourceUrl);
-    return path.contains('/packages/')
-        ? Uri(scheme: 'package', path: path.split('/packages/').last)
-        : Uri.file(p.absolute(path));
-  }
-
-  Future<String> _sourceMapProvider(String scriptId) async {
-    var script = _idToUrl[scriptId];
-    if (script == null) return null;
-    var mapResponse = await http.get('$script.map');
-    if (mapResponse.statusCode != HttpStatus.ok) return null;
-    return mapResponse.body;
-  }
-
-  Future<String> _sourceProvider(String scriptId) async {
-    var script = _idToUrl[scriptId];
-    if (script == null) return null;
-    var scriptResponse = await http.get(script);
-    if (scriptResponse.statusCode != HttpStatus.ok) return null;
-    return scriptResponse.body;
-  }
-}
-
-Future<WipConnection> _connect(
-    Process process, int port, Map<String, String> idToUrl, Uri url) async {
-  // Wait for Chrome to be in a ready state.
-  await process.stderr
-      .transform(utf8.decoder)
-      .transform(LineSplitter())
-      .firstWhere((line) => line.startsWith('DevTools listening'));
-
-  var chromeConnection = ChromeConnection('localhost', port);
-  ChromeTab tab;
-  var attempt = 0;
-  while (tab == null) {
-    attempt++;
-    var tabs = await chromeConnection.getTabs();
-    tab =
-        tabs.firstWhere((tab) => tab.url == url.toString(), orElse: () => null);
-    if (tab == null) {
-      await Future.delayed(Duration(milliseconds: 100));
-      if (attempt > 5) {
-        throw StateError('Could not connect to test tab with url: $url');
-      }
-    }
-  }
-  var tabConnection = await tab.connect();
-
-  // Enable debugging.
-  await tabConnection.debugger.enable();
-
-  // Coverage reports are in terms of scriptIds so keep note of URLs.
-  tabConnection.debugger.onScriptParsed.listen((data) {
-    var script = data.script;
-    if (script.url.isNotEmpty) idToUrl[script.scriptId] = script.url;
-  });
-
-  // Enable coverage collection.
-  await tabConnection.debugger.connection.sendCommand('Profiler.enable', {});
-  await tabConnection.debugger.connection.sendCommand(
-      'Profiler.startPreciseCoverage', {'detailed': true, 'callCount': false});
-
-  return tabConnection;
 }
diff --git a/test/lib/src/runner/browser/firefox.dart b/test/lib/src/runner/browser/firefox.dart
index cf0efe6..aa51a88 100644
--- a/test/lib/src/runner/browser/firefox.dart
+++ b/test/lib/src/runner/browser/firefox.dart
@@ -7,6 +7,7 @@
 
 import 'package:path/path.dart' as p;
 import 'package:pedantic/pedantic.dart';
+
 import 'package:test_api/src/backend/runtime.dart'; // ignore: implementation_imports
 import 'package:test_core/src/util/io.dart'; // ignore: implementation_imports
 
@@ -28,8 +29,7 @@
 ///
 /// Any errors starting or running the process are reported through [onExit].
 class Firefox extends Browser {
-  @override
-  final name = 'Firefox';
+  final name = "Firefox";
 
   Firefox(url, {ExecutableSettings settings})
       : super(() => _startBrowser(url, settings));
@@ -41,15 +41,11 @@
     var dir = createTempDir();
     File(p.join(dir, 'prefs.js')).writeAsStringSync(_preferences);
 
-    var process = await Process.start(settings.executable, [
-      '--profile',
-      '$dir',
-      url.toString(),
-      '--no-remote',
-      ...settings.arguments,
-    ], environment: {
-      'MOZ_CRASHREPORTER_DISABLE': '1'
-    });
+    var process = await Process.start(
+        settings.executable,
+        ["--profile", "$dir", url.toString(), "--no-remote"]
+          ..addAll(settings.arguments),
+        environment: {"MOZ_CRASHREPORTER_DISABLE": "1"});
 
     unawaited(process.exitCode
         .then((_) => Directory(dir).deleteSync(recursive: true)));
diff --git a/test/lib/src/runner/browser/internet_explorer.dart b/test/lib/src/runner/browser/internet_explorer.dart
index d79c9e4..4ddba9e 100644
--- a/test/lib/src/runner/browser/internet_explorer.dart
+++ b/test/lib/src/runner/browser/internet_explorer.dart
@@ -14,8 +14,7 @@
 ///
 /// Any errors starting or running the process are reported through [onExit].
 class InternetExplorer extends Browser {
-  @override
-  final name = 'Internet Explorer';
+  final name = "Internet Explorer";
 
   InternetExplorer(url, {ExecutableSettings settings})
       : super(() => _startBrowser(url, settings));
@@ -25,10 +24,7 @@
   static Future<Process> _startBrowser(url, ExecutableSettings settings) {
     settings ??= defaultSettings[Runtime.internetExplorer];
 
-    return Process.start(settings.executable, [
-      '-extoff',
-      '$url',
-      ...settings.arguments,
-    ]);
+    return Process.start(settings.executable,
+        ['-extoff', url.toString()]..addAll(settings.arguments));
   }
 }
diff --git a/test/lib/src/runner/browser/phantom_js.dart b/test/lib/src/runner/browser/phantom_js.dart
index d2adbcd..75e1d48 100644
--- a/test/lib/src/runner/browser/phantom_js.dart
+++ b/test/lib/src/runner/browser/phantom_js.dart
@@ -7,10 +7,12 @@
 
 import 'package:path/path.dart' as p;
 import 'package:pedantic/pedantic.dart';
+
 import 'package:test_api/src/backend/runtime.dart'; // ignore: implementation_imports
-import 'package:test_core/src/runner/application_exception.dart'; // ignore: implementation_imports
+
 import 'package:test_core/src/util/exit_codes.dart' // ignore: implementation_imports
     as exit_codes;
+import 'package:test_core/src/runner/application_exception.dart'; // ignore: implementation_imports
 import 'package:test_core/src/util/io.dart'; // ignore: implementation_imports
 
 import '../executable_settings.dart';
@@ -18,7 +20,7 @@
 import 'default_settings.dart';
 
 /// The PhantomJS script that opens the host page.
-final _script = '''
+final _script = """
 var system = require('system');
 var page = require('webpage').create();
 
@@ -34,16 +36,14 @@
 page.open(system.args[1], function(status) {
   if (status !== "success") phantom.exit(1);
 });
-''';
+""";
 
 /// A class for running an instance of PhantomJS.
 ///
 /// Any errors starting or running the process are reported through [onExit].
 class PhantomJS extends Browser {
-  @override
-  final name = 'PhantomJS';
+  final name = "PhantomJS";
 
-  @override
   final Future<Uri> remoteDebuggerUrl;
 
   factory PhantomJS(url, {ExecutableSettings settings, bool debug = false}) {
@@ -51,7 +51,7 @@
     var remoteDebuggerCompleter = Completer<Uri>.sync();
     return PhantomJS._(() async {
       var dir = createTempDir();
-      var script = p.join(dir, 'script.js');
+      var script = p.join(dir, "script.js");
       File(script).writeAsStringSync(_script);
 
       var port = debug ? await getUnsafeUnusedPort() : null;
@@ -59,7 +59,7 @@
       var args = settings.arguments.toList();
       if (debug) {
         args.addAll(
-            ['--remote-debugger-port=$port', '--remote-debugger-autorun=yes']);
+            ["--remote-debugger-port=$port", "--remote-debugger-autorun=yes"]);
       }
       args.addAll([script, url.toString()]);
       var process = await Process.start(settings.executable, args);
@@ -73,13 +73,13 @@
 
         if (exitCode == exit_codes.protocol) {
           throw ApplicationException(
-              'Only PhantomJS version 2.0.0 or greater is supported');
+              "Only PhantomJS version 2.0.0 or greater is supported");
         }
       }));
 
       if (port != null) {
         remoteDebuggerCompleter.complete(Uri.parse(
-            'http://localhost:$port/webkit/inspector/inspector.html?page=2'));
+            "http://localhost:$port/webkit/inspector/inspector.html?page=2"));
       } else {
         remoteDebuggerCompleter.complete(null);
       }
@@ -88,6 +88,6 @@
     }, remoteDebuggerCompleter.future);
   }
 
-  PhantomJS._(Future<Process> Function() startBrowser, this.remoteDebuggerUrl)
+  PhantomJS._(Future<Process> startBrowser(), this.remoteDebuggerUrl)
       : super(startBrowser);
 }
diff --git a/test/lib/src/runner/browser/platform.dart b/test/lib/src/runner/browser/platform.dart
index 75c8eb0..1b67088 100644
--- a/test/lib/src/runner/browser/platform.dart
+++ b/test/lib/src/runner/browser/platform.dart
@@ -9,6 +9,7 @@
 
 import 'package:async/async.dart';
 import 'package:http_multi_server/http_multi_server.dart';
+import 'package:package_resolver/package_resolver.dart';
 import 'package:path/path.dart' as p;
 import 'package:pool/pool.dart';
 import 'package:shelf/shelf.dart' as shelf;
@@ -36,7 +37,6 @@
 import 'package:test_core/src/runner/plugin/customizable_platform.dart'; // ignore: implementation_imports
 
 import '../executable_settings.dart';
-import '../../util/package_map.dart';
 import '../../util/path_handler.dart';
 import '../../util/one_off_handler.dart';
 import 'browser_manager.dart';
@@ -55,8 +55,6 @@
         Configuration.current,
         p.fromUri(await Isolate.resolvePackageUri(
             Uri.parse('package:test/src/runner/browser/static/favicon.ico'))),
-        p.fromUri(await Isolate.resolvePackageUri(Uri.parse(
-            'package:test/src/runner/browser/static/default.html.tpl'))),
         root: root);
   }
 
@@ -73,7 +71,7 @@
   final _secret = Uri.encodeComponent(randomBase64(24));
 
   /// The URL for this server.
-  Uri get url => _server.url.resolve(_secret + '/');
+  Uri get url => _server.url.resolve(_secret + "/");
 
   /// A [OneOffHandler] for servicing WebSocket connections for
   /// [BrowserManager]s.
@@ -123,19 +121,15 @@
   ///
   /// This is used to make sure that a given test suite is only compiled once
   /// per run, rather than once per browser per run.
-  final _compileFutures = <String, Future>{};
+  final _compileFutures = Map<String, Future>();
 
   /// Mappers for Dartifying stack traces, indexed by test path.
-  final _mappers = <String, StackTraceMapper>{};
-
-  /// The default template for html tests.
-  final String _defaultTemplatePath;
+  final _mappers = Map<String, StackTraceMapper>();
 
   BrowserPlatform._(this._server, Configuration config, String faviconPath,
-      this._defaultTemplatePath,
       {String root})
       : _config = config,
-        _root = root ?? p.current,
+        _root = root == null ? p.current : root,
         _compiledDir = config.pubServeUrl == null ? createTempDir() : null,
         _http = config.pubServeUrl == null ? null : HttpClient() {
     var cascade = shelf.Cascade().add(_webSocketHandler.handler);
@@ -166,7 +160,7 @@
   shelf.Response _wrapperHandler(shelf.Request request) {
     var path = p.fromUri(request.url);
 
-    if (path.endsWith('.browser_test.dart')) {
+    if (path.endsWith(".browser_test.dart")) {
       var testPath = p.basename(p.withoutExtension(p.withoutExtension(path)));
       return shelf.Response.ok('''
         import "package:stream_channel/stream_channel.dart";
@@ -183,34 +177,36 @@
       ''', headers: {'Content-Type': 'application/dart'});
     }
 
-    if (path.endsWith('.html')) {
-      var test = p.withoutExtension(path) + '.dart';
+    if (path.endsWith(".html")) {
+      var test = p.withoutExtension(path) + ".dart";
+
+      // Link to the Dart wrapper on Dartium and the compiled JS version
+      // elsewhere.
       var scriptBase = htmlEscape.convert(p.basename(test));
       var link = '<link rel="x-dart-test" href="$scriptBase">';
-      var testName = htmlEscape.convert(test);
-      var template = _config.customHtmlTemplatePath ?? _defaultTemplatePath;
-      var contents = File(template).readAsStringSync();
-      var processedContents = contents
-          // Checked during loading phase that there is only one {{testScript}} placeholder.
-          .replaceFirst('{{testScript}}', link)
-          .replaceAll('{{testName}}', testName);
-      return shelf.Response.ok(processedContents,
-          headers: {'Content-Type': 'text/html'});
+
+      return shelf.Response.ok('''
+        <!DOCTYPE html>
+        <html>
+        <head>
+          <title>${htmlEscape.convert(test)} Test</title>
+          $link
+          <script src="packages/test/dart.js"></script>
+        </head>
+        </html>
+      ''', headers: {'Content-Type': 'text/html'});
     }
 
     return shelf.Response.notFound('Not found.');
   }
 
-  @override
   ExecutableSettings parsePlatformSettings(YamlMap settings) =>
       ExecutableSettings.parse(settings);
 
-  @override
   ExecutableSettings mergePlatformSettings(
           ExecutableSettings settings1, ExecutableSettings settings2) =>
       settings1.merge(settings2);
 
-  @override
   void customizePlatform(Runtime runtime, ExecutableSettings settings) {
     var oldSettings =
         _browserSettings[runtime] ?? _browserSettings[runtime.root];
@@ -222,40 +218,22 @@
   ///
   /// This will start a browser to load the suite if one isn't already running.
   /// Throws an [ArgumentError] if `platform.platform` isn't a browser.
-  @override
   Future<RunnerSuite> load(String path, SuitePlatform platform,
       SuiteConfiguration suiteConfig, Object message) async {
     var browser = platform.runtime;
     assert(suiteConfig.runtimes.contains(browser.identifier));
 
     if (!browser.isBrowser) {
-      throw ArgumentError('$browser is not a browser.');
+      throw ArgumentError("$browser is not a browser.");
     }
 
-    var htmlPathFromTestPath = p.withoutExtension(path) + '.html';
-    if (File(htmlPathFromTestPath).existsSync()) {
-      if (_config.customHtmlTemplatePath != null &&
-          p.basename(htmlPathFromTestPath) ==
-              p.basename(_config.customHtmlTemplatePath)) {
-        throw LoadException(
-            path,
-            'template file "${p.basename(_config.customHtmlTemplatePath)}" cannot be named '
-            'like the test file.');
-      }
-      _checkHtmlCorrectness(htmlPathFromTestPath, path);
-    } else if (_config.customHtmlTemplatePath != null) {
-      var htmlTemplatePath = _config.customHtmlTemplatePath;
-      if (!File(htmlTemplatePath).existsSync()) {
-        throw LoadException(
-            path, '"${htmlTemplatePath}" does not exist or is not readable');
-      }
-
-      final templateFileContents = File(htmlTemplatePath).readAsStringSync();
-      if ('{{testScript}}'.allMatches(templateFileContents).length != 1) {
-        throw LoadException(path,
-            '"${htmlTemplatePath}" must contain exactly one {{testScript}} placeholder');
-      }
-      _checkHtmlCorrectness(htmlTemplatePath, path);
+    var htmlPath = p.withoutExtension(path) + '.html';
+    if (File(htmlPath).existsSync() &&
+        !File(htmlPath).readAsStringSync().contains('packages/test/dart.js')) {
+      throw LoadException(
+          path,
+          '"${htmlPath}" must contain <script src="packages/test/dart.js">'
+          '</script>.');
     }
 
     Uri suiteUrl;
@@ -279,7 +257,7 @@
 
       if (_closed) return null;
       suiteUrl = url.resolveUri(
-          p.toUri(p.withoutExtension(p.relative(path, from: _root)) + '.html'));
+          p.toUri(p.withoutExtension(p.relative(path, from: _root)) + ".html"));
     }
 
     if (_closed) return null;
@@ -294,16 +272,6 @@
     return suite;
   }
 
-  void _checkHtmlCorrectness(String htmlPath, String path) {
-    if (!File(htmlPath).readAsStringSync().contains('packages/test/dart.js')) {
-      throw LoadException(
-          path,
-          '"${htmlPath}" must contain <script src="packages/test/dart.js">'
-          '</script>.');
-    }
-  }
-
-  @override
   StreamChannel loadChannel(String path, SuitePlatform platform) =>
       throw UnimplementedError();
 
@@ -340,17 +308,16 @@
 
           throw LoadException(
               path,
-              'Error getting $url: ${response.statusCode} '
-              '${response.reasonPhrase}\n'
+              "Error getting $url: ${response.statusCode} "
+              "${response.reasonPhrase}\n"
               'Make sure "pub serve" is serving the test/ directory.');
         }
 
         if (getSourceMap && !suiteConfig.jsTrace) {
           _mappers[path] = JSStackTraceMapper(await utf8.decodeStream(response),
               mapUrl: url,
-              sdkRoot: p.toUri('packages/\$sdk'),
-              packageMap:
-                  (await currentPackageConfig).toPackagesDirPackageMap());
+              packageResolver: SyncPackageResolver.root('packages'),
+              sdkRoot: p.toUri('packages/\$sdk'));
           return;
         }
 
@@ -359,13 +326,13 @@
       } on IOException catch (error) {
         var message = getErrorMessage(error);
         if (error is SocketException) {
-          message = '${error.osError.message} '
-              '(errno ${error.osError.errorCode})';
+          message = "${error.osError.message} "
+              "(errno ${error.osError.errorCode})";
         }
 
         throw LoadException(
             path,
-            'Error getting $url: $message\n'
+            "Error getting $url: $message\n"
             'Make sure "pub serve" is running.');
       } finally {
         timer.cancel();
@@ -380,7 +347,7 @@
   Future _compileSuite(String dartPath, SuiteConfiguration suiteConfig) {
     return _compileFutures.putIfAbsent(dartPath, () async {
       var dir = Directory(_compiledDir).createTempSync('test_').path;
-      var jsPath = p.join(dir, p.basename(dartPath) + '.browser_test.dart.js');
+      var jsPath = p.join(dir, p.basename(dartPath) + ".browser_test.dart.js");
 
       await _compilers.compile('''
         import "package:test/src/bootstrap/browser.dart";
@@ -411,8 +378,8 @@
       var mapPath = jsPath + '.map';
       _mappers[dartPath] = JSStackTraceMapper(File(mapPath).readAsStringSync(),
           mapUrl: p.toUri(mapPath),
-          sdkRoot: Uri.parse('org-dartlang-sdk:///sdk'),
-          packageMap: (await currentPackageConfig).toPackageMap());
+          packageResolver: await PackageResolver.current.asSync,
+          sdkRoot: p.toUri(sdkDir));
     });
   }
 
@@ -426,7 +393,7 @@
     var completer = Completer<WebSocketChannel>.sync();
     var path = _webSocketHandler.create(webSocketHandler(completer.complete));
     var webSocketUrl = url.replace(scheme: 'ws').resolve(path);
-    var hostUrl = (_config.pubServeUrl ?? url)
+    var hostUrl = (_config.pubServeUrl == null ? url : _config.pubServeUrl)
         .resolve('packages/test/src/runner/browser/static/index.html')
         .replace(queryParameters: {
       'managerUrl': webSocketUrl.toString(),
@@ -448,7 +415,6 @@
   ///
   /// Note that this doesn't close the server itself. Browser tests can still be
   /// loaded, they'll just spawn new browsers.
-  @override
   Future closeEphemeral() {
     var managers = _browserManagers.values.toList();
     _browserManagers.clear();
@@ -463,7 +429,6 @@
   ///
   /// Returns a [Future] that completes once the server is closed and its
   /// resources have been fully released.
-  @override
   Future close() => _closeMemo.runOnce(() async {
         var futures =
             _browserManagers.values.map<Future<dynamic>>((future) async {
diff --git a/test/lib/src/runner/browser/post_message_channel.dart b/test/lib/src/runner/browser/post_message_channel.dart
index 595c2ec..71e3dfa 100644
--- a/test/lib/src/runner/browser/post_message_channel.dart
+++ b/test/lib/src/runner/browser/post_message_channel.dart
@@ -12,7 +12,7 @@
 import 'package:stream_channel/stream_channel.dart';
 
 // Avoid using this from dart:html to work around dart-lang/sdk#32113.
-@JS('window.parent.postMessage')
+@JS("window.parent.postMessage")
 external void _postParentMessage(Object message, String targetOrigin);
 
 /// Constructs a [StreamChannel] wrapping [MessageChannel] communication with
@@ -29,7 +29,7 @@
     // very unlikely that a malicious site would care about hacking someone's
     // unit tests, let alone be able to find the test server while it's
     // running, but it's good practice to check the origin anyway.
-    return message.origin == window.location.origin && message.data == 'port';
+    return message.origin == window.location.origin && message.data == "port";
   }).then((message) {
     var port = message.ports.first;
     var portSubscription = port.onMessage.listen((message) {
@@ -37,9 +37,9 @@
     });
 
     controller.local.stream.listen((data) {
-      port.postMessage({'data': data});
+      port.postMessage({"data": data});
     }, onDone: () {
-      port.postMessage({'event': 'done'});
+      port.postMessage({"event": "done"});
       portSubscription.cancel();
     });
   });
@@ -47,7 +47,7 @@
   // Send a ready message once we're listening so the host knows it's safe to
   // start sending events.
   // TODO(nweiz): Stop manually adding href here once issue 22554 is fixed.
-  _postParentMessage(jsify({'href': window.location.href, 'ready': true}),
+  _postParentMessage(jsify({"href": window.location.href, "ready": true}),
       window.location.origin);
 
   return controller.foreign;
diff --git a/test/lib/src/runner/browser/safari.dart b/test/lib/src/runner/browser/safari.dart
index a47bb09..40faa4f 100644
--- a/test/lib/src/runner/browser/safari.dart
+++ b/test/lib/src/runner/browser/safari.dart
@@ -8,19 +8,19 @@
 
 import 'package:path/path.dart' as p;
 import 'package:pedantic/pedantic.dart';
+
 import 'package:test_api/src/backend/runtime.dart'; // ignore: implementation_imports
 import 'package:test_core/src/util/io.dart'; // ignore: implementation_imports
 
-import '../executable_settings.dart';
 import 'browser.dart';
 import 'default_settings.dart';
+import '../executable_settings.dart';
 
 /// A class for running an instance of Safari.
 ///
 /// Any errors starting or running the process are reported through [onExit].
 class Safari extends Browser {
-  @override
-  final name = 'Safari';
+  final name = "Safari";
 
   Safari(url, {ExecutableSettings settings})
       : super(() => _startBrowser(url, settings));
@@ -36,7 +36,7 @@
     // want it to load.
     var redirect = p.join(dir, 'redirect.html');
     File(redirect).writeAsStringSync(
-        '<script>location = ' + jsonEncode(url.toString()) + '</script>');
+        "<script>location = " + jsonEncode(url.toString()) + "</script>");
 
     var process = await Process.start(
         settings.executable, settings.arguments.toList()..add(redirect));
diff --git a/test/lib/src/runner/browser/static/default.html.tpl b/test/lib/src/runner/browser/static/default.html.tpl
deleted file mode 100644
index a92f529..0000000
--- a/test/lib/src/runner/browser/static/default.html.tpl
+++ /dev/null
@@ -1,8 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>{{testName}} Test</title>
-    {{testScript}}
-    <script src="packages/test/dart.js"></script>
-  </head>
-</html>
diff --git a/test/lib/src/runner/executable_settings.dart b/test/lib/src/runner/executable_settings.dart
index 5aa9b8d..e793d12 100644
--- a/test/lib/src/runner/executable_settings.dart
+++ b/test/lib/src/runner/executable_settings.dart
@@ -72,7 +72,7 @@
   /// Parses settings from a user-provided YAML mapping.
   factory ExecutableSettings.parse(YamlMap settings) {
     List<String> arguments;
-    var argumentsNode = settings.nodes['arguments'];
+    var argumentsNode = settings.nodes["arguments"];
     if (argumentsNode != null) {
       var value = argumentsNode.value;
       if (value is String) {
@@ -83,14 +83,14 @@
         }
       } else {
         throw SourceSpanFormatException(
-            'Must be a string.', argumentsNode.span);
+            "Must be a string.", argumentsNode.span);
       }
     }
 
     String linuxExecutable;
     String macOSExecutable;
     String windowsExecutable;
-    var executableNode = settings.nodes['executable'];
+    var executableNode = settings.nodes["executable"];
     if (executableNode != null) {
       var value = executableNode.value;
       if (value is String) {
@@ -104,25 +104,25 @@
         macOSExecutable = value;
         windowsExecutable = value;
       } else if (executableNode is YamlMap) {
-        linuxExecutable = _getExecutable(executableNode.nodes['linux']);
-        macOSExecutable = _getExecutable(executableNode.nodes['mac_os']);
-        windowsExecutable = _getExecutable(executableNode.nodes['windows'],
+        linuxExecutable = _getExecutable(executableNode.nodes["linux"]);
+        macOSExecutable = _getExecutable(executableNode.nodes["mac_os"]);
+        windowsExecutable = _getExecutable(executableNode.nodes["windows"],
             allowRelative: true);
       } else {
         throw SourceSpanFormatException(
-            'Must be a map or a string.', executableNode.span);
+            "Must be a map or a string.", executableNode.span);
       }
     }
 
     var headless = true;
-    var headlessNode = settings.nodes['headless'];
+    var headlessNode = settings.nodes["headless"];
     if (headlessNode != null) {
       var value = headlessNode.value;
       if (value is bool) {
         headless = value;
       } else {
         throw SourceSpanFormatException(
-            'Must be a boolean.', headlessNode.span);
+            "Must be a boolean.", headlessNode.span);
       }
     }
 
@@ -142,7 +142,7 @@
       {bool allowRelative = false}) {
     if (executableNode == null || executableNode.value == null) return null;
     if (executableNode.value is! String) {
-      throw SourceSpanFormatException('Must be a string.', executableNode.span);
+      throw SourceSpanFormatException("Must be a string.", executableNode.span);
     }
     if (!allowRelative) _assertNotRelative(executableNode as YamlScalar);
     return executableNode.value as String;
@@ -159,7 +159,7 @@
     if (p.posix.basename(executable) == executable) return;
 
     throw SourceSpanFormatException(
-        'Linux and Mac OS executables may not be relative paths.',
+        "Linux and Mac OS executables may not be relative paths.",
         executableNode.span);
   }
 
diff --git a/test/lib/src/runner/node/platform.dart b/test/lib/src/runner/node/platform.dart
index bea9aa9..3d5d9ba 100644
--- a/test/lib/src/runner/node/platform.dart
+++ b/test/lib/src/runner/node/platform.dart
@@ -7,9 +7,10 @@
 import 'dart:convert';
 
 import 'package:async/async.dart';
-import 'package:package_config/package_config.dart';
 import 'package:multi_server_socket/multi_server_socket.dart';
 import 'package:node_preamble/preamble.dart' as preamble;
+import 'package:pedantic/pedantic.dart';
+import 'package:package_resolver/package_resolver.dart';
 import 'package:path/path.dart' as p;
 import 'package:stream_channel/stream_channel.dart';
 import 'package:yaml/yaml.dart';
@@ -32,7 +33,6 @@
 import 'package:test_core/src/runner/plugin/platform_helpers.dart'; // ignore: implementation_imports
 
 import '../executable_settings.dart';
-import '../../util/package_map.dart';
 
 /// A platform that loads tests in Node.js processes.
 class NodePlatform extends PlatformPlugin
@@ -41,7 +41,7 @@
   final Configuration _config;
 
   /// The [CompilerPool] managing active instances of `dart2js`.
-  final _compilers = CompilerPool(['-Dnode=true', '--server-mode']);
+  final _compilers = CompilerPool(["-Dnode=true", "--server-mode"]);
 
   /// The temporary directory in which compiled JS is emitted.
   final _compiledDir = createTempDir();
@@ -53,43 +53,38 @@
   /// it.
   final _settings = {
     Runtime.nodeJS: ExecutableSettings(
-        linuxExecutable: 'node',
-        macOSExecutable: 'node',
-        windowsExecutable: 'node.exe')
+        linuxExecutable: "node",
+        macOSExecutable: "node",
+        windowsExecutable: "node.exe")
   };
 
   NodePlatform()
       : _config = Configuration.current,
         _http = Configuration.current.pubServeUrl == null ? null : HttpClient();
 
-  @override
   ExecutableSettings parsePlatformSettings(YamlMap settings) =>
       ExecutableSettings.parse(settings);
 
-  @override
   ExecutableSettings mergePlatformSettings(
           ExecutableSettings settings1, ExecutableSettings settings2) =>
       settings1.merge(settings2);
 
-  @override
   void customizePlatform(Runtime runtime, ExecutableSettings settings) {
     var oldSettings = _settings[runtime] ?? _settings[runtime.root];
     if (oldSettings != null) settings = oldSettings.merge(settings);
     _settings[runtime] = settings;
   }
 
-  @override
   StreamChannel loadChannel(String path, SuitePlatform platform) =>
       throw UnimplementedError();
 
-  @override
   Future<RunnerSuite> load(String path, SuitePlatform platform,
       SuiteConfiguration suiteConfig, Object message) async {
     var pair = await _loadChannel(path, platform.runtime, suiteConfig);
     var controller = deserializeSuite(
         path, platform, suiteConfig, PluginEnvironment(), pair.first, message);
 
-    controller.channel('test.node.mapper').sink.add(pair.last?.serialize());
+    controller.channel("test.node.mapper").sink.add(pair.last?.serialize());
 
     return await controller.suite;
   }
@@ -151,7 +146,7 @@
   Future<Pair<Process, StackTraceMapper>> _spawnNormalProcess(String testPath,
       Runtime runtime, SuiteConfiguration suiteConfig, int socketPort) async {
     var dir = Directory(_compiledDir).createTempSync('test_').path;
-    var jsPath = p.join(dir, p.basename(testPath) + '.node_test.dart.js');
+    var jsPath = p.join(dir, p.basename(testPath) + ".node_test.dart.js");
     await _compilers.compile('''
         import "package:test/src/bootstrap/node.dart";
 
@@ -173,8 +168,8 @@
       var mapPath = jsPath + '.map';
       mapper = JSStackTraceMapper(await File(mapPath).readAsString(),
           mapUrl: p.toUri(mapPath),
-          sdkRoot: Uri.parse('org-dartlang-sdk:///sdk'),
-          packageMap: (await currentPackageConfig).toPackageMap());
+          packageResolver: await PackageResolver.current.asSync,
+          sdkRoot: p.toUri(sdkDir));
     }
 
     return Pair(await _startProcess(runtime, jsPath, socketPort), mapper);
@@ -192,11 +187,12 @@
     var jsPath = p.join(precompiledPath, '$testPath.node_test.dart.js');
     if (!suiteConfig.jsTrace) {
       var mapPath = jsPath + '.map';
+      var resolver = await SyncPackageResolver.loadConfig(
+          p.toUri(p.join(precompiledPath, '.packages')));
       mapper = JSStackTraceMapper(await File(mapPath).readAsString(),
           mapUrl: p.toUri(mapPath),
-          sdkRoot: Uri.parse('org-dartlang-sdk:///sdk'),
-          packageMap: (await findPackageConfig(Directory(precompiledPath)))
-              .toPackageMap());
+          packageResolver: resolver,
+          sdkRoot: p.toUri(sdkDir));
     }
 
     return Pair(await _startProcess(runtime, jsPath, socketPort), mapper);
@@ -208,7 +204,7 @@
   Future<Pair<Process, StackTraceMapper>> _spawnPubServeProcess(String testPath,
       Runtime runtime, SuiteConfiguration suiteConfig, int socketPort) async {
     var dir = Directory(_compiledDir).createTempSync('test_').path;
-    var jsPath = p.join(dir, p.basename(testPath) + '.node_test.dart.js');
+    var jsPath = p.join(dir, p.basename(testPath) + ".node_test.dart.js");
     var url = _config.pubServeUrl.resolveUri(
         p.toUri(p.relative(testPath, from: 'test') + '.node_test.dart.js'));
 
@@ -220,8 +216,8 @@
       var mapUrl = url.replace(path: url.path + '.map');
       mapper = JSStackTraceMapper(await _get(mapUrl, testPath),
           mapUrl: mapUrl,
-          sdkRoot: p.toUri('packages/\$sdk'),
-          packageMap: (await currentPackageConfig).toPackagesDirPackageMap());
+          packageResolver: SyncPackageResolver.root('packages'),
+          sdkRoot: p.toUri('packages/\$sdk'));
     }
 
     return Pair(await _startProcess(runtime, jsPath, socketPort), mapper);
@@ -233,8 +229,8 @@
     var settings = _settings[runtime];
 
     var nodeModules = p.absolute('node_modules');
-    var nodePath = Platform.environment['NODE_PATH'];
-    nodePath = nodePath == null ? nodeModules : '$nodePath:$nodeModules';
+    var nodePath = Platform.environment["NODE_PATH"];
+    nodePath = nodePath == null ? nodeModules : "$nodePath:$nodeModules";
 
     try {
       return await Process.start(settings.executable,
@@ -243,7 +239,7 @@
     } catch (error, stackTrace) {
       await Future.error(
           ApplicationException(
-              'Failed to run ${runtime.name}: ${getErrorMessage(error)}'),
+              "Failed to run ${runtime.name}: ${getErrorMessage(error)}"),
           stackTrace);
       return null;
     }
@@ -263,8 +259,8 @@
 
         throw LoadException(
             suitePath,
-            'Error getting $url: ${response.statusCode} '
-            '${response.reasonPhrase}\n'
+            "Error getting $url: ${response.statusCode} "
+            "${response.reasonPhrase}\n"
             'Make sure "pub serve" is serving the test/ directory.');
       }
 
@@ -272,18 +268,17 @@
     } on IOException catch (error) {
       var message = getErrorMessage(error);
       if (error is SocketException) {
-        message = '${error.osError.message} '
-            '(errno ${error.osError.errorCode})';
+        message = "${error.osError.message} "
+            "(errno ${error.osError.errorCode})";
       }
 
       throw LoadException(
           suitePath,
-          'Error getting $url: $message\n'
+          "Error getting $url: $message\n"
           'Make sure "pub serve" is running.');
     }
   }
 
-  @override
   Future close() => _closeMemo.runOnce(() async {
         await _compilers.close();
 
diff --git a/test/lib/src/runner/node/socket_channel.dart b/test/lib/src/runner/node/socket_channel.dart
index 0c2036a..c00a139 100644
--- a/test/lib/src/runner/node/socket_channel.dart
+++ b/test/lib/src/runner/node/socket_channel.dart
@@ -9,10 +9,10 @@
 import 'package:stream_channel/stream_channel.dart';
 import 'package:test_api/src/utils.dart'; // ignore: implementation_imports
 
-@JS('require')
+@JS("require")
 external _Net _require(String module);
 
-@JS('process.argv')
+@JS("process.argv")
 external List<String> get _args;
 
 @JS()
@@ -22,9 +22,9 @@
 
 @JS()
 class _Socket {
-  external void setEncoding(String encoding);
-  external void on(String event, void Function(String chunk) callback);
-  external void write(String data);
+  external setEncoding(String encoding);
+  external on(String event, void callback(String chunk));
+  external write(String data);
 }
 
 /// Returns a [StreamChannel] of JSON-encodable objects that communicates over a
@@ -32,12 +32,12 @@
 StreamChannel<Object> socketChannel() {
   var controller =
       StreamChannelController<String>(allowForeignErrors: false, sync: true);
-  var net = _require('net');
+  var net = _require("net");
   var socket = net.connect(int.parse(_args[2]));
-  socket.setEncoding('utf8');
+  socket.setEncoding("utf8");
 
   controller.local.stream.listen((chunk) => socket.write(chunk));
-  socket.on('data', allowInterop(controller.local.sink.add));
+  socket.on("data", allowInterop(controller.local.sink.add));
 
   return controller.foreign.transform(chunksToLines).transform(jsonDocument);
 }
diff --git a/test/lib/src/util/one_off_handler.dart b/test/lib/src/util/one_off_handler.dart
index 7667df3..a317072 100644
--- a/test/lib/src/util/one_off_handler.dart
+++ b/test/lib/src/util/one_off_handler.dart
@@ -12,7 +12,7 @@
 /// invalid and don't need to have a persistent URL.
 class OneOffHandler {
   /// A map from URL paths to handlers.
-  final _handlers = <String, shelf.Handler>{};
+  final _handlers = Map<String, shelf.Handler>();
 
   /// The counter of handlers that have been activated.
   var _counter = 0;
diff --git a/test/lib/src/util/package_map.dart b/test/lib/src/util/package_map.dart
deleted file mode 100644
index 948b6a6..0000000
--- a/test/lib/src/util/package_map.dart
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS 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 'dart:isolate';
-
-import 'package:package_config/package_config.dart';
-
-/// The [PackageConfig] parsed from the current isolates package config file.
-final Future<PackageConfig> currentPackageConfig = () async {
-  return loadPackageConfigUri(await Isolate.packageConfig);
-}();
-
-/// Adds methods to convert to a package map on [PackageConfig].
-extension PackageMap on PackageConfig {
-  /// A package map exactly matching the current package config
-  Map<String, Uri> toPackageMap() =>
-      {for (var package in packages) package.name: package.packageUriRoot};
-
-  /// A package map with all the current packages but where the uris are all
-  /// of the form 'packages/${package.name}'.
-  Map<String, Uri> toPackagesDirPackageMap() => {
-        for (var package in packages)
-          package.name: Uri.parse('packages/${package.name}')
-      };
-}
diff --git a/test/lib/src/util/path_handler.dart b/test/lib/src/util/path_handler.dart
index 2fe31c4..b5303d5 100644
--- a/test/lib/src/util/path_handler.dart
+++ b/test/lib/src/util/path_handler.dart
@@ -48,7 +48,7 @@
       handlerIndex = i;
     }
 
-    if (handler == null) return shelf.Response.notFound('Not found.');
+    if (handler == null) return shelf.Response.notFound("Not found.");
 
     return handler(
         request.change(path: p.url.joinAll(components.take(handlerIndex + 1))));
@@ -58,5 +58,5 @@
 /// A trie node.
 class _Node {
   shelf.Handler handler;
-  final children = <String, _Node>{};
+  final children = Map<String, _Node>();
 }
diff --git a/test/mono_pkg.yaml b/test/mono_pkg.yaml
index ef401af..e2c26a6 100644
--- a/test/mono_pkg.yaml
+++ b/test/mono_pkg.yaml
@@ -9,10 +9,10 @@
         dart: dev
       - group:
         - dartanalyzer: --fatal-warnings .
-        dart: 2.7.0
+        dart: 2.2.0
     - unit_test:
-      - command: xvfb-run -s "-screen 0 1024x768x24" pub run test --preset travis -x phantomjs --total-shards 5 --shard-index 0
-      - command: xvfb-run -s "-screen 0 1024x768x24" pub run test --preset travis -x phantomjs --total-shards 5 --shard-index 1
-      - command: xvfb-run -s "-screen 0 1024x768x24" pub run test --preset travis -x phantomjs --total-shards 5 --shard-index 2
-      - command: xvfb-run -s "-screen 0 1024x768x24" pub run test --preset travis -x phantomjs --total-shards 5 --shard-index 3
-      - command: xvfb-run -s "-screen 0 1024x768x24" pub run test --preset travis -x phantomjs --total-shards 5 --shard-index 4
+      - command: xvfb-run -s "-screen 0 1024x768x24" pub run test --preset travis --total-shards 5 --shard-index 0
+      - command: xvfb-run -s "-screen 0 1024x768x24" pub run test --preset travis --total-shards 5 --shard-index 1
+      - command: xvfb-run -s "-screen 0 1024x768x24" pub run test --preset travis --total-shards 5 --shard-index 2
+      - command: xvfb-run -s "-screen 0 1024x768x24" pub run test --preset travis --total-shards 5 --shard-index 3
+      - command: xvfb-run -s "-screen 0 1024x768x24" pub run test --preset travis --total-shards 5 --shard-index 4
diff --git a/test/pubspec.yaml b/test/pubspec.yaml
index 26df200..3693289 100644
--- a/test/pubspec.yaml
+++ b/test/pubspec.yaml
@@ -1,43 +1,43 @@
 name: test
-version: 1.14.1
+version: 1.9.4
+author: Dart Team <misc@dartlang.org>
 description: A full featured library for writing and running Dart tests.
 homepage: https://github.com/dart-lang/test/blob/master/pkgs/test
 
 environment:
-  sdk: '>=2.7.0 <3.0.0'
+  sdk: '>=2.2.0 <3.0.0'
 
 dependencies:
-  analyzer: '>=0.36.0 <0.40.0'
+  analyzer: ">=0.36.0 <0.40.0"
   async: ^2.0.0
-  boolean_selector: '>=1.0.0 <3.0.0'
-  coverage: ^0.13.4
-  http: ^0.12.0
+  boolean_selector: ^1.0.0
   http_multi_server: ^2.0.0
   io: ^0.3.0
   js: ^0.6.0
   multi_server_socket: ^1.0.0
   node_preamble: ^1.3.0
-  package_config: ^1.9.0
+  package_resolver: ^1.0.0
   path: ^1.2.0
   pedantic: ^1.1.0
   pool: ^1.3.0
   shelf: ^0.7.0
-  shelf_packages_handler: ">=1.0.0 <3.0.0"
+  shelf_packages_handler: ^1.0.0
   shelf_static: ^0.2.6
   shelf_web_socket: ^0.2.0
   source_span: ^1.4.0
   stack_trace: ^1.9.0
-  stream_channel: '>=1.7.0 <3.0.0'
+  stream_channel: ">=1.7.0 <3.0.0"
   typed_data: ^1.0.0
   web_socket_channel: ^1.0.0
-  webkit_inspection_protocol: ^0.5.0
   yaml: ^2.0.0
   # Use an exact version until the test_api and test_core package are stable.
-  test_api: 0.2.15
-  test_core: 0.3.2
+  test_api: 0.2.11
+  test_core: 0.2.15
 
 dev_dependencies:
   fake_async: ^1.0.0
   shelf_test_handler: ^1.0.0
   test_descriptor: ^1.0.0
   test_process: ^1.0.0
+
+
diff --git a/test/tool/host.dart b/test/tool/host.dart
index c1b229c..fb296c7 100644
--- a/test/tool/host.dart
+++ b/test/tool/host.dart
@@ -38,19 +38,18 @@
   /// running.
   external Function get restartCurrent;
 
-  external factory _JSApi(
-      {void Function() resume, void Function() restartCurrent});
+  external factory _JSApi({void resume(), void restartCurrent()});
 }
 
 /// Sets the top-level `dartTest` object so that it's visible to JS.
-@JS('dartTest')
+@JS("dartTest")
 external set _jsApi(_JSApi api);
 
 /// The iframes created for each loaded test suite, indexed by the suite id.
-final _iframes = <int, IFrameElement>{};
+final _iframes = Map<int, IFrameElement>();
 
 /// Subscriptions created for each loaded test suite, indexed by the suite id.
-final _subscriptions = <int, List<StreamSubscription>>{};
+final _subscriptions = Map<int, List<StreamSubscription>>();
 
 /// The URL for the current page.
 final _currentUrl = Uri.parse(window.location.href);
@@ -141,22 +140,22 @@
     // Send periodic pings to the test runner so it can know when the browser is
     // paused for debugging.
     Timer.periodic(Duration(seconds: 1),
-        (_) => serverChannel.sink.add({'command': 'ping'}));
+        (_) => serverChannel.sink.add({"command": "ping"}));
 
-    var play = document.querySelector('#play');
+    var play = document.querySelector("#play");
     play.onClick.listen((_) {
       if (!document.body.classes.remove('paused')) return;
-      serverChannel.sink.add({'command': 'resume'});
+      serverChannel.sink.add({"command": "resume"});
     });
 
     _jsApi = _JSApi(resume: allowInterop(() {
       if (!document.body.classes.remove('paused')) return;
-      serverChannel.sink.add({'command': 'resume'});
+      serverChannel.sink.add({"command": "resume"});
     }), restartCurrent: allowInterop(() {
-      serverChannel.sink.add({'command': 'restart'});
+      serverChannel.sink.add({"command": "restart"});
     }));
   }, onError: (error, StackTrace stackTrace) {
-    print('$error\n${Trace.from(stackTrace).terse}');
+    print("$error\n${Trace.from(stackTrace).terse}");
   });
 }
 
@@ -208,25 +207,25 @@
 
     // TODO(nweiz): Stop manually checking href here once issue 22554 is
     // fixed.
-    if (message.data['href'] != iframe.src) return;
+    if (message.data["href"] != iframe.src) return;
 
     message.stopPropagation();
 
-    if (message.data['ready'] == true) {
+    if (message.data["ready"] == true) {
       // This message indicates that the iframe is actively listening for
       // events, so the message channel's second port can now be transferred.
       iframe.contentWindow
-          .postMessage('port', window.location.origin, [channel.port2]);
+          .postMessage("port", window.location.origin, [channel.port2]);
       readyCompleter.complete();
-    } else if (message.data['exception'] == true) {
+    } else if (message.data["exception"] == true) {
       // This message from `dart.js` indicates that an exception occurred
       // loading the test.
-      controller.local.sink.add(message.data['data']);
+      controller.local.sink.add(message.data["data"]);
     }
   }));
 
   subscriptions.add(channel.port1.onMessage.listen((message) {
-    controller.local.sink.add(message.data['data']);
+    controller.local.sink.add(message.data["data"]);
   }));
 
   subscriptions.add(controller.local.stream.listen((message) async {
diff --git a/test_api/BUILD.gn b/test_api/BUILD.gn
index 4960a26..b020187 100644
--- a/test_api/BUILD.gn
+++ b/test_api/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by importer.py for test_api-0.2.15
+# This file is generated by importer.py for test_api-0.2.11
 
 import("//build/dart/dart_library.gni")
 
@@ -13,6 +13,7 @@
 
   deps = [
     "//third_party/dart-pkg/pub/stack_trace",
+    "//third_party/dart-pkg/pub/pedantic",
     "//third_party/dart-pkg/pub/async",
     "//third_party/dart-pkg/pub/matcher",
     "//third_party/dart-pkg/pub/collection",
diff --git a/test_api/CHANGELOG.md b/test_api/CHANGELOG.md
index ddbb2db..3a31124 100644
--- a/test_api/CHANGELOG.md
+++ b/test_api/CHANGELOG.md
@@ -1,27 +1,3 @@
-## 0.2.15
-
-* Cancel any StreamQueue that is created as a part of a stream matcher once it
-  is done matching.
-  * This fixes a bug where using a matcher on a custom stream controller and
-    then awaiting the `close()` method on that controller would hang.
-* Avoid causing the test runner to hang if there is a timeout during a
-  `tearDown` callback following a failing test case.
-
-## 0.2.14
-
-* Bump minimum SDK to `2.4.0` for safer usage of for-loop elements.
-
-## 0.2.13
-
-* Work around a bug in the `2.3.0` SDK by avoiding for-loop elements at the top
-  level.
-
-## 0.2.12
-
-* Link to docs on setting timeout when a test times out with the default
-  duration.
-* No longer directly depend on `package:pedantic`.
-
 ## 0.2.11
 
 * Extend the timeout for synthetic tests, e.g. `tearDownAll`.
diff --git a/test_api/lib/src/backend/closed_exception.dart b/test_api/lib/src/backend/closed_exception.dart
index 08a45a0..4432466 100644
--- a/test_api/lib/src/backend/closed_exception.dart
+++ b/test_api/lib/src/backend/closed_exception.dart
@@ -7,6 +7,5 @@
 class ClosedException implements Exception {
   ClosedException();
 
-  @override
-  String toString() => 'This test has been closed.';
+  String toString() => "This test has been closed.";
 }
diff --git a/test_api/lib/src/backend/declarer.dart b/test_api/lib/src/backend/declarer.dart
index f4a41b4..cc4b1fd 100644
--- a/test_api/lib/src/backend/declarer.dart
+++ b/test_api/lib/src/backend/declarer.dart
@@ -50,13 +50,13 @@
   final bool _noRetry;
 
   /// The set-up functions to run for each test in this group.
-  final _setUps = <dynamic Function()>[];
+  final _setUps = List<Function()>();
 
   /// The tear-down functions to run for each test in this group.
-  final _tearDowns = <dynamic Function()>[];
+  final _tearDowns = List<Function()>();
 
   /// The set-up functions to run once for this group.
-  final _setUpAlls = <dynamic Function()>[];
+  final _setUpAlls = List<Function()>();
 
   /// The default timeout for synthetic tests.
   final _timeout = Timeout(Duration(minutes: 12));
@@ -69,7 +69,7 @@
   Trace _setUpAllTrace;
 
   /// The tear-down functions to run once for this group.
-  final _tearDownAlls = <Function()>[];
+  final _tearDownAlls = List<Function()>();
 
   /// The trace for the first call to [tearDownAll].
   ///
@@ -78,13 +78,13 @@
   Trace _tearDownAllTrace;
 
   /// The children of this group, either tests or sub-groups.
-  final _entries = <GroupEntry>[];
+  final _entries = List<GroupEntry>();
 
   /// Whether [build] has been called for this declarer.
   bool _built = false;
 
   /// The tests and/or groups that have been flagged as solo.
-  final _soloEntries = <GroupEntry>[];
+  final _soloEntries = Set<GroupEntry>();
 
   /// Whether any tests and/or groups have been flagged as solo.
   bool get _solo => _soloEntries.isNotEmpty;
@@ -127,11 +127,10 @@
   /// Runs [body] with this declarer as [Declarer.current].
   ///
   /// Returns the return value of [body].
-  T declare<T>(T Function() body) =>
-      runZoned(body, zoneValues: {#test.declarer: this});
+  declare(body()) => runZoned(body, zoneValues: {#test.declarer: this});
 
   /// Defines a test case with the given name and body.
-  void test(String name, dynamic Function() body,
+  void test(String name, body(),
       {String testOn,
       Timeout timeout,
       skip,
@@ -139,7 +138,7 @@
       tags,
       int retry,
       bool solo = false}) {
-    _checkNotBuilt('test');
+    _checkNotBuilt("test");
 
     var newMetadata = Metadata.parse(
         testOn: testOn,
@@ -182,7 +181,7 @@
   }
 
   /// Creates a group of tests.
-  void group(String name, void Function() body,
+  void group(String name, void body(),
       {String testOn,
       Timeout timeout,
       skip,
@@ -190,7 +189,7 @@
       tags,
       int retry,
       bool solo = false}) {
-    _checkNotBuilt('group');
+    _checkNotBuilt("group");
 
     var newMetadata = Metadata.parse(
         testOn: testOn,
@@ -210,7 +209,7 @@
       // result of a void method.
       var result = (body as dynamic)();
       if (result is! Future) return;
-      throw ArgumentError('Groups may not be async.');
+      throw ArgumentError("Groups may not be async.");
     });
     _entries.add(declarer.build());
 
@@ -220,45 +219,44 @@
   }
 
   /// Returns [name] prefixed with this declarer's group name.
-  String _prefix(String name) => _name == null ? name : '$_name $name';
+  String _prefix(String name) => _name == null ? name : "$_name $name";
 
   /// Registers a function to be run before each test in this group.
-  void setUp(dynamic Function() callback) {
-    _checkNotBuilt('setUp');
+  void setUp(callback()) {
+    _checkNotBuilt("setUp");
     _setUps.add(callback);
   }
 
   /// Registers a function to be run after each test in this group.
-  void tearDown(dynamic Function() callback) {
-    _checkNotBuilt('tearDown');
+  void tearDown(callback()) {
+    _checkNotBuilt("tearDown");
     _tearDowns.add(callback);
   }
 
   /// Registers a function to be run once before all tests.
-  void setUpAll(dynamic Function() callback) {
-    _checkNotBuilt('setUpAll');
+  void setUpAll(callback()) {
+    _checkNotBuilt("setUpAll");
     if (_collectTraces) _setUpAllTrace ??= Trace.current(2);
     _setUpAlls.add(callback);
   }
 
   /// Registers a function to be run once after all tests.
-  void tearDownAll(dynamic Function() callback) {
-    _checkNotBuilt('tearDownAll');
+  void tearDownAll(callback()) {
+    _checkNotBuilt("tearDownAll");
     if (_collectTraces) _tearDownAllTrace ??= Trace.current(2);
     _tearDownAlls.add(callback);
   }
 
   /// Like [tearDownAll], but called from within a running [setUpAll] test to
   /// dynamically add a [tearDownAll].
-  void addTearDownAll(dynamic Function() callback) =>
-      _tearDownAlls.add(callback);
+  void addTearDownAll(callback()) => _tearDownAlls.add(callback);
 
   /// Finalizes and returns the group being declared.
   ///
   /// **Note**: The tests in this group must be run in a [Invoker.guard]
   /// context; otherwise, test errors won't be captured.
   Group build() {
-    _checkNotBuilt('build');
+    _checkNotBuilt("build");
 
     _built = true;
     var entries = _entries.map((entry) {
@@ -300,7 +298,7 @@
   Test get _setUpAll {
     if (_setUpAlls.isEmpty) return null;
 
-    return LocalTest(_prefix('(setUpAll)'), _metadata.change(timeout: _timeout),
+    return LocalTest(_prefix("(setUpAll)"), _metadata.change(timeout: _timeout),
         () {
       return runZoned(() => Future.forEach(_setUpAlls, (setUp) => setUp()),
           // Make the declarer visible to running scaffolds so they can add to
@@ -316,7 +314,7 @@
     if (_setUpAlls.isEmpty && _tearDownAlls.isEmpty) return null;
 
     return LocalTest(
-        _prefix('(tearDownAll)'), _metadata.change(timeout: _timeout), () {
+        _prefix("(tearDownAll)"), _metadata.change(timeout: _timeout), () {
       return runZoned(() {
         return Invoker.current.unclosable(() async {
           while (_tearDownAlls.isNotEmpty) {
diff --git a/test_api/lib/src/backend/group.dart b/test_api/lib/src/backend/group.dart
index 30f5238..3f831f0 100644
--- a/test_api/lib/src/backend/group.dart
+++ b/test_api/lib/src/backend/group.dart
@@ -13,13 +13,10 @@
 ///
 /// It includes metadata that applies to all contained tests.
 class Group implements GroupEntry {
-  @override
   final String name;
 
-  @override
   final Metadata metadata;
 
-  @override
   final Trace trace;
 
   /// The children of this group.
@@ -52,9 +49,8 @@
   Group(this.name, Iterable<GroupEntry> entries,
       {Metadata metadata, this.trace, this.setUpAll, this.tearDownAll})
       : entries = List<GroupEntry>.unmodifiable(entries),
-        metadata = metadata ?? Metadata();
+        metadata = metadata == null ? Metadata() : metadata;
 
-  @override
   Group forPlatform(SuitePlatform platform) {
     if (!metadata.testOn.evaluate(platform)) return null;
     var newMetadata = metadata.forPlatform(platform);
@@ -67,8 +63,7 @@
         tearDownAll: tearDownAll);
   }
 
-  @override
-  Group filter(bool Function(Test) callback) {
+  Group filter(bool callback(Test test)) {
     var filtered = _map((entry) => entry.filter(callback));
     if (filtered.isEmpty && entries.isNotEmpty) return null;
     return Group(name, filtered,
@@ -81,7 +76,7 @@
   /// Returns the entries of this group mapped using [callback].
   ///
   /// Any `null` values returned by [callback] will be removed.
-  List<GroupEntry> _map(GroupEntry Function(GroupEntry) callback) {
+  List<GroupEntry> _map(GroupEntry callback(GroupEntry entry)) {
     return entries
         .map((entry) => callback(entry))
         .where((entry) => entry != null)
diff --git a/test_api/lib/src/backend/group_entry.dart b/test_api/lib/src/backend/group_entry.dart
index 44b3054..2d2a341 100644
--- a/test_api/lib/src/backend/group_entry.dart
+++ b/test_api/lib/src/backend/group_entry.dart
@@ -35,5 +35,5 @@
   ///
   /// Returns `null` if this is a test that doesn't match [callback] or a group
   /// where no child tests match [callback].
-  GroupEntry filter(bool Function(Test) callback);
+  GroupEntry filter(bool callback(Test test));
 }
diff --git a/test_api/lib/src/backend/invoker.dart b/test_api/lib/src/backend/invoker.dart
index 2c83b2d..5b44624 100644
--- a/test_api/lib/src/backend/invoker.dart
+++ b/test_api/lib/src/backend/invoker.dart
@@ -4,6 +4,7 @@
 
 import 'dart:async';
 
+import 'package:pedantic/pedantic.dart';
 import 'package:stack_trace/stack_trace.dart';
 
 import '../frontend/expect.dart';
@@ -23,11 +24,8 @@
 
 /// A test in this isolate.
 class LocalTest extends Test {
-  @override
   final String name;
-  @override
   final Metadata metadata;
-  @override
   final Trace trace;
 
   /// Whether this is a test defined using `setUpAll()` or `tearDownAll()`.
@@ -53,13 +51,11 @@
       this.isScaffoldAll);
 
   /// Loads a single runnable instance of this test.
-  @override
   LiveTest load(Suite suite, {Iterable<Group> groups}) {
     var invoker = Invoker._(suite, this, groups: groups, guarded: _guarded);
     return invoker.liveTest;
   }
 
-  @override
   Test forPlatform(SuitePlatform platform) {
     if (!metadata.testOn.evaluate(platform)) return null;
     return LocalTest._(name, metadata.forPlatform(platform), _body, trace,
@@ -115,7 +111,7 @@
     var counter = Zone.current[_counterKey] as _AsyncCounter;
     if (counter != null) return counter;
     throw StateError("Can't add or remove outstanding callbacks outside "
-        'of a test body.');
+        "of a test body.");
   }
 
   /// All the zones created by [waitForOutstandingCallbacks], in the order they
@@ -144,7 +140,7 @@
 
   /// Runs [callback] in a zone where unhandled errors from [LiveTest]s are
   /// caught and dispatched to the appropriate [Invoker].
-  static T guard<T>(T Function() callback) =>
+  static T guard<T>(T callback()) =>
       runZoned(callback, zoneSpecification: ZoneSpecification(
           // Use [handleUncaughtError] rather than [onError] so we can
           // capture [zone] and with it the outstanding callback counter for
@@ -187,7 +183,7 @@
   ///
   /// The [callback] may return a [Future]. Like all tear-downs, callbacks are
   /// run in the reverse of the order they're declared.
-  void addTearDown(dynamic Function() callback) {
+  void addTearDown(callback()) {
     if (closed) throw ClosedException();
 
     if (_test.isScaffoldAll) {
@@ -271,20 +267,15 @@
     if (liveTest.isComplete) return;
     if (_timeoutTimer != null) _timeoutTimer.cancel();
 
-    const defaultTimeout = Duration(seconds: 30);
-    var timeout = liveTest.test.metadata.timeout.apply(defaultTimeout);
+    var timeout = liveTest.test.metadata.timeout.apply(Duration(seconds: 30));
     if (timeout == null) return;
-    String message() {
-      var message = 'Test timed out after ${niceDuration(timeout)}.';
-      if (timeout == defaultTimeout) {
-        message += ' See https://pub.dev/packages/test#timeouts';
-      }
-      return message;
-    }
-
     _timeoutTimer = _invokerZone.createTimer(timeout, () {
       _outstandingCallbackZones.last.run(() {
-        _handleError(Zone.current, TimeoutException(message(), timeout));
+        if (liveTest.isComplete) return;
+        _handleError(
+            Zone.current,
+            TimeoutException(
+                "Test timed out after ${niceDuration(timeout)}.", timeout));
       });
     });
   }
@@ -300,9 +291,9 @@
       // Set the state explicitly so we don't get an extra error about the test
       // failing after being complete.
       _controller.setState(const State(Status.complete, Result.error));
-      throw 'This test was marked as skipped after it had already completed. '
-          'Make sure to use\n'
-          '[expectAsync] or the [completes] matcher when testing async code.';
+      throw "This test was marked as skipped after it had already completed. "
+          "Make sure to use\n"
+          "[expectAsync] or the [completes] matcher when testing async code.";
     }
 
     if (message != null) _controller.message(Message.skip(message));
@@ -314,7 +305,7 @@
   void printOnFailure(String message) {
     message = message.trim();
     if (liveTest.state.result.isFailing) {
-      print('\n$message');
+      print("\n$message");
     } else {
       _printsOnFailure.add(message);
     }
@@ -349,13 +340,13 @@
     zone.run(() => _outstandingCallbacks.complete());
 
     if (!liveTest.test.metadata.chainStackTraces) {
-      _printsOnFailure.add('Consider enabling the flag chain-stack-traces to '
-          'receive more detailed exceptions.\n'
+      _printsOnFailure.add("Consider enabling the flag chain-stack-traces to "
+          "receive more detailed exceptions.\n"
           "For example, 'pub run test --chain-stack-traces'.");
     }
 
     if (_printsOnFailure.isNotEmpty) {
-      print(_printsOnFailure.join('\n\n'));
+      print(_printsOnFailure.join("\n\n"));
       _printsOnFailure.clear();
     }
 
@@ -369,9 +360,9 @@
 
     _handleError(
         zone,
-        'This test failed after it had already completed. Make sure to use '
-        '[expectAsync]\n'
-        'or the [completes] matcher when testing async code.',
+        "This test failed after it had already completed. Make sure to use "
+        "[expectAsync]\n"
+        "or the [completes] matcher when testing async code.",
         stackTrace);
   }
 
@@ -395,7 +386,8 @@
           // corresponding [onStateChange], which violates the timing
           // guarantees.
           //
-          // Use the event loop over the microtask queue to avoid starvation.
+          // Using [new Future] also avoids starving the DOM or other
+          // microtask-level events.
           unawaited(Future(() async {
             await _test._body();
             await unclosable(_runTearDowns);
@@ -407,7 +399,7 @@
 
           if (liveTest.state.result != Result.success &&
               _runCount < liveTest.test.metadata.retry + 1) {
-            _controller.message(Message.print('Retry: ${liveTest.test.name}'));
+            _controller.message(Message.print("Retry: ${liveTest.test.name}"));
             _onRun();
             return;
           }
diff --git a/test_api/lib/src/backend/live_test.dart b/test_api/lib/src/backend/live_test.dart
index 875b4d6..ca5a260 100644
--- a/test_api/lib/src/backend/live_test.dart
+++ b/test_api/lib/src/backend/live_test.dart
@@ -109,7 +109,7 @@
 
     // The test will have the same name as the group for virtual tests created
     // to represent skipping the entire group.
-    if (test.name.length == group.name.length) return '';
+    if (test.name.length == group.name.length) return "";
 
     return test.name.substring(group.name.length + 1);
   }
diff --git a/test_api/lib/src/backend/live_test_controller.dart b/test_api/lib/src/backend/live_test_controller.dart
index 0aad846..c8a6671 100644
--- a/test_api/lib/src/backend/live_test_controller.dart
+++ b/test_api/lib/src/backend/live_test_controller.dart
@@ -18,38 +18,27 @@
 class _LiveTest extends LiveTest {
   final LiveTestController _controller;
 
-  @override
   Suite get suite => _controller._suite;
 
-  @override
   List<Group> get groups => _controller._groups;
 
-  @override
   Test get test => _controller._test;
 
-  @override
   State get state => _controller._state;
 
-  @override
   Stream<State> get onStateChange =>
       _controller._onStateChangeController.stream;
 
-  @override
   List<AsyncError> get errors => UnmodifiableListView(_controller._errors);
 
-  @override
   Stream<AsyncError> get onError => _controller._onErrorController.stream;
 
-  @override
   Stream<Message> get onMessage => _controller._onMessageController.stream;
 
-  @override
   Future<void> get onComplete => _controller.onComplete;
 
-  @override
   Future<void> run() => _controller._run();
 
-  @override
   Future<void> close() => _controller._close();
 
   _LiveTest(this._controller);
@@ -87,7 +76,7 @@
   final Function _onClose;
 
   /// The list of errors caught by the test.
-  final _errors = <AsyncError>[];
+  final _errors = List<AsyncError>();
 
   /// The current state of the test.
   var _state = const State(Status.pending, Result.success);
@@ -136,8 +125,7 @@
   ///
   /// If [groups] is passed, it's used to populate the list of groups that
   /// contain this test. Otherwise, `suite.group` is used.
-  LiveTestController(
-      Suite suite, this._test, void Function() onRun, void Function() onClose,
+  LiveTestController(Suite suite, this._test, void onRun(), void onClose(),
       {Iterable<Group> groups})
       : _suite = suite,
         _onRun = onRun,
@@ -187,10 +175,10 @@
   /// [LiveTest.run].
   Future<void> _run() {
     if (_runCalled) {
-      throw StateError('LiveTest.run() may not be called more than once.');
+      throw StateError("LiveTest.run() may not be called more than once.");
     } else if (_isClosed) {
-      throw StateError('LiveTest.run() may not be called for a closed '
-          'test.');
+      throw StateError("LiveTest.run() may not be called for a closed "
+          "test.");
     }
     _runCalled = true;
 
diff --git a/test_api/lib/src/backend/message.dart b/test_api/lib/src/backend/message.dart
index efe152f..3c24c52 100644
--- a/test_api/lib/src/backend/message.dart
+++ b/test_api/lib/src/backend/message.dart
@@ -20,19 +20,19 @@
 
 class MessageType {
   /// A message explicitly printed by the user's test.
-  static const print = MessageType._('print');
+  static const print = MessageType._("print");
 
   /// A message indicating that a test, or some portion of one, was skipped.
-  static const skip = MessageType._('skip');
+  static const skip = MessageType._("skip");
 
   /// The name of the message type.
   final String name;
 
   factory MessageType.parse(String name) {
     switch (name) {
-      case 'print':
+      case "print":
         return MessageType.print;
-      case 'skip':
+      case "skip":
         return MessageType.skip;
       default:
         throw ArgumentError('Invalid message type "$name".');
@@ -41,6 +41,5 @@
 
   const MessageType._(this.name);
 
-  @override
   String toString() => name;
 }
diff --git a/test_api/lib/src/backend/metadata.dart b/test_api/lib/src/backend/metadata.dart
index a020abc..fc4dbba 100644
--- a/test_api/lib/src/backend/metadata.dart
+++ b/test_api/lib/src/backend/metadata.dart
@@ -100,7 +100,7 @@
                 '"$platform".');
           }
 
-          skip = metadatum.reason ?? true;
+          skip = metadatum.reason == null ? true : metadatum.reason;
         } else {
           throw ArgumentError('Metadata for platform "$platform" must be a '
               'Timeout, Skip, or List of those; was "$metadata".');
@@ -116,15 +116,15 @@
   ///
   /// Throws an [ArgumentError] if [tags] is not a [String] or an [Iterable].
   static Set<String> _parseTags(tags) {
-    if (tags == null) return {};
-    if (tags is String) return {tags};
+    if (tags == null) return Set();
+    if (tags is String) return Set.from([tags]);
     if (tags is! Iterable) {
       throw ArgumentError.value(
-          tags, 'tags', 'must be either a String or an Iterable.');
+          tags, "tags", "must be either a String or an Iterable.");
     }
 
     if ((tags as Iterable).any((tag) => tag is! String)) {
-      throw ArgumentError.value(tags, 'tags', 'must contain only Strings.');
+      throw ArgumentError.value(tags, "tags", "must contain only Strings.");
     }
 
     return Set.from(tags as Iterable);
@@ -149,7 +149,7 @@
       Map<PlatformSelector, Metadata> onPlatform,
       Map<BooleanSelector, Metadata> forTag}) {
     // Returns metadata without forTag resolved at all.
-    Metadata _unresolved() => Metadata._(
+    _unresolved() => Metadata._(
         testOn: testOn,
         timeout: timeout,
         skip: skip,
@@ -172,7 +172,7 @@
     // doing it for every test individually.
     var empty = Metadata._();
     var merged = forTag.keys.toList().fold(empty, (Metadata merged, selector) {
-      if (!selector.evaluate(tags.contains)) return merged;
+      if (!selector.evaluate(tags)) return merged;
       return merged.merge(forTag.remove(selector));
     });
 
@@ -194,17 +194,17 @@
       Iterable<String> tags,
       Map<PlatformSelector, Metadata> onPlatform,
       Map<BooleanSelector, Metadata> forTag})
-      : testOn = testOn ?? PlatformSelector.all,
-        timeout = timeout ?? const Timeout.factor(1),
+      : testOn = testOn == null ? PlatformSelector.all : testOn,
+        timeout = timeout == null ? const Timeout.factor(1) : timeout,
         _skip = skip,
         _verboseTrace = verboseTrace,
         _chainStackTraces = chainStackTraces,
         _retry = retry,
-        tags = UnmodifiableSetView(tags == null ? {} : tags.toSet()),
+        tags = UnmodifiableSetView(tags == null ? Set() : tags.toSet()),
         onPlatform =
             onPlatform == null ? const {} : UnmodifiableMapView(onPlatform),
         forTag = forTag == null ? const {} : UnmodifiableMapView(forTag) {
-    if (retry != null) RangeError.checkNotNegative(retry, 'retry');
+    if (retry != null) RangeError.checkNotNegative(retry, "retry");
     _validateTags();
   }
 
@@ -224,7 +224,7 @@
       : testOn = testOn == null
             ? PlatformSelector.all
             : PlatformSelector.parse(testOn),
-        timeout = timeout ?? const Timeout.factor(1),
+        timeout = timeout == null ? const Timeout.factor(1) : timeout,
         _skip = skip == null ? null : skip != false,
         _verboseTrace = verboseTrace,
         _chainStackTraces = chainStackTraces,
@@ -237,7 +237,7 @@
       throw ArgumentError('"skip" must be a String or a bool, was "$skip".');
     }
 
-    if (retry != null) RangeError.checkNotNegative(retry, 'retry');
+    if (retry != null) RangeError.checkNotNegative(retry, "retry");
 
     _validateTags();
   }
@@ -254,11 +254,9 @@
         _chainStackTraces = serialized['chainStackTraces'] as bool,
         _retry = serialized['retry'] as int,
         tags = Set.from(serialized['tags'] as Iterable),
-        onPlatform = {
-          for (var pair in serialized['onPlatform'])
-            PlatformSelector.parse(pair.first as String):
-                Metadata.deserialize(pair.last)
-        },
+        onPlatform = Map.fromIterable(serialized['onPlatform'] as Iterable,
+            key: (pair) => PlatformSelector.parse(pair.first as String),
+            value: (pair) => Metadata.deserialize(pair.last)),
         forTag = (serialized['forTag'] as Map).map((key, nested) => MapEntry(
             BooleanSelector.parse(key as String),
             Metadata.deserialize(nested)));
@@ -282,8 +280,8 @@
     if (invalidTags.isEmpty) return;
 
     throw ArgumentError("Invalid ${pluralize('tag', invalidTags.length)} "
-        '${toSentence(invalidTags)}. Tags must be (optionally hyphenated) '
-        'Dart identifiers.');
+        "${toSentence(invalidTags)}. Tags must be (optionally hyphenated) "
+        "Dart identifiers.");
   }
 
   /// Throws a [FormatException] if any [PlatformSelector]s use any variables
@@ -330,10 +328,10 @@
       Map<BooleanSelector, Metadata> forTag}) {
     testOn ??= this.testOn;
     timeout ??= this.timeout;
-    skip ??= _skip;
-    verboseTrace ??= _verboseTrace;
-    chainStackTraces ??= _chainStackTraces;
-    retry ??= _retry;
+    skip ??= this._skip;
+    verboseTrace ??= this._verboseTrace;
+    chainStackTraces ??= this._chainStackTraces;
+    retry ??= this._retry;
     skipReason ??= this.skipReason;
     onPlatform ??= this.onPlatform;
     tags ??= this.tags;
@@ -365,8 +363,8 @@
   }
 
   /// Serializes [this] into a JSON-safe object that can be deserialized using
-  /// [Metadata.deserialize].
-  Map<String, dynamic> serialize() {
+  /// [new Metadata.deserialize].
+  serialize() {
     // Make this a list to guarantee that the order is preserved.
     var serializedOnPlatform = [];
     onPlatform.forEach((key, value) {
@@ -389,7 +387,7 @@
   }
 
   /// Serializes timeout into a JSON-safe object.
-  dynamic _serializeTimeout(Timeout timeout) {
+  _serializeTimeout(Timeout timeout) {
     if (timeout == Timeout.none) return 'none';
     return {
       'duration':
diff --git a/test_api/lib/src/backend/operating_system.dart b/test_api/lib/src/backend/operating_system.dart
index f2b7639..b914e64 100644
--- a/test_api/lib/src/backend/operating_system.dart
+++ b/test_api/lib/src/backend/operating_system.dart
@@ -9,31 +9,31 @@
 /// running the test runner.
 class OperatingSystem {
   /// Microsoft Windows.
-  static const windows = OperatingSystem._('Windows', 'windows');
+  static const windows = OperatingSystem._("Windows", "windows");
 
   /// Mac OS X.
-  static const macOS = OperatingSystem._('OS X', 'mac-os');
+  static const macOS = OperatingSystem._("OS X", "mac-os");
 
   /// GNU/Linux.
-  static const linux = OperatingSystem._('Linux', 'linux');
+  static const linux = OperatingSystem._("Linux", "linux");
 
   /// Android.
   ///
   /// Since this is the operating system the test runner is running on, this
   /// won't be true when testing remotely on an Android browser.
-  static const android = OperatingSystem._('Android', 'android');
+  static const android = OperatingSystem._("Android", "android");
 
   /// iOS.
   ///
   /// Since this is the operating system the test runner is running on, this
   /// won't be true when testing remotely on an iOS browser.
-  static const iOS = OperatingSystem._('iOS', 'ios');
+  static const iOS = OperatingSystem._("iOS", "ios");
 
   /// No operating system.
   ///
   /// This is used when running in the browser, or if an unrecognized operating
   /// system is used. It can't be referenced by name in platform selectors.
-  static const none = OperatingSystem._('none', 'none');
+  static const none = OperatingSystem._("none", "none");
 
   /// A list of all instances of [OperatingSystem] other than [none].
   static const all = [windows, macOS, linux, android, iOS];
@@ -51,15 +51,15 @@
   /// If no operating system is found, returns [none].
   static OperatingSystem findByIoName(String name) {
     switch (name) {
-      case 'windows':
+      case "windows":
         return windows;
-      case 'macos':
+      case "macos":
         return macOS;
-      case 'linux':
+      case "linux":
         return linux;
-      case 'android':
+      case "android":
         return android;
-      case 'ios':
+      case "ios":
         return iOS;
       default:
         return none;
@@ -77,6 +77,5 @@
 
   const OperatingSystem._(this.name, this.identifier);
 
-  @override
   String toString() => name;
 }
diff --git a/test_api/lib/src/backend/platform_selector.dart b/test_api/lib/src/backend/platform_selector.dart
index f2ab5a9..8fd956f 100644
--- a/test_api/lib/src/backend/platform_selector.dart
+++ b/test_api/lib/src/backend/platform_selector.dart
@@ -10,16 +10,10 @@
 import 'suite_platform.dart';
 
 /// The set of variable names that are valid for all platform selectors.
-final _universalValidVariables = {
-  'posix',
-  'dart-vm',
-  'browser',
-  'js',
-  'blink',
-  'google',
-  for (var runtime in Runtime.builtIn) runtime.identifier,
-  for (var os in OperatingSystem.all) os.identifier,
-};
+final _universalValidVariables =
+    Set<String>.from(["posix", "dart-vm", "browser", "js", "blink", "google"])
+      ..addAll(Runtime.builtIn.map((runtime) => runtime.identifier))
+      ..addAll(OperatingSystem.all.map((os) => os.identifier));
 
 /// An expression for selecting certain platforms, including operating systems
 /// and browsers.
@@ -52,7 +46,7 @@
   /// [SourceSpanFormatException] using [span].
   ///
   /// If [span] is `null`, runs [body] as-is.
-  static T _wrapFormatException<T>(T Function() body, SourceSpan span) {
+  static T _wrapFormatException<T>(T body(), SourceSpan span) {
     if (span == null) return body();
 
     try {
@@ -84,17 +78,17 @@
       if (variable == platform.runtime.parent?.identifier) return true;
       if (variable == platform.os.identifier) return true;
       switch (variable) {
-        case 'dart-vm':
+        case "dart-vm":
           return platform.runtime.isDartVM;
-        case 'browser':
+        case "browser":
           return platform.runtime.isBrowser;
-        case 'js':
+        case "js":
           return platform.runtime.isJS;
-        case 'blink':
+        case "blink":
           return platform.runtime.isBlink;
-        case 'posix':
+        case "posix":
           return platform.os.isPosix;
-        case 'google':
+        case "google":
           return platform.inGoogle;
         default:
           return false;
@@ -109,13 +103,10 @@
     return PlatformSelector._(_inner.intersection(other._inner));
   }
 
-  @override
   String toString() => _inner.toString();
 
-  @override
   bool operator ==(other) =>
       other is PlatformSelector && _inner == other._inner;
 
-  @override
   int get hashCode => _inner.hashCode;
 }
diff --git a/test_api/lib/src/backend/runtime.dart b/test_api/lib/src/backend/runtime.dart
index 380be9c..4236a8f 100644
--- a/test_api/lib/src/backend/runtime.dart
+++ b/test_api/lib/src/backend/runtime.dart
@@ -8,30 +8,30 @@
   // variable tests in test/backend/platform_selector/evaluate_test.
 
   /// The command-line Dart VM.
-  static const Runtime vm = Runtime('VM', 'vm', isDartVM: true);
+  static const Runtime vm = Runtime("VM", "vm", isDartVM: true);
 
   /// Google Chrome.
   static const Runtime chrome =
-      Runtime('Chrome', 'chrome', isBrowser: true, isJS: true, isBlink: true);
+      Runtime("Chrome", "chrome", isBrowser: true, isJS: true, isBlink: true);
 
   /// PhantomJS.
-  static const Runtime phantomJS = Runtime('PhantomJS', 'phantomjs',
+  static const Runtime phantomJS = Runtime("PhantomJS", "phantomjs",
       isBrowser: true, isJS: true, isBlink: true, isHeadless: true);
 
   /// Mozilla Firefox.
   static const Runtime firefox =
-      Runtime('Firefox', 'firefox', isBrowser: true, isJS: true);
+      Runtime("Firefox", "firefox", isBrowser: true, isJS: true);
 
   /// Apple Safari.
   static const Runtime safari =
-      Runtime('Safari', 'safari', isBrowser: true, isJS: true);
+      Runtime("Safari", "safari", isBrowser: true, isJS: true);
 
   /// Microsoft Internet Explorer.
   static const Runtime internetExplorer =
-      Runtime('Internet Explorer', 'ie', isBrowser: true, isJS: true);
+      Runtime("Internet Explorer", "ie", isBrowser: true, isJS: true);
 
   /// The command-line Node.js VM.
-  static const Runtime nodeJS = Runtime('Node.js', 'node', isJS: true);
+  static const Runtime nodeJS = Runtime("Node.js", "node", isJS: true);
 
   /// The platforms that are supported by the test runner by default.
   static const List<Runtime> builtIn = [
@@ -102,45 +102,45 @@
     }
 
     var map = serialized as Map;
-    var parent = map['parent'];
+    var parent = map["parent"];
     if (parent != null) {
       // Note that the returned platform's [parent] won't necessarily be `==` to
       // a separately-deserialized parent platform. This should be fine, though,
       // since we only deserialize platforms in the remote execution context
       // where they're only used to evaluate platform selectors.
-      return Runtime._child(map['name'] as String, map['identifier'] as String,
+      return Runtime._child(map["name"] as String, map["identifier"] as String,
           Runtime.deserialize(parent));
     }
 
-    return Runtime(map['name'] as String, map['identifier'] as String,
-        isDartVM: map['isDartVM'] as bool,
-        isBrowser: map['isBrowser'] as bool,
-        isJS: map['isJS'] as bool,
-        isBlink: map['isBlink'] as bool,
-        isHeadless: map['isHeadless'] as bool);
+    return Runtime(map["name"] as String, map["identifier"] as String,
+        isDartVM: map["isDartVM"] as bool,
+        isBrowser: map["isBrowser"] as bool,
+        isJS: map["isJS"] as bool,
+        isBlink: map["isBlink"] as bool,
+        isHeadless: map["isHeadless"] as bool);
   }
 
   /// Converts [this] into a JSON-safe object that can be converted back to a
-  /// [Runtime] using [Runtime.deserialize].
+  /// [Runtime] using [new Runtime.deserialize].
   Object serialize() {
     if (builtIn.contains(this)) return identifier;
 
     if (parent != null) {
       return {
-        'name': name,
-        'identifier': identifier,
-        'parent': parent.serialize()
+        "name": name,
+        "identifier": identifier,
+        "parent": parent.serialize()
       };
     }
 
     return {
-      'name': name,
-      'identifier': identifier,
-      'isDartVM': isDartVM,
-      'isBrowser': isBrowser,
-      'isJS': isJS,
-      'isBlink': isBlink,
-      'isHeadless': isHeadless
+      "name": name,
+      "identifier": identifier,
+      "isDartVM": isDartVM,
+      "isBrowser": isBrowser,
+      "isJS": isJS,
+      "isBlink": isBlink,
+      "isHeadless": isHeadless
     };
   }
 
@@ -150,9 +150,8 @@
   /// This may not be called on a platform that's already a child.
   Runtime extend(String name, String identifier) {
     if (parent == null) return Runtime._child(name, identifier, this);
-    throw StateError('A child platform may not be extended.');
+    throw StateError("A child platform may not be extended.");
   }
 
-  @override
   String toString() => name;
 }
diff --git a/test_api/lib/src/backend/stack_trace_formatter.dart b/test_api/lib/src/backend/stack_trace_formatter.dart
index 2bb897e..62d5243 100644
--- a/test_api/lib/src/backend/stack_trace_formatter.dart
+++ b/test_api/lib/src/backend/stack_trace_formatter.dart
@@ -22,12 +22,12 @@
   /// as-is.
   StackTraceMapper _mapper;
 
-  /// The set of packages to fold when producing terse [Chain]s.
-  var _except = {'test', 'stream_channel', 'test_api'};
+  /// The list of packages to fold when producing terse [Chain]s.
+  var _except = Set<String>.from(['test', 'stream_channel', 'test_api']);
 
   /// If non-empty, all packages not in this list will be folded when producing
   /// terse [Chain]s.
-  var _only = <String>{};
+  var _only = Set<String>();
 
   /// Returns the current manager, or `null` if this isn't called within a call
   /// to [asCurrent].
@@ -38,8 +38,7 @@
   ///
   /// This is zone-scoped, so [this] will be the current configuration in any
   /// asynchronous callbacks transitively created by [body].
-  T asCurrent<T>(T Function() body) =>
-      runZoned(body, zoneValues: {_currentKey: this});
+  T asCurrent<T>(T body()) => runZoned(body, zoneValues: {_currentKey: this});
 
   /// Configure how stack traces are formatted.
   ///
diff --git a/test_api/lib/src/backend/state.dart b/test_api/lib/src/backend/state.dart
index ba82cd0..3151c1b 100644
--- a/test_api/lib/src/backend/state.dart
+++ b/test_api/lib/src/backend/state.dart
@@ -27,29 +27,26 @@
 
   const State(this.status, this.result);
 
-  @override
   bool operator ==(other) =>
       other is State && status == other.status && result == other.result;
 
-  @override
   int get hashCode => status.hashCode ^ (7 * result.hashCode);
 
-  @override
   String toString() {
-    if (status == Status.pending) return 'pending';
+    if (status == Status.pending) return "pending";
     if (status == Status.complete) return result.toString();
-    if (result == Result.success) return 'running';
-    return 'running with $result';
+    if (result == Result.success) return "running";
+    return "running with $result";
   }
 }
 
 /// Where the test is in its process of running.
 class Status {
   /// The test has not yet begun running.
-  static const pending = Status._('pending');
+  static const pending = Status._("pending");
 
   /// The test is currently running.
-  static const running = Status._('running');
+  static const running = Status._("running");
 
   /// The test has finished running.
   ///
@@ -58,18 +55,18 @@
   /// first error or when all [expectAsync] callbacks have been called and any
   /// returned [Future] has completed, but it's possible for further processing
   /// to happen, which may cause further errors.
-  static const complete = Status._('complete');
+  static const complete = Status._("complete");
 
   /// The name of the status.
   final String name;
 
   factory Status.parse(String name) {
     switch (name) {
-      case 'pending':
+      case "pending":
         return Status.pending;
-      case 'running':
+      case "running":
         return Status.running;
-      case 'complete':
+      case "complete":
         return Status.complete;
       default:
         throw ArgumentError('Invalid status name "$name".');
@@ -78,7 +75,6 @@
 
   const Status._(this.name);
 
-  @override
   String toString() => name;
 }
 
@@ -87,24 +83,24 @@
   /// The test has not yet failed in any way.
   ///
   /// Note that this doesn't mean that the test won't fail in the future.
-  static const success = Result._('success');
+  static const success = Result._("success");
 
   /// The test, or some part of it, has been skipped.
   ///
   /// This implies that the test hasn't failed *yet*. However, it this doesn't
   /// mean that the test won't fail in the future.
-  static const skipped = Result._('skipped');
+  static const skipped = Result._("skipped");
 
   /// The test has failed.
   ///
   /// A failure is specifically caused by a [TestFailure] being thrown; any
   /// other exception causes an error.
-  static const failure = Result._('failure');
+  static const failure = Result._("failure");
 
   /// The test has crashed.
   ///
   /// Any exception other than a [TestFailure] is considered to be an error.
-  static const error = Result._('error');
+  static const error = Result._("error");
 
   /// The name of the result.
   final String name;
@@ -123,13 +119,13 @@
 
   factory Result.parse(String name) {
     switch (name) {
-      case 'success':
+      case "success":
         return Result.success;
-      case 'skipped':
+      case "skipped":
         return Result.skipped;
-      case 'failure':
+      case "failure":
         return Result.failure;
-      case 'error':
+      case "error":
         return Result.error;
       default:
         throw ArgumentError('Invalid result name "$name".');
@@ -138,6 +134,5 @@
 
   const Result._(this.name);
 
-  @override
   String toString() => name;
 }
diff --git a/test_api/lib/src/backend/suite.dart b/test_api/lib/src/backend/suite.dart
index 695d305..2a1a13f 100644
--- a/test_api/lib/src/backend/suite.dart
+++ b/test_api/lib/src/backend/suite.dart
@@ -48,9 +48,9 @@
   ///
   /// Unlike [GroupEntry.filter], this never returns `null`. If all entries are
   /// filtered out, it returns an empty suite.
-  Suite filter(bool Function(Test) callback) {
+  Suite filter(bool callback(Test test)) {
     var filtered = group.filter(callback);
-    filtered ??= Group.root([], metadata: metadata);
+    if (filtered == null) filtered = Group.root([], metadata: metadata);
     return Suite(filtered, platform, path: path);
   }
 
diff --git a/test_api/lib/src/backend/suite_platform.dart b/test_api/lib/src/backend/suite_platform.dart
index 5f80997..1f049eb 100644
--- a/test_api/lib/src/backend/suite_platform.dart
+++ b/test_api/lib/src/backend/suite_platform.dart
@@ -41,7 +41,7 @@
   }
 
   /// Converts [this] into a JSON-safe object that can be converted back to a
-  /// [SuitePlatform] using [SuitePlatform.deserialize].
+  /// [SuitePlatform] using [new SuitePlatform.deserialize].
   Object serialize() => {
         'runtime': runtime.serialize(),
         'os': os.identifier,
diff --git a/test_api/lib/src/backend/test.dart b/test_api/lib/src/backend/test.dart
index 870f5e1..07df8c3 100644
--- a/test_api/lib/src/backend/test.dart
+++ b/test_api/lib/src/backend/test.dart
@@ -17,13 +17,10 @@
 /// directly. To run one, load a live version using [Test.load] and run it using
 /// [LiveTest.run].
 abstract class Test implements GroupEntry {
-  @override
   String get name;
 
-  @override
   Metadata get metadata;
 
-  @override
   Trace get trace;
 
   /// Loads a live version of this test, which can be used to run it a single
@@ -34,9 +31,7 @@
   /// defaults to just containing `suite.group`.
   LiveTest load(Suite suite, {Iterable<Group> groups});
 
-  @override
   Test forPlatform(SuitePlatform platform);
 
-  @override
-  Test filter(bool Function(Test) callback) => callback(this) ? this : null;
+  Test filter(bool callback(Test test)) => callback(this) ? this : null;
 }
diff --git a/test_api/lib/src/frontend/async_matcher.dart b/test_api/lib/src/frontend/async_matcher.dart
index e956aec..e2c544e 100644
--- a/test_api/lib/src/frontend/async_matcher.dart
+++ b/test_api/lib/src/frontend/async_matcher.dart
@@ -27,14 +27,13 @@
   ///
   /// If this returns a [String] synchronously, [expect] will synchronously
   /// throw a [TestFailure] and [matches] will synchronusly return `false`.
-  dynamic /*FutureOr<String>*/ matchAsync(item);
+  /*FutureOr<String>*/ matchAsync(item);
 
-  @override
   bool matches(item, Map matchState) {
     var result = matchAsync(item);
     expect(result,
         anyOf([equals(null), TypeMatcher<Future>(), TypeMatcher<String>()]),
-        reason: 'matchAsync() may only return a String, a Future, or null.');
+        reason: "matchAsync() may only return a String, a Future, or null.");
 
     if (result is Future) {
       Invoker.current.addOutstandingCallback();
@@ -52,7 +51,6 @@
     return true;
   }
 
-  @override
   Description describeMismatch(
           item, Description description, Map matchState, bool verbose) =>
       StringDescription(matchState[this] as String);
diff --git a/test_api/lib/src/frontend/expect.dart b/test_api/lib/src/frontend/expect.dart
index b69dc2f..7fcd503 100644
--- a/test_api/lib/src/frontend/expect.dart
+++ b/test_api/lib/src/frontend/expect.dart
@@ -18,13 +18,12 @@
 
   TestFailure(this.message);
 
-  @override
   String toString() => message;
 }
 
 /// The type used for functions that can be used to build up error reports
 /// upon failures in [expect].
-@Deprecated('Will be removed in 0.13.0.')
+@Deprecated("Will be removed in 0.13.0.")
 typedef ErrorFormatter = String Function(dynamic actual, Matcher matcher,
     String reason, Map matchState, bool verbose);
 
@@ -55,8 +54,8 @@
 void expect(actual, matcher,
     {String reason,
     skip,
-    @Deprecated('Will be removed in 0.13.0.') bool verbose = false,
-    @Deprecated('Will be removed in 0.13.0.') ErrorFormatter formatter}) {
+    @Deprecated("Will be removed in 0.13.0.") bool verbose = false,
+    @Deprecated("Will be removed in 0.13.0.") ErrorFormatter formatter}) {
   _expect(actual, matcher,
       reason: reason, skip: skip, verbose: verbose, formatter: formatter);
 }
@@ -87,25 +86,25 @@
   };
 
   if (Invoker.current == null) {
-    throw StateError('expect() may only be called within a test.');
+    throw StateError("expect() may only be called within a test.");
   }
 
   if (Invoker.current.closed) throw ClosedException();
 
   if (skip != null && skip is! bool && skip is! String) {
-    throw ArgumentError.value(skip, 'skip', 'must be a bool or a String');
+    throw ArgumentError.value(skip, "skip", "must be a bool or a String");
   }
 
   matcher = wrapMatcher(matcher);
   if (skip != null && skip != false) {
     String message;
     if (skip is String) {
-      message = 'Skip expect: $skip';
+      message = "Skip expect: $skip";
     } else if (reason != null) {
-      message = 'Skip expect ($reason).';
+      message = "Skip expect ($reason).";
     } else {
       var description = StringDescription().addDescriptionOf(matcher);
-      message = 'Skip expect ($description).';
+      message = "Skip expect ($description).";
     }
 
     Invoker.current.skip(message);
@@ -117,7 +116,7 @@
     var result = matcher.matchAsync(actual);
     expect(result,
         anyOf([equals(null), TypeMatcher<Future>(), TypeMatcher<String>()]),
-        reason: 'matchAsync() may only return a String, a Future, or null.');
+        reason: "matchAsync() may only return a String, a Future, or null.");
 
     if (result is String) {
       fail(formatFailure(matcher as Matcher, actual, result, reason: reason));
@@ -154,7 +153,7 @@
 Null fail(String message) => throw TestFailure(message);
 
 // The default error formatter.
-@Deprecated('Will be removed in 0.13.0.')
+@Deprecated("Will be removed in 0.13.0.")
 String formatFailure(Matcher expected, actual, String which, {String reason}) {
   var buffer = StringBuffer();
   buffer.writeln(indent(prettyPrint(expected), first: 'Expected: '));
diff --git a/test_api/lib/src/frontend/expect_async.dart b/test_api/lib/src/frontend/expect_async.dart
index 10abf03..07d408c 100644
--- a/test_api/lib/src/frontend/expect_async.dart
+++ b/test_api/lib/src/frontend/expect_async.dart
@@ -18,6 +18,20 @@
 typedef Func5<T, A, B, C, D, E> = T Function([A a, B b, C c, D d, E e]);
 typedef Func6<T, A, B, C, D, E, F> = T Function([A a, B b, C c, D d, E e, F f]);
 
+// Functions used to check how many arguments a callback takes. We can't use the
+// previous functions for this, because (a) {} is not a subtype of
+// ([dynamic]) -> dynamic.
+
+typedef _Func0 = Function();
+typedef _Func1 = Function(Null a);
+typedef _Func2 = Function(Null a, Null b);
+typedef _Func3 = Function(Null a, Null b, Null c);
+typedef _Func4 = Function(Null a, Null b, Null c, Null d);
+typedef _Func5 = Function(Null a, Null b, Null c, Null d, Null e);
+typedef _Func6 = Function(Null a, Null b, Null c, Null d, Null e, Null f);
+
+typedef _IsDoneCallback = bool Function();
+
 /// A wrapper for a function that ensures that it's called the appropriate
 /// number of times.
 ///
@@ -48,7 +62,7 @@
   ///
   /// This may be `null`. If so, the function is considered to be done after
   /// it's been run once.
-  final bool Function() _isDone;
+  final _IsDoneCallback _isDone;
 
   /// A descriptive name for the function.
   final String _id;
@@ -77,20 +91,20 @@
   /// as a reason it's expected to be called. If [isDone] is passed, the test
   /// won't be allowed to complete until it returns `true`.
   _ExpectedFunction(Function callback, int minExpected, int maxExpected,
-      {String id, String reason, bool Function() isDone})
-      : _callback = callback,
+      {String id, String reason, bool isDone()})
+      : this._callback = callback,
         _minExpectedCalls = minExpected,
         _maxExpectedCalls =
             (maxExpected == 0 && minExpected > 0) ? minExpected : maxExpected,
-        _isDone = isDone,
-        _reason = reason == null ? '' : '\n$reason',
-        _zone = Zone.current,
-        _id = _makeCallbackId(id, callback) {
+        this._isDone = isDone,
+        this._reason = reason == null ? '' : '\n$reason',
+        this._zone = Zone.current,
+        this._id = _makeCallbackId(id, callback) {
     if (_invoker == null) {
-      throw StateError('[expectAsync] was called outside of a test.');
+      throw StateError("[expectAsync] was called outside of a test.");
     } else if (maxExpected > 0 && minExpected > maxExpected) {
-      throw ArgumentError('max ($maxExpected) may not be less than count '
-          '($minExpected).');
+      throw ArgumentError("max ($maxExpected) may not be less than count "
+          "($minExpected).");
     }
 
     if (isDone != null || minExpected > 0) {
@@ -106,7 +120,7 @@
   /// If [id] is passed, uses that. Otherwise, tries to determine a name from
   /// calling `toString`. If no name can be found, returns the empty string.
   static String _makeCallbackId(String id, Function callback) {
-    if (id != null) return '$id ';
+    if (id != null) return "$id ";
 
     // If the callback is not an anonymous closure, try to get the
     // name.
@@ -118,19 +132,19 @@
     start += prefix.length;
     var end = toString.indexOf("'", start);
     if (end == -1) return '';
-    return '${toString.substring(start, end)} ';
+    return "${toString.substring(start, end)} ";
   }
 
   /// Returns a function that has the same number of positional arguments as the
   /// wrapped function (up to a total of 6).
   Function get func {
-    if (_callback is Function(Null, Null, Null, Null, Null Nulll)) return max6;
-    if (_callback is Function(Null, Null, Null, Null, Null)) return max5;
-    if (_callback is Function(Null, Null, Null, Null)) return max4;
-    if (_callback is Function(Null, Null, Null)) return max3;
-    if (_callback is Function(Null, Null)) return max2;
-    if (_callback is Function(Null)) return max1;
-    if (_callback is Function()) return max0;
+    if (_callback is _Func6) return max6;
+    if (_callback is _Func5) return max5;
+    if (_callback is _Func4) return max4;
+    if (_callback is _Func3) return max3;
+    if (_callback is _Func2) return max2;
+    if (_callback is _Func1) return max1;
+    if (_callback is _Func0) return max0;
 
     _invoker.removeOutstandingCallback();
     throw ArgumentError(
@@ -213,11 +227,11 @@
 /// Use [expectAsync0], [expectAsync1],
 /// [expectAsync2], [expectAsync3], [expectAsync4], [expectAsync5], or
 /// [expectAsync6] instead.
-@Deprecated('Will be removed in 0.13.0')
+@Deprecated("Will be removed in 0.13.0")
 Function expectAsync(Function callback,
     {int count = 1, int max = 0, String id, String reason}) {
   if (Invoker.current == null) {
-    throw StateError('expectAsync() may only be called within a test.');
+    throw StateError("expectAsync() may only be called within a test.");
   }
 
   return _ExpectedFunction(callback, count, max, id: id, reason: reason).func;
@@ -244,10 +258,10 @@
 /// This method takes callbacks with zero arguments. See also
 /// [expectAsync1], [expectAsync2], [expectAsync3], [expectAsync4],
 /// [expectAsync5], and [expectAsync6] for callbacks with different arity.
-Func0<T> expectAsync0<T>(T Function() callback,
+Func0<T> expectAsync0<T>(T callback(),
     {int count = 1, int max = 0, String id, String reason}) {
   if (Invoker.current == null) {
-    throw StateError('expectAsync0() may only be called within a test.');
+    throw StateError("expectAsync0() may only be called within a test.");
   }
 
   return _ExpectedFunction<T>(callback, count, max, id: id, reason: reason)
@@ -275,10 +289,10 @@
 /// This method takes callbacks with one argument. See also
 /// [expectAsync0], [expectAsync2], [expectAsync3], [expectAsync4],
 /// [expectAsync5], and [expectAsync6] for callbacks with different arity.
-Func1<T, A> expectAsync1<T, A>(T Function(A) callback,
+Func1<T, A> expectAsync1<T, A>(T callback(A a),
     {int count = 1, int max = 0, String id, String reason}) {
   if (Invoker.current == null) {
-    throw StateError('expectAsync1() may only be called within a test.');
+    throw StateError("expectAsync1() may only be called within a test.");
   }
 
   return _ExpectedFunction<T>(callback, count, max, id: id, reason: reason)
@@ -306,10 +320,10 @@
 /// This method takes callbacks with two arguments. See also
 /// [expectAsync0], [expectAsync1], [expectAsync3], [expectAsync4],
 /// [expectAsync5], and [expectAsync6] for callbacks with different arity.
-Func2<T, A, B> expectAsync2<T, A, B>(T Function(A, B) callback,
+Func2<T, A, B> expectAsync2<T, A, B>(T callback(A a, B b),
     {int count = 1, int max = 0, String id, String reason}) {
   if (Invoker.current == null) {
-    throw StateError('expectAsync2() may only be called within a test.');
+    throw StateError("expectAsync2() may only be called within a test.");
   }
 
   return _ExpectedFunction<T>(callback, count, max, id: id, reason: reason)
@@ -337,10 +351,10 @@
 /// This method takes callbacks with three arguments. See also
 /// [expectAsync0], [expectAsync1], [expectAsync2], [expectAsync4],
 /// [expectAsync5], and [expectAsync6] for callbacks with different arity.
-Func3<T, A, B, C> expectAsync3<T, A, B, C>(T Function(A, B, C) callback,
+Func3<T, A, B, C> expectAsync3<T, A, B, C>(T callback(A a, B b, C c),
     {int count = 1, int max = 0, String id, String reason}) {
   if (Invoker.current == null) {
-    throw StateError('expectAsync3() may only be called within a test.');
+    throw StateError("expectAsync3() may only be called within a test.");
   }
 
   return _ExpectedFunction<T>(callback, count, max, id: id, reason: reason)
@@ -368,14 +382,10 @@
 /// This method takes callbacks with four arguments. See also
 /// [expectAsync0], [expectAsync1], [expectAsync2], [expectAsync3],
 /// [expectAsync5], and [expectAsync6] for callbacks with different arity.
-Func4<T, A, B, C, D> expectAsync4<T, A, B, C, D>(
-    T Function(A, B, C, D) callback,
-    {int count = 1,
-    int max = 0,
-    String id,
-    String reason}) {
+Func4<T, A, B, C, D> expectAsync4<T, A, B, C, D>(T callback(A a, B b, C c, D d),
+    {int count = 1, int max = 0, String id, String reason}) {
   if (Invoker.current == null) {
-    throw StateError('expectAsync4() may only be called within a test.');
+    throw StateError("expectAsync4() may only be called within a test.");
   }
 
   return _ExpectedFunction<T>(callback, count, max, id: id, reason: reason)
@@ -404,13 +414,13 @@
 /// [expectAsync0], [expectAsync1], [expectAsync2], [expectAsync3],
 /// [expectAsync4], and [expectAsync6] for callbacks with different arity.
 Func5<T, A, B, C, D, E> expectAsync5<T, A, B, C, D, E>(
-    T Function(A, B, C, D, E) callback,
+    T callback(A a, B b, C c, D d, E e),
     {int count = 1,
     int max = 0,
     String id,
     String reason}) {
   if (Invoker.current == null) {
-    throw StateError('expectAsync5() may only be called within a test.');
+    throw StateError("expectAsync5() may only be called within a test.");
   }
 
   return _ExpectedFunction<T>(callback, count, max, id: id, reason: reason)
@@ -439,13 +449,13 @@
 /// [expectAsync0], [expectAsync1], [expectAsync2], [expectAsync3],
 /// [expectAsync4], and [expectAsync5] for callbacks with different arity.
 Func6<T, A, B, C, D, E, F> expectAsync6<T, A, B, C, D, E, F>(
-    T Function(A, B, C, D, E, F) callback,
+    T callback(A a, B b, C c, D d, E e, F f),
     {int count = 1,
     int max = 0,
     String id,
     String reason}) {
   if (Invoker.current == null) {
-    throw StateError('expectAsync6() may only be called within a test.');
+    throw StateError("expectAsync6() may only be called within a test.");
   }
 
   return _ExpectedFunction<T>(callback, count, max, id: id, reason: reason)
@@ -456,11 +466,11 @@
 /// Use [expectAsyncUntil0], [expectAsyncUntil1],
 /// [expectAsyncUntil2], [expectAsyncUntil3], [expectAsyncUntil4],
 /// [expectAsyncUntil5], or [expectAsyncUntil6] instead.
-@Deprecated('Will be removed in 0.13.0')
-Function expectAsyncUntil(Function callback, bool Function() isDone,
+@Deprecated("Will be removed in 0.13.0")
+Function expectAsyncUntil(Function callback, bool isDone(),
     {String id, String reason}) {
   if (Invoker.current == null) {
-    throw StateError('expectAsyncUntil() may only be called within a test.');
+    throw StateError("expectAsyncUntil() may only be called within a test.");
   }
 
   return _ExpectedFunction(callback, 0, -1,
@@ -485,10 +495,10 @@
 /// [expectAsyncUntil1], [expectAsyncUntil2], [expectAsyncUntil3],
 /// [expectAsyncUntil4], [expectAsyncUntil5], and [expectAsyncUntil6] for
 /// callbacks with different arity.
-Func0<T> expectAsyncUntil0<T>(T Function() callback, bool Function() isDone,
+Func0<T> expectAsyncUntil0<T>(T callback(), bool isDone(),
     {String id, String reason}) {
   if (Invoker.current == null) {
-    throw StateError('expectAsyncUntil0() may only be called within a test.');
+    throw StateError("expectAsyncUntil0() may only be called within a test.");
   }
 
   return _ExpectedFunction<T>(callback, 0, -1,
@@ -513,11 +523,10 @@
 /// [expectAsyncUntil0], [expectAsyncUntil2], [expectAsyncUntil3],
 /// [expectAsyncUntil4], [expectAsyncUntil5], and [expectAsyncUntil6] for
 /// callbacks with different arity.
-Func1<T, A> expectAsyncUntil1<T, A>(
-    T Function(A) callback, bool Function() isDone,
+Func1<T, A> expectAsyncUntil1<T, A>(T callback(A a), bool isDone(),
     {String id, String reason}) {
   if (Invoker.current == null) {
-    throw StateError('expectAsyncUntil1() may only be called within a test.');
+    throw StateError("expectAsyncUntil1() may only be called within a test.");
   }
 
   return _ExpectedFunction<T>(callback, 0, -1,
@@ -542,11 +551,10 @@
 /// [expectAsyncUntil0], [expectAsyncUntil1], [expectAsyncUntil3],
 /// [expectAsyncUntil4], [expectAsyncUntil5], and [expectAsyncUntil6] for
 /// callbacks with different arity.
-Func2<T, A, B> expectAsyncUntil2<T, A, B>(
-    T Function(A, B) callback, bool Function() isDone,
+Func2<T, A, B> expectAsyncUntil2<T, A, B>(T callback(A a, B b), bool isDone(),
     {String id, String reason}) {
   if (Invoker.current == null) {
-    throw StateError('expectAsyncUntil2() may only be called within a test.');
+    throw StateError("expectAsyncUntil2() may only be called within a test.");
   }
 
   return _ExpectedFunction<T>(callback, 0, -1,
@@ -572,10 +580,10 @@
 /// [expectAsyncUntil4], [expectAsyncUntil5], and [expectAsyncUntil6] for
 /// callbacks with different arity.
 Func3<T, A, B, C> expectAsyncUntil3<T, A, B, C>(
-    T Function(A, B, C) callback, bool Function() isDone,
+    T callback(A a, B b, C c), bool isDone(),
     {String id, String reason}) {
   if (Invoker.current == null) {
-    throw StateError('expectAsyncUntil3() may only be called within a test.');
+    throw StateError("expectAsyncUntil3() may only be called within a test.");
   }
 
   return _ExpectedFunction<T>(callback, 0, -1,
@@ -601,10 +609,10 @@
 /// [expectAsyncUntil3], [expectAsyncUntil5], and [expectAsyncUntil6] for
 /// callbacks with different arity.
 Func4<T, A, B, C, D> expectAsyncUntil4<T, A, B, C, D>(
-    T Function(A, B, C, D) callback, bool Function() isDone,
+    T callback(A a, B b, C c, D d), bool isDone(),
     {String id, String reason}) {
   if (Invoker.current == null) {
-    throw StateError('expectAsyncUntil4() may only be called within a test.');
+    throw StateError("expectAsyncUntil4() may only be called within a test.");
   }
 
   return _ExpectedFunction<T>(callback, 0, -1,
@@ -630,10 +638,10 @@
 /// [expectAsyncUntil3], [expectAsyncUntil4], and [expectAsyncUntil6] for
 /// callbacks with different arity.
 Func5<T, A, B, C, D, E> expectAsyncUntil5<T, A, B, C, D, E>(
-    T Function(A, B, C, D, E) callback, bool Function() isDone,
+    T callback(A a, B b, C c, D d, E e), bool isDone(),
     {String id, String reason}) {
   if (Invoker.current == null) {
-    throw StateError('expectAsyncUntil5() may only be called within a test.');
+    throw StateError("expectAsyncUntil5() may only be called within a test.");
   }
 
   return _ExpectedFunction<T>(callback, 0, -1,
@@ -659,10 +667,10 @@
 /// [expectAsyncUntil3], [expectAsyncUntil4], and [expectAsyncUntil5] for
 /// callbacks with different arity.
 Func6<T, A, B, C, D, E, F> expectAsyncUntil6<T, A, B, C, D, E, F>(
-    T Function(A, B, C, D, E, F) callback, bool Function() isDone,
+    T callback(A a, B b, C c, D d, E e, F f), bool isDone(),
     {String id, String reason}) {
   if (Invoker.current == null) {
-    throw StateError('expectAsyncUntil() may only be called within a test.');
+    throw StateError("expectAsyncUntil() may only be called within a test.");
   }
 
   return _ExpectedFunction<T>(callback, 0, -1,
diff --git a/test_api/lib/src/frontend/future_matchers.dart b/test_api/lib/src/frontend/future_matchers.dart
index 8117395..e9566c3 100644
--- a/test_api/lib/src/frontend/future_matchers.dart
+++ b/test_api/lib/src/frontend/future_matchers.dart
@@ -45,9 +45,8 @@
   const _Completes(this._matcher);
 
   // Avoid async/await so we synchronously start listening to [item].
-  @override
-  dynamic /*FutureOr<String>*/ matchAsync(item) {
-    if (item is! Future) return 'was not a Future';
+  /*FutureOr<String>*/ matchAsync(item) {
+    if (item is! Future) return "was not a Future";
 
     return item.then((value) async {
       if (_matcher == null) return null;
@@ -71,7 +70,6 @@
     });
   }
 
-  @override
   Description describe(Description description) {
     if (_matcher == null) {
       description.add('completes successfully');
@@ -92,13 +90,11 @@
 class _DoesNotComplete extends Matcher {
   const _DoesNotComplete();
 
-  @override
   Description describe(Description description) {
-    description.add('does not complete');
+    description.add("does not complete");
     return description;
   }
 
-  @override
   bool matches(item, Map matchState) {
     if (item is! Future) return false;
     item.then((value) {
@@ -109,10 +105,9 @@
     return true;
   }
 
-  @override
   Description describeMismatch(
       item, Description description, Map matchState, bool verbose) {
-    if (item is! Future) return description.add('$item is not a Future');
+    if (item is! Future) return description.add("$item is not a Future");
     return description;
   }
 }
diff --git a/test_api/lib/src/frontend/never_called.dart b/test_api/lib/src/frontend/never_called.dart
index 0ea5351..98bc314 100644
--- a/test_api/lib/src/frontend/never_called.dart
+++ b/test_api/lib/src/frontend/never_called.dart
@@ -57,10 +57,10 @@
 
     zone.handleUncaughtError(
         TestFailure(
-            'Callback should never have been called, but it was called with' +
+            "Callback should never have been called, but it was called with" +
                 (arguments.isEmpty
-                    ? ' no arguments.'
-                    : ':\n${bullet(arguments.map(prettyPrint))}')),
+                    ? " no arguments."
+                    : ":\n${bullet(arguments.map(prettyPrint))}")),
         zone.run(() => Chain.current()));
     return null;
   };
diff --git a/test_api/lib/src/frontend/prints_matcher.dart b/test_api/lib/src/frontend/prints_matcher.dart
index 48cf8a9..5f0380e 100644
--- a/test_api/lib/src/frontend/prints_matcher.dart
+++ b/test_api/lib/src/frontend/prints_matcher.dart
@@ -30,9 +30,8 @@
 
   // Avoid async/await so we synchronously fail if the function is
   // synchronous.
-  @override
-  dynamic /*FutureOr<String>*/ matchAsync(item) {
-    if (item is! Function()) return 'was not a unary Function';
+  /*FutureOr<String>*/ matchAsync(item) {
+    if (item is! Function()) return "was not a unary Function";
 
     var buffer = StringBuffer();
     var result = runZoned(item as Function(),
@@ -45,7 +44,6 @@
         : _check(buffer.toString());
   }
 
-  @override
   Description describe(Description description) =>
       description.add('prints ').addDescriptionOf(_matcher);
 
diff --git a/test_api/lib/src/frontend/spawn_hybrid.dart b/test_api/lib/src/frontend/spawn_hybrid.dart
index 1b0cde2..46c9d68 100644
--- a/test_api/lib/src/frontend/spawn_hybrid.dart
+++ b/test_api/lib/src/frontend/spawn_hybrid.dart
@@ -24,17 +24,17 @@
 // `hybridMain` can send any json encodeable type.
 final _transformer = StreamChannelTransformer<dynamic, dynamic>(
     StreamTransformer.fromHandlers(handleData: (message, sink) {
-  switch (message['type'] as String) {
-    case 'data':
-      sink.add(message['data']);
+  switch (message["type"] as String) {
+    case "data":
+      sink.add(message["data"]);
       break;
 
-    case 'print':
-      print(message['line']);
+    case "print":
+      print(message["line"]);
       break;
 
-    case 'error':
-      var error = RemoteException.deserialize(message['error']);
+    case "error":
+      var error = RemoteException.deserialize(message["error"]);
       sink.addError(error.error, error.stackTrace);
       break;
   }
@@ -98,12 +98,12 @@
   } else if (uri is String) {
     parsedUrl = Uri.parse(uri);
   } else {
-    throw ArgumentError.value(uri, 'uri', 'must be a Uri or a String.');
+    throw ArgumentError.value(uri, "uri", "must be a Uri or a String.");
   }
 
   String absoluteUri;
   if (parsedUrl.scheme.isEmpty) {
-    var isRootRelative = parsedUrl.path.startsWith('/');
+    var isRootRelative = parsedUrl.path.startsWith("/");
 
     // If we're running in a browser context, the working directory is already
     // relative to the test file, whereas on the VM the working directory is the
@@ -113,9 +113,9 @@
         // A root-relative URL is interpreted as relative to the package root,
         // which means placing it beneath the URL secret.
         var secret = Uri.encodeComponent(Uri.base.pathSegments[0]);
-        absoluteUri = p.absolute('/$secret$parsedUrl');
-        print('Uri.base: ${Uri.base}');
-        print('absoluteUri: ${absoluteUri}');
+        absoluteUri = p.absolute("/$secret$parsedUrl");
+        print("Uri.base: ${Uri.base}");
+        print("absoluteUri: ${absoluteUri}");
       } else {
         absoluteUri = p.absolute(parsedUrl.toString());
       }
@@ -192,6 +192,8 @@
 StreamChannel _spawn(String uri, Object message, {bool stayAlive = false}) {
   var channel = Zone.current[#test.runner.test_channel] as MultiChannel;
   if (channel == null) {
+    // TODO(nweiz): Link to an issue tracking support when running the test file
+    // directly.
     throw UnsupportedError("Can't connect to the test runner.\n"
         'spawnHybridUri() is currently only supported within "pub run test".');
   }
@@ -201,10 +203,10 @@
   var virtualChannel = channel.virtualChannel();
   StreamChannel isolateChannel = virtualChannel;
   channel.sink.add({
-    'type': 'spawn-hybrid-uri',
-    'url': uri,
-    'message': message,
-    'channel': virtualChannel.id
+    "type": "spawn-hybrid-uri",
+    "url": uri,
+    "message": message,
+    "channel": virtualChannel.id
   });
 
   if (!stayAlive) {
diff --git a/test_api/lib/src/frontend/stream_matcher.dart b/test_api/lib/src/frontend/stream_matcher.dart
index b1b96e9..e37c5c6 100644
--- a/test_api/lib/src/frontend/stream_matcher.dart
+++ b/test_api/lib/src/frontend/stream_matcher.dart
@@ -6,11 +6,15 @@
 
 import 'package:async/async.dart';
 import 'package:matcher/matcher.dart';
+import 'package:pedantic/pedantic.dart';
 
 import '../utils.dart';
 import 'async_matcher.dart';
 import 'format_stack_trace.dart';
 
+/// The type for [_StreamMatcher._matchQueue].
+typedef _MatchQueue = Future<String> Function(StreamQueue queue);
+
 /// A matcher that matches events from [Stream]s or [StreamQueue]s.
 ///
 /// Stream matchers are designed to make it straightforward to create complex
@@ -42,17 +46,17 @@
 /// at different times:
 ///
 /// ```dart
-/// var stdout = StreamQueue(stdoutLineStream);
+/// var stdout = new StreamQueue(stdoutLineStream);
 ///
 /// // Ignore lines from the process until it's about to emit the URL.
-/// await expectLater(stdout, emitsThrough('WebSocket URL:'));
+/// await expect(stdout, emitsThrough("WebSocket URL:"));
 ///
 /// // Parse the next line as a URL.
 /// var url = Uri.parse(await stdout.next);
 /// expect(url.host, equals('localhost'));
 ///
 /// // You can match against the same StreamQueue multiple times.
-/// await expectLater(stdout, emits('Waiting for connection...'));
+/// await expect(stdout, emits("Waiting for connection..."));
 /// ```
 ///
 /// Users can call [new StreamMatcher] to create custom matchers.
@@ -85,7 +89,7 @@
   /// should be grammatically valid when used after the word "should". For
   /// example, it might be "emit the right events".
   factory StreamMatcher(
-          Future<String> Function(StreamQueue) matchQueue, String description) =
+          Future<String> matchQueue(StreamQueue queue), String description) =
       _StreamMatcher;
 
   /// Tries to match events emitted by [queue].
@@ -112,28 +116,23 @@
 /// This is separate from the original type to hide the private [AsyncMatcher]
 /// interface.
 class _StreamMatcher extends AsyncMatcher implements StreamMatcher {
-  @override
   final String description;
 
   /// The callback used to implement [matchQueue].
-  final Future<String> Function(StreamQueue) _matchQueue;
+  final _MatchQueue _matchQueue;
 
   _StreamMatcher(this._matchQueue, this.description);
 
-  @override
   Future<String> matchQueue(StreamQueue queue) => _matchQueue(queue);
 
-  @override
-  dynamic /*FutureOr<String>*/ matchAsync(item) {
+  /*FutureOr<String>*/ matchAsync(item) {
     StreamQueue queue;
-    var shouldCancelQueue = false;
     if (item is StreamQueue) {
       queue = item;
     } else if (item is Stream) {
       queue = StreamQueue(item);
-      shouldCancelQueue = true;
     } else {
-      return 'was not a Stream or a StreamQueue';
+      return "was not a Stream or a StreamQueue";
     }
 
     // Avoid async/await in the outer method so that we synchronously error out
@@ -162,34 +161,30 @@
 
       var eventsString = events.map((event) {
         if (event == null) {
-          return 'x Stream closed.';
+          return "x Stream closed.";
         } else if (event.isValue) {
           return addBullet(event.asValue.value.toString());
         } else {
           var error = event.asError;
           var chain = formatStackTrace(error.stackTrace);
-          var text = '${error.error}\n$chain';
-          return prefixLines(text, '  ', first: '! ');
+          var text = "${error.error}\n$chain";
+          return prefixLines(text, "  ", first: "! ");
         }
-      }).join('\n');
-      if (eventsString.isEmpty) eventsString = 'no events';
+      }).join("\n");
+      if (eventsString.isEmpty) eventsString = "no events";
 
       transaction.reject();
 
       var buffer = StringBuffer();
-      buffer.writeln(indent(eventsString, first: 'emitted '));
-      if (result.isNotEmpty) buffer.writeln(indent(result, first: '  which '));
+      buffer.writeln(indent(eventsString, first: "emitted "));
+      if (result.isNotEmpty) buffer.writeln(indent(result, first: "  which "));
       return buffer.toString().trimRight();
     }, onError: (error) {
       transaction.reject();
       throw error;
-    }).then((result) {
-      if (shouldCancelQueue) queue.cancel();
-      return result;
     });
   }
 
-  @override
   Description describe(Description description) =>
-      description.add('should ').add(this.description);
+      description.add("should ").add(this.description);
 }
diff --git a/test_api/lib/src/frontend/stream_matchers.dart b/test_api/lib/src/frontend/stream_matchers.dart
index 53f7c8c..dec9859 100644
--- a/test_api/lib/src/frontend/stream_matchers.dart
+++ b/test_api/lib/src/frontend/stream_matchers.dart
@@ -14,7 +14,7 @@
 
 /// Returns a [StreamMatcher] that asserts that the stream emits a "done" event.
 final emitsDone = StreamMatcher(
-    (queue) async => (await queue.hasNext) ? '' : null, 'be done');
+    (queue) async => (await queue.hasNext) ? "" : null, "be done");
 
 /// Returns a [StreamMatcher] for [matcher].
 ///
@@ -32,7 +32,7 @@
   var matcherDescription = wrapped.describe(StringDescription());
 
   return StreamMatcher((queue) async {
-    if (!await queue.hasNext) return '';
+    if (!await queue.hasNext) return "";
 
     var matchState = {};
     var actual = await queue.next;
@@ -41,11 +41,11 @@
     var mismatchDescription = StringDescription();
     wrapped.describeMismatch(actual, mismatchDescription, matchState, false);
 
-    if (mismatchDescription.length == 0) return '';
-    return 'emitted an event that $mismatchDescription';
+    if (mismatchDescription.length == 0) return "";
+    return "emitted an event that $mismatchDescription";
   },
       // TODO(nweiz): add "should" once matcher#42 is fixed.
-      'emit an event that $matcherDescription');
+      "emit an event that $matcherDescription");
 }
 
 /// Returns a [StreamMatcher] that matches a single error event that matches
@@ -58,7 +58,7 @@
   return StreamMatcher(
       (queue) => throwsMatcher.matchAsync(queue.next) as Future<String>,
       // TODO(nweiz): add "should" once matcher#42 is fixed.
-      'emit an error that $matcherDescription');
+      "emit an error that $matcherDescription");
 }
 
 /// Returns a [StreamMatcher] that allows (but doesn't require) [matcher] to
@@ -72,7 +72,7 @@
     await queue.withTransaction(
         (copy) async => (await streamMatcher.matchQueue(copy)) == null);
     return null;
-  }, 'maybe ${streamMatcher.description}');
+  }, "maybe ${streamMatcher.description}");
 }
 
 /// Returns a [StreamMatcher] that matches the stream if at least one of
@@ -87,11 +87,11 @@
 StreamMatcher emitsAnyOf(Iterable matchers) {
   var streamMatchers = matchers.map(emits).toList();
   if (streamMatchers.isEmpty) {
-    throw ArgumentError('matcher may not be empty');
+    throw ArgumentError("matcher may not be empty");
   }
 
   if (streamMatchers.length == 1) return streamMatchers.first;
-  var description = 'do one of the following:\n' +
+  var description = "do one of the following:\n" +
       bullet(streamMatchers.map((matcher) => matcher.description));
 
   return StreamMatcher((queue) async {
@@ -143,16 +143,16 @@
 
       var failureMessages = <String>[];
       for (var i = 0; i < matchers.length; i++) {
-        var message = 'failed to ${streamMatchers[i].description}';
+        var message = "failed to ${streamMatchers[i].description}";
         if (failures[i].isNotEmpty) {
-          message += message.contains('\n') ? '\n' : ' ';
-          message += 'because it ${failures[i]}';
+          message += message.contains("\n") ? "\n" : " ";
+          message += "because it ${failures[i]}";
         }
 
         failureMessages.add(message);
       }
 
-      return 'failed all options:\n${bullet(failureMessages)}';
+      return "failed all options:\n${bullet(failureMessages)}";
     } else {
       transaction.commit(consumedMost);
       return null;
@@ -168,7 +168,7 @@
   var streamMatchers = matchers.map(emits).toList();
   if (streamMatchers.length == 1) return streamMatchers.first;
 
-  var description = 'do the following in order:\n' +
+  var description = "do the following in order:\n" +
       bullet(streamMatchers.map((matcher) => matcher.description));
 
   return StreamMatcher((queue) async {
@@ -179,8 +179,8 @@
 
       var newResult = "didn't ${matcher.description}";
       if (result.isNotEmpty) {
-        newResult += newResult.contains('\n') ? '\n' : ' ';
-        newResult += 'because it $result';
+        newResult += newResult.contains("\n") ? "\n" : " ";
+        newResult += "because it $result";
       }
       return newResult;
     }
@@ -199,7 +199,7 @@
   return StreamMatcher((queue) async {
     var failures = <String>[];
 
-    Future<bool> tryHere() => queue.withTransaction((copy) async {
+    tryHere() => queue.withTransaction((copy) async {
           var result = await streamMatcher.matchQueue(copy);
           if (result == null) return true;
           failures.add(result);
@@ -215,17 +215,17 @@
     // stream.
     if (await tryHere()) return null;
 
-    var result = 'never did ${streamMatcher.description}';
+    var result = "never did ${streamMatcher.description}";
 
     var failureMessages =
         bullet(failures.where((failure) => failure.isNotEmpty));
     if (failureMessages.isNotEmpty) {
-      result += result.contains('\n') ? '\n' : ' ';
-      result += 'because it:\n$failureMessages';
+      result += result.contains("\n") ? "\n" : " ";
+      result += "because it:\n$failureMessages";
     }
 
     return result;
-  }, 'eventually ${streamMatcher.description}');
+  }, "eventually ${streamMatcher.description}");
 }
 
 /// Returns a [StreamMatcher] that matches any number of events that match
@@ -238,8 +238,8 @@
   var streamMatcher = emits(matcher);
 
   var description = streamMatcher.description;
-  description += description.contains('\n') ? '\n' : ' ';
-  description += 'zero or more times';
+  description += description.contains("\n") ? "\n" : " ";
+  description += "zero or more times";
 
   return StreamMatcher((queue) async {
     while (await _tryMatch(queue, streamMatcher)) {
@@ -279,8 +279,8 @@
 
     if (!matched) return null;
     return "after $events ${pluralize('event', events)} did "
-        '${streamMatcher.description}';
-  }, 'never ${streamMatcher.description}');
+        "${streamMatcher.description}";
+  }, "never ${streamMatcher.description}");
 }
 
 /// Returns whether [matcher] matches [queue] at its current position.
@@ -312,11 +312,11 @@
 StreamMatcher emitsInAnyOrder(Iterable matchers) {
   var streamMatchers = matchers.map(emits).toSet();
   if (streamMatchers.length == 1) return streamMatchers.first;
-  var description = 'do the following in any order:\n' +
+  var description = "do the following in any order:\n" +
       bullet(streamMatchers.map((matcher) => matcher.description));
 
   return StreamMatcher(
-      (queue) async => await _tryInAnyOrder(queue, streamMatchers) ? null : '',
+      (queue) async => await _tryInAnyOrder(queue, streamMatchers) ? null : "",
       description);
 }
 
diff --git a/test_api/lib/src/frontend/throws_matcher.dart b/test_api/lib/src/frontend/throws_matcher.dart
index cdf9c6e..c6e34d7 100644
--- a/test_api/lib/src/frontend/throws_matcher.dart
+++ b/test_api/lib/src/frontend/throws_matcher.dart
@@ -15,7 +15,7 @@
 /// Use [throwsA] instead. We strongly recommend that you add assertions about
 /// at least the type of the error, but you can write `throwsA(anything)` to
 /// mimic the behavior of this matcher.
-@Deprecated('Will be removed in 0.13.0')
+@Deprecated("Will be removed in 0.13.0")
 const Matcher throws = Throws();
 
 /// This can be used to match three kinds of objects:
@@ -38,18 +38,17 @@
 Matcher throwsA(matcher) => Throws(wrapMatcher(matcher));
 
 /// Use the [throwsA] function instead.
-@Deprecated('Will be removed in 0.13.0')
+@Deprecated("Will be removed in 0.13.0")
 class Throws extends AsyncMatcher {
   final Matcher _matcher;
 
-  const Throws([Matcher matcher]) : _matcher = matcher;
+  const Throws([Matcher matcher]) : this._matcher = matcher;
 
   // Avoid async/await so we synchronously fail if we match a synchronous
   // function.
-  @override
-  dynamic /*FutureOr<String>*/ matchAsync(item) {
+  /*FutureOr<String>*/ matchAsync(item) {
     if (item is! Function && item is! Future) {
-      return 'was not a Function or Future';
+      return "was not a Function or Future";
     }
 
     if (item is Future) {
@@ -72,10 +71,9 @@
     }
   }
 
-  @override
   Description describe(Description description) {
     if (_matcher == null) {
-      return description.add('throws');
+      return description.add("throws");
     } else {
       return description.add('throws ').addDescriptionOf(_matcher);
     }
diff --git a/test_api/lib/src/frontend/timeout.dart b/test_api/lib/src/frontend/timeout.dart
index 9c1e02e..21b8890 100644
--- a/test_api/lib/src/frontend/timeout.dart
+++ b/test_api/lib/src/frontend/timeout.dart
@@ -9,18 +9,18 @@
 /// This is intended to scan through a number without actually encoding the full
 /// Dart number grammar. It doesn't stop on "e" because that can be a component
 /// of numbers.
-final _untilUnit = RegExp(r'[^a-df-z\s]+', caseSensitive: false);
+final _untilUnit = RegExp(r"[^a-df-z\s]+", caseSensitive: false);
 
 /// A regular expression that matches a time unit.
-final _unit = RegExp(r'([um]s|[dhms])', caseSensitive: false);
+final _unit = RegExp(r"([um]s|[dhms])", caseSensitive: false);
 
 /// A regular expression that matches a section of whitespace.
-final _whitespace = RegExp(r'\s+');
+final _whitespace = RegExp(r"\s+");
 
 /// A class representing a modification to the default timeout for a test.
 ///
 /// By default, a test will time out after 30 seconds. With [new Timeout], that
-/// can be overridden entirely; with [Timeout.factor], it can be scaled
+/// can be overridden entirely; with [new Timeout.factor], it can be scaled
 /// relative to the default.
 class Timeout {
   /// A constant indicating that a test should never time out.
@@ -70,17 +70,17 @@
     var scanner = StringScanner(timeout);
 
     // First check for the string "none".
-    if (scanner.scan('none')) {
+    if (scanner.scan("none")) {
       scanner.expectDone();
       return Timeout.none;
     }
 
     // Scan a number. This will be either a time unit or a scale factor.
-    scanner.expect(_untilUnit, name: 'number');
+    scanner.expect(_untilUnit, name: "number");
     var number = double.parse(scanner.lastMatch[0]);
 
     // A number followed by "x" is a scale factor.
-    if (scanner.scan('x') || scanner.scan('X')) {
+    if (scanner.scan("x") || scanner.scan("X")) {
       scanner.expectDone();
       return Timeout.factor(number);
     }
@@ -89,7 +89,7 @@
     // the loop because we've already parsed the first number.
     var microseconds = 0.0;
     while (true) {
-      scanner.expect(_unit, name: 'unit');
+      scanner.expect(_unit, name: "unit");
       microseconds += _microsecondsFor(number, scanner.lastMatch[0]);
 
       scanner.scan(_whitespace);
@@ -106,20 +106,20 @@
   /// Returns the number of microseconds in [number] [unit]s.
   static double _microsecondsFor(double number, String unit) {
     switch (unit) {
-      case 'd':
+      case "d":
         return number * 24 * 60 * 60 * 1000000;
-      case 'h':
+      case "h":
         return number * 60 * 60 * 1000000;
-      case 'm':
+      case "m":
         return number * 60 * 1000000;
-      case 's':
+      case "s":
         return number * 1000000;
-      case 'ms':
+      case "ms":
         return number * 1000;
-      case 'us':
+      case "us":
         return number;
       default:
-        throw ArgumentError('Unknown unit $unit.');
+        throw ArgumentError("Unknown unit $unit.");
     }
   }
 
@@ -141,22 +141,19 @@
   /// If this is [none], returns `null`.
   Duration apply(Duration base) {
     if (this == none) return null;
-    return duration ?? base * scaleFactor;
+    return duration == null ? base * scaleFactor : duration;
   }
 
-  @override
   int get hashCode => duration.hashCode ^ 5 * scaleFactor.hashCode;
 
-  @override
   bool operator ==(other) =>
       other is Timeout &&
       other.duration == duration &&
       other.scaleFactor == scaleFactor;
 
-  @override
   String toString() {
     if (duration != null) return duration.toString();
-    if (scaleFactor != null) return '${scaleFactor}x';
-    return 'none';
+    if (scaleFactor != null) return "${scaleFactor}x";
+    return "none";
   }
 }
diff --git a/test_api/lib/src/frontend/utils.dart b/test_api/lib/src/frontend/utils.dart
index 17dc2bd..c91e16e 100644
--- a/test_api/lib/src/frontend/utils.dart
+++ b/test_api/lib/src/frontend/utils.dart
@@ -14,6 +14,9 @@
 Future pumpEventQueue({int times}) {
   times ??= 20;
   if (times == 0) return Future.value();
-  // Use the event loop to allow the microtask queue to finish.
+  // Use [new Future] future to allow microtask events to finish. The [new
+  // Future.value] constructor uses scheduleMicrotask itself and would therefore
+  // not wait for microtask callbacks that are scheduled after invoking this
+  // method.
   return Future(() => pumpEventQueue(times: times - 1));
 }
diff --git a/test_api/lib/src/remote_listener.dart b/test_api/lib/src/remote_listener.dart
index e745eb4..970b598 100644
--- a/test_api/lib/src/remote_listener.dart
+++ b/test_api/lib/src/remote_listener.dart
@@ -44,8 +44,8 @@
   ///
   /// If [beforeLoad] is passed, it's called before the tests have been declared
   /// for this worker.
-  static StreamChannel start(Function Function() getMain,
-      {bool hidePrints = true, Future Function() beforeLoad}) {
+  static StreamChannel start(Function getMain(),
+      {bool hidePrints = true, Future beforeLoad()}) {
     // This has to be synchronous to work around sdk#25745. Otherwise, there'll
     // be an asynchronous pause before a syntax error notification is sent,
     // which will cause the send to fail entirely.
@@ -58,7 +58,7 @@
     var printZone = hidePrints ? null : Zone.current;
     var spec = ZoneSpecification(print: (_, __, ___, line) {
       if (printZone != null) printZone.print(line);
-      channel.sink.add({'type': 'print', 'line': line});
+      channel.sink.add({"type": "print", "line": line});
     });
 
     // Work-around for https://github.com/dart-lang/sdk/issues/32556. Remove
@@ -73,7 +73,7 @@
             main = getMain();
           } on NoSuchMethodError catch (_) {
             _sendLoadException(
-                channel, 'No top-level main() function defined.');
+                channel, "No top-level main() function defined.");
             return;
           } catch (error, stackTrace) {
             _sendError(channel, error, stackTrace, verboseChain);
@@ -82,11 +82,11 @@
 
           if (main is! Function) {
             _sendLoadException(
-                channel, 'Top-level main getter is not a function.');
+                channel, "Top-level main getter is not a function.");
             return;
           } else if (main is! Function()) {
             _sendLoadException(
-                channel, 'Top-level main() function takes arguments.');
+                channel, "Top-level main() function takes arguments.");
             return;
           }
 
@@ -95,12 +95,12 @@
           assert(message['type'] == 'initial');
 
           queue.rest.listen((message) {
-            if (message['type'] == 'close') {
+            if (message["type"] == "close") {
               controller.local.sink.close();
               return;
             }
 
-            assert(message['type'] == 'suiteChannel');
+            assert(message["type"] == "suiteChannel");
             SuiteChannelManager.current.connectIn(message['name'] as String,
                 channel.virtualChannel(message['id'] as int));
           });
@@ -155,15 +155,15 @@
   ///
   /// [message] should describe the failure.
   static void _sendLoadException(StreamChannel channel, String message) {
-    channel.sink.add({'type': 'loadException', 'message': message});
+    channel.sink.add({"type": "loadException", "message": message});
   }
 
   /// Sends a message over [channel] indicating an error from user code.
   static void _sendError(
       StreamChannel channel, error, StackTrace stackTrace, bool verboseChain) {
     channel.sink.add({
-      'type': 'error',
-      'error': RemoteException.serialize(
+      "type": "error",
+      "error": RemoteException.serialize(
           error,
           StackTraceFormatter.current
               .formatStackTrace(stackTrace, verbose: verboseChain))
@@ -176,8 +176,8 @@
   /// commands to run the tests.
   void _listen(MultiChannel channel) {
     channel.sink.add({
-      'type': 'success',
-      'root': _serializeGroup(channel, _suite.group, [])
+      "type": "success",
+      "root": _serializeGroup(channel, _suite.group, [])
     });
   }
 
@@ -188,13 +188,13 @@
       MultiChannel channel, Group group, Iterable<Group> parents) {
     parents = parents.toList()..add(group);
     return {
-      'type': 'group',
-      'name': group.name,
-      'metadata': group.metadata.serialize(),
-      'trace': group.trace?.toString(),
-      'setUpAll': _serializeTest(channel, group.setUpAll, parents),
-      'tearDownAll': _serializeTest(channel, group.tearDownAll, parents),
-      'entries': group.entries.map((entry) {
+      "type": "group",
+      "name": group.name,
+      "metadata": group.metadata.serialize(),
+      "trace": group.trace?.toString(),
+      "setUpAll": _serializeTest(channel, group.setUpAll, parents),
+      "tearDownAll": _serializeTest(channel, group.tearDownAll, parents),
+      "entries": group.entries.map((entry) {
         return entry is Group
             ? _serializeGroup(channel, entry, parents)
             : _serializeTest(channel, entry as Test, parents);
@@ -217,11 +217,11 @@
     });
 
     return {
-      'type': 'test',
-      'name': test.name,
-      'metadata': test.metadata.serialize(),
-      'trace': test.trace?.toString(),
-      'channel': testChannel.id
+      "type": "test",
+      "name": test.name,
+      "metadata": test.metadata.serialize(),
+      "trace": test.trace?.toString(),
+      "channel": testChannel.id
     };
   }
 
@@ -234,16 +234,16 @@
 
     liveTest.onStateChange.listen((state) {
       channel.sink.add({
-        'type': 'state-change',
-        'status': state.status.name,
-        'result': state.result.name
+        "type": "state-change",
+        "status": state.status.name,
+        "result": state.result.name
       });
     });
 
     liveTest.onError.listen((asyncError) {
       channel.sink.add({
-        'type': 'error',
-        'error': RemoteException.serialize(
+        "type": "error",
+        "error": RemoteException.serialize(
             asyncError.error,
             StackTraceFormatter.current.formatStackTrace(asyncError.stackTrace,
                 verbose: liveTest.test.metadata.verboseTrace))
@@ -253,14 +253,14 @@
     liveTest.onMessage.listen((message) {
       if (_printZone != null) _printZone.print(message.text);
       channel.sink.add({
-        'type': 'message',
-        'message-type': message.type.name,
-        'text': message.text
+        "type": "message",
+        "message-type": message.type.name,
+        "text": message.text
       });
     });
 
     runZoned(() {
-      liveTest.run().then((_) => channel.sink.add({'type': 'complete'}));
+      liveTest.run().then((_) => channel.sink.add({"type": "complete"}));
     }, zoneValues: {#test.runner.test_channel: channel});
   }
 }
diff --git a/test_api/lib/src/suite_channel_manager.dart b/test_api/lib/src/suite_channel_manager.dart
index 1f70d5f..aebe837 100644
--- a/test_api/lib/src/suite_channel_manager.dart
+++ b/test_api/lib/src/suite_channel_manager.dart
@@ -20,7 +20,7 @@
   final _outgoingConnections = <String, StreamChannelCompleter>{};
 
   /// The channel names that have already been used.
-  final _names = <String>{};
+  final _names = Set<String>();
 
   /// Returns the current manager, or `null` if this isn't called within a call
   /// to [asCurrent].
@@ -31,8 +31,7 @@
   ///
   /// This is zone-scoped, so [this] will be the current configuration in any
   /// asynchronous callbacks transitively created by [body].
-  T asCurrent<T>(T Function() body) =>
-      runZoned(body, zoneValues: {_currentKey: this});
+  T asCurrent<T>(T body()) => runZoned(body, zoneValues: {_currentKey: this});
 
   /// Creates a connection to the test runnner's channel with the given [name].
   StreamChannel connectOut(String name) {
diff --git a/test_api/lib/src/util/iterable_set.dart b/test_api/lib/src/util/iterable_set.dart
index bba6433..5166a72 100644
--- a/test_api/lib/src/util/iterable_set.dart
+++ b/test_api/lib/src/util/iterable_set.dart
@@ -19,22 +19,17 @@
   /// The base iterable that set operations forward to.
   final Iterable<E> _base;
 
-  @override
   int get length => _base.length;
 
-  @override
   Iterator<E> get iterator => _base.iterator;
 
   /// Creates a [Set] view of [base].
   IterableSet(this._base);
 
-  @override
   bool contains(Object element) => _base.contains(element);
 
-  @override
   E lookup(Object needle) =>
       _base.firstWhere((element) => element == needle, orElse: () => null);
 
-  @override
   Set<E> toSet() => _base.toSet();
 }
diff --git a/test_api/lib/src/util/remote_exception.dart b/test_api/lib/src/util/remote_exception.dart
index bb6fc62..a0582d5 100644
--- a/test_api/lib/src/util/remote_exception.dart
+++ b/test_api/lib/src/util/remote_exception.dart
@@ -29,7 +29,7 @@
   ///
   /// Other than JSON- and isolate-safety, no guarantees are made about the
   /// serialized format.
-  static Map<String, dynamic> serialize(error, StackTrace stackTrace) {
+  static serialize(error, StackTrace stackTrace) {
     String message;
     if (error is String) {
       message = error;
@@ -77,7 +77,6 @@
 
   RemoteException._(this.message, this.type, this._toString);
 
-  @override
   String toString() => _toString;
 }
 
diff --git a/test_api/lib/src/util/test.dart b/test_api/lib/src/util/test.dart
index de36fbf..ae5e6a8 100644
--- a/test_api/lib/src/util/test.dart
+++ b/test_api/lib/src/util/test.dart
@@ -13,7 +13,7 @@
 /// callbacks registered outside of [body].
 ///
 /// This may only be called within a test.
-Future errorsDontStopTest(dynamic Function() body) {
+Future errorsDontStopTest(body()) {
   var completer = Completer();
 
   Invoker.current.addOutstandingCallback();
diff --git a/test_api/lib/src/utils.dart b/test_api/lib/src/utils.dart
index ee1cde6..2e90a7c 100644
--- a/test_api/lib/src/utils.dart
+++ b/test_api/lib/src/utils.dart
@@ -31,7 +31,7 @@
 final chunksToLines = StreamChannelTransformer<String, String>(
     const LineSplitter(),
     StreamSinkTransformer.fromHandlers(
-        handleData: (data, sink) => sink.add('$data\n')));
+        handleData: (data, sink) => sink.add("$data\n")));
 
 /// A regular expression to match the exception prefix that some exceptions'
 /// [Object.toString] values contain.
@@ -43,13 +43,8 @@
 /// Directories that are specific to OS X.
 ///
 /// This is used to try to distinguish OS X and Linux in [currentOSGuess].
-final _macOSDirectories = {
-  '/Applications',
-  '/Library',
-  '/Network',
-  '/System',
-  '/Users',
-};
+final _macOSDirectories = Set<String>.from(
+    ["/Applications", "/Library", "/Network", "/System", "/Users"]);
 
 /// Returns the best guess for the current operating system without using
 /// `dart:io`.
@@ -68,12 +63,12 @@
 ///
 /// This is like a standard Dart identifier, except that it can also contain
 /// hyphens.
-final _hyphenatedIdentifier = RegExp(r'[a-zA-Z_-][a-zA-Z0-9_-]*');
+final _hyphenatedIdentifier = RegExp(r"[a-zA-Z_-][a-zA-Z0-9_-]*");
 
 /// Like [_hyphenatedIdentifier], but anchored so that it must match the entire
 /// string.
 final anchoredHyphenatedIdentifier =
-    RegExp('^${_hyphenatedIdentifier.pattern}\$');
+    RegExp("^${_hyphenatedIdentifier.pattern}\$");
 
 /// A pair of values.
 class Pair<E, F> {
@@ -82,16 +77,13 @@
 
   Pair(this.first, this.last);
 
-  @override
   String toString() => '($first, $last)';
 
-  @override
   bool operator ==(other) {
     if (other is! Pair) return false;
     return other.first == first && other.last == last;
   }
 
-  @override
   int get hashCode => first.hashCode ^ last.hashCode;
 }
 
@@ -108,7 +100,7 @@
 /// [size] defaults to `first.length`. Otherwise, [size] defaults to 2.
 String indent(String string, {int size, String first}) {
   size ??= first == null ? 2 : first.length;
-  return prefixLines(string, ' ' * size, first: first);
+  return prefixLines(string, " " * size, first: first);
 }
 
 /// Returns a sentence fragment listing the elements of [iter].
@@ -119,8 +111,8 @@
 String toSentence(Iterable iter, {String conjunction}) {
   if (iter.length == 1) return iter.first.toString();
 
-  var result = iter.take(iter.length - 1).join(', ');
-  if (iter.length > 2) result += ',';
+  var result = iter.take(iter.length - 1).join(", ");
+  if (iter.length > 2) result += ",";
   return "$result ${conjunction ?? 'and'} ${iter.last}";
 }
 
@@ -136,7 +128,7 @@
 
 /// Returns [noun] with an indefinite article ("a" or "an") added, based on
 /// whether its first letter is a vowel.
-String a(String noun) => noun.startsWith(_vowel) ? 'an $noun' : 'a $noun';
+String a(String noun) => noun.startsWith(_vowel) ? "an $noun" : "a $noun";
 
 /// A regular expression matching terminal color codes.
 final _colorCode = RegExp('\u001b\\[[0-9;]+m');
@@ -149,7 +141,7 @@
 ///
 /// The return value *may or may not* be unmodifiable.
 Map<K, V> mergeUnmodifiableMaps<K, V>(Map<K, V> map1, Map<K, V> map2,
-    {V Function(V, V) value}) {
+    {V value(V value1, V value2)}) {
   if (map1.isEmpty) return map2;
   if (map2.isEmpty) return map1;
   return mergeMaps(map1, map2, value: value);
@@ -203,13 +195,13 @@
   var decaseconds = (duration.inMilliseconds % 1000) ~/ 100;
 
   var buffer = StringBuffer();
-  if (minutes != 0) buffer.write('$minutes minutes');
+  if (minutes != 0) buffer.write("$minutes minutes");
 
   if (minutes == 0 || seconds != 0) {
-    if (minutes != 0) buffer.write(', ');
+    if (minutes != 0) buffer.write(", ");
     buffer.write(seconds);
-    if (decaseconds != 0) buffer.write('.$decaseconds');
-    buffer.write(' seconds');
+    if (decaseconds != 0) buffer.write(".$decaseconds");
+    buffer.write(" seconds");
   }
 
   return buffer.toString();
@@ -241,6 +233,14 @@
   return controller.stream;
 }
 
+/// Runs [fn] and discards its return value.
+///
+/// This is useful for making a block of code async without forcing the
+/// containing method to return a future.
+void invoke(fn()) {
+  fn();
+}
+
 /// Returns a random base64 string containing [bytes] bytes of data.
 ///
 /// [seed] is passed to [math.Random].
@@ -279,10 +279,10 @@
 
 /// Indents [text], and adds a bullet at the beginning.
 String addBullet(String text) =>
-    prefixLines(text, '  ', first: '${glyph.bullet} ');
+    prefixLines(text, "  ", first: "${glyph.bullet} ");
 
 /// Converts [strings] to a bulleted list.
-String bullet(Iterable<String> strings) => strings.map(addBullet).join('\n');
+String bullet(Iterable<String> strings) => strings.map(addBullet).join("\n");
 
 /// Prepends each line in [text] with [prefix].
 ///
@@ -297,15 +297,15 @@
   single ??= first ?? last ?? prefix;
 
   var lines = text.split('\n');
-  if (lines.length == 1) return '$single$text';
+  if (lines.length == 1) return "$single$text";
 
-  var buffer = StringBuffer('$first${lines.first}\n');
+  var buffer = StringBuffer("$first${lines.first}\n");
 
   // Write out all but the first and last lines with [prefix].
   for (var line in lines.skip(1).take(lines.length - 2)) {
-    buffer.writeln('$prefix$line');
+    buffer.writeln("$prefix$line");
   }
-  buffer.write('$last${lines.last}');
+  buffer.write("$last${lines.last}");
   return buffer.toString();
 }
 
@@ -315,12 +315,3 @@
 /// we can use it through StringDescription.
 String prettyPrint(value) =>
     StringDescription().addDescriptionOf(value).toString();
-
-/// Indicates to tools that [future] is intentionally not `await`-ed.
-///
-/// In an `async` context, it is normally expected that all [Future]s are
-/// awaited, and that is the basis of the lint `unawaited_futures`. However,
-/// there are times where one or more futures are intentionally not awaited.
-/// This function may be used to ignore a particular future. It silences the
-/// `unawaited_futures` lint.
-void unawaited(Future<void> future) {}
diff --git a/test_api/lib/test_api.dart b/test_api/lib/test_api.dart
index 2927580..9fab4e1 100644
--- a/test_api/lib/test_api.dart
+++ b/test_api/lib/test_api.dart
@@ -73,15 +73,15 @@
 /// annotation classes: [Timeout], [Skip], or lists of those. These
 /// annotations apply only on the given platforms. For example:
 ///
-///     test('potentially slow test', () {
+///     test("potentially slow test", () {
 ///       // ...
 ///     }, onPlatform: {
 ///       // This test is especially slow on Windows.
-///       'windows': Timeout.factor(2),
-///       'browser': [
-///         Skip('TODO: add browser support'),
+///       "windows": new Timeout.factor(2),
+///       "browser": [
+///         new Skip("TODO: add browser support"),
 ///         // This will be slow on browsers once it works on them.
-///         Timeout.factor(2)
+///         new Timeout.factor(2)
 ///       ]
 ///     });
 ///
@@ -94,7 +94,7 @@
 /// avoid this flag if possible and instead use the test runner flag `-n` to
 /// filter tests by name.
 @isTest
-void test(description, dynamic Function() body,
+void test(description, body(),
     {String testOn,
     Timeout timeout,
     skip,
@@ -151,15 +151,15 @@
 /// annotation classes: [Timeout], [Skip], or lists of those. These
 /// annotations apply only on the given platforms. For example:
 ///
-///     group('potentially slow tests', () {
+///     group("potentially slow tests", () {
 ///       // ...
 ///     }, onPlatform: {
 ///       // These tests are especially slow on Windows.
-///       'windows': Timeout.factor(2),
-///       'browser': [
-///         Skip('TODO: add browser support'),
+///       "windows": new Timeout.factor(2),
+///       "browser": [
+///         new Skip("TODO: add browser support"),
 ///         // They'll be slow on browsers once it works on them.
-///         Timeout.factor(2)
+///         new Timeout.factor(2)
 ///       ]
 ///     });
 ///
@@ -172,7 +172,7 @@
 /// avoid this flag if possible, and instead use the test runner flag `-n` to
 /// filter tests by name.
 @isTestGroup
-void group(description, dynamic Function() body,
+void group(description, body(),
     {String testOn,
     Timeout timeout,
     skip,
@@ -207,7 +207,7 @@
 ///
 /// Each callback at the top level or in a given group will be run in the order
 /// they were declared.
-void setUp(dynamic Function() callback) => _declarer.setUp(callback);
+void setUp(callback()) => _declarer.setUp(callback);
 
 /// Registers a function to be run after tests.
 ///
@@ -222,7 +222,7 @@
 /// reverse of the order they were declared.
 ///
 /// See also [addTearDown], which adds tear-downs to a running test.
-void tearDown(dynamic Function() callback) => _declarer.tearDown(callback);
+void tearDown(callback()) => _declarer.tearDown(callback);
 
 /// Registers a function to be run after the current test.
 ///
@@ -235,9 +235,9 @@
 ///
 /// If this is called from within a [setUpAll] or [tearDownAll] callback, it
 /// instead runs the function after *all* tests in the current test suite.
-void addTearDown(dynamic Function() callback) {
+void addTearDown(callback()) {
   if (Invoker.current == null) {
-    throw StateError('addTearDown() may only be called within a test.');
+    throw StateError("addTearDown() may only be called within a test.");
   }
 
   Invoker.current.addTearDown(callback);
@@ -256,7 +256,7 @@
 /// dependencies between tests that should be isolated. In general, you should
 /// prefer [setUp], and only use [setUpAll] if the callback is prohibitively
 /// slow.
-void setUpAll(dynamic Function() callback) => _declarer.setUpAll(callback);
+void setUpAll(callback()) => _declarer.setUpAll(callback);
 
 /// Registers a function to be run once after all tests.
 ///
@@ -269,8 +269,7 @@
 /// dependencies between tests that should be isolated. In general, you should
 /// prefer [tearDown], and only use [tearDownAll] if the callback is
 /// prohibitively slow.
-void tearDownAll(dynamic Function() callback) =>
-    _declarer.tearDownAll(callback);
+void tearDownAll(callback()) => _declarer.tearDownAll(callback);
 
 /// Registers an exception that was caught for the current test.
 void registerException(error, [StackTrace stackTrace]) {
diff --git a/test_api/mono_pkg.yaml b/test_api/mono_pkg.yaml
index 5f08191..62b9aba 100644
--- a/test_api/mono_pkg.yaml
+++ b/test_api/mono_pkg.yaml
@@ -6,7 +6,7 @@
         dart: dev
       - group:
         - dartanalyzer: --fatal-warnings .
-        dart: 2.4.0
+        dart: 2.1.0
     - unit_test:
       - group:
         - test: --preset travis
diff --git a/test_api/pubspec.yaml b/test_api/pubspec.yaml
index 8b34a9c..eab4b86 100644
--- a/test_api/pubspec.yaml
+++ b/test_api/pubspec.yaml
@@ -1,17 +1,19 @@
 name: test_api
-version: 0.2.15
+version: 0.2.11
+author: Dart Team <misc@dartlang.org>
 description: A library for writing Dart tests.
 homepage: https://github.com/dart-lang/test/blob/master/pkgs/test_api
 
 environment:
-  sdk: ">=2.4.0 <3.0.0"
+  sdk: ">=2.2.0 <3.0.0"
 
 dependencies:
   async: ^2.0.0
-  boolean_selector: ">=1.0.0 <3.0.0"
+  boolean_selector: ^1.0.0
   collection: ^1.8.0
   meta: ^1.1.5
   path: ^1.2.0
+  pedantic: ^1.0.0
   source_span: ^1.4.0
   stack_trace: ^1.9.0
   stream_channel: ">=1.7.0 <3.0.0"
@@ -24,7 +26,6 @@
 
 dev_dependencies:
   fake_async: ^1.0.0
-  pedantic: ^1.0.0
   test_descriptor: ^1.0.0
   test_process: ^1.0.0
   test: any
diff --git a/test_core/BUILD.gn b/test_core/BUILD.gn
index f8e5333..033d445 100644
--- a/test_core/BUILD.gn
+++ b/test_core/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by importer.py for test_core-0.3.2
+# This file is generated by importer.py for test_core-0.2.15
 
 import("//build/dart/dart_library.gni")
 
@@ -23,12 +23,13 @@
     "//third_party/dart-pkg/pub/meta",
     "//third_party/dart-pkg/pub/analyzer",
     "//third_party/dart-pkg/pub/io",
-    "//third_party/dart-pkg/pub/path",
     "//third_party/dart-pkg/pub/source_span",
+    "//third_party/dart-pkg/pub/path",
     "//third_party/dart-pkg/pub/stream_channel",
     "//third_party/dart-pkg/pub/pool",
     "//third_party/dart-pkg/pub/source_maps",
     "//third_party/dart-pkg/pub/source_map_stack_trace",
+    "//third_party/dart-pkg/pub/package_resolver",
     "//third_party/dart-pkg/pub/yaml",
     "//third_party/dart-pkg/pub/vm_service",
     "//third_party/dart-pkg/pub/boolean_selector",
diff --git a/test_core/CHANGELOG.md b/test_core/CHANGELOG.md
index dcb572a..cb4d969 100644
--- a/test_core/CHANGELOG.md
+++ b/test_core/CHANGELOG.md
@@ -1,51 +1,3 @@
-## 0.3.2
-
-* Drop the `package_resolver` dependency.
-
-## 0.3.1
-
-* Support latest `package:vm_service`.
-* Enable asserts in code running through `spawnHybrid` APIs.
-* Exit with a non-zero code if no tests were ran, whether due to skips or having
-  no tests defined.
-
-## 0.3.0
-
-* Bump minimum SDK to `2.4.0` for safer usage of for-loop elements.
-* Deprecate `PhantomJS` and provide warning when used. Support for `PhantomJS`
-  will be removed in version `2.0.0`.
-* Differentiate between test-randomize-ordering-seed not set and 0 being chosen
-  as the random seed.
-* `deserializeSuite` now takes an optional `gatherCoverage` callback.
-* Support retrying of entire test suites when they fail to load.
-* Fix the `compiling` message in precompiled mode so it says `loading` instead,
-  which is more accurate.
-* Change the behavior of the concurrency setting so that loading and running
-  don't have separate pools.
-  * The loading and running of a test are now done with the same resource, and
-    the concurrency setting uniformly affects each. With `-j1` only a single
-    test will ever be loaded at a time.
-  * Previously the loading pool was 2x larger than the actual concurrency
-    setting which could cause flaky tests due to tests being loaded while
-    other tests were running, even with `-j1`.
-* Avoid printing uncaught errors within `spawnHybridUri`.
-
-## 0.2.18
-
-* Allow `test_api` `0.2.13` to work around a bug in the SDK version `2.3.0`.
-
-## 0.2.17
-
-* Add `file_reporters` configuration option and `--file-reporter` CLI option to
-  allow specifying a separate reporter that writes to a file instead of stdout.
-
-## 0.2.16
-
-* Internal cleanup.
-* Add `customHtmlTemplateFile` configuration option to allow sharing an
-  html template between tests
-* Depend on the latest `test_api`.
-
 ## 0.2.15
 
 * Add a `StringSink` argument to reporters to prepare for reporting to a file.
diff --git a/test_core/lib/src/bootstrap/vm.dart b/test_core/lib/src/bootstrap/vm.dart
index 41b9269..d18366e 100644
--- a/test_core/lib/src/bootstrap/vm.dart
+++ b/test_core/lib/src/bootstrap/vm.dart
@@ -2,14 +2,14 @@
 // 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 'dart:isolate';
+import "dart:isolate";
 
-import 'package:stream_channel/isolate_channel.dart';
+import "package:stream_channel/isolate_channel.dart";
 
-import 'package:test_core/src/runner/plugin/remote_platform_helpers.dart';
+import "package:test_core/src/runner/plugin/remote_platform_helpers.dart";
 
 /// Bootstraps a vm test to communicate with the test runner.
-void internalBootstrapVmTest(Function Function() getMain, SendPort sendPort) {
+void internalBootstrapVmTest(Function getMain(), SendPort sendPort) {
   var channel = serializeSuite(getMain);
   IsolateChannel.connectSend(sendPort).pipe(channel);
 }
diff --git a/test_core/lib/src/executable.dart b/test_core/lib/src/executable.dart
index f3749f0..f1e3bb5 100644
--- a/test_core/lib/src/executable.dart
+++ b/test_core/lib/src/executable.dart
@@ -2,6 +2,9 @@
 // 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.
 
+// TODO(nweiz): This is under lib so that it can be used by the unittest dummy
+// package. Once that package is no longer being updated, move this back into
+// bin.
 import 'dart:async';
 import 'dart:io';
 
@@ -11,10 +14,11 @@
 import 'package:stack_trace/stack_trace.dart';
 import 'package:test_api/src/utils.dart'; // ignore: implementation_imports
 
+import 'runner.dart';
 import 'runner/application_exception.dart';
 import 'runner/configuration.dart';
 import 'runner/version.dart';
-import 'runner.dart';
+
 import 'util/exit_codes.dart' as exit_codes;
 import 'util/io.dart';
 
@@ -32,7 +36,7 @@
   }
 }();
 
-Future<void> main(List<String> args) async {
+main(List<String> args) async {
   await _execute(args);
   completeShutdown();
 }
@@ -155,9 +159,9 @@
   } catch (error, stackTrace) {
     stderr.writeln(getErrorMessage(error));
     stderr.writeln(Trace.from(stackTrace).terse);
-    stderr.writeln('This is an unexpected error. Please file an issue at '
-        'http://github.com/dart-lang/test\n'
-        'with the stack trace and instructions for reproducing the error.');
+    stderr.writeln("This is an unexpected error. Please file an issue at "
+        "http://github.com/dart-lang/test\n"
+        "with the stack trace and instructions for reproducing the error.");
     exitCode = exit_codes.software;
   } finally {
     await runner?.close();
@@ -165,7 +169,7 @@
 
   // TODO(grouma) - figure out why the executable can hang in the travis
   // environment. https://github.com/dart-lang/test/issues/599
-  if (Platform.environment['FORCE_TEST_EXIT'] == 'true') {
+  if (Platform.environment["FORCE_TEST_EXIT"] == "true") {
     exit(exitCode);
   }
 
@@ -179,16 +183,16 @@
 void _printUsage([String error]) {
   var output = stdout;
 
-  var message = 'Runs tests in this package.';
+  var message = "Runs tests in this package.";
   if (error != null) {
     message = error;
     output = stderr;
   }
 
-  output.write('''${wordWrap(message)}
+  output.write("""${wordWrap(message)}
 
 Usage: pub run test [files or directories...]
 
 ${Configuration.usage}
-''');
+""");
 }
diff --git a/test_core/lib/src/runner.dart b/test_core/lib/src/runner.dart
index 89ed8bd..68a3331 100644
--- a/test_core/lib/src/runner.dart
+++ b/test_core/lib/src/runner.dart
@@ -16,7 +16,6 @@
 import 'package:test_api/src/backend/suite_platform.dart'; // ignore: implementation_imports
 import 'package:test_api/src/backend/test.dart'; // ignore: implementation_imports
 import 'package:test_api/src/utils.dart'; // ignore: implementation_imports
-import 'package:test_core/src/runner/reporter/multiplex.dart';
 
 import 'util/io.dart';
 import 'runner/application_exception.dart';
@@ -59,7 +58,7 @@
   ///
   /// This is used to avoid printing duplicate warnings when a suite is loaded
   /// on multiple platforms.
-  final _tagWarningSuites = <String>{};
+  final _tagWarningSuites = Set<String>();
 
   /// The current debug operation, if any.
   ///
@@ -70,36 +69,17 @@
   final _closeMemo = AsyncMemoizer();
   bool get _closed => _closeMemo.hasRun;
 
-  /// Sinks created for each file reporter (if there are any).
-  final List<IOSink> _sinks;
-
   /// Creates a new runner based on [configuration].
   factory Runner(Configuration config) => config.asCurrent(() {
         var engine =
             Engine(concurrency: config.concurrency, coverage: config.coverage);
 
-        var sinks = <IOSink>[];
-        Reporter createFileReporter(String reporterName, String filepath) {
-          final sink =
-              (File(filepath)..createSync(recursive: true)).openWrite();
-          sinks.add(sink);
-          return allReporters[reporterName].factory(config, engine, sink);
-        }
-
+        var reporterDetails = allReporters[config.reporter];
         return Runner._(
-          engine,
-          MultiplexReporter([
-            // Standard reporter.
-            allReporters[config.reporter].factory(config, engine, stdout),
-            // File reporters.
-            for (var reporter in config.fileReporters.keys)
-              createFileReporter(reporter, config.fileReporters[reporter]),
-          ]),
-          sinks,
-        );
+            engine, reporterDetails.factory(config, engine, stdout));
       });
 
-  Runner._(this._engine, this._reporter, this._sinks);
+  Runner._(this._engine, this._reporter);
 
   /// Starts the runner.
   ///
@@ -107,7 +87,7 @@
   /// or not they ran successfully.
   Future<bool> run() => _config.asCurrent(() async {
         if (_closed) {
-          throw StateError('run() may not be called on a closed Runner.');
+          throw StateError("run() may not be called on a closed Runner.");
         }
 
         _warnForUnsupportedPlatforms();
@@ -160,8 +140,9 @@
           throw ApplicationException('No tests match $patterns.');
         }
 
-        return (success ?? false) &&
-            (_engine.passed.isNotEmpty || _engine.skipped.isNotEmpty);
+        // Explicitly check "== true" here because [Engine.run] can return `null`
+        // if the engine was closed prematurely.
+        return success == true;
       });
 
   /// Emits a warning if the user is trying to run on a platform that's
@@ -194,7 +175,7 @@
         unsupportedNames
             .addAll(unsupportedBrowsers.map((runtime) => runtime.name));
       } else {
-        unsupportedNames.add('browsers');
+        unsupportedNames.add("browsers");
       }
     }
 
@@ -207,13 +188,13 @@
       if (supportsAnyOS) {
         unsupportedNames.add(currentOS.name);
       } else {
-        unsupportedNames.add('the Dart VM');
+        unsupportedNames.add("the Dart VM");
       }
     }
 
     warn("this package doesn't support running tests on " +
-        toSentence(unsupportedNames, conjunction: 'or') +
-        '.');
+        toSentence(unsupportedNames, conjunction: "or") +
+        ".");
   }
 
   /// Closes the runner.
@@ -230,8 +211,8 @@
             // Pause the reporter while we print to ensure that we don't interfere
             // with its output.
             _reporter.pause();
-            print('Waiting for current test(s) to finish.');
-            print('Press Control-C again to terminate immediately.');
+            print("Waiting for current test(s) to finish.");
+            print("Press Control-C again to terminate immediately.");
             _reporter.resume();
           });
         }
@@ -250,10 +231,6 @@
         await Future.wait([_loader.closeEphemeral(), _engine.close()]);
         if (timer != null) timer.cancel();
         await _loader.close();
-
-        // Flush any IOSinks created for file reporters.
-        await Future.wait(_sinks.map((s) => s.flush().then((_) => s.close())));
-        _sinks.clear();
       });
 
   /// Return a stream of [LoadSuite]s in [_config.paths].
@@ -284,12 +261,12 @@
           }
 
           // If the user provided tags, skip tests that don't match all of them.
-          if (!suite.config.includeTags.evaluate(test.metadata.tags.contains)) {
+          if (!suite.config.includeTags.evaluate(test.metadata.tags)) {
             return false;
           }
 
           // Skip tests that do match any tags the user wants to exclude.
-          if (suite.config.excludeTags.evaluate(test.metadata.tags.contains)) {
+          if (suite.config.excludeTags.evaluate(test.metadata.tags)) {
             return false;
           }
 
@@ -313,23 +290,23 @@
     var noColor = _config.color ? '\u001b[0m' : '';
 
     var buffer = StringBuffer()
-      ..write('${yellow}Warning:$noColor ')
-      ..write(unknownTags.length == 1 ? 'A tag was ' : 'Tags were ')
-      ..write('used that ')
+      ..write("${yellow}Warning:$noColor ")
+      ..write(unknownTags.length == 1 ? "A tag was " : "Tags were ")
+      ..write("used that ")
       ..write(unknownTags.length == 1 ? "wasn't " : "weren't ")
-      ..writeln('specified in dart_test.yaml.');
+      ..writeln("specified in dart_test.yaml.");
 
     unknownTags.forEach((tag, entries) {
-      buffer.write('  $bold$tag$noColor was used in');
+      buffer.write("  $bold$tag$noColor was used in");
 
       if (entries.length == 1) {
-        buffer.writeln(' ${_entryDescription(entries.single)}');
+        buffer.writeln(" ${_entryDescription(entries.single)}");
         return;
       }
 
-      buffer.write(':');
+      buffer.write(":");
       for (var entry in entries) {
-        buffer.write('\n    ${_entryDescription(entry)}');
+        buffer.write("\n    ${_entryDescription(entry)}");
       }
       buffer.writeln();
     });
@@ -343,10 +320,10 @@
   /// This returns a map from tag names to lists of entries that use those tags.
   Map<String, List<GroupEntry>> _collectUnknownTags(Suite suite) {
     var unknownTags = <String, List<GroupEntry>>{};
-    var currentTags = <String>{};
+    var currentTags = Set<String>();
 
-    void collect(GroupEntry entry) {
-      var newTags = <String>{};
+    collect(GroupEntry entry) {
+      var newTags = Set<String>();
       for (var unknownTag
           in entry.metadata.tags.difference(_config.knownTags)) {
         if (currentTags.contains(unknownTag)) continue;
diff --git a/test_core/lib/src/runner/application_exception.dart b/test_core/lib/src/runner/application_exception.dart
index 23155a2..919d3ec 100644
--- a/test_core/lib/src/runner/application_exception.dart
+++ b/test_core/lib/src/runner/application_exception.dart
@@ -8,6 +8,5 @@
 
   ApplicationException(this.message);
 
-  @override
   String toString() => message;
 }
diff --git a/test_core/lib/src/runner/compiler_pool.dart b/test_core/lib/src/runner/compiler_pool.dart
index 7556ab5..0465b07 100644
--- a/test_core/lib/src/runner/compiler_pool.dart
+++ b/test_core/lib/src/runner/compiler_pool.dart
@@ -5,9 +5,9 @@
 import 'dart:async';
 import 'dart:convert';
 import 'dart:io';
-import 'dart:isolate';
 
 import 'package:async/async.dart';
+import 'package:package_resolver/package_resolver.dart';
 import 'package:path/path.dart' as p;
 import 'package:pool/pool.dart';
 
@@ -17,7 +17,7 @@
 
 /// A regular expression matching the first status line printed by dart2js.
 final _dart2jsStatus =
-    RegExp(r'^Dart file \(.*\) compiled to JavaScript: .*\n?');
+    RegExp(r"^Dart file \(.*\) compiled to JavaScript: .*\n?");
 
 /// A pool of `dart2js` instances.
 ///
@@ -30,7 +30,7 @@
   final Pool _pool;
 
   /// The currently-active dart2js processes.
-  final _processes = <Process>{};
+  final _processes = Set<Process>();
 
   /// Whether [close] has been called.
   bool get _closed => _closeMemo.hasRun;
@@ -57,22 +57,22 @@
       if (_closed) return null;
 
       return withTempDir((dir) async {
-        var wrapperPath = p.join(dir, 'runInBrowser.dart');
+        var wrapperPath = p.join(dir, "runInBrowser.dart");
         File(wrapperPath).writeAsStringSync(code);
 
         var dart2jsPath = _config.dart2jsPath;
         if (Platform.isWindows) dart2jsPath += '.bat';
 
         var args = [
-          '--enable-asserts',
+          "--enable-asserts",
           wrapperPath,
-          '--out=$jsPath',
-          '--packages=${await Isolate.packageConfig}',
-          ..._extraArgs,
-          ...suiteConfig.dart2jsArgs
-        ];
+          "--out=$jsPath",
+          await PackageResolver.current.processArgument
+        ]
+          ..addAll(_extraArgs)
+          ..addAll(suiteConfig.dart2jsArgs);
 
-        if (_config.color) args.add('--enable-diagnostic-colors');
+        if (_config.color) args.add("--enable-diagnostic-colors");
 
         var process = await Process.start(dart2jsPath, args);
         if (_closed) {
@@ -101,7 +101,7 @@
         var output = buffer.toString().replaceFirst(_dart2jsStatus, '');
         if (output.isNotEmpty) print(output);
 
-        if (exitCode != 0) throw 'dart2js failed.';
+        if (exitCode != 0) throw "dart2js failed.";
 
         _fixSourceMap(jsPath + '.map');
       });
@@ -118,7 +118,7 @@
     map['sources'] = map['sources'].map((source) {
       var url = Uri.parse(root + '$source');
       if (url.scheme != '' && url.scheme != 'file') return source;
-      if (url.path.endsWith('/runInBrowser.dart')) return '';
+      if (url.path.endsWith("/runInBrowser.dart")) return "";
       return p.toUri(mapPath).resolveUri(url).toString();
     }).toList();
 
diff --git a/test_core/lib/src/runner/configuration.dart b/test_core/lib/src/runner/configuration.dart
index e442f91..26f0c62 100644
--- a/test_core/lib/src/runner/configuration.dart
+++ b/test_core/lib/src/runner/configuration.dart
@@ -43,9 +43,6 @@
   bool get help => _help ?? false;
   final bool _help;
 
-  /// Custom HTML template file.
-  final String customHtmlTemplatePath;
-
   /// Whether `--version` was passed.
   bool get version => _version ?? false;
   final bool _version;
@@ -65,7 +62,7 @@
   /// The path to the file from which to load more configuration information.
   ///
   /// This is *not* resolved automatically.
-  String get configurationPath => _configurationPath ?? 'dart_test.yaml';
+  String get configurationPath => _configurationPath ?? "dart_test.yaml";
   final String _configurationPath;
 
   /// The path to dart2js.
@@ -76,10 +73,6 @@
   String get reporter => _reporter ?? defaultReporter;
   final String _reporter;
 
-  /// The map of file reporters where the key is the name of the reporter and
-  /// the value is the filepath to which its output should be written.
-  final Map<String, String> fileReporters;
-
   /// Whether to disable retries of tests.
   bool get noRetry => _noRetry ?? false;
   final bool _noRetry;
@@ -119,16 +112,16 @@
   final int totalShards;
 
   /// The list of packages to fold when producing [StackTrace]s.
-  Set<String> get foldTraceExcept => _foldTraceExcept ?? {};
+  Set<String> get foldTraceExcept => _foldTraceExcept ?? Set();
   final Set<String> _foldTraceExcept;
 
   /// If non-empty, all packages not in this list will be folded when producing
   /// [StackTrace]s.
-  Set<String> get foldTraceOnly => _foldTraceOnly ?? {};
+  Set<String> get foldTraceOnly => _foldTraceOnly ?? Set();
   final Set<String> _foldTraceOnly;
 
   /// The paths from which to load tests.
-  List<String> get paths => _paths ?? ['test'];
+  List<String> get paths => _paths ?? ["test"];
   final List<String> _paths;
 
   /// Whether the load paths were passed explicitly or the default was used.
@@ -223,7 +216,6 @@
 
   factory Configuration(
       {bool help,
-      String customHtmlTemplatePath,
       bool version,
       bool pauseAfterLoad,
       bool debug,
@@ -231,7 +223,6 @@
       String configurationPath,
       String dart2jsPath,
       String reporter,
-      Map<String, String> fileReporters,
       String coverage,
       int pubServePort,
       int concurrency,
@@ -272,7 +263,6 @@
     var chosenPresetSet = chosenPresets?.toSet();
     var configuration = Configuration._(
         help: help,
-        customHtmlTemplatePath: customHtmlTemplatePath,
         version: version,
         pauseAfterLoad: pauseAfterLoad,
         debug: debug,
@@ -280,7 +270,6 @@
         configurationPath: configurationPath,
         dart2jsPath: dart2jsPath,
         reporter: reporter,
-        fileReporters: fileReporters,
         coverage: coverage,
         pubServePort: pubServePort,
         concurrency: concurrency,
@@ -334,7 +323,6 @@
   /// Unlike [new Configuration], this assumes [presets] is already resolved.
   Configuration._(
       {bool help,
-      String customHtmlTemplatePath,
       bool version,
       bool pauseAfterLoad,
       bool debug,
@@ -342,7 +330,6 @@
       String configurationPath,
       String dart2jsPath,
       String reporter,
-      Map<String, String> fileReporters,
       String coverage,
       int pubServePort,
       int concurrency,
@@ -359,7 +346,6 @@
       bool noRetry,
       SuiteConfiguration suiteDefaults})
       : _help = help,
-        customHtmlTemplatePath = customHtmlTemplatePath,
         _version = version,
         _pauseAfterLoad = pauseAfterLoad,
         _debug = debug,
@@ -367,17 +353,16 @@
         _configurationPath = configurationPath,
         _dart2jsPath = dart2jsPath,
         _reporter = reporter,
-        fileReporters = fileReporters ?? {},
         _coverage = coverage,
         pubServeUrl = pubServePort == null
             ? null
-            : Uri.parse('http://localhost:$pubServePort'),
+            : Uri.parse("http://localhost:$pubServePort"),
         _concurrency = concurrency,
         _paths = _list(paths),
         _foldTraceExcept = _set(foldTraceExcept),
         _foldTraceOnly = _set(foldTraceOnly),
         _filename = filename,
-        chosenPresets = UnmodifiableSetView(chosenPresets?.toSet() ?? {}),
+        chosenPresets = UnmodifiableSetView(chosenPresets?.toSet() ?? Set()),
         presets = _map(presets),
         overrideRuntimes = _map(overrideRuntimes),
         defineRuntimes = _map(defineRuntimes),
@@ -389,15 +374,15 @@
     if (_filename != null && _filename.context.style != p.style) {
       throw ArgumentError(
           "filename's context must match the current operating system, was "
-          '${_filename.context.style}.');
+          "${_filename.context.style}.");
     }
 
     if ((shardIndex == null) != (totalShards == null)) {
       throw ArgumentError(
-          'shardIndex and totalShards may only be passed together.');
+          "shardIndex and totalShards may only be passed together.");
     } else if (shardIndex != null) {
       RangeError.checkValueInInterval(
-          shardIndex, 0, totalShards - 1, 'shardIndex');
+          shardIndex, 0, totalShards - 1, "shardIndex");
     }
   }
 
@@ -435,8 +420,7 @@
   ///
   /// This is zone-scoped, so [this] will be the current configuration in any
   /// asynchronous callbacks transitively created by [body].
-  T asCurrent<T>(T Function() body) =>
-      runZoned(body, zoneValues: {_currentKey: this});
+  T asCurrent<T>(T body()) => runZoned(body, zoneValues: {_currentKey: this});
 
   /// Throws a [FormatException] if [this] refers to any undefined runtimes.
   void validateRuntimes(List<Runtime> allRuntimes) {
@@ -485,15 +469,12 @@
 
     var result = Configuration._(
         help: other._help ?? _help,
-        customHtmlTemplatePath:
-            other.customHtmlTemplatePath ?? customHtmlTemplatePath,
         version: other._version ?? _version,
         pauseAfterLoad: other._pauseAfterLoad ?? _pauseAfterLoad,
         color: other._color ?? _color,
         configurationPath: other._configurationPath ?? _configurationPath,
         dart2jsPath: other._dart2jsPath ?? _dart2jsPath,
         reporter: other._reporter ?? _reporter,
-        fileReporters: mergeMaps(fileReporters, other.fileReporters),
         coverage: other._coverage ?? _coverage,
         pubServePort: (other.pubServeUrl ?? pubServeUrl)?.port,
         concurrency: other._concurrency ?? _concurrency,
@@ -529,7 +510,6 @@
   /// always replaced by the new one.
   Configuration change(
       {bool help,
-      String customHtmlTemplatePath,
       bool version,
       bool pauseAfterLoad,
       bool color,
@@ -573,8 +553,6 @@
       Iterable<String> addTags}) {
     var config = Configuration._(
         help: help ?? _help,
-        customHtmlTemplatePath:
-            customHtmlTemplatePath ?? this.customHtmlTemplatePath,
         version: version ?? _version,
         pauseAfterLoad: pauseAfterLoad ?? _pauseAfterLoad,
         color: color ?? _color,
@@ -637,12 +615,12 @@
             merged.merge(newPresets.remove(preset) ?? Configuration.empty));
 
     if (merged == empty) return this;
-    var result = change(presets: newPresets).merge(merged);
+    var result = this.change(presets: newPresets).merge(merged);
 
     // Make sure the configuration knows about presets that were selected and
     // thus removed from [newPresets].
-    result._knownPresets =
-        UnmodifiableSetView(result.knownPresets.toSet()..addAll(presets.keys));
+    result._knownPresets = UnmodifiableSetView(
+        result.knownPresets.toSet()..addAll(this.presets.keys));
 
     return result;
   }
diff --git a/test_core/lib/src/runner/configuration/args.dart b/test_core/lib/src/runner/configuration/args.dart
index 02e5384..0d04fdb 100644
--- a/test_core/lib/src/runner/configuration/args.dart
+++ b/test_core/lib/src/runner/configuration/args.dart
@@ -7,12 +7,11 @@
 
 import 'package:args/args.dart';
 import 'package:boolean_selector/boolean_selector.dart';
+
 import 'package:test_api/src/backend/runtime.dart'; // ignore: implementation_imports
 import 'package:test_api/src/frontend/timeout.dart'; // ignore: implementation_imports
-
-import '../../util/io.dart';
-import '../configuration.dart';
 import '../runtime_selection.dart';
+import '../configuration.dart';
 import 'reporters.dart';
 import 'values.dart';
 
@@ -24,133 +23,129 @@
   if (!Platform.isMacOS) allRuntimes.remove(Runtime.safari);
   if (!Platform.isWindows) allRuntimes.remove(Runtime.internetExplorer);
 
-  parser.addFlag('help',
-      abbr: 'h', negatable: false, help: 'Shows this usage information.');
-  parser.addFlag('version',
+  parser.addFlag("help",
+      abbr: "h", negatable: false, help: "Shows this usage information.");
+  parser.addFlag("version",
       negatable: false, help: "Shows the package's version.");
 
   // Note that defaultsTo declarations here are only for documentation purposes.
-  // We pass null instead of the default so that it merges properly with the
-  // config file.
+  // We pass null values rather than defaults to [new Configuration] so that it
+  // merges properly with the config file.
 
-  parser.addSeparator('======== Selecting Tests');
-  parser.addMultiOption('name',
+  parser.addSeparator("======== Selecting Tests");
+  parser.addMultiOption("name",
       abbr: 'n',
       help: 'A substring of the name of the test to run.\n'
           'Regular expression syntax is supported.\n'
           'If passed multiple times, tests must match all substrings.',
       splitCommas: false);
-  parser.addMultiOption('plain-name',
+  parser.addMultiOption("plain-name",
       abbr: 'N',
       help: 'A plain-text substring of the name of the test to run.\n'
           'If passed multiple times, tests must match all substrings.',
       splitCommas: false);
-  parser.addMultiOption('tags',
+  parser.addMultiOption("tags",
       abbr: 't',
       help: 'Run only tests with all of the specified tags.\n'
           'Supports boolean selector syntax.');
-  parser.addMultiOption('tag', hide: true);
-  parser.addMultiOption('exclude-tags',
+  parser.addMultiOption("tag", hide: true);
+  parser.addMultiOption("exclude-tags",
       abbr: 'x',
       help: "Don't run tests with any of the specified tags.\n"
-          'Supports boolean selector syntax.');
-  parser.addMultiOption('exclude-tag', hide: true);
-  parser.addFlag('run-skipped',
+          "Supports boolean selector syntax.");
+  parser.addMultiOption("exclude-tag", hide: true);
+  parser.addFlag("run-skipped",
       help: 'Run skipped tests instead of skipping them.');
 
-  parser.addSeparator('======== Running Tests');
+  parser.addSeparator("======== Running Tests");
 
   // The UI term "platform" corresponds with the implementation term "runtime".
   // The [Runtime] class used to be called [TestPlatform], but it was changed to
   // avoid conflicting with [SuitePlatform]. We decided not to also change the
   // UI to avoid a painful migration.
-  parser.addMultiOption('platform',
+  parser.addMultiOption("platform",
       abbr: 'p',
       help: 'The platform(s) on which to run the tests.\n'
           '[vm (default), '
           '${allRuntimes.map((runtime) => runtime.identifier).join(", ")}]');
-  parser.addMultiOption('preset',
+  parser.addMultiOption("preset",
       abbr: 'P', help: 'The configuration preset(s) to use.');
-  parser.addOption('concurrency',
+  parser.addOption("concurrency",
       abbr: 'j',
       help: 'The number of concurrent test suites run.',
       defaultsTo: defaultConcurrency.toString(),
       valueHelp: 'threads');
-  parser.addOption('total-shards',
+  parser.addOption("total-shards",
       help: 'The total number of invocations of the test runner being run.');
-  parser.addOption('shard-index',
+  parser.addOption("shard-index",
       help: 'The index of this test runner invocation (of --total-shards).');
-  parser.addOption('pub-serve',
+  parser.addOption("pub-serve",
       help: 'The port of a pub serve instance serving "test/".',
       valueHelp: 'port');
-  parser.addOption('timeout',
+  parser.addOption("timeout",
       help: 'The default test timeout. For example: 15s, 2x, none',
       defaultsTo: '30s');
-  parser.addFlag('pause-after-load',
+  parser.addFlag("pause-after-load",
       help: 'Pauses for debugging before any tests execute.\n'
           'Implies --concurrency=1, --debug, and --timeout=none.\n'
           'Currently only supported for browser tests.',
       negatable: false);
-  parser.addFlag('debug',
+  parser.addFlag("debug",
       help: 'Runs the VM and Chrome tests in debug mode.', negatable: false);
-  parser.addOption('coverage',
+  parser.addOption("coverage",
       help: 'Gathers coverage and outputs it to the specified directory.\n'
           'Implies --debug.',
       valueHelp: 'directory');
-  parser.addFlag('chain-stack-traces',
+  parser.addFlag("chain-stack-traces",
       help: 'Chained stack traces to provide greater exception details\n'
           'especially for asynchronous code. It may be useful to disable\n'
           'to provide improved test performance but at the cost of\n'
           'debuggability.',
       defaultsTo: true);
-  parser.addFlag('no-retry',
+  parser.addFlag("no-retry",
       help: "Don't re-run tests that have retry set.",
       defaultsTo: false,
       negatable: false);
-  parser.addOption('test-randomize-ordering-seed',
-      help: 'The seed to randomize the execution order of test cases.\n'
-          'Must be a 32bit unsigned integer or "random".\n'
+  parser.addOption("test-randomize-ordering-seed",
+      help: 'If positive, use this as a seed to randomize the execution\n'
+          'of test cases (must be a 32bit unsigned integer).\n'
           'If "random", pick a random seed to use.\n'
-          'If not passed, do not randomize test case execution order.');
+          'If 0 or not set, do not randomize test case execution order.\n');
 
   var reporterDescriptions = <String, String>{};
   for (var reporter in allReporters.keys) {
     reporterDescriptions[reporter] = allReporters[reporter].description;
   }
 
-  parser.addSeparator('======== Output');
-  parser.addOption('reporter',
+  parser.addSeparator("======== Output");
+  parser.addOption("reporter",
       abbr: 'r',
       help: 'The runner used to print test results.',
       defaultsTo: defaultReporter,
       allowed: reporterDescriptions.keys.toList(),
       allowedHelp: reporterDescriptions);
-  parser.addOption('file-reporter',
-      help: 'The reporter used to write test results to a file.\n'
-          'Should be in the form <reporter>:<filepath>, '
-          'e.g. "json:reports/tests.json"');
-  parser.addFlag('verbose-trace',
+  parser.addFlag("verbose-trace",
       negatable: false,
       help: 'Whether to emit stack traces with core library frames.');
-  parser.addFlag('js-trace',
+  parser.addFlag("js-trace",
       negatable: false,
       help: 'Whether to emit raw JavaScript stack traces for browser tests.');
-  parser.addFlag('color',
+  parser.addFlag("color",
       help: 'Whether to use terminal colors.\n(auto-detected by default)');
 
   /// The following options are used only by the internal Google test runner.
   /// They're hidden and not supported as stable API surface outside Google.
 
-  parser.addOption('configuration',
+  parser.addOption("configuration",
       help: 'The path to the configuration file.', hide: true);
-  parser.addOption('dart2js-path',
+  parser.addOption("dart2js-path",
       help: 'The path to the dart2js executable.', hide: true);
-  parser.addMultiOption('dart2js-args',
+  parser.addMultiOption("dart2js-args",
       help: 'Extra arguments to pass to dart2js.', hide: true);
 
   // If we're running test/dir/my_test.dart, we'll look for
   // test/dir/my_test.dart.html in the precompiled directory.
-  parser.addOption('precompiled',
+  parser.addOption("precompiled",
       help: 'The path to a mirror of the package directory containing HTML '
           'that points to precompiled JS.',
       hide: true);
@@ -205,13 +200,13 @@
     var totalShards = _parseOption('total-shards', int.parse);
     if ((shardIndex == null) != (totalShards == null)) {
       throw FormatException(
-          '--shard-index and --total-shards may only be passed together.');
+          "--shard-index and --total-shards may only be passed together.");
     } else if (shardIndex != null) {
       if (shardIndex < 0) {
-        throw FormatException('--shard-index may not be negative.');
+        throw FormatException("--shard-index may not be negative.");
       } else if (shardIndex >= totalShards) {
         throw FormatException(
-            '--shard-index must be less than --total-shards.');
+            "--shard-index must be less than --total-shards.");
       }
     }
 
@@ -220,26 +215,12 @@
       var seed = value == 'random'
           ? Random().nextInt(4294967295)
           : int.parse(value).toUnsigned(32);
-      if (seed != null) {
+      if (seed != null && seed > 0) {
         print('Shuffling test order with --test-randomize-ordering-seed=$seed');
       }
       return seed;
     });
 
-    var color = _ifParsed<bool>('color') ?? canUseSpecialChars;
-
-    var platform = _ifParsed<List<String>>('platform')
-        ?.map((runtime) => RuntimeSelection(runtime))
-        ?.toList();
-    if (platform
-            ?.any((runtime) => runtime.name == Runtime.phantomJS.identifier) ??
-        false) {
-      var yellow = color ? '\u001b[33m' : '';
-      var noColor = color ? '\u001b[0m' : '';
-      print('${yellow}Warning:$noColor '
-          'PhatomJS is deprecated and will be removed in version ^2.0.0');
-    }
-
     return Configuration(
         help: _ifParsed('help'),
         version: _ifParsed('version'),
@@ -248,13 +229,12 @@
         jsTrace: _ifParsed('js-trace'),
         pauseAfterLoad: _ifParsed('pause-after-load'),
         debug: _ifParsed('debug'),
-        color: color,
+        color: _ifParsed('color'),
         configurationPath: _ifParsed('configuration'),
         dart2jsPath: _ifParsed('dart2js-path'),
         dart2jsArgs: _ifParsed('dart2js-args'),
         precompiledPath: _ifParsed('precompiled'),
         reporter: _ifParsed('reporter'),
-        fileReporters: _parseFileReporterOption(),
         coverage: _ifParsed('coverage'),
         pubServePort: _parseOption('pub-serve', int.parse),
         concurrency: _parseOption('concurrency', int.parse),
@@ -262,7 +242,9 @@
         totalShards: totalShards,
         timeout: _parseOption('timeout', (value) => Timeout.parse(value)),
         patterns: patterns,
-        runtimes: platform,
+        runtimes: _ifParsed<List<String>>('platform')
+            ?.map((runtime) => RuntimeSelection(runtime))
+            ?.toList(),
         runSkipped: _ifParsed('run-skipped'),
         chosenPresets: _ifParsed('preset'),
         paths: _options.rest.isEmpty ? null : _options.rest,
@@ -282,7 +264,7 @@
 
   /// Runs [parse] on the value of the option [name], and wraps any
   /// [FormatException] it throws with additional information.
-  T _parseOption<T>(String name, T Function(String) parse) {
+  T _parseOption<T>(String name, T parse(String value)) {
     if (!_options.wasParsed(name)) return null;
 
     var value = _options[name];
@@ -291,24 +273,9 @@
     return _wrapFormatException(name, () => parse(value as String));
   }
 
-  Map<String, String> _parseFileReporterOption() =>
-      _parseOption('file-reporter', (value) {
-        if (!value.contains(':')) {
-          throw FormatException(
-              'option must be in the form <reporter>:<filepath>, e.g. '
-              '"json:reports/tests.json"');
-        }
-        final sep = value.indexOf(':');
-        final reporter = value.substring(0, sep);
-        if (!allReporters.containsKey(reporter)) {
-          throw FormatException('"$reporter" is not a supported reporter');
-        }
-        return {reporter: value.substring(sep + 1)};
-      });
-
   /// Runs [parse], and wraps any [FormatException] it throws with additional
   /// information.
-  T _wrapFormatException<T>(String name, T Function() parse) {
+  T _wrapFormatException<T>(String name, T parse()) {
     try {
       return parse();
     } on FormatException catch (error) {
diff --git a/test_core/lib/src/runner/configuration/load.dart b/test_core/lib/src/runner/configuration/load.dart
index d188b3e..fdb5c43 100644
--- a/test_core/lib/src/runner/configuration/load.dart
+++ b/test_core/lib/src/runner/configuration/load.dart
@@ -27,7 +27,7 @@
 /// A regular expression matching a Dart identifier.
 ///
 /// This also matches a package name, since they must be Dart identifiers.
-final _identifierRegExp = RegExp(r'[a-zA-Z_]\w*');
+final _identifierRegExp = RegExp(r"[a-zA-Z_]\w*");
 
 /// A regular expression matching allowed package names.
 ///
@@ -35,7 +35,7 @@
 /// compatibility with Google's internal Dart packages, but they may not be used
 /// when publishing a package to pub.dev.
 final _packageName =
-    RegExp('^${_identifierRegExp.pattern}(\\.${_identifierRegExp.pattern})*\$');
+    RegExp("^${_identifierRegExp.pattern}(\\.${_identifierRegExp.pattern})*\$");
 
 /// Loads configuration information from a YAML file at [path].
 ///
@@ -52,7 +52,7 @@
 
   if (document is! Map) {
     throw SourceSpanFormatException(
-        'The configuration must be a YAML map.', document.span, source);
+        "The configuration must be a YAML map.", document.span, source);
   }
 
   var loader =
@@ -91,14 +91,14 @@
   /// If an `include` node is contained in [node], merges and returns [config].
   Configuration _loadIncludeConfig() {
     if (!_runnerConfig) {
-      _disallow('include');
+      _disallow("include");
       return Configuration.empty;
     }
 
-    var includeNode = _document.nodes['include'];
+    var includeNode = _document.nodes["include"];
     if (includeNode == null) return Configuration.empty;
 
-    var includePath = _parseNode(includeNode, 'include path', p.fromUri);
+    var includePath = _parseNode(includeNode, "include path", p.fromUri);
     var basePath =
         p.join(p.dirname(p.fromUri(_document.span.sourceUrl)), includePath);
     try {
@@ -111,22 +111,22 @@
 
   /// Loads test configuration that's allowed in the global configuration file.
   Configuration _loadGlobalTestConfig() {
-    var verboseTrace = _getBool('verbose_trace');
-    var chainStackTraces = _getBool('chain_stack_traces');
+    var verboseTrace = _getBool("verbose_trace");
+    var chainStackTraces = _getBool("chain_stack_traces");
     var foldStackFrames = _loadFoldedStackFrames();
-    var jsTrace = _getBool('js_trace');
+    var jsTrace = _getBool("js_trace");
 
-    var timeout = _parseValue('timeout', (value) => Timeout.parse(value));
+    var timeout = _parseValue("timeout", (value) => Timeout.parse(value));
 
-    var onPlatform = _getMap('on_platform',
-        key: (keyNode) => _parseNode(keyNode, 'on_platform key',
+    var onPlatform = _getMap("on_platform",
+        key: (keyNode) => _parseNode(keyNode, "on_platform key",
             (value) => PlatformSelector.parse(value, keyNode.span)),
         value: (valueNode) =>
-            _nestedConfig(valueNode, 'on_platform value', runnerConfig: false));
+            _nestedConfig(valueNode, "on_platform value", runnerConfig: false));
 
-    var onOS = _getMap('on_os',
+    var onOS = _getMap("on_os",
         key: (keyNode) {
-          _validate(keyNode, 'on_os key must be a string.',
+          _validate(keyNode, "on_os key must be a string.",
               (value) => value is String);
 
           var os = OperatingSystem.find(keyNode.value as String);
@@ -137,11 +137,11 @@
               keyNode.span,
               _source);
         },
-        value: (valueNode) => _nestedConfig(valueNode, 'on_os value'));
+        value: (valueNode) => _nestedConfig(valueNode, "on_os value"));
 
-    var presets = _getMap('presets',
-        key: (keyNode) => _parseIdentifierLike(keyNode, 'presets key'),
-        value: (valueNode) => _nestedConfig(valueNode, 'presets value'));
+    var presets = _getMap("presets",
+        key: (keyNode) => _parseIdentifierLike(keyNode, "presets key"),
+        value: (valueNode) => _nestedConfig(valueNode, "presets value"));
 
     var config = Configuration(
             verboseTrace: verboseTrace,
@@ -149,8 +149,8 @@
             timeout: timeout,
             presets: presets,
             chainStackTraces: chainStackTraces,
-            foldTraceExcept: foldStackFrames['except'],
-            foldTraceOnly: foldStackFrames['only'])
+            foldTraceExcept: foldStackFrames["except"],
+            foldTraceOnly: foldStackFrames["only"])
         .merge(_extractPresets<PlatformSelector>(
             onPlatform, (map) => Configuration(onPlatform: map)));
 
@@ -165,15 +165,15 @@
   /// configuration fields.
   Configuration _loadLocalTestConfig() {
     if (_global) {
-      _disallow('skip');
-      _disallow('retry');
-      _disallow('test_on');
-      _disallow('add_tags');
-      _disallow('tags');
+      _disallow("skip");
+      _disallow("retry");
+      _disallow("test_on");
+      _disallow("add_tags");
+      _disallow("tags");
       return Configuration.empty;
     }
 
-    var skipRaw = _getValue('skip', 'boolean or string',
+    var skipRaw = _getValue("skip", "boolean or string",
         (value) => value is bool || value is String);
     String skipReason;
     bool skip;
@@ -184,18 +184,18 @@
       skip = skipRaw as bool;
     }
 
-    var testOn = _parsePlatformSelector('test_on');
+    var testOn = _parsePlatformSelector("test_on");
 
     var addTags = _getList(
-        'add_tags', (tagNode) => _parseIdentifierLike(tagNode, 'Tag name'));
+        "add_tags", (tagNode) => _parseIdentifierLike(tagNode, "Tag name"));
 
-    var tags = _getMap('tags',
+    var tags = _getMap("tags",
         key: (keyNode) => _parseNode(
-            keyNode, 'tags key', (value) => BooleanSelector.parse(value)),
+            keyNode, "tags key", (value) => BooleanSelector.parse(value)),
         value: (valueNode) =>
-            _nestedConfig(valueNode, 'tag value', runnerConfig: false));
+            _nestedConfig(valueNode, "tag value", runnerConfig: false));
 
-    var retry = _getNonNegativeInt('retry');
+    var retry = _getNonNegativeInt("retry");
 
     return Configuration(
             skip: skip,
@@ -214,66 +214,47 @@
   /// runner-level configuration fields.
   Configuration _loadGlobalRunnerConfig() {
     if (!_runnerConfig) {
-      _disallow('pause_after_load');
-      _disallow('reporter');
-      _disallow('file_reporters');
-      _disallow('concurrency');
-      _disallow('names');
-      _disallow('plain_names');
-      _disallow('platforms');
-      _disallow('add_presets');
-      _disallow('override_platforms');
-      _disallow('include');
+      _disallow("pause_after_load");
+      _disallow("reporter");
+      _disallow("concurrency");
+      _disallow("names");
+      _disallow("plain_names");
+      _disallow("platforms");
+      _disallow("add_presets");
+      _disallow("override_platforms");
+      _disallow("include");
       return Configuration.empty;
     }
 
-    var pauseAfterLoad = _getBool('pause_after_load');
-    var runSkipped = _getBool('run_skipped');
+    var pauseAfterLoad = _getBool("pause_after_load");
+    var runSkipped = _getBool("run_skipped");
 
-    var reporter = _getString('reporter');
+    var reporter = _getString("reporter");
     if (reporter != null && !allReporters.keys.contains(reporter)) {
-      _error('Unknown reporter "$reporter".', 'reporter');
+      _error('Unknown reporter "$reporter".', "reporter");
     }
 
-    var fileReporters = _getMap('file_reporters', key: (keyNode) {
-      _validate(keyNode, 'file_reporters key must be a string',
-          (value) => value is String);
-      final reporter = keyNode.value as String;
-      if (!allReporters.keys.contains(reporter)) {
-        _error('Unknown reporter "$reporter".', 'file_reporters');
-      }
-      return reporter;
-    }, value: (valueNode) {
-      _validate(valueNode, 'file_reporters value must be a string',
-          (value) => value is String);
-      return valueNode.value as String;
-    });
-
-    var concurrency = _getInt('concurrency');
+    var concurrency = _getInt("concurrency");
 
     // The UI term "platform" corresponds with the implementation term
     // "runtime". The [Runtime] class used to be called [TestPlatform], but it
     // was changed to avoid conflicting with [SuitePlatform]. We decided not to
     // also change the UI to avoid a painful migration.
     var runtimes = _getList(
-        'platforms',
+        "platforms",
         (runtimeNode) => RuntimeSelection(
-            _parseIdentifierLike(runtimeNode, 'Platform name'),
+            _parseIdentifierLike(runtimeNode, "Platform name"),
             runtimeNode.span));
 
-    var chosenPresets = _getList('add_presets',
-        (presetNode) => _parseIdentifierLike(presetNode, 'Preset name'));
+    var chosenPresets = _getList("add_presets",
+        (presetNode) => _parseIdentifierLike(presetNode, "Preset name"));
 
     var overrideRuntimes = _loadOverrideRuntimes();
 
-    var customHtmlTemplatePath = _getString('custom_html_template_path');
-
     return Configuration(
         pauseAfterLoad: pauseAfterLoad,
-        customHtmlTemplatePath: customHtmlTemplatePath,
         runSkipped: runSkipped,
         reporter: reporter,
-        fileReporters: fileReporters,
         concurrency: concurrency,
         runtimes: runtimes,
         chosenPresets: chosenPresets,
@@ -283,21 +264,21 @@
   /// Loads the `override_platforms` field.
   Map<String, RuntimeSettings> _loadOverrideRuntimes() {
     var runtimesNode =
-        _getNode('override_platforms', 'map', (value) => value is Map)
+        _getNode("override_platforms", "map", (value) => value is Map)
             as YamlMap;
     if (runtimesNode == null) return const {};
 
     var runtimes = <String, RuntimeSettings>{};
     runtimesNode.nodes.forEach((identifierNode, valueNode) {
       var identifier = _parseIdentifierLike(
-          identifierNode as YamlNode, 'Platform identifier');
+          identifierNode as YamlNode, "Platform identifier");
 
-      _validate(valueNode, 'Platform definition must be a map.',
+      _validate(valueNode, "Platform definition must be a map.",
           (value) => value is Map);
       var map = valueNode as YamlMap;
 
-      var settings = _expect(map, 'settings');
-      _validate(settings, 'Must be a map.', (value) => value is Map);
+      var settings = _expect(map, "settings");
+      _validate(settings, "Must be a map.", (value) => value is Map);
 
       runtimes[identifier] = RuntimeSettings(
           identifier, (identifierNode as YamlNode).span, [settings as YamlMap]);
@@ -312,41 +293,41 @@
   /// if there are any local test-level configuration fields.
   Configuration _loadLocalRunnerConfig() {
     if (!_runnerConfig || _global) {
-      _disallow('pub_serve');
-      _disallow('names');
-      _disallow('plain_names');
-      _disallow('paths');
-      _disallow('filename');
-      _disallow('include_tags');
-      _disallow('exclude_tags');
-      _disallow('define_platforms');
+      _disallow("pub_serve");
+      _disallow("names");
+      _disallow("plain_names");
+      _disallow("paths");
+      _disallow("filename");
+      _disallow("include_tags");
+      _disallow("exclude_tags");
+      _disallow("define_platforms");
       return Configuration.empty;
     }
 
-    var pubServePort = _getInt('pub_serve');
+    var pubServePort = _getInt("pub_serve");
 
-    var patterns = _getList('names', (nameNode) {
-      _validate(nameNode, 'Names must be strings.', (value) => value is String);
-      return _parseNode(nameNode, 'name', (value) => RegExp(value));
+    var patterns = _getList("names", (nameNode) {
+      _validate(nameNode, "Names must be strings.", (value) => value is String);
+      return _parseNode(nameNode, "name", (value) => RegExp(value));
     })
-      ..addAll(_getList('plain_names', (nameNode) {
+      ..addAll(_getList("plain_names", (nameNode) {
         _validate(
-            nameNode, 'Names must be strings.', (value) => value is String);
-        return _parseNode(nameNode, 'name', (value) => RegExp(value));
+            nameNode, "Names must be strings.", (value) => value is String);
+        return _parseNode(nameNode, "name", (value) => RegExp(value));
       }));
 
-    var paths = _getList('paths', (pathNode) {
-      _validate(pathNode, 'Paths must be strings.', (value) => value is String);
-      _validate(pathNode, 'Paths must be relative.',
+    var paths = _getList("paths", (pathNode) {
+      _validate(pathNode, "Paths must be strings.", (value) => value is String);
+      _validate(pathNode, "Paths must be relative.",
           (value) => p.url.isRelative(value as String));
 
-      return _parseNode(pathNode, 'path', p.fromUri);
+      return _parseNode(pathNode, "path", p.fromUri);
     });
 
-    var filename = _parseValue('filename', (value) => Glob(value));
+    var filename = _parseValue("filename", (value) => Glob(value));
 
-    var includeTags = _parseBooleanSelector('include_tags');
-    var excludeTags = _parseBooleanSelector('exclude_tags');
+    var includeTags = _parseBooleanSelector("include_tags");
+    var excludeTags = _parseBooleanSelector("exclude_tags");
 
     var defineRuntimes = _loadDefineRuntimes();
 
@@ -367,10 +348,10 @@
   /// test [Chain].
   Map<String, List<String>> _loadFoldedStackFrames() {
     var foldOptionSet = false;
-    return _getMap('fold_stack_frames', key: (keyNode) {
-      _validate(keyNode, 'Must be a string', (value) => value is String);
+    return _getMap("fold_stack_frames", key: (keyNode) {
+      _validate(keyNode, "Must be a string", (value) => value is String);
       _validate(keyNode, 'Must be "only" or "except".',
-          (value) => value == 'only' || value == 'except');
+          (value) => value == "only" || value == "except");
 
       if (foldOptionSet) {
         throw SourceSpanFormatException(
@@ -383,14 +364,14 @@
     }, value: (valueNode) {
       _validate(
           valueNode,
-          'Folded packages must be strings.',
+          "Folded packages must be strings.",
           (valueList) =>
               valueList is YamlList &&
               valueList.every((value) => value is String));
 
       _validate(
           valueNode,
-          'Invalid package name.',
+          "Invalid package name.",
           (valueList) => (valueList as Iterable)
               .every((value) => _packageName.hasMatch(value as String)));
 
@@ -401,27 +382,27 @@
   /// Loads the `define_platforms` field.
   Map<String, CustomRuntime> _loadDefineRuntimes() {
     var runtimesNode =
-        _getNode('define_platforms', 'map', (value) => value is Map) as YamlMap;
+        _getNode("define_platforms", "map", (value) => value is Map) as YamlMap;
     if (runtimesNode == null) return const {};
 
     var runtimes = <String, CustomRuntime>{};
     runtimesNode.nodes.forEach((identifierNode, valueNode) {
       var identifier = _parseIdentifierLike(
-          identifierNode as YamlNode, 'Platform identifier');
+          identifierNode as YamlNode, "Platform identifier");
 
-      _validate(valueNode, 'Platform definition must be a map.',
+      _validate(valueNode, "Platform definition must be a map.",
           (value) => value is Map);
       var map = valueNode as YamlMap;
 
-      var nameNode = _expect(map, 'name');
-      _validate(nameNode, 'Must be a string.', (value) => value is String);
+      var nameNode = _expect(map, "name");
+      _validate(nameNode, "Must be a string.", (value) => value is String);
       var name = nameNode.value as String;
 
-      var parentNode = _expect(map, 'extends');
-      var parent = _parseIdentifierLike(parentNode, 'Platform parent');
+      var parentNode = _expect(map, "extends");
+      var parent = _parseIdentifierLike(parentNode, "Platform parent");
 
-      var settings = _expect(map, 'settings');
-      _validate(settings, 'Must be a map.', (value) => value is Map);
+      var settings = _expect(map, "settings");
+      _validate(settings, "Must be a map.", (value) => value is Map);
 
       runtimes[identifier] = CustomRuntime(
           name,
@@ -437,7 +418,7 @@
 
   /// Throws an exception with [message] if [test] returns `false` when passed
   /// [node]'s value.
-  void _validate(YamlNode node, String message, bool Function(dynamic) test) {
+  void _validate(YamlNode node, String message, bool test(value)) {
     if (test(node.value)) return;
     throw SourceSpanFormatException(message, node.span, _source);
   }
@@ -446,47 +427,45 @@
   ///
   /// If [typeTest] returns `false` for that value, instead throws an error
   /// complaining that the field is not a [typeName].
-  dynamic _getValue(
-      String field, String typeName, bool Function(dynamic) typeTest) {
+  _getValue(String field, String typeName, bool typeTest(value)) {
     var value = _document[field];
     if (value == null || typeTest(value)) return value;
-    _error('$field must be ${a(typeName)}.', field);
+    _error("$field must be ${a(typeName)}.", field);
   }
 
   /// Returns the YAML node at [field].
   ///
   /// If [typeTest] returns `false` for that node's value, instead throws an
   /// error complaining that the field is not a [typeName].
-  YamlNode _getNode(
-      String field, String typeName, bool Function(dynamic) typeTest) {
+  YamlNode _getNode(String field, String typeName, bool typeTest(value)) {
     var node = _document.nodes[field];
     if (node == null) return null;
-    _validate(node, '$field must be ${a(typeName)}.', typeTest);
+    _validate(node, "$field must be ${a(typeName)}.", typeTest);
     return node;
   }
 
   /// Asserts that [field] is an int and returns its value.
   int _getInt(String field) =>
-      _getValue(field, 'int', (value) => value is int) as int;
+      _getValue(field, "int", (value) => value is int) as int;
 
   /// Asserts that [field] is a non-negative int and returns its value.
   int _getNonNegativeInt(String field) => _getValue(
-      field, 'non-negative int', (value) => value is int && value >= 0) as int;
+      field, "non-negative int", (value) => value is int && value >= 0) as int;
 
   /// Asserts that [field] is a boolean and returns its value.
   bool _getBool(String field) =>
-      _getValue(field, 'boolean', (value) => value is bool) as bool;
+      _getValue(field, "boolean", (value) => value is bool) as bool;
 
   /// Asserts that [field] is a string and returns its value.
   String _getString(String field) =>
-      _getValue(field, 'string', (value) => value is String) as String;
+      _getValue(field, "string", (value) => value is String) as String;
 
   /// Asserts that [field] is a list and runs [forElement] for each element it
   /// contains.
   ///
   /// Returns a list of values returned by [forElement].
-  List<T> _getList<T>(String field, T Function(YamlNode) forElement) {
-    var node = _getNode(field, 'list', (value) => value is List) as YamlList;
+  List<T> _getList<T>(String field, T forElement(YamlNode elementNode)) {
+    var node = _getNode(field, "list", (value) => value is List) as YamlList;
     if (node == null) return [];
     return node.nodes.map(forElement).toList();
   }
@@ -496,19 +475,19 @@
   /// Returns a map with the keys and values returned by [key] and [value]. Each
   /// of these defaults to asserting that the value is a string.
   Map<K, V> _getMap<K, V>(String field,
-      {K Function(YamlNode) key, V Function(YamlNode) value}) {
-    var node = _getNode(field, 'map', (value) => value is Map) as YamlMap;
+      {K key(YamlNode keyNode), V value(YamlNode valueNode)}) {
+    var node = _getNode(field, "map", (value) => value is Map) as YamlMap;
     if (node == null) return {};
 
     key ??= (keyNode) {
       _validate(
-          keyNode, '$field keys must be strings.', (value) => value is String);
+          keyNode, "$field keys must be strings.", (value) => value is String);
 
       return keyNode.value as K;
     };
 
     value ??= (valueNode) {
-      _validate(valueNode, '$field values must be strings.',
+      _validate(valueNode, "$field values must be strings.",
           (value) => value is String);
 
       return valueNode.value as V;
@@ -521,8 +500,8 @@
   /// Verifies that [node]'s value is an optionally hyphenated Dart identifier,
   /// and returns it
   String _parseIdentifierLike(YamlNode node, String name) {
-    _validate(node, '$name must be a string.', (value) => value is String);
-    _validate(node, '$name must be an (optionally hyphenated) Dart identifier.',
+    _validate(node, "$name must be a string.", (value) => value is String);
+    _validate(node, "$name must be an (optionally hyphenated) Dart identifier.",
         (value) => (value as String).contains(anchoredHyphenatedIdentifier));
     return node.value as String;
   }
@@ -544,8 +523,8 @@
   ///
   /// If [parse] throws a [FormatException], it's wrapped to include [node]'s
   /// span.
-  T _parseNode<T>(YamlNode node, String name, T Function(String) parse) {
-    _validate(node, '$name must be a string.', (value) => value is String);
+  T _parseNode<T>(YamlNode node, String name, T parse(String value)) {
+    _validate(node, "$name must be a string.", (value) => value is String);
 
     try {
       return parse(node.value as String);
@@ -560,7 +539,7 @@
   ///
   /// If [parse] throws a [FormatException], it's wrapped to include [field]'s
   /// span.
-  T _parseValue<T>(String field, T Function(String) parse) {
+  T _parseValue<T>(String field, T parse(String value)) {
     var node = _document.nodes[field];
     if (node == null) return null;
     return _parseNode(node, field, parse);
@@ -574,7 +553,7 @@
   Configuration _nestedConfig(YamlNode node, String name, {bool runnerConfig}) {
     if (node == null || node.value == null) return Configuration.empty;
 
-    _validate(node, '$name must be a map.', (value) => value is Map);
+    _validate(node, "$name must be a map.", (value) => value is Map);
     var loader = _ConfigurationLoader(node as YamlMap, _source,
         global: _global, runnerConfig: runnerConfig ?? _runnerConfig);
     return loader.load();
@@ -590,7 +569,7 @@
   /// [SuiteConfiguration]s. The [create] function is used to construct
   /// [Configuration]s from the resolved maps.
   Configuration _extractPresets<T>(Map<T, Configuration> map,
-      Configuration Function(Map<T, SuiteConfiguration>) create) {
+      Configuration create(Map<T, SuiteConfiguration> map)) {
     if (map.isEmpty) return Configuration.empty;
 
     var base = <T, SuiteConfiguration>{};
diff --git a/test_core/lib/src/runner/configuration/reporters.dart b/test_core/lib/src/runner/configuration/reporters.dart
index dc557cf..7fe1656 100644
--- a/test_core/lib/src/runner/configuration/reporters.dart
+++ b/test_core/lib/src/runner/configuration/reporters.dart
@@ -29,17 +29,17 @@
     UnmodifiableMapView<String, ReporterDetails>(_allReporters);
 
 final _allReporters = <String, ReporterDetails>{
-  'expanded': ReporterDetails(
-      'A separate line for each update.',
+  "expanded": ReporterDetails(
+      "A separate line for each update.",
       (config, engine, sink) => ExpandedReporter.watch(engine, sink,
           color: config.color,
           printPath: config.paths.length > 1 ||
               Directory(config.paths.single).existsSync(),
           printPlatform: config.suiteDefaults.runtimes.length > 1)),
-  'compact': ReporterDetails('A single line, updated continuously.',
+  "compact": ReporterDetails("A single line, updated continuously.",
       (_, engine, sink) => CompactReporter.watch(engine, sink)),
-  'json': ReporterDetails(
-      'A machine-readable format (see https://goo.gl/gBsV1a).',
+  "json": ReporterDetails(
+      "A machine-readable format (see https://goo.gl/gBsV1a).",
       (_, engine, sink) => JsonReporter.watch(engine, sink)),
 };
 
diff --git a/test_core/lib/src/runner/configuration/values.dart b/test_core/lib/src/runner/configuration/values.dart
index 6e2f50a..37abc16 100644
--- a/test_core/lib/src/runner/configuration/values.dart
+++ b/test_core/lib/src/runner/configuration/values.dart
@@ -16,4 +16,4 @@
 /// The default filename pattern.
 ///
 /// This is stored here so that we don't have to recompile it multiple times.
-final defaultFilename = Glob('*_test.dart');
+final defaultFilename = Glob("*_test.dart");
diff --git a/test_core/lib/src/runner/console.dart b/test_core/lib/src/runner/console.dart
index 1592d6d..85fd112 100644
--- a/test_core/lib/src/runner/console.dart
+++ b/test_core/lib/src/runner/console.dart
@@ -6,7 +6,8 @@
 import 'dart:math' as math;
 
 import 'package:async/async.dart';
-import 'package:pedantic/pedantic.dart';
+
+import 'package:test_api/src/utils.dart'; // ignore: implementation_imports
 
 import '../util/io.dart';
 
@@ -40,7 +41,7 @@
       : _red = color ? '\u001b[31m' : '',
         _bold = color ? '\u001b[1m' : '',
         _noColor = color ? '\u001b[0m' : '' {
-    registerCommand('help', 'Displays this help information.', _displayHelp);
+    registerCommand("help", "Displays this help information.", _displayHelp);
   }
 
   /// Registers a command to be run whenever the user types [name].
@@ -48,8 +49,7 @@
   /// The [description] should be a one-line description of the command to print
   /// in the help output. The [body] callback will be called when the user types
   /// the command, and may return a [Future].
-  void registerCommand(
-      String name, String description, dynamic Function() body) {
+  void registerCommand(String name, String description, body()) {
     if (_commands.containsKey(name)) {
       throw ArgumentError('The console already has a command named "$name".');
     }
@@ -62,9 +62,9 @@
   /// This prints the initial prompt and loops while waiting for user input.
   void start() {
     _running = true;
-    unawaited(() async {
+    invoke(() async {
       while (_running) {
-        stdout.write('> ');
+        stdout.write("> ");
         _nextLine = stdinLines.cancelable((queue) => queue.next);
         var commandName = await _nextLine.value;
         _nextLine = null;
@@ -72,13 +72,13 @@
         var command = _commands[commandName];
         if (command == null) {
           stderr.writeln(
-              '${_red}Unknown command $_bold$commandName$_noColor$_red.'
-              '$_noColor');
+              "${_red}Unknown command $_bold$commandName$_noColor$_red."
+              "$_noColor");
         } else {
           await command.body();
         }
       }
-    }());
+    });
   }
 
   /// Stops the console running.
@@ -97,7 +97,7 @@
 
     for (var command in _commands.values) {
       var name = command.name.padRight(maxCommandLength + 4);
-      print('$_bold$name$_noColor${command.description}');
+      print("$_bold$name$_noColor${command.description}");
     }
   }
 }
diff --git a/test_core/lib/src/runner/coverage.dart b/test_core/lib/src/runner/coverage.dart
index 841bdd7..7dc2184 100644
--- a/test_core/lib/src/runner/coverage.dart
+++ b/test_core/lib/src/runner/coverage.dart
@@ -5,20 +5,30 @@
 import 'dart:convert';
 import 'dart:io';
 
+import 'package:coverage/coverage.dart';
 import 'package:path/path.dart' as p;
 
 import 'live_suite_controller.dart';
+import 'runner_suite.dart';
 
-/// Collects coverage and outputs to the [coveragePath] path.
-Future<void> writeCoverage(
-    String coveragePath, LiveSuiteController controller) async {
-  var suite = controller.liveSuite.suite;
-  var coverage = await controller.liveSuite.suite.gatherCoverage();
-  final outfile = File(p.join(coveragePath,
-      '${suite.path}.${suite.platform.runtime.name.toLowerCase()}.json'))
+/// Collects coverage and outputs to the [coverage] path.
+Future<void> gatherCoverage(
+    String coverage, LiveSuiteController controller) async {
+  final RunnerSuite suite = controller.liveSuite.suite;
+
+  if (!suite.platform.runtime.isDartVM) return;
+
+  final String isolateId = Uri.parse(suite.environment.observatoryUrl.fragment)
+      .queryParameters['isolateId'];
+
+  final cov = await collect(
+      suite.environment.observatoryUrl, false, false, false, Set(),
+      isolateIds: {isolateId});
+
+  final outfile = File(p.join('$coverage', '${suite.path}.vm.json'))
     ..createSync(recursive: true);
-  final out = outfile.openWrite();
-  out.write(json.encode(coverage));
+  final IOSink out = outfile.openWrite();
+  out.write(json.encode(cov));
   await out.flush();
   await out.close();
 }
diff --git a/test_core/lib/src/runner/coverage_stub.dart b/test_core/lib/src/runner/coverage_stub.dart
index 64f69c7..5a2285f 100644
--- a/test_core/lib/src/runner/coverage_stub.dart
+++ b/test_core/lib/src/runner/coverage_stub.dart
@@ -4,7 +4,6 @@
 
 import 'live_suite_controller.dart';
 
-Future<void> writeCoverage(
-        String coveragePath, LiveSuiteController controller) =>
+Future<void> gatherCoverage(String coverage, LiveSuiteController controller) =>
     throw UnsupportedError(
         'Coverage is only supported through the test runner.');
diff --git a/test_core/lib/src/runner/debugger.dart b/test_core/lib/src/runner/debugger.dart
index 524f08e..e273d1c 100644
--- a/test_core/lib/src/runner/debugger.dart
+++ b/test_core/lib/src/runner/debugger.dart
@@ -89,8 +89,8 @@
 
   _Debugger(this._engine, this._reporter, this._suite)
       : _console = Console(color: Configuration.current.color) {
-    _console.registerCommand('restart',
-        'Restart the current test after it finishes running.', _restartTest);
+    _console.registerCommand("restart",
+        "Restart the current test after it finishes running.", _restartTest);
 
     _onRestartSubscription = _suite.environment.onRestart.listen((_) {
       _restartTest();
@@ -141,35 +141,35 @@
           var url = _suite.environment.observatoryUrl;
           if (url == null) {
             print("${yellow}Observatory URL not found. Make sure you're using "
-                '${runtime.name} 1.11 or later.$noColor');
+                "${runtime.name} 1.11 or later.$noColor");
           } else {
-            print('Observatory URL: $bold$url$noColor');
+            print("Observatory URL: $bold$url$noColor");
           }
         }
 
         if (runtime.isHeadless && !runtime.isDartVM) {
           var url = _suite.environment.remoteDebuggerUrl;
           if (url == null) {
-            print('${yellow}Remote debugger URL not found.$noColor');
+            print("${yellow}Remote debugger URL not found.$noColor");
           } else {
-            print('Remote debugger URL: $bold$url$noColor');
+            print("Remote debugger URL: $bold$url$noColor");
           }
         }
 
         var buffer =
-            StringBuffer('${bold}The test runner is paused.${noColor} ');
+            StringBuffer("${bold}The test runner is paused.${noColor} ");
         if (runtime.isDartVM) {
-          buffer.write('Open the Observatory ');
+          buffer.write("Open the Observatory ");
         } else {
           if (!runtime.isHeadless) {
-            buffer.write('Open the dev console in $runtime ');
+            buffer.write("Open the dev console in $runtime ");
           } else {
-            buffer.write('Open the remote debugger ');
+            buffer.write("Open the remote debugger ");
           }
         }
 
         buffer.write("and set breakpoints. Once you're finished, return to "
-            'this terminal and press Enter.');
+            "this terminal and press Enter.");
 
         print(wordWrap(buffer.toString()));
       }
diff --git a/test_core/lib/src/runner/engine.dart b/test_core/lib/src/runner/engine.dart
index a60ea7f..812d7a8 100644
--- a/test_core/lib/src/runner/engine.dart
+++ b/test_core/lib/src/runner/engine.dart
@@ -73,6 +73,12 @@
   /// A pool that limits the number of test suites running concurrently.
   final Pool _runPool;
 
+  /// A pool that limits the number of test suites loaded concurrently.
+  ///
+  /// Once this reaches its limit, loading any additional test suites will cause
+  /// previous suites to be unloaded in the order they completed.
+  final Pool _loadPool;
+
   /// A completer that will complete when [this] is unpaused.
   ///
   /// If [this] isn't paused, [_pauseCompleter] is `null`.
@@ -90,7 +96,8 @@
   /// This will be `null` if [close] was called before all the tests finished
   /// running.
   Future<bool> get success async {
-    await Future.wait(<Future>[_group.future, _runPool.done], eagerError: true);
+    await Future.wait(<Future>[_group.future, _loadPool.done],
+        eagerError: true);
     if (_closedBeforeDone) return null;
     return liveTests.every((liveTest) =>
         liveTest.state.result.isPassing &&
@@ -101,7 +108,7 @@
   final _group = FutureGroup();
 
   /// All of the engine's stream subscriptions.
-  final _subscriptions = <StreamSubscription>{};
+  final _subscriptions = Set<StreamSubscription>();
 
   /// A sink used to pass [RunnerSuite]s in to the engine to run.
   ///
@@ -118,7 +125,7 @@
   /// Note that if a [LoadSuite] is added, this will only contain that suite,
   /// not the suite it loads.
   Set<RunnerSuite> get addedSuites => UnmodifiableSetView(_addedSuites);
-  final _addedSuites = <RunnerSuite>{};
+  final _addedSuites = Set<RunnerSuite>();
 
   /// A broadcast stream that emits each [RunnerSuite] as it's added to the
   /// engine via [suiteSink].
@@ -139,7 +146,7 @@
   /// [LoadSuite]s, both the [LoadSuite] and the suite it loads will eventually
   /// be in this set.
   Set<LiveSuite> get liveSuites => UnmodifiableSetView(_liveSuites);
-  final _liveSuites = <LiveSuite>{};
+  final _liveSuites = Set<LiveSuite>();
 
   /// A broadcast stream that emits each [LiveSuite] as it's loaded.
   ///
@@ -189,13 +196,13 @@
   ///
   /// This is always a subset of [active]. Once a test in here has finished
   /// running, it's run again.
-  final _restarted = <LiveTest>{};
+  final _restarted = Set<LiveTest>();
 
   /// The tests from [LoadSuite]s that are still running, in the order they
   /// began running.
   ///
   /// This is separate from [active] because load tests aren't always surfaced.
-  final _activeLoadTests = <LiveTest>{};
+  final _activeLoadTests = List<LiveTest>();
 
   /// Whether this engine is idle—that is, not currently executing a test.
   bool get isIdle => _group.isIdle;
@@ -208,15 +215,17 @@
   // dart:io is unavailable.
   /// Creates an [Engine] that will run all tests provided via [suiteSink].
   ///
-  /// [concurrency] controls how many suites are loaded and ran at once, and
-  /// defaults to 1.
-  Engine({int concurrency, String coverage})
+  /// [concurrency] controls how many suites are run at once, and defaults to 1.
+  /// [maxSuites] controls how many suites are *loaded* at once, and defaults to
+  /// four times [concurrency].
+  Engine({int concurrency, int maxSuites, String coverage})
       : _runPool = Pool(concurrency ?? 1),
+        _loadPool = Pool(maxSuites ?? (concurrency ?? 1) * 2),
         _coverage = coverage {
     _group.future.then((_) {
       _onTestStartedGroup.close();
       _onSuiteStartedController.close();
-      _closedBeforeDone ??= false;
+      if (_closedBeforeDone == null) _closedBeforeDone = false;
     }).catchError((_) {
       // Don't top-level errors. They'll be thrown via [success] anyway.
     });
@@ -246,7 +255,7 @@
   /// closed.
   Future<bool> run() {
     if (_runCalled) {
-      throw StateError('Engine.run() may not be called more than once.');
+      throw StateError("Engine.run() may not be called more than once.");
     }
     _runCalled = true;
 
@@ -256,32 +265,35 @@
       _onSuiteAddedController.add(suite);
 
       _group.add(() async {
-        var resource = await _runPool.request();
+        var loadResource = await _loadPool.request();
+
         LiveSuiteController controller;
-        try {
-          if (suite is LoadSuite) {
-            await _onUnpaused;
-            controller = await _addLoadSuite(suite);
-            if (controller == null) return;
-          } else {
-            controller = LiveSuiteController(suite);
+        if (suite is LoadSuite) {
+          await _onUnpaused;
+          controller = await _addLoadSuite(suite);
+          if (controller == null) {
+            loadResource.release();
+            return;
           }
+        } else {
+          controller = LiveSuiteController(suite);
+        }
 
-          _addLiveSuite(controller.liveSuite);
+        _addLiveSuite(controller.liveSuite);
 
+        await _runPool.withResource(() async {
           if (_closed) return;
           await _runGroup(controller, controller.liveSuite.suite.group, []);
           controller.noMoreLiveTests();
-          if (_coverage != null) await writeCoverage(_coverage, controller);
-        } finally {
-          resource.allowRelease(() => controller?.close());
-        }
+          if (_coverage != null) await gatherCoverage(_coverage, controller);
+          loadResource.allowRelease(() => controller.close());
+        });
       }());
     }, onDone: () {
       _subscriptions.remove(subscription);
       _onSuiteAddedController.close();
       _group.close();
-      _runPool.close();
+      _loadPool.close();
     });
     _subscriptions.add(subscription);
 
@@ -311,8 +323,7 @@
       if (!_closed && setUpAllSucceeded) {
         // shuffle the group entries
         var entries = group.entries.toList();
-        if (suiteConfig.testRandomizeOrderingSeed != null &&
-            suiteConfig.testRandomizeOrderingSeed > 0) {
+        if (suiteConfig.testRandomizeOrderingSeed > 0) {
           entries.shuffle(Random(suiteConfig.testRandomizeOrderingSeed));
         }
 
@@ -405,7 +416,7 @@
 
       if (skipped.metadata.skipReason != null) {
         controller
-            .message(Message.skip('Skip: ${skipped.metadata.skipReason}'));
+            .message(Message.skip("Skip: ${skipped.metadata.skipReason}"));
       }
 
       controller.setState(const State(Status.complete, Result.skipped));
@@ -426,7 +437,7 @@
 
     if (!_active.contains(liveTest)) {
       throw StateError("Can't restart inactive test "
-          '\"${liveTest.test.name}\".');
+          "\"${liveTest.test.name}\".");
     }
 
     _restarted.add(liveTest);
@@ -534,21 +545,23 @@
   /// Closing [suiteSink] indicates that no more input will be provided, closing
   /// the engine indicates that no more output should be emitted.
   Future close() async {
+    // TODO(grouma) - Remove this unecessary await.
+    await Future(() {});
     _closed = true;
     if (_closedBeforeDone != null) _closedBeforeDone = true;
-    await _suiteController.close();
     await _onSuiteAddedController.close();
+    await _suiteController.close();
 
     // Close the running tests first so that we're sure to wait for them to
     // finish before we close their suites and cause them to become unloaded.
     var allLiveTests = liveTests.toSet()..addAll(_activeLoadTests);
     var futures = allLiveTests.map((liveTest) => liveTest.close()).toList();
 
-    // Closing the run pool will close the test suites as soon as their tests
+    // Closing the load pool will close the test suites as soon as their tests
     // are done. For browser suites this is effectively immediate since their
     // tests shut down as soon as they're closed, but for VM suites we may need
     // to wait for tearDowns or tearDownAlls to run.
-    futures.add(_runPool.close());
+    futures.add(_loadPool.close());
     await Future.wait(futures, eagerError: true);
   }
 }
diff --git a/test_core/lib/src/runner/environment.dart b/test_core/lib/src/runner/environment.dart
index 2ddfdb3..164aaa4 100644
--- a/test_core/lib/src/runner/environment.dart
+++ b/test_core/lib/src/runner/environment.dart
@@ -36,20 +36,15 @@
 
 /// The default environment for platform plugins.
 class PluginEnvironment implements Environment {
-  @override
   final supportsDebugging = false;
-  @override
   Stream get onRestart => StreamController.broadcast().stream;
 
   const PluginEnvironment();
 
-  @override
   Uri get observatoryUrl => null;
 
-  @override
   Uri get remoteDebuggerUrl => null;
 
-  @override
   CancelableOperation displayPause() => throw UnsupportedError(
-      'PluginEnvironment.displayPause is not supported.');
+      "PluginEnvironment.displayPause is not supported.");
 }
diff --git a/test_core/lib/src/runner/hybrid_listener.dart b/test_core/lib/src/runner/hybrid_listener.dart
index 022405b..c818a59 100644
--- a/test_core/lib/src/runner/hybrid_listener.dart
+++ b/test_core/lib/src/runner/hybrid_listener.dart
@@ -2,14 +2,14 @@
 // 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 'dart:async';
-import 'dart:isolate';
+import "dart:async";
+import "dart:isolate";
 
-import 'package:async/async.dart';
-import 'package:stack_trace/stack_trace.dart';
-import 'package:stream_channel/isolate_channel.dart';
-import 'package:stream_channel/stream_channel.dart';
-import 'package:test_api/src/util/remote_exception.dart'; // ignore: implementation_imports
+import "package:async/async.dart";
+import "package:stack_trace/stack_trace.dart";
+import "package:stream_channel/isolate_channel.dart";
+import "package:stream_channel/stream_channel.dart";
+import "package:test_api/src/util/remote_exception.dart"; // ignore: implementation_imports
 import 'package:test_api/src/utils.dart'; // ignore: implementation_imports
 
 /// A sink transformer that wraps data and error events so that errors can be
@@ -17,10 +17,10 @@
 final _transformer = StreamSinkTransformer<dynamic, dynamic>.fromHandlers(
     handleData: (data, sink) {
   ensureJsonEncodable(data);
-  sink.add({'type': 'data', 'data': data});
+  sink.add({"type": "data", "data": data});
 }, handleError: (error, stackTrace, sink) {
   sink.add(
-      {'type': 'error', 'error': RemoteException.serialize(error, stackTrace)});
+      {"type": "error", "error": RemoteException.serialize(error, stackTrace)});
 });
 
 /// Runs the body of a hybrid isolate and communicates its messages, errors, and
@@ -32,7 +32,7 @@
 ///
 /// The [data] argument contains two values: a [SendPort] that communicates with
 /// the main isolate, and a message to pass to `hybridMain()`.
-void listen(Function Function() getMain, List data) {
+void listen(Function getMain(), List data) {
   var channel = IsolateChannel.connectSend(data.first as SendPort);
   var message = data.last;
 
@@ -42,7 +42,7 @@
       try {
         main = getMain();
       } on NoSuchMethodError catch (_) {
-        _sendError(channel, 'No top-level hybridMain() function defined.');
+        _sendError(channel, "No top-level hybridMain() function defined.");
         return;
       } catch (error, stackTrace) {
         _sendError(channel, error, stackTrace);
@@ -50,12 +50,12 @@
       }
 
       if (main is! Function) {
-        _sendError(channel, 'Top-level hybridMain is not a function.');
+        _sendError(channel, "Top-level hybridMain is not a function.");
         return;
       } else if (main is! Function(StreamChannel) &&
           main is! Function(StreamChannel, Null)) {
         _sendError(channel,
-            'Top-level hybridMain() function must take one or two arguments.');
+            "Top-level hybridMain() function must take one or two arguments.");
         return;
       }
 
@@ -69,19 +69,19 @@
         main(transformedChannel, message);
       }
     }, zoneSpecification: ZoneSpecification(print: (_, __, ___, line) {
-      channel.sink.add({'type': 'print', 'line': line});
+      channel.sink.add({"type": "print", "line": line});
     }));
   }, onError: (error, stackTrace) async {
     _sendError(channel, error, stackTrace);
     await channel.sink.close();
-    Isolate.current.kill();
+    Zone.current.handleUncaughtError(error, stackTrace);
   });
 }
 
 /// Sends a message over [channel] indicating an error from user code.
 void _sendError(StreamChannel channel, error, [StackTrace stackTrace]) {
   channel.sink.add({
-    'type': 'error',
-    'error': RemoteException.serialize(error, stackTrace ?? Chain.current())
+    "type": "error",
+    "error": RemoteException.serialize(error, stackTrace ?? Chain.current())
   });
 }
diff --git a/test_core/lib/src/runner/live_suite.dart b/test_core/lib/src/runner/live_suite.dart
index e580845..9c84492 100644
--- a/test_core/lib/src/runner/live_suite.dart
+++ b/test_core/lib/src/runner/live_suite.dart
@@ -59,12 +59,11 @@
   ///
   /// This is guaranteed to contain the same tests as the union of [passed],
   /// [skipped], [failed], and [active].
-  Set<LiveTest> get liveTests => UnionSet.from([
-        passed,
-        skipped,
-        failed,
-        if (active != null) {active}
-      ]);
+  Set<LiveTest> get liveTests {
+    var sets = [passed, skipped, failed];
+    if (active != null) sets.add(Set.from([active]));
+    return UnionSet.from(sets);
+  }
 
   /// A stream that emits each [LiveTest] in this suite as it's about to start
   /// running.
diff --git a/test_core/lib/src/runner/live_suite_controller.dart b/test_core/lib/src/runner/live_suite_controller.dart
index e2ec1dc..e33bfde 100644
--- a/test_core/lib/src/runner/live_suite_controller.dart
+++ b/test_core/lib/src/runner/live_suite_controller.dart
@@ -18,35 +18,25 @@
 class _LiveSuite extends LiveSuite {
   final LiveSuiteController _controller;
 
-  @override
   RunnerSuite get suite => _controller._suite;
 
-  @override
   bool get isComplete => _controller._isComplete;
 
-  @override
   Future get onComplete => _controller._onCompleteGroup.future;
 
-  @override
   bool get isClosed => _controller._onCloseCompleter.isCompleted;
 
-  @override
   Future get onClose => _controller._onCloseCompleter.future;
 
-  @override
   Stream<LiveTest> get onTestStarted =>
       _controller._onTestStartedController.stream;
 
-  @override
   Set<LiveTest> get passed => UnmodifiableSetView(_controller._passed);
 
-  @override
   Set<LiveTest> get skipped => UnmodifiableSetView(_controller._skipped);
 
-  @override
   Set<LiveTest> get failed => UnmodifiableSetView(_controller._failed);
 
-  @override
   LiveTest get active => _controller._active;
 
   _LiveSuite(this._controller);
@@ -86,13 +76,13 @@
       StreamController<LiveTest>.broadcast(sync: true);
 
   /// The set that backs [LiveTest.passed].
-  final _passed = <LiveTest>{};
+  final _passed = Set<LiveTest>();
 
   /// The set that backs [LiveTest.skipped].
-  final _skipped = <LiveTest>{};
+  final _skipped = Set<LiveTest>();
 
   /// The set that backs [LiveTest.failed].
-  final _failed = <LiveTest>{};
+  final _failed = Set<LiveTest>();
 
   /// The test exposed through [LiveTest.active].
   LiveTest _active;
diff --git a/test_core/lib/src/runner/load_exception.dart b/test_core/lib/src/runner/load_exception.dart
index dcc5f17..b94df49 100644
--- a/test_core/lib/src/runner/load_exception.dart
+++ b/test_core/lib/src/runner/load_exception.dart
@@ -12,7 +12,6 @@
 
   LoadException(this.path, this.innerError);
 
-  @override
   String toString({bool color = false}) {
     var buffer = StringBuffer();
     if (color) buffer.write('\u001b[31m'); // red
@@ -23,10 +22,10 @@
     if (innerError is SourceSpanException) {
       innerString = (innerError as SourceSpanException)
           .toString(color: color)
-          .replaceFirst(' of $path', '');
+          .replaceFirst(" of $path", "");
     }
 
-    buffer.write(innerString.contains('\n') ? '\n' : ' ');
+    buffer.write(innerString.contains("\n") ? "\n" : " ");
     buffer.write(innerString);
     return buffer.toString();
   }
diff --git a/test_core/lib/src/runner/load_suite.dart b/test_core/lib/src/runner/load_suite.dart
index fd6dfb0..246c489 100644
--- a/test_core/lib/src/runner/load_suite.dart
+++ b/test_core/lib/src/runner/load_suite.dart
@@ -6,23 +6,27 @@
 
 import 'package:stack_trace/stack_trace.dart';
 import 'package:stream_channel/stream_channel.dart';
+
 import 'package:test_api/src/backend/group.dart'; // ignore: implementation_imports
 import 'package:test_api/src/backend/invoker.dart'; // ignore: implementation_imports
 import 'package:test_api/src/backend/metadata.dart'; // ignore: implementation_imports
-import 'package:test_api/src/backend/runtime.dart'; // ignore: implementation_imports
 import 'package:test_api/src/backend/suite.dart'; // ignore: implementation_imports
 import 'package:test_api/src/backend/suite_platform.dart'; // ignore: implementation_imports
 import 'package:test_api/src/backend/test.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/runtime.dart'; // ignore: implementation_imports
 import 'package:test_api/src/utils.dart'; // ignore: implementation_imports
 
+import 'runner_suite.dart';
+import 'suite.dart';
+
 import '../../test_core.dart';
+
+// ignore: uri_does_not_exist
 import '../util/io_stub.dart'
     // ignore: uri_does_not_exist
     if (dart.library.io) '../util/io.dart';
 import 'load_exception.dart';
 import 'plugin/environment.dart';
-import 'runner_suite.dart';
-import 'suite.dart';
 
 /// The timeout for loading a test suite.
 ///
@@ -47,13 +51,9 @@
 /// suite itself is returned by [suite] once it's avaialble, but any errors or
 /// prints will be emitted through the running [LiveTest].
 class LoadSuite extends Suite implements RunnerSuite {
-  @override
   final environment = const PluginEnvironment();
-  @override
   final SuiteConfiguration config;
-  @override
   final isDebugging = false;
-  @override
   final onDebugging = StreamController<bool>().stream;
 
   @override
@@ -87,14 +87,14 @@
   /// If the the load test is closed before [body] is complete, it will close
   /// the suite returned by [body] once it completes.
   factory LoadSuite(String name, SuiteConfiguration config,
-      SuitePlatform platform, FutureOr<RunnerSuite> Function() body,
+      SuitePlatform platform, FutureOr<RunnerSuite> body(),
       {String path}) {
     var completer = Completer<Pair<RunnerSuite, Zone>>.sync();
     return LoadSuite._(name, config, platform, () {
       var invoker = Invoker.current;
       invoker.addOutstandingCallback();
 
-      unawaited(() async {
+      invoke(() async {
         var suite = await body();
         if (completer.isCompleted) {
           // If the load test has already been closed, close the suite it
@@ -105,7 +105,7 @@
 
         completer.complete(suite == null ? null : Pair(suite, Zone.current));
         invoker.removeOutstandingCallback();
-      }());
+      });
 
       // If the test completes before the body callback, either an out-of-band
       // error occurred or the test was canceled. Either way, we return a `null`
@@ -126,10 +126,10 @@
   factory LoadSuite.forLoadException(
       LoadException exception, SuiteConfiguration config,
       {SuitePlatform platform, StackTrace stackTrace}) {
-    stackTrace ??= Trace.current();
+    if (stackTrace == null) stackTrace = Trace.current();
 
     return LoadSuite(
-        'loading ${exception.path}',
+        "loading ${exception.path}",
         config ?? SuiteConfiguration.empty,
         platform ?? currentPlatform(Runtime.vm),
         () => Future.error(exception, stackTrace),
@@ -139,12 +139,12 @@
   /// A utility constructor for a load suite that just emits [suite].
   factory LoadSuite.forSuite(RunnerSuite suite) {
     return LoadSuite(
-        'loading ${suite.path}', suite.config, suite.platform, () => suite,
+        "loading ${suite.path}", suite.config, suite.platform, () => suite,
         path: suite.path);
   }
 
-  LoadSuite._(String name, this.config, SuitePlatform platform,
-      void Function() body, this._suiteAndZone, {String path})
+  LoadSuite._(String name, this.config, SuitePlatform platform, void body(),
+      this._suiteAndZone, {String path})
       : super(
             Group.root(
                 [LocalTest(name, Metadata(timeout: Timeout(_timeout)), body)]),
@@ -168,7 +168,7 @@
   /// If [suite] completes to `null`, [change] won't be run. [change] is run
   /// within the load test's zone, so any errors or prints it emits will be
   /// associated with that test.
-  LoadSuite changeSuite(RunnerSuite Function(RunnerSuite) change) {
+  LoadSuite changeSuite(RunnerSuite change(RunnerSuite suite)) {
     return LoadSuite._changeSuite(this, _suiteAndZone.then((pair) {
       if (pair == null) return null;
 
@@ -197,21 +197,14 @@
     throw 'unreachable';
   }
 
-  @override
-  LoadSuite filter(bool Function(Test) callback) {
+  LoadSuite filter(bool callback(Test test)) {
     var filtered = this.group.filter(callback);
-    filtered ??= Group.root([], metadata: metadata);
+    if (filtered == null) filtered = Group.root([], metadata: metadata);
     return LoadSuite._filtered(this, filtered);
   }
 
-  @override
   StreamChannel channel(String name) =>
-      throw UnsupportedError('LoadSuite.channel() is not supported.');
+      throw UnsupportedError("LoadSuite.channel() is not supported.");
 
-  @override
   Future close() async {}
-
-  @override
-  Future<Map<String, dynamic>> gatherCoverage() =>
-      throw UnsupportedError('Coverage is not supported for LoadSuite tests.');
 }
diff --git a/test_core/lib/src/runner/loader.dart b/test_core/lib/src/runner/loader.dart
index b8c779f..74fe5f2 100644
--- a/test_core/lib/src/runner/loader.dart
+++ b/test_core/lib/src/runner/loader.dart
@@ -35,7 +35,7 @@
   final _config = Configuration.current;
 
   /// All suites that have been created by the loader.
-  final _suites = <RunnerSuite>{};
+  final _suites = Set<RunnerSuite>();
 
   /// Memoizers for platform plugins, indexed by the runtimes they support.
   final _platformPlugins = <Runtime, AsyncMemoizer<PlatformPlugin>>{};
@@ -179,8 +179,7 @@
       return;
     }
 
-    if (_config.suiteDefaults.excludeTags
-        .evaluate(suiteConfig.metadata.tags.contains)) {
+    if (_config.suiteDefaults.excludeTags.evaluate(suiteConfig.metadata.tags)) {
       return;
     }
 
@@ -208,44 +207,28 @@
         yield LoadSuite.forSuite(RunnerSuite(
             const PluginEnvironment(),
             platformConfig,
-            Group.root([LocalTest('(suite)', platformConfig.metadata, () {})],
+            Group.root([LocalTest("(suite)", platformConfig.metadata, () {})],
                 metadata: platformConfig.metadata),
             platform,
             path: path));
         continue;
       }
 
-      var name =
-          (platform.runtime.isJS && platformConfig.precompiledPath == null
-                  ? 'compiling '
-                  : 'loading ') +
-              path;
+      var name = (platform.runtime.isJS ? "compiling " : "loading ") + path;
       yield LoadSuite(name, platformConfig, platform, () async {
         var memo = _platformPlugins[platform.runtime];
 
-        var retriesLeft = suiteConfig.metadata.retry;
-        while (true) {
-          try {
-            var plugin =
-                await memo.runOnce(_platformCallbacks[platform.runtime]);
-            _customizePlatform(plugin, platform.runtime);
-            var suite = await plugin.load(path, platform, platformConfig,
-                {'platformVariables': _runtimeVariables.toList()});
-            if (suite != null) _suites.add(suite);
-            return suite;
-          } catch (error, stackTrace) {
-            if (retriesLeft > 0) {
-              retriesLeft--;
-              print('Retrying load of $path in 1s ($retriesLeft remaining)');
-              await Future.delayed(Duration(seconds: 1));
-              continue;
-            }
-            if (error is LoadException) {
-              rethrow;
-            }
-            await Future.error(LoadException(path, error), stackTrace);
-            return null;
-          }
+        try {
+          var plugin = await memo.runOnce(_platformCallbacks[platform.runtime]);
+          _customizePlatform(plugin, platform.runtime);
+          var suite = await plugin.load(path, platform, platformConfig,
+              {"platformVariables": _runtimeVariables.toList()});
+          if (suite != null) _suites.add(suite);
+          return suite;
+        } catch (error, stackTrace) {
+          if (error is LoadException) rethrow;
+          await Future.error(LoadException(path, error), stackTrace);
+          return null;
         }
       }, path: path);
     }
diff --git a/test_core/lib/src/runner/parse_metadata.dart b/test_core/lib/src/runner/parse_metadata.dart
index 1de3d33..40366a2 100644
--- a/test_core/lib/src/runner/parse_metadata.dart
+++ b/test_core/lib/src/runner/parse_metadata.dart
@@ -42,7 +42,7 @@
   Set<String> _prefixes;
 
   /// The actual contents of the file.
-  final String _contents;
+  String _contents;
 
   _Parser(this._path, this._contents, this._platformVariables) {
     // ignore: deprecated_member_use
@@ -162,7 +162,7 @@
   /// constructor for the annotation, if any.
   ///
   /// Returns either `true` or a reason string.
-  dynamic _parseSkip(Annotation annotation, String constructorName) {
+  _parseSkip(Annotation annotation, String constructorName) {
     var args = annotation.arguments.arguments;
     return args.isEmpty ? true : _parseString(args.first).stringValue;
   }
@@ -170,7 +170,7 @@
   /// Parses a `Skip` constructor.
   ///
   /// Returns either `true` or a reason string.
-  dynamic _parseSkipConstructor(Expression constructor) {
+  _parseSkipConstructor(Expression constructor) {
     _findConstructorName(constructor, 'Skip');
     var arguments = _parseArguments(constructor);
     return arguments.isEmpty ? true : _parseString(arguments.first).stringValue;
@@ -283,10 +283,9 @@
 
   Map<String, Expression> _parseNamedArguments(
           NodeList<Expression> arguments) =>
-      {
-        for (var a in arguments.whereType<NamedExpression>())
-          a.name.label.name: a.expression
-      };
+      Map.fromIterable(arguments.whereType<NamedExpression>(),
+          key: (a) => (a as NamedExpression).name.label.name,
+          value: (a) => (a as NamedExpression).expression);
 
   /// Asserts that [existing] is null.
   ///
@@ -436,7 +435,7 @@
   /// By default, returns [Expression] keys and values. These can be overridden
   /// with the [key] and [value] parameters.
   Map<K, V> _parseMap<K, V>(Expression expression,
-      {K Function(Expression) key, V Function(Expression) value}) {
+      {K key(Expression expression), V value(Expression expression)}) {
     key ??= (expression) => expression as K;
     value ??= (expression) => expression as V;
 
@@ -506,7 +505,7 @@
 
   /// Runs [fn] and contextualizes any [SourceSpanFormatException]s that occur
   /// in it relative to [literal].
-  T _contextualize<T>(StringLiteral literal, T Function() fn) {
+  T _contextualize<T>(StringLiteral literal, T fn()) {
     try {
       return fn();
     } on SourceSpanFormatException catch (error) {
diff --git a/test_core/lib/src/runner/plugin/environment.dart b/test_core/lib/src/runner/plugin/environment.dart
index 40e88b2..e8cb05f 100644
--- a/test_core/lib/src/runner/plugin/environment.dart
+++ b/test_core/lib/src/runner/plugin/environment.dart
@@ -10,20 +10,15 @@
 
 /// The default environment for platform plugins.
 class PluginEnvironment implements Environment {
-  @override
   final supportsDebugging = false;
-  @override
   Stream get onRestart => StreamController.broadcast().stream;
 
   const PluginEnvironment();
 
-  @override
   Uri get observatoryUrl => null;
 
-  @override
   Uri get remoteDebuggerUrl => null;
 
-  @override
   CancelableOperation displayPause() => throw UnsupportedError(
-      'PluginEnvironment.displayPause is not supported.');
+      "PluginEnvironment.displayPause is not supported.");
 }
diff --git a/test_core/lib/src/runner/plugin/platform_helpers.dart b/test_core/lib/src/runner/plugin/platform_helpers.dart
index bd11058..442c0c6 100644
--- a/test_core/lib/src/runner/plugin/platform_helpers.dart
+++ b/test_core/lib/src/runner/plugin/platform_helpers.dart
@@ -7,18 +7,19 @@
 
 import 'package:stack_trace/stack_trace.dart';
 import 'package:stream_channel/stream_channel.dart';
+
 import 'package:test_api/src/backend/group.dart'; // ignore: implementation_imports
 import 'package:test_api/src/backend/metadata.dart'; // ignore: implementation_imports
 import 'package:test_api/src/backend/suite_platform.dart'; // ignore: implementation_imports
 import 'package:test_api/src/backend/test.dart'; // ignore: implementation_imports
 import 'package:test_api/src/util/remote_exception.dart'; // ignore: implementation_imports
 
-import '../configuration.dart';
-import '../environment.dart';
-import '../load_exception.dart';
 import '../runner_suite.dart';
-import '../runner_test.dart';
+import '../environment.dart';
 import '../suite.dart';
+import '../configuration.dart';
+import '../load_exception.dart';
+import '../runner_test.dart';
 
 /// A helper method for creating a [RunnerSuiteController] containing tests
 /// that communicate over [channel].
@@ -34,17 +35,13 @@
 ///
 /// If [mapper] is passed, it will be used to adjust stack traces for any errors
 /// emitted by tests.
-///
-/// [gatherCoverage] is a callback which returns a hit-map containing merged
-/// coverage report suitable for use with `package:coverage`.
 RunnerSuiteController deserializeSuite(
     String path,
     SuitePlatform platform,
     SuiteConfiguration suiteConfig,
     Environment environment,
     StreamChannel channel,
-    Object message,
-    {Future<Map<String, dynamic>> Function() gatherCoverage}) {
+    Object message) {
   var disconnector = Disconnector();
   var suiteChannel = MultiChannel(channel.transform(disconnector));
 
@@ -54,8 +51,7 @@
     'metadata': suiteConfig.metadata.serialize(),
     'asciiGlyphs': Platform.isWindows,
     'path': path,
-    'collectTraces': Configuration.current.reporter == 'json' ||
-        Configuration.current.fileReporters.containsKey('json'),
+    'collectTraces': Configuration.current.reporter == 'json',
     'noRetry': Configuration.current.noRetry,
     'foldTraceExcept': Configuration.current.foldTraceExcept.toList(),
     'foldTraceOnly': Configuration.current.foldTraceOnly.toList(),
@@ -64,7 +60,7 @@
   var completer = Completer<Group>();
 
   var loadSuiteZone = Zone.current;
-  void handleError(error, StackTrace stackTrace) {
+  handleError(error, StackTrace stackTrace) {
     disconnector.disconnect();
 
     if (completer.isCompleted) {
@@ -79,26 +75,26 @@
 
   suiteChannel.stream.listen(
       (response) {
-        switch (response['type'] as String) {
-          case 'print':
-            print(response['line']);
+        switch (response["type"] as String) {
+          case "print":
+            print(response["line"]);
             break;
 
-          case 'loadException':
+          case "loadException":
             handleError(
-                LoadException(path, response['message']), Trace.current());
+                LoadException(path, response["message"]), Trace.current());
             break;
 
-          case 'error':
-            var asyncError = RemoteException.deserialize(response['error']);
+          case "error":
+            var asyncError = RemoteException.deserialize(response["error"]);
             handleError(
                 LoadException(path, asyncError.error), asyncError.stackTrace);
             break;
 
-          case 'success':
+          case "success":
             var deserializer = _Deserializer(suiteChannel);
             completer.complete(
-                deserializer.deserializeGroup(response['root'] as Map));
+                deserializer.deserializeGroup(response["root"] as Map));
             break;
         }
       },
@@ -106,15 +102,14 @@
       onDone: () {
         if (completer.isCompleted) return;
         completer.completeError(
-            LoadException(path, 'Connection closed before test suite loaded.'),
+            LoadException(path, "Connection closed before test suite loaded."),
             Trace.current());
       });
 
   return RunnerSuiteController(
       environment, suiteConfig, suiteChannel, completer.future, platform,
       path: path,
-      onClose: () => disconnector.disconnect().catchError(handleError),
-      gatherCoverage: gatherCoverage);
+      onClose: () => disconnector.disconnect().catchError(handleError));
 }
 
 /// A utility class for storing state while deserializing tests.
diff --git a/test_core/lib/src/runner/plugin/remote_platform_helpers.dart b/test_core/lib/src/runner/plugin/remote_platform_helpers.dart
index 17c6d4c..310d0f5 100644
--- a/test_core/lib/src/runner/plugin/remote_platform_helpers.dart
+++ b/test_core/lib/src/runner/plugin/remote_platform_helpers.dart
@@ -30,8 +30,8 @@
 ///
 /// If [beforeLoad] is passed, it's called before the tests have been declared
 /// for this worker.
-StreamChannel serializeSuite(Function Function() getMain,
-        {bool hidePrints = true, Future Function() beforeLoad}) =>
+StreamChannel serializeSuite(Function getMain(),
+        {bool hidePrints = true, Future beforeLoad()}) =>
     RemoteListener.start(getMain,
         hidePrints: hidePrints, beforeLoad: beforeLoad);
 
diff --git a/test_core/lib/src/runner/reporter/compact.dart b/test_core/lib/src/runner/reporter/compact.dart
index 4c3e69a..7827811 100644
--- a/test_core/lib/src/runner/reporter/compact.dart
+++ b/test_core/lib/src/runner/reporter/compact.dart
@@ -99,7 +99,7 @@
   var _paused = false;
 
   /// The set of all subscriptions to various streams.
-  final _subscriptions = <StreamSubscription>{};
+  final _subscriptions = Set<StreamSubscription>();
 
   /// Watches the tests run by [engine] and prints their results to the
   /// terminal.
@@ -114,7 +114,6 @@
     _subscriptions.add(_engine.success.asStream().listen(_onDone));
   }
 
-  @override
   void pause() {
     if (_paused) return;
     _paused = true;
@@ -133,7 +132,6 @@
     }
   }
 
-  @override
   void resume() {
     if (!_paused) return;
     _paused = false;
@@ -145,7 +143,6 @@
     }
   }
 
-  @override
   void cancel() {
     for (var subscription in _subscriptions) {
       subscription.cancel();
@@ -210,7 +207,7 @@
     if (liveTest.state.status != Status.complete) return;
 
     _progressLine(_description(liveTest),
-        truncate: false, suffix: ' $_bold$_red[E]$_noColor');
+        truncate: false, suffix: " $_bold$_red[E]$_noColor");
     if (!_printedNewline) _sink.writeln('');
     _printedNewline = true;
 
@@ -245,33 +242,33 @@
     // shouldn't print summary information, we should just make sure the
     // terminal cursor is on its own line.
     if (success == null) {
-      if (!_printedNewline) _sink.writeln('');
+      if (!_printedNewline) _sink.writeln("");
       _printedNewline = true;
       return;
     }
 
     if (_engine.liveTests.isEmpty) {
-      if (!_printedNewline) _sink.write('\r');
-      var message = 'No tests ran.';
+      if (!_printedNewline) _sink.write("\r");
+      var message = "No tests ran.";
       _sink.write(message);
 
       // Add extra padding to overwrite any load messages.
-      if (!_printedNewline) _sink.write(' ' * (lineLength - message.length));
+      if (!_printedNewline) _sink.write(" " * (lineLength - message.length));
       _sink.writeln('');
     } else if (!success) {
       for (var liveTest in _engine.active) {
         _progressLine(_description(liveTest),
             truncate: false,
-            suffix: ' - did not complete $_bold$_red[E]$_noColor');
+            suffix: " - did not complete $_bold$_red[E]$_noColor");
         _sink.writeln('');
       }
       _progressLine('Some tests failed.', color: _red);
       _sink.writeln('');
     } else if (_engine.passed.isEmpty) {
-      _progressLine('All tests skipped.');
+      _progressLine("All tests skipped.");
       _sink.writeln('');
     } else {
-      _progressLine('All tests passed!');
+      _progressLine("All tests passed!");
       _sink.writeln('');
     }
   }
@@ -312,7 +309,7 @@
     _lastProgressTruncated = truncate;
 
     if (suffix != null) message += suffix;
-    color ??= '';
+    if (color == null) color = '';
     var duration = _stopwatch.elapsed;
     var buffer = StringBuffer();
 
@@ -372,15 +369,15 @@
     if (_printPath &&
         liveTest.suite is! LoadSuite &&
         liveTest.suite.path != null) {
-      name = '${liveTest.suite.path}: $name';
+      name = "${liveTest.suite.path}: $name";
     }
 
     if (_config.suiteDefaults.runtimes.length > 1 &&
         liveTest.suite.platform != null) {
-      name = '[${liveTest.suite.platform.runtime.name}] $name';
+      name = "[${liveTest.suite.platform.runtime.name}] $name";
     }
 
-    if (liveTest.suite is LoadSuite) name = '$_bold$_gray$name$_noColor';
+    if (liveTest.suite is LoadSuite) name = "$_bold$_gray$name$_noColor";
 
     return name;
   }
diff --git a/test_core/lib/src/runner/reporter/expanded.dart b/test_core/lib/src/runner/reporter/expanded.dart
index 2785ae9..87588d0 100644
--- a/test_core/lib/src/runner/reporter/expanded.dart
+++ b/test_core/lib/src/runner/reporter/expanded.dart
@@ -82,7 +82,7 @@
   var _paused = false;
 
   /// The set of all subscriptions to various streams.
-  final _subscriptions = <StreamSubscription>{};
+  final _subscriptions = Set<StreamSubscription>();
 
   final StringSink _sink;
 
@@ -119,7 +119,6 @@
     _subscriptions.add(_engine.success.asStream().listen(_onDone));
   }
 
-  @override
   void pause() {
     if (_paused) return;
     _paused = true;
@@ -131,7 +130,6 @@
     }
   }
 
-  @override
   void resume() {
     if (!_paused) return;
     _stopwatch.start();
@@ -141,7 +139,6 @@
     }
   }
 
-  @override
   void cancel() {
     for (var subscription in _subscriptions) {
       subscription.cancel();
@@ -165,7 +162,7 @@
           .listen((state) => _onStateChange(liveTest, state)));
     } else if (_engine.active.length == 1 &&
         _engine.active.first == liveTest &&
-        liveTest.test.name.startsWith('compiling ')) {
+        liveTest.test.name.startsWith("compiling ")) {
       // Print a progress line for load tests that come from compiling JS, since
       // that takes a long time.
       _progressLine(_description(liveTest));
@@ -197,7 +194,7 @@
   void _onError(LiveTest liveTest, error, StackTrace stackTrace) {
     if (liveTest.state.status != Status.complete) return;
 
-    _progressLine(_description(liveTest), suffix: ' $_bold$_red[E]$_noColor');
+    _progressLine(_description(liveTest), suffix: " $_bold$_red[E]$_noColor");
 
     if (error is! LoadException) {
       _sink..writeln(indent('$error'))..writeln(indent('$stackTrace'));
@@ -224,17 +221,17 @@
     if (success == null) return;
 
     if (_engine.liveTests.isEmpty) {
-      _sink.writeln('No tests ran.');
+      _sink.writeln("No tests ran.");
     } else if (!success) {
       for (var liveTest in _engine.active) {
         _progressLine(_description(liveTest),
-            suffix: ' - did not complete $_bold$_red[E]$_noColor');
+            suffix: " - did not complete $_bold$_red[E]$_noColor");
       }
       _progressLine('Some tests failed.', color: _red);
     } else if (_engine.passed.isEmpty) {
-      _progressLine('All tests skipped.');
+      _progressLine("All tests skipped.");
     } else {
-      _progressLine('All tests passed!');
+      _progressLine("All tests passed!");
     }
   }
 
@@ -261,7 +258,7 @@
     _lastProgressSuffix = suffix;
 
     if (suffix != null) message += suffix;
-    color ??= '';
+    if (color == null) color = '';
     var duration = _stopwatch.elapsed;
     var buffer = StringBuffer();
 
@@ -310,14 +307,14 @@
     if (_printPath &&
         liveTest.suite is! LoadSuite &&
         liveTest.suite.path != null) {
-      name = '${liveTest.suite.path}: $name';
+      name = "${liveTest.suite.path}: $name";
     }
 
     if (_printPlatform) {
-      name = '[${liveTest.suite.platform.runtime.name}] $name';
+      name = "[${liveTest.suite.platform.runtime.name}] $name";
     }
 
-    if (liveTest.suite is LoadSuite) name = '$_bold$_gray$name$_noColor';
+    if (liveTest.suite is LoadSuite) name = "$_bold$_gray$name$_noColor";
 
     return name;
   }
diff --git a/test_core/lib/src/runner/reporter/json.dart b/test_core/lib/src/runner/reporter/json.dart
index 5edb4a0..eb3c9fb 100644
--- a/test_core/lib/src/runner/reporter/json.dart
+++ b/test_core/lib/src/runner/reporter/json.dart
@@ -48,16 +48,16 @@
   var _paused = false;
 
   /// The set of all subscriptions to various streams.
-  final _subscriptions = <StreamSubscription>{};
+  final _subscriptions = Set<StreamSubscription>();
 
   /// An expando that associates unique IDs with [LiveTest]s.
-  final _liveTestIDs = <LiveTest, int>{};
+  final _liveTestIDs = Map<LiveTest, int>();
 
   /// An expando that associates unique IDs with [Suite]s.
-  final _suiteIDs = <Suite, int>{};
+  final _suiteIDs = Map<Suite, int>();
 
   /// An expando that associates unique IDs with [Group]s.
-  final _groupIDs = <Group, int>{};
+  final _groupIDs = Map<Group, int>();
 
   /// The next ID to associate with a [LiveTest].
   var _nextID = 0;
@@ -74,14 +74,13 @@
     _subscriptions.add(_engine.success.asStream().listen(_onDone));
 
     _subscriptions.add(_engine.onSuiteAdded.listen(null, onDone: () {
-      _emit('allSuites', {'count': _engine.addedSuites.length});
+      _emit("allSuites", {"count": _engine.addedSuites.length});
     }));
 
-    _emit('start',
-        {'protocolVersion': '0.1.1', 'runnerVersion': testVersion, 'pid': pid});
+    _emit("start",
+        {"protocolVersion": "0.1.1", "runnerVersion": testVersion, "pid": pid});
   }
 
-  @override
   void pause() {
     if (_paused) return;
     _paused = true;
@@ -93,7 +92,6 @@
     }
   }
 
-  @override
   void resume() {
     if (!_paused) return;
     _paused = false;
@@ -105,7 +103,6 @@
     }
   }
 
-  @override
   void cancel() {
     for (var subscription in _subscriptions) {
       subscription.cancel();
@@ -131,15 +128,15 @@
     var suiteConfig = _configFor(liveTest.suite);
     var id = _nextID++;
     _liveTestIDs[liveTest] = id;
-    _emit('testStart', {
-      'test': _addFrameInfo(
+    _emit("testStart", {
+      "test": _addFrameInfo(
           suiteConfig,
           {
-            'id': id,
-            'name': liveTest.test.name,
-            'suiteID': suiteID,
-            'groupIDs': groupIDs,
-            'metadata': _serializeMetadata(suiteConfig, liveTest.test.metadata)
+            "id": id,
+            "name": liveTest.test.name,
+            "suiteID": suiteID,
+            "groupIDs": groupIDs,
+            "metadata": _serializeMetadata(suiteConfig, liveTest.test.metadata)
           },
           liveTest.test,
           liveTest.suite.platform.runtime,
@@ -155,10 +152,10 @@
         .listen((error) => _onError(liveTest, error.error, error.stackTrace)));
 
     _subscriptions.add(liveTest.onMessage.listen((message) {
-      _emit('print', {
-        'testID': id,
-        'messageType': message.type.name,
-        'message': message.text
+      _emit("print", {
+        "testID": id,
+        "messageType": message.type.name,
+        "message": message.text
       });
     }));
   }
@@ -182,20 +179,20 @@
 
         // TODO(nweiz): test this when we have a library for communicating with
         // the Chrome remote debugger, or when we have VM debug support.
-        _emit('debug', {
-          'suiteID': id,
-          'observatory': runnerSuite.environment.observatoryUrl?.toString(),
-          'remoteDebugger':
+        _emit("debug", {
+          "suiteID": id,
+          "observatory": runnerSuite.environment.observatoryUrl?.toString(),
+          "remoteDebugger":
               runnerSuite.environment.remoteDebuggerUrl?.toString(),
         });
       });
     }
 
-    _emit('suite', {
-      'suite': {
-        'id': id,
-        'platform': suite.platform.runtime.identifier,
-        'path': suite.path
+    _emit("suite", {
+      "suite": {
+        "id": id,
+        "platform": suite.platform.runtime.identifier,
+        "path": suite.path
       }
     });
     return id;
@@ -218,16 +215,16 @@
       _groupIDs[group] = id;
 
       var suiteConfig = _configFor(suite);
-      _emit('group', {
-        'group': _addFrameInfo(
+      _emit("group", {
+        "group": _addFrameInfo(
             suiteConfig,
             {
-              'id': id,
-              'suiteID': _idForSuite(suite),
-              'parentID': parentID,
-              'name': group.name,
-              'metadata': _serializeMetadata(suiteConfig, group.metadata),
-              'testCount': group.testCount
+              "id": id,
+              "suiteID": _idForSuite(suite),
+              "parentID": parentID,
+              "name": group.name,
+              "metadata": _serializeMetadata(suiteConfig, group.metadata),
+              "testCount": group.testCount
             },
             group,
             suite.platform.runtime,
@@ -241,29 +238,29 @@
   /// Serializes [metadata] into a JSON-protocol-compatible map.
   Map _serializeMetadata(SuiteConfiguration suiteConfig, Metadata metadata) =>
       suiteConfig.runSkipped
-          ? {'skip': false, 'skipReason': null}
-          : {'skip': metadata.skip, 'skipReason': metadata.skipReason};
+          ? {"skip": false, "skipReason": null}
+          : {"skip": metadata.skip, "skipReason": metadata.skipReason};
 
   /// A callback called when [liveTest] finishes running.
   void _onComplete(LiveTest liveTest) {
-    _emit('testDone', {
-      'testID': _liveTestIDs[liveTest],
+    _emit("testDone", {
+      "testID": _liveTestIDs[liveTest],
       // For backwards-compatibility, report skipped tests as successes.
-      'result': liveTest.state.result == Result.skipped
-          ? 'success'
+      "result": liveTest.state.result == Result.skipped
+          ? "success"
           : liveTest.state.result.toString(),
-      'skipped': liveTest.state.result == Result.skipped,
-      'hidden': !_engine.liveTests.contains(liveTest)
+      "skipped": liveTest.state.result == Result.skipped,
+      "hidden": !_engine.liveTests.contains(liveTest)
     });
   }
 
   /// A callback called when [liveTest] throws [error].
   void _onError(LiveTest liveTest, error, StackTrace stackTrace) {
-    _emit('error', {
-      'testID': _liveTestIDs[liveTest],
-      'error': error.toString(),
-      'stackTrace': '$stackTrace',
-      'isFailure': error is TestFailure
+    _emit("error", {
+      "testID": _liveTestIDs[liveTest],
+      "error": error.toString(),
+      "stackTrace": '$stackTrace',
+      "isFailure": error is TestFailure
     });
   }
 
@@ -275,7 +272,7 @@
     cancel();
     _stopwatch.stop();
 
-    _emit('done', {'success': success});
+    _emit("done", {"success": success});
   }
 
   /// Returns the configuration for [suite].
@@ -287,8 +284,8 @@
 
   /// Emits an event with the given type and attributes.
   void _emit(String type, Map attributes) {
-    attributes['type'] = type;
-    attributes['time'] = _stopwatch.elapsed.inMilliseconds;
+    attributes["type"] = type;
+    attributes["time"] = _stopwatch.elapsed.inMilliseconds;
     _sink.writeln(jsonEncode(attributes));
   }
 
@@ -313,13 +310,13 @@
       rootFrame = null;
     }
 
-    map['line'] = frame?.line;
-    map['column'] = frame?.column;
-    map['url'] = frame?.uri?.toString();
+    map["line"] = frame?.line;
+    map["column"] = frame?.column;
+    map["url"] = frame?.uri?.toString();
     if (rootFrame != null && rootFrame != frame) {
-      map['root_line'] = rootFrame.line;
-      map['root_column'] = rootFrame.column;
-      map['root_url'] = rootFrame.uri.toString();
+      map["root_line"] = rootFrame.line;
+      map["root_column"] = rootFrame.column;
+      map["root_url"] = rootFrame.uri.toString();
     }
     return map;
   }
diff --git a/test_core/lib/src/runner/reporter/multiplex.dart b/test_core/lib/src/runner/reporter/multiplex.dart
deleted file mode 100644
index e13c1b4..0000000
--- a/test_core/lib/src/runner/reporter/multiplex.dart
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS 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 '../reporter.dart';
-
-class MultiplexReporter implements Reporter {
-  Iterable<Reporter> delegates;
-
-  MultiplexReporter(this.delegates);
-
-  @override
-  void cancel() {
-    for (var d in delegates) {
-      d.cancel();
-    }
-  }
-
-  @override
-  void pause() {
-    for (var d in delegates) {
-      d.pause();
-    }
-  }
-
-  @override
-  void resume() {
-    for (var d in delegates) {
-      d.resume();
-    }
-  }
-}
diff --git a/test_core/lib/src/runner/runner_suite.dart b/test_core/lib/src/runner/runner_suite.dart
index 1b166d3..4bed418 100644
--- a/test_core/lib/src/runner/runner_suite.dart
+++ b/test_core/lib/src/runner/runner_suite.dart
@@ -6,13 +6,14 @@
 
 import 'package:async/async.dart';
 import 'package:stream_channel/stream_channel.dart';
+
 import 'package:test_api/src/backend/group.dart'; // ignore: implementation_imports
 import 'package:test_api/src/backend/suite.dart'; // ignore: implementation_imports
 import 'package:test_api/src/backend/suite_platform.dart'; // ignore: implementation_imports
 import 'package:test_api/src/backend/test.dart'; // ignore: implementation_imports
 
-import 'environment.dart';
 import 'suite.dart';
+import 'environment.dart';
 
 /// A suite produced and consumed by the test runner that has runner-specific
 /// logic and lifecycle management.
@@ -67,8 +68,7 @@
       this._controller, Group group, String path, SuitePlatform platform)
       : super(group, platform, path: path);
 
-  @override
-  RunnerSuite filter(bool Function(Test) callback) {
+  RunnerSuite filter(bool callback(Test test)) {
     var filtered = group.filter(callback);
     filtered ??= Group.root([], metadata: metadata);
     return RunnerSuite._(_controller, filtered, path, platform);
@@ -76,13 +76,6 @@
 
   /// Closes the suite and releases any resources associated with it.
   Future close() => _controller._close();
-
-  /// Collects a hit-map containing merged coverage.
-  ///
-  /// Result is suitable for input to the coverage formatters provided by
-  /// `package:coverage`.
-  Future<Map<String, dynamic>> gatherCoverage() async =>
-      (await _controller._gatherCoverage?.call()) ?? {};
 }
 
 /// A class that exposes and controls a [RunnerSuite].
@@ -110,18 +103,12 @@
   final _onDebuggingController = StreamController<bool>.broadcast();
 
   /// The channel names that have already been used.
-  final _channelNames = <String>{};
-
-  /// Collects a hit-map containing merged coverage.
-  final Future<Map<String, dynamic>> Function() _gatherCoverage;
+  final _channelNames = Set<String>();
 
   RunnerSuiteController(this._environment, this._config, this._suiteChannel,
       Future<Group> groupFuture, SuitePlatform platform,
-      {String path,
-      Function() onClose,
-      Future<Map<String, dynamic>> Function() gatherCoverage})
-      : _onClose = onClose,
-        _gatherCoverage = gatherCoverage {
+      {String path, Function() onClose})
+      : _onClose = onClose {
     _suite =
         groupFuture.then((group) => RunnerSuite._(this, group, path, platform));
   }
@@ -129,11 +116,9 @@
   /// Used by [new RunnerSuite] to create a runner suite that's not loaded from
   /// an external source.
   RunnerSuiteController._local(this._environment, this._config,
-      {Function() onClose,
-      Future<Map<String, dynamic>> Function() gatherCoverage})
+      {Function() onClose})
       : _suiteChannel = null,
-        _onClose = onClose,
-        _gatherCoverage = gatherCoverage;
+        _onClose = onClose;
 
   /// Sets whether the suite is paused for debugging.
   ///
@@ -161,7 +146,7 @@
 
     var channel = _suiteChannel.virtualChannel();
     _suiteChannel.sink
-        .add({'type': 'suiteChannel', 'name': name, 'id': channel.id});
+        .add({"type": "suiteChannel", "name": name, "id": channel.id});
     return channel;
   }
 
diff --git a/test_core/lib/src/runner/runner_test.dart b/test_core/lib/src/runner/runner_test.dart
index 44fb3a7..f19cad1 100644
--- a/test_core/lib/src/runner/runner_test.dart
+++ b/test_core/lib/src/runner/runner_test.dart
@@ -2,9 +2,9 @@
 // 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 'package:pedantic/pedantic.dart';
 import 'package:stack_trace/stack_trace.dart';
 import 'package:stream_channel/stream_channel.dart';
+
 import 'package:test_api/src/backend/group.dart'; // ignore: implementation_imports
 import 'package:test_api/src/backend/live_test.dart'; // ignore: implementation_imports
 import 'package:test_api/src/backend/live_test_controller.dart'; // ignore: implementation_imports
@@ -15,16 +15,14 @@
 import 'package:test_api/src/backend/suite_platform.dart'; // ignore: implementation_imports
 import 'package:test_api/src/backend/test.dart'; // ignore: implementation_imports
 import 'package:test_api/src/util/remote_exception.dart'; // ignore: implementation_imports
+import 'package:test_api/src/utils.dart'; // ignore: implementation_imports
 
 import 'spawn_hybrid.dart';
 
 /// A test running remotely, controlled by a stream channel.
 class RunnerTest extends Test {
-  @override
   final String name;
-  @override
   final Metadata metadata;
-  @override
   final Trace trace;
 
   /// The channel used to communicate with the test's [IframeListener].
@@ -34,7 +32,6 @@
 
   RunnerTest._(this.name, this.metadata, this.trace, this._channel);
 
-  @override
   LiveTest load(Suite suite, {Iterable<Group> groups}) {
     LiveTestController controller;
     VirtualChannel testChannel;
@@ -88,19 +85,18 @@
         return;
       }
 
-      unawaited(() async {
+      invoke(() async {
         // If the test is still running, send it a message telling it to shut
         // down ASAP. This causes the [Invoker] to eagerly throw exceptions
         // whenever the test touches it.
         testChannel.sink.add({'command': 'close'});
         await controller.completer.future;
         await testChannel.sink.close();
-      }());
+      });
     }, groups: groups);
     return controller.liveTest;
   }
 
-  @override
   Test forPlatform(SuitePlatform platform) {
     if (!metadata.testOn.evaluate(platform)) return null;
     return RunnerTest._(name, metadata.forPlatform(platform), trace, _channel);
diff --git a/test_core/lib/src/runner/runtime_selection.dart b/test_core/lib/src/runner/runtime_selection.dart
index 8105dcd..0585a0f 100644
--- a/test_core/lib/src/runner/runtime_selection.dart
+++ b/test_core/lib/src/runner/runtime_selection.dart
@@ -16,9 +16,7 @@
 
   RuntimeSelection(this.name, [this.span]);
 
-  @override
   bool operator ==(other) => other is RuntimeSelection && other.name == name;
 
-  @override
   int get hashCode => name.hashCode;
 }
diff --git a/test_core/lib/src/runner/spawn_hybrid.dart b/test_core/lib/src/runner/spawn_hybrid.dart
index 1db6769..797ba66 100644
--- a/test_core/lib/src/runner/spawn_hybrid.dart
+++ b/test_core/lib/src/runner/spawn_hybrid.dart
@@ -24,13 +24,13 @@
     var port = ReceivePort();
     var onExitPort = ReceivePort();
     try {
-      var code = '''
+      var code = """
         import "package:test_core/src/runner/hybrid_listener.dart";
 
         import "${url.replaceAll(r'$', '%24')}" as lib;
 
         void main(_, List data) => listen(() => lib.hybridMain, data);
-      ''';
+      """;
 
       var isolate = await dart.runInIsolate(code, [port.sendPort, message],
           onExit: onExitPort.sendPort);
@@ -57,8 +57,8 @@
       // Make sure any errors in spawning the isolate are forwarded to the test.
       return StreamChannel(
           Stream.fromFuture(Future.value({
-            'type': 'error',
-            'error': RemoteException.serialize(error, stackTrace)
+            "type": "error",
+            "error": RemoteException.serialize(error, stackTrace)
           })),
           NullStreamSink());
     }
diff --git a/test_core/lib/src/runner/suite.dart b/test_core/lib/src/runner/suite.dart
index c95494a..08c6ab4 100644
--- a/test_core/lib/src/runner/suite.dart
+++ b/test_core/lib/src/runner/suite.dart
@@ -55,7 +55,7 @@
 
   /// The set of runtimes on which to run tests.
   List<String> get runtimes => _runtimes == null
-      ? const ['vm']
+      ? const ["vm"]
       : List.unmodifiable(_runtimes.map((runtime) => runtime.name));
   final List<RuntimeSelection> _runtimes;
 
@@ -85,9 +85,10 @@
   final Map<PlatformSelector, SuiteConfiguration> onPlatform;
 
   /// The seed with which to shuffle the test order.
-  /// Default value is null if not provided and will not change the test order.
+  /// Default value is 0 if not provided and will not change the test order.
   /// The same seed will shuffle the tests in the same way every time.
-  final int testRandomizeOrderingSeed;
+  int get testRandomizeOrderingSeed => _testRandomizeOrderingSeed ?? 0;
+  final int _testRandomizeOrderingSeed;
 
   /// The global test metadata derived from this configuration.
   Metadata get metadata {
@@ -195,13 +196,13 @@
       : _jsTrace = jsTrace,
         _runSkipped = runSkipped,
         dart2jsArgs = _list(dart2jsArgs) ?? const [],
-        patterns = UnmodifiableSetView(patterns?.toSet() ?? {}),
+        patterns = UnmodifiableSetView(patterns?.toSet() ?? Set()),
         _runtimes = _list(runtimes),
         includeTags = includeTags ?? BooleanSelector.all,
         excludeTags = excludeTags ?? BooleanSelector.none,
         tags = _map(tags),
         onPlatform = _map(onPlatform),
-        testRandomizeOrderingSeed = testRandomizeOrderingSeed,
+        _testRandomizeOrderingSeed = testRandomizeOrderingSeed,
         _metadata = metadata ?? Metadata.empty;
 
   /// Creates a new [SuiteConfiguration] that takes its configuration from
@@ -251,7 +252,7 @@
         tags: _mergeConfigMaps(tags, other.tags),
         onPlatform: _mergeConfigMaps(onPlatform, other.onPlatform),
         testRandomizeOrderingSeed:
-            other.testRandomizeOrderingSeed ?? testRandomizeOrderingSeed,
+            other._testRandomizeOrderingSeed ?? _testRandomizeOrderingSeed,
         metadata: metadata.merge(other.metadata));
     return config._resolveTags();
   }
@@ -294,7 +295,7 @@
         tags: tags ?? this.tags,
         onPlatform: onPlatform ?? this.onPlatform,
         testRandomizeOrderingSeed:
-            testRandomizeOrderingSeed ?? testRandomizeOrderingSeed,
+            testRandomizeOrderingSeed ?? _testRandomizeOrderingSeed,
         metadata: _metadata.change(
             timeout: timeout,
             verboseTrace: verboseTrace,
@@ -363,11 +364,11 @@
     // Otherwise, resolve the tag-specific components.
     var newTags = Map<BooleanSelector, SuiteConfiguration>.from(tags);
     var merged = tags.keys.fold(empty, (SuiteConfiguration merged, selector) {
-      if (!selector.evaluate(_metadata.tags.contains)) return merged;
+      if (!selector.evaluate(_metadata.tags)) return merged;
       return merged.merge(newTags.remove(selector));
     });
 
     if (merged == empty) return this;
-    return change(tags: newTags).merge(merged);
+    return this.change(tags: newTags).merge(merged);
   }
 }
diff --git a/test_core/lib/src/runner/version.dart b/test_core/lib/src/runner/version.dart
index b10aefa..23d8048 100644
--- a/test_core/lib/src/runner/version.dart
+++ b/test_core/lib/src/runner/version.dart
@@ -13,7 +13,7 @@
 final String testVersion = (() {
   dynamic lockfile;
   try {
-    lockfile = loadYaml(File('pubspec.lock').readAsStringSync());
+    lockfile = loadYaml(File("pubspec.lock").readAsStringSync());
   } on FormatException catch (_) {
     return null;
   } on IOException catch (_) {
@@ -21,38 +21,38 @@
   }
 
   if (lockfile is! Map) return null;
-  var packages = lockfile['packages'];
+  var packages = lockfile["packages"];
   if (packages is! Map) return null;
-  var package = packages['test'];
+  var package = packages["test"];
   if (package is! Map) return null;
 
-  var source = package['source'];
+  var source = package["source"];
   if (source is! String) return null;
 
   switch (source as String) {
-    case 'hosted':
-      var version = package['version'];
+    case "hosted":
+      var version = package["version"];
       return (version is String) ? version : null;
 
-    case 'git':
-      var version = package['version'];
+    case "git":
+      var version = package["version"];
       if (version is! String) return null;
-      var description = package['description'];
+      var description = package["description"];
       if (description is! Map) return null;
-      var ref = description['resolved-ref'];
+      var ref = description["resolved-ref"];
       if (ref is! String) return null;
 
-      return '$version (${ref.substring(0, 7)})';
+      return "$version (${ref.substring(0, 7)})";
 
-    case 'path':
-      var version = package['version'];
+    case "path":
+      var version = package["version"];
       if (version is! String) return null;
-      var description = package['description'];
+      var description = package["description"];
       if (description is! Map) return null;
-      var path = description['path'];
+      var path = description["path"];
       if (path is! String) return null;
 
-      return '$version (from $path)';
+      return "$version (from $path)";
 
     default:
       return null;
diff --git a/test_core/lib/src/runner/vm/environment.dart b/test_core/lib/src/runner/vm/environment.dart
index 6f919bf..6fedbec 100644
--- a/test_core/lib/src/runner/vm/environment.dart
+++ b/test_core/lib/src/runner/vm/environment.dart
@@ -10,9 +10,7 @@
 
 /// The environment in which VM tests are loaded.
 class VMEnvironment implements Environment {
-  @override
   final supportsDebugging = true;
-  @override
   final Uri observatoryUrl;
 
   /// The VM service isolate object used to control this isolate.
@@ -21,13 +19,10 @@
 
   VMEnvironment(this.observatoryUrl, this._isolate, this._client);
 
-  @override
   Uri get remoteDebuggerUrl => null;
 
-  @override
   Stream get onRestart => StreamController.broadcast().stream;
 
-  @override
   CancelableOperation displayPause() {
     var completer =
         CancelableCompleter(onCancel: () => _client.resume(_isolate.id));
diff --git a/test_core/lib/src/runner/vm/platform.dart b/test_core/lib/src/runner/vm/platform.dart
index 83e4b4f..4f521be 100644
--- a/test_core/lib/src/runner/vm/platform.dart
+++ b/test_core/lib/src/runner/vm/platform.dart
@@ -7,23 +7,23 @@
 import 'dart:io';
 import 'dart:isolate';
 
-import 'package:coverage/coverage.dart';
 import 'package:path/path.dart' as p;
 import 'package:stream_channel/isolate_channel.dart';
 import 'package:stream_channel/stream_channel.dart';
 import 'package:test_api/src/backend/runtime.dart'; // ignore: implementation_imports
 import 'package:test_api/src/backend/suite_platform.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/configuration.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/load_exception.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/platform.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/plugin/environment.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/plugin/platform_helpers.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/runner_suite.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/suite.dart'; // ignore: implementation_imports
+import 'package:test_core/src/util/dart.dart' // ignore: implementation_imports
+    as dart;
 import 'package:vm_service/vm_service.dart' hide Isolate;
 import 'package:vm_service/vm_service_io.dart';
 
-import '../../runner/configuration.dart';
-import '../../runner/environment.dart';
-import '../../runner/load_exception.dart';
-import '../../runner/platform.dart';
-import '../../runner/plugin/platform_helpers.dart';
-import '../../runner/runner_suite.dart';
-import '../../runner/suite.dart';
-import '../../util/dart.dart' as dart;
 import 'environment.dart';
 
 /// A platform that loads tests in isolates spawned within this Dart process.
@@ -33,11 +33,9 @@
 
   VMPlatform();
 
-  @override
   StreamChannel loadChannel(String path, SuitePlatform platform) =>
       throw UnimplementedError();
 
-  @override
   Future<RunnerSuite> load(String path, SuitePlatform platform,
       SuiteConfiguration suiteConfig, Object message) async {
     assert(platform.runtime == Runtime.vm);
@@ -61,7 +59,7 @@
       sink.close();
     }));
 
-    Environment environment;
+    VMEnvironment environment;
     IsolateRef isolateRef;
     if (_config.debug) {
       // Print an empty line because the VM prints an "Observatory listening on"
@@ -74,7 +72,7 @@
 
       var libraryPath = p.toUri(p.absolute(path)).toString();
       client = await vmServiceConnectUri(_wsUriFor(info.serverUri.toString()));
-      var isolateNumber = int.parse(isolateID.split('/').last);
+      var isolateNumber = int.parse(isolateID.split("/").last);
       isolateRef = (await client.getVM())
           .isolates
           .firstWhere((isolate) => isolate.number == isolateNumber.toString());
@@ -87,11 +85,8 @@
       environment = VMEnvironment(url, isolateRef, client);
     }
 
-    environment ??= PluginEnvironment();
-
-    var controller = deserializeSuite(
-        path, platform, suiteConfig, environment, channel, message,
-        gatherCoverage: () => _gatherCoverage(environment));
+    var controller = deserializeSuite(path, platform, suiteConfig,
+        environment ?? PluginEnvironment(), channel, message);
 
     if (isolateRef != null) {
       await client.streamListen('Debug');
@@ -139,7 +134,7 @@
       var channel = serializeSuite(() => test.main);
       IsolateChannel.connectSend(message).pipe(channel);
     }
-  ''', message);
+  ''', message, checked: true);
 }
 
 Future<Isolate> _spawnPrecompiledIsolate(
@@ -155,13 +150,6 @@
       checked: true);
 }
 
-Future<Map<String, dynamic>> _gatherCoverage(Environment environment) async {
-  final isolateId = Uri.parse(environment.observatoryUrl.fragment)
-      .queryParameters['isolateId'];
-  return await collect(environment.observatoryUrl, false, false, false, {},
-      isolateIds: {isolateId});
-}
-
 Future<Isolate> _spawnPubServeIsolate(
     String testPath, SendPort message, Uri pubServeUrl) async {
   var url = pubServeUrl.resolveUri(
@@ -170,16 +158,16 @@
   try {
     return await Isolate.spawnUri(url, [], message, checked: true);
   } on IsolateSpawnException catch (error) {
-    if (error.message.contains('OS Error: Connection refused') ||
-        error.message.contains('The remote computer refused')) {
+    if (error.message.contains("OS Error: Connection refused") ||
+        error.message.contains("The remote computer refused")) {
       throw LoadException(
           testPath,
-          'Error getting $url: Connection refused\n'
+          "Error getting $url: Connection refused\n"
           'Make sure "pub serve" is running.');
-    } else if (error.message.contains('404 Not Found')) {
+    } else if (error.message.contains("404 Not Found")) {
       throw LoadException(
           testPath,
-          'Error getting $url: 404 Not Found\n'
+          "Error getting $url: 404 Not Found\n"
           'Make sure "pub serve" is serving the test/ directory.');
     }
 
@@ -191,5 +179,5 @@
     "ws:${observatoryUrl.split(':').sublist(1).join(':')}ws";
 
 Uri _observatoryUrlFor(String base, String isolateId, String id) =>
-    Uri.parse('$base#/inspect?isolateId=${Uri.encodeQueryComponent(isolateId)}&'
-        'objectId=${Uri.encodeQueryComponent(id)}');
+    Uri.parse("$base#/inspect?isolateId=${Uri.encodeQueryComponent(isolateId)}&"
+        "objectId=${Uri.encodeQueryComponent(id)}");
diff --git a/test_core/lib/src/util/dart.dart b/test_core/lib/src/util/dart.dart
index bdb39ea..e142dca 100644
--- a/test_core/lib/src/util/dart.dart
+++ b/test_core/lib/src/util/dart.dart
@@ -8,6 +8,7 @@
 
 // ignore: deprecated_member_use
 import 'package:analyzer/analyzer.dart';
+import 'package:package_resolver/package_resolver.dart';
 import 'package:source_span/source_span.dart';
 
 import 'string_literal_iterator.dart';
@@ -18,16 +19,23 @@
 /// they will be resolved in the same context as the host isolate. [message] is
 /// passed to the [main] method of the code being run; the caller is responsible
 /// for using this to establish communication with the isolate.
-Future<Isolate> runInIsolate(String code, Object message,
-        {SendPort onExit}) async =>
-    Isolate.spawnUri(
-        Uri.dataFromString(code, mimeType: 'application/dart', encoding: utf8),
-        [],
-        message,
-        packageConfig: await Isolate.packageConfig,
-        checked: true,
-        onExit: onExit);
+///
+/// If [resolver] is passed, its package resolution strategy is used to resolve
+/// code in the spawned isolate. It defaults to [PackageResolver.current].
+Future<Isolate> runInIsolate(String code, message,
+    {PackageResolver resolver, bool checked, SendPort onExit}) async {
+  resolver ??= PackageResolver.current;
+  return await Isolate.spawnUri(
+      Uri.dataFromString(code, mimeType: 'application/dart', encoding: utf8),
+      [],
+      message,
+      packageConfig: await resolver.packageConfigUri,
+      checked: checked,
+      onExit: onExit);
+}
 
+// TODO(nweiz): Move this into the analyzer once it starts using SourceSpan
+// (issue 22977).
 /// Takes a span whose source is the value of a string that has been parsed from
 /// a Dart file and returns the corresponding span from within that Dart file.
 ///
diff --git a/test_core/lib/src/util/io.dart b/test_core/lib/src/util/io.dart
index c2cc3da..fafa9b0 100644
--- a/test_core/lib/src/util/io.dart
+++ b/test_core/lib/src/util/io.dart
@@ -21,7 +21,7 @@
 const _defaultLineLength = 200;
 
 /// Whether the test runner is running on Google-internal infrastructure.
-final bool inGoogle = Platform.version.contains('(google3)');
+final bool inGoogle = Platform.version.contains("(google3)");
 
 /// The maximum line length for output.
 final int lineLength = () {
@@ -48,6 +48,9 @@
   throw UnsupportedError('Unsupported operating system "$name".');
 })();
 
+// TODO(nweiz): Make this `new SuitePlatform.current()` once we only support
+// Dart 2 and we can import `dart:io` from within cross-platform libraries. See
+// commit 4ffda6d2.
 /// Returns a [SuitePlatform] with the given [runtime], and with [os] and
 /// [inGoogle] determined automatically.
 ///
@@ -60,15 +63,15 @@
 final stdinLines = StreamQueue(lineSplitter.bind(stdin));
 
 /// Whether this is being run as a subprocess in the test package's own tests.
-bool inTestTests = Platform.environment['_DART_TEST_TESTING'] == 'true';
+bool inTestTests = Platform.environment["_DART_TEST_TESTING"] == "true";
 
 /// The root directory below which to nest temporary directories created by the
 /// test runner.
 ///
 /// This is configurable so that the test code can validate that the runner
 /// cleans up after itself fully.
-final _tempDir = Platform.environment.containsKey('_UNITTEST_TEMP_DIR')
-    ? Platform.environment['_UNITTEST_TEMP_DIR']
+final _tempDir = Platform.environment.containsKey("_UNITTEST_TEMP_DIR")
+    ? Platform.environment["_UNITTEST_TEMP_DIR"]
     : Directory.systemTemp.path;
 
 /// Whether or not the current terminal supports ansi escape codes.
@@ -89,7 +92,7 @@
 ///
 /// Returns a future that completes to the value that the future returned from
 /// [fn] completes to.
-Future withTempDir(Future Function(String) fn) {
+Future withTempDir(Future fn(String path)) {
   return Future.sync(() {
     var tempDir = createTempDir();
     return Future.sync(() => fn(tempDir))
@@ -103,10 +106,10 @@
 /// part of a word's length. It only splits words on spaces, not on other sorts
 /// of whitespace.
 String wordWrap(String text) {
-  return text.split('\n').map((originalLine) {
+  return text.split("\n").map((originalLine) {
     var buffer = StringBuffer();
     var lengthSoFar = 0;
-    for (var word in originalLine.split(' ')) {
+    for (var word in originalLine.split(" ")) {
       var wordLength = withoutColors(word).length;
       if (wordLength > lineLength) {
         if (lengthSoFar != 0) buffer.writeln();
@@ -119,12 +122,12 @@
         buffer.write(word);
         lengthSoFar = wordLength;
       } else {
-        buffer.write(' $word');
+        buffer.write(" $word");
         lengthSoFar += 1 + wordLength;
       }
     }
     return buffer.toString();
-  }).join('\n');
+  }).join("\n");
 }
 
 /// Print a warning containing [message].
@@ -136,9 +139,9 @@
 /// If [print] is `true`, this prints the message using [print] to associate it
 /// with the current test. Otherwise, it prints it using [stderr].
 void warn(String message, {bool color, bool print = false}) {
-  color ??= canUseSpecialChars;
-  var header = color ? '\u001b[33mWarning:\u001b[0m' : 'Warning:';
-  (print ? core.print : stderr.writeln)(wordWrap('$header $message\n'));
+  if (color == null) color = canUseSpecialChars;
+  var header = color ? "\u001b[33mWarning:\u001b[0m" : "Warning:";
+  (print ? core.print : stderr.writeln)(wordWrap("$header $message\n"));
 }
 
 /// Repeatedly finds a probably-unused port on localhost and passes it to
@@ -150,7 +153,7 @@
 ///
 /// This is necessary for ensuring that our port binding isn't flaky for
 /// applications that don't print out the bound port.
-Future<T> getUnusedPort<T>(FutureOr<T> Function(int port) tryPort) async {
+Future<T> getUnusedPort<T>(FutureOr<T> tryPort(int port)) async {
   T value;
   await Future.doWhile(() async {
     value = await tryPort(await getUnsafeUnusedPort());
@@ -195,11 +198,11 @@
 Future<Uri> getRemoteDebuggerUrl(Uri base) async {
   try {
     var client = HttpClient();
-    var request = await client.getUrl(base.resolve('/json/list'));
+    var request = await client.getUrl(base.resolve("/json/list"));
     var response = await request.close();
     var jsonObject =
         await json.fuse(utf8).decoder.bind(response).single as List;
-    return base.resolve(jsonObject.first['devtoolsFrontendUrl'] as String);
+    return base.resolve(jsonObject.first["devtoolsFrontendUrl"] as String);
   } catch (_) {
     // If we fail to talk to the remote debugger protocol, give up and return
     // the raw URL rather than crashing.
diff --git a/test_core/lib/src/util/stack_trace_mapper.dart b/test_core/lib/src/util/stack_trace_mapper.dart
index 4f4a821..d99608c 100644
--- a/test_core/lib/src/util/stack_trace_mapper.dart
+++ b/test_core/lib/src/util/stack_trace_mapper.dart
@@ -2,6 +2,7 @@
 // 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 'package:package_resolver/package_resolver.dart';
 import 'package:source_map_stack_trace/source_map_stack_trace.dart' as mapper;
 import 'package:source_maps/source_maps.dart';
 import 'package:test_api/src/util/stack_trace_mapper.dart'; // ignore: implementation_imports
@@ -13,8 +14,8 @@
   /// This is initialized lazily in `mapStackTrace()`.
   Mapping _mapping;
 
-  /// The same package resolution information as was passed to dart2js.
-  final Map<String, Uri> _packageMap;
+  /// The package resolution information passed to dart2js.
+  final SyncPackageResolver _packageResolver;
 
   /// The URL of the SDK root from which dart2js loaded its sources.
   final Uri _sdkRoot;
@@ -26,26 +27,26 @@
   final Uri _mapUrl;
 
   JSStackTraceMapper(this._mapContents,
-      {Uri mapUrl, Map<String, Uri> packageMap, Uri sdkRoot})
+      {Uri mapUrl, SyncPackageResolver packageResolver, Uri sdkRoot})
       : _mapUrl = mapUrl,
-        _packageMap = packageMap,
+        _packageResolver = packageResolver,
         _sdkRoot = sdkRoot;
 
   /// Converts [trace] into a Dart stack trace.
-  @override
   StackTrace mapStackTrace(StackTrace trace) {
     _mapping ??= parseExtended(_mapContents, mapUrl: _mapUrl);
     return mapper.mapStackTrace(_mapping, trace,
-        packageMap: _packageMap, sdkRoot: _sdkRoot);
+        packageResolver: _packageResolver, sdkRoot: _sdkRoot);
   }
 
   /// Returns a Map representation which is suitable for JSON serialization.
-  @override
   Map<String, dynamic> serialize() {
     return {
       'mapContents': _mapContents,
       'sdkRoot': _sdkRoot?.toString(),
-      'packageConfigMap': _serializePackageConfigMap(_packageMap),
+      'packageConfigMap':
+          _serializePackageConfigMap(_packageResolver.packageConfigMap),
+      'packageRoot': _packageResolver.packageRoot?.toString(),
       'mapUrl': _mapUrl?.toString(),
     };
   }
@@ -54,12 +55,15 @@
   /// representation.
   static StackTraceMapper deserialize(Map serialized) {
     if (serialized == null) return null;
-    var deserialized = _deserializePackageConfigMap(
-        (serialized['packageConfigMap'] as Map).cast<String, String>());
-
+    String packageRoot = serialized['packageRoot'] as String ?? '';
     return JSStackTraceMapper(serialized['mapContents'] as String,
         sdkRoot: Uri.parse(serialized['sdkRoot'] as String),
-        packageMap: deserialized,
+        packageResolver: packageRoot.isNotEmpty
+            ? SyncPackageResolver.root(
+                Uri.parse(serialized['packageRoot'] as String))
+            : SyncPackageResolver.config(_deserializePackageConfigMap(
+                (serialized['packageConfigMap'] as Map)
+                    .cast<String, String>())),
         mapUrl: Uri.parse(serialized['mapUrl'] as String));
   }
 
diff --git a/test_core/lib/src/util/string_literal_iterator.dart b/test_core/lib/src/util/string_literal_iterator.dart
index 24caa08..6b340c2 100644
--- a/test_core/lib/src/util/string_literal_iterator.dart
+++ b/test_core/lib/src/util/string_literal_iterator.dart
@@ -37,7 +37,6 @@
 /// In addition to exposing the values of the runes themselves, this also
 /// exposes the offset of the current rune in the Dart source file.
 class StringLiteralIterator extends Iterator<int> {
-  @override
   int get current => _current;
   int _current;
 
@@ -97,7 +96,6 @@
     _offset = _strings.first.contentsOffset - 1;
   }
 
-  @override
   bool moveNext() {
     // If we're at beginning of a [SimpleStringLiteral], move forward until
     // there's actually text to consume.
diff --git a/test_core/lib/test_core.dart b/test_core/lib/test_core.dart
index 3dc5960..3e937c0 100644
--- a/test_core/lib/test_core.dart
+++ b/test_core/lib/test_core.dart
@@ -10,6 +10,7 @@
 
 import 'package:meta/meta.dart';
 import 'package:path/path.dart' as p;
+import 'package:pedantic/pedantic.dart';
 import 'package:test_api/backend.dart'; //ignore: deprecated_member_use
 import 'package:test_api/src/backend/declarer.dart'; // ignore: implementation_imports
 import 'package:test_api/src/backend/invoker.dart'; // ignore: implementation_imports
@@ -64,9 +65,10 @@
 
     var success = await runZoned(() => Invoker.guard(engine.run),
         zoneValues: {#test.declarer: _globalDeclarer});
+    // TODO(nweiz): Set the exit code on the VM when issue 6943 is fixed.
     if (success) return null;
     print('');
-    unawaited(Future.error('Dummy exception to set exit code.'));
+    unawaited(Future.error("Dummy exception to set exit code."));
   });
   return _globalDeclarer;
 }
@@ -106,15 +108,15 @@
 /// annotation classes: [Timeout], [Skip], or lists of those. These
 /// annotations apply only on the given platforms. For example:
 ///
-///     test('potentially slow test', () {
+///     test("potentially slow test", () {
 ///       // ...
 ///     }, onPlatform: {
 ///       // This test is especially slow on Windows.
-///       'windows': Timeout.factor(2),
-///       'browser': [
-///         Skip('TODO: add browser support'),
+///       "windows": new Timeout.factor(2),
+///       "browser": [
+///         new Skip("TODO: add browser support"),
 ///         // This will be slow on browsers once it works on them.
-///         Timeout.factor(2)
+///         new Timeout.factor(2)
 ///       ]
 ///     });
 ///
@@ -127,7 +129,7 @@
 /// avoid this flag if possible and instead use the test runner flag `-n` to
 /// filter tests by name.
 @isTest
-void test(description, dynamic Function() body,
+void test(description, body(),
     {String testOn,
     Timeout timeout,
     skip,
@@ -184,15 +186,15 @@
 /// annotation classes: [Timeout], [Skip], or lists of those. These
 /// annotations apply only on the given platforms. For example:
 ///
-///     group('potentially slow tests', () {
+///     group("potentially slow tests", () {
 ///       // ...
 ///     }, onPlatform: {
 ///       // These tests are especially slow on Windows.
-///       'windows': Timeout.factor(2),
-///       'browser': [
-///         Skip('TODO: add browser support'),
+///       "windows": new Timeout.factor(2),
+///       "browser": [
+///         new Skip("TODO: add browser support"),
 ///         // They'll be slow on browsers once it works on them.
-///         Timeout.factor(2)
+///         new Timeout.factor(2)
 ///       ]
 ///     });
 ///
@@ -205,7 +207,7 @@
 /// avoid this flag if possible, and instead use the test runner flag `-n` to
 /// filter tests by name.
 @isTestGroup
-void group(description, dynamic Function() body,
+void group(description, body(),
     {String testOn,
     Timeout timeout,
     skip,
@@ -240,7 +242,7 @@
 ///
 /// Each callback at the top level or in a given group will be run in the order
 /// they were declared.
-void setUp(dynamic Function() callback) => _declarer.setUp(callback);
+void setUp(callback()) => _declarer.setUp(callback);
 
 /// Registers a function to be run after tests.
 ///
@@ -255,7 +257,7 @@
 /// reverse of the order they were declared.
 ///
 /// See also [addTearDown], which adds tear-downs to a running test.
-void tearDown(dynamic Function() callback) => _declarer.tearDown(callback);
+void tearDown(callback()) => _declarer.tearDown(callback);
 
 /// Registers a function to be run once before all tests.
 ///
@@ -270,7 +272,7 @@
 /// dependencies between tests that should be isolated. In general, you should
 /// prefer [setUp], and only use [setUpAll] if the callback is prohibitively
 /// slow.
-void setUpAll(dynamic Function() callback) => _declarer.setUpAll(callback);
+void setUpAll(callback()) => _declarer.setUpAll(callback);
 
 /// Registers a function to be run once after all tests.
 ///
@@ -283,5 +285,4 @@
 /// dependencies between tests that should be isolated. In general, you should
 /// prefer [tearDown], and only use [tearDownAll] if the callback is
 /// prohibitively slow.
-void tearDownAll(dynamic Function() callback) =>
-    _declarer.tearDownAll(callback);
+void tearDownAll(callback()) => _declarer.tearDownAll(callback);
diff --git a/test_core/mono_pkg.yaml b/test_core/mono_pkg.yaml
index f6da98d..995000f 100644
--- a/test_core/mono_pkg.yaml
+++ b/test_core/mono_pkg.yaml
@@ -6,4 +6,4 @@
       dart: dev
     - group:
       - dartanalyzer: --fatal-warnings .
-      dart: 2.7.0
+      dart: 2.1.0
diff --git a/test_core/pubspec.yaml b/test_core/pubspec.yaml
index 881878a..8c6a1f1 100644
--- a/test_core/pubspec.yaml
+++ b/test_core/pubspec.yaml
@@ -1,33 +1,36 @@
 name: test_core
-version: 0.3.2
+version: 0.2.15
+author: Dart Team <misc@dartlang.org>
 description: A basic library for writing tests and running them on the VM.
 homepage: https://github.com/dart-lang/test/blob/master/pkgs/test_core
 
 environment:
-  sdk: ">=2.7.0 <3.0.0"
+  sdk: ">=2.2.0 <3.0.0"
 
 dependencies:
   analyzer: ">=0.36.0 <0.40.0"
   async: ^2.0.0
   args: ^1.4.0
-  boolean_selector: ">=1.0.0 <3.0.0"
+  boolean_selector: ^1.0.0
   collection: ^1.8.0
   coverage: ^0.13.3
   glob: ^1.0.0
   io: ^0.3.0
   meta: ^1.1.5
+  package_resolver: ^1.0.0
   path: ^1.2.0
   pedantic: ^1.0.0
   pool: ^1.3.0
-  source_map_stack_trace: ^2.0.0
+  source_map_stack_trace: ^1.1.4
   source_maps: ^0.10.2
   source_span: ^1.4.0
   stack_trace: ^1.9.0
   stream_channel: ">=1.7.0 <3.0.0"
-  vm_service: '>=1.0.0 <4.0.0'
+  vm_service: '>=1.0.0 <3.0.0'
   yaml: ^2.0.0
   # Use a tight version constraint to ensure that a constraint on matcher
   # properly constrains all features it provides.
   matcher: ">=0.12.6 <0.12.7"
   # Use an exact version until the test_api package is stable.
-  test_api: 0.2.15
+  test_api: 0.2.11
+
diff --git a/timing/.gitignore b/timing/.gitignore
new file mode 100644
index 0000000..1ddf798
--- /dev/null
+++ b/timing/.gitignore
@@ -0,0 +1,7 @@
+.packages
+/build/
+pubspec.lock
+
+# Files generated by dart tools
+.dart_tool
+doc/
diff --git a/timing/.travis.yml b/timing/.travis.yml
new file mode 100644
index 0000000..581db89
--- /dev/null
+++ b/timing/.travis.yml
@@ -0,0 +1,23 @@
+language: dart
+
+dart:
+  - 2.2.0
+  - dev
+
+dart_task:
+- dartanalyzer: --fatal-infos --fatal-warnings .
+- test
+
+matrix:
+  include:
+  # Only validate formatting using the dev release
+  - dart: dev
+    dart_task: dartfmt
+
+branches:
+  only:
+  - master
+
+cache:
+  directories:
+  - $HOME/.pub-cache
diff --git a/timing/BUILD.gn b/timing/BUILD.gn
new file mode 100644
index 0000000..d6b6e7e
--- /dev/null
+++ b/timing/BUILD.gn
@@ -0,0 +1,17 @@
+# This file is generated by importer.py for timing-0.1.1+2
+
+import("//build/dart/dart_library.gni")
+
+dart_library("timing") {
+  package_name = "timing"
+
+  # This parameter is left empty as we don't care about analysis or exporting
+  # these sources outside of the tree.
+  sources = []
+
+  disable_analysis = true
+
+  deps = [
+    "//third_party/dart-pkg/pub/json_annotation",
+  ]
+}
diff --git a/timing/CHANGELOG.md b/timing/CHANGELOG.md
new file mode 100644
index 0000000..8cb1627
--- /dev/null
+++ b/timing/CHANGELOG.md
@@ -0,0 +1,16 @@
+## 0.1.1+2
+
+- Support the latest version of `package:json_annotation`.
+- Require Dart 2.2 or later.
+
+## 0.1.1+1
+
+- Support the latest version of `package:json_annotation`.
+
+## 0.1.1
+
+- Add JSON serialization
+
+## 0.1.0
+
+- Initial release
diff --git a/timing/LICENSE b/timing/LICENSE
new file mode 100644
index 0000000..c4dc9ba
--- /dev/null
+++ b/timing/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2018, the Dart project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of Google Inc. nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/timing/README.md b/timing/README.md
new file mode 100644
index 0000000..400bc4b
--- /dev/null
+++ b/timing/README.md
@@ -0,0 +1,22 @@
+# [![Build Status](https://travis-ci.org/dart-lang/timing.svg?branch=master)](https://travis-ci.org/dart-lang/timing)
+
+Timing is a simple package for tracking performance of both async and sync actions
+
+```dart
+var tracker = AsyncTimeTracker();
+await tracker.track(() async {
+  // some async code here
+});
+
+// Use results
+print('${tracker.duration} ${tracker.innerDuration} ${tracker.slices}');
+```
+
+
+## Building
+
+Use the following command to re-generate `lib/src/timing.g.dart` file:
+
+```bash
+pub run build_runner build
+```
\ No newline at end of file
diff --git a/timing/analysis_options.yaml b/timing/analysis_options.yaml
new file mode 100644
index 0000000..210b18f
--- /dev/null
+++ b/timing/analysis_options.yaml
@@ -0,0 +1,93 @@
+include: package:pedantic/analysis_options.yaml
+analyzer:
+  strong-mode:
+    implicit-casts: false
+linter:
+  rules:
+    - always_declare_return_types
+    - annotate_overrides
+    - avoid_bool_literals_in_conditional_expressions
+    - avoid_classes_with_only_static_members
+    - avoid_empty_else
+    - avoid_function_literals_in_foreach_calls
+    - avoid_init_to_null
+    - avoid_null_checks_in_equality_operators
+    - avoid_relative_lib_imports
+    - avoid_renaming_method_parameters
+    - avoid_return_types_on_setters
+    - avoid_returning_null
+    - avoid_returning_null_for_void
+    - avoid_returning_this
+    - avoid_shadowing_type_parameters
+    - avoid_single_cascade_in_expression_statements
+    - avoid_types_as_parameter_names
+    - avoid_unused_constructor_parameters
+    - await_only_futures
+    - camel_case_types
+    - cancel_subscriptions
+    - cascade_invocations
+    - comment_references
+    - constant_identifier_names
+    - control_flow_in_finally
+    - curly_braces_in_flow_control_structures
+    - directives_ordering
+    - empty_catches
+    - empty_constructor_bodies
+    - empty_statements
+    - file_names
+    - hash_and_equals
+    - implementation_imports
+    - invariant_booleans
+    - iterable_contains_unrelated_type
+    - join_return_with_assignment
+    - library_names
+    - library_prefixes
+    - list_remove_unrelated_type
+    - literal_only_boolean_expressions
+    - no_duplicate_case_values
+    - non_constant_identifier_names
+    - null_closures
+    - omit_local_variable_types
+    - only_throw_errors
+    - overridden_fields
+    - package_api_docs
+    - package_names
+    - package_prefixed_library_names
+    - prefer_adjacent_string_concatenation
+    - prefer_collection_literals
+    - prefer_conditional_assignment
+    - prefer_const_constructors
+    - prefer_contains
+    - prefer_equal_for_default_values
+    - prefer_final_fields
+    - prefer_final_locals
+    - prefer_generic_function_type_aliases
+    - prefer_initializing_formals
+    - prefer_interpolation_to_compose_strings
+    - prefer_iterable_whereType
+    - prefer_is_empty
+    - prefer_is_not_empty
+    - prefer_null_aware_operators
+    - prefer_single_quotes
+    - prefer_typing_uninitialized_variables
+    - prefer_void_to_null
+    - recursive_getters
+    - slash_for_doc_comments
+    - test_types_in_equals
+    - throw_in_finally
+    - type_init_formals
+    - unawaited_futures
+    - unnecessary_brace_in_string_interps
+    - unnecessary_const
+    - unnecessary_getters_setters
+    - unnecessary_lambdas
+    - unnecessary_new
+    - unnecessary_null_aware_assignments
+    - unnecessary_null_in_if_null_operators
+    - unnecessary_parenthesis
+    - unnecessary_statements
+    - unnecessary_this
+    - unrelated_type_equality_checks
+    - use_rethrow_when_possible
+    - valid_regexps
+    - void_checks
diff --git a/timing/lib/src/clock.dart b/timing/lib/src/clock.dart
new file mode 100644
index 0000000..ae1f25f
--- /dev/null
+++ b/timing/lib/src/clock.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+/// A function that returns the current [DateTime].
+typedef _Clock = DateTime Function();
+DateTime _defaultClock() => DateTime.now();
+
+const _zoneKey = #timing_Clock;
+
+/// Returns the current [DateTime].
+///
+/// May be overridden for tests using [scopeClock].
+DateTime now() => (Zone.current[_zoneKey] as _Clock ?? _defaultClock)();
+
+/// Runs [f], with [clock] scoped whenever [now] is called.
+T scopeClock<T>(DateTime clock(), T f()) =>
+    runZoned(f, zoneValues: {_zoneKey: clock});
diff --git a/timing/lib/src/timing.dart b/timing/lib/src/timing.dart
new file mode 100644
index 0000000..17e2fb1
--- /dev/null
+++ b/timing/lib/src/timing.dart
@@ -0,0 +1,337 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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 'dart:async';
+
+import 'package:json_annotation/json_annotation.dart';
+
+import 'clock.dart';
+
+part 'timing.g.dart';
+
+/// The timings of an operation, including its [startTime], [stopTime], and
+/// [duration].
+@JsonSerializable(nullable: false)
+class TimeSlice {
+  /// The total duration of this operation, equivalent to taking the difference
+  /// between [stopTime] and [startTime].
+  Duration get duration => stopTime.difference(startTime);
+
+  final DateTime startTime;
+
+  final DateTime stopTime;
+
+  TimeSlice(this.startTime, this.stopTime);
+
+  factory TimeSlice.fromJson(Map<String, dynamic> json) =>
+      _$TimeSliceFromJson(json);
+
+  Map<String, dynamic> toJson() => _$TimeSliceToJson(this);
+
+  @override
+  String toString() => '($startTime + $duration)';
+}
+
+/// The timings of an async operation, consist of several sync [slices] and
+/// includes total [startTime], [stopTime], and [duration].
+@JsonSerializable(nullable: false)
+class TimeSliceGroup implements TimeSlice {
+  final List<TimeSlice> slices;
+
+  @override
+  DateTime get startTime => slices.first.startTime;
+
+  @override
+  DateTime get stopTime => slices.last.stopTime;
+
+  /// The total duration of this operation, equivalent to taking the difference
+  /// between [stopTime] and [startTime].
+  @override
+  Duration get duration => stopTime.difference(startTime);
+
+  /// Sum of [duration]s of all [slices].
+  ///
+  /// If some of slices implements [TimeSliceGroup] [innerDuration] will be used
+  /// to compute sum.
+  Duration get innerDuration => slices.fold(
+      Duration.zero,
+      (duration, slice) =>
+          duration +
+          (slice is TimeSliceGroup ? slice.innerDuration : slice.duration));
+
+  TimeSliceGroup(this.slices);
+
+  /// Constructs TimeSliceGroup from JSON representation
+  factory TimeSliceGroup.fromJson(Map<String, dynamic> json) =>
+      _$TimeSliceGroupFromJson(json);
+
+  @override
+  Map<String, dynamic> toJson() => _$TimeSliceGroupToJson(this);
+
+  @override
+  String toString() => slices.toString();
+}
+
+abstract class TimeTracker implements TimeSlice {
+  /// Whether tracking is active.
+  ///
+  /// Tracking is only active after `isStarted` and before `isFinished`.
+  bool get isTracking;
+
+  /// Whether tracking is finished.
+  ///
+  /// Tracker can't be used as [TimeSlice] before it is finished
+  bool get isFinished;
+
+  /// Whether tracking was started.
+  ///
+  /// Equivalent of `isTracking || isFinished`
+  bool get isStarted;
+
+  T track<T>(T Function() action);
+}
+
+/// Tracks only sync actions
+class SyncTimeTracker implements TimeTracker {
+  /// When this operation started, call [_start] to set this.
+  @override
+  DateTime get startTime => _startTime;
+  DateTime _startTime;
+
+  /// When this operation stopped, call [_stop] to set this.
+  @override
+  DateTime get stopTime => _stopTime;
+  DateTime _stopTime;
+
+  /// Start tracking this operation, must only be called once, before [_stop].
+  void _start() {
+    assert(_startTime == null && _stopTime == null);
+    _startTime = now();
+  }
+
+  /// Stop tracking this operation, must only be called once, after [_start].
+  void _stop() {
+    assert(_startTime != null && _stopTime == null);
+    _stopTime = now();
+  }
+
+  /// Splits tracker into two slices
+  ///
+  /// Returns new [TimeSlice] started on [startTime] and ended now.
+  /// Modifies [startTime] of tracker to current time point
+  ///
+  /// Don't change state of tracker. Can be called only while [isTracking], and
+  /// tracker will sill be tracking after call.
+  TimeSlice _split() {
+    if (!isTracking) {
+      throw StateError('Can be only called while tracking');
+    }
+    final _now = now();
+    final prevSlice = TimeSlice(_startTime, _now);
+    _startTime = _now;
+    return prevSlice;
+  }
+
+  @override
+  T track<T>(T Function() action) {
+    if (isStarted) {
+      throw StateError('Can not be tracked twice');
+    }
+    _start();
+    try {
+      return action();
+    } finally {
+      _stop();
+    }
+  }
+
+  @override
+  bool get isStarted => startTime != null;
+
+  @override
+  bool get isTracking => startTime != null && stopTime == null;
+
+  @override
+  bool get isFinished => startTime != null && stopTime != null;
+
+  @override
+  Duration get duration => stopTime?.difference(startTime);
+
+  /// Converts to JSON representation
+  ///
+  /// Can't be used before [isFinished]
+  @override
+  Map<String, dynamic> toJson() => _$TimeSliceToJson(this);
+}
+
+/// Async actions returning [Future] will be tracked as single sync time span
+/// from the beginning of execution till completion of future
+class SimpleAsyncTimeTracker extends SyncTimeTracker {
+  @override
+  T track<T>(T Function() action) {
+    if (isStarted) {
+      throw StateError('Can not be tracked twice');
+    }
+    T result;
+    _start();
+    try {
+      result = action();
+    } catch (_) {
+      _stop();
+      rethrow;
+    }
+    if (result is Future) {
+      return result.whenComplete(_stop) as T;
+    } else {
+      _stop();
+      return result;
+    }
+  }
+}
+
+/// No-op implementation of [SyncTimeTracker] that does nothing.
+class NoOpTimeTracker implements TimeTracker {
+  static final sharedInstance = NoOpTimeTracker();
+
+  @override
+  Duration get duration =>
+      throw UnsupportedError('Unsupported in no-op implementation');
+
+  @override
+  DateTime get startTime =>
+      throw UnsupportedError('Unsupported in no-op implementation');
+
+  @override
+  DateTime get stopTime =>
+      throw UnsupportedError('Unsupported in no-op implementation');
+
+  @override
+  bool get isStarted =>
+      throw UnsupportedError('Unsupported in no-op implementation');
+
+  @override
+  bool get isTracking =>
+      throw UnsupportedError('Unsupported in no-op implementation');
+
+  @override
+  bool get isFinished =>
+      throw UnsupportedError('Unsupported in no-op implementation');
+
+  @override
+  T track<T>(T Function() action) => action();
+
+  @override
+  Map<String, dynamic> toJson() =>
+      throw UnsupportedError('Unsupported in no-op implementation');
+}
+
+/// Track all async execution as disjoint time [slices] in ascending order.
+///
+/// Can [track] both async and sync actions.
+/// Can exclude time of tested trackers.
+///
+/// If tracked action spawns some dangled async executions behavior is't
+/// defined. Tracked might or might not track time of such executions
+class AsyncTimeTracker extends TimeSliceGroup implements TimeTracker {
+  final bool trackNested;
+
+  static const _zoneKey = #timing_AsyncTimeTracker;
+
+  AsyncTimeTracker({this.trackNested = true}) : super([]);
+
+  T _trackSyncSlice<T>(ZoneDelegate parent, Zone zone, T Function() action) {
+    // Ignore dangling runs after tracker completes
+    if (isFinished) {
+      return action();
+    }
+
+    final isNestedRun = slices.isNotEmpty &&
+        slices.last is SyncTimeTracker &&
+        (slices.last as SyncTimeTracker).isTracking;
+    final isExcludedNestedTrack = !trackNested && zone[_zoneKey] != this;
+
+    // Exclude nested sync tracks
+    if (isNestedRun && isExcludedNestedTrack) {
+      final timer = slices.last as SyncTimeTracker;
+      // Split already tracked time into new slice.
+      // Replace tracker in slices.last with splitted slice, to indicate for
+      // recursive calls that we not tracking.
+      slices.last = parent.run(zone, timer._split);
+      try {
+        return action();
+      } finally {
+        // Split tracker again and discard slice that was spend in nested tracker
+        parent.run(zone, timer._split);
+        // Add tracker back to list of slices and continue tracking
+        slices.add(timer);
+      }
+    }
+
+    // Exclude nested async tracks
+    if (isExcludedNestedTrack) {
+      return action();
+    }
+
+    // Split time slices in nested sync runs
+    if (isNestedRun) {
+      return action();
+    }
+
+    final timer = SyncTimeTracker();
+    slices.add(timer);
+
+    // Pass to parent zone, in case of overwritten clock
+    return parent.runUnary(zone, timer.track, action);
+  }
+
+  static final asyncTimeTrackerZoneSpecification = ZoneSpecification(
+    run: <R>(Zone self, ZoneDelegate parent, Zone zone, R Function() f) {
+      final tracker = self[_zoneKey] as AsyncTimeTracker;
+      return tracker._trackSyncSlice(parent, zone, () => parent.run(zone, f));
+    },
+    runUnary: <R, T>(Zone self, ZoneDelegate parent, Zone zone, R Function(T) f,
+        T arg) {
+      final tracker = self[_zoneKey] as AsyncTimeTracker;
+      return tracker._trackSyncSlice(
+          parent, zone, () => parent.runUnary(zone, f, arg));
+    },
+    runBinary: <R, T1, T2>(Zone self, ZoneDelegate parent, Zone zone,
+        R Function(T1, T2) f, T1 arg1, T2 arg2) {
+      final tracker = self[_zoneKey] as AsyncTimeTracker;
+      return tracker._trackSyncSlice(
+          parent, zone, () => parent.runBinary(zone, f, arg1, arg2));
+    },
+  );
+
+  @override
+  T track<T>(T Function() action) {
+    if (isStarted) {
+      throw StateError('Can not be tracked twice');
+    }
+    _tracking = true;
+    final result = runZoned(action,
+        zoneSpecification: asyncTimeTrackerZoneSpecification,
+        zoneValues: {_zoneKey: this});
+    if (result is Future) {
+      return result
+          // Break possible sync processing of future completion, so slice trackers can be finished
+          .whenComplete(() => Future.value())
+          .whenComplete(() => _tracking = false) as T;
+    } else {
+      _tracking = false;
+      return result;
+    }
+  }
+
+  bool _tracking;
+
+  @override
+  bool get isStarted => _tracking != null;
+
+  @override
+  bool get isFinished => _tracking == false;
+
+  @override
+  bool get isTracking => _tracking == true;
+}
diff --git a/timing/lib/src/timing.g.dart b/timing/lib/src/timing.g.dart
new file mode 100644
index 0000000..91293b2
--- /dev/null
+++ b/timing/lib/src/timing.g.dart
@@ -0,0 +1,32 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'timing.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+TimeSlice _$TimeSliceFromJson(Map<String, dynamic> json) {
+  return TimeSlice(
+    DateTime.parse(json['startTime'] as String),
+    DateTime.parse(json['stopTime'] as String),
+  );
+}
+
+Map<String, dynamic> _$TimeSliceToJson(TimeSlice instance) => <String, dynamic>{
+      'startTime': instance.startTime.toIso8601String(),
+      'stopTime': instance.stopTime.toIso8601String(),
+    };
+
+TimeSliceGroup _$TimeSliceGroupFromJson(Map<String, dynamic> json) {
+  return TimeSliceGroup(
+    (json['slices'] as List)
+        .map((e) => TimeSlice.fromJson(e as Map<String, dynamic>))
+        .toList(),
+  );
+}
+
+Map<String, dynamic> _$TimeSliceGroupToJson(TimeSliceGroup instance) =>
+    <String, dynamic>{
+      'slices': instance.slices,
+    };
diff --git a/timing/lib/timing.dart b/timing/lib/timing.dart
new file mode 100644
index 0000000..0163e8d
--- /dev/null
+++ b/timing/lib/timing.dart
@@ -0,0 +1,13 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS 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.
+
+export 'src/timing.dart'
+    show
+        TimeSlice,
+        TimeSliceGroup,
+        TimeTracker,
+        SyncTimeTracker,
+        SimpleAsyncTimeTracker,
+        AsyncTimeTracker,
+        NoOpTimeTracker;
diff --git a/timing/pubspec.yaml b/timing/pubspec.yaml
new file mode 100644
index 0000000..0b45123
--- /dev/null
+++ b/timing/pubspec.yaml
@@ -0,0 +1,19 @@
+name: timing
+version: 0.1.1+2
+description: >-
+  A simple package for tracking the performance of synchronous and asynchronous
+  actions.
+author: Dart Team <misc@dartlang.org>
+homepage: https://github.com/dart-lang/timing
+
+environment:
+  sdk: ">=2.2.0 <3.0.0"
+
+dependencies:
+  json_annotation: '>=1.0.0 <4.0.0'
+
+dev_dependencies:
+  build_runner: ^1.0.0
+  json_serializable: ^3.1.0
+  pedantic: ^1.1.0
+  test: ^1.0.0
diff --git a/url_launcher/BUILD.gn b/url_launcher/BUILD.gn
index ec21259..51448e6 100644
--- a/url_launcher/BUILD.gn
+++ b/url_launcher/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by importer.py for url_launcher-5.4.2
+# This file is generated by importer.py for url_launcher-5.4.1
 
 import("//build/dart/dart_library.gni")
 
diff --git a/url_launcher/CHANGELOG.md b/url_launcher/CHANGELOG.md
index 0cab101..a38c3eb 100644
--- a/url_launcher/CHANGELOG.md
+++ b/url_launcher/CHANGELOG.md
@@ -1,7 +1,3 @@
-## 5.4.2
-
-* Make the pedantic dev_dependency explicit.
-
 ## 5.4.1
 
 * Update unit tests to work with the PlatformInterface from package `plugin_platform_interface`.
diff --git a/url_launcher/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/url_launcher/example/macos/Runner.xcworkspace/contents.xcworkspacedata
deleted file mode 100644
index 1d526a1..0000000
--- a/url_launcher/example/macos/Runner.xcworkspace/contents.xcworkspacedata
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<Workspace
-   version = "1.0">
-   <FileRef
-      location = "group:Runner.xcodeproj">
-   </FileRef>
-</Workspace>
diff --git a/url_launcher/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/url_launcher/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
deleted file mode 100644
index 18d9810..0000000
--- a/url_launcher/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-	<key>IDEDidComputeMac32BitWarning</key>
-	<true/>
-</dict>
-</plist>
diff --git a/url_launcher/example/pubspec.yaml b/url_launcher/example/pubspec.yaml
index 065693e..4d9d01a 100644
--- a/url_launcher/example/pubspec.yaml
+++ b/url_launcher/example/pubspec.yaml
@@ -11,7 +11,6 @@
   e2e: "^0.2.0"
   flutter_driver:
     sdk: flutter
-  pedantic: ^1.8.0
 
 flutter:
   uses-material-design: true
diff --git a/url_launcher/pubspec.yaml b/url_launcher/pubspec.yaml
index ba92970..cd51673 100644
--- a/url_launcher/pubspec.yaml
+++ b/url_launcher/pubspec.yaml
@@ -2,7 +2,7 @@
 description: Flutter plugin for launching a URL on Android and iOS. Supports
   web, phone, SMS, and email schemes.
 homepage: https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher
-version: 5.4.2
+version: 5.4.1
 
 flutter:
   plugin:
@@ -35,7 +35,6 @@
   test: ^1.3.0
   mockito: ^4.1.1
   plugin_platform_interface: ^1.0.0
-  pedantic: ^1.8.0
 
 environment:
   sdk: ">=2.0.0-dev.28.0 <3.0.0"
diff --git a/url_launcher_macos/BUILD.gn b/url_launcher_macos/BUILD.gn
index e4bf2db..f91dd9d 100644
--- a/url_launcher_macos/BUILD.gn
+++ b/url_launcher_macos/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by importer.py for url_launcher_macos-0.0.1+4
+# This file is generated by importer.py for url_launcher_macos-0.0.1+2
 
 import("//build/dart/dart_library.gni")
 
diff --git a/url_launcher_macos/CHANGELOG.md b/url_launcher_macos/CHANGELOG.md
index 6ecac5b..446b448 100644
--- a/url_launcher_macos/CHANGELOG.md
+++ b/url_launcher_macos/CHANGELOG.md
@@ -1,11 +1,3 @@
-# 0.0.1+4
-
-* Make the pedantic dev_dependency explicit.
-
-# 0.0.1+3
-
-* Update Gradle version.
-
 # 0.0.1+2
 
 * Update README.
diff --git a/url_launcher_macos/android/gradle/wrapper/gradle-wrapper.properties b/url_launcher_macos/android/gradle/wrapper/gradle-wrapper.properties
index d757f3d..019065d 100644
--- a/url_launcher_macos/android/gradle/wrapper/gradle-wrapper.properties
+++ b/url_launcher_macos/android/gradle/wrapper/gradle-wrapper.properties
@@ -2,4 +2,4 @@
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
diff --git a/url_launcher_macos/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/url_launcher_macos/example/macos/Runner.xcworkspace/contents.xcworkspacedata
deleted file mode 100644
index 1d526a1..0000000
--- a/url_launcher_macos/example/macos/Runner.xcworkspace/contents.xcworkspacedata
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<Workspace
-   version = "1.0">
-   <FileRef
-      location = "group:Runner.xcodeproj">
-   </FileRef>
-</Workspace>
diff --git a/url_launcher_macos/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/url_launcher_macos/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
deleted file mode 100644
index 18d9810..0000000
--- a/url_launcher_macos/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-	<key>IDEDidComputeMac32BitWarning</key>
-	<true/>
-</dict>
-</plist>
diff --git a/url_launcher_macos/example/pubspec.yaml b/url_launcher_macos/example/pubspec.yaml
index 1125b6d..2552786 100644
--- a/url_launcher_macos/example/pubspec.yaml
+++ b/url_launcher_macos/example/pubspec.yaml
@@ -12,7 +12,6 @@
   e2e: "^0.2.0"
   flutter_driver:
     sdk: flutter
-  pedantic: ^1.8.0
 
 flutter:
   uses-material-design: true
diff --git a/url_launcher_macos/pubspec.yaml b/url_launcher_macos/pubspec.yaml
index 3f6d5e0..9e4b200 100644
--- a/url_launcher_macos/pubspec.yaml
+++ b/url_launcher_macos/pubspec.yaml
@@ -1,6 +1,6 @@
 name: url_launcher_macos
 description: macOS implementation of the url_launcher plugin.
-version: 0.0.1+4
+version: 0.0.1+2
 homepage: https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher_macos
 
 flutter:
@@ -17,6 +17,3 @@
 dependencies:
   flutter:
     sdk: flutter
-
-dev_dependencies:
-  pedantic: ^1.8.0
diff --git a/url_launcher_platform_interface/BUILD.gn b/url_launcher_platform_interface/BUILD.gn
index ac8d95a..4564bfc 100644
--- a/url_launcher_platform_interface/BUILD.gn
+++ b/url_launcher_platform_interface/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by importer.py for url_launcher_platform_interface-1.0.6
+# This file is generated by importer.py for url_launcher_platform_interface-1.0.5
 
 import("//build/dart/dart_library.gni")
 
diff --git a/url_launcher_platform_interface/CHANGELOG.md b/url_launcher_platform_interface/CHANGELOG.md
index 592102f..f55b4cb 100644
--- a/url_launcher_platform_interface/CHANGELOG.md
+++ b/url_launcher_platform_interface/CHANGELOG.md
@@ -1,7 +1,3 @@
-## 1.0.6
-
-* Make the pedantic dev_dependency explicit.
-
 ## 1.0.5
 
 * Make the `PlatformInterface` `_token` non `const` (as `const` `Object`s are not unique).
diff --git a/url_launcher_platform_interface/pubspec.yaml b/url_launcher_platform_interface/pubspec.yaml
index 806c967..aa57b14 100644
--- a/url_launcher_platform_interface/pubspec.yaml
+++ b/url_launcher_platform_interface/pubspec.yaml
@@ -3,7 +3,7 @@
 homepage: https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher_platform_interface
 # NOTE: We strongly prefer non-breaking changes, even at the expense of a
 # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes
-version: 1.0.6
+version: 1.0.5
 
 dependencies:
   flutter:
@@ -15,7 +15,6 @@
   flutter_test:
     sdk: flutter
   mockito: ^4.1.1
-  pedantic: ^1.8.0
 
 environment:
   sdk: ">=2.0.0-dev.28.0 <3.0.0"
diff --git a/url_launcher_web/BUILD.gn b/url_launcher_web/BUILD.gn
index c277ba0..afe68bd 100644
--- a/url_launcher_web/BUILD.gn
+++ b/url_launcher_web/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by importer.py for url_launcher_web-0.1.1+1
+# This file is generated by importer.py for url_launcher_web-0.1.1
 
 import("//build/dart/dart_library.gni")
 
diff --git a/url_launcher_web/CHANGELOG.md b/url_launcher_web/CHANGELOG.md
index 0cb57ef..df73939 100644
--- a/url_launcher_web/CHANGELOG.md
+++ b/url_launcher_web/CHANGELOG.md
@@ -1,7 +1,3 @@
-# 0.1.1+1
-
-- Make the pedantic dev_dependency explicit.
-
 # 0.1.1
 
 - Added support for mailto scheme
diff --git a/url_launcher_web/pubspec.yaml b/url_launcher_web/pubspec.yaml
index 8e74677..d02d250 100644
--- a/url_launcher_web/pubspec.yaml
+++ b/url_launcher_web/pubspec.yaml
@@ -1,7 +1,7 @@
 name: url_launcher_web
 description: Web platform implementation of url_launcher
 homepage: https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher_web
-version: 0.1.1+1
+version: 0.1.1
 
 flutter:
   plugin:
@@ -22,7 +22,6 @@
   flutter_test:
     sdk: flutter
   url_launcher: ^5.2.5
-  pedantic: ^1.8.0
 
 environment:
   sdk: ">=2.0.0-dev.28.0 <3.0.0"
diff --git a/video_player_platform_interface/BUILD.gn b/video_player_platform_interface/BUILD.gn
index a22628c..c75bc39 100644
--- a/video_player_platform_interface/BUILD.gn
+++ b/video_player_platform_interface/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by importer.py for video_player_platform_interface-1.0.5
+# This file is generated by importer.py for video_player_platform_interface-1.0.4
 
 import("//build/dart/dart_library.gni")
 
diff --git a/video_player_platform_interface/CHANGELOG.md b/video_player_platform_interface/CHANGELOG.md
index be1b0e3..79be27a 100644
--- a/video_player_platform_interface/CHANGELOG.md
+++ b/video_player_platform_interface/CHANGELOG.md
@@ -1,7 +1,3 @@
-## 1.0.5
-
-* Make the pedantic dev_dependency explicit.
-
 ## 1.0.4
 
 * Remove the deprecated `author:` field from pubspec.yaml
diff --git a/video_player_platform_interface/pubspec.yaml b/video_player_platform_interface/pubspec.yaml
index 2ce3bd1..8bae556 100644
--- a/video_player_platform_interface/pubspec.yaml
+++ b/video_player_platform_interface/pubspec.yaml
@@ -3,7 +3,7 @@
 homepage: https://github.com/flutter/plugins/tree/master/packages/video_player/video_player_platform_interface
 # NOTE: We strongly prefer non-breaking changes, even at the expense of a
 # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes
-version: 1.0.5
+version: 1.0.4
 
 dependencies:
   flutter:
@@ -14,7 +14,6 @@
   flutter_test:
     sdk: flutter
   mockito: ^4.1.1
-  pedantic: ^1.8.0
 
 environment:
   sdk: ">=2.0.0-dev.28.0 <3.0.0"
diff --git a/video_player_web/BUILD.gn b/video_player_web/BUILD.gn
index 27d4a7e..5a949ae 100644
--- a/video_player_web/BUILD.gn
+++ b/video_player_web/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by importer.py for video_player_web-0.1.2+1
+# This file is generated by importer.py for video_player_web-0.1.2
 
 import("//build/dart/dart_library.gni")
 
diff --git a/video_player_web/CHANGELOG.md b/video_player_web/CHANGELOG.md
index 00fc486..4c3aaa7 100644
--- a/video_player_web/CHANGELOG.md
+++ b/video_player_web/CHANGELOG.md
@@ -1,7 +1,3 @@
-## 0.1.2+1
-
-* Make the pedantic dev_dependency explicit.
-
 ## 0.1.2
 
 * Add a `PlatformException` to the player's `eventController` when there's a `videoElement.onError`. Fixes https://github.com/flutter/flutter/issues/48884.
diff --git a/video_player_web/pubspec.yaml b/video_player_web/pubspec.yaml
index e1435c8..4358f82 100644
--- a/video_player_web/pubspec.yaml
+++ b/video_player_web/pubspec.yaml
@@ -1,7 +1,7 @@
 name: video_player_web
 description: Web platform implementation of video_player
 homepage: https://github.com/flutter/plugins/tree/master/packages/video_player/video_player_web
-version: 0.1.2+1
+version: 0.1.2
 
 flutter:
   plugin:
@@ -23,7 +23,6 @@
     sdk: flutter
   video_player:
     path: ../video_player
-  pedantic: ^1.8.0
 
 environment:
   sdk: ">=2.0.0-dev.28.0 <3.0.0"
diff --git a/webdriver/BUILD.gn b/webdriver/BUILD.gn
index ddb3675..1c2312d 100644
--- a/webdriver/BUILD.gn
+++ b/webdriver/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by importer.py for webdriver-2.1.2
+# This file is generated by importer.py for webdriver-2.1.1
 
 import("//build/dart/dart_library.gni")
 
diff --git a/webdriver/CHANGELOG.md b/webdriver/CHANGELOG.md
index 1b9b0b0..db252ae 100644
--- a/webdriver/CHANGELOG.md
+++ b/webdriver/CHANGELOG.md
@@ -1,7 +1,3 @@
-## v2.1.2
-
-* Updated to latest version of sync_http.
-
 ## v2.1.1
 
 * Forward-compatible fix for upcoming Dart SDK breaking change
diff --git a/webdriver/analysis_options.yaml b/webdriver/analysis_options.yaml
index 6f8b463..0b79a58 100644
--- a/webdriver/analysis_options.yaml
+++ b/webdriver/analysis_options.yaml
@@ -1,3 +1,5 @@
+analyzer:
+  strong-mode: true
 linter:
   rules:
     - always_declare_return_types
@@ -11,6 +13,7 @@
     - cancel_subscriptions
     - control_flow_in_finally
     - empty_constructor_bodies
+    - empty_constructor_bodies
     - empty_statements
     - hash_and_equals
     - implementation_imports
@@ -40,6 +43,8 @@
     - prefer_typing_uninitialized_variables
     - recursive_getters
     - slash_for_doc_comments
+    - slash_for_doc_comments
+    - super_goes_last
     - test_types_in_equals
     - throw_in_finally
     - type_init_formals
diff --git a/webdriver/lib/async_core.dart b/webdriver/lib/async_core.dart
index 8013467..ce49382 100644
--- a/webdriver/lib/async_core.dart
+++ b/webdriver/lib/async_core.dart
@@ -124,5 +124,5 @@
   }
 
   return WebDriver(uri, sessionId, UnmodifiableMapView(capabilities),
-      createRequestClient(uri.resolve('session/$sessionId/')), spec);
+      createRequestClient(uri.resolve('session/${sessionId}/')), spec);
 }
diff --git a/webdriver/lib/src/async/common.dart b/webdriver/lib/src/async/common.dart
index d015eb6..26f7e9e 100644
--- a/webdriver/lib/src/async/common.dart
+++ b/webdriver/lib/src/async/common.dart
@@ -23,7 +23,7 @@
 /// Simple class to provide access to indexed properties such as WebElement
 /// attributes or css styles.
 class Attributes {
-  final GetAttribute _getAttribute;
+  GetAttribute _getAttribute;
 
   Attributes(this._getAttribute);
 
diff --git a/webdriver/lib/src/handler/json_wire_handler.dart b/webdriver/lib/src/handler/json_wire_handler.dart
index 4bcd061..3bad7bd 100644
--- a/webdriver/lib/src/handler/json_wire_handler.dart
+++ b/webdriver/lib/src/handler/json_wire_handler.dart
@@ -69,5 +69,5 @@
       deserialize(parseJsonWireResponse(response), createElement);
 
   @override
-  String toString() => 'JsonWire';
+  String toString() => "JsonWire";
 }
diff --git a/webdriver/pubspec.yaml b/webdriver/pubspec.yaml
index 31a5052..8fed1b8 100644
--- a/webdriver/pubspec.yaml
+++ b/webdriver/pubspec.yaml
@@ -1,5 +1,5 @@
 name: webdriver
-version: 2.1.2
+version: 2.1.1
 authors:
   - Marc Fisher II <fisherii@google.com>
   - Matt Staats<staats@google.com>
@@ -15,6 +15,6 @@
   matcher: ^0.12.3
   path: ^1.3.0
   stack_trace: ^1.3.0
-  sync_http: '>=0.1.1 <0.3.0'
+  sync_http: ^0.1.1
 dev_dependencies:
   test: '^1.0.0'
diff --git a/webkit_inspection_protocol/.travis.yml b/webkit_inspection_protocol/.travis.yml
index 35e0ab9..ad23559 100644
--- a/webkit_inspection_protocol/.travis.yml
+++ b/webkit_inspection_protocol/.travis.yml
@@ -1,10 +1,11 @@
 language: dart
+sudo: false
 
 dart:
   - dev
 
-#before_install:
-#  - export DISPLAY=:99.0
-#  - sh -e /etc/init.d/xvfb start
+before_install:
+  - export DISPLAY=:99.0
+  - sh -e /etc/init.d/xvfb start
 
 script: ./tool/travis.sh
diff --git a/webkit_inspection_protocol/BUILD.gn b/webkit_inspection_protocol/BUILD.gn
index 60ef4d0..12419e9 100644
--- a/webkit_inspection_protocol/BUILD.gn
+++ b/webkit_inspection_protocol/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by importer.py for webkit_inspection_protocol-0.5.0+1
+# This file is generated by importer.py for webkit_inspection_protocol-0.5.0
 
 import("//build/dart/dart_library.gni")
 
diff --git a/webkit_inspection_protocol/changelog.md b/webkit_inspection_protocol/changelog.md
index 27a9bb7..1f1847f 100644
--- a/webkit_inspection_protocol/changelog.md
+++ b/webkit_inspection_protocol/changelog.md
@@ -1,8 +1,5 @@
 # webkit_inspection_protocol.dart
 
-## 0.5.0+1
-- fixed a bug in reading type of `WipScope`
-
 ## 0.5.0
 - removed the bin/multiplex.dart binary to the example/ directory
 - remove dependencies on `package:args`, package:shelf`, and `package:shelf_web_socket`
diff --git a/webkit_inspection_protocol/lib/src/debugger.dart b/webkit_inspection_protocol/lib/src/debugger.dart
index fa4c428..5c3808b 100644
--- a/webkit_inspection_protocol/lib/src/debugger.dart
+++ b/webkit_inspection_protocol/lib/src/debugger.dart
@@ -160,7 +160,7 @@
   WipScope(this._map);
 
   // "catch", "closure", "global", "local", "with"
-  String get scope => _map['type'] as String;
+  String get scope => _map['scope'] as String;
 
   /// Object representing the scope. For global and with scopes it represents
   /// the actual object; for the rest of the scopes, it is artificial transient
diff --git a/webkit_inspection_protocol/pubspec.yaml b/webkit_inspection_protocol/pubspec.yaml
index 1de55da..8df358b 100644
--- a/webkit_inspection_protocol/pubspec.yaml
+++ b/webkit_inspection_protocol/pubspec.yaml
@@ -1,5 +1,5 @@
 name: webkit_inspection_protocol
-version: 0.5.0+1
+version: 0.5.0
 description: A client for the Chrome DevTools Protocol (previously called the Webkit Inspection Protocol).
 
 homepage: https://github.com/google/webkit_inspection_protocol.dart