Skip to content

Commit

Permalink
feat(core): add support for wildcards in dependsOn (#19611)
Browse files Browse the repository at this point in the history
Now it is possible to define targets like this:

```
{
  "targets": {
    "build-css": {},
    "build-js": {},
    "test": {
      "dependsOn": ["build-*"]
    },
  }
}
```

<!-- 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` -->

## Current Behavior
No support for wildcard target dependencies.

## Expected Behavior
This PR is an example of what I described here:
#19414.

## Related Issue(s)
Closes #19414

---------

Co-authored-by: Craigory Coppola <[email protected]>
(cherry picked from commit 3e0d2de)
  • Loading branch information
fxposter authored and FrozenPandaz committed Jul 8, 2024
1 parent 8462387 commit e2eda2c
Show file tree
Hide file tree
Showing 5 changed files with 286 additions and 80 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,19 @@ function shouldSkipInitialTargetRun(
project: string,
target: string
): boolean {
const allTargetNames = new Set<string>();
for (const projectName in projectGraph.nodes) {
const project = projectGraph.nodes[projectName];
for (const targetName in project.data.targets ?? {}) {
allTargetNames.add(targetName);
}
}

const projectDependencyConfigs = getDependencyConfigs(
{ project, target },
{},
projectGraph
projectGraph,
Array.from(allTargetNames)
);

// if the task runner already ran the target, skip the initial run
Expand Down
68 changes: 18 additions & 50 deletions packages/nx/src/tasks-runner/create-task-graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,27 @@ import {
import { Task, TaskGraph } from '../config/task-graph';
import { TargetDefaults, TargetDependencies } from '../config/nx-json';
import { TargetDependencyConfig } from '../devkit-exports';
import { findMatchingProjects } from '../utils/find-matching-projects';
import { output } from '../utils/output';

export class ProcessTasks {
private readonly seen = new Set<string>();
readonly tasks: { [id: string]: Task } = {};
readonly dependencies: { [k: string]: string[] } = {};
private readonly allTargetNames: string[];

constructor(
private readonly extraTargetDependencies: TargetDependencies,
private readonly projectGraph: ProjectGraph
) {}
) {
const allTargetNames = new Set<string>();
for (const projectName in projectGraph.nodes) {
const project = projectGraph.nodes[projectName];
for (const targetName in project.data.targets ?? {}) {
allTargetNames.add(targetName);
}
}
this.allTargetNames = Array.from(allTargetNames);
}

processTasks(
projectNames: string[],
Expand Down Expand Up @@ -100,48 +109,16 @@ export class ProcessTasks {
const dependencyConfigs = getDependencyConfigs(
{ project: task.target.project, target: task.target.target },
this.extraTargetDependencies,
this.projectGraph
this.projectGraph,
this.allTargetNames
);
for (const dependencyConfig of dependencyConfigs) {
const taskOverrides =
dependencyConfig.params === 'forward'
? overrides
: { __overrides_unparsed__: [] };
if (dependencyConfig.projects) {
/** LERNA SUPPORT START - Remove in v20 */
// Lerna uses `dependencies` in `prepNxOptions`, so we need to maintain
// support for it until lerna can be updated to use the syntax.
//
// This should have been removed in v17, but the updates to lerna had not
// been made yet.
//
// TODO(@agentender): Remove this part in v20
if (typeof dependencyConfig.projects === 'string') {
if (dependencyConfig.projects === 'self') {
this.processTasksForSingleProject(
task,
task.target.project,
dependencyConfig,
configuration,
taskOverrides,
overrides
);
continue;
} else if (dependencyConfig.projects === 'dependencies') {
this.processTasksForDependencies(
projectUsedToDeriveDependencies,
dependencyConfig,
configuration,
task,
taskOverrides,
overrides
);
continue;
}
}
/** LERNA SUPPORT END - Remove in v17 */

this.processTasksForMatchingProjects(
this.processTasksForMultipleProjects(
dependencyConfig,
configuration,
task,
Expand Down Expand Up @@ -170,31 +147,22 @@ export class ProcessTasks {
}
}

private processTasksForMatchingProjects(
private processTasksForMultipleProjects(
dependencyConfig: TargetDependencyConfig,
configuration: string,
task: Task,
taskOverrides: Object | { __overrides_unparsed__: any[] },
overrides: Object
) {
const targetProjectSpecifiers =
typeof dependencyConfig.projects === 'string'
? [dependencyConfig.projects]
: dependencyConfig.projects;
const matchingProjects = findMatchingProjects(
targetProjectSpecifiers,
this.projectGraph.nodes
);

if (matchingProjects.length === 0) {
if (dependencyConfig.projects.length === 0) {
output.warn({
title: `\`dependsOn\` is misconfigured for ${task.target.project}:${task.target.target}`,
bodyLines: [
`Project patterns "${targetProjectSpecifiers}" does not match any projects.`,
`Project patterns "${dependencyConfig.projects}" does not match any projects.`,
],
});
}
for (const projectName of matchingProjects) {
for (const projectName of dependencyConfig.projects) {
this.processTasksForSingleProject(
task,
projectName,
Expand Down
110 changes: 110 additions & 0 deletions packages/nx/src/tasks-runner/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
expandDependencyConfigSyntaxSugar,
getDependencyConfigs,
getOutputsForTargetAndConfiguration,
interpolate,
transformLegacyOutputs,
Expand Down Expand Up @@ -408,6 +409,7 @@ describe('utils', () => {
const result = transformLegacyOutputs('myapp', e);
expect(result).toEqual(['{workspaceRoot}/dist']);
}
expect.assertions(1);
});

it('should prefix unix-absolute paths with {workspaceRoot}', () => {
Expand All @@ -418,6 +420,7 @@ describe('utils', () => {
const result = transformLegacyOutputs('myapp', e);
expect(result).toEqual(['{workspaceRoot}/dist']);
}
expect.assertions(1);
});
});

Expand All @@ -429,6 +432,7 @@ describe('utils', () => {
const result = transformLegacyOutputs('myapp', e);
expect(result).toEqual(['{workspaceRoot}/dist']);
}
expect.assertions(1);
});

