| Index: analyzer/lib/src/task/driver.dart
|
| diff --git a/analyzer/lib/src/task/driver.dart b/analyzer/lib/src/task/driver.dart
|
| deleted file mode 100644
|
| index ff5767db903edd1f5eabbf0ca856578b1fcb41a4..0000000000000000000000000000000000000000
|
| --- a/analyzer/lib/src/task/driver.dart
|
| +++ /dev/null
|
| @@ -1,817 +0,0 @@
|
| -// Copyright (c) 2015, 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 analyzer.src.task.driver;
|
| -
|
| -import 'dart:async';
|
| -import 'dart:collection';
|
| -
|
| -import 'package:analyzer/src/context/cache.dart';
|
| -import 'package:analyzer/src/generated/engine.dart'
|
| - hide AnalysisTask, AnalysisContextImpl;
|
| -import 'package:analyzer/src/generated/java_engine.dart';
|
| -import 'package:analyzer/src/generated/resolver.dart';
|
| -import 'package:analyzer/src/generated/utilities_general.dart';
|
| -import 'package:analyzer/src/task/inputs.dart';
|
| -import 'package:analyzer/src/task/manager.dart';
|
| -import 'package:analyzer/task/model.dart';
|
| -
|
| -final PerformanceTag workOrderMoveNextPerfTag =
|
| - new PerformanceTag('WorkOrder.moveNext');
|
| -
|
| -/**
|
| - * An object that is used to cause analysis to be performed until all of the
|
| - * required analysis information has been computed.
|
| - */
|
| -class AnalysisDriver {
|
| - /**
|
| - * The task manager used to figure out how to compute analysis results.
|
| - */
|
| - final TaskManager taskManager;
|
| -
|
| - /**
|
| - * The list of [WorkManager] used to figure out which analysis results to
|
| - * compute.
|
| - */
|
| - final List<WorkManager> workManagers;
|
| -
|
| - /**
|
| - * The context in which analysis is to be performed.
|
| - */
|
| - final InternalAnalysisContext context;
|
| -
|
| - /**
|
| - * The map of [ComputedResult] controllers.
|
| - */
|
| - final Map<ResultDescriptor, StreamController<ComputedResult>> resultComputedControllers =
|
| - <ResultDescriptor, StreamController<ComputedResult>>{};
|
| -
|
| - /**
|
| - * The work order that was previously computed but that has not yet been
|
| - * completed.
|
| - */
|
| - WorkOrder currentWorkOrder;
|
| -
|
| - /**
|
| - * Indicates whether any tasks are currently being performed (or building
|
| - * their inputs). In debug builds, we use this to ensure that tasks don't
|
| - * try to make use of the task manager in reentrant fashion.
|
| - */
|
| - bool isTaskRunning = false;
|
| -
|
| - /**
|
| - * The controller that is notified when a task is started.
|
| - */
|
| - StreamController<AnalysisTask> _onTaskStartedController;
|
| -
|
| - /**
|
| - * The controller that is notified when a task is complete.
|
| - */
|
| - StreamController<AnalysisTask> _onTaskCompletedController;
|
| -
|
| - /**
|
| - * Initialize a newly created driver to use the tasks know to the given
|
| - * [taskManager] to perform analysis in the given [context].
|
| - */
|
| - AnalysisDriver(this.taskManager, this.workManagers, this.context) {
|
| - _onTaskStartedController = new StreamController.broadcast();
|
| - _onTaskCompletedController = new StreamController.broadcast();
|
| - }
|
| -
|
| - /**
|
| - * The stream that is notified when a task is complete.
|
| - */
|
| - Stream<AnalysisTask> get onTaskCompleted => _onTaskCompletedController.stream;
|
| -
|
| - /**
|
| - * The stream that is notified when a task is started.
|
| - */
|
| - Stream<AnalysisTask> get onTaskStarted => _onTaskStartedController.stream;
|
| -
|
| - /**
|
| - * Perform work until the given [result] has been computed for the given
|
| - * [target]. Return the last [AnalysisTask] that was performed.
|
| - */
|
| - AnalysisTask computeResult(AnalysisTarget target, ResultDescriptor result) {
|
| - assert(!isTaskRunning);
|
| - try {
|
| - isTaskRunning = true;
|
| - AnalysisTask task;
|
| - WorkOrder workOrder = createWorkOrderForResult(target, result);
|
| - if (workOrder != null) {
|
| - while (workOrder.moveNext()) {
|
| - task = performWorkItem(workOrder.current);
|
| - }
|
| - }
|
| - return task;
|
| - } finally {
|
| - isTaskRunning = false;
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * Return the work order describing the work that should be getting worked on,
|
| - * or `null` if there is currently no work to be done.
|
| - */
|
| - WorkOrder createNextWorkOrder() {
|
| - while (true) {
|
| - // Find the WorkManager with the highest priority.
|
| - WorkOrderPriority highestPriority = null;
|
| - WorkManager highestManager = null;
|
| - for (WorkManager manager in workManagers) {
|
| - WorkOrderPriority priority = manager.getNextResultPriority();
|
| - if (highestPriority == null || highestPriority.index > priority.index) {
|
| - highestPriority = priority;
|
| - highestManager = manager;
|
| - }
|
| - }
|
| - // Nothing to do.
|
| - if (highestPriority == WorkOrderPriority.NONE) {
|
| - return null;
|
| - }
|
| - // Create a new WorkOrder.
|
| - TargetedResult request = highestManager.getNextResult();
|
| -// print('request: $request');
|
| - if (request != null) {
|
| - WorkOrder workOrder =
|
| - createWorkOrderForResult(request.target, request.result);
|
| - if (workOrder != null) {
|
| - return workOrder;
|
| - }
|
| - }
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * Create a work order that will produce the given [result] for the given
|
| - * [target]. Return the work order that was created, or `null` if the result
|
| - * has already been computed.
|
| - */
|
| - WorkOrder createWorkOrderForResult(
|
| - AnalysisTarget target, ResultDescriptor result) {
|
| - CacheEntry entry = context.getCacheEntry(target);
|
| - CacheState state = entry.getState(result);
|
| - if (state == CacheState.VALID ||
|
| - state == CacheState.ERROR ||
|
| - state == CacheState.IN_PROCESS) {
|
| - return null;
|
| - }
|
| - TaskDescriptor taskDescriptor = taskManager.findTask(target, result);
|
| - try {
|
| - WorkItem workItem = new WorkItem(context, target, taskDescriptor, result);
|
| - return new WorkOrder(taskManager, workItem);
|
| - } catch (exception, stackTrace) {
|
| - throw new AnalysisException(
|
| - 'Could not create work order (target = $target; taskDescriptor = $taskDescriptor; result = $result)',
|
| - new CaughtException(exception, stackTrace));
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * Create a work order that will produce the required analysis results for
|
| - * the given [target]. If [isPriority] is true, then the target is a priority
|
| - * target. Return the work order that was created, or `null` if there is no
|
| - * further work that needs to be done for the given target.
|
| - */
|
| - WorkOrder createWorkOrderForTarget(AnalysisTarget target, bool isPriority) {
|
| - for (ResultDescriptor result in taskManager.generalResults) {
|
| - WorkOrder workOrder = createWorkOrderForResult(target, result);
|
| - if (workOrder != null) {
|
| - return workOrder;
|
| - }
|
| - }
|
| - if (isPriority) {
|
| - for (ResultDescriptor result in taskManager.priorityResults) {
|
| - WorkOrder workOrder = createWorkOrderForResult(target, result);
|
| - if (workOrder != null) {
|
| - return workOrder;
|
| - }
|
| - }
|
| - }
|
| - return null;
|
| - }
|
| -
|
| - /**
|
| - * Return the stream that is notified when a new value for the given
|
| - * [descriptor] is computed.
|
| - */
|
| - Stream<ComputedResult> onResultComputed(ResultDescriptor descriptor) {
|
| - return resultComputedControllers.putIfAbsent(descriptor, () =>
|
| - new StreamController<ComputedResult>.broadcast(sync: true)).stream;
|
| - }
|
| -
|
| - /**
|
| - * Perform the next analysis task, and return `true` if there is more work to
|
| - * be done in order to compute all of the required analysis information.
|
| - */
|
| - bool performAnalysisTask() {
|
| - //
|
| - // TODO(brianwilkerson) This implementaiton does not allow us to prioritize
|
| - // work across contexts. What we need is a way for an external client to ask
|
| - // to have all priority files analyzed for each context, then ask for normal
|
| - // files to be analyzed. There are a couple of ways to do this.
|
| - //
|
| - // First, we could add a "bool priorityOnly" parameter to this method and
|
| - // return null here when it is true.
|
| - //
|
| - // Second, we could add a concept of a priority order and (externally) run
|
| - // through the priorities from highest to lowest. That would be a nice
|
| - // generalization of the previous idea, but it isn't clear that we need the
|
| - // generality.
|
| - //
|
| - // Third, we could move performAnalysisTask and createNextWorkOrder to an
|
| - // object that knows about all sources in all contexts, so that instead of
|
| - // the client choosing a context and telling it do to some work, the client
|
| - // simply says "do some work", and the engine chooses the best thing to do
|
| - // next regardless of what context it's in.
|
| - //
|
| - assert(!isTaskRunning);
|
| - try {
|
| - isTaskRunning = true;
|
| - if (currentWorkOrder == null) {
|
| - currentWorkOrder = createNextWorkOrder();
|
| - } else if (currentWorkOrder.moveNext()) {
|
| - performWorkItem(currentWorkOrder.current);
|
| - } else {
|
| - currentWorkOrder = createNextWorkOrder();
|
| - }
|
| - return currentWorkOrder != null;
|
| - } finally {
|
| - isTaskRunning = false;
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * Perform the given work item.
|
| - * Return the performed [AnalysisTask].
|
| - */
|
| - AnalysisTask performWorkItem(WorkItem item) {
|
| - if (item.exception != null) {
|
| - // Mark all of the results that the task would have computed as being in
|
| - // ERROR with the exception recorded on the work item.
|
| - CacheEntry targetEntry = context.getCacheEntry(item.target);
|
| - targetEntry.setErrorState(item.exception, item.descriptor.results);
|
| - return null;
|
| - }
|
| - // Otherwise, perform the task.
|
| - AnalysisTask task = item.buildTask();
|
| - _onTaskStartedController.add(task);
|
| - task.perform();
|
| - AnalysisTarget target = task.target;
|
| - CacheEntry entry = context.getCacheEntry(target);
|
| - if (task.caughtException == null) {
|
| - List<TargetedResult> dependedOn = item.inputTargetedResults.toList();
|
| - Map<ResultDescriptor, dynamic> outputs = task.outputs;
|
| - for (ResultDescriptor result in task.descriptor.results) {
|
| - // TODO(brianwilkerson) We could check here that a value was produced
|
| - // and throw an exception if not (unless we want to allow null values).
|
| - entry.setValue(result, outputs[result], dependedOn);
|
| - }
|
| - outputs.forEach((ResultDescriptor descriptor, value) {
|
| - StreamController<ComputedResult> controller =
|
| - resultComputedControllers[descriptor];
|
| - if (controller != null) {
|
| - ComputedResult event =
|
| - new ComputedResult(context, descriptor, target, value);
|
| - controller.add(event);
|
| - }
|
| - });
|
| - for (WorkManager manager in workManagers) {
|
| - manager.resultsComputed(target, outputs);
|
| - }
|
| - } else {
|
| - entry.setErrorState(task.caughtException, item.descriptor.results);
|
| - }
|
| - _onTaskCompletedController.add(task);
|
| - return task;
|
| - }
|
| -
|
| - /**
|
| - * Reset the state of the driver in response to a change in the state of one
|
| - * or more analysis targets. This will cause any analysis that was currently
|
| - * in process to be stopped and for analysis to resume based on the new state.
|
| - */
|
| - void reset() {
|
| - currentWorkOrder = null;
|
| - }
|
| -}
|
| -
|
| -/**
|
| - * Generic dependency walker suitable for use in the analysis task driver.
|
| - * This class implements a variant of the path-based strong component algorithm
|
| - * (described here: http://www.cs.colorado.edu/~hal/Papers/DFS/ipl.ps.gz), with
|
| - * the following differences:
|
| - *
|
| - * - The algorithm is non-recursive so that it can be used in a coroutine
|
| - * fashion (each call to [getNextStronglyConnectedComponent] computes a
|
| - * single strongly connected component and then waits to be called again)
|
| - *
|
| - * - Instead of keeping a temporary array which maps nodes to their locations
|
| - * in the [path] array, we simply search the array when necessary. This
|
| - * allows us to begin finding strongly connected components without having to
|
| - * know the size of the whole graph.
|
| - *
|
| - * - This algorithm does not compute all strongly connected components; only
|
| - * those reachable from the starting point which are as yet unevaluated.
|
| - *
|
| - * The algorithm, in essence, is to traverse the dependency graph in
|
| - * depth-first fashion from a starting node. If the path from the starting
|
| - * node ever encounters the same node twice, then a cycle has been found, and
|
| - * all the nodes comprising the cycle are (conceptually) contracted into a
|
| - * single node. The algorithm yields possibly-collapsed nodes in proper
|
| - * topological sort order (all the dependencies of a node are yielded before,
|
| - * or in the same contracted component as, the node itself).
|
| - */
|
| -abstract class CycleAwareDependencyWalker<Node> {
|
| - /**
|
| - * The path through the dependency graph that is currently being considered,
|
| - * with un-collapsed nodes.
|
| - */
|
| - final List<Node> _path;
|
| -
|
| - /**
|
| - * For each node in [_path], a list of the unevaluated nodes which it is
|
| - * already known to depend on.
|
| - */
|
| - final List<List<Node>> _provisionalDependencies;
|
| -
|
| - /**
|
| - * Indices into [_path] of the nodes which begin a new strongly connected
|
| - * component, in order. The first index in [_contractedPath] is always 0.
|
| - *
|
| - * For all i < contractedPath.length - 1, at least one node in the strongly
|
| - * connected component represented by [contractedPath[i]] depends directly
|
| - * on at least one node in the strongly connected component represented by
|
| - * [contractedPath[i+1]].
|
| - */
|
| - final List<int> _contractedPath;
|
| -
|
| - /**
|
| - * Index into [_path] of the nodes which we are currently in the process of
|
| - * querying for their dependencies.
|
| - *
|
| - * [currentIndices.last] always refers to a member of the last strongly
|
| - * connected component indicated by [_contractedPath].
|
| - */
|
| - final List<int> _currentIndices;
|
| -
|
| - /**
|
| - * Begin walking dependencies starting at [startingNode].
|
| - */
|
| - CycleAwareDependencyWalker(Node startingNode)
|
| - : _path = <Node>[startingNode],
|
| - _provisionalDependencies = <List<Node>>[<Node>[]],
|
| - _contractedPath = <int>[0],
|
| - _currentIndices = <int>[0];
|
| -
|
| - /**
|
| - * Determine the next unevaluated input for [node], skipping any inputs in
|
| - * [skipInputs], and return it. If [node] has no further inputs, return
|
| - * `null`.
|
| - */
|
| - Node getNextInput(Node node, List<Node> skipInputs);
|
| -
|
| - /**
|
| - * Determine the next strongly connected component in the graph, and return
|
| - * it. The client is expected to evaluate this component before calling
|
| - * [getNextStronglyConnectedComponent] again.
|
| - */
|
| - StronglyConnectedComponent<Node> getNextStronglyConnectedComponent() {
|
| - while (_currentIndices.isNotEmpty) {
|
| - Node nextUnevaluatedInput = getNextInput(_path[_currentIndices.last],
|
| - _provisionalDependencies[_currentIndices.last]);
|
| - assert(!_provisionalDependencies[_currentIndices.last]
|
| - .contains(nextUnevaluatedInput));
|
| - if (nextUnevaluatedInput != null) {
|
| - // TODO(paulberry): the call to _path.indexOf makes the algorithm
|
| - // O(n^2) in the depth of the dependency graph. If this becomes a
|
| - // problem, consider maintaining a map from node to index.
|
| - int previousIndex = _path.indexOf(nextUnevaluatedInput);
|
| - if (previousIndex != -1) {
|
| - // Update contractedPath to indicate that all nodes in the path
|
| - // between previousIndex and currentIndex are part of the same
|
| - // strongly connected component.
|
| - while (_contractedPath.last > previousIndex) {
|
| - _contractedPath.removeLast();
|
| - }
|
| - // Store nextUnevaluatedInput as a provisional dependency so that we
|
| - // can move on to computing other dependencies.
|
| - _provisionalDependencies[_currentIndices.last]
|
| - .add(nextUnevaluatedInput);
|
| - // And loop to move on to the node's next input.
|
| - continue;
|
| - } else {
|
| - // This is a brand new input and there's no reason (yet) to believe
|
| - // that it is in the same strongly connected component as any other
|
| - // node, so push it onto the end of the path.
|
| - int newIndex = _path.length;
|
| - _path.add(nextUnevaluatedInput);
|
| - _provisionalDependencies.add(<Node>[]);
|
| - _contractedPath.add(newIndex);
|
| - _currentIndices.add(newIndex);
|
| - // And loop to move on to the new node's inputs.
|
| - continue;
|
| - }
|
| - } else {
|
| - // The node has no more inputs. Figure out if there are any more nodes
|
| - // in the current strongly connected component that need to have their
|
| - // indices examined.
|
| - _currentIndices.removeLast();
|
| - if (_currentIndices.isEmpty ||
|
| - _currentIndices.last < _contractedPath.last) {
|
| - // No more nodes in the current strongly connected component need to
|
| - // have their indices examined. We can now yield this component to
|
| - // the caller.
|
| - List<Node> nodes = _path.sublist(_contractedPath.last);
|
| - bool containsCycle = nodes.length > 1;
|
| - if (!containsCycle) {
|
| - if (_provisionalDependencies.last.isNotEmpty) {
|
| - containsCycle = true;
|
| - }
|
| - }
|
| - _path.length = _contractedPath.last;
|
| - _provisionalDependencies.length = _contractedPath.last;
|
| - _contractedPath.removeLast();
|
| - return new StronglyConnectedComponent<Node>(nodes, containsCycle);
|
| - } else {
|
| - // At least one node in the current strongly connected component
|
| - // still needs to have its inputs examined. So loop and allow the
|
| - // inputs to be examined.
|
| - continue;
|
| - }
|
| - }
|
| - }
|
| - // No further strongly connected components found.
|
| - return null;
|
| - }
|
| -}
|
| -
|
| -/**
|
| - * A place to define the behaviors that need to be added to
|
| - * [InternalAnalysisContext].
|
| - */
|
| -abstract class ExtendedAnalysisContext implements InternalAnalysisContext {
|
| - List<AnalysisTarget> get explicitTargets;
|
| - List<AnalysisTarget> get priorityTargets;
|
| - void set typeProvider(TypeProvider typeProvider);
|
| - CacheEntry getCacheEntry(AnalysisTarget target);
|
| -}
|
| -
|
| -/**
|
| - * An exception indicating that an attempt was made to perform a task on a
|
| - * target while gathering the inputs to perform the same task for the same
|
| - * target.
|
| - */
|
| -class InfiniteTaskLoopException extends AnalysisException {
|
| - /**
|
| - * If a dependency cycle was found while computing the inputs for the task,
|
| - * the set of [WorkItem]s contained in the cycle (if there are overlapping
|
| - * cycles, this is the set of all [WorkItem]s in the entire strongly
|
| - * connected component). Otherwise, `null`.
|
| - */
|
| - final List<WorkItem> dependencyCycle;
|
| -
|
| - /**
|
| - * Initialize a newly created exception to represent a failed attempt to
|
| - * perform the given [task] due to the given [dependencyCycle].
|
| - */
|
| - InfiniteTaskLoopException(AnalysisTask task, this.dependencyCycle) : super(
|
| - 'Infinite loop while performing task ${task.descriptor.name} for ${task.target}');
|
| -}
|
| -
|
| -/**
|
| - * Object used by CycleAwareDependencyWalker to report a single strongly
|
| - * connected component of nodes.
|
| - */
|
| -class StronglyConnectedComponent<Node> {
|
| - /**
|
| - * The nodes contained in the strongly connected component.
|
| - */
|
| - final List<Node> nodes;
|
| -
|
| - /**
|
| - * Indicates whether the strongly component contains any cycles. Note that
|
| - * if [nodes] has multiple elements, this will always be `true`. However, if
|
| - * [nodes] has exactly one element, this may be either `true` or `false`
|
| - * depending on whether the node has a dependency on itself.
|
| - */
|
| - final bool containsCycle;
|
| -
|
| - StronglyConnectedComponent(this.nodes, this.containsCycle);
|
| -}
|
| -
|
| -/**
|
| - * A description of a single anaysis task that can be performed to advance
|
| - * analysis.
|
| - */
|
| -class WorkItem {
|
| - /**
|
| - * The context in which the task will be performed.
|
| - */
|
| - final InternalAnalysisContext context;
|
| -
|
| - /**
|
| - * The target for which a task is to be performed.
|
| - */
|
| - final AnalysisTarget target;
|
| -
|
| - /**
|
| - * A description of the task to be performed.
|
| - */
|
| - final TaskDescriptor descriptor;
|
| -
|
| - /**
|
| - * The [ResultDescriptor] which was led to this work item being spawned.
|
| - */
|
| - final ResultDescriptor spawningResult;
|
| -
|
| - /**
|
| - * An iterator used to iterate over the descriptors of the inputs to the task,
|
| - * or `null` if all of the inputs have been collected and the task can be
|
| - * created.
|
| - */
|
| - TaskInputBuilder builder;
|
| -
|
| - /**
|
| - * The [TargetedResult]s outputs of this task depends on.
|
| - */
|
| - final HashSet<TargetedResult> inputTargetedResults =
|
| - new HashSet<TargetedResult>();
|
| -
|
| - /**
|
| - * The inputs to the task that have been computed.
|
| - */
|
| - Map<String, dynamic> inputs;
|
| -
|
| - /**
|
| - * The exception that was found while trying to populate the inputs. If this
|
| - * field is non-`null`, then the task cannot be performed and all of the
|
| - * results that this task would have computed need to be marked as being in
|
| - * ERROR with this exception.
|
| - */
|
| - CaughtException exception = null;
|
| -
|
| - /**
|
| - * If a dependency cycle was found while computing the inputs for the task,
|
| - * the set of [WorkItem]s contained in the cycle (if there are overlapping
|
| - * cycles, this is the set of all [WorkItem]s in the entire strongly
|
| - * connected component). Otherwise, `null`.
|
| - */
|
| - List<WorkItem> dependencyCycle;
|
| -
|
| - /**
|
| - * Initialize a newly created work item to compute the inputs for the task
|
| - * described by the given descriptor.
|
| - */
|
| - WorkItem(this.context, this.target, this.descriptor, this.spawningResult) {
|
| - AnalysisTarget actualTarget = identical(
|
| - target, AnalysisContextTarget.request)
|
| - ? new AnalysisContextTarget(context)
|
| - : target;
|
| - Map<String, TaskInput> inputDescriptors =
|
| - descriptor.createTaskInputs(actualTarget);
|
| - builder = new TopLevelTaskInputBuilder(inputDescriptors);
|
| - if (!builder.moveNext()) {
|
| - builder = null;
|
| - }
|
| - inputs = new HashMap<String, dynamic>();
|
| - }
|
| -
|
| - @override
|
| - int get hashCode =>
|
| - JenkinsSmiHash.hash2(descriptor.hashCode, target.hashCode);
|
| -
|
| - @override
|
| - bool operator ==(other) {
|
| - if (other is WorkItem) {
|
| - return this.descriptor == other.descriptor && this.target == other.target;
|
| - } else {
|
| - return false;
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * Build the task represented by this work item.
|
| - */
|
| - AnalysisTask buildTask() {
|
| - if (builder != null) {
|
| - throw new StateError("some inputs have not been computed");
|
| - }
|
| - AnalysisTask task = descriptor.createTask(context, target, inputs);
|
| - task.dependencyCycle = dependencyCycle;
|
| - return task;
|
| - }
|
| -
|
| - /**
|
| - * Gather all of the inputs needed to perform the task.
|
| - *
|
| - * If at least one of the inputs have not yet been computed, return a work
|
| - * item that can be used to generate that input to indicate that the caller
|
| - * should perform the returned item's task before returning to gathering
|
| - * inputs for this item's task.
|
| - *
|
| - * If all of the inputs have been gathered, return `null` to indicate that the
|
| - * client should build and perform the task. A value of `null` will also be
|
| - * returned if some of the inputs cannot be computed and the task cannot be
|
| - * performed. Callers can differentiate between these cases by checking the
|
| - * [exception] field. If the field is `null`, then the task can be performed;
|
| - * if the field is non-`null` then the task cannot be performed and all of the
|
| - * tasks' results should be marked as being in ERROR.
|
| - */
|
| - WorkItem gatherInputs(TaskManager taskManager, List<WorkItem> skipInputs) {
|
| - while (builder != null) {
|
| - AnalysisTarget inputTarget = builder.currentTarget;
|
| - ResultDescriptor inputResult = builder.currentResult;
|
| - inputTargetedResults.add(new TargetedResult(inputTarget, inputResult));
|
| - CacheEntry inputEntry = context.getCacheEntry(inputTarget);
|
| - CacheState inputState = inputEntry.getState(inputResult);
|
| - if (skipInputs.any((WorkItem item) =>
|
| - item.target == inputTarget && item.spawningResult == inputResult)) {
|
| - // This input is being skipped due to a circular dependency. Tell the
|
| - // builder that it's not available so we can move on to other inputs.
|
| - builder.currentValueNotAvailable();
|
| - } else if (inputState == CacheState.ERROR) {
|
| - exception = inputEntry.exception;
|
| - return null;
|
| - } else if (inputState == CacheState.IN_PROCESS) {
|
| - //
|
| - // TODO(brianwilkerson) Implement this case.
|
| - //
|
| - // One possibility would be to return a WorkItem that would perform a
|
| - // no-op task in order to cause us to come back to this work item on the
|
| - // next iteration. It would be more efficient, in general, to push this
|
| - // input onto a waiting list and proceed to the next input so that work
|
| - // could proceed, but given that the only result that can currently be
|
| - // IN_PROCESS is CONTENT, I don't know that it's worth the extra effort
|
| - // to implement the general solution at this point.
|
| - //
|
| - throw new UnimplementedError();
|
| - } else if (inputState != CacheState.VALID) {
|
| - try {
|
| - TaskDescriptor descriptor =
|
| - taskManager.findTask(inputTarget, inputResult);
|
| - return new WorkItem(context, inputTarget, descriptor, inputResult);
|
| - } on AnalysisException catch (exception, stackTrace) {
|
| - this.exception = new CaughtException(exception, stackTrace);
|
| - return null;
|
| - }
|
| - } else {
|
| - builder.currentValue = inputEntry.getValue(inputResult);
|
| - }
|
| - if (!builder.moveNext()) {
|
| - inputs = builder.inputValue;
|
| - builder = null;
|
| - }
|
| - }
|
| - return null;
|
| - }
|
| -
|
| - @override
|
| - String toString() => 'Run $descriptor on $target';
|
| -}
|
| -
|
| -/**
|
| - * [AnalysisDriver] uses [WorkManager]s to select results to compute.
|
| - *
|
| - * They know specific of the targets and results they care about,
|
| - * so they can request analysis results in optimal order.
|
| - */
|
| -abstract class WorkManager {
|
| - /**
|
| - * Notifies the managers that the given set of priority [targets] was set.
|
| - */
|
| - void applyPriorityTargets(List<AnalysisTarget> targets);
|
| -
|
| - /**
|
| - * Return the next [TargetedResult] that this work manager wants to be
|
| - * computed, or `null` if this manager doesn't need any new results.
|
| - */
|
| - TargetedResult getNextResult();
|
| -
|
| - /**
|
| - * Return the priority if the next work order this work manager want to be
|
| - * computed. The [AnalysisDriver] will perform the work order with
|
| - * the highest priority.
|
| - *
|
| - * Even if the returned value is [WorkOrderPriority.NONE], it still does not
|
| - * guarantee that [getNextResult] will return not `null`.
|
| - */
|
| - WorkOrderPriority getNextResultPriority();
|
| -
|
| - /**
|
| - * Notifies the manager that the given [outputs] were produced for
|
| - * the given [target].
|
| - */
|
| - void resultsComputed(
|
| - AnalysisTarget target, Map<ResultDescriptor, dynamic> outputs);
|
| -}
|
| -
|
| -/**
|
| - * A description of the work to be done to compute a desired analysis result.
|
| - * The class implements a lazy depth-first traversal of the work item's input.
|
| - */
|
| -class WorkOrder implements Iterator<WorkItem> {
|
| - /**
|
| - * The dependency walker which is being used to determine what work to do
|
| - * next.
|
| - */
|
| - final _WorkOrderDependencyWalker _dependencyWalker;
|
| -
|
| - /**
|
| - * The strongly connected component most recently returned by
|
| - * [_dependencyWalker], minus any [WorkItem]s that the iterator has already
|
| - * moved past.
|
| - *
|
| - * Null if the [_dependencyWalker] hasn't been used yet.
|
| - */
|
| - List<WorkItem> currentItems;
|
| -
|
| - /**
|
| - * Initialize a newly created work order to compute the result described by
|
| - * the given work item.
|
| - */
|
| - WorkOrder(TaskManager taskManager, WorkItem item)
|
| - : _dependencyWalker = new _WorkOrderDependencyWalker(taskManager, item);
|
| -
|
| - @override
|
| - WorkItem get current {
|
| - if (currentItems == null) {
|
| - return null;
|
| - } else {
|
| - return currentItems.last;
|
| - }
|
| - }
|
| -
|
| - @override
|
| - bool moveNext() {
|
| - return workOrderMoveNextPerfTag.makeCurrentWhile(() {
|
| - if (currentItems != null && currentItems.length > 1) {
|
| - // Yield more items.
|
| - currentItems.removeLast();
|
| - return true;
|
| - } else {
|
| - // Get a new strongly connected component.
|
| - StronglyConnectedComponent<WorkItem> nextStronglyConnectedComponent =
|
| - _dependencyWalker.getNextStronglyConnectedComponent();
|
| - if (nextStronglyConnectedComponent == null) {
|
| - currentItems = null;
|
| - return false;
|
| - }
|
| - currentItems = nextStronglyConnectedComponent.nodes;
|
| - if (nextStronglyConnectedComponent.containsCycle) {
|
| - // A cycle has been found.
|
| - for (WorkItem item in currentItems) {
|
| - item.dependencyCycle = currentItems.toList();
|
| - }
|
| - } else {
|
| - assert(currentItems.length == 1);
|
| - }
|
| - return true;
|
| - }
|
| - });
|
| - }
|
| -}
|
| -
|
| -/**
|
| - * The priorities of work orders returned by [WorkManager]s.
|
| - */
|
| -enum WorkOrderPriority {
|
| - /**
|
| - * Responding to an user's action.
|
| - */
|
| - INTERACTIVE,
|
| -
|
| - /**
|
| - * Computing information for priority sources.
|
| - */
|
| - PRIORITY,
|
| -
|
| - /**
|
| - * A work should be done, but without any special urgency.
|
| - */
|
| - NORMAL,
|
| -
|
| - /**
|
| - * Nothing to do.
|
| - */
|
| - NONE
|
| -}
|
| -
|
| -/**
|
| - * Specilaization of [CycleAwareDependencyWalker] for use by [WorkOrder].
|
| - */
|
| -class _WorkOrderDependencyWalker extends CycleAwareDependencyWalker<WorkItem> {
|
| - /**
|
| - * The task manager used to build work items.
|
| - */
|
| - final TaskManager taskManager;
|
| -
|
| - _WorkOrderDependencyWalker(this.taskManager, WorkItem startingNode)
|
| - : super(startingNode);
|
| -
|
| - @override
|
| - WorkItem getNextInput(WorkItem node, List<WorkItem> skipInputs) {
|
| - return node.gatherInputs(taskManager, skipInputs);
|
| - }
|
| -}
|
|
|