| Index: sdk/lib/_internal/pub_generated/lib/src/barback/asset_environment.dart
|
| diff --git a/sdk/lib/_internal/pub_generated/lib/src/barback/asset_environment.dart b/sdk/lib/_internal/pub_generated/lib/src/barback/asset_environment.dart
|
| index 894a1aff4cfe1a2e9f6453c2ff0afa056405d53b..27c25932f2b922e2ab8f8bd0c49148f1e5684041 100644
|
| --- a/sdk/lib/_internal/pub_generated/lib/src/barback/asset_environment.dart
|
| +++ b/sdk/lib/_internal/pub_generated/lib/src/barback/asset_environment.dart
|
| @@ -1,9 +1,16 @@
|
| +// 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.
|
| +
|
| library pub.barback.asset_environment;
|
| +
|
| import 'dart:async';
|
| import 'dart:io';
|
| +
|
| import 'package:barback/barback.dart';
|
| import 'package:path/path.dart' as path;
|
| import 'package:watcher/watcher.dart';
|
| +
|
| import '../cached_package.dart';
|
| import '../entrypoint.dart';
|
| import '../exceptions.dart';
|
| @@ -21,7 +28,40 @@ import 'dart2js_transformer.dart';
|
| import 'load_all_transformers.dart';
|
| import 'pub_package_provider.dart';
|
| import 'source_directory.dart';
|
| +
|
| +/// The entire "visible" state of the assets of a package and all of its
|
| +/// dependencies, taking into account the user's configuration when running pub.
|
| +///
|
| +/// Where [PackageGraph] just describes the entrypoint's dependencies as
|
| +/// specified by pubspecs, this includes "transient" information like the mode
|
| +/// that the user is running pub in, or which directories they want to
|
| +/// transform.
|
| class AssetEnvironment {
|
| + /// Creates a new build environment for working with the assets used by
|
| + /// [entrypoint] and its dependencies.
|
| + ///
|
| + /// HTTP servers that serve directories from this environment will be bound
|
| + /// to [hostname] and have ports based on [basePort]. If omitted, they
|
| + /// default to "localhost" and "0" (use ephemeral ports), respectively.
|
| + ///
|
| + /// Loads all used transformers using [mode] (including dart2js if
|
| + /// [useDart2JS] is true).
|
| + ///
|
| + /// This will only add the root package's "lib" directory to the environment.
|
| + /// Other directories can be added to the environment using [serveDirectory].
|
| + ///
|
| + /// If [watcherType] is not [WatcherType.NONE] (the default), watches source
|
| + /// assets for modification.
|
| + ///
|
| + /// If [packages] is passed, only those packages' assets are loaded and
|
| + /// served.
|
| + ///
|
| + /// If [entrypoints] is passed, only transformers necessary to run those
|
| + /// entrypoints are loaded. Each entrypoint is expected to refer to a Dart
|
| + /// library.
|
| + ///
|
| + /// Returns a [Future] that completes to the environment once the inputs,
|
| + /// transformers, and server are loaded and ready.
|
| static Future<AssetEnvironment> create(Entrypoint entrypoint,
|
| BarbackMode mode, {WatcherType watcherType, String hostname, int basePort,
|
| Iterable<String> packages, Iterable<AssetId> entrypoints, bool useDart2JS:
|
| @@ -29,22 +69,29 @@ class AssetEnvironment {
|
| if (watcherType == null) watcherType = WatcherType.NONE;
|
| if (hostname == null) hostname = "localhost";
|
| if (basePort == null) basePort = 0;
|
| +
|
| return entrypoint.loadPackageGraph().then((graph) {
|
| log.fine("Loaded package graph.");
|
| graph = _adjustPackageGraph(graph, mode, packages);
|
| var barback = new Barback(new PubPackageProvider(graph));
|
| barback.log.listen(_log);
|
| +
|
| var environment =
|
| new AssetEnvironment._(graph, barback, mode, watcherType, hostname, basePort);
|
| +
|
| return environment._load(
|
| entrypoints: entrypoints,
|
| useDart2JS: useDart2JS).then((_) => environment);
|
| });
|
| }
|
| +
|
| + /// Return a version of [graph] that's restricted to [packages] (if passed)
|
| + /// and loads cached packages (if [mode] is [BarbackMode.DEBUG]).
|
| static PackageGraph _adjustPackageGraph(PackageGraph graph, BarbackMode mode,
|
| Iterable<String> packages) {
|
| if (mode != BarbackMode.DEBUG && packages == null) return graph;
|
| packages = (packages == null ? graph.packages.keys : packages).toSet();
|
| +
|
| return new PackageGraph(
|
| graph.entrypoint,
|
| graph.lockFile,
|
| @@ -56,31 +103,104 @@ class AssetEnvironment {
|
| return new CachedPackage(package, cache);
|
| }));
|
| }
|
| +
|
| + /// The server for the Web Socket API and admin interface.
|
| AdminServer _adminServer;
|
| +
|
| + /// The public directories in the root package that are included in the asset
|
| + /// environment, keyed by their root directory.
|
| final _directories = new Map<String, SourceDirectory>();
|
| +
|
| + /// The [Barback] instance used to process assets in this environment.
|
| final Barback barback;
|
| +
|
| + /// The root package being built.
|
| Package get rootPackage => graph.entrypoint.root;
|
| +
|
| + /// The graph of packages whose assets and transformers are loaded in this
|
| + /// environment.
|
| + ///
|
| + /// This isn't necessarily identical to the graph that's passed in to the
|
| + /// environment. It may expose fewer packages if some packages' assets don't
|
| + /// need to be loaded, and it may expose some [CachedPackage]s.
|
| final PackageGraph graph;
|
| +
|
| + /// The mode to run the transformers in.
|
| final BarbackMode mode;
|
| +
|
| + /// The [Transformer]s that should be appended by default to the root
|
| + /// package's transformer cascade. Will be empty if there are none.
|
| final _builtInTransformers = <Transformer>[];
|
| +
|
| + /// How source files should be watched.
|
| final WatcherType _watcherType;
|
| +
|
| + /// The hostname that servers are bound to.
|
| final String _hostname;
|
| +
|
| + /// The starting number for ports that servers will be bound to.
|
| + ///
|
| + /// Servers will be bound to ports starting at this number and then
|
| + /// incrementing from there. However, if this is zero, then ephemeral port
|
| + /// numbers will be selected for each server.
|
| final int _basePort;
|
| +
|
| + /// The modified source assets that have not been sent to barback yet.
|
| + ///
|
| + /// The build environment can be paused (by calling [pauseUpdates]) and
|
| + /// resumed ([resumeUpdates]). While paused, all source asset updates that
|
| + /// come from watching or adding new directories are not sent to barback.
|
| + /// When resumed, all pending source updates are sent to barback.
|
| + ///
|
| + /// This lets pub serve and pub build create an environment and bind several
|
| + /// servers before barback starts building and producing results
|
| + /// asynchronously.
|
| + ///
|
| + /// If this is `null`, then the environment is "live" and all updates will
|
| + /// go to barback immediately.
|
| Set<AssetId> _modifiedSources;
|
| +
|
| AssetEnvironment._(this.graph, this.barback, this.mode, this._watcherType,
|
| this._hostname, this._basePort);
|
| +
|
| + /// Gets the built-in [Transformer]s that should be added to [package].
|
| + ///
|
| + /// Returns `null` if there are none.
|
| Iterable<Transformer> getBuiltInTransformers(Package package) {
|
| + // Built-in transformers only apply to the root package.
|
| if (package.name != rootPackage.name) return null;
|
| +
|
| + // The built-in transformers are for dart2js and forwarding assets around
|
| + // dart2js.
|
| if (_builtInTransformers.isEmpty) return null;
|
| +
|
| return _builtInTransformers;
|
| }
|
| +
|
| + /// Starts up the admin server on an appropriate port and returns it.
|
| + ///
|
| + /// This may only be called once on the build environment.
|
| Future<AdminServer> startAdminServer([int port]) {
|
| + // Can only start once.
|
| assert(_adminServer == null);
|
| +
|
| + // The admin server is bound to one before the base port by default, unless
|
| + // it's ephemeral in which case the admin port is too.
|
| if (port == null) port = _basePort == 0 ? 0 : _basePort - 1;
|
| +
|
| return AdminServer.bind(this, _hostname, port).then((server) => _adminServer =
|
| server);
|
| }
|
| +
|
| + /// Binds a new port to serve assets from within [rootDirectory] in the
|
| + /// entrypoint package.
|
| + ///
|
| + /// Adds and watches the sources within that directory. Returns a [Future]
|
| + /// that completes to the bound server.
|
| + ///
|
| + /// If [rootDirectory] is already being served, returns that existing server.
|
| Future<BarbackServer> serveDirectory(String rootDirectory) {
|
| + // See if there is already a server bound to the directory.
|
| var directory = _directories[rootDirectory];
|
| if (directory != null) {
|
| return directory.server.then((server) {
|
| @@ -88,15 +208,21 @@ class AssetEnvironment {
|
| return server;
|
| });
|
| }
|
| +
|
| + // See if the new directory overlaps any existing servers.
|
| var overlapping = _directories.keys.where(
|
| (directory) =>
|
| path.isWithin(directory, rootDirectory) ||
|
| path.isWithin(rootDirectory, directory)).toList();
|
| +
|
| if (overlapping.isNotEmpty) {
|
| return new Future.error(
|
| new OverlappingSourceDirectoryException(overlapping));
|
| }
|
| +
|
| var port = _basePort;
|
| +
|
| + // If not using an ephemeral port, find the lowest-numbered available one.
|
| if (port != 0) {
|
| var boundPorts =
|
| _directories.values.map((directory) => directory.port).toSet();
|
| @@ -104,9 +230,11 @@ class AssetEnvironment {
|
| port++;
|
| }
|
| }
|
| +
|
| var sourceDirectory =
|
| new SourceDirectory(this, rootDirectory, _hostname, port);
|
| _directories[rootDirectory] = sourceDirectory;
|
| +
|
| return _provideDirectorySources(
|
| rootPackage,
|
| rootDirectory).then((subscription) {
|
| @@ -114,6 +242,15 @@ class AssetEnvironment {
|
| return sourceDirectory.serve();
|
| });
|
| }
|
| +
|
| + /// Binds a new port to serve assets from within the "bin" directory of
|
| + /// [package].
|
| + ///
|
| + /// Adds the sources within that directory and then binds a server to it.
|
| + /// Unlike [serveDirectory], this works with packages that are not the
|
| + /// entrypoint.
|
| + ///
|
| + /// Returns a [Future] that completes to the bound server.
|
| Future<BarbackServer> servePackageBinDirectory(String package) {
|
| return _provideDirectorySources(
|
| graph.packages[package],
|
| @@ -121,6 +258,14 @@ class AssetEnvironment {
|
| (_) =>
|
| BarbackServer.bind(this, _hostname, 0, package: package, rootDirectory: "bin"));
|
| }
|
| +
|
| + /// Precompiles all of [packageName]'s executables to snapshots in
|
| + /// [directory].
|
| + ///
|
| + /// If [executableIds] is passed, only those executables are precompiled.
|
| + ///
|
| + /// Returns a map from executable name to path for the snapshots that were
|
| + /// successfully precompiled.
|
| Future<Map<String, String>> precompileExecutables(String packageName,
|
| String directory, {Iterable<AssetId> executableIds}) {
|
| final completer0 = new Completer();
|
| @@ -133,15 +278,15 @@ class AssetEnvironment {
|
| servePackageBinDirectory(packageName).then((x0) {
|
| try {
|
| var server = x0;
|
| - join2(x1) {
|
| - completer0.complete(null);
|
| + join2() {
|
| + completer0.complete();
|
| }
|
| - finally0(cont0, v0) {
|
| + finally0(cont0) {
|
| server.close();
|
| - cont0(v0);
|
| + cont0();
|
| }
|
| - catch0(e1) {
|
| - finally0(join2, null);
|
| + catch0(e1, s1) {
|
| + finally0(() => completer0.completeError(e1, s1));
|
| }
|
| try {
|
| var precompiled = {};
|
| @@ -160,7 +305,7 @@ class AssetEnvironment {
|
| try {
|
| var result = x0;
|
| join0() {
|
| - completer0.complete(null);
|
| + completer0.complete();
|
| }
|
| if (result.success) {
|
| log.message(
|
| @@ -169,43 +314,38 @@ class AssetEnvironment {
|
| snapshotPath;
|
| join0();
|
| } else {
|
| - completer0.completeError(
|
| - new ApplicationException(
|
| - log.yellow("Failed to precompile ${_formatExecutable(id)}:\n") +
|
| - result.stderr.join('\n')));
|
| + throw new ApplicationException(
|
| + log.yellow("Failed to precompile ${_formatExecutable(id)}:\n") +
|
| + result.stderr.join('\n'));
|
| + join0();
|
| }
|
| - } catch (e0) {
|
| - completer0.completeError(e0);
|
| + } catch (e0, s0) {
|
| + completer0.completeError(e0, s0);
|
| }
|
| - }, onError: (e1) {
|
| - completer0.completeError(e1);
|
| - });
|
| - } catch (e2) {
|
| - completer0.completeError(e2);
|
| + }, onError: completer0.completeError);
|
| + } catch (e, s) {
|
| + completer0.completeError(e, s);
|
| }
|
| });
|
| return completer0.future;
|
| - }))).then((x2) {
|
| + }))).then((x1) {
|
| try {
|
| - x2;
|
| - finally0((v1) {
|
| - completer0.complete(v1);
|
| - }, precompiled);
|
| - } catch (e2) {
|
| - catch0(e2);
|
| + x1;
|
| + final v0 = precompiled;
|
| + finally0(() {
|
| + completer0.complete(v0);
|
| + });
|
| + } catch (e2, s2) {
|
| + catch0(e2, s2);
|
| }
|
| - }, onError: (e3) {
|
| - catch0(e3);
|
| - });
|
| - } catch (e4) {
|
| - catch0(e4);
|
| + }, onError: catch0);
|
| + } catch (e3, s3) {
|
| + catch0(e3, s3);
|
| }
|
| - } catch (e0) {
|
| - completer0.completeError(e0);
|
| + } catch (e4, s4) {
|
| + completer0.completeError(e4, s4);
|
| }
|
| - }, onError: (e5) {
|
| - completer0.completeError(e5);
|
| - });
|
| + }, onError: completer0.completeError);
|
| }
|
| if (executableIds.isEmpty) {
|
| completer0.complete([]);
|
| @@ -219,18 +359,30 @@ class AssetEnvironment {
|
| } else {
|
| join0();
|
| }
|
| - } catch (e6) {
|
| - completer0.completeError(e6);
|
| + } catch (e, s) {
|
| + completer0.completeError(e, s);
|
| }
|
| });
|
| return completer0.future;
|
| }
|
| +
|
| + /// Returns the executable name for [id].
|
| + ///
|
| + /// [id] is assumed to be an executable in a bin directory. The return value
|
| + /// is intended for log output and may contain formatting.
|
| String _formatExecutable(AssetId id) =>
|
| log.bold("${id.package}:${path.basenameWithoutExtension(id.path)}");
|
| +
|
| + /// Stops the server bound to [rootDirectory].
|
| + ///
|
| + /// Also removes any source files within that directory from barback. Returns
|
| + /// the URL of the unbound server, of `null` if [rootDirectory] was not
|
| + /// bound to a server.
|
| Future<Uri> unserveDirectory(String rootDirectory) {
|
| log.fine("Unserving $rootDirectory.");
|
| var directory = _directories.remove(rootDirectory);
|
| if (directory == null) return new Future.value();
|
| +
|
| return directory.server.then((server) {
|
| var url = server.url;
|
| return directory.close().then((_) {
|
| @@ -239,10 +391,19 @@ class AssetEnvironment {
|
| });
|
| });
|
| }
|
| +
|
| + /// Gets the source directory that contains [assetPath] within the entrypoint
|
| + /// package.
|
| + ///
|
| + /// If [assetPath] is not contained within a source directory, this throws
|
| + /// an exception.
|
| String getSourceDirectoryContaining(String assetPath) =>
|
| _directories.values.firstWhere(
|
| (dir) => path.isWithin(dir.directory, assetPath)).directory;
|
| +
|
| + /// Return all URLs serving [assetPath] in this environment.
|
| Future<List<Uri>> getUrlsForAssetPath(String assetPath) {
|
| + // Check the three (mutually-exclusive) places the path could be pointing.
|
| return _lookUpPathInServerRoot(assetPath).then((urls) {
|
| if (urls.isNotEmpty) return urls;
|
| return _lookUpPathInPackagesDirectory(assetPath);
|
| @@ -251,7 +412,12 @@ class AssetEnvironment {
|
| return _lookUpPathInDependency(assetPath);
|
| });
|
| }
|
| +
|
| + /// Look up [assetPath] in the root directories of servers running in the
|
| + /// entrypoint package.
|
| Future<List<Uri>> _lookUpPathInServerRoot(String assetPath) {
|
| + // Find all of the servers whose root directories contain the asset and
|
| + // generate appropriate URLs for each.
|
| return Future.wait(
|
| _directories.values.where(
|
| (dir) => path.isWithin(dir.directory, assetPath)).map((dir) {
|
| @@ -260,6 +426,8 @@ class AssetEnvironment {
|
| (server) => server.url.resolveUri(path.toUri(relativePath)));
|
| }));
|
| }
|
| +
|
| + /// Look up [assetPath] in the "packages" directory in the entrypoint package.
|
| Future<List<Uri>> _lookUpPathInPackagesDirectory(String assetPath) {
|
| var components = path.split(path.relative(assetPath));
|
| if (components.first != "packages") return new Future.value([]);
|
| @@ -269,11 +437,15 @@ class AssetEnvironment {
|
| (server) => server.url.resolveUri(path.toUri(assetPath)));
|
| }));
|
| }
|
| +
|
| + /// Look up [assetPath] in the "lib" or "asset" directory of a dependency
|
| + /// package.
|
| Future<List<Uri>> _lookUpPathInDependency(String assetPath) {
|
| for (var packageName in graph.packages.keys) {
|
| var package = graph.packages[packageName];
|
| var libDir = package.path('lib');
|
| var assetDir = package.path('asset');
|
| +
|
| var uri;
|
| if (path.isWithin(libDir, assetPath)) {
|
| uri = path.toUri(
|
| @@ -284,12 +456,19 @@ class AssetEnvironment {
|
| } else {
|
| continue;
|
| }
|
| +
|
| return Future.wait(_directories.values.map((dir) {
|
| return dir.server.then((server) => server.url.resolveUri(uri));
|
| }));
|
| }
|
| +
|
| return new Future.value([]);
|
| }
|
| +
|
| + /// Given a URL to an asset served by this environment, returns the ID of the
|
| + /// asset that would be accessed by that URL.
|
| + ///
|
| + /// If no server can serve [url], completes to `null`.
|
| Future<AssetId> getAssetIdForUrl(Uri url) {
|
| return Future.wait(
|
| _directories.values.map((dir) => dir.server)).then((servers) {
|
| @@ -302,28 +481,58 @@ class AssetEnvironment {
|
| return server.urlToId(url);
|
| });
|
| }
|
| +
|
| + /// Determines if [sourcePath] is contained within any of the directories in
|
| + /// the root package that are visible to this build environment.
|
| bool containsPath(String sourcePath) {
|
| var directories = ["lib"];
|
| directories.addAll(_directories.keys);
|
| return directories.any((dir) => path.isWithin(dir, sourcePath));
|
| }
|
| +
|
| + /// Pauses sending source asset updates to barback.
|
| void pauseUpdates() {
|
| + // Cannot pause while already paused.
|
| assert(_modifiedSources == null);
|
| +
|
| _modifiedSources = new Set<AssetId>();
|
| }
|
| +
|
| + /// Sends any pending source updates to barback and begins the asynchronous
|
| + /// build process.
|
| void resumeUpdates() {
|
| + // Cannot resume while not paused.
|
| assert(_modifiedSources != null);
|
| +
|
| barback.updateSources(_modifiedSources);
|
| _modifiedSources = null;
|
| }
|
| +
|
| + /// Loads the assets and transformers for this environment.
|
| + ///
|
| + /// This transforms and serves all library and asset files in all packages in
|
| + /// the environment's package graph. It loads any transformer plugins defined
|
| + /// in packages in [graph] and re-runs them as necessary when any input files
|
| + /// change.
|
| + ///
|
| + /// If [useDart2JS] is `true`, then the [Dart2JSTransformer] is implicitly
|
| + /// added to end of the root package's transformer phases.
|
| + ///
|
| + /// If [entrypoints] is passed, only transformers necessary to run those
|
| + /// entrypoints will be loaded.
|
| + ///
|
| + /// Returns a [Future] that completes once all inputs and transformers are
|
| + /// loaded.
|
| Future _load({Iterable<AssetId> entrypoints, bool useDart2JS}) {
|
| return log.progress("Initializing barback", () {
|
| final completer0 = new Completer();
|
| scheduleMicrotask(() {
|
| try {
|
| - var containsDart2JS = graph.entrypoint.root.pubspec.transformers.any(
|
| - ((transformers) =>
|
| - transformers.any((config) => config.id.package == '\$dart2js')));
|
| + var containsDart2JS =
|
| + graph.entrypoint.root.pubspec.transformers.any(((transformers) {
|
| + return transformers.any(
|
| + (config) => config.id.package == '\$dart2js');
|
| + }));
|
| join0() {
|
| BarbackServer.bind(this, _hostname, 0).then((x0) {
|
| try {
|
| @@ -369,21 +578,19 @@ class AssetEnvironment {
|
| try {
|
| x0;
|
| transformerServer.close();
|
| - completer0.complete(null);
|
| - } catch (e0) {
|
| - completer0.completeError(e0);
|
| + completer0.complete();
|
| + } catch (e0, s0) {
|
| + completer0.completeError(e0, s0);
|
| }
|
| - }, onError: (e1) {
|
| - completer0.completeError(e1);
|
| - });
|
| - } catch (e2) {
|
| - completer0.completeError(e2);
|
| + }, onError: completer0.completeError);
|
| + } catch (e, s) {
|
| + completer0.completeError(e, s);
|
| }
|
| });
|
| return completer0.future;
|
| }), fine: true));
|
| - } catch (e0) {
|
| - completer0.completeError(e0);
|
| + } catch (e, s) {
|
| + completer0.completeError(e, s);
|
| }
|
| });
|
| return completer0.future;
|
| @@ -391,25 +598,19 @@ class AssetEnvironment {
|
| [errorStream, barback.results, transformerServer.results]).then((x2) {
|
| try {
|
| x2;
|
| - completer0.complete(null);
|
| - } catch (e2) {
|
| - completer0.completeError(e2);
|
| + completer0.complete();
|
| + } catch (e0, s0) {
|
| + completer0.completeError(e0, s0);
|
| }
|
| - }, onError: (e3) {
|
| - completer0.completeError(e3);
|
| - });
|
| - } catch (e1) {
|
| - completer0.completeError(e1);
|
| + }, onError: completer0.completeError);
|
| + } catch (e1, s1) {
|
| + completer0.completeError(e1, s1);
|
| }
|
| - }, onError: (e4) {
|
| - completer0.completeError(e4);
|
| - });
|
| - } catch (e0) {
|
| - completer0.completeError(e0);
|
| + }, onError: completer0.completeError);
|
| + } catch (e2, s2) {
|
| + completer0.completeError(e2, s2);
|
| }
|
| - }, onError: (e5) {
|
| - completer0.completeError(e5);
|
| - });
|
| + }, onError: completer0.completeError);
|
| }
|
| if (!containsDart2JS && useDart2JS) {
|
| _builtInTransformers.addAll(
|
| @@ -418,13 +619,17 @@ class AssetEnvironment {
|
| } else {
|
| join0();
|
| }
|
| - } catch (e6) {
|
| - completer0.completeError(e6);
|
| + } catch (e, s) {
|
| + completer0.completeError(e, s);
|
| }
|
| });
|
| return completer0.future;
|
| }, fine: true);
|
| }
|
| +
|
| + /// Provides the public source assets in the environment to barback.
|
| + ///
|
| + /// If [watcherType] is not [WatcherType.NONE], enables watching on them.
|
| Future _provideSources() {
|
| final completer0 = new Completer();
|
| scheduleMicrotask(() {
|
| @@ -437,51 +642,64 @@ class AssetEnvironment {
|
| _provideDirectorySources(package, "lib").then((x0) {
|
| try {
|
| x0;
|
| - completer0.complete(null);
|
| - } catch (e0) {
|
| - completer0.completeError(e0);
|
| + completer0.complete();
|
| + } catch (e0, s0) {
|
| + completer0.completeError(e0, s0);
|
| }
|
| - }, onError: (e1) {
|
| - completer0.completeError(e1);
|
| - });
|
| + }, onError: completer0.completeError);
|
| }
|
| if (graph.isPackageStatic(package.name)) {
|
| completer0.complete(null);
|
| } else {
|
| join0();
|
| }
|
| - } catch (e2) {
|
| - completer0.completeError(e2);
|
| + } catch (e, s) {
|
| + completer0.completeError(e, s);
|
| }
|
| });
|
| return completer0.future;
|
| }))).then((x0) {
|
| try {
|
| x0;
|
| - completer0.complete(null);
|
| - } catch (e0) {
|
| - completer0.completeError(e0);
|
| + completer0.complete();
|
| + } catch (e0, s0) {
|
| + completer0.completeError(e0, s0);
|
| }
|
| - }, onError: (e1) {
|
| - completer0.completeError(e1);
|
| - });
|
| - } catch (e2) {
|
| - completer0.completeError(e2);
|
| + }, onError: completer0.completeError);
|
| + } catch (e, s) {
|
| + completer0.completeError(e, s);
|
| }
|
| });
|
| return completer0.future;
|
| }
|
| +
|
| + /// Provides all of the source assets within [dir] in [package] to barback.
|
| + ///
|
| + /// If [watcherType] is not [WatcherType.NONE], enables watching on them.
|
| + /// Returns the subscription to the watcher, or `null` if none was created.
|
| Future<StreamSubscription<WatchEvent>>
|
| _provideDirectorySources(Package package, String dir) {
|
| log.fine("Providing sources for ${package.name}|$dir.");
|
| + // TODO(rnystrom): Handle overlapping directories. If two served
|
| + // directories overlap like so:
|
| + //
|
| + // $ pub serve example example/subdir
|
| + //
|
| + // Then the sources of the subdirectory will be updated and watched twice.
|
| + // See: #17454
|
| if (_watcherType == WatcherType.NONE) {
|
| _updateDirectorySources(package, dir);
|
| return new Future.value();
|
| }
|
| +
|
| + // Watch the directory before listing is so we don't miss files that
|
| + // are added between the initial list and registering the watcher.
|
| return _watchDirectorySources(package, dir).then((_) {
|
| _updateDirectorySources(package, dir);
|
| });
|
| }
|
| +
|
| + /// Updates barback with all of the files in [dir] inside [package].
|
| void _updateDirectorySources(Package package, String dir) {
|
| var ids = _listDirectorySources(package, dir);
|
| if (_modifiedSources == null) {
|
| @@ -490,6 +708,8 @@ class AssetEnvironment {
|
| _modifiedSources.addAll(ids);
|
| }
|
| }
|
| +
|
| + /// Removes all of the files in [dir] in the root package from barback.
|
| void _removeDirectorySources(String dir) {
|
| var ids = _listDirectorySources(rootPackage, dir);
|
| if (_modifiedSources == null) {
|
| @@ -498,32 +718,69 @@ class AssetEnvironment {
|
| _modifiedSources.removeAll(ids);
|
| }
|
| }
|
| +
|
| + /// Lists all of the source assets in [dir] inside [package].
|
| + ///
|
| + /// For large packages, listing the contents is a performance bottleneck, so
|
| + /// this is optimized for our needs in here instead of using the more general
|
| + /// but slower [listDir].
|
| Iterable<AssetId> _listDirectorySources(Package package, String dir) {
|
| + // This is used in some performance-sensitive paths and can list many, many
|
| + // files. As such, it leans more havily towards optimization as opposed to
|
| + // readability than most code in pub. In particular, it avoids using the
|
| + // path package, since re-parsing a path is very expensive relative to
|
| + // string operations.
|
| return package.listFiles(beneath: dir).map((file) {
|
| + // From profiling, path.relative here is just as fast as a raw substring
|
| + // and is correct in the case where package.dir has a trailing slash.
|
| var relative = package.relative(file);
|
| +
|
| if (Platform.operatingSystem == 'windows') {
|
| relative = relative.replaceAll("\\", "/");
|
| }
|
| +
|
| var uri = new Uri(pathSegments: relative.split("/"));
|
| return new AssetId(package.name, uri.toString());
|
| });
|
| }
|
| +
|
| + /// Adds a file watcher for [dir] within [package], if the directory exists
|
| + /// and the package needs watching.
|
| Future<StreamSubscription<WatchEvent>> _watchDirectorySources(Package package,
|
| String dir) {
|
| + // If this package comes from a cached source, its contents won't change so
|
| + // we don't need to monitor it. `packageId` will be null for the
|
| + // application package, since that's not locked.
|
| var packageId = graph.lockFile.packages[package.name];
|
| if (packageId != null &&
|
| graph.entrypoint.cache.sources[packageId.source] is CachedSource) {
|
| return new Future.value();
|
| }
|
| +
|
| var subdirectory = package.path(dir);
|
| if (!dirExists(subdirectory)) return new Future.value();
|
| +
|
| + // TODO(nweiz): close this watcher when [barback] is closed.
|
| var watcher = _watcherType.create(subdirectory);
|
| var subscription = watcher.events.listen((event) {
|
| + // Don't watch files symlinked into these directories.
|
| + // TODO(rnystrom): If pub gets rid of symlinks, remove this.
|
| var parts = path.split(event.path);
|
| if (parts.contains("packages")) return;
|
| +
|
| + // Skip files that were (most likely) compiled from nearby ".dart"
|
| + // files. These are created by the Editor's "Run as JavaScript"
|
| + // command and are written directly into the package's directory.
|
| + // When pub's dart2js transformer then tries to create the same file
|
| + // name, we get a build error. To avoid that, just don't consider
|
| + // that file to be a source.
|
| + // TODO(rnystrom): Remove these when the Editor no longer generates
|
| + // .js files and users have had enough time that they no longer have
|
| + // these files laying around. See #15859.
|
| if (event.path.endsWith(".dart.js")) return;
|
| if (event.path.endsWith(".dart.js.map")) return;
|
| if (event.path.endsWith(".dart.precompiled.js")) return;
|
| +
|
| var idPath = package.relative(event.path);
|
| var id = new AssetId(package.name, path.toUri(idPath).toString());
|
| if (event.type == ChangeType.REMOVE) {
|
| @@ -538,17 +795,28 @@ class AssetEnvironment {
|
| barback.updateSources([id]);
|
| }
|
| });
|
| +
|
| return watcher.ready.then((_) => subscription);
|
| }
|
| +
|
| + /// Returns the result of [futureCallback] unless any stream in [streams]
|
| + /// emits an error before it's done.
|
| + ///
|
| + /// If a stream does emit an error, that error is thrown instead.
|
| + /// [futureCallback] is a callback rather than a plain future to ensure that
|
| + /// [streams] are listened to before any code that might cause an error starts
|
| + /// running.
|
| Future _withStreamErrors(Future futureCallback(), List<Stream> streams) {
|
| var completer = new Completer.sync();
|
| var subscriptions = streams.map(
|
| (stream) => stream.listen((_) {}, onError: completer.completeError)).toList();
|
| +
|
| new Future.sync(futureCallback).then((_) {
|
| if (!completer.isCompleted) completer.complete();
|
| }).catchError((error, stackTrace) {
|
| if (!completer.isCompleted) completer.completeError(error, stackTrace);
|
| });
|
| +
|
| return completer.future.whenComplete(() {
|
| for (var subscription in subscriptions) {
|
| subscription.cancel();
|
| @@ -556,68 +824,116 @@ class AssetEnvironment {
|
| });
|
| }
|
| }
|
| +
|
| +/// Log [entry] using Pub's logging infrastructure.
|
| +///
|
| +/// Since both [LogEntry] objects and the message itself often redundantly
|
| +/// show the same context like the file where an error occurred, this tries
|
| +/// to avoid showing redundant data in the entry.
|
| void _log(LogEntry entry) {
|
| messageMentions(text) =>
|
| entry.message.toLowerCase().contains(text.toLowerCase());
|
| +
|
| messageMentionsAsset(id) =>
|
| messageMentions(id.toString()) ||
|
| messageMentions(path.fromUri(entry.assetId.path));
|
| +
|
| var prefixParts = [];
|
| +
|
| + // Show the level (unless the message mentions it).
|
| if (!messageMentions(entry.level.name)) {
|
| prefixParts.add("${entry.level} from");
|
| }
|
| +
|
| + // Show the transformer.
|
| prefixParts.add(entry.transform.transformer);
|
| +
|
| + // Mention the primary input of the transform unless the message seems to.
|
| if (!messageMentionsAsset(entry.transform.primaryId)) {
|
| prefixParts.add("on ${entry.transform.primaryId}");
|
| }
|
| +
|
| + // If the relevant asset isn't the primary input, mention it unless the
|
| + // message already does.
|
| if (entry.assetId != entry.transform.primaryId &&
|
| !messageMentionsAsset(entry.assetId)) {
|
| prefixParts.add("with input ${entry.assetId}");
|
| }
|
| +
|
| var prefix = "[${prefixParts.join(' ')}]:";
|
| var message = entry.message;
|
| if (entry.span != null) {
|
| message = entry.span.message(entry.message);
|
| }
|
| +
|
| switch (entry.level) {
|
| case LogLevel.ERROR:
|
| log.error("${log.red(prefix)}\n$message");
|
| break;
|
| +
|
| case LogLevel.WARNING:
|
| log.warning("${log.yellow(prefix)}\n$message");
|
| break;
|
| +
|
| case LogLevel.INFO:
|
| log.message("${log.cyan(prefix)}\n$message");
|
| break;
|
| +
|
| case LogLevel.FINE:
|
| log.fine("${log.gray(prefix)}\n$message");
|
| break;
|
| }
|
| }
|
| +
|
| +/// Exception thrown when trying to serve a new directory that overlaps one or
|
| +/// more directories already being served.
|
| class OverlappingSourceDirectoryException implements Exception {
|
| + /// The relative paths of the directories that overlap the one that could not
|
| + /// be served.
|
| final List<String> overlappingDirectories;
|
| +
|
| OverlappingSourceDirectoryException(this.overlappingDirectories);
|
| }
|
| +
|
| +/// An enum describing different modes of constructing a [DirectoryWatcher].
|
| abstract class WatcherType {
|
| + /// A watcher that automatically chooses its type based on the operating
|
| + /// system.
|
| static const AUTO = const _AutoWatcherType();
|
| +
|
| + /// A watcher that always polls the filesystem for changes.
|
| static const POLLING = const _PollingWatcherType();
|
| +
|
| + /// No directory watcher at all.
|
| static const NONE = const _NoneWatcherType();
|
| +
|
| + /// Creates a new DirectoryWatcher.
|
| DirectoryWatcher create(String directory);
|
| +
|
| String toString();
|
| }
|
| +
|
| class _AutoWatcherType implements WatcherType {
|
| const _AutoWatcherType();
|
| +
|
| DirectoryWatcher create(String directory) => new DirectoryWatcher(directory);
|
| +
|
| String toString() => "auto";
|
| }
|
| +
|
| class _PollingWatcherType implements WatcherType {
|
| const _PollingWatcherType();
|
| +
|
| DirectoryWatcher create(String directory) =>
|
| new PollingDirectoryWatcher(directory);
|
| +
|
| String toString() => "polling";
|
| }
|
| +
|
| class _NoneWatcherType implements WatcherType {
|
| const _NoneWatcherType();
|
| +
|
| DirectoryWatcher create(String directory) => null;
|
| +
|
| String toString() => "none";
|
| }
|
|
|