UNPKG

@rushstack/operation-graph

Version:

Library for managing and executing operations in a directed acyclic graph.

88 lines 3.93 kB
"use strict"; // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. Object.defineProperty(exports, "__esModule", { value: true }); exports.calculateCriticalPathLengths = calculateCriticalPathLengths; exports.calculateShortestPath = calculateShortestPath; exports.calculateCriticalPathLength = calculateCriticalPathLength; /** * For every operation in the input, computes the length of the longest chain of operations that depend on it. * This value is stored as `operation.criticalPathLength`. */ function calculateCriticalPathLengths(operations) { // Clone the set of operations as an array, so that we can sort it. const queue = Array.from(operations); // Create a collection for detecting visited nodes const cycleDetectorStack = new Set(); for (const operation of queue) { calculateCriticalPathLength(operation, cycleDetectorStack); } return queue; } /** * Calculates the shortest path from `startOperation` to `endOperation`. * Used when printing out circular dependencies. */ function calculateShortestPath(startOperation, endOperation) { // Map of each operation to the most optimal parent const parents = new Map([[endOperation, undefined]]); let finalParent; // Run a breadth-first search to find the shortest path between the start and end operations outer: for (const [operation] of parents) { for (const consumer of operation.consumers) { // Since this is a breadth-first traversal, the first encountered path to a given node // will be tied for shortest, so only the first encountered path needs to be tracked if (!parents.has(consumer)) { parents.set(consumer, operation); } if (consumer === startOperation) { finalParent = operation; break outer; } } } if (!finalParent) { throw new Error(`Could not find a path from "${startOperation.name}" to "${endOperation.name}"`); } // Walk back up the path from the end operation to the start operation let currentOperation = finalParent; const path = [startOperation]; while (currentOperation !== undefined) { path.push(currentOperation); currentOperation = parents.get(currentOperation); } return path; } /** * Perform a depth-first search to find critical path length to the provided operation. * Cycle detection comes at minimal additional cost. */ function calculateCriticalPathLength(operation, dependencyChain) { if (dependencyChain.has(operation)) { // Ensure we have the shortest path to the cycle const shortestPath = calculateShortestPath(operation, operation); throw new Error('A cyclic dependency was encountered:\n ' + shortestPath.map((visitedTask) => visitedTask.name).join('\n -> ')); } let { criticalPathLength } = operation; if (criticalPathLength !== undefined) { // This has been visited already return criticalPathLength; } criticalPathLength = 0; if (operation.consumers.size) { dependencyChain.add(operation); for (const consumer of operation.consumers) { criticalPathLength = Math.max(criticalPathLength, calculateCriticalPathLength(consumer, dependencyChain)); } dependencyChain.delete(operation); } // Include the contribution from the current operation criticalPathLength += operation.weight ?? 1; // Record result operation.criticalPathLength = criticalPathLength; // Directly writing operations to an output collection here would yield a topological sorted set // However, we want a bit more fine-tuning of the output than just the raw topology return criticalPathLength; } //# sourceMappingURL=calculateCriticalPath.js.map