Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/nx/src/native/tests/watcher.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ describe('watcher', () => {
await wait();
temp.renameFile('app1/main.js', 'app1/rename.js');
});
}, 15000);
}, 30000);

it('should trigger on deletes', async () => {
return new Promise<void>(async (done) => {
Expand Down
71 changes: 71 additions & 0 deletions packages/nx/src/tasks-runner/task-graph-utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import '../internal-testing-utils/mock-fs';

import {
assertTaskGraphDoesNotContainInvalidTargets,
findCycle,
findCycles,
makeAcyclic,
validateNoAtomizedTasks,
} from './task-graph-utils';
import { TaskGraph } from '../config/task-graph';

describe('task graph utils', () => {
describe('findCycle', () => {
Expand Down Expand Up @@ -310,4 +312,73 @@ describe('task graph utils', () => {
expect(mockProcessExit).toHaveBeenCalledWith(1);
});
});

describe('assertTaskGraphDoesNotContainInvalidTargets', () => {
it('should throw if a task has parallelism set to false and has continuous dependencies', () => {
const taskGraph: TaskGraph = {
tasks: {
'a:build': {
id: 'a:build',
target: { project: 'a', target: 'build' },
parallelism: false,
overrides: {},
outputs: [],
},
'b:watch': {
id: 'b:watch',
target: { project: 'b', target: 'watch' },
parallelism: false,
overrides: {},
outputs: [],
},
},
continuousDependencies: {
'a:build': ['b:watch'],
'b:watch': [],
},
roots: ['a:build'],
dependencies: {
'a:build': [],
'b:watch': [],
},
};
expect(() => {
assertTaskGraphDoesNotContainInvalidTargets(taskGraph);
}).toThrowErrorMatchingInlineSnapshot(`
"The following tasks do not support parallelism but depend on continuous tasks:
- a:build -> b:watch"
`);
});

it('should throw if a task that is depended on and is continuous has parallelism set to false', () => {
const taskGraph: TaskGraph = {
tasks: {
'a:build': {
id: 'a:build',
target: { project: 'a', target: 'build' },
overrides: {},
outputs: [],
parallelism: true,
},
'b:watch': {
id: 'b:watch',
target: { project: 'b', target: 'watch' },
parallelism: false,
overrides: {},
outputs: [],
},
},
continuousDependencies: { 'a:build': ['b:watch'], 'b:watch': [] },
dependencies: { 'a:build': [], 'b:watch': [] },
roots: ['a:build'],
};
expect(() => {
assertTaskGraphDoesNotContainInvalidTargets(taskGraph);
}).toThrowErrorMatchingInlineSnapshot(`
"The following continuous tasks do not support parallelism but are depended on:
- b:watch <- a:build
Parallelism must be enabled for a continuous task if it is depended on, as the tasks that depend on it will run in parallel with it."
`);
});
});
});
44 changes: 40 additions & 4 deletions packages/nx/src/tasks-runner/task-graph-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,19 +159,33 @@ export function validateNoAtomizedTasks(
export function assertTaskGraphDoesNotContainInvalidTargets(
taskGraph: TaskGraph
) {
const invalidTasks = [];
const nonParallelTasksThatDependOnContinuousTasks = [];
const nonParallelContinuousTasksThatAreDependedOn = [];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a typo in the variable name nonParallelContinousTasksThatAreDependedOn (missing the letter 'u' in "Continuous"). This misspelling appears in multiple places in the file. The correct spelling should be nonParallelContinuousTasksThatAreDependedOn.

Spotted by Diamond

Is this helpful? React 👍 or 👎 to let us know.

for (const task of Object.values(taskGraph.tasks)) {
if (
task.parallelism === false &&
taskGraph.continuousDependencies[task.id].length > 0
) {
invalidTasks.push(task);
nonParallelTasksThatDependOnContinuousTasks.push(task);
}
for (const dependency of taskGraph.continuousDependencies[task.id]) {
if (taskGraph.tasks[dependency].parallelism === false) {
nonParallelContinuousTasksThatAreDependedOn.push(
taskGraph.tasks[dependency]
);
}
}
}

if (invalidTasks.length > 0) {
if (nonParallelTasksThatDependOnContinuousTasks.length > 0) {
throw new NonParallelTaskDependsOnContinuousTasksError(
invalidTasks,
nonParallelTasksThatDependOnContinuousTasks,
taskGraph
);
}
if (nonParallelContinuousTasksThatAreDependedOn.length > 0) {
throw new DependingOnNonParallelContinuousTaskError(
nonParallelContinuousTasksThatAreDependedOn,
taskGraph
);
}
Expand All @@ -193,6 +207,28 @@ class NonParallelTaskDependsOnContinuousTasksError extends Error {
}
}

class DependingOnNonParallelContinuousTaskError extends Error {
constructor(public invalidTasks: Task[], taskGraph: TaskGraph) {
let message =
'The following continuous tasks do not support parallelism but are depended on:';

for (const task of invalidTasks) {
const dependents = Object.keys(taskGraph.continuousDependencies).filter(
(parentTaskId) =>
taskGraph.continuousDependencies[parentTaskId].includes(task.id)
);

message += `\n - ${task.id} <- ${dependents.join(', ')}`;
}

message +=
'\nParallelism must be enabled for a continuous task if it is depended on, as the tasks that depend on it will run in parallel with it.';

super(message);
this.name = 'DependingOnNonParallelContinuousTaskError';
}
}

export function getLeafTasks(taskGraph: TaskGraph): Set<string> {
const reversed = reverseTaskGraph(taskGraph);
const leafTasks = new Set<string>();
Expand Down
Loading