UNPKG

nx

Version:

Smart, Fast and Extensible Build System

313 lines • 14.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TaskOrchestrator = void 0; const tslib_1 = require("tslib"); const workspaces_1 = require("../config/workspaces"); const perf_hooks_1 = require("perf_hooks"); const forked_process_task_runner_1 = require("./forked-process-task-runner"); const workspace_root_1 = require("../utils/workspace-root"); const cache_1 = require("./cache"); const utils_1 = require("./utils"); const tasks_schedule_1 = require("./tasks-schedule"); class TaskOrchestrator { // endregion internal state constructor(hasher, initiatingProject, projectGraph, taskGraph, options, bail) { this.hasher = hasher; this.initiatingProject = initiatingProject; this.projectGraph = projectGraph; this.taskGraph = taskGraph; this.options = options; this.bail = bail; this.cache = new cache_1.Cache(this.options); this.workspace = new workspaces_1.Workspaces(workspace_root_1.workspaceRoot); this.forkedProcessTaskRunner = new forked_process_task_runner_1.ForkedProcessTaskRunner(this.options); this.nxJson = this.workspace.readNxJson(); this.tasksSchedule = new tasks_schedule_1.TasksSchedule(this.hasher, this.nxJson, this.projectGraph, this.taskGraph, this.workspace, this.options); // region internal state this.reverseTaskDeps = (0, utils_1.calculateReverseDeps)(this.taskGraph); this.completedTasks = {}; this.waitingForTasks = []; this.groups = []; this.bailed = false; } run() { return tslib_1.__awaiter(this, void 0, void 0, function* () { // initial scheduling yield this.tasksSchedule.scheduleNextTasks(); perf_hooks_1.performance.mark('task-execution-begins'); const threads = []; // initial seeding of the queue for (let i = 0; i < this.options.parallel; ++i) { threads.push(this.executeNextBatchOfTasksUsingTaskSchedule()); } yield Promise.all(threads); perf_hooks_1.performance.mark('task-execution-ends'); perf_hooks_1.performance.measure('command-execution', 'task-execution-begins', 'task-execution-ends'); this.cache.removeOldCacheRecords(); return this.completedTasks; }); } executeNextBatchOfTasksUsingTaskSchedule() { return tslib_1.__awaiter(this, void 0, void 0, function* () { // completed all the tasks if (!this.tasksSchedule.hasTasks() || this.bailed) { return null; } const doNotSkipCache = this.options.skipNxCache === false || this.options.skipNxCache === undefined; const batch = this.tasksSchedule.nextBatch(); if (batch) { const groupId = this.closeGroup(); yield this.applyFromCacheOrRunBatch(doNotSkipCache, batch, groupId); this.openGroup(groupId); return this.executeNextBatchOfTasksUsingTaskSchedule(); } const task = this.tasksSchedule.nextTask(); if (task) { const groupId = this.closeGroup(); yield this.applyFromCacheOrRunTask(doNotSkipCache, task, groupId); this.openGroup(groupId); return this.executeNextBatchOfTasksUsingTaskSchedule(); } // block until some other task completes, then try again return new Promise((res) => this.waitingForTasks.push(res)).then(() => this.executeNextBatchOfTasksUsingTaskSchedule()); }); } // region Applying Cache applyCachedResults(tasks) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const cacheableTasks = tasks.filter((t) => (0, utils_1.isCacheableTask)(t, this.options)); const res = yield Promise.all(cacheableTasks.map((t) => this.applyCachedResult(t))); return res.filter((r) => r !== null); }); } applyCachedResult(task) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const cachedResult = yield this.cache.get(task); if (!cachedResult || cachedResult.code !== 0) return null; const outputs = (0, utils_1.getOutputs)(this.projectGraph.nodes, task); const shouldCopyOutputsFromCache = !!outputs.length && (yield this.cache.shouldCopyOutputsFromCache({ task, cachedResult }, outputs)); if (shouldCopyOutputsFromCache) { yield this.cache.copyFilesFromCache(task.hash, cachedResult, outputs); } const status = cachedResult.remote ? 'remote-cache' : shouldCopyOutputsFromCache ? 'local-cache' : 'local-cache-kept-existing'; this.options.lifeCycle.printTaskTerminalOutput(task, status, cachedResult.terminalOutput); return { task, status, }; }); } // endregion Applying Cache // region Batch applyFromCacheOrRunBatch(doNotSkipCache, batch, groupId) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const taskEntries = Object.entries(batch.taskGraph.tasks); const tasks = taskEntries.map(([, task]) => task); yield this.preRunSteps(tasks, { groupId }); let results = doNotSkipCache ? yield this.applyCachedResults(tasks) : []; // Run tasks that were not cached if (results.length !== taskEntries.length) { const unrunTaskGraph = (0, utils_1.removeTasksFromTaskGraph)(batch.taskGraph, results.map(({ task }) => task.id)); // cache prep for (const task of Object.values(unrunTaskGraph.tasks)) { const taskOutputs = (0, utils_1.getOutputs)(this.projectGraph.nodes, task); yield this.cache.removeRecordedOutputsHashes(taskOutputs); } const batchResults = yield this.runBatch({ executorName: batch.executorName, taskGraph: unrunTaskGraph, }); results.push(...batchResults); } yield this.postRunSteps(results, { groupId }); const tasksCompleted = taskEntries.filter(([taskId]) => this.completedTasks[taskId]); // Batch is still not done, run it again if (tasksCompleted.length !== taskEntries.length) { yield this.applyFromCacheOrRunBatch(doNotSkipCache, { executorName: batch.executorName, taskGraph: (0, utils_1.removeTasksFromTaskGraph)(batch.taskGraph, tasksCompleted.map(([taskId]) => taskId)), }, groupId); } }); } runBatch(batch) { return tslib_1.__awaiter(this, void 0, void 0, function* () { try { const results = yield this.forkedProcessTaskRunner.forkProcessForBatch(batch); const batchResultEntries = Object.entries(results); return batchResultEntries.map(([taskId, result]) => ({ task: this.taskGraph.tasks[taskId], status: (result.success ? 'success' : 'failure'), terminalOutput: result.terminalOutput, })); } catch (e) { return batch.taskGraph.roots.map((rootTaskId) => ({ task: this.taskGraph.tasks[rootTaskId], status: 'failure', })); } }); } // endregion Batch // region Single Task applyFromCacheOrRunTask(doNotSkipCache, task, groupId) { return tslib_1.__awaiter(this, void 0, void 0, function* () { yield this.preRunSteps([task], { groupId }); // hash the task here let results = doNotSkipCache ? yield this.applyCachedResults([task]) : []; // the task wasn't cached if (results.length === 0) { // cache prep const taskOutputs = (0, utils_1.getOutputs)(this.projectGraph.nodes, task); yield this.cache.removeRecordedOutputsHashes(taskOutputs); const { code, terminalOutput } = yield this.runTaskInForkedProcess(task); results.push({ task, status: code === 0 ? 'success' : 'failure', terminalOutput, }); } yield this.postRunSteps(results, { groupId }); }); } runTaskInForkedProcess(task) { return tslib_1.__awaiter(this, void 0, void 0, function* () { try { // obtain metadata const temporaryOutputPath = this.cache.temporaryOutputPath(task); const streamOutput = (0, utils_1.shouldStreamOutput)(task, this.initiatingProject, this.options); const pipeOutput = this.pipeOutputCapture(task); // execution const { code, terminalOutput } = pipeOutput ? yield this.forkedProcessTaskRunner.forkProcessPipeOutputCapture(task, { temporaryOutputPath, streamOutput, }) : yield this.forkedProcessTaskRunner.forkProcessDirectOutputCapture(task, { temporaryOutputPath, streamOutput, }); return { code, terminalOutput, }; } catch (e) { return { code: 1, }; } }); } // endregion Single Task // region Lifecycle preRunSteps(tasks, metadata) { return tslib_1.__awaiter(this, void 0, void 0, function* () { this.options.lifeCycle.startTasks(tasks, metadata); }); } postRunSteps(results, { groupId }) { return tslib_1.__awaiter(this, void 0, void 0, function* () { // cache the results yield Promise.all(results .filter(({ status }) => status !== 'local-cache' && status !== 'local-cache-kept-existing' && status !== 'remote-cache' && status !== 'skipped') .map((result) => (Object.assign(Object.assign({}, result), { code: result.status === 'local-cache' || result.status === 'local-cache-kept-existing' || result.status === 'remote-cache' || result.status === 'success' ? 0 : 1, outputs: (0, utils_1.getOutputs)(this.projectGraph.nodes, result.task) }))) .filter(({ task, code }) => this.shouldCacheTaskResult(task, code)) .filter(({ terminalOutput, outputs }) => terminalOutput || outputs) .map(({ task, code, terminalOutput, outputs }) => tslib_1.__awaiter(this, void 0, void 0, function* () { yield this.cache.put(task, terminalOutput, outputs, code); }))); this.options.lifeCycle.endTasks(results.map((result) => { const code = result.status === 'success' || result.status === 'local-cache' || result.status === 'local-cache-kept-existing' || result.status === 'remote-cache' ? 0 : 1; return { task: result.task, status: result.status, code, }; }), { groupId }); this.complete(results.map(({ task, status }) => { return { taskId: task.id, status, }; })); yield this.tasksSchedule.scheduleNextTasks(); // release blocked threads this.waitingForTasks.forEach((f) => f(null)); this.waitingForTasks.length = 0; }); } complete(taskResults) { this.tasksSchedule.complete(taskResults.map(({ taskId }) => taskId)); for (const { taskId, status } of taskResults) { if (this.completedTasks[taskId] === undefined) { this.completedTasks[taskId] = status; if (status === 'failure' || status === 'skipped') { if (this.bail) { // mark the execution as bailed which will stop all further execution // only the tasks that are currently running will finish this.bailed = true; } else { // only mark the packages that depend on the current task as skipped // other tasks will continue to execute this.complete(this.reverseTaskDeps[taskId].map((depTaskId) => ({ taskId: depTaskId, status: 'skipped', }))); } } } } } //endregion Lifecycle // region utils pipeOutputCapture(task) { try { return ((0, utils_1.getExecutorForTask)(task, this.workspace, this.projectGraph, this.nxJson) .schema.outputCapture === 'pipe' || process.env.NX_STREAM_OUTPUT === 'true'); } catch (e) { return false; } } shouldCacheTaskResult(task, code) { return ((0, utils_1.isCacheableTask)(task, this.options) && (process.env.NX_CACHE_FAILURES == 'true' ? true : code === 0)); } closeGroup() { for (let i = 0; i < this.options.parallel; i++) { if (!this.groups[i]) { this.groups[i] = true; return i; } } } openGroup(id) { this.groups[id] = false; } } exports.TaskOrchestrator = TaskOrchestrator; //# sourceMappingURL=task-orchestrator.js.map