"use strict";
exports.id = "lib-intermediate-esm_logic_operations_BuildPlanPlugin_js";
exports.ids = ["lib-intermediate-esm_logic_operations_BuildPlanPlugin_js"];
exports.modules = {

/***/ 205573
/*!******************************************************************!*\
  !*** ./lib-intermediate-esm/logic/operations/BuildPlanPlugin.js ***!
  \******************************************************************/
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {

__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */   BuildPlanPlugin: () => (/* binding */ BuildPlanPlugin)
/* harmony export */ });
/* harmony import */ var _CacheableOperationPlugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./CacheableOperationPlugin */ 623429);
/* harmony import */ var _cobuild_DisjointSet__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../cobuild/DisjointSet */ 317311);
/* harmony import */ var _api_RushProjectConfiguration__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../api/RushProjectConfiguration */ 218825);
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.



const PLUGIN_NAME = 'BuildPlanPlugin';
class BuildPlanPlugin {
    constructor(terminal) {
        this._terminal = terminal;
    }
    apply(hooks) {
        const terminal = this._terminal;
        hooks.beforeExecuteOperations.tap(PLUGIN_NAME, createBuildPlan);
        function createBuildPlan(recordByOperation, context) {
            const { projectConfigurations, inputsSnapshot } = context;
            const disjointSet = new _cobuild_DisjointSet__WEBPACK_IMPORTED_MODULE_1__.DisjointSet();
            const operations = [...recordByOperation.keys()];
            for (const operation of operations) {
                disjointSet.add(operation);
            }
            const buildCacheByOperation = new Map();
            for (const operation of operations) {
                const { associatedProject, associatedPhase } = operation;
                const projectConfiguration = projectConfigurations.get(associatedProject);
                const fileHashes = inputsSnapshot === null || inputsSnapshot === void 0 ? void 0 : inputsSnapshot.getTrackedFileHashesForOperation(associatedProject, associatedPhase.name);
                if (!fileHashes) {
                    continue;
                }
                const cacheDisabledReason = _api_RushProjectConfiguration__WEBPACK_IMPORTED_MODULE_2__.RushProjectConfiguration.getCacheDisabledReasonForProject({
                    projectConfiguration,
                    trackedFileNames: fileHashes.keys(),
                    isNoOp: operation.isNoOp,
                    phaseName: associatedPhase.name
                });
                buildCacheByOperation.set(operation, { cacheDisabledReason });
            }
            (0,_CacheableOperationPlugin__WEBPACK_IMPORTED_MODULE_0__.clusterOperations)(disjointSet, buildCacheByOperation);
            const buildPlan = createCobuildPlan(disjointSet, terminal, buildCacheByOperation);
            logCobuildBuildPlan(buildPlan, terminal);
        }
    }
}
/**
 * Output the build plan summary, this will include the depth of the build plan, the width of the build plan, and
 * the number of nodes at each depth.
 *
 * Example output:
```
Build Plan Depth (deepest dependency tree): 3
Build Plan Width (maximum parallelism): 7
Number of Nodes per Depth: 2, 7, 5
Plan @ Depth 0 has 2 nodes and 0 dependents:
- b (build)
- a (build)
Plan @ Depth 1 has 7 nodes and 2 dependents:
- c (build)
- d (build)
- f (pre-build)
- g (pre-build)
- e (build)
- f (build)
- g (build)
Plan @ Depth 2 has 5 nodes and 9 dependents:
- c (build)
- d (build)
- e (build)
- f (build)
- g (build)
```
 * The summary data can be useful for understanding the shape of the build plan. The depth of the build plan is the
 *  longest dependency chain in the build plan. The width of the build plan is the maximum number of operations that
 *  can be executed in parallel. The number of nodes per depth is the number of operations that can be executed in parallel
 *  at each depth. **This does not currently include clustering information, which further restricts which operations can
 *  be executed in parallel.**
 * The depth data can be useful for debugging situations where cobuilds aren't utilizing multiple agents as expected. There may be
 *  some long dependency trees that can't be executed in parallel. Or there may be some key operations at the base of the
 *  build graph that are blocking the rest of the build.
 */
