diff --git a/docs/generated/devkit/TargetConfiguration.md b/docs/generated/devkit/TargetConfiguration.md index ef01902f857fc..ac5ef2b463a36 100644 --- a/docs/generated/devkit/TargetConfiguration.md +++ b/docs/generated/devkit/TargetConfiguration.md @@ -22,6 +22,7 @@ Target's configuration - [metadata](../../devkit/documents/TargetConfiguration#metadata): TargetMetadata - [options](../../devkit/documents/TargetConfiguration#options): T - [outputs](../../devkit/documents/TargetConfiguration#outputs): string[] +- [parallelism](../../devkit/documents/TargetConfiguration#parallelism): boolean ## Properties @@ -109,3 +110,12 @@ Target's options. They are passed in to the executor. List of the target's outputs. The outputs will be cached by the Nx computation caching engine. + +--- + +### parallelism + +• `Optional` **parallelism**: `boolean` + +Whether this target can be run in parallel with other tasks +Default is true diff --git a/docs/generated/devkit/Task.md b/docs/generated/devkit/Task.md index 686bac2a5e6f2..10f6d600f80f1 100644 --- a/docs/generated/devkit/Task.md +++ b/docs/generated/devkit/Task.md @@ -13,6 +13,7 @@ A representation of the invocation of an Executor - [id](../../devkit/documents/Task#id): string - [outputs](../../devkit/documents/Task#outputs): string[] - [overrides](../../devkit/documents/Task#overrides): any +- [parallelism](../../devkit/documents/Task#parallelism): boolean - [projectRoot](../../devkit/documents/Task#projectroot): string - [startTime](../../devkit/documents/Task#starttime): number - [target](../../devkit/documents/Task#target): Object @@ -84,6 +85,14 @@ Overrides for the configured options of the target --- +### parallelism + +• **parallelism**: `boolean` + +Determines if a given task should be parallelizable. + +--- + ### projectRoot • `Optional` **projectRoot**: `string` diff --git a/packages/cypress/src/plugins/plugin.spec.ts b/packages/cypress/src/plugins/plugin.spec.ts index aced265f5ef53..6393344224965 100644 --- a/packages/cypress/src/plugins/plugin.spec.ts +++ b/packages/cypress/src/plugins/plugin.spec.ts @@ -121,6 +121,7 @@ describe('@nx/cypress/plugin', () => { "{projectRoot}/dist/videos", "{projectRoot}/dist/screenshots", ], + "parallelism": false, }, "open-cypress": { "command": "cypress open", @@ -329,6 +330,7 @@ describe('@nx/cypress/plugin', () => { "{projectRoot}/dist/videos", "{projectRoot}/dist/screenshots", ], + "parallelism": false, }, "e2e-ci": { "cache": true, @@ -369,6 +371,7 @@ describe('@nx/cypress/plugin', () => { "{projectRoot}/dist/videos", "{projectRoot}/dist/screenshots", ], + "parallelism": false, }, "e2e-ci--src/test.cy.ts": { "cache": true, @@ -404,6 +407,7 @@ describe('@nx/cypress/plugin', () => { "{projectRoot}/dist/videos", "{projectRoot}/dist/screenshots", ], + "parallelism": false, }, "open-cypress": { "command": "cypress open", diff --git a/packages/cypress/src/plugins/plugin.ts b/packages/cypress/src/plugins/plugin.ts index d43489cf500f6..15a094bbaf3b5 100644 --- a/packages/cypress/src/plugins/plugin.ts +++ b/packages/cypress/src/plugins/plugin.ts @@ -211,6 +211,7 @@ async function buildCypressTargets( cache: true, inputs: getInputs(namedInputs), outputs: getOutputs(projectRoot, cypressConfig, 'e2e'), + parallelism: false, metadata: { technologies: ['cypress'], description: 'Runs Cypress Tests', @@ -276,6 +277,7 @@ async function buildCypressTargets( options: { cwd: projectRoot, }, + parallelism: false, metadata: { technologies: ['cypress'], description: `Runs Cypress Tests in ${relativeSpecFilePath} in CI`, @@ -300,6 +302,7 @@ async function buildCypressTargets( inputs, outputs, dependsOn, + parallelism: false, metadata: { technologies: ['cypress'], description: 'Runs Cypress Tests in CI', diff --git a/packages/js/src/utils/buildable-libs-utils.spec.ts b/packages/js/src/utils/buildable-libs-utils.spec.ts index bf987d362344e..84f9f331f533a 100644 --- a/packages/js/src/utils/buildable-libs-utils.spec.ts +++ b/packages/js/src/utils/buildable-libs-utils.spec.ts @@ -389,30 +389,35 @@ describe('calculateDependenciesFromTaskGraph', () => { overrides: {}, target: { project: 'lib1', target: 'build' }, outputs: [], + parallelism: true, }, 'lib2:build': { id: 'lib2:build', overrides: {}, target: { project: 'lib2', target: 'build' }, outputs: [], + parallelism: true, }, 'lib2:build-base': { id: 'lib2:build-base', overrides: {}, target: { project: 'lib2', target: 'build-base' }, outputs: [], + parallelism: true, }, 'lib3:build': { id: 'lib3:build', overrides: {}, target: { project: 'lib3', target: 'build' }, outputs: [], + parallelism: true, }, 'lib4:build': { id: 'lib4:build', overrides: {}, target: { project: 'lib4', target: 'build' }, outputs: [], + parallelism: true, }, }, }; @@ -559,48 +564,56 @@ describe('calculateDependenciesFromTaskGraph', () => { overrides: {}, target: { project: 'lib1', target: 'build' }, outputs: [], + parallelism: true, }, 'lib1:build-base': { id: 'lib1:build-base', overrides: {}, target: { project: 'lib1', target: 'build-base' }, outputs: [], + parallelism: true, }, 'lib2:build': { id: 'lib2:build', overrides: {}, target: { project: 'lib2', target: 'build' }, outputs: [], + parallelism: true, }, 'lib2:build-base': { id: 'lib2:build-base', overrides: {}, target: { project: 'lib2', target: 'build-base' }, outputs: [], + parallelism: true, }, 'lib3:build': { id: 'lib3:build', overrides: {}, target: { project: 'lib3', target: 'build' }, outputs: [], + parallelism: true, }, 'lib3:build-base': { id: 'lib3:build-base', overrides: {}, target: { project: 'lib3', target: 'build-base' }, outputs: [], + parallelism: true, }, 'lib4:build': { id: 'lib4:build', overrides: {}, target: { project: 'lib4', target: 'build' }, outputs: [], + parallelism: true, }, 'lib4:build-base': { id: 'lib4:build-base', overrides: {}, target: { project: 'lib4', target: 'build-base' }, outputs: [], + parallelism: true, }, }, }; diff --git a/packages/nx/schemas/project-schema.json b/packages/nx/schemas/project-schema.json index 969ddbf126dd2..cb8fa2b220b5f 100644 --- a/packages/nx/schemas/project-schema.json +++ b/packages/nx/schemas/project-schema.json @@ -4,6 +4,27 @@ "title": "JSON schema for Nx projects", "type": "object", "properties": { + "name": { + "type": "string", + "description": "Project's name. Optional if specified in workspace.json" + }, + "root": { + "type": "string", + "description": "Project's location relative to the root of the workspace" + }, + "sourceRoot": { + "type": "string", + "description": "The location of project's sources relative to the root of the workspace" + }, + "projectType": { + "type": "string", + "description": "Type of project supported", + "enum": ["library", "application"] + }, + "generators": { + "type": "object", + "description": "List of default values used by generators" + }, "namedInputs": { "type": "object", "description": "Named inputs used by inputs defined in targets", @@ -112,6 +133,11 @@ "cache": { "type": "boolean", "description": "Specifies if the given target should be cacheable" + }, + "parallelism": { + "type": "boolean", + "default": true, + "description": "Whether this target can be run in parallel with other tasks" } } } diff --git a/packages/nx/src/config/task-graph.ts b/packages/nx/src/config/task-graph.ts index 573f691ccc220..9829af409e10f 100644 --- a/packages/nx/src/config/task-graph.ts +++ b/packages/nx/src/config/task-graph.ts @@ -76,6 +76,11 @@ export interface Task { * Determines if a given task should be cacheable. */ cache?: boolean; + + /** + * Determines if a given task should be parallelizable. + */ + parallelism: boolean; } /** diff --git a/packages/nx/src/config/to-project-name.spec.ts b/packages/nx/src/config/to-project-name.spec.ts index 208997128a118..0815e9237b3d4 100644 --- a/packages/nx/src/config/to-project-name.spec.ts +++ b/packages/nx/src/config/to-project-name.spec.ts @@ -73,6 +73,7 @@ describe('Workspaces', () => { ], "executor": "@nx/js:release-publish", "options": {}, + "parallelism": true, }, }, } diff --git a/packages/nx/src/config/workspace-json-project-json.ts b/packages/nx/src/config/workspace-json-project-json.ts index 4d7b1e4ef4fd0..eff58d64c4870 100644 --- a/packages/nx/src/config/workspace-json-project-json.ts +++ b/packages/nx/src/config/workspace-json-project-json.ts @@ -230,4 +230,10 @@ export interface TargetConfiguration { * Metadata about the target */ metadata?: TargetMetadata; + + /** + * Whether this target can be run in parallel with other tasks + * Default is true + */ + parallelism?: boolean; } diff --git a/packages/nx/src/hasher/native-task-hasher-impl.spec.ts b/packages/nx/src/hasher/native-task-hasher-impl.spec.ts index 4a9d7923130c6..30db683af3de9 100644 --- a/packages/nx/src/hasher/native-task-hasher-impl.spec.ts +++ b/packages/nx/src/hasher/native-task-hasher-impl.spec.ts @@ -155,21 +155,21 @@ describe('native task hasher', () => { "AllExternalDependencies": "3244421341483603138", "env:NONEXISTENTENV": "3244421341483603138", "env:TESTENV": "11441948532827618368", - "parent:ProjectConfiguration": "4131510303084753861", + "parent:ProjectConfiguration": "3608670998275221195", "parent:TsConfig": "2264969541778889434", "parent:{projectRoot}/**/*": "15295586939211629225", "runtime:echo runtime123": "29846575039086708", - "tagged:ProjectConfiguration": "1604492097835699503", + "tagged:ProjectConfiguration": "8596726088057301092", "tagged:TsConfig": "2264969541778889434", "tagged:{projectRoot}/**/*": "112200405683630828", - "unrelated:ProjectConfiguration": "439515135357674343", + "unrelated:ProjectConfiguration": "11133337791644294114", "unrelated:TsConfig": "2264969541778889434", "unrelated:{projectRoot}/**/*": "10505120368757496776", "{workspaceRoot}/.gitignore": "3244421341483603138", "{workspaceRoot}/.nxignore": "3244421341483603138", "{workspaceRoot}/nx.json": "5219582320960288192", }, - "value": "6332317845632665670", + "value": "17193008237392864712", }, ] `); @@ -226,17 +226,17 @@ describe('native task hasher', () => { { "details": { "AllExternalDependencies": "3244421341483603138", - "child:ProjectConfiguration": "7051130583729928229", + "child:ProjectConfiguration": "710102491746666394", "child:TsConfig": "2264969541778889434", "child:{projectRoot}/**/*": "7694964870822928111", - "parent:ProjectConfiguration": "7704699416930647320", + "parent:ProjectConfiguration": "8031122597231773116", "parent:TsConfig": "2264969541778889434", "parent:{projectRoot}/**/*": "15295586939211629225", "{workspaceRoot}/.gitignore": "3244421341483603138", "{workspaceRoot}/.nxignore": "3244421341483603138", "{workspaceRoot}/nx.json": "5219582320960288192", }, - "value": "18412450685244791672", + "value": "4141725338792606519", } `); }); @@ -307,17 +307,17 @@ describe('native task hasher', () => { { "details": { "AllExternalDependencies": "3244421341483603138", - "child:ProjectConfiguration": "2562552455862160288", + "child:ProjectConfiguration": "13051054958929525761", "child:TsConfig": "2264969541778889434", "child:{projectRoot}/**/*": "7694964870822928111", "parent:!{projectRoot}/**/*.spec.ts": "7663204892242899157", - "parent:ProjectConfiguration": "4131510303084753861", + "parent:ProjectConfiguration": "3608670998275221195", "parent:TsConfig": "2264969541778889434", "{workspaceRoot}/.gitignore": "3244421341483603138", "{workspaceRoot}/.nxignore": "3244421341483603138", "{workspaceRoot}/nx.json": "4641558175996703359", }, - "value": "5825507912633865657", + "value": "12061654175538209437", } `); }); @@ -379,25 +379,25 @@ describe('native task hasher', () => { "details": { "AllExternalDependencies": "3244421341483603138", "parent:!{projectRoot}/**/*.spec.ts": "7663204892242899157", - "parent:ProjectConfiguration": "8008830016795210968", + "parent:ProjectConfiguration": "16402137858974842465", "parent:TsConfig": "2264969541778889434", "{workspaceRoot}/.gitignore": "3244421341483603138", "{workspaceRoot}/.nxignore": "3244421341483603138", "{workspaceRoot}/nx.json": "4641558175996703359", }, - "value": "16919987205625802616", + "value": "1683972350273460485", }, { "details": { "AllExternalDependencies": "3244421341483603138", - "parent:ProjectConfiguration": "8008830016795210968", + "parent:ProjectConfiguration": "16402137858974842465", "parent:TsConfig": "2264969541778889434", "parent:{projectRoot}/**/*": "15295586939211629225", "{workspaceRoot}/.gitignore": "3244421341483603138", "{workspaceRoot}/.nxignore": "3244421341483603138", "{workspaceRoot}/nx.json": "4641558175996703359", }, - "value": "2732213649703581334", + "value": "2469956415584213984", }, ] `); @@ -478,10 +478,10 @@ describe('native task hasher', () => { "details": { "AllExternalDependencies": "3244421341483603138", "child:!{projectRoot}/**/*.spec.ts": "13790135045935437026", - "child:ProjectConfiguration": "11541456798478268276", + "child:ProjectConfiguration": "10085593111011845427", "child:TsConfig": "2264969541778889434", "env:MY_TEST_HASH_ENV": "17357374746554314488", - "parent:ProjectConfiguration": "2287392686890337925", + "parent:ProjectConfiguration": "14398811678394411425", "parent:TsConfig": "2264969541778889434", "parent:{projectRoot}/**/*": "15295586939211629225", "{workspaceRoot}/.gitignore": "3244421341483603138", @@ -490,7 +490,7 @@ describe('native task hasher', () => { "{workspaceRoot}/global2": "13625885481717016690", "{workspaceRoot}/nx.json": "10897751101872977225", }, - "value": "1217581064022758580", + "value": "12563443797830627612", }, ] `); @@ -539,14 +539,14 @@ describe('native task hasher', () => { { "details": { "AllExternalDependencies": "3244421341483603138", - "parent:ProjectConfiguration": "4131510303084753861", + "parent:ProjectConfiguration": "3608670998275221195", "parent:TsConfig": "8661678577354855152", "parent:{projectRoot}/**/*": "15295586939211629225", "{workspaceRoot}/.gitignore": "3244421341483603138", "{workspaceRoot}/.nxignore": "3244421341483603138", "{workspaceRoot}/nx.json": "5219582320960288192", }, - "value": "9574395623667735815", + "value": "192468752006013407", } `); }); @@ -616,17 +616,17 @@ describe('native task hasher', () => { { "details": { "AllExternalDependencies": "3244421341483603138", - "child:ProjectConfiguration": "3898391056798628885", + "child:ProjectConfiguration": "13748859057138736105", "child:TsConfig": "2264969541778889434", "child:{projectRoot}/**/*": "7694964870822928111", - "parent:ProjectConfiguration": "4131510303084753861", + "parent:ProjectConfiguration": "3608670998275221195", "parent:TsConfig": "2264969541778889434", "parent:{projectRoot}/**/*": "15295586939211629225", "{workspaceRoot}/.gitignore": "3244421341483603138", "{workspaceRoot}/.nxignore": "3244421341483603138", "{workspaceRoot}/nx.json": "5219582320960288192", }, - "value": "3140483997697830788", + "value": "571545311225175014", } `); @@ -640,17 +640,17 @@ describe('native task hasher', () => { { "details": { "AllExternalDependencies": "3244421341483603138", - "child:ProjectConfiguration": "3898391056798628885", + "child:ProjectConfiguration": "13748859057138736105", "child:TsConfig": "2264969541778889434", "child:{projectRoot}/**/*": "7694964870822928111", - "parent:ProjectConfiguration": "4131510303084753861", + "parent:ProjectConfiguration": "3608670998275221195", "parent:TsConfig": "2264969541778889434", "parent:{projectRoot}/**/*": "15295586939211629225", "{workspaceRoot}/.gitignore": "3244421341483603138", "{workspaceRoot}/.nxignore": "3244421341483603138", "{workspaceRoot}/nx.json": "5219582320960288192", }, - "value": "3140483997697830788", + "value": "571545311225175014", } `); }); diff --git a/packages/nx/src/hasher/task-hasher.spec.ts b/packages/nx/src/hasher/task-hasher.spec.ts index 29fa359bb6bd1..45b695515babc 100644 --- a/packages/nx/src/hasher/task-hasher.spec.ts +++ b/packages/nx/src/hasher/task-hasher.spec.ts @@ -121,6 +121,7 @@ describe('TaskHasher', () => { id: 'parent-build', overrides: { prop: 'prop-value' }, outputs: [], + parallelism: true, }, { roots: ['parent-build'], @@ -130,6 +131,7 @@ describe('TaskHasher', () => { target: { project: 'parent', target: 'build' }, overrides: {}, outputs: [], + parallelism: true, }, }, dependencies: {}, @@ -188,6 +190,7 @@ describe('TaskHasher', () => { id: 'parent-build', overrides: { prop: 'prop-value' }, outputs: [], + parallelism: true, }, { roots: ['child-build'], @@ -197,12 +200,14 @@ describe('TaskHasher', () => { target: { project: 'parent', target: 'build' }, overrides: {}, outputs: [], + parallelism: true, }, 'child-build': { id: 'child-build', target: { project: 'child', target: 'build' }, overrides: {}, outputs: [], + parallelism: true, }, }, dependencies: { @@ -275,6 +280,7 @@ describe('TaskHasher', () => { id: 'parent-build', overrides: { prop: 'prop-value' }, outputs: [], + parallelism: true, }, { roots: ['child-build'], @@ -284,12 +290,14 @@ describe('TaskHasher', () => { target: { project: 'parent', target: 'build' }, overrides: {}, outputs: [], + parallelism: true, }, 'child-build': { id: 'child-build', target: { project: 'child', target: 'build' }, overrides: {}, outputs: [], + parallelism: true, }, }, dependencies: { @@ -354,6 +362,7 @@ describe('TaskHasher', () => { target: { project: 'parent', target: 'test' }, overrides: {}, outputs: [], + parallelism: true, }, }, dependencies: {}, @@ -365,6 +374,7 @@ describe('TaskHasher', () => { id: 'parent-test', overrides: { prop: 'prop-value' }, outputs: [], + parallelism: true, }, taskGraph, {} @@ -378,6 +388,7 @@ describe('TaskHasher', () => { id: 'parent-build', overrides: { prop: 'prop-value' }, outputs: [], + parallelism: true, }, taskGraph, {} @@ -459,12 +470,14 @@ describe('TaskHasher', () => { target: { project: 'parent', target: 'test' }, overrides: {}, outputs: [], + parallelism: true, }, 'child-test': { id: 'child-test', target: { project: 'child', target: 'test' }, overrides: {}, outputs: [], + parallelism: true, }, }, dependencies: { @@ -478,6 +491,7 @@ describe('TaskHasher', () => { id: 'parent-test', overrides: { prop: 'prop-value' }, outputs: [], + parallelism: true, }, taskGraph, { MY_TEST_HASH_ENV: 'MY_TEST_HASH_ENV_VALUE' } @@ -491,6 +505,7 @@ describe('TaskHasher', () => { id: 'child-test', overrides: { prop: 'prop-value' }, outputs: [], + parallelism: true, }, taskGraph, { MY_TEST_HASH_ENV: 'MY_TEST_HASH_ENV_VALUE' } @@ -559,6 +574,7 @@ describe('TaskHasher', () => { id: 'parent-build', overrides: { prop: 'prop-value' }, outputs: [], + parallelism: true, }, { roots: ['child-build'], @@ -568,12 +584,14 @@ describe('TaskHasher', () => { target: { project: 'parent', target: 'build' }, overrides: {}, outputs: [], + parallelism: true, }, 'child-build': { id: 'child-build', target: { project: 'child', target: 'build' }, overrides: {}, outputs: [], + parallelism: true, }, }, dependencies: { @@ -622,6 +640,7 @@ describe('TaskHasher', () => { id: 'parent-build', overrides: { prop: 'prop-value' }, outputs: [], + parallelism: true, }, { roots: ['parent:build'], @@ -631,6 +650,7 @@ describe('TaskHasher', () => { target: { project: 'parent', target: 'build' }, overrides: {}, outputs: [], + parallelism: true, }, }, dependencies: {}, @@ -687,12 +707,14 @@ describe('TaskHasher', () => { target: { project: 'parent', target: 'build' }, overrides: {}, outputs: [], + parallelism: true, }, 'child-build': { id: 'child-build', target: { project: 'child', target: 'build' }, overrides: {}, outputs: [], + parallelism: true, }, }, dependencies: { @@ -706,6 +728,7 @@ describe('TaskHasher', () => { id: 'parent-build', overrides: { prop: 'prop-value' }, outputs: [], + parallelism: true, }, taskGraph, {} @@ -719,6 +742,7 @@ describe('TaskHasher', () => { id: 'child-build', overrides: { prop: 'prop-value' }, outputs: [], + parallelism: true, }, taskGraph, {} @@ -763,6 +787,7 @@ describe('TaskHasher', () => { id: 'parent-build', overrides: {}, outputs: [], + parallelism: true, }, { roots: ['parent:build'], @@ -772,6 +797,7 @@ describe('TaskHasher', () => { target: { project: 'parent', target: 'build' }, overrides: {}, outputs: [], + parallelism: true, }, }, dependencies: {}, @@ -834,6 +860,7 @@ describe('TaskHasher', () => { id: 'app-build', overrides: { prop: 'prop-value' }, outputs: [], + parallelism: true, }, { roots: ['app-build'], @@ -843,6 +870,7 @@ describe('TaskHasher', () => { target: { project: 'app', target: 'build' }, overrides: {}, outputs: [], + parallelism: true, }, }, dependencies: {}, @@ -902,6 +930,7 @@ describe('TaskHasher', () => { id: 'app-build', overrides: { prop: 'prop-value' }, outputs: [], + parallelism: true, }, { roots: ['app-build'], @@ -911,6 +940,7 @@ describe('TaskHasher', () => { target: { project: 'app', target: 'build' }, overrides: {}, outputs: [], + parallelism: true, }, }, dependencies: {}, @@ -961,6 +991,7 @@ describe('TaskHasher', () => { id: 'app-build', overrides: { prop: 'prop-value' }, outputs: [], + parallelism: true, }, { roots: ['app-build'], @@ -970,6 +1001,7 @@ describe('TaskHasher', () => { target: { project: 'app', target: 'build' }, overrides: {}, outputs: [], + parallelism: true, }, }, dependencies: {}, @@ -1051,12 +1083,14 @@ describe('TaskHasher', () => { target: { project: 'a', target: 'build' }, overrides: {}, outputs: [], + parallelism: true, }, 'b-build': { id: 'b-build', target: { project: 'b', target: 'build' }, overrides: {}, outputs: [], + parallelism: true, }, }, dependencies: {}, @@ -1071,6 +1105,7 @@ describe('TaskHasher', () => { target: { project: 'a', target: 'build' }, overrides: {}, outputs: [], + parallelism: true, }, taskGraph, {} @@ -1081,6 +1116,7 @@ describe('TaskHasher', () => { target: { project: 'b', target: 'build' }, overrides: {}, outputs: [], + parallelism: true, }, taskGraph, {} @@ -1092,6 +1128,7 @@ describe('TaskHasher', () => { target: { project: 'b', target: 'build' }, overrides: {}, outputs: [], + parallelism: true, }, taskGraph, {} @@ -1102,6 +1139,7 @@ describe('TaskHasher', () => { target: { project: 'a', target: 'build' }, overrides: {}, outputs: [], + parallelism: true, }, taskGraph, {} @@ -1201,6 +1239,7 @@ describe('TaskHasher', () => { id: 'app-build', overrides: { prop: 'prop-value' }, outputs: [], + parallelism: true, }, { roots: ['app-build'], @@ -1210,6 +1249,7 @@ describe('TaskHasher', () => { target: { project: 'app', target: 'build' }, overrides: {}, outputs: [], + parallelism: true, }, }, dependencies: {}, @@ -1418,6 +1458,7 @@ describe('TaskHasher', () => { id: 'app-build', overrides: { prop: 'prop-value' }, outputs: [], + parallelism: true, }, { roots: ['app-build'], @@ -1427,6 +1468,7 @@ describe('TaskHasher', () => { target: { project: 'app', target: 'build' }, overrides: {}, outputs: [], + parallelism: true, }, }, dependencies: {}, @@ -1502,6 +1544,7 @@ describe('TaskHasher', () => { id: 'app-build', overrides: { prop: 'prop-value' }, outputs: [], + parallelism: true, }, { roots: ['app-build'], @@ -1511,6 +1554,7 @@ describe('TaskHasher', () => { target: { project: 'app', target: 'build' }, overrides: {}, outputs: [], + parallelism: true, }, }, dependencies: {}, @@ -1584,6 +1628,7 @@ describe('TaskHasher', () => { id: 'app-build', overrides: { prop: 'prop-value' }, outputs: [], + parallelism: true, }, { roots: ['app-build'], @@ -1593,6 +1638,7 @@ describe('TaskHasher', () => { target: { project: 'app', target: 'build' }, overrides: {}, outputs: [], + parallelism: true, }, }, dependencies: {}, @@ -1710,6 +1756,7 @@ describe('TaskHasher', () => { id: 'parent-build', overrides: { prop: 'prop-value' }, outputs: [], + parallelism: true, }, { roots: ['grandchild-build'], @@ -1719,18 +1766,21 @@ describe('TaskHasher', () => { target: { project: 'parent', target: 'build' }, overrides: {}, outputs: ['dist/libs/libs/parent'], + parallelism: true, }, 'child-build': { id: 'child-build', target: { project: 'child', target: 'build' }, overrides: {}, outputs: ['dist/libs/libs/child'], + parallelism: true, }, 'grandchild-build': { id: 'grandchild-build', target: { project: 'grandchild', target: 'build' }, overrides: {}, outputs: ['dist/libs/libs/grandchild'], + parallelism: true, }, }, dependencies: { @@ -1850,6 +1900,7 @@ describe('TaskHasher', () => { id: 'parent-build', overrides: { prop: 'prop-value' }, outputs: [], + parallelism: true, }, { roots: ['grandchild-build'], @@ -1859,18 +1910,21 @@ describe('TaskHasher', () => { target: { project: 'parent', target: 'build' }, overrides: {}, outputs: [], + parallelism: true, }, 'child-build': { id: 'child-build', target: { project: 'child', target: 'build' }, overrides: {}, outputs: [], + parallelism: true, }, 'grandchild-build': { id: 'grandchild-build', target: { project: 'grandchild', target: 'build' }, overrides: {}, outputs: [], + parallelism: true, }, }, dependencies: { diff --git a/packages/nx/src/native/index.d.ts b/packages/nx/src/native/index.d.ts index 405dd176c1f9e..828f54fb9c8fd 100644 --- a/packages/nx/src/native/index.d.ts +++ b/packages/nx/src/native/index.d.ts @@ -185,6 +185,7 @@ export interface Target { outputs?: Array options?: string configurations?: string + parallelism?: boolean } export interface Task { diff --git a/packages/nx/src/native/project_graph/types.rs b/packages/nx/src/native/project_graph/types.rs index 8eb4171bc0cc3..99c6c07764b81 100644 --- a/packages/nx/src/native/project_graph/types.rs +++ b/packages/nx/src/native/project_graph/types.rs @@ -16,6 +16,7 @@ pub struct Target { pub outputs: Option>, pub options: Option, pub configurations: Option, + pub parallelism: Option, } #[napi(object)] diff --git a/packages/nx/src/native/tasks/hashers/hash_project_config.rs b/packages/nx/src/native/tasks/hashers/hash_project_config.rs index a45a75e10c12a..4ea79d4209967 100644 --- a/packages/nx/src/native/tasks/hashers/hash_project_config.rs +++ b/packages/nx/src/native/tasks/hashers/hash_project_config.rs @@ -21,12 +21,13 @@ pub fn hash_project_config( .sorted_by(|a, b| a.0.cmp(b.0)) .map(|(k, v)| { format!( - "{}{}{}{}{}", + "{}{}{}{}{}{}", k, v.executor.as_deref().unwrap_or_default(), v.outputs.as_deref().unwrap_or_default().concat(), v.options.as_deref().unwrap_or_default(), v.configurations.as_deref().unwrap_or_default(), + v.parallelism.unwrap_or_default() ) }) .collect::>() @@ -144,7 +145,7 @@ mod tests { assert_eq!(nx_project_hash.unwrap(), "3244421341483603138"); let js_project_hash = hash_project_config("js", &projects).unwrap(); - assert_eq!(js_project_hash, "18342193044952101577"); + assert_eq!(js_project_hash, "13565578942842640362"); let js_unsorted = hash_project_config("js-unsorted", &projects); assert_eq!(js_unsorted.unwrap(), js_project_hash); diff --git a/packages/nx/src/native/transform-objects.ts b/packages/nx/src/native/transform-objects.ts index 923652bf067f5..f7ee35f2558e2 100644 --- a/packages/nx/src/native/transform-objects.ts +++ b/packages/nx/src/native/transform-objects.ts @@ -23,6 +23,7 @@ export function transformProjectGraphForRust( outputs: targetConfig.outputs, options: JSON.stringify(targetConfig.options), configurations: JSON.stringify(targetConfig.configurations), + parallelism: targetConfig.parallelism, }; } nodes[projectName] = { diff --git a/packages/nx/src/project-graph/utils/project-configuration-utils.spec.ts b/packages/nx/src/project-graph/utils/project-configuration-utils.spec.ts index c24db50ef4189..a722abf6fc521 100644 --- a/packages/nx/src/project-graph/utils/project-configuration-utils.spec.ts +++ b/packages/nx/src/project-graph/utils/project-configuration-utils.spec.ts @@ -1502,6 +1502,7 @@ describe('project-configuration-utils', () => { "options": { "command": "echo libs/project", }, + "parallelism": true, } `); }); @@ -1658,6 +1659,7 @@ describe('project-configuration-utils', () => { "options": { "command": "echo a @ libs/a", }, + "parallelism": true, } `); }); diff --git a/packages/nx/src/project-graph/utils/project-configuration-utils.ts b/packages/nx/src/project-graph/utils/project-configuration-utils.ts index e1a38c2c12eaf..9eeae9a695f35 100644 --- a/packages/nx/src/project-graph/utils/project-configuration-utils.ts +++ b/packages/nx/src/project-graph/utils/project-configuration-utils.ts @@ -1104,5 +1104,7 @@ export function normalizeTarget( ); } + target.parallelism ??= true; + return target; } diff --git a/packages/nx/src/tasks-runner/create-task-graph.spec.ts b/packages/nx/src/tasks-runner/create-task-graph.spec.ts index 9c96eee5740c3..878bb07b0789f 100644 --- a/packages/nx/src/tasks-runner/create-task-graph.spec.ts +++ b/packages/nx/src/tasks-runner/create-task-graph.spec.ts @@ -107,6 +107,7 @@ describe('createTaskGraph', () => { outputs: [], overrides: { a: 123 }, projectRoot: 'app1-root', + parallelism: true, }, }, dependencies: { @@ -137,6 +138,7 @@ describe('createTaskGraph', () => { outputs: [], overrides: { a: 123 }, projectRoot: 'app1-root', + parallelism: true, }, 'lib1:test': { id: 'lib1:test', @@ -147,6 +149,7 @@ describe('createTaskGraph', () => { outputs: [], overrides: { a: 123 }, projectRoot: 'lib1-root', + parallelism: true, }, }, dependencies: { @@ -286,6 +289,7 @@ describe('createTaskGraph', () => { outputs: [], overrides: {}, projectRoot: 'lib1-root', + parallelism: true, }, 'lib2:compile': { id: 'lib2:compile', @@ -298,6 +302,7 @@ describe('createTaskGraph', () => { __overrides_unparsed__: [], }, projectRoot: 'lib2-root', + parallelism: true, }, }, dependencies: { @@ -328,6 +333,7 @@ describe('createTaskGraph', () => { outputs: [], overrides: {}, projectRoot: 'app1-root', + parallelism: true, }, 'lib1:compile:libDefault': { id: 'lib1:compile:libDefault', @@ -341,6 +347,7 @@ describe('createTaskGraph', () => { __overrides_unparsed__: [], }, projectRoot: 'lib1-root', + parallelism: true, }, 'lib2:compile:ci': { id: 'lib2:compile:ci', @@ -354,6 +361,7 @@ describe('createTaskGraph', () => { __overrides_unparsed__: [], }, projectRoot: 'lib2-root', + parallelism: true, }, }, dependencies: { @@ -454,6 +462,7 @@ describe('createTaskGraph', () => { project: 'app1', target: 'compile', }, + parallelism: true, }, 'lib3:compile': { id: 'lib3:compile', @@ -466,6 +475,7 @@ describe('createTaskGraph', () => { project: 'lib3', target: 'compile', }, + parallelism: true, }, }, }); @@ -494,6 +504,7 @@ describe('createTaskGraph', () => { outputs: [], overrides: { a: '--value=app1-root' }, projectRoot: 'app1-root', + parallelism: true, }, }, dependencies: { @@ -525,6 +536,7 @@ describe('createTaskGraph', () => { outputs: [], overrides: { a: '--base-href=/app1-root${deploymentId}' }, projectRoot: 'app1-root', + parallelism: true, }, }, dependencies: { @@ -639,6 +651,7 @@ describe('createTaskGraph', () => { outputs: [], overrides: { myFlag: 'flag value' }, projectRoot: 'app1-root', + parallelism: true, }, 'app1:precompile': { id: 'app1:precompile', @@ -649,6 +662,7 @@ describe('createTaskGraph', () => { outputs: [], overrides: { myFlag: 'flag value' }, projectRoot: 'app1-root', + parallelism: true, }, 'lib1:compile': { id: 'lib1:compile', @@ -659,6 +673,7 @@ describe('createTaskGraph', () => { outputs: [], overrides: { myFlag: 'flag value' }, projectRoot: 'lib1-root', + parallelism: true, }, 'lib2:compile': { id: 'lib2:compile', @@ -669,6 +684,7 @@ describe('createTaskGraph', () => { outputs: [], overrides: { __overrides_unparsed__: [] }, projectRoot: 'lib2-root', + parallelism: true, }, }, dependencies: { @@ -706,6 +722,7 @@ describe('createTaskGraph', () => { __overrides_unparsed__: [], }, projectRoot: 'app1-root', + parallelism: true, }, 'app1:precompile': { id: 'app1:precompile', @@ -718,6 +735,7 @@ describe('createTaskGraph', () => { __overrides_unparsed__: [], }, projectRoot: 'app1-root', + parallelism: true, }, 'app1:precompile2': { id: 'app1:precompile2', @@ -730,6 +748,7 @@ describe('createTaskGraph', () => { __overrides_unparsed__: [], }, projectRoot: 'app1-root', + parallelism: true, }, 'lib1:compile': { id: 'lib1:compile', @@ -742,6 +761,7 @@ describe('createTaskGraph', () => { __overrides_unparsed__: [], }, projectRoot: 'lib1-root', + parallelism: true, }, }, dependencies: { @@ -779,6 +799,7 @@ describe('createTaskGraph', () => { __overrides_unparsed__: [], }, projectRoot: 'app1-root', + parallelism: true, }, 'app1:precompile': { id: 'app1:precompile', @@ -791,6 +812,7 @@ describe('createTaskGraph', () => { __overrides_unparsed__: [], }, projectRoot: 'app1-root', + parallelism: true, }, 'app1:precompile2': { id: 'app1:precompile2', @@ -803,6 +825,7 @@ describe('createTaskGraph', () => { __overrides_unparsed__: [], }, projectRoot: 'app1-root', + parallelism: true, }, 'lib1:compile': { id: 'lib1:compile', @@ -815,6 +838,7 @@ describe('createTaskGraph', () => { __overrides_unparsed__: [], }, projectRoot: 'lib1-root', + parallelism: true, }, }, dependencies: { @@ -921,6 +945,7 @@ describe('createTaskGraph', () => { __overrides_unparsed__: [], }, projectRoot: 'app1-root', + parallelism: true, }, 'lib1:compile': { id: 'lib1:compile', @@ -933,6 +958,7 @@ describe('createTaskGraph', () => { __overrides_unparsed__: [], }, projectRoot: 'lib1-root', + parallelism: true, }, 'lib2:compile': { id: 'lib2:compile', @@ -945,6 +971,7 @@ describe('createTaskGraph', () => { __overrides_unparsed__: [], }, projectRoot: 'lib2-root', + parallelism: true, }, 'lib3:compile': { id: 'lib3:compile', @@ -957,6 +984,7 @@ describe('createTaskGraph', () => { __overrides_unparsed__: [], }, projectRoot: 'lib3-root', + parallelism: true, }, }, dependencies: { @@ -1080,6 +1108,7 @@ describe('createTaskGraph', () => { projectRoot: 'infra1-root', outputs: [], overrides: { myFlag: 'flag value' }, + parallelism: true, }, 'app2:compile': { id: 'app2:compile', @@ -1087,6 +1116,7 @@ describe('createTaskGraph', () => { projectRoot: 'app2-root', outputs: [], overrides: { __overrides_unparsed__: [] }, + parallelism: true, }, 'coreInfra:apply': { id: 'coreInfra:apply', @@ -1094,6 +1124,7 @@ describe('createTaskGraph', () => { projectRoot: 'infra3-root', outputs: [], overrides: { myFlag: 'flag value' }, + parallelism: true, }, 'app1:compile': { id: 'app1:compile', @@ -1101,6 +1132,7 @@ describe('createTaskGraph', () => { projectRoot: 'app1-root', outputs: [], overrides: { __overrides_unparsed__: [] }, + parallelism: true, }, 'infra2:apply': { id: 'infra2:apply', @@ -1108,6 +1140,7 @@ describe('createTaskGraph', () => { projectRoot: 'infra2-root', outputs: [], overrides: { myFlag: 'flag value' }, + parallelism: true, }, }, dependencies: { @@ -1174,6 +1207,7 @@ describe('createTaskGraph', () => { __overrides_unparsed__: [], }, projectRoot: 'app1-root', + parallelism: true, }, 'app1:test': { id: 'app1:test', @@ -1186,6 +1220,7 @@ describe('createTaskGraph', () => { __overrides_unparsed__: [], }, projectRoot: 'app1-root', + parallelism: true, }, }, dependencies: { @@ -1267,6 +1302,7 @@ describe('createTaskGraph', () => { __overrides_unparsed__: [], }, projectRoot: 'app1-root', + parallelism: true, }, 'app3:compile': { id: 'app3:compile', @@ -1279,6 +1315,7 @@ describe('createTaskGraph', () => { __overrides_unparsed__: [], }, projectRoot: 'app3-root', + parallelism: true, }, }, dependencies: { @@ -1357,6 +1394,7 @@ describe('createTaskGraph', () => { __overrides_unparsed__: [], }, projectRoot: 'app1-root', + parallelism: true, }, 'app3:compile': { id: 'app3:compile', @@ -1369,6 +1407,7 @@ describe('createTaskGraph', () => { __overrides_unparsed__: [], }, projectRoot: 'app3-root', + parallelism: true, }, }, dependencies: { @@ -1445,6 +1484,7 @@ describe('createTaskGraph', () => { __overrides_unparsed__: [], }, projectRoot: 'app1-root', + parallelism: true, }, }, dependencies: { @@ -1478,6 +1518,7 @@ describe('createTaskGraph', () => { __overrides_unparsed__: [], }, projectRoot: 'app1-root', + parallelism: true, }, 'app2:compile': { id: 'app2:compile', @@ -1490,6 +1531,7 @@ describe('createTaskGraph', () => { __overrides_unparsed__: [], }, projectRoot: 'app2-root', + parallelism: true, }, }, dependencies: { diff --git a/packages/nx/src/tasks-runner/create-task-graph.ts b/packages/nx/src/tasks-runner/create-task-graph.ts index 8989476b4b584..909fc411beba1 100644 --- a/packages/nx/src/tasks-runner/create-task-graph.ts +++ b/packages/nx/src/tasks-runner/create-task-graph.ts @@ -35,7 +35,7 @@ export class ProcessTasks { configuration: string, overrides: Object, excludeTaskDependencies: boolean - ) { + ): string[] { for (const projectName of projectNames) { for (const target of targets) { const project = this.projectGraph.nodes[projectName]; @@ -319,6 +319,7 @@ export class ProcessTasks { interpolatedOverrides ), cache: project.data.targets[target].cache, + parallelism: project.data.targets[target].parallelism ?? true, }; } diff --git a/packages/nx/src/tasks-runner/tasks-schedule.spec.ts b/packages/nx/src/tasks-runner/tasks-schedule.spec.ts index d849fccdd21e8..0f85a6e562310 100644 --- a/packages/nx/src/tasks-runner/tasks-schedule.spec.ts +++ b/packages/nx/src/tasks-runner/tasks-schedule.spec.ts @@ -5,7 +5,7 @@ import { DependencyType, ProjectGraph } from '../config/project-graph'; import * as nxJsonUtils from '../config/nx-json'; import * as executorUtils from '../command-line/run/executor-utils'; -function createMockTask(id: string): Task { +function createMockTask(id: string, parallelism: boolean = true): Task { const [project, target] = id.split(':'); return { id, @@ -15,6 +15,7 @@ function createMockTask(id: string): Task { }, outputs: [], overrides: {}, + parallelism, }; } @@ -411,4 +412,367 @@ describe('TasksSchedule', () => { }); }); }); + + describe('tasks with parallelism false', () => { + describe('dependent tasks', () => { + let taskSchedule: TasksSchedule; + let taskGraph: TaskGraph; + let app1Build: Task; + let app2Build: Task; + let lib1Build: Task; + let lifeCycle: any; + beforeEach(() => { + // app1 depends on lib1 + // app2 does not depend on anything + // lib1 does not depend on anything + // all tasks have parallelism set to false + app1Build = createMockTask('app1:build', false); + app2Build = createMockTask('app2:build', false); + lib1Build = createMockTask('lib1:build', false); + + taskGraph = { + tasks: { + 'app1:build': app1Build, + 'app2:build': app2Build, + 'lib1:build': lib1Build, + }, + dependencies: { + 'app1:build': ['lib1:build'], + 'app2:build': [], + 'lib1:build': [], + }, + roots: ['lib1:build', 'app2:build'], + }; + jest.spyOn(nxJsonUtils, 'readNxJson').mockReturnValue({}); + jest.spyOn(executorUtils, 'getExecutorInformation').mockReturnValue({ + schema: { + version: 2, + properties: {}, + }, + implementationFactory: jest.fn(), + batchImplementationFactory: jest.fn(), + isNgCompat: true, + isNxExecutor: true, + }); + + const projectGraph: ProjectGraph = { + nodes: { + app1: { + data: { + root: 'app1', + targets: { + build: { + executor: 'awesome-executors:build', + }, + }, + }, + name: 'app1', + type: 'app', + }, + app2: { + name: 'app2', + type: 'app', + data: { + root: 'app2', + targets: { + build: { + executor: 'awesome-executors:app2-build', + }, + }, + }, + }, + lib1: { + name: 'lib1', + type: 'lib', + data: { + root: 'lib1', + targets: { + build: { + executor: 'awesome-executors:build', + }, + }, + }, + }, + } as any, + dependencies: { + app1: [ + { + source: 'app1', + target: 'lib1', + type: DependencyType.static, + }, + ], + app2: [ + { + source: 'app2', + target: 'lib1', + type: DependencyType.static, + }, + ], + }, + externalNodes: {}, + version: '5', + }; + + lifeCycle = { + startTask: jest.fn(), + endTask: jest.fn(), + scheduleTask: jest.fn(), + }; + taskSchedule = new TasksSchedule(projectGraph, taskGraph, { + lifeCycle, + }); + }); + + describe('Without Batch Mode', () => { + let original; + beforeEach(() => { + original = process.env['NX_BATCH_MODE']; + process.env['NX_BATCH_MODE'] = 'false'; + }); + + afterEach(() => { + process.env['NX_BATCH_MODE'] = original; + }); + + it('should begin with no scheduled tasks', () => { + expect(taskSchedule.nextBatch()).toBeNull(); + expect(taskSchedule.nextTask()).toBeNull(); + }); + + it('should schedule root tasks first', async () => { + // app1 depends on lib1, app2 has no dependencies + await taskSchedule.scheduleNextTasks(); + expect(taskSchedule.nextTask()).toEqual(lib1Build); + // since lib1 is not parallel, app2 should not be scheduled even though it has no dependencies + expect(taskSchedule.nextTask()).toBeNull(); + taskSchedule.complete([lib1Build.id]); + await taskSchedule.scheduleNextTasks(); + expect(taskSchedule.nextTask()).toEqual(app1Build); + expect(taskSchedule.nextTask()).toBeNull(); // app2 should not be scheduled since app1 is not parallel and not completed + taskSchedule.complete([app1Build.id]); + await taskSchedule.scheduleNextTasks(); + expect(taskSchedule.nextTask()).toEqual(app2Build); + taskSchedule.complete([app2Build.id]); + expect(taskSchedule.hasTasks()).toEqual(false); + }); + + it('should not schedule batches', async () => { + await taskSchedule.scheduleNextTasks(); + + expect(taskSchedule.nextTask()).not.toBeNull(); + + expect(taskSchedule.nextBatch()).toBeNull(); + }); + }); + + describe('With Batch Mode', () => { + let original; + beforeEach(() => { + original = process.env['NX_BATCH_MODE']; + process.env['NX_BATCH_MODE'] = 'true'; + }); + + afterEach(() => { + process.env['NX_BATCH_MODE'] = original; + }); + + it('should not schedule batches of tasks by different executors if task has parallelism false', async () => { + await taskSchedule.scheduleNextTasks(); + + // since all tasks have parallelism false, they should not be batched + expect(taskSchedule.nextTask()).toEqual(lib1Build); + + expect(taskSchedule.nextBatch()).toBeNull(); + }); + }); + }); + + describe('non-dependent tasks', () => { + let taskSchedule: TasksSchedule; + let taskGraph: TaskGraph; + let app1Test: Task; + let app2Test: Task; + let lib1Test: Task; + let lifeCycle: any; + beforeEach(() => { + // app1, app2, and lib1 do not depend on each other + // all tasks have parallelism set to false + app1Test = createMockTask('app1:test', false); + app2Test = createMockTask('app2:test', false); + lib1Test = createMockTask('lib1:test', false); + + taskGraph = { + tasks: { + 'app1:test': app1Test, + 'app2:test': app2Test, + 'lib1:test': lib1Test, + }, + dependencies: { + 'app1:test': [], + 'app2:test': [], + 'lib1:test': [], + }, + roots: ['app1:test', 'app2:test', 'lib1:test'], + }; + jest.spyOn(nxJsonUtils, 'readNxJson').mockReturnValue({}); + jest.spyOn(executorUtils, 'getExecutorInformation').mockReturnValue({ + schema: { + version: 2, + properties: {}, + }, + implementationFactory: jest.fn(), + batchImplementationFactory: jest.fn(), + isNgCompat: true, + isNxExecutor: true, + }); + + const projectGraph: ProjectGraph = { + nodes: { + app1: { + data: { + root: 'app1', + targets: { + test: { + executor: 'awesome-executors:test', + parallelism: false, + }, + }, + }, + name: 'app1', + type: 'app', + }, + app2: { + name: 'app2', + type: 'app', + data: { + root: 'app2', + targets: { + test: { + executor: 'awesome-executors:app2-test', + parallelism: false, + }, + }, + }, + }, + lib1: { + name: 'lib1', + type: 'lib', + data: { + root: 'lib1', + targets: { + test: { + executor: 'awesome-executors:test', + }, + }, + }, + }, + } as any, + dependencies: { + app1: [ + { + source: 'app1', + target: 'lib1', + type: DependencyType.static, + }, + ], + app2: [ + { + source: 'app2', + target: 'lib1', + type: DependencyType.static, + }, + ], + }, + externalNodes: {}, + version: '5', + }; + + lifeCycle = { + startTask: jest.fn(), + endTask: jest.fn(), + scheduleTask: jest.fn(), + }; + taskSchedule = new TasksSchedule(projectGraph, taskGraph, { + lifeCycle, + }); + }); + + describe('Without Batch Mode', () => { + let original; + beforeEach(() => { + original = process.env['NX_BATCH_MODE']; + process.env['NX_BATCH_MODE'] = 'false'; + }); + + afterEach(() => { + process.env['NX_BATCH_MODE'] = original; + }); + + it('should begin with no scheduled tasks', () => { + expect(taskSchedule.nextBatch()).toBeNull(); + expect(taskSchedule.nextTask()).toBeNull(); + }); + + it('should schedule root tasks in topological order', async () => { + await taskSchedule.scheduleNextTasks(); + expect(taskSchedule.nextTask()).toEqual(app1Test); + let nextTask = taskSchedule.nextTask(); + expect(nextTask).not.toEqual(app2Test); // app2 should not be scheduled since app1 is not parallel and not completed + expect(nextTask).not.toEqual(lib1Test); // lib1 should not be scheduled since app1 is not parallel and not completed + expect(nextTask).toBeNull(); + + taskSchedule.complete([app1Test.id]); + await taskSchedule.scheduleNextTasks(); + nextTask = taskSchedule.nextTask(); + expect(nextTask).toEqual(app2Test); // app2 should be scheduled since app1 is completed now + + nextTask = taskSchedule.nextTask(); + expect(nextTask).not.toEqual(lib1Test); // lib1 should not be scheduled since app2 is not parallel and not completed + expect(nextTask).toBeNull(); + + taskSchedule.complete([app1Test.id]); // this should not do anything since app1 is already completed + await taskSchedule.scheduleNextTasks(); + nextTask = taskSchedule.nextTask(); + expect(nextTask).not.toEqual(lib1Test); // lib1 should not be scheduled since app2 is not parallel and not completed + expect(nextTask).toBeNull(); + + taskSchedule.complete([app2Test.id]); + await taskSchedule.scheduleNextTasks(); + expect(taskSchedule.nextTask()).toEqual(lib1Test); // lib1 should be scheduled since app2 is completed now + taskSchedule.complete([lib1Test.id]); + expect(taskSchedule.hasTasks()).toEqual(false); + }); + + it('should not schedule batches', async () => { + await taskSchedule.scheduleNextTasks(); + + expect(taskSchedule.nextTask()).not.toBeNull(); + + expect(taskSchedule.nextBatch()).toBeNull(); + }); + }); + + describe('With Batch Mode', () => { + let original; + beforeEach(() => { + original = process.env['NX_BATCH_MODE']; + process.env['NX_BATCH_MODE'] = 'true'; + }); + + afterEach(() => { + process.env['NX_BATCH_MODE'] = original; + }); + + it('should not schedule batches of tasks by different executors if task have parallelism false', async () => { + await taskSchedule.scheduleNextTasks(); + + // app1, app2, and lib1 are not parallel, so they should not be batched + expect(taskSchedule.nextTask()).toEqual(app1Test); + + expect(taskSchedule.nextBatch()).toBeNull(); + }); + }); + }); + }); }); diff --git a/packages/nx/src/tasks-runner/tasks-schedule.ts b/packages/nx/src/tasks-runner/tasks-schedule.ts index 5d63f3a317302..f862b9ec04e71 100644 --- a/packages/nx/src/tasks-runner/tasks-schedule.ts +++ b/packages/nx/src/tasks-runner/tasks-schedule.ts @@ -21,6 +21,7 @@ export class TasksSchedule { private reverseProjectGraph = reverse(this.projectGraph); private scheduledBatches: Batch[] = []; private scheduledTasks: string[] = []; + private runningTasks = new Set(); private completedTasks = new Set(); private scheduleRequestsExecutionChain = Promise.resolve(); @@ -48,6 +49,7 @@ export class TasksSchedule { public complete(taskIds: string[]) { for (const taskId of taskIds) { this.completedTasks.add(taskId); + this.runningTasks.delete(taskId); } this.notScheduledTaskGraph = removeTasksFromTaskGraph( this.notScheduledTaskGraph, @@ -117,6 +119,7 @@ export class TasksSchedule { .length ); }); + this.runningTasks.add(taskId); } private async scheduleBatches() { @@ -146,8 +149,8 @@ export class TasksSchedule { task: Task, rootExecutorName: string, isRoot: boolean - ) { - if (!this.canBatchTaskBeScheduled(task.id, batches[rootExecutorName])) { + ): Promise { + if (!this.canBatchTaskBeScheduled(task, batches[rootExecutorName])) { return; } @@ -191,18 +194,45 @@ export class TasksSchedule { } private canBatchTaskBeScheduled( - taskId: string, + task: Task, batchTaskGraph: TaskGraph | undefined ): boolean { + // task self needs to have parallelism true // all deps have either completed or belong to the same batch - return this.taskGraph.dependencies[taskId].every( - (id) => this.completedTasks.has(id) || !!batchTaskGraph?.tasks[id] + return ( + task.parallelism === true && + this.taskGraph.dependencies[task.id].every( + (id) => this.completedTasks.has(id) || !!batchTaskGraph?.tasks[id] + ) ); } - private canBeScheduled(taskId: string) { - return this.taskGraph.dependencies[taskId].every((id) => - this.completedTasks.has(id) + private canBeScheduled(taskId: string): boolean { + const hasDependenciesCompleted = this.taskGraph.dependencies[taskId].every( + (id) => this.completedTasks.has(id) ); + + // if dependencies have not completed, cannot schedule + if (!hasDependenciesCompleted) { + return false; + } + + // if there are no running tasks, can schedule anything + if (this.runningTasks.size === 0) { + return true; + } + + const runningTasksNotSupportParallelism = Array.from( + this.runningTasks + ).some((taskId) => { + return this.taskGraph.tasks[taskId].parallelism === false; + }); + if (runningTasksNotSupportParallelism) { + // if any running tasks do not support parallelism, no other tasks can be scheduled + return false; + } else { + // if all running tasks support parallelism, can only schedule task with parallelism + return this.taskGraph.tasks[taskId].parallelism === true; + } } } diff --git a/packages/playwright/src/plugins/plugin.spec.ts b/packages/playwright/src/plugins/plugin.spec.ts index f6740f0a17297..60c36e4f2c4a0 100644 --- a/packages/playwright/src/plugins/plugin.spec.ts +++ b/packages/playwright/src/plugins/plugin.spec.ts @@ -91,6 +91,7 @@ describe('@nx/playwright/plugin', () => { "outputs": [ "{projectRoot}/test-results", ], + "parallelism": false, }, "e2e-ci": { "cache": true, @@ -123,6 +124,7 @@ describe('@nx/playwright/plugin', () => { "outputs": [ "{projectRoot}/test-results", ], + "parallelism": false, }, }, }, @@ -200,6 +202,7 @@ describe('@nx/playwright/plugin', () => { "{projectRoot}/test-results/html", "{projectRoot}/test-results", ], + "parallelism": false, }, "e2e-ci": { "cache": true, @@ -235,6 +238,7 @@ describe('@nx/playwright/plugin', () => { "{projectRoot}/test-results/html", "{projectRoot}/test-results", ], + "parallelism": false, }, }, }, @@ -323,6 +327,7 @@ describe('@nx/playwright/plugin', () => { "outputs": [ "{projectRoot}/test-results", ], + "parallelism": false, } `); expect(targets['e2e-ci--tests/run-me.spec.ts']).toMatchInlineSnapshot(` @@ -358,6 +363,7 @@ describe('@nx/playwright/plugin', () => { "outputs": [ "{projectRoot}/test-results", ], + "parallelism": false, } `); expect(targets['e2e-ci--tests/run-me-2.spec.ts']).toMatchInlineSnapshot(` @@ -393,6 +399,7 @@ describe('@nx/playwright/plugin', () => { "outputs": [ "{projectRoot}/test-results", ], + "parallelism": false, } `); expect(targets['e2e-ci--tests/skip-me.spec.ts']).not.toBeDefined(); diff --git a/packages/playwright/src/plugins/plugin.ts b/packages/playwright/src/plugins/plugin.ts index 9f64ffc8a6513..dc45d1ccdf2a2 100644 --- a/packages/playwright/src/plugins/plugin.ts +++ b/packages/playwright/src/plugins/plugin.ts @@ -162,6 +162,7 @@ async function buildPlaywrightTargets( options: { cwd: '{projectRoot}', }, + parallelism: false, metadata: { technologies: ['playwright'], description: 'Runs Playwright Tests', @@ -257,6 +258,7 @@ async function buildPlaywrightTargets( inputs: ciBaseTargetConfig.inputs, outputs: ciBaseTargetConfig.outputs, dependsOn, + parallelism: false, metadata: { technologies: ['playwright'], description: 'Runs Playwright Tests in CI', diff --git a/packages/plugin/src/generators/executor/files/hasher/hasher.spec.ts.template b/packages/plugin/src/generators/executor/files/hasher/hasher.spec.ts.template index bcdaac89f331a..92fc67ddccdd9 100644 --- a/packages/plugin/src/generators/executor/files/hasher/hasher.spec.ts.template +++ b/packages/plugin/src/generators/executor/files/hasher/hasher.spec.ts.template @@ -14,7 +14,8 @@ describe('<%=propertyName%>Hasher', () => { target: 'target' }, overrides: {}, - outputs: [] + outputs: [], + parallelism: true }, { hasher: mockHasher } as unknown as HasherContext)