Chromium Code Reviews| 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]; |
| + } |
| +} |