diff --git a/lib/src/barback.dart b/lib/src/barback.dart
index 4467531a58a28a89ae1c5ef926bcb3dd9eea9189..111b7689257095c526c9cc136a97891735915a74 100644
--- a/lib/src/barback.dart
+++ b/lib/src/barback.dart
@@ -7,8 +7,10 @@ library pub.barback;
 import 'dart:async';
 
 import 'package:barback/barback.dart';
-import 'package:path/path.dart' as path;
+import 'package:collection/collection.dart';
+import 'package:path/path.dart' as p;
 
+import 'io.dart';
 import 'utils.dart';
 import 'version.dart';
 
@@ -173,13 +175,13 @@ class TransformerId {
     throw new FormatException('Unsupported built-in transformer $package.');
   }
 
-  // TODO(nweiz): support deep equality on [configuration] as well.
   bool operator==(other) => other is TransformerId &&
       other.package == package &&
       other.path == path &&
-      other.configuration == configuration;
+      const DeepCollectionEquality().equals(other.configuration, configuration);
 
-  int get hashCode => package.hashCode ^ path.hashCode ^ configuration.hashCode;
+  int get hashCode => package.hashCode ^ path.hashCode ^
+      const DeepCollectionEquality().hash(configuration);
 
   String toString() => path == null ? package : '$package/$path';
 
@@ -197,11 +199,26 @@ class TransformerId {
             test: (e) => e is AssetNotFoundException);
   }
 
+  /// Returns the path to the library identified by this transformer within
+  /// [packageDir], which should be the directory of [package].
+  ///
+  /// If `path` is null, this will determine which library to load. Unlike
+  /// [getAssetId], this doesn't take generated assets into account; it's used
+  /// to determine transformers' dependencies, which requires looking at files
+  /// on disk.
+  String getFullPath(String packageDir) {
+    if (path != null) return p.join(packageDir, 'lib', p.fromUri('$path.dart'));
+
+    var transformerPath = p.join(packageDir, 'lib', 'transformer.dart');
+    if (fileExists(transformerPath)) return transformerPath;
+    return p.join(packageDir, 'lib', '$package.dart');
+  }
+
   /// Returns whether the include/exclude rules allow the transformer to run on
   /// [pathWithinPackage].
   ///
-  /// [pathWithinPackage] must be a path relative to the containing package's
-  /// root directory.
+  /// [pathWithinPackage] must be a URL-style path relative to the containing
+  /// package's root directory.
   bool canTransform(String pathWithinPackage) {
     // TODO(rnystrom): Support globs in addition to paths. See #17093.
     if (excludes != null) {
@@ -224,7 +241,7 @@ Uri idToPackageUri(AssetId id) {
   }
 
   return new Uri(scheme: 'package',
-      path: path.url.join(id.package, id.path.replaceFirst('lib/', '')));
+      path: p.url.join(id.package, id.path.replaceFirst('lib/', '')));
 }
 
 /// Converts [uri] into an [AssetId] if its path is within "packages".
