| // 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. |
| |
| import 'dart:async'; |
| |
| import 'package:barback/barback.dart'; |
| |
| import '../log.dart' as log; |
| import '../package_graph.dart'; |
| import '../utils.dart'; |
| import 'asset_environment.dart'; |
| import 'barback_server.dart'; |
| import 'dependency_computer.dart'; |
| import 'transformer_id.dart'; |
| import 'transformer_loader.dart'; |
| |
| /// Loads all transformers depended on by packages in [environment]. |
| /// |
| /// This uses [environment]'s primary server to serve the Dart files from which |
| /// transformers are loaded, then adds the transformers to |
| /// `environment.barback`. |
| /// |
| /// Any built-in transformers that are provided by the environment will |
| /// automatically be added to the end of the root package's cascade. |
| /// |
| /// If [entrypoints] is passed, only transformers necessary to run those |
| /// entrypoints will be loaded. |
| Future loadAllTransformers( |
| AssetEnvironment environment, BarbackServer transformerServer, |
| {Iterable<AssetId> entrypoints}) async { |
| var dependencyComputer = new DependencyComputer(environment.graph); |
| |
| // If we only need to load transformers for a specific set of entrypoints, |
| // remove any other transformers from [transformersNeededByTransformers]. |
| Set<TransformerId> necessaryTransformers; |
| if (entrypoints != null) { |
| if (entrypoints.isEmpty) return; |
| |
| necessaryTransformers = unionAll( |
| entrypoints.map(dependencyComputer.transformersNeededByLibrary)); |
| |
| if (necessaryTransformers.isEmpty) { |
| log.fine("No transformers are needed for ${toSentence(entrypoints)}."); |
| return; |
| } |
| } |
| |
| var transformersNeededByTransformers = dependencyComputer |
| .transformersNeededByTransformers(necessaryTransformers); |
| |
| 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 stagedTransformers = _stageTransformers(transformersNeededByTransformers); |
| |
| var packagesThatUseTransformers = |
| _packagesThatUseTransformers(environment.graph); |
| |
| var loader = new TransformerLoader(environment, transformerServer); |
| |
| // Only save compiled snapshots when a physical entrypoint package is being |
| // used. There's no physical entrypoint when e.g. globally activating a cached |
| // package. |
| var cache = environment.rootPackage.dir == null |
| ? null |
| : environment.graph.loadTransformerCache(); |
| |
| var first = true; |
| for (var stage in stagedTransformers) { |
| // Only cache the first stage, since its contents aren't based on other |
| // transformers and thus is independent of the current mode. |
| var snapshotPath = |
| cache == null || !first ? null : cache.snapshotPath(stage); |
| first = false; |
| |
| /// Load all the transformers in [stage], then add them to the appropriate |
| /// locations in the transformer graphs of the packages that use them. |
| await loader.load(stage, snapshot: snapshotPath); |
| |
| // Only update packages that use transformers in [stage]. |
| var packagesToUpdate = |
| unionAll(stage.map((id) => packagesThatUseTransformers[id])); |
| await Future.wait(packagesToUpdate.map((packageName) async { |
| var package = environment.graph.packages[packageName]; |
| var phases = |
| await loader.transformersForPhases(package.pubspec.transformers); |
| environment.barback.updateTransformers(packageName, phases); |
| environment.dartDevcEnvironment?.invalidatePackage(packageName); |
| })); |
| } |
| |
| if (cache != null) cache.save(); |
| |
| /// Add built-in transformers for the packages that need them. |
| await Future.wait(environment.graph.packages.values.map((package) async { |
| var phases = |
| await loader.transformersForPhases(package.pubspec.transformers); |
| phases.addAll(environment.getBuiltInTransformers(package)); |
| if (phases.isEmpty) return; |
| |
| // TODO(nweiz): remove the [newFuture] here when issue 17305 is fixed. |
| // If no transformer in [phases] applies to a source input, |
| // [updateTransformers] may cause a [BuildResult] to be scheduled for |
| // immediate emission. Issue 17305 means that the caller will be unable |
| // to receive this result unless we delay the update to after this |
| // function returns. |
| newFuture(() { |
| environment.barback.updateTransformers(package.name, phases); |
| environment.dartDevcEnvironment?.invalidatePackage(package.name); |
| }); |
| })); |
| } |
| |
| /// Given [transformerDependencies], a directed acyclic graph, returns a list of |
| /// "stages" (sets of transformers). |
| /// |
| /// Each stage must be fully loaded and passed to barback before the next stage |
| /// can be safely loaded. However, transformers within a stage can be safely |
| /// loaded in parallel. |
| List<Set<TransformerId>> _stageTransformers( |
| Map<TransformerId, Set<TransformerId>> transformerDependencies) { |
| // A map from transformer ids to the indices of the stages that those |
| // transformer ids should end up in. Populated by [stageNumberFor]. |
| var stageNumbers = <TransformerId, int>{}; |
| var stages = <Set<TransformerId>>[]; |
| |
| int stageNumberFor(TransformerId id) { |
| // Built-in transformers don't have to be loaded in stages, since they're |
| // run from pub's source. Return -1 so that the "next stage" is 0. |
| if (id.isBuiltInTransformer) return -1; |
| |
| if (stageNumbers.containsKey(id)) return stageNumbers[id]; |
| var dependencies = transformerDependencies[id]; |
| stageNumbers[id] = |
| dependencies.isEmpty ? 0 : maxAll(dependencies.map(stageNumberFor)) + 1; |
| return stageNumbers[id]; |
| } |
| |
| for (var id in transformerDependencies.keys) { |
| var stageNumber = stageNumberFor(id); |
| if (stages.length <= stageNumber) stages.length = stageNumber + 1; |
| if (stages[stageNumber] == null) stages[stageNumber] = new Set(); |
| stages[stageNumber].add(id); |
| } |
| |
| return stages; |
| } |
| |
| /// Returns a map from transformer ids to all packages in [graph] that use each |
| /// transformer. |
| Map<TransformerId, Set<String>> _packagesThatUseTransformers( |
| PackageGraph graph) { |
| var results = <TransformerId, Set<String>>{}; |
| for (var package in graph.packages.values) { |
| for (var phase in package.pubspec.transformers) { |
| for (var config in phase) { |
| results.putIfAbsent(config.id, () => new Set()).add(package.name); |
| } |
| } |
| } |
| return results; |
| } |