function generateCobuildPlanSummary(operations, terminal) {
    var _a, _b;
    const numberOfDependenciesByOperation = new Map();
    const queue = operations.filter((e) => e.dependencies.size === 0);
    const seen = new Set(queue);
    for (const operation of queue) {
        numberOfDependenciesByOperation.set(operation, 0);
    }
    /**
     * Traverse the build plan to determine the number of dependencies for each operation. This is done by starting
     *  at the base of the build plan and traversing the graph in a breadth-first manner. We use the parent operation
     *  to determine the number of dependencies for each child operation. This allows us to detect cases where no-op
     *  operations are strung together, and correctly mark the first real operation as being a root operation.
     */
    while (queue.length > 0) {
        const operation = queue.shift();
        const increment = operation.isNoOp ? 0 : 1;
        for (const consumer of operation.consumers) {
            const numberOfDependencies = ((_a = numberOfDependenciesByOperation.get(operation)) !== null && _a !== void 0 ? _a : 0) + increment;
            numberOfDependenciesByOperation.set(consumer, numberOfDependencies);
            if (!seen.has(consumer)) {
                queue.push(consumer);
                seen.add(consumer);
            }
        }
    }
    const layerQueue = [];
    for (const operation of operations) {
        if (operation.isNoOp) {
            continue;
        }
        const numberOfDependencies = (_b = numberOfDependenciesByOperation.get(operation)) !== null && _b !== void 0 ? _b : 0;
        if (numberOfDependencies === 0) {
            layerQueue.push(operation);
        }
    }
    let nextLayer = new Set();
    const remainingOperations = new Set(operations);
    let depth = 0;
    let maxWidth = layerQueue.length;
    const numberOfNodes = [maxWidth];
    const depthToOperationsMap = new Map();
    depthToOperationsMap.set(depth, new Set(layerQueue));
    /**
     * Determine the depth and width of the build plan. We start with the inner layer and gradually traverse layer by
     *  layer up the tree/graph until we have no more nodes to process. At each layer, we determine the
     *  number of executable operations.
     */
    do {
        if (layerQueue.length === 0) {
            layerQueue.push(...nextLayer);
            const realOperations = layerQueue.filter((e) => !e.isNoOp);
            if (realOperations.length > 0) {
                depth += 1;
                depthToOperationsMap.set(depth, new Set(realOperations));
                numberOfNodes.push(realOperations.length);
            }
            const currentWidth = realOperations.length;
            if (currentWidth > maxWidth) {
                maxWidth = currentWidth;
            }
            nextLayer = new Set();
            if (layerQueue.length === 0) {
                break;
            }
        }
        const leaf = layerQueue.shift();
        if (remainingOperations.delete(leaf)) {
            for (const consumer of leaf.consumers) {
                nextLayer.add(consumer);
            }
        }
    } while (remainingOperations.size > 0);
    terminal.writeLine(`Build Plan Depth (deepest dependency tree): ${depth + 1}`);
    terminal.writeLine(`Build Plan Width (maximum parallelism): ${maxWidth}`);
    terminal.writeLine(`Number of Nodes per Depth: ${numberOfNodes.join(', ')}`);
    for (const [operationDepth, operationsAtDepth] of depthToOperationsMap) {
        let numberOfDependents = 0;
        for (let i = 0; i < operationDepth; i++) {
            numberOfDependents += numberOfNodes[i];
        }
        terminal.writeLine(`Plan @ Depth ${operationDepth} has ${numberOfNodes[operationDepth]} nodes and ${numberOfDependents} dependents:`);
        for (const operation of operationsAtDepth) {
            if (operation.isNoOp !== true) {
                terminal.writeLine(`- ${operation.name}`);
            }
        }
    }
    return {
        maxDepth: depth === 0 && numberOfNodes[0] !== 0 ? depth + 1 : 0,
        maxWidth: maxWidth,
        numberOfNodesPerDepth: numberOfNodes
    };
}
function getName(op) {
    return op.name;
}
/**
 * Log the cobuild build plan by cluster. This is intended to help debug situations where cobuilds aren't
 *  utilizing multiple agents correctly.
 */
function createCobuildPlan(disjointSet, terminal, buildCacheByOperation) {
    const clusters = [...disjointSet.getAllSets()];
    const operations = clusters.flatMap((e) => Array.from(e));
    const operationToClusterMap = new Map();
    for (const cluster of clusters) {
        for (const operation of cluster) {
            operationToClusterMap.set(operation, cluster);
        }
    }
    return {
        summary: generateCobuildPlanSummary(operations, terminal),
        operations,
        buildCacheByOperation,
        clusterByOperation: operationToClusterMap,
        clusters
    };
}
/**
 * This method logs in depth details about the cobuild plan, including the operations in each cluster, the dependencies
 *  for each cluster, and the reason why each operation is clustered.
 */
