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"; |
} |