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

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: 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 unified diff | Download patch | Annotate | Revision Log
« 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 »
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_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 transformers in transformerPhases.reversed) {
54 nextPhase = new Phase(this, _phases.length, transformers.toList(),
55 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
82 // TODO(rnystrom): Currently does not omit assets that are actually used
83 // as inputs for transformers. This means you can request and get an
84 // asset that should be "consumed" because it's used to generate the
85 // real asset you care about. Need to figure out how we want to handle
86 // that and what use cases there are related to it.
87 for (var i = _phases.length - 1; i >= 0; i--) {
88 var node = _phases[i].inputs[id];
89 if (node != null) {
90 // By the time we get here, the asset should have been built.
91 assert(node.asset != null);
92 return node.asset;
93 }
94 }
95
96 // Couldn't find it.
97 var error = new AssetNotFoundException(id);
98 reportError(error);
99 throw error;
100 });
101 }
102
103 /// Adds [sources] to the graph's known set of source assets.
104 ///
105 /// Begins applying any transforms that can consume any of the sources. If a
106 /// given source is already known, it is considered modified and all
107 /// transforms that use it will be re-applied.
108 void updateSources(Iterable<AssetId> sources) {
109 if (_sourceChanges == null) _sourceChanges = new ChangeBatch();
110 _sourceChanges.update(sources);
111
112 _waitForProcess();
113 }
114
115 /// Removes [removed] from the graph's known set of source assets.
116 void removeSources(Iterable<AssetId> removed) {
117 if (_sourceChanges == null) _sourceChanges = new ChangeBatch();
118 _sourceChanges.remove(removed);
119
120 _waitForProcess();
121 }
122
123 /// Reports a process result with the given error then throws it.
124 void reportError(error) {
125 _resultsController.add(new ProcessResult(error));
126 }
127
128 /// Starts the build process asynchronously if there is work to be done.
129 ///
130 /// Returns a future that completes with the background processing is done.
131 /// If there is no work to do, returns a future that completes immediately.
132 /// All errors that occur during processing will be caught (and routed to the
133 /// [results] stream) before they get to the returned future, so it is safe
134 /// to discard it.
135 Future _waitForProcess() {
136 if (_processDone != null) return _processDone;
137 return _processDone = _process().catchError((error) {
138 // If we get here, it's an unexpected error. Runtime errors like missing
139 // assets should be handled earlier. Errors from transformers or other
140 // external code that barback calls into should be caught at that API
141 // boundary.
142 //
143 // On the off chance we get here, pipe the error to the results stream
144 // as an error. That will let applications handle it without it appearing
145 // in the same path as "normal" errors that get reported.
146 _resultsController.addError(error);
147 }).whenComplete(() {
148 _processDone = null;
149 // Report the build completion.
150 // TODO(rnystrom): Put some useful data in here.
151 _resultsController.add(new ProcessResult());
152 });
153 }
154
155 /// Starts the background processing.
156 ///
157 /// Returns a future that completes when all assets have been processed.
158 Future _process() {
159 return _processSourceChanges().then((_) {
160 // Find the first phase that has work to do and do it.
161 var future;
162 for (var phase in _phases) {
163 future = phase.process();
164 if (future != null) break;
165 }
166
167 // If all phases are done and no new updates have come in, we're done.
168 if (future == null) {
169 // If changes have come in, start over.
170 if (_sourceChanges != null) return _process();
171
172 // Otherwise, everything is done.
173 return;
174 }
175
176 // Process that phase and then loop onto the next.
177 return future.then((_) => _process());
178 });
179 }
180
181 /// Processes the current batch of changes to source assets.
182 Future _processSourceChanges() {
183 // Always pump the event loop. This ensures a bunch of synchronous source
184 // changes are processed in a single batch even when the first one starts
185 // the build process.
186 return new Future(() {
187 if (_sourceChanges == null) return null;
188
189 // Take the current batch to ensure it doesn't get added to while we're
190 // processing it.
191 var changes = _sourceChanges;
192 _sourceChanges = null;
193
194 var updated = new Map<AssetId, Asset>();
195 var futures = [];
196 for (var id in changes.updated) {
197 // TODO(rnystrom): Catch all errors from provider and route to results.
198 futures.add(_provider.getAsset(id).then((asset) {
199 updated[id] = asset;
200 }).catchError((error) {
201 if (error is AssetNotFoundException) {
202 // Handle missing asset errors like regular missing assets.
203 reportError(error);
204 } else {
205 // It's an unexpected error, so rethrow it.
206 throw error;
207 }
208 }));
209 }
210
211 return Future.wait(futures).then((_) {
212 _phases.first.updateInputs(updated, changes.removed);
213 });
214 });
215 }
216 }
217
218 /// Used to report build results back from the asynchronous build process
219 /// running in the background.
220 class ProcessResult {
221 /// The error that occurred, or `null` if the result is not an error.
222 final error;
223
224 ProcessResult([this.error]);
225 }
OLDNEW
« 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