function logCobuildBuildPlan(buildPlan, terminal) {
    var _a;
    const { operations, clusters, buildCacheByOperation, clusterByOperation } = buildPlan;
    const executionPlan = [];
    for (const operation of operations) {
        if (!operation.isNoOp) {
            executionPlan.push(operation);
        }
    }
    // This is a lazy way of getting the waterfall chart, basically check for the latest
    //  dependency and put this operation after that finishes.
    const spacingByDependencyMap = new Map();
    for (let index = 0; index < executionPlan.length; index++) {
        const operation = executionPlan[index];
        const spacing = Math.max(...Array.from(operation.dependencies, (e) => {
            const dependencySpacing = spacingByDependencyMap.get(e);
            return dependencySpacing !== undefined ? dependencySpacing + 1 : 0;
        }), 0);
        spacingByDependencyMap.set(operation, spacing);
    }
    executionPlan.sort((a, b) => {
        var _a, _b;
        const aSpacing = (_a = spacingByDependencyMap.get(a)) !== null && _a !== void 0 ? _a : 0;
        const bSpacing = (_b = spacingByDependencyMap.get(b)) !== null && _b !== void 0 ? _b : 0;
        return aSpacing - bSpacing;
    });
    terminal.writeLine('##################################################');
    // Get the maximum name length for left padding.
    let maxOperationNameLength = 1;
    for (const operation of executionPlan) {
        const name = getName(operation);
        maxOperationNameLength = Math.max(maxOperationNameLength, name.length);
    }
    for (const operation of executionPlan) {
        const spacing = (_a = spacingByDependencyMap.get(operation)) !== null && _a !== void 0 ? _a : 0;
        terminal.writeLine(`${getName(operation).padStart(maxOperationNameLength + 1)}: ${'-'.repeat(spacing)}(${clusters.indexOf(clusterByOperation.get(operation))})`);
    }
    terminal.writeLine('##################################################');
    function getDependenciesForCluster(cluster) {
        const dependencies = new Set();
        for (const operation of cluster) {
            for (const dependent of operation.dependencies) {
                dependencies.add(dependent);
            }
        }
        return dependencies;
    }
    function dedupeShards(ops) {
        const dedupedOperations = new Set();
        for (const operation of ops) {
            dedupedOperations.add(`${operation.associatedProject.packageName} (${operation.associatedPhase.name})`);
        }
        return [...dedupedOperations];
    }
    for (let clusterIndex = 0; clusterIndex < clusters.length; clusterIndex++) {
        const cluster = clusters[clusterIndex];
        const allClusterDependencies = getDependenciesForCluster(cluster);
        const outOfClusterDependencies = new Set([...allClusterDependencies].filter((e) => !cluster.has(e)));
        terminal.writeLine(`Cluster ${clusterIndex}:`);
        terminal.writeLine(`- Dependencies: ${dedupeShards(outOfClusterDependencies).join(', ') || 'none'}`);
        // Only log clustering info, if we did in fact cluster.
        if (cluster.size > 1) {
            terminal.writeLine(`- Clustered by: \n${[...allClusterDependencies]
                .filter((e) => { var _a; return (_a = buildCacheByOperation.get(e)) === null || _a === void 0 ? void 0 : _a.cacheDisabledReason; })
                .map((e) => { var _a, _b; return `  - (${e.name}) "${(_b = (_a = buildCacheByOperation.get(e)) === null || _a === void 0 ? void 0 : _a.cacheDisabledReason) !== null && _b !== void 0 ? _b : ''}"`; })
                .join('\n')}`);
        }
        terminal.writeLine(`- Operations: ${Array.from(cluster, (e) => `${getName(e)}${e.isNoOp ? ' [SKIPPED]' : ''}`).join(', ')}`);
        terminal.writeLine('--------------------------------------------------');
    }
    terminal.writeLine('##################################################');
}
//# sourceMappingURL=BuildPlanPlugin.js.map

/***/ }

};
;
//# sourceMappingURL=lib-intermediate-esm_logic_operations_BuildPlanPlugin_js.js.map