nx/packages/workspace/src/tasks-runner/tasks-schedule.ts
James Henry 98ffe8574b feat(linter): support eslint v8 (#7800)
* feat(core): support eslint v8

* fix(core): align version of eslint across all packages
2021-11-19 14:55:33 -05:00

172 lines
4.6 KiB
TypeScript

import { Task, TaskGraph } from '@nrwl/devkit';
import { Workspaces } from '@nrwl/tao/src/shared/workspace';
import {
calculateReverseDeps,
getCustomHasher,
getExecutorForTask,
getExecutorNameForTask,
removeTasksFromTaskGraph,
} from './utils';
import { DefaultTasksRunnerOptions } from './default-tasks-runner';
import { Hasher } from '@nrwl/workspace/src/core/hasher/hasher';
export interface Batch {
executorName: string;
taskGraph: TaskGraph;
}
export class TasksSchedule {
private notScheduledTaskGraph = this.taskGraph;
private reverseTaskDeps = calculateReverseDeps(this.taskGraph);
private scheduledBatches: Batch[] = [];
private scheduledTasks: string[] = [];
private completedTasks = new Set<string>();
constructor(
private readonly hasher: Hasher,
private taskGraph: TaskGraph,
private workspace: Workspaces,
private options: DefaultTasksRunnerOptions
) {}
public async scheduleNextTasks() {
if (process.env.NX_BATCH_MODE === 'true') {
this.scheduleBatches();
}
for (let root of this.notScheduledTaskGraph.roots) {
if (this.canBeScheduled(root)) {
await this.scheduleTask(root);
}
}
}
public hasTasks() {
return (
this.scheduledBatches.length +
this.scheduledTasks.length +
Object.keys(this.notScheduledTaskGraph.tasks).length !==
0
);
}
public complete(taskIds: string[]) {
for (const taskId of taskIds) {
this.completedTasks.add(taskId);
}
this.notScheduledTaskGraph = removeTasksFromTaskGraph(
this.notScheduledTaskGraph,
taskIds
);
}
public nextTask() {
if (this.scheduledTasks.length > 0) {
return this.taskGraph.tasks[this.scheduledTasks.shift()];
} else {
return null;
}
}
public nextBatch(): Batch {
return this.scheduledBatches.length > 0
? this.scheduledBatches.shift()
: null;
}
private async scheduleTask(taskId: string) {
const task = this.taskGraph.tasks[taskId];
await this.hashTask(task);
this.notScheduledTaskGraph = removeTasksFromTaskGraph(
this.notScheduledTaskGraph,
[taskId]
);
this.scheduledTasks.push(taskId);
// TODO vsavkin: remove the if statement after Nx 14 is out
if (this.options.lifeCycle.scheduleTask) {
this.options.lifeCycle.scheduleTask(task);
}
}
private scheduleBatches() {
const batchMap: Record<string, TaskGraph> = {};
for (const root of this.notScheduledTaskGraph.roots) {
const rootTask = this.notScheduledTaskGraph.tasks[root];
const executorName = getExecutorNameForTask(rootTask, this.workspace);
this.processTaskForBatches(batchMap, rootTask, executorName, true);
}
for (const [executorName, taskGraph] of Object.entries(batchMap)) {
this.scheduleBatch({ executorName, taskGraph });
}
}
private scheduleBatch({ executorName, taskGraph }: Batch) {
// Create a new task graph without the tasks that are being scheduled as part of this batch
this.notScheduledTaskGraph = removeTasksFromTaskGraph(
this.notScheduledTaskGraph,
Object.keys(taskGraph.tasks)
);
this.scheduledBatches.push({ executorName, taskGraph });
}
private processTaskForBatches(
batches: Record<string, TaskGraph>,
task: Task,
rootExecutorName: string,
isRoot: boolean
) {
const { batchImplementationFactory } = getExecutorForTask(
task,
this.workspace
);
const executorName = getExecutorNameForTask(task, this.workspace);
if (rootExecutorName !== executorName) {
return;
}
if (!batchImplementationFactory) {
return;
}
const batch = (batches[rootExecutorName] =
batches[rootExecutorName] ??
({
tasks: {},
dependencies: {},
roots: [],
} as TaskGraph));
batch.tasks[task.id] = task;
batch.dependencies[task.id] = this.taskGraph.dependencies[task.id];
if (isRoot) {
batch.roots.push(task.id);
}
for (const dep of this.reverseTaskDeps[task.id]) {
const depTask = this.taskGraph.tasks[dep];
this.processTaskForBatches(batches, depTask, rootExecutorName, false);
}
}
private canBeScheduled(taskId: string) {
return this.taskGraph.dependencies[taskId].every((id) =>
this.completedTasks.has(id)
);
}
private async hashTask(task: Task) {
const customHasher = getCustomHasher(task, this.workspace);
const { value, details } = await (customHasher
? customHasher(task, this.taskGraph, this.hasher)
: this.hasher.hashTaskWithDepsAndContext(task));
task.hash = value;
task.hashDetails = details;
}
}