@@ -234,7 +251,7 @@ Uri idToPackageUri(AssetId id) {
 ///
 /// If the URI doesn't contain one of those special directories, returns null.
 AssetId packagesUrlToId(Uri url) {
-  var parts = path.url.split(url.path);
+  var parts = p.url.split(url.path);
 
   // Strip the leading "/" from the URL.
   if (parts.isNotEmpty && parts.first == "/") parts = parts.skip(1).toList();
@@ -256,6 +273,6 @@ AssetId packagesUrlToId(Uri url) {
   }
 
   var package = parts[index + 1];
-  var assetPath = path.url.join("lib", path.url.joinAll(parts.skip(index + 2)));
+  var assetPath = p.url.join("lib", p.url.joinAll(parts.skip(index + 2)));
   return new AssetId(package, assetPath);
 }
diff --git a/lib/src/barback/asset_environment.dart b/lib/src/barback/asset_environment.dart
index 71dd4decde22a6bd1d21714c93b3e0d729f58e87..0c5a0340388d054d1011704ff36bc79e31cbaf06 100644
--- a/lib/src/barback/asset_environment.dart
+++ b/lib/src/barback/asset_environment.dart
@@ -9,7 +9,6 @@ import 'dart:io';
 
 import 'package:barback/barback.dart';
 import 'package:path/path.dart' as path;
-import 'package:stack_trace/stack_trace.dart';
 import 'package:watcher/watcher.dart';
 
 import '../entrypoint.dart';
diff --git a/lib/src/barback/cycle_exception.dart b/lib/src/barback/cycle_exception.dart
new file mode 100644
index 0000000000000000000000000000000000000000..c417ecf27ef526c238b6fd8a313941094ebfb9ab
--- /dev/null
+++ b/lib/src/barback/cycle_exception.dart
@@ -0,0 +1,65 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS d.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.cycle_exception;
+
+import 'package:stack_trace/stack_trace.dart';
+
+import '../utils.dart';
+
+/// An exception thrown when a transformer dependency cycle is detected.
+///
+/// A cycle exception is usually produced within a deeply-nested series of
+/// calls. The API is designed to make it easy for each of these calls to add to
+/// the message so that the full reasoning for the cycle is made visible to the
+/// user.
+///
+/// Each call's individual message is called a "step". A [CycleException] is
+/// represented internally as a linked list of steps.
+class CycleException implements ApplicationException {
+  final innerError = null;
+  final Trace innerTrace = null;
+
+  /// The step for this exception.
+  final String _step;
+
+  /// The next exception in the linked list.
+  ///
+  /// [_next]'s steps come after [_step].
+  final CycleException _next;
+
+  /// A list of all steps in the cycle.
+  List<String> get steps {
+    if (_step == null) return [];
+
+    var exception = this;
+    var steps = [];
+    while (exception != null) {
+      steps.add(exception._step);
+      exception = exception._next;
+    }
+    return steps;
+  }
+
+  String get message {
+    var steps = this.steps;
+    if (steps.isEmpty) return "Transformer cycle detected.";
+    return "Transformer cycle detected:\n" +
+        steps.map((step) => "  $step").join("\n");
+  }
+
+  /// Creates a new [CycleException] with zero or one steps.
+  CycleException([this._step])
+      : _next = null;
+
+  CycleException._(this._step, this._next);
+
+  /// Returns a copy of [this] with [step] added to the beginning of [steps].
+  CycleException prependStep(String step) {
+    if (_step == null) return new CycleException(step);
+    return new CycleException._(step, this);
+  }
+
+  String toString() => message;
+}
diff --git a/lib/src/barback/load_all_transformers.dart b/lib/src/barback/load_all_transformers.dart
index f8827f2ce79fe57b0101572ab6755f890cff4e93..bc14ea5a70e25316c08d064a4591b88edae2cf4d 100644
--- a/lib/src/barback/load_all_transformers.dart
+++ b/lib/src/barback/load_all_transformers.dart
@@ -13,11 +13,12 @@ import '../log.dart' as log;
 import '../package_graph.dart';
 import '../utils.dart';
 import 'asset_environment.dart';
+import 'barback_server.dart';
 import 'dart2js_transformer.dart';
 import 'excluding_transformer.dart';
 import 'load_transformers.dart';
 import 'rewrite_import_transformer.dart';
-import 'barback_server.dart';
+import 'transformers_needed_by_transformers.dart';
 
 /// Loads all transformers depended on by packages in [environment].
 ///
@@ -29,33 +30,26 @@ import 'barback_server.dart';
 /// automatically be added to the end of the root package's cascade.
 Future loadAllTransformers(AssetEnvironment environment,
     BarbackServer transformerServer) {
-  // 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
-  // (that is, the packages form a partially ordered set). We then load[2]
-  // packages in a topological sort order of this graph.
-  //
-  // [1] TODO(nweiz): support cycles in some cases.
-  //
-  // [2] We use "loading a package" as a shorthand for loading that package's
-  //     transformers.
+  var transformersNeededByTransformers =
+      computeTransformersNeededByTransformers(environment.graph);
+
+  var buffer = new StringBuffer();
+  buffer.writeln("Transformer dependencies:");
+  transformersNeededByTransformers.forEach((id, dependencies) {
+    if (dependencies.isEmpty) {
+      buffer.writeln("$id: -");
+    } else {
+      buffer.writeln("$id: ${toSentence(dependencies)}");
+    }
+  });
+  log.fine(buffer);
+
+  var phasedTransformers = _phaseTransformers(transformersNeededByTransformers);
+
+  var packagesThatUseTransformers =
+      _packagesThatUseTransformers(environment.graph);
+
+  var loader = new _TransformerLoader(environment, transformerServer);
 
   // Add a rewrite transformer for each package, so that we can resolve
   // "package:" imports while loading transformers.
@@ -65,67 +59,26 @@ Future loadAllTransformers(AssetEnvironment environment,
   }
   environment.barback.updateTransformers(r'$pub', [[rewrite]]);
 
-  var orderingDeps = _computeOrderingDeps(environment.graph);
-  var reverseOrderingDeps = reverseGraph(orderingDeps);
-  var packageTransformers = _computePackageTransformers(environment.graph);
-
-  var loader = new _TransformerLoader(environment, transformerServer);
-
-  // The packages on which no packages have ordering dependencies -- that is,
-  // the packages that don't need to be loaded before any other packages. These
-  // packages will be loaded last, since all of their ordering dependencies need
-  // to be loaded before they're loaded. However, they'll be traversed by
-  // [loadPackage] first.
-  var rootPackages = environment.graph.packages.keys.toSet()
-      .difference(unionAll(orderingDeps.values));
-
-  // 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.
-    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 transformer
-      // should be loaded after running all previous phases.
-      var transformers = [[rewrite]];
-
-      var phases = environment.graph.packages[package].pubspec.transformers;
-      return Future.forEach(phases, (phase) {
-        return loader.load(phase.where((id) => id.package == package))
-            .then((_) {
-          // If we've already loaded all the transformers in this package and no
-          // other package imports it, there's no need to keep applying
-          // transformers, so we can short-circuit.
-          var loadedAllTransformers = packageTransformers[package]
-              .difference(loader.loadedTransformers).isEmpty;
-          if (loadedAllTransformers &&
-              !reverseOrderingDeps.containsKey(package)) {
-            return null;
-          }
-
-          transformers.add(unionAll(phase.map(
-              (id) => loader.transformersFor(id))));
-          environment.barback.updateTransformers(package, transformers);
-        });
-      }).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 loader.load(packageTransformers[package]);
-      });
+  return Future.forEach(phasedTransformers, (phase) {
+    /// Load all the transformers in [phase], then add them to the appropriate
+    /// locations in the transformer graphs of the packages that use them.
+    return loader.load(phase).then((_) {
+      // Only update packages that use transformers in [phase].
+      var packagesToUpdate = unionAll(phase.map((id) =>
+          packagesThatUseTransformers[id]));
+      for (var packageName in packagesToUpdate) {
+        var package = environment.graph.packages[packageName];
+        var transformers = package.pubspec.transformers.map((packagePhase) {
+          return unionAll(packagePhase.map(loader.transformersFor));
+        }).toList();
+
+        // Make sure [rewrite] is still the first phase so that future
+        // transformers' "package:" imports will work.
+        transformers.insert(0, [rewrite]);
+        environment.barback.updateTransformers(packageName, transformers);
+      }
     });
-    loadingPackages[package] = future;
-    return future;
-  }
-
-  return Future.wait(rootPackages.map(loadPackage)).then((_) {
+  }).then((_) {
     /// Reset the transformers for each package to get rid of [rewrite], which
     /// is no longer needed.
     for (var package in environment.graph.packages.values) {
@@ -148,85 +101,50 @@ Future loadAllTransformers(AssetEnvironment environment,
   });
 }
 
-/// Computes and returns the graph of ordering dependencies for [graph].
+/// Given [transformerDependencies], a directed acyclic graph, returns a list of
+/// "phases" (sets of transformers).
 ///
-/// This graph is in the form of a map whose keys are packages and whose values
-/// are those packages' ordering dependencies.
-Map<String, Set<String>> _computeOrderingDeps(PackageGraph graph) {
-  var orderingDeps = new Map<String, Set<String>>();
-  // Iterate through the packages in a deterministic order so that if there are
-  // multiple cycles we choose which to print consistently.
-  var packages = ordered(graph.packages.values.map((package) => package.name));
-  for (var package in packages) {
-    // This package's transformer dependencies are also ordering dependencies.
-    var deps = _transformerDeps(graph, package);
-    deps.remove(package);
-    // The transformer dependencies of this package's transitive package
-    // dependencies are also ordering dependencies for this package.
-    var transitivePackageDeps = graph.transitiveDependencies(package)
-        .map((package) => package.name);
-    for (var packageDep in ordered(transitivePackageDeps)) {
-      var transformerDeps = _transformerDeps(graph, packageDep);
-      if (transformerDeps.contains(package)) {
-        throw _cycleError(graph, package, packageDep);
-      }
-      deps.addAll(transformerDeps);
-    }
-    orderingDeps[package] = deps;
+/// Each phase must be fully loaded and passed to barback before the next phase
+/// can be safely loaded. However, transformers within a phase can be safely
+/// loaded in parallel.
+List<Set<TransformerId>> _phaseTransformers(
+    Map<TransformerId, Set<TransformerId>> transformerDependencies) {
+  // A map from transformer ids to the indices of the phases that those
+  // transformer ids should end up in. Populated by [phaseNumberFor].
+  var phaseNumbers = {};
+  var phases = [];
+
+  phaseNumberFor(id) {
+    if (phaseNumbers.containsKey(id)) return phaseNumbers[id];
+    var dependencies = transformerDependencies[id];
+    phaseNumbers[id] = dependencies.isEmpty ? 0 :
+        maxAll(dependencies.map(phaseNumberFor)) + 1;
+    return phaseNumbers[id];
   }
 
-  return orderingDeps;
-}
-
-/// Returns the set of transformer dependencies for [package].
-Set<String> _transformerDeps(PackageGraph graph, String package) =>
-  unionAll(graph.packages[package].pubspec.transformers)
-      .where((id) => !id.isBuiltInTransformer)
-      .map((id) => id.package).toSet();
+  for (var id in transformerDependencies.keys) {
+    var phaseNumber = phaseNumberFor(id);
+    if (phases.length <= phaseNumber) phases.length = phaseNumber + 1;
+    if (phases[phaseNumber] == null) phases[phaseNumber] = new Set();
+    phases[phaseNumber].add(id);
+  }
 
-/// Returns an [ApplicationException] describing an ordering dependency cycle
-/// detected in [graph].
-///
-/// [dependee] and [depender] should be the names of two packages known to be in
-/// the cycle. In addition, [depender] should have a transformer dependency on
-/// [dependee].
-ApplicationException _cycleError(PackageGraph graph, String dependee,
-    String depender) {
-  assert(_transformerDeps(graph, depender).contains(dependee));
-
-  var simpleGraph = mapMap(graph.packages, value: (_, package) =>
-      package.dependencies.map((dep) => dep.name).toList());
-  var path = shortestPath(simpleGraph, dependee, depender);
-  path.add(dependee);
-  return new ApplicationException("Transformer cycle detected:\n" +
-      pairs(path).map((pair) {
-    var transformers = unionAll(graph.packages[pair.first].pubspec.transformers)
-        .where((id) => id.package == pair.last)
-        .map((id) => id.toString()).toList();
-    if (transformers.isEmpty) {
-      return "  ${pair.first} depends on ${pair.last}";
-    } else {
-      return "  ${pair.first} is transformed by ${toSentence(transformers)}";
-    }
-  }).join("\n"));
+  return phases;
 }
 
-/// Returns a map from each package name in [graph] to the transformer ids of
-/// all transformers exposed by that package and used by other packages.
-Map<String, Set<TransformerId>> _computePackageTransformers(
+/// Returns a map from transformer ids to all packages in [graph] that use each
+/// transformer.
+Map<TransformerId, Set<String>> _packagesThatUseTransformers(
     PackageGraph graph) {
-  var packageTransformers = new Map.fromIterable(graph.packages.values,
-      key: (package) => package.name,
-      value: (_) => new Set<TransformerId>());
+  var results = {};
   for (var package in graph.packages.values) {
     for (var phase in package.pubspec.transformers) {
       for (var id in phase) {
-        if (id.isBuiltInTransformer) continue;
-        packageTransformers[id.package].add(id);
+        results.putIfAbsent(id, () => new Set()).add(package.name);
       }
     }
   }
-  return packageTransformers;
+  return results;
 }
 
 /// A class that loads transformers defined in specific files.
@@ -304,12 +222,12 @@ class _TransformerLoader {
 
   /// 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.
+  /// If this is called before [load] for a given [id], it will return an empty
+  /// set.
   Set<Transformer> transformersFor(TransformerId id) {
     if (_transformers.containsKey(id)) return _transformers[id];
+    if (id.package != '\$dart2js') return new Set();
 
-    assert(id.package == '\$dart2js');
     var transformer;
     try {
       transformer = new Dart2JSTransformer.withSettings(_environment,
@@ -324,4 +242,4 @@ class _TransformerLoader {
     _transformers[id] = new Set.from([transformer]);
     return _transformers[id];
   }
-}
\ No newline at end of file
+}
diff --git a/lib/src/barback/rewrite_import_transformer.dart b/lib/src/barback/rewrite_import_transformer.dart
index 8392289e3e33d8d0d4d7b8815b84a6f36b7ace63..7482d620a840df40be7a5ecdb6c093888f231a6a 100644
--- a/lib/src/barback/rewrite_import_transformer.dart
+++ b/lib/src/barback/rewrite_import_transformer.dart
@@ -7,7 +7,8 @@ library pub.rewrite_import_transformer;
 import 'dart:async';
 
 import 'package:barback/barback.dart';
-import 'package:analyzer/analyzer.dart';
+
+import '../dart.dart';
 
 /// A transformer used internally to rewrite "package:" imports so they point to
 /// the barback server rather than to pub's package root.
@@ -16,13 +17,12 @@ class RewriteImportTransformer extends Transformer {
 
   Future apply(Transform transform) {
     return transform.primaryInput.readAsString().then((contents) {
-      var collector = new _DirectiveCollector();
-      parseDirectives(contents, name: transform.primaryInput.id.toString())
-          .accept(collector);
+      var directives = parseImportsAndExports(contents,
+          name: transform.primaryInput.id.toString());
 
       var buffer = new StringBuffer();
       var index = 0;
-      for (var directive in collector.directives) {
+      for (var directive in directives) {
         var uri = Uri.parse(directive.uri.stringValue);
         if (uri.scheme != 'package') continue;
 
@@ -38,10 +38,3 @@ class RewriteImportTransformer extends Transformer {
     });
   }
 }
-
-/// A simple visitor that collects import and export nodes.
-class _DirectiveCollector extends GeneralizingAstVisitor {
-  final directives = <UriBasedDirective>[];
-
-  visitUriBasedDirective(UriBasedDirective node) => directives.add(node);
-}
diff --git a/lib/src/barback/transformers_needed_by_transformers.dart b/lib/src/barback/transformers_needed_by_transformers.dart
new file mode 100644
index 0000000000000000000000000000000000000000..26bac5371f162bab0ac18b1590739b46bfd790d2
--- /dev/null
+++ b/lib/src/barback/transformers_needed_by_transformers.dart
@@ -0,0 +1,389 @@
+// 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.transformers_needed_by_transformers;
+
+import 'package:path/path.dart' as p;
+
+import '../barback.dart';
+import '../dart.dart';
+import '../io.dart';
+import '../package.dart';
+import '../package_graph.dart';
+import '../utils.dart';
+import 'cycle_exception.dart';
+
+/// Returns a dependency graph for transformers in [graph].
+///
+/// This graph is represented by a map whose keys are the vertices and whose
+/// values are sets representing edges from the given vertex. Each vertex is a
+/// [TransformerId]. If there's an edge from `T1` to `T2`, then `T2` must be
+/// loaded before `T1` can be loaded.
+///
+/// The returned graph is transitively closed. That is, if there's an edge from
+/// `T1` to `T2` and an edge from `T2` to `T3`, there's also an edge from `T1`
+/// to `T2`.
+Map<TransformerId, Set<TransformerId>> computeTransformersNeededByTransformers(
+    PackageGraph graph) {
+  var result = {};
+  var computer = new _DependencyComputer(graph);
+  for (var packageName in ordered(graph.packages.keys)) {
+    var package = graph.packages[packageName];
+    for (var phase in package.pubspec.transformers) {
+      for (var id in phase) {
+        if (id.isBuiltInTransformer) continue;
+        result[id] = computer.transformersNeededByTransformer(id);
+      }
+    }
+  }
+  return result;
+}
+
+/// A helper class for [computeTransformersNeededByTransformers] that keeps
+/// package-graph-wide state and caches over the course of the computation.
+class _DependencyComputer {
+  /// The package graph being analyzed.
+  final PackageGraph _graph;
+
+  /// The names of packages for which [_PackageDependencyComputer]s are
+  /// currently loading.
+  ///
+  /// This is used to detect transformer cycles. If a package's libraries or
+  /// transformers are referenced while the transformers that apply to it are
+  /// being processed, that indicates an unresolvable cycle.
+  final _loadingPackageComputers = new Set<String>();
+
+  /// [_PackageDependencyComputer]s that have been loaded.
+  final _packageComputers = new Map<String, _PackageDependencyComputer>();
+
+  /// A cache of the results of [transformersNeededByPackage].
+  final _transformersNeededByPackages = new Map<String, Set<TransformerId>>();
+
+  _DependencyComputer(this._graph) {
+    ordered(_graph.packages.keys).forEach(_loadPackageComputer);
+  }
+
+  /// Returns the set of all transformers that need to be loaded before [id] is
+  /// loaded.
+  Set<TransformerId> transformersNeededByTransformer(TransformerId id) {
+    if (id.isBuiltInTransformer) return new Set();
+    _loadPackageComputer(id.package);
+    return _packageComputers[id.package].transformersNeededByTransformer(id);
+  }
+
+  /// Returns the set of all transformers that need to be loaded before
+  /// [packageUri] (a "package:" URI) can be safely imported from an external
+  /// package.
+  Set<TransformerId> transformersNeededByPackageUri(Uri packageUri) {
+    // TODO(nweiz): We can do some pre-processing on the package graph (akin to
+    // the old ordering dependency computation) to figure out which packages are
+    // guaranteed not to require any transformers. That'll let us avoid extra
+    // work here and in [transformersNeededByPackage].
+
+    var components = p.split(p.fromUri(packageUri.path));
+    var packageName = components.first;
+    var package = _graph.packages[packageName];
+    if (package == null) {
+      // TODO(nweiz): include source range information here.
+      fail('A transformer imported unknown package "$packageName" (in '
+          '"$packageUri").');
+    }
+
+    var library = p.join(package.dir, 'lib', p.joinAll(components.skip(1)));
+
+    _loadPackageComputer(packageName);
+    return _packageComputers[packageName].transformersNeededByLibrary(library);
+  }
+
+  /// Returns the set of all transformers that need to be loaded before
+  /// everything in [rootPackage] can be used.
+  ///
+  /// This is conservative in that it returns all transformers that could
+  /// theoretically affect [rootPackage]. It only looks at which transformers
+  /// packages use and which packages they depend on; it ignores imports
+  /// entirely.
+  ///
+  /// We fall back on this conservative analysis when a transformer
+  /// (transitively) imports a transformed library. The result of the
+  /// transformation may import any dependency or hit any transformer, so we
+  /// have to assume that it will.
+  Set<TransformerId> transformersNeededByPackage(String rootPackage) {
+    if (_transformersNeededByPackages.containsKey(rootPackage)) {
+      return _transformersNeededByPackages[rootPackage];
+    }
+
+    var results = new Set();
+    var seen = new Set();
+
+    traversePackage(packageName) {
+      if (seen.contains(packageName)) return;
+      seen.add(packageName);
+
+      var package = _graph.packages[packageName];
+      for (var phase in package.pubspec.transformers) {
+        for (var id in phase) {
+          if (id.isBuiltInTransformer) continue;
+          if (_loadingPackageComputers.contains(id.package)) {
+            throw new CycleException("$packageName is transformed by $id");
+          }
+          results.add(id);
+        }
+      }
+
+      var dependencies = packageName == _graph.entrypoint.root.name ?
+          package.immediateDependencies : package.dependencies;
+      for (var dep in dependencies) {
+        try {
+          traversePackage(dep.name);
+        } on CycleException catch (error) {
+          throw error.prependStep("$packageName depends on ${dep.name}");
+        }
+      }
+    }
+
+    traversePackage(rootPackage);
+    _transformersNeededByPackages[rootPackage] = results;
+    return results;
+  }
+
+
+  /// Ensure that a [_PackageDependencyComputer] for [packageName] is loaded.
+  ///
+  /// If the computer has already been loaded, this does nothing. If the
+  /// computer is in the process of being loaded, this throws a
+  /// [CycleException].
+  void _loadPackageComputer(String packageName) {
+    if (_loadingPackageComputers.contains(packageName)) {
+      throw new CycleException();
+    }
+    if (_packageComputers.containsKey(packageName)) return;
+    _loadingPackageComputers.add(packageName);
+    _packageComputers[packageName] =
+        new _PackageDependencyComputer(this, packageName);
+    _loadingPackageComputers.remove(packageName);
+  }
+}
+
+/// A helper class for [computeTransformersNeededByTransformers] that keeps
+/// package-specific state and caches over the course of the computation.
+class _PackageDependencyComputer {
+  /// The parent [_DependencyComputer].
+  final _DependencyComputer _dependencyComputer;
+
+  /// The package whose dependencies [this] is computing.
+  final Package _package;
+
+  /// The set of transformers that currently apply to [this].
+  ///
+  /// This is added to phase-by-phase while [this] is being initialized. This is
+  /// necessary to model the dependencies of a transformer that's applied to its
+  /// own package.
+  final _applicableTransformers = new Set();
+
+  /// A cache of imports and exports parsed from libraries in this package.
+  final _directives = new Map<Uri, Set<Uri>>();
+
+  /// The set of libraries for which there are currently active
+  /// [transformersNeededByLibrary] calls.
+  ///
+  /// This is used to guard against infinite loops caused by libraries in
+  /// different packages importing one another circularly.
+  /// [transformersNeededByLibrary] will return an empty set for any active
+  /// libraries.
+  final _activeLibraries = new Set<String>();
+
+  /// A cache of the results of [transformersNeededByTransformer].
+  final _transformersNeededByTransformers =
+      new Map<TransformerId, Set<TransformerId>>();
+
+  /// A cache of the results of [_getTransitiveExternalDirectives].
+  ///
+  /// This is invalidated whenever [_applicableTransformers] changes.
+  final _transitiveExternalDirectives = new Map<String, Set<Uri>>();
+
+  _PackageDependencyComputer(_DependencyComputer dependencyComputer,
+          String packageName)
+      : _dependencyComputer = dependencyComputer,
+        _package = dependencyComputer._graph.packages[packageName] {
+    // If [_package] uses its own transformers, there will be fewer transformers
+    // running on [_package] while its own transformers are loading than there
+    // will be once all its transformers are finished loading. To handle this,
+    // we run [transformersNeededByTransformer] to pre-populate
+    // [_transformersNeededByLibraries] while [_applicableTransformers] is
+    // smaller.
+    for (var phase in _package.pubspec.transformers) {
+      for (var id in phase) {
+        try {
+          if (id.package != _package.name) {
+            // Probe [id]'s transformer dependencies to ensure that it doesn't
+            // depend on this package. If it does, a CycleError will be thrown.
+            _dependencyComputer.transformersNeededByTransformer(id);
+          } else {
+            // Store the transformers needed specifically with the current set
+            // of [_applicableTransformers]. When reporting this transformer's
+            // dependencies, [computeTransformersNeededByTransformers] will use
+            // this stored set of dependencies rather than the potentially wider
+            // set that would be recomputed if [transformersNeededByLibrary]
+            // were called anew.
+            _transformersNeededByTransformers[id] =
+                transformersNeededByLibrary(id.getFullPath(_package.dir));
+          }
+        } on CycleException catch (error) {
+          throw error.prependStep("$packageName is transformed by $id");
+        }
+      }
+
+      // Clear the cached imports and exports because the new transformers may
+      // start transforming a library whose directives were previously
+      // statically analyzable.
+      _transitiveExternalDirectives.clear();
+      _applicableTransformers.addAll(phase);
+    }
+  }
+
+  /// Returns the set of all transformers that need to be loaded before [id] is
+  /// loaded.
+  ///
+  /// [id] must refer to a transformer in [_package].
+  Set<TransformerId> transformersNeededByTransformer(TransformerId id) {
+    assert(id.package == _package.name);
+    if (_transformersNeededByTransformers.containsKey(id)) {
+      return _transformersNeededByTransformers[id];
+    }
+
+    _transformersNeededByTransformers[id] =
+        transformersNeededByLibrary(id.getFullPath(_package.dir));
+    return _transformersNeededByTransformers[id];
+  }
+
+  /// Returns the set of all transformers that need to be loaded before
+  /// [library] is imported.
+  ///
+  /// If [library] or anything it imports/exports within this package is
+  /// transformed by [_applicableTransformers], this will return a conservative
+  /// set of transformers (see also
+  /// [_DependencyComputer.transformersNeededByPackage]).
+  Set<TransformerId> transformersNeededByLibrary(String library) {
+    library = p.normalize(library);
+    if (_activeLibraries.contains(library)) return new Set();
+    _activeLibraries.add(library);
+
+    try {
+      var externalDirectives = _getTransitiveExternalDirectives(library);
+      if (externalDirectives == null) {
+        var rootName = _dependencyComputer._graph.entrypoint.root.name;
+        var dependencies = _package.name == rootName ?
+            _package.immediateDependencies : _package.dependencies;
+
+        // If anything transitively imported/exported by [library] within this
+        // package is modified by a transformer, we don't know what it will
+        // load, so we take the conservative approach and say it depends on
+        // everything.
+        return _applicableTransformers.union(unionAll(dependencies.map((dep) {
+          try {
+            return _dependencyComputer.transformersNeededByPackage(dep.name);
+          } on CycleException catch (error) {
+            throw error.prependStep("${_package.name} depends on ${dep.name}");
+          }
+        })));
+      } else {
+        // If nothing's transformed, then we only depend on the transformers
+        // used by the external packages' libraries that we import or export.
+        return unionAll(externalDirectives.map((uri) {
+          try {
+            return _dependencyComputer.transformersNeededByPackageUri(uri);
+          } on CycleException catch (error) {
+            var packageName = p.url.split(uri.path).first;
+            throw error.prependStep("${_package.name} depends on $packageName");
+          }
+        }));
+      }
+    } finally {
+      _activeLibraries.remove(library);
+    }
+  }
+
+  /// Returns the set of all external package libraries transitively imported or
+  /// exported by [rootLibrary].
+  ///
+  /// All of the returned URIs will have the "package:" scheme. None of them
+  /// will be URIs for this package.
+  ///
+  /// If [rootLibrary] transitively imports or exports a library that's modified
+  /// by a transformer, this will return `null`.
+  Set<Uri> _getTransitiveExternalDirectives(String rootLibrary) {
+    rootLibrary = p.normalize(rootLibrary);
+    if (_transitiveExternalDirectives.containsKey(rootLibrary)) {
+      return _transitiveExternalDirectives[rootLibrary];
+    }
+
+    var results = new Set();
+    var seen = new Set();
+
+    traverseLibrary(library) {
+      library = p.normalize(library);
+      if (seen.contains(library)) return true;
+      seen.add(library);
+
+      var directives = _getDirectives(library);
+      if (directives == null) return false;
+
+      for (var uri in directives) {
+        var path;
+        if (uri.scheme == 'package') {
+          var components = p.split(p.fromUri(uri.path));
+          if (components.first != _package.name) {
+            results.add(uri);
+            continue;
+          }
+
+          path = p.join(_package.dir, 'lib', p.joinAll(components.skip(1)));
+        } else if (uri.scheme == '' || uri.scheme == 'file') {
+          path = p.join(p.dirname(library), p.fromUri(uri));
+        } else {
+          // Ignore "dart:" URIs and theoretically-possible "http:" URIs.
+          continue;
+        }
+
+        if (!traverseLibrary(path)) return false;
+      }
+
+      return true;
+    }
+
+    _transitiveExternalDirectives[rootLibrary] =
+        traverseLibrary(rootLibrary) ? results : null;
+    return _transitiveExternalDirectives[rootLibrary];
+  }
+
+  /// Returns the set of all imports or exports in [library].
+  ///
+  /// If [library] is modified by a transformer, this will return `null`.
+  Set<Uri> _getDirectives(String library) {
+    var libraryUri = p.toUri(p.normalize(library));
+    var relative = p.toUri(p.relative(library, from: _package.dir)).path;
+    if (_applicableTransformers.any((id) => id.canTransform(relative))) {
+      _directives[libraryUri] = null;
+      return null;
+    }
+
+    // Check the cache *after* checking [_applicableTransformers] because
+    // [_applicableTransformers] changes over time so the directives may be
+    // invalidated.
+    if (_directives.containsKey(libraryUri)) return _directives[libraryUri];
+
+    // If a nonexistent library is imported, it will probably be generated by a
+    // transformer.
+    if (!fileExists(library)) {
+      _directives[libraryUri] = null;
+      return null;
+    }
+
+    _directives[libraryUri] =
+        parseImportsAndExports(readTextFile(library), name: library)
+        .map((directive) => Uri.parse(directive.uri.stringValue))
+        .toSet();
+    return _directives[libraryUri];
+  }
+}
diff --git a/lib/src/dart.dart b/lib/src/dart.dart
index 64374db6774f06e3e7b94c9bcec6d24cd3271390..9a8510568c66d70f3fd0a814d5500cdcf78187c7 100644
--- a/lib/src/dart.dart
+++ b/lib/src/dart.dart
@@ -122,6 +122,22 @@ bool isEntrypoint(CompilationUnit dart) {
   });
 }
 
+/// Efficiently parses the import and export directives in [contents].
+///
+/// If [name] is passed, it's used as the filename for error reporting.
+List<UriBasedDirective> parseImportsAndExports(String contents, {String name}) {
+  var collector = new _DirectiveCollector();
+  parseDirectives(contents, name: name).accept(collector);
+  return collector.directives;
+}
+
+/// A simple visitor that collects import and export nodes.
+class _DirectiveCollector extends GeneralizingAstVisitor {
+  final directives = <UriBasedDirective>[];
+
+  visitUriBasedDirective(UriBasedDirective node) => directives.add(node);
+}
+
 /// Runs [code] in an isolate.
 ///
 /// [code] should be the contents of a Dart entrypoint. It may contain imports;
diff --git a/lib/src/package_graph.dart b/lib/src/package_graph.dart
index 47e1349bfec2cd9679e2844ca483dc6b08e7a18f..14913787b95be9a5c9a70ad98a672c5762a03332 100644
--- a/lib/src/package_graph.dart
+++ b/lib/src/package_graph.dart
@@ -25,22 +25,4 @@ class PackageGraph {
   final Map<String, Package> packages;
 
   PackageGraph(this.entrypoint, this.lockFile, this.packages);
-
-  /// Returns the set of transitive dependencies of the package named
-  /// [packageName].
-  Set<Package> transitiveDependencies(String packageName) {
-    var seen = new Set<Package>();
-    traverse(Package package) {
-      if (seen.contains(package)) return;
-      seen.add(package);
-      for (var dep in package.dependencies) {
-        traverse(packages[dep.name]);
-      }
-    }
-
-    var package = packages[packageName];
-    traverse(package);
-    seen.remove(package);
-    return seen;
-  }
 }
diff --git a/lib/src/utils.dart b/lib/src/utils.dart
index 0ec5e46286bcedbe52368558ac49653d55e56519..8ebc8fe8ec62ebeda14d1fe25ccbcf7dda6cb9d1 100644
--- a/lib/src/utils.dart
+++ b/lib/src/utils.dart
@@ -6,7 +6,6 @@
 library pub.utils;
 
 import 'dart:async';
-import "dart:collection";
 import "dart:convert";
 import 'dart:io';
 import 'dart:isolate';
@@ -329,69 +328,9 @@ Future<Map> mapMapAsync(Map map, {key(key, value), value(key, value)}) {
       value: (mapKey) => value(mapKey, map[mapKey]));
 }
 
-/// Returns the shortest path from [start] to [end] in [graph].
-///
-/// The graph is represented by a map where each key is a vertex and the value
-/// is the set of other vertices directly reachable from the key. [start] and
-/// [end] must be vertices in this graph.
-List shortestPath(Map<dynamic, Iterable> graph, start, end) {
-  assert(graph.containsKey(start));
-  assert(graph.containsKey(end));
-
-  // Dijkstra's algorithm.
-  var infinity = graph.length;
-  var distance = mapMap(graph, value: (_1, _2) => infinity);
-
-  // A map from each node to the node that came before it on the shortest path
-  // from it back to [start].
-  var previous = {};
-
-  distance[start] = 0;
-  var remaining = graph.keys.toSet();
-  while (!remaining.isEmpty) {
-    var current = minBy(remaining, (node) => distance[node]);
-    remaining.remove(current);
-
-    // If there's no remaining node that's reachable from [start], then there's
-    // no path from [start] to [end].
-    if (distance[current] == infinity) return null;
-
-    // If we've reached [end], we've found the shortest path to it and we just
-    // need to reconstruct that path.
-    if (current == end) break;
-
-    for (var neighbor in graph[current]) {
-      if (!remaining.contains(neighbor)) continue;
-      var newDistance = distance[current] + 1;
-      if (newDistance >= distance[neighbor]) continue;
-      distance[neighbor] = newDistance;
-      previous[neighbor] = current;
-    }
-  }
-
-  var path = new Queue();
-  var current = end;
-  while (current != null) {
-    path.addFirst(current);
-    current = previous[current];
-  }
-
-  return path.toList();
-}
-
-/// Returns a copy of [graph] with all the edges reversed.
-///
-/// The graph is represented by a map where each key is a vertex and the value
-/// is the set of other vertices directly reachable from the key.
-Map<dynamic, Set> reverseGraph(Map<dynamic, Set> graph) {
-  var reversed = new Map.fromIterable(graph.keys, value: (_) => new Set());
-  graph.forEach((vertex, edges) {
-    for (var edge in edges) {
-      reversed[edge].add(vertex);
-    }
-  });
-  return reversed;
-}
+/// Returns the maximum value in [iter].
+int maxAll(Iterable<int> iter) =>
+    iter.reduce((max, element) => element > max ? element : max);
 
 /// Replace each instance of [matcher] in [source] with the return value of
 /// [fn].
diff --git a/test/test_pub.dart b/test/test_pub.dart
index 9a6a7e6c42faac9f6a57ad5112b8d2770bf8b411..b89a02729bff21948b283aaacc6120d183d7d8a6 100644
--- a/test/test_pub.dart
+++ b/test/test_pub.dart
@@ -287,6 +287,11 @@ String yaml(value) => JSON.encode(value);
 String get sandboxDir => _sandboxDir;
 String _sandboxDir;
 
+/// The path to the Dart repo's packages.
+final String pkgPath = path.absolute(path.join(
+    path.dirname(Platform.executable),
+    '..', '..', '..', '..', 'pkg'));
+
 /// The path of the package cache directory used for tests. Relative to the
 /// sandbox directory.
 final String cachePath = "cache";
@@ -673,10 +678,6 @@ void createLockFile(String package, {Iterable<String> sandbox,
   }
 
   if (pkg != null) {
-    var pkgDir = path.absolute(path.join(
-        path.dirname(Platform.executable),
-        '..', '..', '..', '..', 'pkg'));
-
     _addPackage(String package) {
       if (dependencies.containsKey(package)) return;
 
@@ -689,7 +690,7 @@ void createLockFile(String package, {Iterable<String> sandbox,
         }
         packagePath = _barbackDir;
       } else {
-        packagePath = path.join(pkgDir, package);
+        packagePath = path.join(pkgPath, package);
       }
 
       dependencies[package] = packagePath;
diff --git a/test/transformer/detects_a_transformer_cycle_test.dart b/test/transformer/detects_a_transformer_cycle_test.dart
deleted file mode 100644
index df8c0baabb2443f70f0235f3748f0b46f984c40c..0000000000000000000000000000000000000000
--- a/test/transformer/detects_a_transformer_cycle_test.dart
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS d.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_tests;
-
-import '../descriptor.dart' as d;
-import '../test_pub.dart';
-import '../serve/utils.dart';
-
-main() {
-  initConfig();
-  withBarbackVersions("any", () {
-    integration("detects a transformer cycle", () {
-      d.dir("foo", [
-        d.pubspec({
-          "name": "foo",
-          "version": "1.0.0",
-          "transformers": ["myapp/transformer"],
-          "dependencies": {'myapp': {'path': '../myapp'}}
-        }),
-        d.dir("lib", [
-          d.file("transformer.dart", dartTransformer('foo')),
-        ])
-      ]).create();
-
-      d.dir(appPath, [
-        d.pubspec({
-          "name": "myapp",
-          "transformers": ["foo/transformer"],
-          "dependencies": {'foo': {'path': '../foo'}}
-        }),
-        d.dir("lib", [
-          d.file("transformer.dart", dartTransformer('myapp')),
-        ])
-      ]).create();
-
-      createLockFile('myapp', sandbox: ['foo'], pkg: ['barback']);
-
-      var process = startPubServe();
-      process.shouldExit(1);
-      process.stderr.expect(emitsLines(
-          "Transformer cycle detected:\n"
-          "  foo is transformed by myapp/transformer\n"
-          "  myapp is transformed by foo/transformer"));
-    });
-  });
-}
diff --git a/test/transformer/detects_an_ordering_dependency_cycle_test.dart b/test/transformer/detects_an_ordering_dependency_cycle_test.dart
deleted file mode 100644
index fe973e8ddccb330f1269db61f5c2fa3339c287ef..0000000000000000000000000000000000000000
--- a/test/transformer/detects_an_ordering_dependency_cycle_test.dart
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS d.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_tests;
-
-import '../descriptor.dart' as d;
-import '../test_pub.dart';
-import '../serve/utils.dart';
-
-main() {
-  initConfig();
-  withBarbackVersions("any", () {
-    integration("detects an ordering dependency cycle", () {
-      d.dir("foo", [
-        d.pubspec({
-          "name": "foo",
-          "version": "1.0.0",
-          "transformers": ["myapp/transformer"],
-          "dependencies": {'myapp': {'path': '../myapp'}}
-        })
-      ]).create();
-
-      d.dir("bar", [
-        d.pubspec({
-          "name": "bar",
-          "version": "1.0.0",
-          "dependencies": {'foo': {'path': '../foo'}}
-        }),
-        d.dir("lib", [
-          d.file("transformer.dart", dartTransformer('bar')),
-        ])
-      ]).create();
-
-      d.dir("baz", [
-        d.pubspec({
-          "name": "baz",
-          "version": "1.0.0",
-          "transformers": ["bar/transformer"],
-          "dependencies": {'bar': {'path': '../bar'}}
-        })
-      ]).create();
-
-      d.dir(appPath, [
-        d.pubspec({
-          "name": "myapp",
-          "dependencies": {'baz': {'path': '../baz'}}
-        }),
-        d.dir("lib", [
-          d.file("transformer.dart", dartTransformer('myapp')),
-        ])
-      ]).create();
-
-      createLockFile('myapp', sandbox: ['foo', 'bar', 'baz'], pkg: ['barback']);
-
-      var process = startPubServe();
-      process.shouldExit(1);
-      process.stderr.expect(emitsLines(
-          "Transformer cycle detected:\n"
-          "  bar depends on foo\n"
-          "  foo is transformed by myapp/transformer\n"
-          "  myapp depends on baz\n"
-          "  baz is transformed by bar/transformer"));
-    });
-  });
-}
diff --git a/test/transformer/loads_ordering_dependencies_in_the_correct_order_test.dart b/test/transformer/loads_ordering_dependencies_in_the_correct_order_test.dart
deleted file mode 100644
index 7689b6af47e56b60b221c8f040e5b6db80d88af9..0000000000000000000000000000000000000000
--- a/test/transformer/loads_ordering_dependencies_in_the_correct_order_test.dart
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS d.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_tests;
-
-import '../descriptor.dart' as d;
-import '../test_pub.dart';
-import '../serve/utils.dart';
-
-main() {
-  initConfig();
-  withBarbackVersions("any", () {
-    integration("loads ordering dependencies in the correct order", () {
-      d.dir("foo", [
-        d.libPubspec("foo", '1.0.0'),
-        d.dir("lib", [
-          d.file("transformer.dart", dartTransformer('foo'))
-        ])
-      ]).create();
-
-      d.dir("bar", [
-        d.pubspec({
-          "name": "bar",
-          "version": "1.0.0",
-          "transformers": ["foo/transformer"],
-          "dependencies": {"foo": {"path": "../foo"}}
-        }),
-        d.dir("lib", [
-          d.file("bar.dart", 'const TOKEN = "bar";')
-        ])
-      ]).create();
-
-      d.dir(appPath, [
-        d.pubspec({
-          "name": "myapp",
-          "transformers": ["myapp/transformer"],
-          "dependencies": {"bar": {"path": "../bar"}}
-        }),
-        d.dir("lib", [
-          d.file("transformer.dart", dartTransformer('myapp', import: 'bar'))
-        ]),
-        d.dir("web", [
-          d.file("main.dart", 'const TOKEN = "main.dart";')
-        ])
-      ]).create();
-
-      createLockFile('myapp', sandbox: ['foo', 'bar'], pkg: ['barback']);
-
-      pubServe();
-      requestShouldSucceed("main.dart",
-          'const TOKEN = "(main.dart, myapp imports (bar, foo))";');
-      endPubServe();
-    });
-  });
-}
diff --git a/test/transformers_needed_by_transformers/conservative_dependencies_test.dart b/test/transformers_needed_by_transformers/conservative_dependencies_test.dart
new file mode 100644
index 0000000000000000000000000000000000000000..19118a6d5965e9b0ec223853b269bdcaa800d966
--- /dev/null
+++ b/test/transformers_needed_by_transformers/conservative_dependencies_test.dart
@@ -0,0 +1,466 @@
+// 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_tests;
+
+import 'package:scheduled_test/scheduled_test.dart';
+
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+import 'utils.dart';
+
+void main() {
+  initConfig();
+
+  integration("reports previous transformers as dependencies if the "
+      "transformer is transformed", () {
+    // The root app just exists so that something is transformed by pkg and qux.
+    d.dir(appPath, [
+      d.pubspec({
+        "name": "myapp",
+        "version": "1.0.0",
+        "dependencies": {
+          "pkg": {"path": "../pkg"},
+          "qux": {"path": "../qux"}
+        },
+        "transformers": ["pkg", "qux"]
+      })
+    ]).create();
+
+    d.dir("pkg", [
+      d.pubspec({
+        "name": "pkg",
+        "version": "1.0.0",
+        "dependencies": {
+          "foo": {"path": "../foo"},
+          "bar": {"path": "../bar"},
+          "baz": {"path": "../baz"},
+        },
+        "transformers": [
+          {"foo": {"\$include": "lib/pkg.dart"}},
+          {"bar": {"\$exclude": "lib/transformer.dart"}},
+          "baz"
+        ]
+      }),
+      d.dir("lib", [
+        d.file("pkg.dart", ""),
+        d.file("transformer.dart", transformer())
+      ])
+    ]).create();
+
+    // Even though foo and bar don't modify pkg/lib/transformer.dart themselves,
+    // it may be modified to import a library that they modify or generate, so
+    // pkg will depend on them.
+    d.dir("foo", [
+      d.libPubspec("foo", "1.0.0"),
+      d.dir("lib", [d.file("foo.dart", transformer())])
+    ]).create();
+
+    d.dir("bar", [
+      d.libPubspec("bar", "1.0.0"),
+      d.dir("lib", [d.file("bar.dart", transformer())])
+    ]).create();
+
+    // baz transforms pkg/lib/transformer.dart, so pkg will obviously
+    // depend on it.
+    d.dir("baz", [
+      d.libPubspec("baz", "1.0.0"),
+      d.dir("lib", [d.file("baz.dart", transformer())])
+    ]).create();
+
+    // qux doesn't transform anything in pkg, so pkg won't depend on it.
+    d.dir("qux", [
+      d.libPubspec("qux", "1.0.0"),
+      d.dir("lib", [d.file("qux.dart", transformer())])
+    ]).create();
+
+    expectDependencies({
+      'pkg': ['foo', 'bar', 'baz'], 'foo': [], 'bar': [], 'baz': [], 'qux': []
+    });
+  });
+
+  integration("reports all transitive package dependencies' transformers as "
+      "dependencies if the transformer is transformed", () {
+    // The root app just exists so that something is transformed by pkg and qux.
+    d.dir(appPath, [
+      d.pubspec({
+        "name": "myapp",
+        "dependencies": {
+          "pkg": {"path": "../pkg"},
+          "qux": {"path": "../qux"}
+        },
+        "transformers": ["pkg"]
+      })
+    ]).create();
+
+    d.dir("pkg", [
+      d.pubspec({
+        "name": "pkg",
+        "version": "1.0.0",
+        "dependencies": {
+          "foo": {"path": "../foo"},
+          "baz": {"path": "../baz"}
+        },
+        "transformers": ["baz"]
+      }),
+      d.dir("lib", [d.file("pkg.dart", transformer())])
+    ]).create();
+
+    // pkg depends on foo. Even though it's not transformed by foo, its
+    // transformed transformer could import foo, so it has to depend on foo.
+    d.dir("foo", [
+      d.pubspec({
+        "name": "foo",
+        "version": "1.0.0",
+        "dependencies": {"bar": {"path": "../bar"}},
+        "transformers": ["foo"]
+      }),
+      d.dir("lib", [d.file("foo.dart", transformer())])
+    ]).create();
+
+    // foo depends on bar, and like pkg's dependency on foo, the transformed
+    // version of foo's transformer could import bar, so foo has to depend on
+    // bar.
+    d.dir("bar", [
+      d.pubspec({
+        "name": "bar",
+        "version": "1.0.0",
+        "transformers": ["bar"]
+      }),
+      d.dir("lib", [d.file("bar.dart", transformer())])
+    ]).create();
+
+    /// foo is transformed by baz.
+    d.dir("baz", [
+      d.libPubspec("baz", "1.0.0"),
+      d.dir("lib", [d.file("baz.dart", transformer())])
+    ]).create();
+
+    /// qux is not part of pkg's transitive dependency tree, so pkg shouldn't
+    /// depend on it.
+    d.dir("qux", [
+      d.pubspec({
+        "name": "qux",
+        "version": "1.0.0",
+        "transformers": ["qux"]
+      }),
+      d.dir("lib", [d.file("qux.dart", transformer())])
+    ]).create();
+
+    expectDependencies({
+      'pkg': ['foo', 'bar', 'baz'], 'foo': [], 'bar': [], 'baz': [], 'qux': []
+    });
+  });
+
+  integration("reports previous transformers as dependencies if a "
+      "nonexistent local file is imported", () {
+    // The root app just exists so that something is transformed by pkg and bar.
+    d.dir(appPath, [
+      d.pubspec({
+        "name": "myapp",
+        "dependencies": {
+          "pkg": {"path": "../pkg"},
+          "bar": {"path": "../bar"}
+        },
+        "transformers": ["pkg", "bar"]
+      })
+    ]).create();
+
+    d.dir("pkg", [
+      d.pubspec({
+        "name": "pkg",
+        "version": "1.0.0",
+        "dependencies": {
+          "foo": {"path": "../foo"},
+          "bar": {"path": "../bar"}
+        },
+        "transformers": [{"foo": {"\$include": "lib/pkg.dart"}}]
+      }),
+      d.dir("lib", [
+        d.file("pkg.dart", ""),
+        d.file("transformer.dart", transformer(["nonexistent.dart"]))
+      ])
+    ]).create();
+
+    // Since pkg's transformer imports a nonexistent file, we assume that file
+    // was generated by foo's transformer. Thus pkg's transformer depends on
+    // foo's even though the latter doesn't transform the former.
+    d.dir("foo", [
+      d.libPubspec("foo", "1.0.0"),
+      d.dir("lib", [d.file("foo.dart", transformer())])
+    ]).create();
+
+    /// qux is not part of pkg's transitive dependency tree, so pkg shouldn't
+    /// depend on it.
+    d.dir("bar", [
+      d.libPubspec("bar", "1.0.0"),
+      d.dir("lib", [d.file("bar.dart", transformer())])
+    ]).create();
+
+    expectDependencies({'pkg': ['foo'], 'foo': [], 'bar': []});
+  });
+
+  integration("reports all that package's dependencies' transformers as "
+      "dependencies if a non-existent file is imported from another package",
+      () {
+    d.dir(appPath, [
+      d.pubspec({
+        "name": "myapp",
+        "dependencies": {
+          "foo": {"path": "../foo"},
+          "qux": {"path": "../qux"}
+        },
+        "transformers": ["myapp"]
+      }),
+      d.dir("lib", [
+        d.file("myapp.dart", transformer(["package:foo/nonexistent.dart"]))
+      ])
+    ]).create();
+
+    // myapp imported a nonexistent file from foo so myapp will depend on every
+    // transformer transitively reachable from foo, since the nonexistent file
+    // could be generated to import anything.
+    d.dir("foo", [
+      d.pubspec({
+        "name": "foo",
+        "version": "1.0.0",
+        "dependencies": {
+          "bar": {"path": "../bar"},
+          "baz": {"path": "../baz"}
+        },
+        "transformers": ["foo"]
+      }),
+      d.dir("lib", [d.file("foo.dart", transformer())])
+    ]).create();
+
+    // bar is a dependency of foo so myapp will depend on it.
+    d.dir("bar", [
+      d.pubspec({
+        "name": "bar",
+        "version": "1.0.0",
+        "transformers": ["bar"]
+      }),
+      d.dir("lib", [d.file("bar.dart", transformer())])
+    ]).create();
+
+    // baz is a dependency of foo so myapp will depend on it.
+    d.dir("baz", [
+      d.pubspec({
+        "name": "baz",
+        "version": "1.0.0",
+        "transformers": ["baz"]
+      }),
+      d.dir("lib", [d.file("baz.dart", transformer())])
+    ]).create();
+
+    // qux is not transitively reachable from foo so myapp won't depend on it.
+    d.dir("qux", [
+      d.pubspec({
+        "name": "qux",
+        "version": "1.0.0",
+        "transformers": ["qux"]
+      }),
+      d.dir("lib", [d.file("qux.dart", transformer())])
+    ]).create();
+
+    expectDependencies({
+      'myapp': ['foo', 'bar', 'baz'], 'foo': [], 'bar': [], 'baz': [], 'qux': []
+    });
+  });
+
+  integration("reports all that package's dependencies' transformers as "
+      "dependencies if a non-existent transformer is used from another package",
+      () {
+    d.dir(appPath, [
+      d.pubspec({
+        "name": "myapp",
+        "dependencies": {
+          "foo": {"path": "../foo"},
+          "qux": {"path": "../qux"}
+        },
+        "transformers": ["myapp"]
+      }),
+      d.dir("lib", [
+        d.file("myapp.dart", transformer(["package:foo/nonexistent.dart"]))
+      ])
+    ]).create();
+
+    // myapp imported a nonexistent file from foo so myapp will depend on every
+    // transformer transitively reachable from foo, since the nonexistent file
+    // could be generated to import anything.
+    d.dir("foo", [
+      d.pubspec({
+        "name": "foo",
+        "version": "1.0.0",
+        "dependencies": {
+          "bar": {"path": "../bar"},
+          "baz": {"path": "../baz"}
+        },
+        "transformers": ["bar"]
+      })
+    ]).create();
+
+    // bar is a dependency of foo so myapp will depend on it.
+    d.dir("bar", [
+      d.libPubspec("bar", "1.0.0"),
+      d.dir("lib", [d.file("bar.dart", transformer())])
+    ]).create();
+
+    // baz is a dependency of foo so myapp will depend on it.
+    d.dir("baz", [
+      d.pubspec({
+        "name": "baz",
+        "version": "1.0.0",
+        "transformers": ["baz"]
+      }),
+      d.dir("lib", [d.file("baz.dart", transformer())])
+    ]).create();
+
+    // qux is not transitively reachable from foo so myapp won't depend on it.
+    d.dir("qux", [
+      d.pubspec({
+        "name": "qux",
+        "version": "1.0.0",
+        "transformers": ["qux"]
+      }),
+      d.dir("lib", [d.file("qux.dart", transformer())])
+    ]).create();
+
+    expectDependencies({
+      'myapp': ['bar', 'baz'], 'bar': [], 'baz': [], 'qux': []
+    });
+  });
+
+  test("reports dependencies on transformers in past phases", () {
+    d.dir(appPath, [
+      d.pubspec({
+        "name": "myapp",
+        "transformers": [
+          "myapp/first",
+          "myapp/second",
+          "myapp/third"
+        ]
+      }),
+      d.dir("lib", [
+        d.file("first.dart", transformer()),
+        d.file("second.dart", transformer()),
+        d.file("third.dart", transformer())
+      ])
+    ]).create();
+
+    expectDependencies({
+      'myapp/first': [],
+      'myapp/second': ['myapp/first'],
+      'myapp/third': ['myapp/second', 'myapp/first']
+    });
+  });
+
+  integration("considers the entrypoint package's dev and override "
+      "dependencies", () {
+    d.dir(appPath, [
+      d.pubspec({
+        "name": "myapp",
+        "dependencies": {"foo": {"path": "../foo"}},
+        "dev_dependencies": {"bar": {"path": "../bar"}},
+        "dependency_overrides": {"baz": {"path": "../baz"}},
+        "transformers": ["foo", "myapp"]
+      }),
+      d.dir("lib", [d.file("myapp.dart", transformer())])
+    ]).create();
+
+    // foo transforms myapp's transformer so it could import from bar or baz.
+    d.dir("foo", [
+      d.pubspec({
+        "name": "foo",
+        "version": "1.0.0",
+        "transformers": ["foo"]
+      }),
+      d.dir("lib", [d.file("foo.dart", transformer())])
+    ]).create();
+
+    // bar is a dev dependency that myapp could import from, so myapp should
+    // depend on it.
+    d.dir("bar", [
+      d.pubspec({
+        "name": "bar",
+        "version": "1.0.0",
+        "transformers": ["bar"]
+      }),
+      d.dir("lib", [d.file("bar.dart", transformer())])
+    ]).create();
+
+    // baz is an override dependency that myapp could import from, so myapp
+    // should depend on it.
+    d.dir("baz", [
+      d.pubspec({
+        "name": "baz",
+        "version": "1.0.0",
+        "transformers": ["baz"]
+      }),
+      d.dir("lib", [d.file("baz.dart", transformer())])
+    ]).create();
+
+    expectDependencies({
+      'myapp': ['foo', 'bar', 'baz'], 'foo': [], 'bar': [], 'baz': []
+    });
+  });
+
+  integration("doesn't consider a non-entrypoint package's dev and override "
+      "dependencies", () {
+    // myapp just exists so that pkg isn't the entrypoint.
+    d.dir(appPath, [
+      d.pubspec({
+        "name": "myapp",
+        "dependencies": {"pkg": {"path": "../pkg"}}
+      })
+    ]).create();
+
+    d.dir("pkg", [
+      d.pubspec({
+        "name": "pkg",
+        "dependencies": {"foo": {"path": "../foo"}},
+        "dev_dependencies": {"bar": {"path": "../bar"}},
+        "dependency_overrides": {"baz": {"path": "../baz"}},
+        "transformers": ["foo", "pkg"]
+      }),
+      d.dir("lib", [d.file("pkg.dart", transformer())])
+    ]).create();
+
+    // foo transforms pkg's transformer so it could theoretcially import from
+    // bar or baz. However, since pkg isn't the entrypoint, it doesn't have
+    // access to them.
+    d.dir("foo", [
+      d.pubspec({
+        "name": "foo",
+        "version": "1.0.0",
+        "transformers": ["foo"]
+      }),
+      d.dir("lib", [d.file("foo.dart", transformer())])
+    ]).create();
+
+    // bar is a dev dependency that myapp can't import from, so myapp shouldn't
+    // depend on it.
+    d.dir("bar", [
+      d.pubspec({
+        "name": "bar",
+        "version": "1.0.0",
+        "transformers": ["bar"]
+      }),
+      d.dir("lib", [d.file("bar.dart", transformer())])
+    ]).create();
+
+    // baz is a dev dependency that myapp can't import from, so myapp shouldn't
+    // depend on it.
+    d.dir("baz", [
+      d.pubspec({
+        "name": "baz",
+        "version": "1.0.0",
+        "transformers": ["baz"]
+      }),
+      d.dir("lib", [d.file("baz.dart", transformer())])
+    ]).create();
+
+    expectDependencies({'pkg': ['foo'], 'foo': [], 'bar': [], 'baz': []});
+  });
+}
\ No newline at end of file
diff --git a/test/transformers_needed_by_transformers/cycle_test.dart b/test/transformers_needed_by_transformers/cycle_test.dart
new file mode 100644
index 0000000000000000000000000000000000000000..a8458c1499b8380f657a3965f7bfcfc402dc3a39
--- /dev/null
+++ b/test/transformers_needed_by_transformers/cycle_test.dart
@@ -0,0 +1,212 @@
+// 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_tests;
+
+import 'package:scheduled_test/scheduled_test.dart';
+
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+import 'utils.dart';
+
+void main() {
+  initConfig();
+
+  integration("allows a package dependency cycle that's unrelated to "
+      "transformers", () {
+    d.dir(appPath, [
+      d.pubspec({
+        "name": "myapp",
+        "dependencies": {"foo": {"path": "../foo"}},
+        "transformers": ["myapp/first", "myapp/second"]
+      }),
+      d.dir('lib', [
+        d.file("first.dart", transformer()),
+        d.file("second.dart", transformer())
+      ])
+    ]).create();
+
+    d.dir("foo", [
+      d.libPubspec("foo", "1.0.0", deps: {"bar": {"path": "../bar"}})
+    ]).create();
+
+    d.dir("bar", [
+      d.libPubspec("bar", "1.0.0", deps: {"baz": {"path": "../baz"}})
+    ]).create();
+
+    d.dir("baz", [
+      d.libPubspec("baz", "1.0.0", deps: {"foo": {"path": "../foo"}})
+    ]).create();
+
+    expectDependencies({'myapp/first': [], 'myapp/second': ['myapp/first']});
+  });
+
+  integration("disallows a package dependency cycle that may be related to "
+      "transformers", () {
+    // Two layers of myapp transformers are necessary here because otherwise pub
+    // will figure out that the transformer doesn't import "foo" and thus
+    // doesn't transitively import itself. Import loops are tested below.
+    d.dir(appPath, [
+      d.pubspec({
+        "name": "myapp",
+        "dependencies": {"foo": {"path": "../foo"}},
+        "transformers": ["myapp/first", "myapp/second"]
+      }),
+      d.dir('lib', [
+        d.file("first.dart", transformer()),
+        d.file("second.dart", transformer())
+      ])
+    ]).create();
+
+    d.dir("foo", [
+      d.libPubspec("foo", "1.0.0", deps: {"bar": {"path": "../bar"}})
+    ]).create();
+
+    d.dir("bar", [
+      d.libPubspec("bar", "1.0.0", deps: {"myapp": {"path": "../myapp"}})
+    ]).create();
+
+    expectCycleException([
+      "myapp is transformed by myapp/second",
+      "myapp depends on foo",
+      "foo depends on bar",
+      "bar depends on myapp",
+      "myapp is transformed by myapp/first"
+    ]);
+  });
+
+  integration("disallows a transformation dependency cycle", () {
+    d.dir(appPath, [
+      d.pubspec({
+        "name": "myapp",
+        "dependencies": {"foo": {"path": "../foo"}},
+        "transformers": ["foo"]
+      }),
+      d.dir('lib', [d.file("myapp.dart", transformer())])
+    ]).create();
+
+    d.dir("foo", [
+      d.pubspec({
+        "name": "foo",
+        "dependencies": {"bar": {"path": "../bar"}},
+        "transformers": ["bar"]
+      }),
+      d.dir('lib', [d.file("foo.dart", transformer())])
+    ]).create();
+
+    d.dir("bar", [
+      d.pubspec({
+        "name": "bar",
+        "dependencies": {"myapp": {"path": "../myapp"}},
+        "transformers": ["myapp"]
+      }),
+      d.dir('lib', [d.file("bar.dart", transformer())])
+    ]).create();
+
+    expectCycleException([
+      "bar is transformed by myapp",
+      "myapp is transformed by foo",
+      "foo is transformed by bar"
+    ]);
+  });
+
+  integration("allows a cross-package import cycle that's unrelated to "
+      "transformers", () {
+     d.dir(appPath, [
+      d.pubspec({
+        "name": "myapp",
+        "dependencies": {"foo": {"path": "../foo"}},
+        "transformers": ["myapp"]
+      }),
+      d.dir('lib', [
+        d.file("myapp.dart", transformer(['package:foo/foo.dart']))
+      ])
+    ]).create();
+
+    d.dir("foo", [
+      d.libPubspec("foo", "1.0.0", deps: {"bar": {"path": "../bar"}}),
+      d.dir('lib', [d.file("foo.dart", "import 'package:bar/bar.dart';")])
+    ]).create();
+
+    d.dir("bar", [
+      d.libPubspec("bar", "1.0.0", deps: {"baz": {"path": "../baz"}}),
+      d.dir('lib', [d.file("bar.dart", "import 'package:baz/baz.dart';")])
+    ]).create();
+
+    d.dir("baz", [
+      d.libPubspec("baz", "1.0.0", deps: {"foo": {"path": "../foo"}}),
+      d.dir('lib', [d.file("baz.dart", "import 'package:foo/foo.dart';")])
+    ]).create();
+
+    expectDependencies({'myapp': []});
+  });
+
+  integration("disallows a cross-package import cycle that's related to "
+      "transformers", () {
+     d.dir(appPath, [
+      d.pubspec({
+        "name": "myapp",
+        "dependencies": {"foo": {"path": "../foo"}},
+        "transformers": ["myapp"]
+      }),
+      d.dir('lib', [
+        d.file("myapp.dart", transformer(['package:foo/foo.dart']))
+      ])
+    ]).create();
+
+    d.dir("foo", [
+      d.libPubspec("foo", "1.0.0", deps: {"bar": {"path": "../bar"}}),
+      d.dir('lib', [d.file("foo.dart", "import 'package:bar/bar.dart';")])
+    ]).create();
+
+    d.dir("bar", [
+      d.libPubspec("bar", "1.0.0", deps: {"myapp": {"path": "../myapp"}}),
+      d.dir('lib', [d.file("bar.dart", "import 'package:myapp/myapp.dart';")])
+    ]).create();
+
+    expectCycleException([
+      "myapp is transformed by myapp",
+      "myapp depends on foo",
+      "foo depends on bar",
+      "bar depends on myapp"
+    ]);
+  });
+
+  integration("allows a single-package import cycle that's unrelated to "
+      "transformers", () {
+     d.dir(appPath, [
+      d.pubspec({
+        "name": "myapp",
+        "dependencies": {"foo": {"path": "../foo"}},
+        "transformers": ["myapp"]
+      }),
+      d.dir('lib', [
+        d.file("myapp.dart", transformer(['foo.dart'])),
+        d.file("foo.dart", "import 'bar.dart';"),
+        d.file("bar.dart", "import 'baz.dart';"),
+        d.file("baz.dart", "import 'foo.dart';")
+      ])
+    ]).create();
+
+    expectDependencies({'myapp': []});
+  });
+
+  integration("allows a single-package import cycle that's related to "
+      "transformers", () {
+     d.dir(appPath, [
+      d.pubspec({
+        "name": "myapp",
+        "dependencies": {"foo": {"path": "../foo"}},
+        "transformers": ["myapp"]
+      }),
+      d.dir('lib', [
+        d.file("myapp.dart", transformer(['foo.dart'])),
+        d.file("foo.dart", "import 'bar.dart';"),
+        d.file("bar.dart", "import 'myapp.dart';"),
+      ])
+    ]).create();
+
+    expectDependencies({'myapp': []});
+  });
+}
\ No newline at end of file
diff --git a/test/transformers_needed_by_transformers/error_test.dart b/test/transformers_needed_by_transformers/error_test.dart
new file mode 100644
index 0000000000000000000000000000000000000000..0908cb4f917df710e3c807a852933798f771bae6
--- /dev/null
+++ b/test/transformers_needed_by_transformers/error_test.dart
@@ -0,0 +1,49 @@
+// 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_tests;
+
+import 'package:scheduled_test/scheduled_test.dart';
+
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+import 'utils.dart';
+
+void main() {
+  initConfig();
+
+  integration("fails if an unknown package is imported", () {
+    d.dir(appPath, [
+      d.pubspec({
+        "name": "myapp",
+        "transformers": ["myapp"]
+      }),
+      d.dir('lib', [
+        d.file("myapp.dart", transformer(["package:foo/foo.dart"]))
+      ])
+    ]).create();
+
+    expectException(predicate((error) {
+      expect(error, new isInstanceOf<ApplicationException>());
+      expect(error.message, equals(
+          'A transformer imported unknown package "foo" (in '
+          '"package:foo/foo.dart").'));
+      return true;
+    }));
+  });
+
+  integration("fails on a syntax error", () {
+    d.dir(appPath, [
+      d.pubspec({
+        "name": "myapp",
+        "transformers": ["myapp"]
+      }),
+      d.dir('lib', [
+        d.file("myapp.dart", "library;")
+      ])
+    ]).create();
+
+    expectException(new isInstanceOf<AnalyzerErrorGroup>());
+  });
+}
diff --git a/test/transformers_needed_by_transformers/import_dependencies_test.dart b/test/transformers_needed_by_transformers/import_dependencies_test.dart
new file mode 100644
index 0000000000000000000000000000000000000000..342425e6a3d27da8ba46e489bb5f12cd0d1cafd0
--- /dev/null
+++ b/test/transformers_needed_by_transformers/import_dependencies_test.dart
@@ -0,0 +1,195 @@
+// 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_tests;
+
+import 'package:scheduled_test/scheduled_test.dart';
+
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+import 'utils.dart';
+
+void main() {
+  initConfig();
+
+  integration("reports a dependency if a transformed local file is imported",
+      () {
+    d.dir(appPath, [
+      d.pubspec({
+        "name": "myapp",
+        "dependencies": {"foo": {"path": "../foo"}},
+        "transformers": [
+          {"foo": {"\$include": "lib/lib.dart"}},
+          "myapp"
+        ]
+      }),
+      d.dir("lib", [
+        d.file("myapp.dart", ""),
+        d.file("lib.dart", ""),
+        d.file("transformer.dart", transformer(["lib.dart"]))
+      ])
+    ]).create();
+
+    d.dir("foo", [
+      d.pubspec({"name": "foo", "version": "1.0.0"}),
+      d.dir("lib", [d.file("foo.dart", transformer())])
+    ]).create();
+
+    expectDependencies({'myapp': ['foo'], 'foo': []});
+  });
+
+  integration("reports a dependency if a transformed foreign file is imported",
+      () {
+    d.dir(appPath, [
+      d.pubspec({
+        "name": "myapp",
+        "dependencies": {"foo": {"path": "../foo"}},
+        "transformers": ["myapp"]
+      }),
+      d.dir("lib", [
+        d.file("myapp.dart", ""),
+        d.file("transformer.dart", transformer(["package:foo/foo.dart"]))
+      ])
+    ]).create();
+
+    d.dir("foo", [
+      d.pubspec({
+        "name": "foo",
+        "version": "1.0.0",
+        "transformers": [{"foo": {"\$include": "lib/foo.dart"}}]
+      }),
+      d.dir("lib", [
+        d.file("foo.dart", ""),
+        d.file("transformer.dart", transformer())
+      ])
+    ]).create();
+
+    expectDependencies({'myapp': ['foo'], 'foo': []});
+  });
+
+  integration("reports a dependency if a transformed external package file is "
+      "imported from an export", () {
+    d.dir(appPath, [
+      d.pubspec({
+        "name": "myapp",
+        "dependencies": {"foo": {"path": "../foo"}},
+        "transformers": ["myapp"]
+      }),
+      d.dir("lib", [
+        d.file("myapp.dart", ""),
+        d.file("transformer.dart", transformer(["local.dart"])),
+        d.file("local.dart", "export 'package:foo/foo.dart';")
+      ])
+    ]).create();
+
+    d.dir("foo", [
+      d.pubspec({
+        "name": "foo",
+        "version": "1.0.0",
+        "transformers": [{"foo": {"\$include": "lib/foo.dart"}}]
+      }),
+      d.dir("lib", [
+        d.file("foo.dart", ""),
+        d.file("transformer.dart", transformer())
+      ])
+    ]).create();
+
+    expectDependencies({'myapp': ['foo'], 'foo': []});
+  });
+
+  integration("reports a dependency if a transformed foreign file is "
+      "transitively imported", () {
+    d.dir(appPath, [
+      d.pubspec({
+        "name": "myapp",
+        "dependencies": {"foo": {"path": "../foo"}},
+        "transformers": ["myapp"]
+      }),
+      d.dir("lib", [
+        d.file("myapp.dart", ""),
+        d.file("transformer.dart", transformer(["local.dart"])),
+        d.file("local.dart", "import 'package:foo/foreign.dart';")
+      ])
+    ]).create();
+
+    d.dir("foo", [
+      d.pubspec({
+        "name": "foo",
+        "version": "1.0.0",
+        "transformers": [{"foo": {"\$include": "lib/foo.dart"}}]
+      }),
+      d.dir("lib", [
+        d.file("foo.dart", ""),
+        d.file("transformer.dart", transformer()),
+        d.file("foreign.dart", "import 'foo.dart';")
+      ])
+    ]).create();
+
+    expectDependencies({'myapp': ['foo'], 'foo': []});
+  });
+
+  integration("reports a dependency if a transformed foreign file is "
+      "transitively imported across packages", () {
+    d.dir(appPath, [
+      d.pubspec({
+        "name": "myapp",
+        "dependencies": {"foo": {"path": "../foo"}},
+        "transformers": ["myapp"]
+      }),
+      d.dir("lib", [
+        d.file("myapp.dart", ""),
+        d.file("transformer.dart", transformer(["package:foo/foo.dart"])),
+      ])
+    ]).create();
+
+    d.dir("foo", [
+      d.pubspec({
+        "name": "foo",
+        "version": "1.0.0",
+        "dependencies": {"bar": {"path": "../bar"}}
+      }),
+      d.dir("lib", [d.file("foo.dart", "import 'package:bar/bar.dart';")])
+    ]).create();
+
+    d.dir("bar", [
+      d.pubspec({
+        "name": "bar",
+        "version": "1.0.0",
+        "transformers": [{"bar": {"\$include": "lib/bar.dart"}}]
+      }),
+      d.dir("lib", [
+        d.file("bar.dart", ""),
+        d.file("transformer.dart", transformer())
+      ])
+    ]).create();
+
+    expectDependencies({'myapp': ['bar'], 'bar': []});
+  });
+
+  integration("reports a dependency if an imported file is transformed by a "
+      "different package", () {
+    d.dir(appPath, [
+      d.pubspec({
+        "name": "myapp",
+        "dependencies": {"foo": {"path": "../foo"}},
+        "transformers": [
+          {"foo": {'\$include': 'lib/local.dart'}},
+          "myapp"
+        ]
+      }),
+      d.dir("lib", [
+        d.file("myapp.dart", ""),
+        d.file("transformer.dart", transformer(["local.dart"])),
+        d.file("local.dart", "")
+      ])
+    ]).create();
+
+    d.dir("foo", [
+      d.pubspec({"name": "foo", "version": "1.0.0"}),
+      d.dir("lib", [d.file("transformer.dart", transformer())])
+    ]).create();
+
+    expectDependencies({'myapp': ['foo'], 'foo': []});
+  });
+}
\ No newline at end of file
diff --git a/test/transformers_needed_by_transformers/no_dependencies_test.dart b/test/transformers_needed_by_transformers/no_dependencies_test.dart
new file mode 100644
index 0000000000000000000000000000000000000000..c28e4a0ae694329fd29a26a292f5303ee30cbb85
--- /dev/null
+++ b/test/transformers_needed_by_transformers/no_dependencies_test.dart
@@ -0,0 +1,161 @@
+// 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_tests;
+
+import 'package:scheduled_test/scheduled_test.dart';
+
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+import 'utils.dart';
+
+void main() {
+  initConfig();
+
+  integration("reports no dependencies if no transformers are used", () {
+    d.dir(appPath, [
+      d.pubspec({
+        "name": "myapp",
+        "dependencies": {"foo": {"path": "../foo"}}
+      })
+    ]).create();
+
+    d.dir("foo", [d.libPubspec("foo", "1.0.0")]).create();
+
+    expectDependencies({});
+  });
+
+  integration("reports no dependencies if a transformer is used in a "
+      "package that doesn't expose a transformer", () {
+    d.dir(appPath, [
+      d.pubspec({
+        "name": "myapp",
+        "dependencies": {"foo": {"path": "../foo"}},
+        "transformers": ["foo"]
+      })
+    ]).create();
+
+    d.dir("foo", [
+      d.libPubspec("foo", "1.0.0"),
+      d.dir("lib", [d.file("foo.dart", transformer())])
+    ]).create();
+
+    expectDependencies({"foo": []});
+  });
+
+  integration("reports no dependencies for non-file/package imports", () {
+    d.dir(appPath, [
+      d.pubspec({
+        "name": "myapp",
+        "dependencies": {"foo": {"path": "../foo"}},
+        "transformers": ["myapp"]
+      }),
+      d.dir("lib", [
+        d.file("myapp.dart", transformer([
+          "dart:async",
+          "http://dartlang.org/nonexistent.dart"
+        ]))
+      ])
+    ]).create();
+
+    d.dir("foo", [
+      d.libPubspec("foo", "1.0.0"),
+      d.dir("lib", [d.file("foo.dart", transformer())])
+    ]).create();
+
+    expectDependencies({"myapp": []});
+  });
+
+  integration("reports no dependencies for a single self transformer", () {
+    d.dir(appPath, [
+      d.pubspec({
+        "name": "myapp",
+        "transformers": ["myapp"]
+      }),
+      d.dir("lib", [d.file("myapp.dart", transformer())])
+    ]).create();
+
+    expectDependencies({"myapp": []});
+  });
+
+  integration("reports no dependencies if a transformer applies to files that "
+      "aren't used by the exposed transformer", () {
+    d.dir(appPath, [
+      d.pubspec({
+        "name": "myapp",
+        "dependencies": {"foo": {"path": "../foo"}},
+        "transformers": [
+          {"foo": {"\$include": "lib/myapp.dart"}},
+          {"foo": {"\$exclude": "lib/transformer.dart"}},
+          "myapp"
+        ]
+      }),
+      d.dir("lib", [
+        d.file("myapp.dart", ""),
+        d.file("transformer.dart", transformer())
+      ])
+    ]).create();
+
+    d.dir("foo", [
+      d.libPubspec("foo", "1.0.0"),
+      d.dir("lib", [d.file("foo.dart", transformer())])
+    ]).create();
+
+    expectDependencies({"myapp": [], "foo": []});
+  });
+
+  integration("reports no dependencies if a transformer applies to a "
+      "dependency's files that aren't used by the exposed transformer", () {
+    d.dir(appPath, [
+      d.pubspec({
+        "name": "myapp",
+        "dependencies": {"foo": {"path": "../foo"}},
+        "transformers": ["myapp"]
+      }),
+      d.dir("lib", [
+        d.file("myapp.dart", ""),
+        d.file("transformer.dart", transformer(["package:foo/foo.dart"]))
+      ])
+    ]).create();
+
+    d.dir("foo", [
+      d.pubspec({
+        "name": "foo",
+        "version": "1.0.0",
+        "transformers": [{"foo": {"\$exclude": "lib/foo.dart"}}]
+      }),
+      d.dir("lib", [
+        d.file("foo.dart", ""),
+        d.file("transformer.dart", transformer())
+      ])
+    ]).create();
+
+    expectDependencies({'myapp': [], 'foo': []});
+  });
+
+  test("reports no dependencies on transformers in future phases", () {
+    d.dir(appPath, [
+      d.pubspec({
+        "name": "myapp",
+        "transformers": [
+          {"myapp/first": {"\$include": "lib/myapp.dart"}},
+          {"myapp/second": {"\$include": "lib/first.dart"}},
+          {"myapp/third": {"\$include": "lib/second.dart"}}
+        ]
+      }),
+      d.dir("lib", [
+        d.file("myapp.dart", ""),
+        d.file("first.dart", transformer()),
+        d.file("second.dart", transformer()),
+        d.file("third.dart", transformer())
+      ])
+    ]).create();
+
+    expectDependencies({
+      'myapp/first': [],
+      'myapp/second': [],
+      'myapp/third': []
+    });
+  });
+}
diff --git a/test/transformers_needed_by_transformers/utils.dart b/test/transformers_needed_by_transformers/utils.dart
new file mode 100644
index 0000000000000000000000000000000000000000..47e1cf7894ed4af132ea576318a8b48a0b7f7603
--- /dev/null
+++ b/test/transformers_needed_by_transformers/utils.dart
@@ -0,0 +1,112 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS d.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_tests;
+
+import 'package:path/path.dart' as p;
+import 'package:scheduled_test/scheduled_test.dart';
+
+import '../../lib/src/barback/cycle_exception.dart';
+import '../../lib/src/barback/transformers_needed_by_transformers.dart';
+import '../../lib/src/entrypoint.dart';
+import '../../lib/src/io.dart';
+import '../../lib/src/package.dart';
+import '../../lib/src/package_graph.dart';
+import '../../lib/src/source/path.dart';
+import '../../lib/src/system_cache.dart';
+import '../../lib/src/utils.dart';
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+
+/// Expects that [computeTransformersNeededByTransformers] will return a graph
+/// matching [expected] when run on the package graph defined by packages in
+/// the sandbox.
+void expectDependencies(Map<String, Iterable<String>> expected) {
+  expected = mapMap(expected, value: (_, ids) => ids.toSet());
+
+  schedule(() {
+    var result = mapMap(
+        computeTransformersNeededByTransformers(_loadPackageGraph()),
+        key: (id, _) => id.toString(),
+        value: (_, ids) => ids.map((id) => id.toString()).toSet());
+    expect(result, equals(expected));
+  }, "expect dependencies to match $expected");
+}
+
+/// Expects that [computeTransformersNeededByTransformers] will throw an
+/// exception matching [matcher] when run on the package graph defiend by
+/// packages in the sandbox.
+void expectException(matcher) {
+  schedule(() {
+    expect(() => computeTransformersNeededByTransformers(_loadPackageGraph()),
+        throwsA(matcher));
+  }, "expect an exception: $matcher");
+}
+
+/// Expects that [computeTransformersNeededByTransformers] will throw a
+/// [CycleException] with the given [steps] when run on the package graph
+/// defiend by packages in the sandbox.
+void expectCycleException(Iterable<String> steps) {
+  expectException(predicate((error) {
+    expect(error, new isInstanceOf<CycleException>());
+    expect(error.steps, equals(steps));
+    return true;
+  }, "cycle exception:\n${steps.map((step) => "  $step").join("\n")}"));
+}
+
+/// Loads a [PackageGraph] from the packages in the sandbox.
+///
+/// This graph will also include barback and its transitive dependencies from
+/// the repo.
+PackageGraph _loadPackageGraph() {
+  // Load the sandbox packages.
+  var packages = {};
+
+  var systemCache = new SystemCache(p.join(sandboxDir, cachePath));
+  systemCache.sources
+      ..register(new PathSource())
+      ..setDefault('path');
+  var entrypoint = new Entrypoint(p.join(sandboxDir, appPath), systemCache);
+
+  for (var package in listDir(sandboxDir)) {
+    if (!fileExists(p.join(package, 'pubspec.yaml'))) continue;
+    var packageName = p.basename(package);
+    packages[packageName] = new Package.load(
+        packageName, package, systemCache.sources);
+  }
+
+  loadPackage(packageName) {
+    if (packages.containsKey(packageName)) return;
+    packages[packageName] = new Package.load(
+        packageName, p.join(pkgPath, packageName), systemCache.sources);
+    for (var dep in packages[packageName].dependencies) {
+      loadPackage(dep.name);
+    }
+  }
+
+  loadPackage('barback');
+
+  return new PackageGraph(entrypoint, null, packages);
+}
+
+/// Returns the contents of a no-op transformer that imports each URL in
+/// [imports].
+String transformer([Iterable<String> imports]) {
+  if (imports == null) imports = [];
+
+  var buffer = new StringBuffer()
+      ..writeln('import "package:barback/barback.dart";');
+  for (var import in imports) {
+    buffer.writeln('import "$import";');
+  }
+
+  buffer.writeln("""
+NoOpTransformer extends Transformer {
+  bool isPrimary(AssetId id) => true;
+  void apply(Transform transform) {}
+}
+""");
+
+  return buffer.toString();
+}