[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: '&',
_LT: '<',
_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