it('should prefix paths within the project with {projectRoot}', () => {
Expand All @@ -439,6 +443,7 @@ describe('utils', () => {
const result = transformLegacyOutputs('myapp', e);
expect(result).toEqual(['{projectRoot}/dist']);
}
expect.assertions(1);
});

describe('expandDependencyConfigSyntaxSugar', () => {
Expand Down Expand Up @@ -509,6 +514,111 @@ describe('utils', () => {
target: 'target:with:colons',
});
});

it('supports wildcards in targets', () => {
const result = getDependencyConfigs(
{ project: 'project', target: 'build' },
{},
{
dependencies: {},
nodes: {
project: {
name: 'project',
type: 'app',
data: {
root: 'libs/project',
targets: {
build: {
dependsOn: ['build-*'],
},
'build-css': {},
'build-js': {},
'then-build-something-else': {},
},
},
},
},
},
['build', 'build-css', 'build-js', 'then-build-something-else']
);
expect(result).toEqual([
{
target: 'build-css',
projects: ['project'],
},
{
target: 'build-js',
projects: ['project'],
},
]);
});

it('should support wildcards with dependencies', () => {
const result = getDependencyConfigs(
{ project: 'project', target: 'build' },
{},
{
dependencies: {},
nodes: {
project: {
name: 'project',
type: 'app',
data: {
root: 'libs/project',
targets: {
build: {
dependsOn: ['^build-*'],
},
'then-build-something-else': {},
},
},
},
dep1: {
name: 'dep1',
type: 'lib',
data: {
root: 'libs/dep1',
targets: {
'build-css': {},
'build-js': {},
},
},
},
dep2: {
name: 'dep2',
type: 'lib',
data: {
root: 'libs/dep2',
targets: {
'build-python': {},
},
},
},
},
},
[
'build',
'build-css',
'build-js',
'then-build-something-else',
'build-python',
]
);
expect(result).toEqual([
{
target: 'build-css',
dependencies: true,
},
{
target: 'build-js',
dependencies: true,
},
{
target: 'build-python',
dependencies: true,
},
]);
});
});

describe('validateOutputs', () => {
Expand Down
Loading

0 comments on commit e2eda2c

Please sign in to comment.