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

Side by Side 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: Revise. 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 unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 library barback.asset_graph;
6
7 import 'dart:async';
8 import 'dart:collection';
9
10 import 'asset.dart';
11 import 'asset_id.dart';
12 import 'asset_provider.dart';
13 import 'errors.dart';
14 import 'change_batch.dart';
15 import 'phase.dart';
16 import 'transformer.dart';
17
18 /// The main build dependency manager.
19 ///
20 /// For any given input file, it can tell which output files are affected by
21 /// it, and vice versa.
22 class AssetGraph {
23 final AssetProvider _provider;
24
25 final _phases = <Phase>[];
26
27 Stream<ProcessResult> get results => _resultsController.stream;
28 final _resultsController = new StreamController<ProcessResult>.broadcast();
29
30 /// A future that completes when the currently running build process finishes.
31 ///
32 /// If no build it in progress, is `null`.
33 Future _processDone;
34
35 ChangeBatch _sourceChanges;
36
37 /// Creates a new [AssetGraph].
38 ///
39 /// It loads source assets using [provider] and then uses [transformerPhases]
40 /// to generate output files from them.
41 //TODO(rnystrom): Better way of specifying transformers and their ordering.
42 AssetGraph(this._provider,
43 Iterable<Iterable<Transformer>> transformerPhases) {
44 // Flatten the phases to a list so we can traverse backwards to wire up
45 // each phase to its next.
46 transformerPhases = transformerPhases.toList();
47
48 // Each phase writes its outputs as inputs to the next phase after it.
49 // Add a phase at the end for the final outputs of the last phase.
50 transformerPhases.add([]);
51
52 Phase nextPhase = null;
53 for (var i = transformerPhases.length - 1; i >= 0; i--) {
nweiz 2013/06/20 23:06:08 List has a [reversed] getter these days :).
Bob Nystrom 2013/06/21 00:13:20 Done.
54 nextPhase = new Phase(this, _phases.length,
55 transformerPhases[i].toList(), nextPhase);
56 _phases.insert(0, nextPhase);
57 }
58 }
59
60 /// Gets the asset identified by [id].
61 ///
62 /// If [id] is for a generated or transformed asset, this will wait until
63 /// it has been created and return it. If the asset cannot be found, throws
64 /// [AssetNotFoundException].
65 Future<Asset> getAssetById(AssetId id) {
66 // TODO(rnystrom): Waiting for the entire build to complete is unnecessary
67 // in some cases. Should optimize:
68 // * [id] may be generated before the compilation is finished. We should
69 // be able to quickly check whether there are any more in-place
70 // transformations that can be run on it. If not, we can return it early.
71 // * If everything is compiled, something that didn't output [id] is
72 // dirtied, and then [id] is requested, we can return it immediately,
73 // since anything overwriting it at that point is an error.
74 // * If [id] has never been generated and all active transformers provide
75 // metadata about the file names of assets it can emit, we can prove that
76 // none of them can emit [id] and fail early.
77 return _waitForProcess().then((_) {
78 // Each phase's inputs are the outputs of the previous phase. Find the
79 // last phase that contains the asset. Since the last phase has no
80 // transformers, this will find the latest output for that id.
81 // TODO(rnystrom): Currently does not omit assets that are actually used
nweiz 2013/06/20 23:06:08 Style nit: I like separating informative comments
Bob Nystrom 2013/06/21 00:13:20 Done.
82 // as inputs for transformers. This means you can request and get a
nweiz 2013/06/20 23:06:08 "a an" -> "an"
Bob Nystrom 2013/06/21 00:13:20 Done.
83 // an asset that should be "consumed" because it's used to generate the
84 // real asset you care about. Need to figure out how we want to handle
85 // that and what use cases there are related to it.
86 for (var i = _phases.length - 1; i >= 0; i--) {
87 var node = _phases[i].inputs[id];
88 if (node != null) {
89 // By the time we get here, the asset should have been built.
90 assert(node.asset != null);
91 return node.asset;
92 }
93 }
94
95 // Couldn't find it.
96 var error = new AssetNotFoundException(id);
97 reportError(error);
98 throw error;
99 });
100 }
101
102 /// Adds [sources] to the graph's known set of source assets.
103 ///
104 /// Begins applying any transforms that can consume any of the sources. If a
105 /// given source is already known, it is considered modified and all
106 /// transforms that use it will be re-applied.
107 void updateSources(Iterable<AssetId> sources) {
108 if (_sourceChanges == null) _sourceChanges = new ChangeBatch();
109 _sourceChanges.update(sources);
110
111 _waitForProcess();
112 }
113
114 /// Removes [removed] from the graph's known set of source assets.
115 void removeSources(Iterable<AssetId> removed) {
116 if (_sourceChanges == null) _sourceChanges = new ChangeBatch();
117 _sourceChanges.remove(removed);
118
119 _waitForProcess();
120 }
121
122 /// Reports a process result with the given error then throws it.
123 void reportError(error) {
124 _resultsController.add(new ProcessResult(error));
125 }
126
127 /// Starts the build process asynchronously if there is work to be done.
128 ///
129 /// Returns a future that completes with the background processing is done.
130 /// If there is no work to do, returns a future that completes immediately.
131 /// All errors that occur during processing will be caught (and routed to the
132 /// [results] stream) before they get to the returned future, so it is safe
133 /// to discard it.
134 Future _waitForProcess() {
135 if (_processDone != null) return _processDone;
136 return _processDone = _process().whenComplete(() {
137 _processDone = null;
138 // Report the build completion.
139 // TODO(rnystrom): Put some useful data in here.
140 _resultsController.add(new ProcessResult());
141 });
142 }
143
144 /// Starts the background processing.
145 ///
146 /// Returns a future that completes when all assets have been processed.
147 Future _process() {
148 return _processSourceChanges().then((_) {
149 // Find the first phase that has work to do and do it.
150 var future;
151 for (var phase in _phases) {
152 future = phase.process();
153 if (future != null) break;
154 }
155
156 // If all phases are done and no new updates have come in, we're done.
157 if (future == null) {
158 // If changes have come in, start over.
159 if (_sourceChanges != null) return _process();
160
161 // Otherwise, everything is done.
162 return;
163 }
164
165 // Process that phase and then loop onto the next.
166 return future.then((_) => _process());
167 });
168 }
169
170 /// Processes the current batch of changes to source assets.
171 Future _processSourceChanges() {
172 // Always pump the event loop. This ensures a bunch of synchronous source
173 // changes are processed in a single batch even when the first one starts
174 // the build process.
175 return new Future(() {
176 if (_sourceChanges == null) return null;
177
178 // Take the current batch to ensure it doesn't get added to while we're
179 // processing it.
180 var changes = _sourceChanges;
181 _sourceChanges = null;
182
183 var updated = new Map<AssetId, Asset>();
184 var futures = [];
185 for (var id in changes.updated) {
186 // TODO(rnystrom): Catch all errors from provider and route to results.
187 futures.add(_provider.getAsset(id).then((asset) {
188 updated[id] = asset;
189 }));
190 }
191
192 return Future.wait(futures).then((_) {
193 _phases.first.updateInputs(updated, changes.removed);
194 });
195 });
196 }
197 }
198
199 /// Used to report build results back from the asynchronous build process
200 /// running in the background.
201 class ProcessResult {
202 /// The error that occurred, or `null` if the result is not an error.
203 final error;
204
205 ProcessResult([this.error]);
206 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698