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

Unified Diff: pkg/barback/lib/src/asset_graph.dart

Issue 16854005: First pass at build dependency graph for barback. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Handle unprovided sources and errors a bit better. Created 7 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « pkg/barback/lib/src/asset.dart ('k') | pkg/barback/lib/src/asset_id.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: pkg/barback/lib/src/asset_graph.dart
diff --git a/pkg/barback/lib/src/asset_graph.dart b/pkg/barback/lib/src/asset_graph.dart
new file mode 100644
index 0000000000000000000000000000000000000000..a73b67403bfe42ef71a3a2fc15665c0456debf4f
--- /dev/null
+++ b/pkg/barback/lib/src/asset_graph.dart
@@ -0,0 +1,225 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library barback.asset_graph;
+
+import 'dart:async';
+import 'dart:collection';
+
+import 'asset.dart';
+import 'asset_id.dart';
+import 'asset_provider.dart';
+import 'errors.dart';
+import 'change_batch.dart';
+import 'phase.dart';
+import 'transformer.dart';
+
+/// The main build dependency manager.
+///
+/// For any given input file, it can tell which output files are affected by
+/// it, and vice versa.
+class AssetGraph {
+ final AssetProvider _provider;
+
+ final _phases = <Phase>[];
+
+ Stream<ProcessResult> get results => _resultsController.stream;
+ final _resultsController = new StreamController<ProcessResult>.broadcast();
+
+ /// A future that completes when the currently running build process finishes.
+ ///
+ /// If no build it in progress, is `null`.
+ Future _processDone;
+
+ ChangeBatch _sourceChanges;
+
+ /// Creates a new [AssetGraph].
+ ///
+ /// It loads source assets using [provider] and then uses [transformerPhases]
+ /// to generate output files from them.
+ //TODO(rnystrom): Better way of specifying transformers and their ordering.
+ AssetGraph(this._provider,
+ Iterable<Iterable<Transformer>> transformerPhases) {
+ // Flatten the phases to a list so we can traverse backwards to wire up
+ // each phase to its next.
+ transformerPhases = transformerPhases.toList();
+
+ // Each phase writes its outputs as inputs to the next phase after it.
+ // Add a phase at the end for the final outputs of the last phase.
+ transformerPhases.add([]);
+
+ Phase nextPhase = null;
+ for (var transformers in transformerPhases.reversed) {
+ nextPhase = new Phase(this, _phases.length, transformers.toList(),
+ nextPhase);
+ _phases.insert(0, nextPhase);
+ }
+ }
+
+ /// Gets the asset identified by [id].
+ ///
+ /// If [id] is for a generated or transformed asset, this will wait until
+ /// it has been created and return it. If the asset cannot be found, throws
+ /// [AssetNotFoundException].
+ Future<Asset> getAssetById(AssetId id) {
+ // TODO(rnystrom): Waiting for the entire build to complete is unnecessary
+ // in some cases. Should optimize:
+ // * [id] may be generated before the compilation is finished. We should
+ // be able to quickly check whether there are any more in-place
+ // transformations that can be run on it. If not, we can return it early.
+ // * If everything is compiled, something that didn't output [id] is
+ // dirtied, and then [id] is requested, we can return it immediately,
+ // since anything overwriting it at that point is an error.
+ // * If [id] has never been generated and all active transformers provide
+ // metadata about the file names of assets it can emit, we can prove that
+ // none of them can emit [id] and fail early.
+ return _waitForProcess().then((_) {
+ // Each phase's inputs are the outputs of the previous phase. Find the
+ // last phase that contains the asset. Since the last phase has no
+ // transformers, this will find the latest output for that id.
+
+ // TODO(rnystrom): Currently does not omit assets that are actually used
+ // as inputs for transformers. This means you can request and get an
+ // asset that should be "consumed" because it's used to generate the
+ // real asset you care about. Need to figure out how we want to handle
+ // that and what use cases there are related to it.
+ for (var i = _phases.length - 1; i >= 0; i--) {
+ var node = _phases[i].inputs[id];
+ if (node != null) {
+ // By the time we get here, the asset should have been built.
+ assert(node.asset != null);
+ return node.asset;
+ }
+ }
+
+ // Couldn't find it.
+ var error = new AssetNotFoundException(id);
+ reportError(error);
+ throw error;
+ });
+ }
+
+ /// Adds [sources] to the graph's known set of source assets.
+ ///
+ /// Begins applying any transforms that can consume any of the sources. If a
+ /// given source is already known, it is considered modified and all
+ /// transforms that use it will be re-applied.
+ void updateSources(Iterable<AssetId> sources) {
+ if (_sourceChanges == null) _sourceChanges = new ChangeBatch();
+ _sourceChanges.update(sources);
+
+ _waitForProcess();
+ }
+
+ /// Removes [removed] from the graph's known set of source assets.
+ void removeSources(Iterable<AssetId> removed) {
+ if (_sourceChanges == null) _sourceChanges = new ChangeBatch();
+ _sourceChanges.remove(removed);
+
+ _waitForProcess();
+ }
+
+ /// Reports a process result with the given error then throws it.
+ void reportError(error) {
+ _resultsController.add(new ProcessResult(error));
+ }
+
+ /// Starts the build process asynchronously if there is work to be done.
+ ///
+ /// Returns a future that completes with the background processing is done.
+ /// If there is no work to do, returns a future that completes immediately.
+ /// All errors that occur during processing will be caught (and routed to the
+ /// [results] stream) before they get to the returned future, so it is safe
+ /// to discard it.
+ Future _waitForProcess() {
+ if (_processDone != null) return _processDone;
+ return _processDone = _process().catchError((error) {
+ // If we get here, it's an unexpected error. Runtime errors like missing
+ // assets should be handled earlier. Errors from transformers or other
+ // external code that barback calls into should be caught at that API
+ // boundary.
+ //
+ // On the off chance we get here, pipe the error to the results stream
+ // as an error. That will let applications handle it without it appearing
+ // in the same path as "normal" errors that get reported.
+ _resultsController.addError(error);
+ }).whenComplete(() {
+ _processDone = null;
+ // Report the build completion.
+ // TODO(rnystrom): Put some useful data in here.
+ _resultsController.add(new ProcessResult());
+ });
+ }
+
+ /// Starts the background processing.
+ ///
+ /// Returns a future that completes when all assets have been processed.
+ Future _process() {
+ return _processSourceChanges().then((_) {
+ // Find the first phase that has work to do and do it.
+ var future;
+ for (var phase in _phases) {
+ future = phase.process();
+ if (future != null) break;
+ }
+
+ // If all phases are done and no new updates have come in, we're done.
+ if (future == null) {
+ // If changes have come in, start over.
+ if (_sourceChanges != null) return _process();
+
+ // Otherwise, everything is done.
+ return;
+ }
+
+ // Process that phase and then loop onto the next.
+ return future.then((_) => _process());
+ });
+ }
+
+ /// Processes the current batch of changes to source assets.
+ Future _processSourceChanges() {
+ // Always pump the event loop. This ensures a bunch of synchronous source
+ // changes are processed in a single batch even when the first one starts
+ // the build process.
+ return new Future(() {
+ if (_sourceChanges == null) return null;
+
+ // Take the current batch to ensure it doesn't get added to while we're
+ // processing it.
+ var changes = _sourceChanges;
+ _sourceChanges = null;
+
+ var updated = new Map<AssetId, Asset>();
+ var futures = [];
+ for (var id in changes.updated) {
+ // TODO(rnystrom): Catch all errors from provider and route to results.
+ futures.add(_provider.getAsset(id).then((asset) {
+ updated[id] = asset;
+ }).catchError((error) {
+ if (error is AssetNotFoundException) {
+ // Handle missing asset errors like regular missing assets.
+ reportError(error);
+ } else {
+ // It's an unexpected error, so rethrow it.
+ throw error;
+ }
+ }));
+ }
+
+ return Future.wait(futures).then((_) {
+ _phases.first.updateInputs(updated, changes.removed);
+ });
+ });
+ }
+}
+
+/// Used to report build results back from the asynchronous build process
+/// running in the background.
+class ProcessResult {
+ /// The error that occurred, or `null` if the result is not an error.
+ final error;
+
+ ProcessResult([this.error]);
+}
« no previous file with comments | « pkg/barback/lib/src/asset.dart ('k') | pkg/barback/lib/src/asset_id.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698