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

Side by Side Diff: pkg/barback/lib/src/asset_graph.dart

Issue 19402003: Rename several classes in barback. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Code review changes. Created 7 years, 5 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
« no previous file with comments | « pkg/barback/lib/src/asset_cascade.dart ('k') | pkg/barback/lib/src/asset_graph_manager.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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_graph_manager.dart';
13 import 'asset_provider.dart';
14 import 'asset_set.dart';
15 import 'errors.dart';
16 import 'change_batch.dart';
17 import 'phase.dart';
18 import 'transformer.dart';
19 import 'utils.dart';
20
21 /// The asset manager for an individual package.
22 ///
23 /// This keeps track of which transformers are applied to which assets, and
24 /// re-runs those transformers when their dependencies change. The transformed
25 /// assets are accessible via [getAssetById].
26 class AssetGraph {
27 /// The name of the package whose assets are managed.
28 final String package;
29
30 /// The [AssetGraphManager] that manages the [AssetGraph]s for all
31 /// dependencies of the current app.
32 final AssetGraphManager _manager;
33
34 final _phases = <Phase>[];
35
36 /// A stream that emits a [BuildResult] each time the build is completed,
37 /// whether or not it succeeded.
38 ///
39 /// If an unexpected error in barback itself occurs, it will be emitted
40 /// through this stream's error channel.
41 Stream<BuildResult> get results => _resultsController.stream;
42 final _resultsController = new StreamController<BuildResult>.broadcast();
43
44 /// A stream that emits any errors from the asset graph or the transformers.
45 ///
46 /// This emits errors as they're detected. If an error occurs in one part of
47 /// the asset graph, unrelated parts will continue building.
48 ///
49 /// This will not emit programming errors from barback itself. Those will be
50 /// emitted through the [results] stream's error channel.
51 Stream get errors => _errorsController.stream;
52 final _errorsController = new StreamController.broadcast();
53
54 /// The errors that have occurred since the current build started.
55 ///
56 /// This will be empty if no build is occurring.
57 Queue _accumulatedErrors;
58
59 /// A future that completes when the currently running build process finishes.
60 ///
61 /// If no build it in progress, is `null`.
62 Future _processDone;
63
64 ChangeBatch _sourceChanges;
65
66 /// Creates a new [AssetGraph].
67 ///
68 /// It loads source assets within [package] using [provider] and then uses
69 /// [transformerPhases] to generate output files from them.
70 //TODO(rnystrom): Better way of specifying transformers and their ordering.
71 AssetGraph(this._manager, this.package,
72 Iterable<Iterable<Transformer>> transformerPhases) {
73 // Flatten the phases to a list so we can traverse backwards to wire up
74 // each phase to its next.
75 var phases = transformerPhases.toList();
76
77 // Each phase writes its outputs as inputs to the next phase after it.
78 // Add a phase at the end for the final outputs of the last phase.
79 phases.add([]);
80
81 Phase nextPhase = null;
82 for (var transformers in phases.reversed) {
83 nextPhase = new Phase(this, _phases.length, transformers.toList(),
84 nextPhase);
85 _phases.insert(0, nextPhase);
86 }
87 }
88
89 /// Gets the asset identified by [id].
90 ///
91 /// If [id] is for a generated or transformed asset, this will wait until
92 /// it has been created and return it. If the asset cannot be found, throws
93 /// [AssetNotFoundException].
94 Future<Asset> getAssetById(AssetId id) {
95 assert(id.package == package);
96
97 // TODO(rnystrom): Waiting for the entire build to complete is unnecessary
98 // in some cases. Should optimize:
99 // * [id] may be generated before the compilation is finished. We should
100 // be able to quickly check whether there are any more in-place
101 // transformations that can be run on it. If not, we can return it early.
102 // * If everything is compiled, something that didn't output [id] is
103 // dirtied, and then [id] is requested, we can return it immediately,
104 // since anything overwriting it at that point is an error.
105 // * If [id] has never been generated and all active transformers provide
106 // metadata about the file names of assets it can emit, we can prove that
107 // none of them can emit [id] and fail early.
108 return (_processDone == null ? new Future.value() : _processDone).then((_) {
109 // Each phase's inputs are the outputs of the previous phase. Find the
110 // last phase that contains the asset. Since the last phase has no
111 // transformers, this will find the latest output for that id.
112
113 // TODO(rnystrom): Currently does not omit assets that are actually used
114 // as inputs for transformers. This means you can request and get an
115 // asset that should be "consumed" because it's used to generate the
116 // real asset you care about. Need to figure out how we want to handle
117 // that and what use cases there are related to it.
118 for (var i = _phases.length - 1; i >= 0; i--) {
119 var node = _phases[i].inputs[id];
120 if (node != null) {
121 // By the time we get here, the asset should have been built.
122 assert(node.asset != null);
123 return node.asset;
124 }
125 }
126
127 // Couldn't find it.
128 throw new AssetNotFoundException(id);
129 });
130 }
131
132 /// Adds [sources] to the graph's known set of source assets.
133 ///
134 /// Begins applying any transforms that can consume any of the sources. If a
135 /// given source is already known, it is considered modified and all
136 /// transforms that use it will be re-applied.
137 void updateSources(Iterable<AssetId> sources) {
138 if (_sourceChanges == null) _sourceChanges = new ChangeBatch();
139 assert(sources.every((id) => id.package == package));
140 _sourceChanges.update(sources);
141
142 _waitForProcess();
143 }
144
145 /// Removes [removed] from the graph's known set of source assets.
146 void removeSources(Iterable<AssetId> removed) {
147 if (_sourceChanges == null) _sourceChanges = new ChangeBatch();
148 assert(removed.every((id) => id.package == package));
149 _sourceChanges.remove(removed);
150
151 _waitForProcess();
152 }
153
154 void reportError(error) {
155 _accumulatedErrors.add(error);
156 _errorsController.add(error);
157 }
158
159 /// Starts the build process asynchronously if there is work to be done.
160 ///
161 /// Returns a future that completes with the background processing is done.
162 /// If there is no work to do, returns a future that completes immediately.
163 /// All errors that occur during processing will be caught (and routed to the
164 /// [results] stream) before they get to the returned future, so it is safe
165 /// to discard it.
166 Future _waitForProcess() {
167 if (_processDone != null) return _processDone;
168
169 _accumulatedErrors = new Queue();
170 return _processDone = _process().then((_) {
171 // Report the build completion.
172 // TODO(rnystrom): Put some useful data in here.
173 _resultsController.add(new BuildResult(_accumulatedErrors));
174 }).catchError((error) {
175 // If we get here, it's an unexpected error. Runtime errors like missing
176 // assets should be handled earlier. Errors from transformers or other
177 // external code that barback calls into should be caught at that API
178 // boundary.
179 //
180 // On the off chance we get here, pipe the error to the results stream
181 // as an error. That will let applications handle it without it appearing
182 // in the same path as "normal" errors that get reported.
183 _resultsController.addError(error);
184 }).whenComplete(() {
185 _processDone = null;
186 _accumulatedErrors = null;
187 });
188 }
189
190 /// Starts the background processing.
191 ///
192 /// Returns a future that completes when all assets have been processed.
193 Future _process() {
194 return _processSourceChanges().then((_) {
195 // Find the first phase that has work to do and do it.
196 var future;
197 for (var phase in _phases) {
198 future = phase.process();
199 if (future != null) break;
200 }
201
202 // If all phases are done and no new updates have come in, we're done.
203 if (future == null) {
204 // If changes have come in, start over.
205 if (_sourceChanges != null) return _process();
206
207 // Otherwise, everything is done.
208 return;
209 }
210
211 // Process that phase and then loop onto the next.
212 return future.then((_) => _process());
213 });
214 }
215
216 /// Processes the current batch of changes to source assets.
217 Future _processSourceChanges() {
218 // Always pump the event loop. This ensures a bunch of synchronous source
219 // changes are processed in a single batch even when the first one starts
220 // the build process.
221 return new Future(() {
222 if (_sourceChanges == null) return null;
223
224 // Take the current batch to ensure it doesn't get added to while we're
225 // processing it.
226 var changes = _sourceChanges;
227 _sourceChanges = null;
228
229 var updated = new AssetSet();
230 var futures = [];
231 for (var id in changes.updated) {
232 // TODO(rnystrom): Catch all errors from provider and route to results.
233 futures.add(_manager.provider.getAsset(id).then((asset) {
234 updated.add(asset);
235 }).catchError((error) {
236 if (error is AssetNotFoundException) {
237 // Handle missing asset errors like regular missing assets.
238 reportError(error);
239 } else {
240 // It's an unexpected error, so rethrow it.
241 throw error;
242 }
243 }));
244 }
245
246 return Future.wait(futures).then((_) {
247 _phases.first.updateInputs(updated, changes.removed);
248 });
249 });
250 }
251 }
252
253 /// An event indicating that the asset graph has finished building.
254 ///
255 /// A build can end either in success or failure. If there were no errors during
256 /// the build, it's considered to be a success; any errors render it a failure,
257 /// although individual assets may still have built successfully.
258 class BuildResult {
259 /// All errors that occurred during the build.
260 final List errors;
261
262 /// `true` if the build succeeded.
263 bool get succeeded => errors.isEmpty;
264
265 BuildResult(Iterable errors)
266 : errors = errors.toList();
267
268 /// Creates a build result indicating a successful build.
269 ///
270 /// This equivalent to a build result with no errors.
271 BuildResult.success()
272 : this([]);
273
274 String toString() {
275 if (succeeded) return "success";
276
277 return "errors:\n" + errors.map((error) {
278 var stackTrace = getAttachedStackTrace(error);
279 if (stackTrace != null) stackTrace = new Trace.from(stackTrace);
280
281 var msg = new StringBuffer();
282 msg.write(prefixLines(error.toString()));
283 if (stackTrace != null) {
284 msg.write("\n\n");
285 msg.write("Stack trace:\n");
286 msg.write(prefixLines(stackTrace.toString()));
287 }
288 return msg.toString();
289 }).join("\n\n");
290 }
291 }
OLDNEW
« no previous file with comments | « pkg/barback/lib/src/asset_cascade.dart ('k') | pkg/barback/lib/src/asset_graph_manager.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698