feat(core): hash tasks early when possible to enable optimizations (#11533)

This commit is contained in:
Victor Savkin 2022-08-11 14:56:16 -04:00 committed by GitHub
parent 36213b71fb
commit 918ddf6d4b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 116 additions and 35 deletions

3
.gitignore vendored
View File

@ -1,5 +1,6 @@
node_modules node_modules
.idea /.idea
/.fleet
/.vscode /.vscode
dist dist
/build /build

View File

@ -942,6 +942,7 @@ stored in the daemon process. To reset both run: `nx reset`.
| `tasks` | [`Task`](../../devkit/index#task)[] | | `tasks` | [`Task`](../../devkit/index#task)[] |
| `options` | [`DefaultTasksRunnerOptions`](../../devkit/index#defaulttasksrunneroptions) | | `options` | [`DefaultTasksRunnerOptions`](../../devkit/index#defaulttasksrunneroptions) |
| `context?` | `Object` | | `context?` | `Object` |
| `context.hasher?` | [`Hasher`](../../devkit/index#hasher) |
| `context.initiatingProject?` | `string` | | `context.initiatingProject?` | `string` |
| `context.nxArgs` | `NxArgs` | | `context.nxArgs` | `NxArgs` |
| `context.nxJson` | [`NxJsonConfiguration`](../../devkit/index#nxjsonconfiguration)<`string`[] \| `"*"`\> | | `context.nxJson` | [`NxJsonConfiguration`](../../devkit/index#nxjsonconfiguration)<`string`[] \| `"*"`\> |

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,49 @@
import { Task, TaskGraph } from '../config/task-graph';
import { getCustomHasher } from '../tasks-runner/utils';
import { readProjectsConfigurationFromProjectGraph } from '../project-graph/project-graph';
import { Hasher } from './hasher';
import { ProjectGraph } from '../config/project-graph';
import { Workspaces } from '../config/workspaces';
export function hashDependsOnOtherTasks(
workspaces: Workspaces,
hasher: Hasher,
projectGraph: ProjectGraph,
taskGraph: TaskGraph,
task: Task
) {
const customHasher = getCustomHasher(
task,
workspaces,
workspaces.readNxJson(),
projectGraph
);
if (customHasher) return true;
return hasher.hashDependsOnOtherTasks(task);
}
export async function hashTask(
workspaces: Workspaces,
hasher: Hasher,
projectGraph: ProjectGraph,
taskGraph: TaskGraph,
task: Task
) {
const customHasher = getCustomHasher(
task,
workspaces,
workspaces.readNxJson(),
projectGraph
);
const { value, details } = await (customHasher
? customHasher(task, {
hasher,
projectGraph,
taskGraph,
workspaceConfig:
readProjectsConfigurationFromProjectGraph(projectGraph),
})
: hasher.hashTask(task));
task.hash = value;
task.hashDetails = details;
}

View File

@ -115,6 +115,12 @@ export class Hasher {
}; };
} }
hashDependsOnOtherTasks(task: Task) {
const inputs = this.taskHasher.inputs(task);
// check here for outputs
return false;
}
/** /**
* @deprecated use hashTask instead * @deprecated use hashTask instead
*/ */
@ -212,10 +218,11 @@ class TaskHasher {
if (!projectNode) { if (!projectNode) {
return this.hashExternalDependency(task); return this.hashExternalDependency(task);
} }
const projectGraphDeps = const projectGraphDeps =
this.projectGraph.dependencies[task.target.project] ?? []; this.projectGraph.dependencies[task.target.project] ?? [];
const { selfInputs, depsInputs } = this.inputs(task, projectNode); const { selfInputs, depsInputs } = this.inputs(task);
const self = await this.hashSelfInputs(task, selfInputs); const self = await this.hashSelfInputs(task, selfInputs);
const deps = await this.hashDepsTasks( const deps = await this.hashDepsTasks(
depsInputs, depsInputs,
@ -276,10 +283,11 @@ class TaskHasher {
.filter((r) => !!r); .filter((r) => !!r);
} }
private inputs( inputs(task: Task): {
task: Task, depsInputs: { input: string }[];
projectNode: ProjectGraphProjectNode<any> selfInputs: ExpandedSelfInput[];
): { depsInputs: { input: string }[]; selfInputs: ExpandedSelfInput[] } { } {
const projectNode = this.projectGraph.nodes[task.target.project];
const namedInputs = { const namedInputs = {
default: [{ fileset: '{projectRoot}/**/*' }], default: [{ fileset: '{projectRoot}/**/*' }],
...this.nxJson.namedInputs, ...this.nxJson.namedInputs,

View File

@ -37,6 +37,7 @@ export const defaultTasksRunner: TasksRunner<
nxJson: NxJsonConfiguration; nxJson: NxJsonConfiguration;
nxArgs: NxArgs; nxArgs: NxArgs;
taskGraph: TaskGraph; taskGraph: TaskGraph;
hasher: Hasher;
} }
): Promise<{ [id: string]: TaskStatus }> => { ): Promise<{ [id: string]: TaskStatus }> => {
if ( if (
@ -69,6 +70,7 @@ async function runAllTasks(
nxJson: NxJsonConfiguration; nxJson: NxJsonConfiguration;
nxArgs: NxArgs; nxArgs: NxArgs;
taskGraph: TaskGraph; taskGraph: TaskGraph;
hasher: Hasher;
} }
): Promise<{ [id: string]: TaskStatus }> { ): Promise<{ [id: string]: TaskStatus }> {
// TODO: vsavkin: remove this after Nx 16 // TODO: vsavkin: remove this after Nx 16
@ -81,10 +83,8 @@ async function runAllTasks(
'task-graph-created' 'task-graph-created'
); );
const hasher = new Hasher(context.projectGraph, context.nxJson, options);
const orchestrator = new TaskOrchestrator( const orchestrator = new TaskOrchestrator(
hasher, context.hasher,
context.initiatingProject, context.initiatingProject,
context.projectGraph, context.projectGraph,
context.taskGraph, context.taskGraph,

View File

@ -19,11 +19,14 @@ import {
TargetDefaults, TargetDefaults,
TargetDependencies, TargetDependencies,
} from '../config/nx-json'; } from '../config/nx-json';
import { Task } from '../config/task-graph'; import { Task, TaskGraph } from '../config/task-graph';
import { createTaskGraph } from './create-task-graph'; import { createTaskGraph } from './create-task-graph';
import { findCycle, makeAcyclic } from './task-graph-utils'; import { findCycle, makeAcyclic } from './task-graph-utils';
import { TargetDependencyConfig } from '../config/workspace-json-project-json'; import { TargetDependencyConfig } from '../config/workspace-json-project-json';
import { handleErrors } from '../utils/params'; import { handleErrors } from '../utils/params';
import { Workspaces } from 'nx/src/config/workspaces';
import { Hasher } from 'nx/src/hasher/hasher';
import { hashDependsOnOtherTasks, hashTask } from 'nx/src/hasher/hash-task';
async function getTerminalOutputLifeCycle( async function getTerminalOutputLifeCycle(
initiatingProject: string, initiatingProject: string,
@ -82,6 +85,23 @@ async function getTerminalOutputLifeCycle(
} }
} }
async function hashTasksThatDontDependOnOtherTasks(
workspaces: Workspaces,
hasher: Hasher,
projectGraph: ProjectGraph,
taskGraph: TaskGraph
) {
const res = [] as Promise<void>[];
for (let t of Object.values(taskGraph.tasks)) {
if (
!hashDependsOnOtherTasks(workspaces, hasher, projectGraph, taskGraph, t)
) {
res.push(hashTask(workspaces, hasher, projectGraph, taskGraph, t));
}
}
return Promise.all(res);
}
export async function runCommand( export async function runCommand(
projectsToRun: ProjectGraphProjectNode[], projectsToRun: ProjectGraphProjectNode[],
projectGraph: ProjectGraph, projectGraph: ProjectGraph,
@ -99,6 +119,7 @@ export async function runCommand(
extraTargetDependencies extraTargetDependencies
); );
const projectNames = projectsToRun.map((t) => t.name); const projectNames = projectsToRun.map((t) => t.name);
const taskGraph = createTaskGraph( const taskGraph = createTaskGraph(
projectGraph, projectGraph,
defaultDependencyConfigs, defaultDependencyConfigs,
@ -108,6 +129,14 @@ export async function runCommand(
overrides overrides
); );
const hasher = new Hasher(projectGraph, nxJson, runnerOptions);
await hashTasksThatDontDependOnOtherTasks(
new Workspaces(workspaceRoot),
hasher,
projectGraph,
taskGraph
);
const cycle = findCycle(taskGraph); const cycle = findCycle(taskGraph);
if (cycle) { if (cycle) {
if (nxArgs.nxIgnoreCycles) { if (nxArgs.nxIgnoreCycles) {
@ -162,6 +191,7 @@ export async function runCommand(
nxJson, nxJson,
nxArgs, nxArgs,
taskGraph, taskGraph,
hasher,
} }
); );
let anyFailures; let anyFailures;

View File

@ -2,6 +2,7 @@ import { NxJsonConfiguration } from '../config/nx-json';
import { ProjectGraph } from '../config/project-graph'; import { ProjectGraph } from '../config/project-graph';
import { Task, TaskGraph } from '../config/task-graph'; import { Task, TaskGraph } from '../config/task-graph';
import { NxArgs } from '../utils/command-line-utils'; import { NxArgs } from '../utils/command-line-utils';
import { Hasher } from '../hasher/hasher';
export type TaskStatus = export type TaskStatus =
| 'success' | 'success'
@ -25,5 +26,6 @@ export type TasksRunner<T = unknown> = (
nxJson: NxJsonConfiguration; nxJson: NxJsonConfiguration;
nxArgs: NxArgs; nxArgs: NxArgs;
taskGraph?: TaskGraph; taskGraph?: TaskGraph;
hasher?: Hasher;
} }
) => any | Promise<{ [id: string]: TaskStatus }>; ) => any | Promise<{ [id: string]: TaskStatus }>;

View File

@ -49,6 +49,9 @@ describe('TasksSchedule', () => {
batchImplementationFactory: jest.fn(), batchImplementationFactory: jest.fn(),
}; };
}, },
readNxJson() {
return {};
},
}; };
const projectGraph: ProjectGraph = { const projectGraph: ProjectGraph = {

View File

@ -2,7 +2,6 @@ import { Workspaces } from '../config/workspaces';
import { import {
calculateReverseDeps, calculateReverseDeps,
getCustomHasher,
getExecutorForTask, getExecutorForTask,
getExecutorNameForTask, getExecutorNameForTask,
removeTasksFromTaskGraph, removeTasksFromTaskGraph,
@ -11,8 +10,8 @@ import { DefaultTasksRunnerOptions } from './default-tasks-runner';
import { Hasher } from '../hasher/hasher'; import { Hasher } from '../hasher/hasher';
import { Task, TaskGraph } from '../config/task-graph'; import { Task, TaskGraph } from '../config/task-graph';
import { ProjectGraph } from '../config/project-graph'; import { ProjectGraph } from '../config/project-graph';
import { readProjectsConfigurationFromProjectGraph } from '../project-graph/project-graph';
import { NxJsonConfiguration } from '../config/nx-json'; import { NxJsonConfiguration } from '../config/nx-json';
import { hashTask } from '../hasher/hash-task';
export interface Batch { export interface Batch {
executorName: string; executorName: string;
@ -84,7 +83,16 @@ export class TasksSchedule {
private async scheduleTask(taskId: string) { private async scheduleTask(taskId: string) {
const task = this.taskGraph.tasks[taskId]; const task = this.taskGraph.tasks[taskId];
await this.hashTask(task);
if (!task.hash) {
await hashTask(
this.workspaces,
this.hasher,
this.projectGraph,
this.taskGraph,
task
);
}
this.notScheduledTaskGraph = removeTasksFromTaskGraph( this.notScheduledTaskGraph = removeTasksFromTaskGraph(
this.notScheduledTaskGraph, this.notScheduledTaskGraph,
@ -170,25 +178,4 @@ export class TasksSchedule {
this.completedTasks.has(id) this.completedTasks.has(id)
); );
} }
private async hashTask(task: Task) {
const customHasher = getCustomHasher(
task,
this.workspaces,
this.nxJson,
this.projectGraph
);
const { value, details } = await (customHasher
? customHasher(task, {
hasher: this.hasher,
projectGraph: this.projectGraph,
taskGraph: this.taskGraph,
workspaceConfig: readProjectsConfigurationFromProjectGraph(
this.projectGraph
),
})
: this.hasher.hashTask(task));
task.hash = value;
task.hashDetails = details;
}
} }