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

Side by Side Diff: pkg/analyzer/lib/src/task/driver.dart

Issue 913483002: First cut at analysis driver (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 5 years, 10 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) 2015, 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 analyzer.src.task.driver;
6
7 import 'dart:async';
8 import 'dart:collection';
9
10 import 'package:analyzer/src/context/cache.dart';
11 import 'package:analyzer/src/generated/engine.dart' hide AnalysisTask;
12 import 'package:analyzer/src/generated/java_engine.dart';
13 import 'package:analyzer/src/task/inputs.dart';
14 import 'package:analyzer/src/task/manager.dart';
15 import 'package:analyzer/task/model.dart';
16
17 /**
18 * An object that is used to cause analysis to be performed until all of the
19 * required analysis information has been computed.
20 */
21 class AnalysisDriver {
22 /**
23 * The task manager used to figure out how to compute analysis results.
24 */
25 final TaskManager taskManager;
26
27 /**
28 * The context in which analysis is to be performed.
29 */
30 final ExtendedAnalysisContext context;
31
32 /**
33 * The work order that was previously computed but that has not yet been
34 * completed.
35 */
36 WorkOrder currentWorkOrder;
37
38 /**
39 * The controller that is notified when a task is started.
40 */
41 StreamController<AnalysisTask> _onTaskStartedController;
42
43 /**
44 * The controller that is notified when a task is complete.
45 */
46 StreamController<AnalysisTask> _onTaskCompletedController;
47
48 /**
49 * Initialize a newly created driver to use the tasks know to the given
50 * [taskManager] to perform analysis in the given [context].
51 */
52 AnalysisDriver(this.taskManager, this.context) {
53 _onTaskStartedController = new StreamController.broadcast();
54 _onTaskCompletedController = new StreamController.broadcast();
55 }
56
57 /**
58 * The stream that is notified when a task is complete.
59 */
60 Stream<AnalysisTask> get onTaskCompleted => _onTaskCompletedController.stream;
61
62 /**
63 * The stream that is notified when a task is started.
64 */
65 Stream<AnalysisTask> get onTaskStarted => _onTaskStartedController.stream;
66
67 /**
68 * Perform work until the given [result] has been computed for the given
69 * [target].
70 */
71 void computeResult(AnalysisTarget target, ResultDescriptor result) {
72 WorkOrder workOrder = createWorkOrderForResult(target, result);
73 if (workOrder != null) {
74 while (workOrder.moveNext()) {
75 performWorkItem(workOrder.current);
76 }
77 }
78 }
79
80 /**
81 * Return the work order describing the work that should be getting worked on,
82 * or `null` if there is currently no work to be done.
83 */
84 WorkOrder createNextWorkOrder() {
85 //
86 // TODO(brianwilkerson) This is an inefficient implementation. We need to
87 // port over the concept of the WorkManager to manage the list of sources
88 // for which some work needs to be performed so that we do not waste time
89 // repeatedly looking at the same completed sources to see whether there is
90 // work that needs to be done.
Paul Berry 2015/03/03 17:21:32 An easy (but slightly hacky) way to achieve most o
Brian Wilkerson 2015/03/03 21:21:17 True. I think WorkManager is less "hacky" and shou
91 //
92 for (AnalysisTarget target in context.priorityTargets) {
Paul Berry 2015/03/03 17:21:32 I'm surprised to see only two levels of priority h
Brian Wilkerson 2015/03/03 21:21:17 That was probably done after I copied this code. I
93 WorkOrder workOrder = createWorkOrderForTarget(target, true);
94 if (workOrder != null) {
95 return workOrder;
96 }
97 }
98 for (AnalysisTarget target in context.explicitTargets) {
99 WorkOrder workOrder = createWorkOrderForTarget(target, false);
100 if (workOrder != null) {
101 return workOrder;
102 }
103 }
104 return null;
105 }
106
107 /**
108 * Create a work order that will produce the given [result] for the given
109 * [target]. Return the work order that was created, or `null` if the result
110 * has already been computed.
111 */
112 WorkOrder createWorkOrderForResult(AnalysisTarget target,
113 ResultDescriptor result) {
114 CacheEntry entry = context.getCacheEntry(target);
115 CacheState state = entry.getState(result);
116 if (state == CacheState.VALID ||
117 state == CacheState.ERROR ||
118 state == CacheState.IN_PROCESS) {
119 return null;
120 }
121 return new WorkOrder(
122 taskManager,
123 new WorkItem(context, target, taskManager.findTask(target, result)));
124 }
125
126 /**
127 * Create a work order that will produce the required analysis results for
128 * the given [target]. If [isPriority] is true, then the target is a priority
129 * target. Return the work order that was created, or `null` if there is no
130 * further work that needs to be done for the given target.
131 */
132 WorkOrder createWorkOrderForTarget(AnalysisTarget target, bool isPriority) {
133 for (ResultDescriptor result in taskManager.generalResults) {
134 WorkOrder workOrder = createWorkOrderForResult(target, result);
135 if (workOrder != null) {
136 return workOrder;
137 }
138 }
139 if (isPriority) {
140 for (ResultDescriptor result in taskManager.priorityResults) {
141 WorkOrder workOrder = createWorkOrderForResult(target, result);
142 if (workOrder != null) {
143 return workOrder;
144 }
145 }
146 }
147 return null;
148 }
149
150 /**
151 * Perform the next analysis task, and return `true` if there is more work to
152 * be done in order to compute all of the required analysis information.
153 */
154 bool performAnalysisTask() {
155 //
156 // TODO(brianwilkerson) This implementaiton does not allow us to prioritize
157 // work across contexts. What we need is a way for an external client to ask
158 // to have all priority files analyzed for each context, then ask for normal
159 // files to be analyzed. There are a couple of ways to do this.
160 //
161 // First, we could add a "bool priorityOnly" parameter to this method and
162 // return null here when it is true.
163 //
164 // Second, we could add a concept of a priority order and (externally) run
165 // through the priorities from highest to lowest. That would be a nice
166 // generalization of the previous idea, but it isn't clear that we need the
167 // generality.
168 //
Paul Berry 2015/03/03 17:21:32 A third approach, which I personally favor, would
Brian Wilkerson 2015/03/03 21:21:18 Added to the TODO.
169 if (currentWorkOrder == null) {
170 currentWorkOrder = createNextWorkOrder();
171 } else if (currentWorkOrder.moveNext()) {
172 performWorkItem(currentWorkOrder.current);
173 } else {
174 currentWorkOrder = createNextWorkOrder();
175 }
176 return currentWorkOrder != null;
177 }
178
179 /**
180 * Perform the given work item.
181 */
182 void performWorkItem(WorkItem item) {
183 if (item.exception != null) {
184 // Mark all of the results that the task would have computed as being in
185 // ERROR with the exception recorded on the work item.
186 CacheEntry targetEntry = context.getCacheEntry(item.target);
187 targetEntry.exception = item.exception;
188 for (ResultDescriptor result in item.descriptor.results) {
189 targetEntry.setState(result, CacheState.ERROR);
190 }
191 return;
192 }
193 // Otherwise, perform the task.
194 AnalysisTask task = item.buildTask();
195 _onTaskStartedController.add(task);
196 task.perform();
197 CacheEntry entry = context.getCacheEntry(task.target);
198 if (task.caughtException == null) {
199 Map<ResultDescriptor, dynamic> outputs = task.outputs;
200 for (ResultDescriptor result in task.descriptor.results) {
201 // TODO(brianwilkerson) We could check here that a value was produced
202 // and throw an exception if not (unless we want to allow null values).
203 entry.setValue(result, outputs[result]);
204 }
205 } else {
206 entry.exception = task.caughtException;
207 for (ResultDescriptor result in task.descriptor.results) {
208 entry.setState(result, CacheState.ERROR);
209 }
210 }
211 _onTaskCompletedController.add(task);
212 }
213
214 /**
215 * Reset the state of the driver in response to a change in the state of one
216 * or more analysis targets. This will cause any analysis that was currently
217 * in process to be stopped and for analysis to resume based on the new state.
218 */
219 void reset() {
220 currentWorkOrder = null;
221 }
222 }
223
224 /**
225 * A place to define the behaviors that need to be added to
226 * [InternalAnalysisContext].
227 */
228 abstract class ExtendedAnalysisContext implements InternalAnalysisContext {
229 List<AnalysisTarget> get explicitTargets;
230 List<AnalysisTarget> get priorityTargets;
231 CacheEntry getCacheEntry(AnalysisTarget target);
232 }
233
234 /**
235 * A description of a single anaysis task that can be performed to advance
236 * analysis.
237 */
238 class WorkItem {
239 /**
240 * The context in which the task will be performed.
241 */
242 final ExtendedAnalysisContext context;
243
244 /**
245 * The target for which a task is to be performed.
246 */
247 final AnalysisTarget target;
248
249 /**
250 * A description of the task to be performed.
251 */
252 final TaskDescriptor descriptor;
253
254 /**
255 * An iterator used to iterate over the descriptors of the inputs to the task,
256 * or `null` if all of the inputs have been collected and the task can be
257 * created.
258 */
259 TaskInputBuilder builder;
260
261 /**
262 * The inputs to the task that have been computed.
263 */
264 Map<String, dynamic> inputs;
265
266 /**
267 * The exception that was found while trying to populate the inputs. If this
268 * field is non-`null`, then the task cannot be performed and all of the
269 * results that this task would have computed need to be marked as being in
270 * ERROR with this exception.
271 */
272 CaughtException exception = null;
273
274 /**
275 * Initialize a newly created work item to compute the inputs for the task
276 * described by the given descriptor.
277 */
278 WorkItem(this.context, this.target, this.descriptor) {
279 Map<String, TaskInput> inputDescriptors =
280 descriptor.createTaskInputs(target);
281 builder = new TopLevelTaskInputBuilder(inputDescriptors);
282 if (!builder.moveNext()) {
283 builder = null;
284 }
285 inputs = new HashMap<String, dynamic>();
286 }
287
288 /**
289 * Build the task represented by this work item.
290 */
291 AnalysisTask buildTask() {
292 if (builder != null) {
293 throw new StateError("some inputs have not been computed");
294 }
295 return descriptor.createTask(context, target, inputs);
296 }
297
298 /**
299 * Gather all of the inputs needed to perform the task.
300 *
301 * If at least one of the inputs have not yet been computed, return a work
302 * item that can be used to generate that input to indicate that the caller
303 * should perform the returned item's task before returning to gathering
304 * inputs for this item's task.
305 *
306 * If all of the inputs have been gathered, return `null` to indicate that the
307 * client should build and perform the task. A value of `null` will also be
308 * returned if some of the inputs cannot be computed and the task cannot be
309 * performed. Callers can differentiate between these cases by checking the
310 * [exception] field. If the field is `null`, then the task can be performed;
311 * if the field is non-`null` then the task cannot be performed and all of the
312 * tasks' results should be marked as being in ERROR.
313 */
314 WorkItem gatherInputs(TaskManager taskManager) {
315 while (builder != null) {
316 //
317 // TODO(brianwilkerson) Capture information about which inputs were used
318 // to compute the results. This information can later be used to compute
319 // which results depend on a given result, and hence which results need to
320 // be invalidated when one result is invalidated.
321 //
322 AnalysisTarget inputTarget = builder.currentTarget;
323 ResultDescriptor inputResult = builder.currentResult;
324 CacheEntry inputEntry = context.getCacheEntry(inputTarget);
325 CacheState inputState = inputEntry.getState(inputResult);
326 if (inputState == CacheState.ERROR) {
327 exception = inputEntry.exception;
328 return null;
329 } else if (inputState == CacheState.IN_PROCESS) {
330 //
331 // TODO(brianwilkerson) Implement this case.
332 //
333 // One possibility would be to return a WorkItem that would perform a
334 // no-op task in order to cause us to come back to this work item on the
335 // next iteration. It would be more efficient, in general, to push this
336 // input onto a waiting list and proceed to the next input so that work
337 // could proceed, but given that the only result that can currently be
338 // IN_PROCESS is CONTENT, I don't know that it's worth the extra effort
339 // to implement the general solution at this point.
340 //
341 } else if (inputState != CacheState.VALID) {
342 try {
343 TaskDescriptor descriptor =
344 taskManager.findTask(inputTarget, inputResult);
345 return new WorkItem(context, inputTarget, descriptor);
346 } on AnalysisException catch (exception, stackTrace) {
347 this.exception = new CaughtException(exception, stackTrace);
348 return null;
349 }
350 }
351 builder.currentValue = inputEntry.getValue(inputResult);
352 if (!builder.moveNext()) {
353 inputs = builder.inputValue;
354 builder = null;
355 }
356 }
357 return null;
358 }
359 }
360
361 /**
362 * A description of the work to be done to compute a desired analysis result.
Paul Berry 2015/03/03 17:21:32 It would me nice to have a comment explaining that
Brian Wilkerson 2015/03/03 21:21:18 Done
363 */
364 class WorkOrder implements Iterator<WorkItem> {
365 /**
366 * The task manager used to build work items.
367 */
368 final TaskManager taskManager;
369
370 /**
371 * A list containing the work items that are being prepared for being worked.
372 */
373 final List<WorkItem> pendingItems = <WorkItem>[];
374
375 /**
376 * The current work item.
377 */
378 WorkItem currentItem;
379
380 /**
381 * Initialize a newly created work order to compute the result described by
382 * the given work item.
383 */
384 WorkOrder(this.taskManager, WorkItem item) {
385 pendingItems.add(item);
386 }
387
388 @override
389 WorkItem get current {
390 WorkItem item = currentItem;
391 currentItem = null;
Paul Berry 2015/03/03 17:21:32 This seems dangerous because it violates the usual
Brian Wilkerson 2015/03/03 21:21:18 Done
392 return item;
393 }
394
395 @override
396 bool moveNext() {
397 if (currentItem == null) {
398 if (pendingItems.isEmpty) {
399 return false;
400 }
401 currentItem = pendingItems.removeLast();
402 }
403 WorkItem childItem = currentItem.gatherInputs(taskManager);
404 while (childItem != null) {
405 pendingItems.add(currentItem);
406 currentItem = childItem;
407 childItem = currentItem.gatherInputs(taskManager);
408 }
409 return true;
410 }
411 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698