fix(core): filter out task dependencies on itself (#28261)

<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

Tasks can end up depending on themselves if they depend on a dummy task
which ends up depending back on itself. This throws a weird error like:

```
 NX   Could not execute command because the task graph has a circular dependency

devkit:build --> devkit:build-base --> nx:build-base --> nx:build-base
```

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

Tasks cannot depend on themselves. No errors are thrown in those cases.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
This commit is contained in:
Jason Jean 2024-10-02 20:45:10 -04:00 committed by GitHub
parent 23a217d8dd
commit 8c59a7eb40
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 71 additions and 4 deletions

View File

@ -1490,6 +1490,71 @@ describe('createTaskGraph', () => {
});
});
it('should handle cycles where tasks seem to depend on themselves (lib1:build -> lib2 -> lib1:build)', () => {
projectGraph = {
nodes: {
lib1: {
name: 'lib1',
type: 'lib',
data: {
root: 'lib1-root',
targets: {
build: {
executor: 'nx:run-commands',
},
},
},
},
lib2: {
name: 'lib2',
type: 'lib',
data: {
root: 'lib2-root',
targets: {},
},
},
},
dependencies: {
lib1: [{ source: 'lib1', target: 'lib2', type: 'static' }],
lib2: [{ source: 'lib2', target: 'lib1', type: 'static' }],
},
};
const taskGraph = createTaskGraph(
projectGraph,
{
build: [{ target: 'build', dependencies: true }],
},
['lib1'],
['build'],
'development',
{
__overrides_unparsed__: [],
}
);
expect(taskGraph).toEqual({
roots: ['lib1:build'],
tasks: {
'lib1:build': expect.objectContaining({
id: 'lib1:build',
target: {
project: 'lib1',
target: 'build',
},
outputs: expect.arrayContaining([expect.any(String)]),
overrides: {
__overrides_unparsed__: [],
},
projectRoot: 'lib1-root',
parallelism: true,
}),
},
dependencies: {
'lib1:build': [],
},
});
});
it('should handle cycles between projects where all projects do not contain the same task target (lib1:build -> lib2:build -> lib3 -> lib4:build -> lib1:build)', () => {
projectGraph = {
nodes: {

View File

@ -85,10 +85,12 @@ export class ProcessTasks {
this.filterDummyTasks();
for (const projectName of Object.keys(this.dependencies)) {
if (this.dependencies[projectName].length > 1) {
this.dependencies[projectName] = [
...new Set(this.dependencies[projectName]).values(),
for (const taskId of Object.keys(this.dependencies)) {
if (this.dependencies[taskId].length > 0) {
this.dependencies[taskId] = [
...new Set(
this.dependencies[taskId].filter((d) => d !== taskId)
).values(),
];
}
}