Index: sdk/lib/_internal/pub/lib/src/barback/load_all_transformers.dart |
diff --git a/sdk/lib/_internal/pub/lib/src/barback/load_all_transformers.dart b/sdk/lib/_internal/pub/lib/src/barback/load_all_transformers.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..03283889692e7d4ec6f136b9ad6a7cc9d224abb4 |
--- /dev/null |
+++ b/sdk/lib/_internal/pub/lib/src/barback/load_all_transformers.dart |
@@ -0,0 +1,223 @@ |
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
+// for 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.load_all_transformers; |
+ |
+import 'dart:async'; |
+ |
+import 'package:barback/barback.dart'; |
+ |
+import 'load_transformers.dart'; |
+import 'rewrite_import_transformer.dart'; |
+import 'server.dart'; |
+import 'watch_sources.dart'; |
+import '../utils.dart'; |
+ |
+/// Loads all transformers depended on by packages in [graph]. |
+/// |
+/// This uses [server] to serve the Dart files from which transformers are |
+/// loaded, then adds the transformers to `server.barback`. |
+Future loadAllTransformers(BarbackServer server, PackageGraph graph) { |
+ // In order to determine in what order we should load transformers, we need to |
+ // know which transformers depend on which others. This is different than |
+ // normal package dependencies. Let's begin with some terminology: |
+ // |
+ // * If package A is transformed by package B, we say A has a "transformer |
+ // dependency" on B. |
+ // * If A imports B we say A has a "package dependency" on B. |
+ // * If A needs B's transformers to be loaded in order to load A's |
+ // transformers, we say A has an "ordering dependency" on B. |
+ // |
+ // In particular, an ordering dependency is defined as follows: |
+ // |
+ // * If A has a transformer dependency on B, A also has an ordering dependency |
+ // on B. |
+ // * If A has a transitive package dependency on B and B has a transformer |
+ // dependency on C, A has an ordering dependency on C. |
+ // |
+ // The order that transformers are loaded is determined by each package's |
+ // ordering dependencies. We treat the packages as a directed acyclic[1] graph |
+ // where each package is a node and the ordering dependencies are the edges, |
+ // and start loading[2] nodes with no outgoing edges first. We then work our |
+ // way inward until all nodes are loaded. |
+ // |
+ // [1] TODO(nweiz): support cycles in some cases. |
+ // |
+ // [2] We use "loading a package" as a shorthand for loading that package's |
+ // transformers. |
Bob Nystrom
2013/09/06 18:27:29
I found this hard to grok and tried rewriting it.
nweiz
2013/09/09 20:43:38
I'm worried that these verb-based descriptions wil
Bob Nystrom
2013/09/09 23:34:57
I think I tried to catch most of the comments here
nweiz
2013/09/10 00:47:44
All of your rewrites were more verbose than the or
|
+ |
+ // Add a rewrite transformer for each package, so that we can resolve |
+ // "package:" imports while loading transformers. |
+ var rewrite = new RewriteImportTransformer(); |
+ for (var package in graph.packages.values) { |
+ server.barback.updateTransformers(package.name, [[rewrite]]); |
+ } |
+ |
+ var orderingDeps = _computeOrderingDeps(graph); |
+ var packageTransformers = _computePackageTransformers(graph); |
+ |
+ var loader = new _TransformerLoader(server, graph); |
+ |
+ // The packages that have no incoming ordering dependencies. These packages |
+ // will be loaded last, since all of their outgoing ordering dependencies need |
+ // to be loaded before they're loaded. However, they'll be traversed by |
+ // [loadPackage] first. |
Bob Nystrom
2013/09/06 18:27:29
This is confusing to me. "incoming" and "outgoing"
nweiz
2013/09/09 20:43:38
Done.
|
+ var rootPackages = graph.packages.keys.toSet() |
+ .difference(unionAll(orderingDeps.values)); |
Bob Nystrom
2013/09/06 18:27:29
If there is a cyclic dependency, this may be an em
nweiz
2013/09/09 20:43:38
There's a TODO in _computeOrderingDeps to detect a
|
+ |
+ // The Futures for packages that have been loaded or are being actively loaded |
+ // by [loadPackage]. Once one of these Futures is complete, the transformers |
+ // for that package will all be available from [loader]. |
+ var loadingPackages = new Map<String, Future>(); |
+ |
+ // A helper function that loads all the transformers that [package] uses, then |
+ // all the transformers that [package] defines. |
+ Future loadPackage(String package) { |
+ if (loadingPackages.containsKey(package)) return loadingPackages[package]; |
+ |
+ // First, load each package upon which [package] has an ordering dependency. |
Bob Nystrom
2013/09/06 18:27:29
"load each package that need to come before [packa
nweiz
2013/09/09 20:43:38
See above.
|
+ var future = Future.wait(orderingDeps[package].map(loadPackage)).then((_) { |
+ // Go through the transformers used by [package] phase-by-phase. If any |
+ // phase uses a transformer defined in [package] itself, that transform |
+ // should be loaded after running all previous phases. |
+ var transformers = [[rewrite]]; |
+ return Future.forEach(graph.packages[package].pubspec.transformers, |
+ (phase) { |
+ return Future.wait(phase.where((id) => id.package == package) |
+ .map(loader.load)).then((_) { |
+ transformers.add(unionAll(phase.map( |
+ (id) => loader.transformersFor(id)))); |
+ server.barback.updateTransformers(package, transformers); |
+ }); |
Bob Nystrom
2013/09/06 18:27:29
I don't mind not *supporting* cycles yet, but it w
nweiz
2013/09/09 20:43:38
This will also be covered by the TODO in _computeO
|
+ }).then((_) { |
+ // Now that we've applied all the transformers used by [package] via |
+ // [Barback.updateTransformers], we load any transformers defined in |
+ // [package] but used elsewhere. |
+ return Future.wait(packageTransformers[package].map(loader.load)); |
+ }); |
+ }); |
+ loadingPackages[package] = future; |
+ return future; |
+ } |
+ |
+ return Future.wait(rootPackages.map(loadPackage)).then((_) { |
+ /// Reset the transformers for each package to get rid of [rewrite], which |
+ /// is no longer needed. |
+ for (var package in graph.packages.values) { |
+ var phases = package.pubspec.transformers.map((phase) { |
+ return unionAll(phase.map((id) => loader.transformersFor(id))); |
+ }); |
+ server.barback.updateTransformers(package.name, phases); |
+ } |
+ }); |
+} |
+ |
+/// Computes and returns the graph of ordering dependencies for [graph]. |
+/// |
+/// This graph is in the form of a map whose keys are packages and whose values |
+/// are those packages' ordering dependencies. |
Bob Nystrom
2013/09/06 18:27:29
How about:
"Returns a map where each key is a pac
nweiz
2013/09/09 20:43:38
See above.
|
+Map<String, Set<String>> _computeOrderingDeps(PackageGraph graph) { |
Bob Nystrom
2013/09/06 18:27:29
Using "deps" here for dependencies, transformers,
nweiz
2013/09/09 20:43:38
See above. Also, using "ordering" here to refer to
|
+ var orderingDeps = new Map<String, Set<String>>(); |
+ for (var package in graph.packages.values) { |
+ var deps = _transformerDeps(graph, package.name); |
Bob Nystrom
2013/09/06 18:27:29
// The transformers applied to this package must b
nweiz
2013/09/09 20:43:38
Done, using my terminology.
|
+ deps.remove(package.name); |
+ for (var packageDep in _transitivePackageDeps(graph, package.name)) { |
Bob Nystrom
2013/09/06 18:27:29
// And everything those transformers import must t
nweiz
2013/09/09 20:43:38
That isn't quite what this code is doing, but I've
|
+ deps.addAll(_transformerDeps(graph, packageDep)); |
+ } |
+ orderingDeps[package.name] = deps; |
+ } |
+ // TODO(nweiz): check for cycles in orderingDeps. |
+ return orderingDeps; |
+} |
+ |
+/// Returns the transitive set of package dependencies for [package]. |
+Set<String> _transitivePackageDeps(PackageGraph graph, String package) { |
Bob Nystrom
2013/09/06 18:27:29
Move this into PackageGraph?
nweiz
2013/09/09 20:43:38
Done.
|
+ var seen = new Set<String>(); |
+ traverse(String package) { |
+ if (seen.contains(package)) return; |
+ seen.add(package); |
+ for (var dep in graph.packages[package].dependencies) { |
+ traverse(dep.name); |
+ } |
+ } |
+ |
+ traverse(package); |
+ seen.remove(package); |
+ return seen; |
+} |
+ |
+/// Returns the set of transformer dependencies for [package]. |
Bob Nystrom
2013/09/06 18:27:29
How about: "Returns the set of packages whose tran
nweiz
2013/09/09 20:43:38
See above.
|
+Set<String> _transformerDeps(PackageGraph graph, String package) => |
+ unionAll(graph.packages[package].pubspec.transformers) |
+ .map((id) => id.package).toSet(); |
+ |
+/// Returns a map from each package in [graph]'s name to the asset ids of all |
Bob Nystrom
2013/09/06 18:27:29
"package in [graph]'s name" -> "package name in gr
nweiz
2013/09/09 20:43:38
Done.
|
+/// transformers exposed by that package and used by other packages. |
+Map<String, Set<AssetId>> _computePackageTransformers(PackageGraph graph) { |
+ var packageTransformers = listToMap(graph.packages.values, |
+ (package) => package.name, (_) => new Set<AssetId>()); |
+ for (var package in graph.packages.values) { |
+ for (var id in unionAll(package.pubspec.transformers)) { |
Bob Nystrom
2013/09/06 18:27:29
Don't bother unioning here. Just do another nested
nweiz
2013/09/09 20:43:38
Done.
|
+ packageTransformers[id.package].add(id); |
+ } |
+ } |
+ return packageTransformers; |
+} |
+ |
+/// A class that loads transformers defined in specific files. |
+class _TransformerLoader { |
+ final BarbackServer _server; |
+ |
+ /// The loaded transformers for each asset id. |
Bob Nystrom
2013/09/06 18:27:29
"for" is a bit ambiguous. Took me a while to reali
nweiz
2013/09/09 20:43:38
Done.
|
+ final _transformers = new Map<AssetId, Set<Transformer>>(); |
+ |
+ /// The packages that use each transformer id. |
+ /// |
+ /// Used for error reporting. |
+ final _transformerUsers = new Map<AssetId, List<String>>(); |
+ |
+ _TransformerLoader(this._server, PackageGraph graph) { |
+ for (var package in graph.packages.values) { |
+ for (var id in unionAll(package.pubspec.transformers)) { |
+ _transformerUsers.putIfAbsent(id, () => <String>[]).add(package.name); |
+ } |
+ } |
+ |
+ // Ensure that the transformer users are printed in a deterministic order if |
+ // an error occurs. |
+ for (var list in _transformerUsers.values) { |
+ list.sort(); |
+ } |
+ } |
+ |
+ /// Loads the transformer(s) defined in the asset [id]. |
+ /// |
+ /// Once the returned future completes, these transformers can be retrieved |
+ /// using [transformersFor]. If [id] doesn't define any transformers, this |
+ /// will complete to an error. |
+ Future load(AssetId id) { |
+ if (_transformers.containsKey(id)) return new Future.value(); |
+ |
+ return loadTransformers(_server, id).then((transformers) { |
+ if (!transformers.isEmpty) { |
+ _transformers[id] = transformers; |
+ return; |
+ } |
+ |
+ var path = id.path.replaceFirst('lib/', ''); |
Bob Nystrom
2013/09/06 18:27:29
Use pathos here. This would do the wrong thing on
nweiz
2013/09/09 20:43:38
There should be no way that [id.path] doesn't begi
|
+ throw new ApplicationException( |
+ "No transformers were defined in package:${id.package}/$path,\n" |
+ "required by ${_transformerUsers[id].join(', ')}."); |
+ }); |
+ } |
+ |
+ /// Returns the set of transformers for [id]. |
+ /// |
+ /// It's an error to call this before [load] is called with [id] and the |
+ /// future it returns has completed. |
+ Set<Transformers> transformersFor(AssetId id) { |
+ assert(_transformers.containsKey(id)); |
+ return _transformers[id]; |
+ } |
+} |