Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(196)

Unified Diff: sdk/lib/_internal/pub/lib/src/barback/load_all_transformers.dart

Issue 24016002: Support transformers that depend on other transformers. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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];
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698