cleanup(core): rework task graph creation to handle edge cases better
This commit is contained in:
parent
37c529d3bb
commit
912e81957f
@ -89,6 +89,14 @@ Default: false
|
||||
|
||||
Stop command execution after the first failed task
|
||||
|
||||
### nx-ignore-cycles
|
||||
|
||||
Type: boolean
|
||||
|
||||
Default: false
|
||||
|
||||
Ignore cycles in the task graph
|
||||
|
||||
### ~~only-failed~~
|
||||
|
||||
Type: boolean
|
||||
|
||||
@ -131,6 +131,14 @@ Default: false
|
||||
|
||||
Stop command execution after the first failed task
|
||||
|
||||
### nx-ignore-cycles
|
||||
|
||||
Type: boolean
|
||||
|
||||
Default: false
|
||||
|
||||
Ignore cycles in the task graph
|
||||
|
||||
### ~~only-failed~~
|
||||
|
||||
Type: boolean
|
||||
|
||||
@ -89,6 +89,14 @@ Default: false
|
||||
|
||||
Stop command execution after the first failed task
|
||||
|
||||
### nx-ignore-cycles
|
||||
|
||||
Type: boolean
|
||||
|
||||
Default: false
|
||||
|
||||
Ignore cycles in the task graph
|
||||
|
||||
### ~~only-failed~~
|
||||
|
||||
Type: boolean
|
||||
|
||||
@ -107,6 +107,14 @@ Default: false
|
||||
|
||||
Stop command execution after the first failed task
|
||||
|
||||
### nx-ignore-cycles
|
||||
|
||||
Type: boolean
|
||||
|
||||
Default: false
|
||||
|
||||
Ignore cycles in the task graph
|
||||
|
||||
### ~~only-failed~~
|
||||
|
||||
Type: boolean
|
||||
|
||||
@ -75,6 +75,14 @@ Default: false
|
||||
|
||||
Stop command execution after the first failed task
|
||||
|
||||
### nx-ignore-cycles
|
||||
|
||||
Type: boolean
|
||||
|
||||
Default: false
|
||||
|
||||
Ignore cycles in the task graph
|
||||
|
||||
### ~~only-failed~~
|
||||
|
||||
Type: boolean
|
||||
|
||||
@ -75,6 +75,14 @@ Default: false
|
||||
|
||||
Stop command execution after the first failed task
|
||||
|
||||
### nx-ignore-cycles
|
||||
|
||||
Type: boolean
|
||||
|
||||
Default: false
|
||||
|
||||
Ignore cycles in the task graph
|
||||
|
||||
### ~~only-failed~~
|
||||
|
||||
Type: boolean
|
||||
|
||||
@ -101,6 +101,14 @@ Default: false
|
||||
|
||||
Stop command execution after the first failed task
|
||||
|
||||
### nx-ignore-cycles
|
||||
|
||||
Type: boolean
|
||||
|
||||
Default: false
|
||||
|
||||
Ignore cycles in the task graph
|
||||
|
||||
### ~~only-failed~~
|
||||
|
||||
Type: boolean
|
||||
|
||||
@ -71,6 +71,14 @@ Default: false
|
||||
|
||||
Stop command execution after the first failed task
|
||||
|
||||
### nx-ignore-cycles
|
||||
|
||||
Type: boolean
|
||||
|
||||
Default: false
|
||||
|
||||
Ignore cycles in the task graph
|
||||
|
||||
### ~~only-failed~~
|
||||
|
||||
Type: boolean
|
||||
|
||||
@ -931,6 +931,7 @@ stored in the daemon process. To reset both run: `nx reset`.
|
||||
| `context.nxJson` | [`NxJsonConfiguration`](../../devkit/index#nxjsonconfiguration)<`string`[] \| `"*"`\> |
|
||||
| `context.projectGraph` | [`ProjectGraph`](../../devkit/index#projectgraph)<`any`\> |
|
||||
| `context.target?` | `string` |
|
||||
| `context.taskGraph?` | [`TaskGraph`](../../devkit/index#taskgraph) |
|
||||
|
||||
#### Returns
|
||||
|
||||
|
||||
@ -39,49 +39,49 @@
|
||||
"name": "run-many",
|
||||
"id": "run-many",
|
||||
"file": "generated/cli/run-many",
|
||||
"content": "---\ntitle: 'run-many - CLI command'\ndescription: 'Run target for multiple listed projects'\n---\n\n# run-many\n\nRun target for multiple listed projects\n\n## Usage\n\n```bash\nnx run-many\n```\n\n[Install `nx` globally](/getting-started/nx-setup#install-nx) to invoke the command directly using `nx`, or use `npx nx`, `yarn nx`, or `pnpx nx`.\n\n### Examples\n\nTest all projects:\n\n```bash\nnx run-many --target=test --all\n```\n\nTest proj1 and proj2:\n\n```bash\nnx run-many --target=test --projects=proj1,proj2\n```\n\nTest proj1 and proj2 in parallel:\n\n```bash\nnx run-many --target=test --projects=proj1,proj2 --parallel=2\n```\n\n## Options\n\n### all\n\nType: boolean\n\nRun the target on all projects in the workspace\n\n### configuration\n\nType: string\n\nThis is the configuration to use when performing tasks on projects\n\n### exclude\n\nType: array\n\nDefault: []\n\nExclude certain projects from being processed\n\n### help\n\nType: boolean\n\nShow help\n\n### nx-bail\n\nType: boolean\n\nDefault: false\n\nStop command execution after the first failed task\n\n### ~~only-failed~~\n\nType: boolean\n\nDefault: false\n\n**Deprecated:** The command to rerun failed projects will appear if projects fail. This now does nothing and will be removed in v15.\n\nOnly run the target on projects which previously failed\n\n### output-style\n\nType: string\n\nChoices: [dynamic, static, stream, stream-without-prefixes]\n\nDefines how Nx emits outputs tasks logs\n\n### parallel\n\nType: string\n\nMax number of parallel processes [default is 3]\n\n### projects\n\nType: string\n\nProjects to run (comma delimited)\n\n### runner\n\nType: string\n\nOverride the tasks runner in `nx.json`\n\n### skip-nx-cache\n\nType: boolean\n\nDefault: false\n\nRerun the tasks even when the results are available in the cache\n\n### target\n\nType: string\n\nTask to run for affected projects\n\n### verbose\n\nPrint additional error stack trace on failure\n\n### version\n\nType: boolean\n\nShow version number\n"
|
||||
"content": "---\ntitle: 'run-many - CLI command'\ndescription: 'Run target for multiple listed projects'\n---\n\n# run-many\n\nRun target for multiple listed projects\n\n## Usage\n\n```bash\nnx run-many\n```\n\n[Install `nx` globally](/getting-started/nx-setup#install-nx) to invoke the command directly using `nx`, or use `npx nx`, `yarn nx`, or `pnpx nx`.\n\n### Examples\n\nTest all projects:\n\n```bash\nnx run-many --target=test --all\n```\n\nTest proj1 and proj2:\n\n```bash\nnx run-many --target=test --projects=proj1,proj2\n```\n\nTest proj1 and proj2 in parallel:\n\n```bash\nnx run-many --target=test --projects=proj1,proj2 --parallel=2\n```\n\n## Options\n\n### all\n\nType: boolean\n\nRun the target on all projects in the workspace\n\n### configuration\n\nType: string\n\nThis is the configuration to use when performing tasks on projects\n\n### exclude\n\nType: array\n\nDefault: []\n\nExclude certain projects from being processed\n\n### help\n\nType: boolean\n\nShow help\n\n### nx-bail\n\nType: boolean\n\nDefault: false\n\nStop command execution after the first failed task\n\n### nx-ignore-cycles\n\nType: boolean\n\nDefault: false\n\nIgnore cycles in the task graph\n\n### ~~only-failed~~\n\nType: boolean\n\nDefault: false\n\n**Deprecated:** The command to rerun failed projects will appear if projects fail. This now does nothing and will be removed in v15.\n\nOnly run the target on projects which previously failed\n\n### output-style\n\nType: string\n\nChoices: [dynamic, static, stream, stream-without-prefixes]\n\nDefines how Nx emits outputs tasks logs\n\n### parallel\n\nType: string\n\nMax number of parallel processes [default is 3]\n\n### projects\n\nType: string\n\nProjects to run (comma delimited)\n\n### runner\n\nType: string\n\nOverride the tasks runner in `nx.json`\n\n### skip-nx-cache\n\nType: boolean\n\nDefault: false\n\nRerun the tasks even when the results are available in the cache\n\n### target\n\nType: string\n\nTask to run for affected projects\n\n### verbose\n\nPrint additional error stack trace on failure\n\n### version\n\nType: boolean\n\nShow version number\n"
|
||||
},
|
||||
{
|
||||
"name": "affected",
|
||||
"id": "affected",
|
||||
"file": "generated/cli/affected",
|
||||
"content": "---\ntitle: 'affected - CLI command'\ndescription: 'Run target for affected projects'\n---\n\n# affected\n\nRun target for affected projects\n\n## Usage\n\n```bash\nnx affected\n```\n\n[Install `nx` globally](/getting-started/nx-setup#install-nx) to invoke the command directly using `nx`, or use `npx nx`, `yarn nx`, or `pnpx nx`.\n\n### Examples\n\nRun custom target for all affected projects:\n\n```bash\nnx affected --target=custom-target\n```\n\nRun tests in parallel:\n\n```bash\nnx affected --target=test --parallel=5\n```\n\nRun the test target for all projects:\n\n```bash\nnx affected --target=test --all\n```\n\nRun tests for all the projects affected by changing the index.ts file:\n\n```bash\nnx affected --target=test --files=libs/mylib/src/index.ts\n```\n\nRun tests for all the projects affected by the changes between main and HEAD (e.g., PR):\n\n```bash\nnx affected --target=test --base=main --head=HEAD\n```\n\nRun tests for all the projects affected by the last commit on main:\n\n```bash\nnx affected --target=test --base=main~1 --head=main\n```\n\n## Options\n\n### all\n\nType: boolean\n\nAll projects\n\n### base\n\nType: string\n\nBase of the current branch (usually main)\n\n### configuration\n\nType: string\n\nThis is the configuration to use when performing tasks on projects\n\n### exclude\n\nType: array\n\nDefault: []\n\nExclude certain projects from being processed\n\n### files\n\nType: array\n\nChange the way Nx is calculating the affected command by providing directly changed files, list of files delimited by commas\n\n### head\n\nType: string\n\nLatest commit of the current branch (usually HEAD)\n\n### help\n\nType: boolean\n\nShow help\n\n### nx-bail\n\nType: boolean\n\nDefault: false\n\nStop command execution after the first failed task\n\n### ~~only-failed~~\n\nType: boolean\n\nDefault: false\n\n**Deprecated:** The command to rerun failed projects will appear if projects fail. This now does nothing and will be removed in v15.\n\nIsolate projects which previously failed\n\n### output-style\n\nType: string\n\nChoices: [dynamic, static, stream, stream-without-prefixes]\n\nDefines how Nx emits outputs tasks logs\n\n### parallel\n\nType: string\n\nMax number of parallel processes [default is 3]\n\n### runner\n\nType: string\n\nThis is the name of the tasks runner configured in nx.json\n\n### skip-nx-cache\n\nType: boolean\n\nDefault: false\n\nRerun the tasks even when the results are available in the cache\n\n### target\n\nType: string\n\nTask to run for affected projects\n\n### uncommitted\n\nType: boolean\n\nUncommitted changes\n\n### untracked\n\nType: boolean\n\nUntracked changes\n\n### verbose\n\nPrint additional error stack trace on failure\n\n### version\n\nType: boolean\n\nShow version number\n"
|
||||
"content": "---\ntitle: 'affected - CLI command'\ndescription: 'Run target for affected projects'\n---\n\n# affected\n\nRun target for affected projects\n\n## Usage\n\n```bash\nnx affected\n```\n\n[Install `nx` globally](/getting-started/nx-setup#install-nx) to invoke the command directly using `nx`, or use `npx nx`, `yarn nx`, or `pnpx nx`.\n\n### Examples\n\nRun custom target for all affected projects:\n\n```bash\nnx affected --target=custom-target\n```\n\nRun tests in parallel:\n\n```bash\nnx affected --target=test --parallel=5\n```\n\nRun the test target for all projects:\n\n```bash\nnx affected --target=test --all\n```\n\nRun tests for all the projects affected by changing the index.ts file:\n\n```bash\nnx affected --target=test --files=libs/mylib/src/index.ts\n```\n\nRun tests for all the projects affected by the changes between main and HEAD (e.g., PR):\n\n```bash\nnx affected --target=test --base=main --head=HEAD\n```\n\nRun tests for all the projects affected by the last commit on main:\n\n```bash\nnx affected --target=test --base=main~1 --head=main\n```\n\n## Options\n\n### all\n\nType: boolean\n\nAll projects\n\n### base\n\nType: string\n\nBase of the current branch (usually main)\n\n### configuration\n\nType: string\n\nThis is the configuration to use when performing tasks on projects\n\n### exclude\n\nType: array\n\nDefault: []\n\nExclude certain projects from being processed\n\n### files\n\nType: array\n\nChange the way Nx is calculating the affected command by providing directly changed files, list of files delimited by commas\n\n### head\n\nType: string\n\nLatest commit of the current branch (usually HEAD)\n\n### help\n\nType: boolean\n\nShow help\n\n### nx-bail\n\nType: boolean\n\nDefault: false\n\nStop command execution after the first failed task\n\n### nx-ignore-cycles\n\nType: boolean\n\nDefault: false\n\nIgnore cycles in the task graph\n\n### ~~only-failed~~\n\nType: boolean\n\nDefault: false\n\n**Deprecated:** The command to rerun failed projects will appear if projects fail. This now does nothing and will be removed in v15.\n\nIsolate projects which previously failed\n\n### output-style\n\nType: string\n\nChoices: [dynamic, static, stream, stream-without-prefixes]\n\nDefines how Nx emits outputs tasks logs\n\n### parallel\n\nType: string\n\nMax number of parallel processes [default is 3]\n\n### runner\n\nType: string\n\nThis is the name of the tasks runner configured in nx.json\n\n### skip-nx-cache\n\nType: boolean\n\nDefault: false\n\nRerun the tasks even when the results are available in the cache\n\n### target\n\nType: string\n\nTask to run for affected projects\n\n### uncommitted\n\nType: boolean\n\nUncommitted changes\n\n### untracked\n\nType: boolean\n\nUntracked changes\n\n### verbose\n\nPrint additional error stack trace on failure\n\n### version\n\nType: boolean\n\nShow version number\n"
|
||||
},
|
||||
{
|
||||
"name": "affected:graph",
|
||||
"id": "affected-dep-graph",
|
||||
"file": "generated/cli/affected-graph",
|
||||
"content": "---\ntitle: 'affected:graph - CLI command'\ndescription: 'Graph dependencies affected by changes'\n---\n\n# affected:graph\n\nGraph dependencies affected by changes\n\n## Usage\n\n```bash\nnx affected:graph\n```\n\n[Install `nx` globally](/getting-started/nx-setup#install-nx) to invoke the command directly using `nx`, or use `npx nx`, `yarn nx`, or `pnpx nx`.\n\n### Examples\n\nOpen the project graph of the workspace in the browser, and highlight the projects affected by changing the index.ts file:\n\n```bash\nnx affected:graph --files=libs/mylib/src/index.ts\n```\n\nOpen the project graph of the workspace in the browser, and highlight the projects affected by the changes between main and HEAD (e.g., PR):\n\n```bash\nnx affected:graph --base=main --head=HEAD\n```\n\nSave the project graph of the workspace in a json file, and highlight the projects affected by the changes between main and HEAD (e.g., PR):\n\n```bash\nnx affected:graph --base=main --head=HEAD --file=output.json\n```\n\nGenerate a static website with project graph data in an html file, highlighting the projects affected by the changes between main and HEAD (e.g., PR):\n\n```bash\nnx affected:graph --base=main --head=HEAD --file=output.html\n```\n\nOpen the project graph of the workspace in the browser, and highlight the projects affected by the last commit on main:\n\n```bash\nnx affected:graph --base=main~1 --head=main\n```\n\nOpen the project graph of the workspace in the browser, highlight the projects affected, but exclude project-one and project-two:\n\n```bash\nnx affected:graph --exclude=project-one,project-two\n```\n\n## Options\n\n### all\n\nType: boolean\n\nAll projects\n\n### base\n\nType: string\n\nBase of the current branch (usually main)\n\n### configuration\n\nType: string\n\nThis is the configuration to use when performing tasks on projects\n\n### exclude\n\nType: array\n\nDefault: []\n\nExclude certain projects from being processed\n\n### file\n\nType: string\n\nOutput file (e.g. --file=output.json or --file=dep-graph.html)\n\n### files\n\nType: array\n\nChange the way Nx is calculating the affected command by providing directly changed files, list of files delimited by commas\n\n### focus\n\nType: string\n\nUse to show the project graph for a particular project and every node that is either an ancestor or a descendant.\n\n### groupByFolder\n\nType: boolean\n\nGroup projects by folder in the project graph\n\n### head\n\nType: string\n\nLatest commit of the current branch (usually HEAD)\n\n### help\n\nType: boolean\n\nShow help\n\n### host\n\nType: string\n\nBind the project graph server to a specific ip address.\n\n### nx-bail\n\nType: boolean\n\nDefault: false\n\nStop command execution after the first failed task\n\n### ~~only-failed~~\n\nType: boolean\n\nDefault: false\n\n**Deprecated:** The command to rerun failed projects will appear if projects fail. This now does nothing and will be removed in v15.\n\nIsolate projects which previously failed\n\n### open\n\nType: boolean\n\nDefault: true\n\nOpen the project graph in the browser.\n\n### port\n\nType: number\n\nBind the project graph server to a specific port.\n\n### runner\n\nType: string\n\nThis is the name of the tasks runner configured in nx.json\n\n### skip-nx-cache\n\nType: boolean\n\nDefault: false\n\nRerun the tasks even when the results are available in the cache\n\n### uncommitted\n\nType: boolean\n\nUncommitted changes\n\n### untracked\n\nType: boolean\n\nUntracked changes\n\n### verbose\n\nPrint additional error stack trace on failure\n\n### version\n\nType: boolean\n\nShow version number\n\n### watch\n\nType: boolean\n\nDefault: false\n\nWatch for changes to project graph and update in-browser\n"
|
||||
"content": "---\ntitle: 'affected:graph - CLI command'\ndescription: 'Graph dependencies affected by changes'\n---\n\n# affected:graph\n\nGraph dependencies affected by changes\n\n## Usage\n\n```bash\nnx affected:graph\n```\n\n[Install `nx` globally](/getting-started/nx-setup#install-nx) to invoke the command directly using `nx`, or use `npx nx`, `yarn nx`, or `pnpx nx`.\n\n### Examples\n\nOpen the project graph of the workspace in the browser, and highlight the projects affected by changing the index.ts file:\n\n```bash\nnx affected:graph --files=libs/mylib/src/index.ts\n```\n\nOpen the project graph of the workspace in the browser, and highlight the projects affected by the changes between main and HEAD (e.g., PR):\n\n```bash\nnx affected:graph --base=main --head=HEAD\n```\n\nSave the project graph of the workspace in a json file, and highlight the projects affected by the changes between main and HEAD (e.g., PR):\n\n```bash\nnx affected:graph --base=main --head=HEAD --file=output.json\n```\n\nGenerate a static website with project graph data in an html file, highlighting the projects affected by the changes between main and HEAD (e.g., PR):\n\n```bash\nnx affected:graph --base=main --head=HEAD --file=output.html\n```\n\nOpen the project graph of the workspace in the browser, and highlight the projects affected by the last commit on main:\n\n```bash\nnx affected:graph --base=main~1 --head=main\n```\n\nOpen the project graph of the workspace in the browser, highlight the projects affected, but exclude project-one and project-two:\n\n```bash\nnx affected:graph --exclude=project-one,project-two\n```\n\n## Options\n\n### all\n\nType: boolean\n\nAll projects\n\n### base\n\nType: string\n\nBase of the current branch (usually main)\n\n### configuration\n\nType: string\n\nThis is the configuration to use when performing tasks on projects\n\n### exclude\n\nType: array\n\nDefault: []\n\nExclude certain projects from being processed\n\n### file\n\nType: string\n\nOutput file (e.g. --file=output.json or --file=dep-graph.html)\n\n### files\n\nType: array\n\nChange the way Nx is calculating the affected command by providing directly changed files, list of files delimited by commas\n\n### focus\n\nType: string\n\nUse to show the project graph for a particular project and every node that is either an ancestor or a descendant.\n\n### groupByFolder\n\nType: boolean\n\nGroup projects by folder in the project graph\n\n### head\n\nType: string\n\nLatest commit of the current branch (usually HEAD)\n\n### help\n\nType: boolean\n\nShow help\n\n### host\n\nType: string\n\nBind the project graph server to a specific ip address.\n\n### nx-bail\n\nType: boolean\n\nDefault: false\n\nStop command execution after the first failed task\n\n### nx-ignore-cycles\n\nType: boolean\n\nDefault: false\n\nIgnore cycles in the task graph\n\n### ~~only-failed~~\n\nType: boolean\n\nDefault: false\n\n**Deprecated:** The command to rerun failed projects will appear if projects fail. This now does nothing and will be removed in v15.\n\nIsolate projects which previously failed\n\n### open\n\nType: boolean\n\nDefault: true\n\nOpen the project graph in the browser.\n\n### port\n\nType: number\n\nBind the project graph server to a specific port.\n\n### runner\n\nType: string\n\nThis is the name of the tasks runner configured in nx.json\n\n### skip-nx-cache\n\nType: boolean\n\nDefault: false\n\nRerun the tasks even when the results are available in the cache\n\n### uncommitted\n\nType: boolean\n\nUncommitted changes\n\n### untracked\n\nType: boolean\n\nUntracked changes\n\n### verbose\n\nPrint additional error stack trace on failure\n\n### version\n\nType: boolean\n\nShow version number\n\n### watch\n\nType: boolean\n\nDefault: false\n\nWatch for changes to project graph and update in-browser\n"
|
||||
},
|
||||
{
|
||||
"name": "affected:apps",
|
||||
"id": "affected-apps",
|
||||
"file": "generated/cli/affected-apps",
|
||||
"content": "---\ntitle: 'affected:apps - CLI command'\ndescription: 'Print applications affected by changes'\n---\n\n# affected:apps\n\nPrint applications affected by changes\n\n## Usage\n\n```bash\nnx affected:apps\n```\n\n[Install `nx` globally](/getting-started/nx-setup#install-nx) to invoke the command directly using `nx`, or use `npx nx`, `yarn nx`, or `pnpx nx`.\n\n### Examples\n\nPrint the names of all the apps affected by changing the index.ts file:\n\n```bash\nnx affected:apps --files=libs/mylib/src/index.ts\n```\n\nPrint the names of all the apps affected by the changes between main and HEAD (e.g., PR):\n\n```bash\nnx affected:apps --base=main --head=HEAD\n```\n\nPrint the names of all the apps affected by the last commit on main:\n\n```bash\nnx affected:apps --base=main~1 --head=main\n```\n\n## Options\n\n### all\n\nType: boolean\n\nAll projects\n\n### base\n\nType: string\n\nBase of the current branch (usually main)\n\n### configuration\n\nType: string\n\nThis is the configuration to use when performing tasks on projects\n\n### exclude\n\nType: array\n\nDefault: []\n\nExclude certain projects from being processed\n\n### files\n\nType: array\n\nChange the way Nx is calculating the affected command by providing directly changed files, list of files delimited by commas\n\n### head\n\nType: string\n\nLatest commit of the current branch (usually HEAD)\n\n### help\n\nType: boolean\n\nShow help\n\n### nx-bail\n\nType: boolean\n\nDefault: false\n\nStop command execution after the first failed task\n\n### ~~only-failed~~\n\nType: boolean\n\nDefault: false\n\n**Deprecated:** The command to rerun failed projects will appear if projects fail. This now does nothing and will be removed in v15.\n\nIsolate projects which previously failed\n\n### plain\n\nProduces a plain output for affected:apps and affected:libs\n\n### runner\n\nType: string\n\nThis is the name of the tasks runner configured in nx.json\n\n### skip-nx-cache\n\nType: boolean\n\nDefault: false\n\nRerun the tasks even when the results are available in the cache\n\n### uncommitted\n\nType: boolean\n\nUncommitted changes\n\n### untracked\n\nType: boolean\n\nUntracked changes\n\n### verbose\n\nPrint additional error stack trace on failure\n\n### version\n\nType: boolean\n\nShow version number\n"
|
||||
"content": "---\ntitle: 'affected:apps - CLI command'\ndescription: 'Print applications affected by changes'\n---\n\n# affected:apps\n\nPrint applications affected by changes\n\n## Usage\n\n```bash\nnx affected:apps\n```\n\n[Install `nx` globally](/getting-started/nx-setup#install-nx) to invoke the command directly using `nx`, or use `npx nx`, `yarn nx`, or `pnpx nx`.\n\n### Examples\n\nPrint the names of all the apps affected by changing the index.ts file:\n\n```bash\nnx affected:apps --files=libs/mylib/src/index.ts\n```\n\nPrint the names of all the apps affected by the changes between main and HEAD (e.g., PR):\n\n```bash\nnx affected:apps --base=main --head=HEAD\n```\n\nPrint the names of all the apps affected by the last commit on main:\n\n```bash\nnx affected:apps --base=main~1 --head=main\n```\n\n## Options\n\n### all\n\nType: boolean\n\nAll projects\n\n### base\n\nType: string\n\nBase of the current branch (usually main)\n\n### configuration\n\nType: string\n\nThis is the configuration to use when performing tasks on projects\n\n### exclude\n\nType: array\n\nDefault: []\n\nExclude certain projects from being processed\n\n### files\n\nType: array\n\nChange the way Nx is calculating the affected command by providing directly changed files, list of files delimited by commas\n\n### head\n\nType: string\n\nLatest commit of the current branch (usually HEAD)\n\n### help\n\nType: boolean\n\nShow help\n\n### nx-bail\n\nType: boolean\n\nDefault: false\n\nStop command execution after the first failed task\n\n### nx-ignore-cycles\n\nType: boolean\n\nDefault: false\n\nIgnore cycles in the task graph\n\n### ~~only-failed~~\n\nType: boolean\n\nDefault: false\n\n**Deprecated:** The command to rerun failed projects will appear if projects fail. This now does nothing and will be removed in v15.\n\nIsolate projects which previously failed\n\n### plain\n\nProduces a plain output for affected:apps and affected:libs\n\n### runner\n\nType: string\n\nThis is the name of the tasks runner configured in nx.json\n\n### skip-nx-cache\n\nType: boolean\n\nDefault: false\n\nRerun the tasks even when the results are available in the cache\n\n### uncommitted\n\nType: boolean\n\nUncommitted changes\n\n### untracked\n\nType: boolean\n\nUntracked changes\n\n### verbose\n\nPrint additional error stack trace on failure\n\n### version\n\nType: boolean\n\nShow version number\n"
|
||||
},
|
||||
{
|
||||
"name": "affected:libs",
|
||||
"id": "affected-libs",
|
||||
"file": "generated/cli/affected-libs",
|
||||
"content": "---\ntitle: 'affected:libs - CLI command'\ndescription: 'Print libraries affected by changes'\n---\n\n# affected:libs\n\nPrint libraries affected by changes\n\n## Usage\n\n```bash\nnx affected:libs\n```\n\n[Install `nx` globally](/getting-started/nx-setup#install-nx) to invoke the command directly using `nx`, or use `npx nx`, `yarn nx`, or `pnpx nx`.\n\n### Examples\n\nPrint the names of all the libs affected by changing the index.ts file:\n\n```bash\nnx affected:libs --files=libs/mylib/src/index.ts\n```\n\nPrint the names of all the libs affected by the changes between main and HEAD (e.g., PR):\n\n```bash\nnx affected:libs --base=main --head=HEAD\n```\n\nPrint the names of all the libs affected by the last commit on main:\n\n```bash\nnx affected:libs --base=main~1 --head=main\n```\n\n## Options\n\n### all\n\nType: boolean\n\nAll projects\n\n### base\n\nType: string\n\nBase of the current branch (usually main)\n\n### configuration\n\nType: string\n\nThis is the configuration to use when performing tasks on projects\n\n### exclude\n\nType: array\n\nDefault: []\n\nExclude certain projects from being processed\n\n### files\n\nType: array\n\nChange the way Nx is calculating the affected command by providing directly changed files, list of files delimited by commas\n\n### head\n\nType: string\n\nLatest commit of the current branch (usually HEAD)\n\n### help\n\nType: boolean\n\nShow help\n\n### nx-bail\n\nType: boolean\n\nDefault: false\n\nStop command execution after the first failed task\n\n### ~~only-failed~~\n\nType: boolean\n\nDefault: false\n\n**Deprecated:** The command to rerun failed projects will appear if projects fail. This now does nothing and will be removed in v15.\n\nIsolate projects which previously failed\n\n### plain\n\nProduces a plain output for affected:apps and affected:libs\n\n### runner\n\nType: string\n\nThis is the name of the tasks runner configured in nx.json\n\n### skip-nx-cache\n\nType: boolean\n\nDefault: false\n\nRerun the tasks even when the results are available in the cache\n\n### uncommitted\n\nType: boolean\n\nUncommitted changes\n\n### untracked\n\nType: boolean\n\nUntracked changes\n\n### verbose\n\nPrint additional error stack trace on failure\n\n### version\n\nType: boolean\n\nShow version number\n"
|
||||
"content": "---\ntitle: 'affected:libs - CLI command'\ndescription: 'Print libraries affected by changes'\n---\n\n# affected:libs\n\nPrint libraries affected by changes\n\n## Usage\n\n```bash\nnx affected:libs\n```\n\n[Install `nx` globally](/getting-started/nx-setup#install-nx) to invoke the command directly using `nx`, or use `npx nx`, `yarn nx`, or `pnpx nx`.\n\n### Examples\n\nPrint the names of all the libs affected by changing the index.ts file:\n\n```bash\nnx affected:libs --files=libs/mylib/src/index.ts\n```\n\nPrint the names of all the libs affected by the changes between main and HEAD (e.g., PR):\n\n```bash\nnx affected:libs --base=main --head=HEAD\n```\n\nPrint the names of all the libs affected by the last commit on main:\n\n```bash\nnx affected:libs --base=main~1 --head=main\n```\n\n## Options\n\n### all\n\nType: boolean\n\nAll projects\n\n### base\n\nType: string\n\nBase of the current branch (usually main)\n\n### configuration\n\nType: string\n\nThis is the configuration to use when performing tasks on projects\n\n### exclude\n\nType: array\n\nDefault: []\n\nExclude certain projects from being processed\n\n### files\n\nType: array\n\nChange the way Nx is calculating the affected command by providing directly changed files, list of files delimited by commas\n\n### head\n\nType: string\n\nLatest commit of the current branch (usually HEAD)\n\n### help\n\nType: boolean\n\nShow help\n\n### nx-bail\n\nType: boolean\n\nDefault: false\n\nStop command execution after the first failed task\n\n### nx-ignore-cycles\n\nType: boolean\n\nDefault: false\n\nIgnore cycles in the task graph\n\n### ~~only-failed~~\n\nType: boolean\n\nDefault: false\n\n**Deprecated:** The command to rerun failed projects will appear if projects fail. This now does nothing and will be removed in v15.\n\nIsolate projects which previously failed\n\n### plain\n\nProduces a plain output for affected:apps and affected:libs\n\n### runner\n\nType: string\n\nThis is the name of the tasks runner configured in nx.json\n\n### skip-nx-cache\n\nType: boolean\n\nDefault: false\n\nRerun the tasks even when the results are available in the cache\n\n### uncommitted\n\nType: boolean\n\nUncommitted changes\n\n### untracked\n\nType: boolean\n\nUntracked changes\n\n### verbose\n\nPrint additional error stack trace on failure\n\n### version\n\nType: boolean\n\nShow version number\n"
|
||||
},
|
||||
{
|
||||
"name": "print-affected",
|
||||
"id": "print-affected",
|
||||
"file": "generated/cli/print-affected",
|
||||
"content": "---\ntitle: 'print-affected - CLI command'\ndescription: 'Prints information about the projects and targets affected by changes'\n---\n\n# print-affected\n\nPrints information about the projects and targets affected by changes\n\n## Usage\n\n```bash\nnx print-affected\n```\n\n[Install `nx` globally](/getting-started/nx-setup#install-nx) to invoke the command directly using `nx`, or use `npx nx`, `yarn nx`, or `pnpx nx`.\n\n### Examples\n\nPrint information about affected projects and the project graph:\n\n```bash\nnx print-affected\n```\n\nPrint information about the projects affected by the changes between main and HEAD (e.g,. PR):\n\n```bash\nnx print-affected --base=main --head=HEAD\n```\n\nPrints information about the affected projects and a list of tasks to test them:\n\n```bash\nnx print-affected --target=test\n```\n\nPrints the projects property from the print-affected output:\n\n```bash\nnx print-affected --target=build --select=projects\n```\n\nPrints the tasks.target.project property from the print-affected output:\n\n```bash\nnx print-affected --target=build --select=tasks.target.project\n```\n\n## Options\n\n### all\n\nType: boolean\n\nAll projects\n\n### base\n\nType: string\n\nBase of the current branch (usually main)\n\n### configuration\n\nType: string\n\nThis is the configuration to use when performing tasks on projects\n\n### exclude\n\nType: array\n\nDefault: []\n\nExclude certain projects from being processed\n\n### files\n\nType: array\n\nChange the way Nx is calculating the affected command by providing directly changed files, list of files delimited by commas\n\n### head\n\nType: string\n\nLatest commit of the current branch (usually HEAD)\n\n### help\n\nType: boolean\n\nShow help\n\n### nx-bail\n\nType: boolean\n\nDefault: false\n\nStop command execution after the first failed task\n\n### ~~only-failed~~\n\nType: boolean\n\nDefault: false\n\n**Deprecated:** The command to rerun failed projects will appear if projects fail. This now does nothing and will be removed in v15.\n\nIsolate projects which previously failed\n\n### runner\n\nType: string\n\nThis is the name of the tasks runner configured in nx.json\n\n### select\n\nType: string\n\nSelect the subset of the returned json document (e.g., --selected=projects)\n\n### skip-nx-cache\n\nType: boolean\n\nDefault: false\n\nRerun the tasks even when the results are available in the cache\n\n### uncommitted\n\nType: boolean\n\nUncommitted changes\n\n### untracked\n\nType: boolean\n\nUntracked changes\n\n### verbose\n\nPrint additional error stack trace on failure\n\n### version\n\nType: boolean\n\nShow version number\n"
|
||||
"content": "---\ntitle: 'print-affected - CLI command'\ndescription: 'Prints information about the projects and targets affected by changes'\n---\n\n# print-affected\n\nPrints information about the projects and targets affected by changes\n\n## Usage\n\n```bash\nnx print-affected\n```\n\n[Install `nx` globally](/getting-started/nx-setup#install-nx) to invoke the command directly using `nx`, or use `npx nx`, `yarn nx`, or `pnpx nx`.\n\n### Examples\n\nPrint information about affected projects and the project graph:\n\n```bash\nnx print-affected\n```\n\nPrint information about the projects affected by the changes between main and HEAD (e.g,. PR):\n\n```bash\nnx print-affected --base=main --head=HEAD\n```\n\nPrints information about the affected projects and a list of tasks to test them:\n\n```bash\nnx print-affected --target=test\n```\n\nPrints the projects property from the print-affected output:\n\n```bash\nnx print-affected --target=build --select=projects\n```\n\nPrints the tasks.target.project property from the print-affected output:\n\n```bash\nnx print-affected --target=build --select=tasks.target.project\n```\n\n## Options\n\n### all\n\nType: boolean\n\nAll projects\n\n### base\n\nType: string\n\nBase of the current branch (usually main)\n\n### configuration\n\nType: string\n\nThis is the configuration to use when performing tasks on projects\n\n### exclude\n\nType: array\n\nDefault: []\n\nExclude certain projects from being processed\n\n### files\n\nType: array\n\nChange the way Nx is calculating the affected command by providing directly changed files, list of files delimited by commas\n\n### head\n\nType: string\n\nLatest commit of the current branch (usually HEAD)\n\n### help\n\nType: boolean\n\nShow help\n\n### nx-bail\n\nType: boolean\n\nDefault: false\n\nStop command execution after the first failed task\n\n### nx-ignore-cycles\n\nType: boolean\n\nDefault: false\n\nIgnore cycles in the task graph\n\n### ~~only-failed~~\n\nType: boolean\n\nDefault: false\n\n**Deprecated:** The command to rerun failed projects will appear if projects fail. This now does nothing and will be removed in v15.\n\nIsolate projects which previously failed\n\n### runner\n\nType: string\n\nThis is the name of the tasks runner configured in nx.json\n\n### select\n\nType: string\n\nSelect the subset of the returned json document (e.g., --selected=projects)\n\n### skip-nx-cache\n\nType: boolean\n\nDefault: false\n\nRerun the tasks even when the results are available in the cache\n\n### uncommitted\n\nType: boolean\n\nUncommitted changes\n\n### untracked\n\nType: boolean\n\nUntracked changes\n\n### verbose\n\nPrint additional error stack trace on failure\n\n### version\n\nType: boolean\n\nShow version number\n"
|
||||
},
|
||||
{
|
||||
"name": "format:check",
|
||||
"id": "format-check",
|
||||
"file": "generated/cli/format-check",
|
||||
"content": "---\ntitle: 'format:check - CLI command'\ndescription: 'Check for un-formatted files'\n---\n\n# format:check\n\nCheck for un-formatted files\n\n## Usage\n\n```bash\nnx format:check\n```\n\n[Install `nx` globally](/getting-started/nx-setup#install-nx) to invoke the command directly using `nx`, or use `npx nx`, `yarn nx`, or `pnpx nx`.\n\n## Options\n\n### all\n\nType: boolean\n\nAll projects\n\n### base\n\nType: string\n\nBase of the current branch (usually main)\n\n### configuration\n\nType: string\n\nThis is the configuration to use when performing tasks on projects\n\n### exclude\n\nType: array\n\nDefault: []\n\nExclude certain projects from being processed\n\n### files\n\nType: array\n\nChange the way Nx is calculating the affected command by providing directly changed files, list of files delimited by commas\n\n### head\n\nType: string\n\nLatest commit of the current branch (usually HEAD)\n\n### help\n\nType: boolean\n\nShow help\n\n### libs-and-apps\n\nType: boolean\n\nFormat only libraries and applications files.\n\n### nx-bail\n\nType: boolean\n\nDefault: false\n\nStop command execution after the first failed task\n\n### ~~only-failed~~\n\nType: boolean\n\nDefault: false\n\n**Deprecated:** The command to rerun failed projects will appear if projects fail. This now does nothing and will be removed in v15.\n\nIsolate projects which previously failed\n\n### projects\n\nType: array\n\nProjects to format (comma delimited)\n\n### runner\n\nType: string\n\nThis is the name of the tasks runner configured in nx.json\n\n### skip-nx-cache\n\nType: boolean\n\nDefault: false\n\nRerun the tasks even when the results are available in the cache\n\n### uncommitted\n\nType: boolean\n\nUncommitted changes\n\n### untracked\n\nType: boolean\n\nUntracked changes\n\n### verbose\n\nPrint additional error stack trace on failure\n\n### version\n\nType: boolean\n\nShow version number\n"
|
||||
"content": "---\ntitle: 'format:check - CLI command'\ndescription: 'Check for un-formatted files'\n---\n\n# format:check\n\nCheck for un-formatted files\n\n## Usage\n\n```bash\nnx format:check\n```\n\n[Install `nx` globally](/getting-started/nx-setup#install-nx) to invoke the command directly using `nx`, or use `npx nx`, `yarn nx`, or `pnpx nx`.\n\n## Options\n\n### all\n\nType: boolean\n\nAll projects\n\n### base\n\nType: string\n\nBase of the current branch (usually main)\n\n### configuration\n\nType: string\n\nThis is the configuration to use when performing tasks on projects\n\n### exclude\n\nType: array\n\nDefault: []\n\nExclude certain projects from being processed\n\n### files\n\nType: array\n\nChange the way Nx is calculating the affected command by providing directly changed files, list of files delimited by commas\n\n### head\n\nType: string\n\nLatest commit of the current branch (usually HEAD)\n\n### help\n\nType: boolean\n\nShow help\n\n### libs-and-apps\n\nType: boolean\n\nFormat only libraries and applications files.\n\n### nx-bail\n\nType: boolean\n\nDefault: false\n\nStop command execution after the first failed task\n\n### nx-ignore-cycles\n\nType: boolean\n\nDefault: false\n\nIgnore cycles in the task graph\n\n### ~~only-failed~~\n\nType: boolean\n\nDefault: false\n\n**Deprecated:** The command to rerun failed projects will appear if projects fail. This now does nothing and will be removed in v15.\n\nIsolate projects which previously failed\n\n### projects\n\nType: array\n\nProjects to format (comma delimited)\n\n### runner\n\nType: string\n\nThis is the name of the tasks runner configured in nx.json\n\n### skip-nx-cache\n\nType: boolean\n\nDefault: false\n\nRerun the tasks even when the results are available in the cache\n\n### uncommitted\n\nType: boolean\n\nUncommitted changes\n\n### untracked\n\nType: boolean\n\nUntracked changes\n\n### verbose\n\nPrint additional error stack trace on failure\n\n### version\n\nType: boolean\n\nShow version number\n"
|
||||
},
|
||||
{
|
||||
"name": "format:write",
|
||||
"id": "format-write",
|
||||
"file": "generated/cli/format-write",
|
||||
"content": "---\ntitle: 'format:write - CLI command'\ndescription: 'Overwrite un-formatted files'\n---\n\n# format:write\n\nOverwrite un-formatted files\n\n## Usage\n\n```bash\nnx format:write\n```\n\n[Install `nx` globally](/getting-started/nx-setup#install-nx) to invoke the command directly using `nx`, or use `npx nx`, `yarn nx`, or `pnpx nx`.\n\n## Options\n\n### all\n\nType: boolean\n\nAll projects\n\n### base\n\nType: string\n\nBase of the current branch (usually main)\n\n### configuration\n\nType: string\n\nThis is the configuration to use when performing tasks on projects\n\n### exclude\n\nType: array\n\nDefault: []\n\nExclude certain projects from being processed\n\n### files\n\nType: array\n\nChange the way Nx is calculating the affected command by providing directly changed files, list of files delimited by commas\n\n### head\n\nType: string\n\nLatest commit of the current branch (usually HEAD)\n\n### help\n\nType: boolean\n\nShow help\n\n### libs-and-apps\n\nType: boolean\n\nFormat only libraries and applications files.\n\n### nx-bail\n\nType: boolean\n\nDefault: false\n\nStop command execution after the first failed task\n\n### ~~only-failed~~\n\nType: boolean\n\nDefault: false\n\n**Deprecated:** The command to rerun failed projects will appear if projects fail. This now does nothing and will be removed in v15.\n\nIsolate projects which previously failed\n\n### projects\n\nType: array\n\nProjects to format (comma delimited)\n\n### runner\n\nType: string\n\nThis is the name of the tasks runner configured in nx.json\n\n### skip-nx-cache\n\nType: boolean\n\nDefault: false\n\nRerun the tasks even when the results are available in the cache\n\n### uncommitted\n\nType: boolean\n\nUncommitted changes\n\n### untracked\n\nType: boolean\n\nUntracked changes\n\n### verbose\n\nPrint additional error stack trace on failure\n\n### version\n\nType: boolean\n\nShow version number\n"
|
||||
"content": "---\ntitle: 'format:write - CLI command'\ndescription: 'Overwrite un-formatted files'\n---\n\n# format:write\n\nOverwrite un-formatted files\n\n## Usage\n\n```bash\nnx format:write\n```\n\n[Install `nx` globally](/getting-started/nx-setup#install-nx) to invoke the command directly using `nx`, or use `npx nx`, `yarn nx`, or `pnpx nx`.\n\n## Options\n\n### all\n\nType: boolean\n\nAll projects\n\n### base\n\nType: string\n\nBase of the current branch (usually main)\n\n### configuration\n\nType: string\n\nThis is the configuration to use when performing tasks on projects\n\n### exclude\n\nType: array\n\nDefault: []\n\nExclude certain projects from being processed\n\n### files\n\nType: array\n\nChange the way Nx is calculating the affected command by providing directly changed files, list of files delimited by commas\n\n### head\n\nType: string\n\nLatest commit of the current branch (usually HEAD)\n\n### help\n\nType: boolean\n\nShow help\n\n### libs-and-apps\n\nType: boolean\n\nFormat only libraries and applications files.\n\n### nx-bail\n\nType: boolean\n\nDefault: false\n\nStop command execution after the first failed task\n\n### nx-ignore-cycles\n\nType: boolean\n\nDefault: false\n\nIgnore cycles in the task graph\n\n### ~~only-failed~~\n\nType: boolean\n\nDefault: false\n\n**Deprecated:** The command to rerun failed projects will appear if projects fail. This now does nothing and will be removed in v15.\n\nIsolate projects which previously failed\n\n### projects\n\nType: array\n\nProjects to format (comma delimited)\n\n### runner\n\nType: string\n\nThis is the name of the tasks runner configured in nx.json\n\n### skip-nx-cache\n\nType: boolean\n\nDefault: false\n\nRerun the tasks even when the results are available in the cache\n\n### uncommitted\n\nType: boolean\n\nUncommitted changes\n\n### untracked\n\nType: boolean\n\nUntracked changes\n\n### verbose\n\nPrint additional error stack trace on failure\n\n### version\n\nType: boolean\n\nShow version number\n"
|
||||
},
|
||||
{
|
||||
"name": "migrate",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -96,13 +96,6 @@ describe('run-one', () => {
|
||||
);
|
||||
}, 10000);
|
||||
|
||||
it('should error for invalid configurations', () => {
|
||||
const myapp = uniq('app');
|
||||
runCLI(`generate @nrwl/react:app ${myapp}`);
|
||||
// configuration has to be valid for the initiating project
|
||||
expect(() => runCLI(`build ${myapp} -c=invalid`)).toThrow();
|
||||
}, 10000);
|
||||
|
||||
describe('target dependencies', () => {
|
||||
let myapp;
|
||||
let mylib1;
|
||||
|
||||
@ -434,6 +434,11 @@ function withAffectedOptions(yargs: yargs.Argv): yargs.Argv {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
})
|
||||
.option('nx-ignore-cycles', {
|
||||
describe: 'Ignore cycles in the task graph',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
})
|
||||
.conflicts({
|
||||
files: ['uncommitted', 'untracked', 'base', 'head', 'all'],
|
||||
untracked: ['uncommitted', 'files', 'base', 'head', 'all'],
|
||||
@ -499,6 +504,11 @@ function withRunManyOptions(yargs: yargs.Argv): yargs.Argv {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
})
|
||||
.option('nx-ignore-cycles', {
|
||||
describe: 'Ignore cycles in the task graph',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
})
|
||||
.conflicts({
|
||||
all: 'projects',
|
||||
});
|
||||
@ -654,6 +664,11 @@ function withRunOneOptions(yargs: yargs.Argv) {
|
||||
describe: 'Stop command execution after the first failed task',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
})
|
||||
.option('nx-ignore-cycles', {
|
||||
describe: 'Ignore cycles in the task graph',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
});
|
||||
|
||||
if (executorShouldShowHelp) {
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { createTask } from '../tasks-runner/run-command';
|
||||
import { getCommandAsString, getOutputs } from '../tasks-runner/utils';
|
||||
import * as yargs from 'yargs';
|
||||
import type { NxArgs } from '../utils/command-line-utils';
|
||||
import { ProjectGraph, ProjectGraphProjectNode } from '../config/project-graph';
|
||||
import { Task } from '../config/task-graph';
|
||||
import { Environment } from './read-environment';
|
||||
import { ProcessTasks } from 'nx/src/tasks-runner/create-task-graph';
|
||||
|
||||
export async function printAffected(
|
||||
affectedProjectsWithTargetAndConfig: ProjectGraphProjectNode[],
|
||||
@ -40,14 +40,21 @@ async function createTasks(
|
||||
overrides: yargs.Arguments
|
||||
) {
|
||||
const tasks: Task[] = affectedProjectsWithTargetAndConfig.map(
|
||||
(affectedProject) =>
|
||||
createTask({
|
||||
project: affectedProject,
|
||||
target: nxArgs.target,
|
||||
configuration: nxArgs.configuration,
|
||||
overrides,
|
||||
errorIfCannotFindConfiguration: false,
|
||||
})
|
||||
(affectedProject) => {
|
||||
const p = new ProcessTasks({}, projectGraph);
|
||||
const resolvedConfiguration = p.resolveConfiguration(
|
||||
affectedProject,
|
||||
nxArgs.target,
|
||||
nxArgs.configuration
|
||||
);
|
||||
return p.createTask(
|
||||
p.getId(affectedProject.name, nxArgs.target, resolvedConfiguration),
|
||||
affectedProject,
|
||||
nxArgs.target,
|
||||
resolvedConfiguration,
|
||||
overrides
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
return tasks.map((task, index) => ({
|
||||
|
||||
@ -1,397 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`TaskGraphCreator (tasks with dependency configurations) should create a task graph (builds depend on builds of dependencies even with intermediate projects) 1`] = `
|
||||
Object {
|
||||
"dependencies": Object {
|
||||
"app1:build": Array [
|
||||
"common2:build",
|
||||
],
|
||||
"common2:build": Array [],
|
||||
},
|
||||
"roots": Array [
|
||||
"common2:build",
|
||||
],
|
||||
"tasks": Object {
|
||||
"app1:build": Object {
|
||||
"id": "app1:build",
|
||||
"overrides": Object {},
|
||||
"projectRoot": "app1-root",
|
||||
"target": Object {
|
||||
"configuration": undefined,
|
||||
"project": "app1",
|
||||
"target": "build",
|
||||
},
|
||||
},
|
||||
"common2:build": Object {
|
||||
"id": "common2:build",
|
||||
"overrides": Object {},
|
||||
"projectRoot": "common2-root",
|
||||
"target": Object {
|
||||
"configuration": undefined,
|
||||
"project": "common2",
|
||||
"target": "build",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`TaskGraphCreator (tasks with dependency configurations) should create a task graph (builds depend on builds of dependencies with intermediate projects and circular dependencies between projects) 2 1`] = `
|
||||
Object {
|
||||
"dependencies": Object {
|
||||
"app1:build": Array [
|
||||
"common3:build",
|
||||
],
|
||||
"common3:build": Array [],
|
||||
},
|
||||
"roots": Array [
|
||||
"common3:build",
|
||||
],
|
||||
"tasks": Object {
|
||||
"app1:build": Object {
|
||||
"id": "app1:build",
|
||||
"overrides": Object {},
|
||||
"projectRoot": "app1-root",
|
||||
"target": Object {
|
||||
"configuration": undefined,
|
||||
"project": "app1",
|
||||
"target": "build",
|
||||
},
|
||||
},
|
||||
"common3:build": Object {
|
||||
"id": "common3:build",
|
||||
"overrides": Object {},
|
||||
"projectRoot": "common3",
|
||||
"target": Object {
|
||||
"configuration": undefined,
|
||||
"project": "common3",
|
||||
"target": "build",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`TaskGraphCreator (tasks with dependency configurations) should create task graph (builds depend on build of dependencies and prebuild of self) 1`] = `
|
||||
Object {
|
||||
"dependencies": Object {
|
||||
"app1:build": Array [
|
||||
"common1:build",
|
||||
"app1:prebuild",
|
||||
],
|
||||
"app1:prebuild": Array [],
|
||||
"app2:build": Array [
|
||||
"common2:build",
|
||||
"app2:prebuild",
|
||||
],
|
||||
"app2:prebuild": Array [],
|
||||
"common1:build": Array [],
|
||||
"common2:build": Array [],
|
||||
},
|
||||
"roots": Array [
|
||||
"common1:build",
|
||||
"app1:prebuild",
|
||||
"common2:build",
|
||||
"app2:prebuild",
|
||||
],
|
||||
"tasks": Object {
|
||||
"app1:build": Object {
|
||||
"id": "app1:build",
|
||||
"overrides": Object {},
|
||||
"projectRoot": "app1-root",
|
||||
"target": Object {
|
||||
"configuration": undefined,
|
||||
"project": "app1",
|
||||
"target": "build",
|
||||
},
|
||||
},
|
||||
"app1:prebuild": Object {
|
||||
"id": "app1:prebuild",
|
||||
"overrides": Object {},
|
||||
"projectRoot": "app1-root",
|
||||
"target": Object {
|
||||
"configuration": undefined,
|
||||
"project": "app1",
|
||||
"target": "prebuild",
|
||||
},
|
||||
},
|
||||
"app2:build": Object {
|
||||
"id": "app2:build",
|
||||
"overrides": Object {},
|
||||
"projectRoot": "app2-root",
|
||||
"target": Object {
|
||||
"configuration": undefined,
|
||||
"project": "app2",
|
||||
"target": "build",
|
||||
},
|
||||
},
|
||||
"app2:prebuild": Object {
|
||||
"id": "app2:prebuild",
|
||||
"overrides": Object {},
|
||||
"projectRoot": "app2-root",
|
||||
"target": Object {
|
||||
"configuration": undefined,
|
||||
"project": "app2",
|
||||
"target": "prebuild",
|
||||
},
|
||||
},
|
||||
"common1:build": Object {
|
||||
"id": "common1:build",
|
||||
"overrides": Object {},
|
||||
"projectRoot": "common1-root",
|
||||
"target": Object {
|
||||
"configuration": undefined,
|
||||
"project": "common1",
|
||||
"target": "build",
|
||||
},
|
||||
},
|
||||
"common2:build": Object {
|
||||
"id": "common2:build",
|
||||
"overrides": Object {},
|
||||
"projectRoot": "common2-root",
|
||||
"target": Object {
|
||||
"configuration": undefined,
|
||||
"project": "common2",
|
||||
"target": "build",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`TaskGraphCreator (tasks with dependency configurations) should create task graph (builds depend on build of dependencies) 1`] = `
|
||||
Object {
|
||||
"dependencies": Object {
|
||||
"app1:build": Array [
|
||||
"common1:build",
|
||||
],
|
||||
"app2:build": Array [
|
||||
"common2:build",
|
||||
],
|
||||
"common1:build": Array [],
|
||||
"common2:build": Array [],
|
||||
},
|
||||
"roots": Array [
|
||||
"common1:build",
|
||||
"common2:build",
|
||||
],
|
||||
"tasks": Object {
|
||||
"app1:build": Object {
|
||||
"id": "app1:build",
|
||||
"overrides": Object {},
|
||||
"projectRoot": "app1-root",
|
||||
"target": Object {
|
||||
"configuration": undefined,
|
||||
"project": "app1",
|
||||
"target": "build",
|
||||
},
|
||||
},
|
||||
"app2:build": Object {
|
||||
"id": "app2:build",
|
||||
"overrides": Object {},
|
||||
"projectRoot": "app2-root",
|
||||
"target": Object {
|
||||
"configuration": undefined,
|
||||
"project": "app2",
|
||||
"target": "build",
|
||||
},
|
||||
},
|
||||
"common1:build": Object {
|
||||
"id": "common1:build",
|
||||
"overrides": Object {},
|
||||
"projectRoot": "common1-root",
|
||||
"target": Object {
|
||||
"configuration": undefined,
|
||||
"project": "common1",
|
||||
"target": "build",
|
||||
},
|
||||
},
|
||||
"common2:build": Object {
|
||||
"id": "common2:build",
|
||||
"overrides": Object {},
|
||||
"projectRoot": "common2-root",
|
||||
"target": Object {
|
||||
"configuration": undefined,
|
||||
"project": "common2",
|
||||
"target": "build",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`TaskGraphCreator (tasks with dependency configurations) should create task graph (builds depend on build of dependencies, builds depend on prebuilds) 1`] = `
|
||||
Object {
|
||||
"dependencies": Object {
|
||||
"app1:build": Array [
|
||||
"common1:build",
|
||||
"app1:prebuild",
|
||||
],
|
||||
"app1:prebuild": Array [
|
||||
"common1:build",
|
||||
],
|
||||
"app2:build": Array [
|
||||
"common2:build",
|
||||
"app2:prebuild",
|
||||
],
|
||||
"app2:prebuild": Array [
|
||||
"common2:build",
|
||||
],
|
||||
"common1:build": Array [],
|
||||
"common2:build": Array [],
|
||||
},
|
||||
"roots": Array [
|
||||
"common1:build",
|
||||
"common2:build",
|
||||
],
|
||||
"tasks": Object {
|
||||
"app1:build": Object {
|
||||
"id": "app1:build",
|
||||
"overrides": Object {},
|
||||
"projectRoot": "app1-root",
|
||||
"target": Object {
|
||||
"configuration": undefined,
|
||||
"project": "app1",
|
||||
"target": "build",
|
||||
},
|
||||
},
|
||||
"app1:prebuild": Object {
|
||||
"id": "app1:prebuild",
|
||||
"overrides": Object {},
|
||||
"projectRoot": "app1-root",
|
||||
"target": Object {
|
||||
"configuration": undefined,
|
||||
"project": "app1",
|
||||
"target": "prebuild",
|
||||
},
|
||||
},
|
||||
"app2:build": Object {
|
||||
"id": "app2:build",
|
||||
"overrides": Object {},
|
||||
"projectRoot": "app2-root",
|
||||
"target": Object {
|
||||
"configuration": undefined,
|
||||
"project": "app2",
|
||||
"target": "build",
|
||||
},
|
||||
},
|
||||
"app2:prebuild": Object {
|
||||
"id": "app2:prebuild",
|
||||
"overrides": Object {},
|
||||
"projectRoot": "app2-root",
|
||||
"target": Object {
|
||||
"configuration": undefined,
|
||||
"project": "app2",
|
||||
"target": "prebuild",
|
||||
},
|
||||
},
|
||||
"common1:build": Object {
|
||||
"id": "common1:build",
|
||||
"overrides": Object {},
|
||||
"projectRoot": "common1-root",
|
||||
"target": Object {
|
||||
"configuration": undefined,
|
||||
"project": "common1",
|
||||
"target": "build",
|
||||
},
|
||||
},
|
||||
"common2:build": Object {
|
||||
"id": "common2:build",
|
||||
"overrides": Object {},
|
||||
"projectRoot": "common2-root",
|
||||
"target": Object {
|
||||
"configuration": undefined,
|
||||
"project": "common2",
|
||||
"target": "build",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`TaskGraphCreator (tasks with dependency configurations) should create task graph (builds depend on build of dependencies, builds depend on prebuilds) 2`] = `
|
||||
Object {
|
||||
"dependencies": Object {
|
||||
"app1:build": Array [
|
||||
"common1:build",
|
||||
],
|
||||
"app2:build": Array [
|
||||
"common2:build",
|
||||
],
|
||||
"common1:build": Array [
|
||||
"common1:prebuild",
|
||||
],
|
||||
"common1:prebuild": Array [],
|
||||
"common2:build": Array [
|
||||
"common2:prebuild",
|
||||
],
|
||||
"common2:prebuild": Array [],
|
||||
},
|
||||
"roots": Array [
|
||||
"common1:prebuild",
|
||||
"common2:prebuild",
|
||||
],
|
||||
"tasks": Object {
|
||||
"app1:build": Object {
|
||||
"id": "app1:build",
|
||||
"overrides": Object {},
|
||||
"projectRoot": "app1-root",
|
||||
"target": Object {
|
||||
"configuration": undefined,
|
||||
"project": "app1",
|
||||
"target": "build",
|
||||
},
|
||||
},
|
||||
"app2:build": Object {
|
||||
"id": "app2:build",
|
||||
"overrides": Object {},
|
||||
"projectRoot": "app2-root",
|
||||
"target": Object {
|
||||
"configuration": undefined,
|
||||
"project": "app2",
|
||||
"target": "build",
|
||||
},
|
||||
},
|
||||
"common1:build": Object {
|
||||
"id": "common1:build",
|
||||
"overrides": Object {},
|
||||
"projectRoot": "common1-root",
|
||||
"target": Object {
|
||||
"configuration": undefined,
|
||||
"project": "common1",
|
||||
"target": "build",
|
||||
},
|
||||
},
|
||||
"common1:prebuild": Object {
|
||||
"id": "common1:prebuild",
|
||||
"overrides": Object {},
|
||||
"projectRoot": "common1-root",
|
||||
"target": Object {
|
||||
"configuration": undefined,
|
||||
"project": "common1",
|
||||
"target": "prebuild",
|
||||
},
|
||||
},
|
||||
"common2:build": Object {
|
||||
"id": "common2:build",
|
||||
"overrides": Object {},
|
||||
"projectRoot": "common2-root",
|
||||
"target": Object {
|
||||
"configuration": undefined,
|
||||
"project": "common2",
|
||||
"target": "build",
|
||||
},
|
||||
},
|
||||
"common2:prebuild": Object {
|
||||
"id": "common2:prebuild",
|
||||
"overrides": Object {},
|
||||
"projectRoot": "common2-root",
|
||||
"target": Object {
|
||||
"configuration": undefined,
|
||||
"project": "common2",
|
||||
"target": "prebuild",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
568
packages/nx/src/tasks-runner/create-task-graph.spec.ts
Normal file
568
packages/nx/src/tasks-runner/create-task-graph.spec.ts
Normal file
@ -0,0 +1,568 @@
|
||||
import { ProjectGraph } from '../config/project-graph';
|
||||
import { createTaskGraph } from 'nx/src/tasks-runner/create-task-graph';
|
||||
|
||||
describe('createTaskGraph', () => {
|
||||
let projectGraph: ProjectGraph;
|
||||
beforeEach(() => {
|
||||
projectGraph = {
|
||||
nodes: {
|
||||
app1: {
|
||||
name: 'app1',
|
||||
type: 'app',
|
||||
data: {
|
||||
root: 'app1-root',
|
||||
files: [],
|
||||
targets: {
|
||||
prebuild: {},
|
||||
build: {
|
||||
dependsOn: [
|
||||
{
|
||||
projects: 'dependencies',
|
||||
target: 'build',
|
||||
},
|
||||
{
|
||||
projects: 'self',
|
||||
target: 'prebuild',
|
||||
},
|
||||
],
|
||||
},
|
||||
test: {},
|
||||
serve: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
lib1: {
|
||||
name: 'lib1',
|
||||
type: 'lib',
|
||||
data: {
|
||||
root: 'lib1-root',
|
||||
files: [],
|
||||
targets: {
|
||||
build: {},
|
||||
test: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
dependencies: {
|
||||
app1: [{ source: 'app1', target: 'lib1', type: 'static' }],
|
||||
lib1: [],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('should return an empty task for an empty project graph', () => {
|
||||
const tasks = createTaskGraph(
|
||||
{ nodes: {}, dependencies: {} },
|
||||
{},
|
||||
[],
|
||||
['test'],
|
||||
'development',
|
||||
{}
|
||||
);
|
||||
|
||||
expect(tasks).toEqual({
|
||||
roots: [],
|
||||
tasks: {},
|
||||
dependencies: {},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a task per project with the given target', () => {
|
||||
const oneTask = createTaskGraph(
|
||||
projectGraph,
|
||||
{},
|
||||
['app1'],
|
||||
['test'],
|
||||
'development',
|
||||
{
|
||||
a: 123,
|
||||
}
|
||||
);
|
||||
expect(oneTask).toEqual({
|
||||
roots: ['app1:test'],
|
||||
tasks: {
|
||||
'app1:test': {
|
||||
id: 'app1:test',
|
||||
target: {
|
||||
project: 'app1',
|
||||
target: 'test',
|
||||
},
|
||||
overrides: { a: 123 },
|
||||
projectRoot: 'app1-root',
|
||||
},
|
||||
},
|
||||
dependencies: {
|
||||
'app1:test': [],
|
||||
},
|
||||
});
|
||||
|
||||
const twoTasks = createTaskGraph(
|
||||
projectGraph,
|
||||
{},
|
||||
['app1', 'lib1'],
|
||||
['test'],
|
||||
'development',
|
||||
{
|
||||
a: 123,
|
||||
}
|
||||
);
|
||||
|
||||
expect(twoTasks).toEqual({
|
||||
roots: ['app1:test', 'lib1:test'],
|
||||
tasks: {
|
||||
'app1:test': {
|
||||
id: 'app1:test',
|
||||
target: {
|
||||
project: 'app1',
|
||||
target: 'test',
|
||||
},
|
||||
overrides: { a: 123 },
|
||||
projectRoot: 'app1-root',
|
||||
},
|
||||
'lib1:test': {
|
||||
id: 'lib1:test',
|
||||
target: {
|
||||
project: 'lib1',
|
||||
target: 'test',
|
||||
},
|
||||
overrides: { a: 123 },
|
||||
projectRoot: 'lib1-root',
|
||||
},
|
||||
},
|
||||
dependencies: {
|
||||
'app1:test': [],
|
||||
'lib1:test': [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should interpolate overrides', () => {
|
||||
const oneTask = createTaskGraph(
|
||||
projectGraph,
|
||||
{},
|
||||
['app1'],
|
||||
['test'],
|
||||
'development',
|
||||
{
|
||||
a: '--value={project.root}',
|
||||
}
|
||||
);
|
||||
expect(oneTask).toEqual({
|
||||
roots: ['app1:test'],
|
||||
tasks: {
|
||||
'app1:test': {
|
||||
id: 'app1:test',
|
||||
target: {
|
||||
project: 'app1',
|
||||
target: 'test',
|
||||
},
|
||||
overrides: { a: '--value=app1-root' },
|
||||
projectRoot: 'app1-root',
|
||||
},
|
||||
},
|
||||
dependencies: {
|
||||
'app1:test': [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should create graphs with dependencies', () => {
|
||||
const taskGraph = createTaskGraph(
|
||||
projectGraph,
|
||||
{},
|
||||
['app1'],
|
||||
['build'],
|
||||
'development',
|
||||
{}
|
||||
);
|
||||
// prebuild should also be in here
|
||||
expect(taskGraph).toEqual({
|
||||
roots: ['lib1:build', 'app1:prebuild'],
|
||||
tasks: {
|
||||
'app1:build': {
|
||||
id: 'app1:build',
|
||||
target: {
|
||||
project: 'app1',
|
||||
target: 'build',
|
||||
},
|
||||
overrides: {},
|
||||
projectRoot: 'app1-root',
|
||||
},
|
||||
'app1:prebuild': {
|
||||
id: 'app1:prebuild',
|
||||
target: {
|
||||
project: 'app1',
|
||||
target: 'prebuild',
|
||||
},
|
||||
overrides: {},
|
||||
projectRoot: 'app1-root',
|
||||
},
|
||||
'lib1:build': {
|
||||
id: 'lib1:build',
|
||||
target: {
|
||||
project: 'lib1',
|
||||
target: 'build',
|
||||
},
|
||||
overrides: {},
|
||||
projectRoot: 'lib1-root',
|
||||
},
|
||||
},
|
||||
dependencies: {
|
||||
'app1:build': ['lib1:build', 'app1:prebuild'],
|
||||
'app1:prebuild': [],
|
||||
'lib1:build': [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle diamond shape dependencies', () => {
|
||||
projectGraph = {
|
||||
nodes: {
|
||||
app1: {
|
||||
name: 'app1',
|
||||
type: 'app',
|
||||
data: {
|
||||
root: 'app1-root',
|
||||
files: [],
|
||||
targets: {
|
||||
build: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
lib1: {
|
||||
name: 'lib1',
|
||||
type: 'lib',
|
||||
data: {
|
||||
root: 'lib1-root',
|
||||
files: [],
|
||||
targets: {
|
||||
build: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
lib2: {
|
||||
name: 'lib2',
|
||||
type: 'lib',
|
||||
data: {
|
||||
root: 'lib2-root',
|
||||
files: [],
|
||||
targets: {
|
||||
build: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
lib3: {
|
||||
name: 'lib3',
|
||||
type: 'lib',
|
||||
data: {
|
||||
root: 'lib3-root',
|
||||
files: [],
|
||||
targets: {
|
||||
build: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
dependencies: {
|
||||
app1: [
|
||||
{ source: 'app1', target: 'lib1', type: 'static' },
|
||||
{ source: 'app1', target: 'lib2', type: 'static' },
|
||||
],
|
||||
lib1: [{ source: 'lib1', target: 'lib3', type: 'static' }],
|
||||
lib2: [{ source: 'lib2', target: 'lib3', type: 'static' }],
|
||||
lib3: [],
|
||||
},
|
||||
};
|
||||
|
||||
const taskGraph = createTaskGraph(
|
||||
projectGraph,
|
||||
{
|
||||
build: [
|
||||
{
|
||||
projects: 'dependencies',
|
||||
target: 'build',
|
||||
},
|
||||
],
|
||||
},
|
||||
['app1'],
|
||||
['build'],
|
||||
'development',
|
||||
{}
|
||||
);
|
||||
// prebuild should also be in here
|
||||
expect(taskGraph).toEqual({
|
||||
roots: ['lib3:build'],
|
||||
tasks: {
|
||||
'app1:build': {
|
||||
id: 'app1:build',
|
||||
target: {
|
||||
project: 'app1',
|
||||
target: 'build',
|
||||
},
|
||||
overrides: {},
|
||||
projectRoot: 'app1-root',
|
||||
},
|
||||
'lib1:build': {
|
||||
id: 'lib1:build',
|
||||
target: {
|
||||
project: 'lib1',
|
||||
target: 'build',
|
||||
},
|
||||
overrides: {},
|
||||
projectRoot: 'lib1-root',
|
||||
},
|
||||
'lib2:build': {
|
||||
id: 'lib2:build',
|
||||
target: {
|
||||
project: 'lib2',
|
||||
target: 'build',
|
||||
},
|
||||
overrides: {},
|
||||
projectRoot: 'lib2-root',
|
||||
},
|
||||
'lib3:build': {
|
||||
id: 'lib3:build',
|
||||
target: {
|
||||
project: 'lib3',
|
||||
target: 'build',
|
||||
},
|
||||
overrides: {},
|
||||
projectRoot: 'lib3-root',
|
||||
},
|
||||
},
|
||||
dependencies: {
|
||||
'app1:build': ['lib1:build', 'lib2:build'],
|
||||
'lib1:build': ['lib3:build'],
|
||||
'lib2:build': ['lib3:build'],
|
||||
'lib3:build': [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle cycles within the same project', () => {
|
||||
projectGraph = {
|
||||
nodes: {
|
||||
app1: {
|
||||
name: 'app1',
|
||||
type: 'app',
|
||||
data: {
|
||||
root: 'app1-root',
|
||||
files: [],
|
||||
targets: {
|
||||
build: {
|
||||
dependsOn: [{ target: 'test', projects: 'self' }],
|
||||
},
|
||||
test: {
|
||||
dependsOn: [{ target: 'build', projects: 'self' }],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
dependencies: {},
|
||||
};
|
||||
|
||||
const taskGraph = createTaskGraph(
|
||||
projectGraph,
|
||||
{},
|
||||
['app1'],
|
||||
['build'],
|
||||
'development',
|
||||
{}
|
||||
);
|
||||
// prebuild should also be in here
|
||||
expect(taskGraph).toEqual({
|
||||
roots: [],
|
||||
tasks: {
|
||||
'app1:build': {
|
||||
id: 'app1:build',
|
||||
target: {
|
||||
project: 'app1',
|
||||
target: 'build',
|
||||
},
|
||||
overrides: {},
|
||||
projectRoot: 'app1-root',
|
||||
},
|
||||
'app1:test': {
|
||||
id: 'app1:test',
|
||||
target: {
|
||||
project: 'app1',
|
||||
target: 'test',
|
||||
},
|
||||
overrides: {},
|
||||
projectRoot: 'app1-root',
|
||||
},
|
||||
},
|
||||
dependencies: {
|
||||
'app1:build': ['app1:test'],
|
||||
'app1:test': ['app1:build'],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle cycles between projects (app1:build <-> app2 <-> app3:build)', () => {
|
||||
projectGraph = {
|
||||
nodes: {
|
||||
app1: {
|
||||
name: 'app1',
|
||||
type: 'app',
|
||||
data: {
|
||||
root: 'app1-root',
|
||||
files: [],
|
||||
targets: {
|
||||
build: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
app2: {
|
||||
name: 'app2',
|
||||
type: 'app',
|
||||
data: {
|
||||
root: 'app2-root',
|
||||
files: [],
|
||||
targets: {},
|
||||
},
|
||||
},
|
||||
app3: {
|
||||
name: 'app3',
|
||||
type: 'app',
|
||||
data: {
|
||||
root: 'app3-root',
|
||||
files: [],
|
||||
targets: {
|
||||
build: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
dependencies: {
|
||||
app1: [{ source: 'app1', target: 'app2', type: 'static' }],
|
||||
app2: [
|
||||
{ source: 'app2', target: 'app1', type: 'static' },
|
||||
{ source: 'app2', target: 'app3', type: 'static' },
|
||||
],
|
||||
app3: [{ source: 'app3', target: 'app2', type: 'static' }],
|
||||
},
|
||||
};
|
||||
|
||||
const taskGraph = createTaskGraph(
|
||||
projectGraph,
|
||||
{
|
||||
build: [{ target: 'build', projects: 'dependencies' }],
|
||||
},
|
||||
['app1'],
|
||||
['build'],
|
||||
'development',
|
||||
{}
|
||||
);
|
||||
expect(taskGraph).toEqual({
|
||||
roots: [],
|
||||
tasks: {
|
||||
'app1:build': {
|
||||
id: 'app1:build',
|
||||
target: {
|
||||
project: 'app1',
|
||||
target: 'build',
|
||||
},
|
||||
overrides: {},
|
||||
projectRoot: 'app1-root',
|
||||
},
|
||||
'app3:build': {
|
||||
id: 'app3:build',
|
||||
target: {
|
||||
project: 'app3',
|
||||
target: 'build',
|
||||
},
|
||||
overrides: {},
|
||||
projectRoot: 'app3-root',
|
||||
},
|
||||
},
|
||||
dependencies: {
|
||||
'app1:build': ['app3:build'],
|
||||
'app3:build': ['app1:build'],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle cycles between projects that do not create cycles between tasks (app1:build -> app2 <-> app3:build)``', () => {
|
||||
projectGraph = {
|
||||
nodes: {
|
||||
app1: {
|
||||
name: 'app1',
|
||||
type: 'app',
|
||||
data: {
|
||||
root: 'app1-root',
|
||||
files: [],
|
||||
targets: {
|
||||
build: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
app2: {
|
||||
name: 'app2',
|
||||
type: 'app',
|
||||
data: {
|
||||
root: 'app2-root',
|
||||
files: [],
|
||||
targets: {},
|
||||
},
|
||||
},
|
||||
app3: {
|
||||
name: 'app3',
|
||||
type: 'app',
|
||||
data: {
|
||||
root: 'app3-root',
|
||||
files: [],
|
||||
targets: {
|
||||
build: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
dependencies: {
|
||||
app1: [{ source: 'app1', target: 'app2', type: 'static' }],
|
||||
app2: [{ source: 'app2', target: 'app3', type: 'static' }],
|
||||
app3: [{ source: 'app3', target: 'app2', type: 'static' }],
|
||||
},
|
||||
};
|
||||
|
||||
const taskGraph = createTaskGraph(
|
||||
projectGraph,
|
||||
{
|
||||
build: [{ target: 'build', projects: 'dependencies' }],
|
||||
},
|
||||
['app1'],
|
||||
['build'],
|
||||
'development',
|
||||
{}
|
||||
);
|
||||
expect(taskGraph).toEqual({
|
||||
roots: ['app3:build'],
|
||||
tasks: {
|
||||
'app1:build': {
|
||||
id: 'app1:build',
|
||||
target: {
|
||||
project: 'app1',
|
||||
target: 'build',
|
||||
},
|
||||
overrides: {},
|
||||
projectRoot: 'app1-root',
|
||||
},
|
||||
'app3:build': {
|
||||
id: 'app3:build',
|
||||
target: {
|
||||
project: 'app3',
|
||||
target: 'build',
|
||||
},
|
||||
overrides: {},
|
||||
projectRoot: 'app3-root',
|
||||
},
|
||||
},
|
||||
dependencies: {
|
||||
'app1:build': ['app3:build'],
|
||||
'app3:build': [],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
241
packages/nx/src/tasks-runner/create-task-graph.ts
Normal file
241
packages/nx/src/tasks-runner/create-task-graph.ts
Normal file
@ -0,0 +1,241 @@
|
||||
import { ProjectGraph, ProjectGraphProjectNode } from '../config/project-graph';
|
||||
import { TargetDependencyConfig } from '../config/workspace-json-project-json';
|
||||
import { getDependencyConfigs } from './utils';
|
||||
import {
|
||||
projectHasTarget,
|
||||
projectHasTargetAndConfiguration,
|
||||
} from '../utils/project-graph-utils';
|
||||
import { Task, TaskGraph } from '../config/task-graph';
|
||||
|
||||
export class ProcessTasks {
|
||||
private readonly seen = new Set<string>();
|
||||
readonly tasks: { [id: string]: Task } = {};
|
||||
readonly dependencies: { [k: string]: string[] } = {};
|
||||
|
||||
constructor(
|
||||
private readonly defaultDependencyConfigs: Record<
|
||||
string,
|
||||
(TargetDependencyConfig | string)[]
|
||||
>,
|
||||
private readonly projectGraph: ProjectGraph
|
||||
) {}
|
||||
|
||||
processTasks(
|
||||
projectNames: string[],
|
||||
targets: string[],
|
||||
configuration: string,
|
||||
overrides: Object
|
||||
) {
|
||||
for (const projectName of projectNames) {
|
||||
for (const target of targets) {
|
||||
const resolvedConfiguration = this.resolveConfiguration(
|
||||
this.projectGraph.nodes[projectName],
|
||||
target,
|
||||
configuration
|
||||
);
|
||||
const id = this.getId(projectName, target, resolvedConfiguration);
|
||||
debugger;
|
||||
const task = this.createTask(
|
||||
id,
|
||||
this.projectGraph.nodes[projectName],
|
||||
target,
|
||||
resolvedConfiguration,
|
||||
overrides
|
||||
);
|
||||
this.tasks[task.id] = task;
|
||||
this.dependencies[task.id] = [];
|
||||
}
|
||||
}
|
||||
for (const taskId of Object.keys(this.tasks)) {
|
||||
const task = this.tasks[taskId];
|
||||
this.processTask(task, task.target.project, configuration);
|
||||
}
|
||||
return Object.keys(this.dependencies).filter(
|
||||
(d) => this.dependencies[d].length === 0
|
||||
);
|
||||
}
|
||||
|
||||
processTask(
|
||||
task: Task,
|
||||
projectUsedToDeriveDependencies: string,
|
||||
configuration: string
|
||||
) {
|
||||
const seenKey = `${task.id}-${projectUsedToDeriveDependencies}`;
|
||||
if (this.seen.has(seenKey)) {
|
||||
return;
|
||||
}
|
||||
this.seen.add(seenKey);
|
||||
|
||||
const dependencyConfigs = getDependencyConfigs(
|
||||
{ project: task.target.project, target: task.target.target },
|
||||
this.defaultDependencyConfigs,
|
||||
this.projectGraph
|
||||
);
|
||||
for (const dependencyConfig of dependencyConfigs) {
|
||||
if (dependencyConfig.projects === 'dependencies') {
|
||||
for (const dep of this.projectGraph.dependencies[
|
||||
projectUsedToDeriveDependencies
|
||||
]) {
|
||||
const depProject = this.projectGraph.nodes[
|
||||
dep.target
|
||||
] as ProjectGraphProjectNode;
|
||||
|
||||
// this is to handle external dependencies
|
||||
if (!depProject) continue;
|
||||
|
||||
if (projectHasTarget(depProject, dependencyConfig.target)) {
|
||||
const resolvedConfiguration = this.resolveConfiguration(
|
||||
depProject,
|
||||
dependencyConfig.target,
|
||||
configuration
|
||||
);
|
||||
const depTargetId = this.getId(
|
||||
depProject.name,
|
||||
dependencyConfig.target,
|
||||
resolvedConfiguration
|
||||
);
|
||||
|
||||
if (task.id !== depTargetId) {
|
||||
this.dependencies[task.id].push(depTargetId);
|
||||
}
|
||||
if (!this.tasks[depTargetId]) {
|
||||
const newTask = this.createTask(
|
||||
depTargetId,
|
||||
depProject,
|
||||
dependencyConfig.target,
|
||||
resolvedConfiguration,
|
||||
{}
|
||||
);
|
||||
this.tasks[depTargetId] = newTask;
|
||||
this.dependencies[depTargetId] = [];
|
||||
|
||||
this.processTask(newTask, newTask.target.project, configuration);
|
||||
}
|
||||
} else {
|
||||
this.processTask(task, depProject.name, configuration);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const selfProject = this.projectGraph.nodes[
|
||||
task.target.project
|
||||
] as ProjectGraphProjectNode;
|
||||
|
||||
if (projectHasTarget(selfProject, dependencyConfig.target)) {
|
||||
const resolvedConfiguration = this.resolveConfiguration(
|
||||
selfProject,
|
||||
dependencyConfig.target,
|
||||
configuration
|
||||
);
|
||||
const selfTaskId = this.getId(
|
||||
selfProject.name,
|
||||
dependencyConfig.target,
|
||||
resolvedConfiguration
|
||||
);
|
||||
if (task.id !== selfTaskId) {
|
||||
this.dependencies[task.id].push(selfTaskId);
|
||||
}
|
||||
if (!this.tasks[selfTaskId]) {
|
||||
const newTask = this.createTask(
|
||||
selfTaskId,
|
||||
selfProject,
|
||||
dependencyConfig.target,
|
||||
resolvedConfiguration,
|
||||
{}
|
||||
);
|
||||
this.tasks[selfTaskId] = newTask;
|
||||
this.dependencies[selfTaskId] = [];
|
||||
this.processTask(newTask, newTask.target.project, configuration);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createTask(
|
||||
id: string,
|
||||
project: ProjectGraphProjectNode,
|
||||
target: string,
|
||||
resolvedConfiguration: string | undefined,
|
||||
overrides: Object
|
||||
): Task {
|
||||
const qualifiedTarget = {
|
||||
project: project.name,
|
||||
target,
|
||||
configuration: resolvedConfiguration,
|
||||
};
|
||||
|
||||
return {
|
||||
id,
|
||||
target: qualifiedTarget,
|
||||
projectRoot: project.data.root,
|
||||
overrides: interpolateOverrides(overrides, project.name, project.data),
|
||||
};
|
||||
}
|
||||
|
||||
resolveConfiguration(
|
||||
project: ProjectGraphProjectNode,
|
||||
target: string,
|
||||
configuration: string | undefined
|
||||
) {
|
||||
configuration ??= project.data.targets?.[target]?.defaultConfiguration;
|
||||
return projectHasTargetAndConfiguration(project, target, configuration)
|
||||
? configuration
|
||||
: undefined;
|
||||
}
|
||||
|
||||
getId(
|
||||
project: string,
|
||||
target: string,
|
||||
configuration: string | undefined
|
||||
): string {
|
||||
let id = `${project}:${target}`;
|
||||
if (configuration) {
|
||||
id += `:${configuration}`;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
export function createTaskGraph(
|
||||
projectGraph: ProjectGraph,
|
||||
defaultDependencyConfigs: Record<
|
||||
string,
|
||||
(TargetDependencyConfig | string)[]
|
||||
> = {},
|
||||
projectNames: string[],
|
||||
targets: string[],
|
||||
configuration: string | undefined,
|
||||
overrides: Object
|
||||
): TaskGraph {
|
||||
const p = new ProcessTasks(defaultDependencyConfigs, projectGraph);
|
||||
const roots = p.processTasks(projectNames, targets, configuration, overrides);
|
||||
return {
|
||||
roots,
|
||||
tasks: p.tasks,
|
||||
dependencies: p.dependencies,
|
||||
};
|
||||
}
|
||||
|
||||
function interpolateOverrides<T = any>(
|
||||
args: T,
|
||||
projectName: string,
|
||||
projectMetadata: any
|
||||
): T {
|
||||
const interpolatedArgs: T = { ...args };
|
||||
Object.entries(interpolatedArgs).forEach(([name, value]) => {
|
||||
if (typeof value === 'string') {
|
||||
const regex = /{project\.([^}]+)}/g;
|
||||
interpolatedArgs[name] = value.replace(regex, (_, group: string) => {
|
||||
if (group.includes('.')) {
|
||||
throw new Error('Only top-level properties can be interpolated');
|
||||
}
|
||||
|
||||
if (group === 'name') {
|
||||
return projectName;
|
||||
}
|
||||
return projectMetadata[group];
|
||||
});
|
||||
}
|
||||
});
|
||||
return interpolatedArgs;
|
||||
}
|
||||
@ -1,12 +1,11 @@
|
||||
import { TasksRunner, TaskStatus } from './tasks-runner';
|
||||
import { TaskOrchestrator } from './task-orchestrator';
|
||||
import { performance } from 'perf_hooks';
|
||||
import { TaskGraphCreator } from './task-graph-creator';
|
||||
import { Hasher } from '../hasher/hasher';
|
||||
import { LifeCycle } from './life-cycle';
|
||||
import { ProjectGraph } from '../config/project-graph';
|
||||
import { NxJsonConfiguration } from '../config/nx-json';
|
||||
import { Task } from '../config/task-graph';
|
||||
import { Task, TaskGraph } from '../config/task-graph';
|
||||
import { NxArgs } from '../utils/command-line-utils';
|
||||
|
||||
export interface RemoteCache {
|
||||
@ -37,6 +36,7 @@ export const defaultTasksRunner: TasksRunner<
|
||||
projectGraph: ProjectGraph;
|
||||
nxJson: NxJsonConfiguration;
|
||||
nxArgs: NxArgs;
|
||||
taskGraph: TaskGraph;
|
||||
}
|
||||
): Promise<{ [id: string]: TaskStatus }> => {
|
||||
if (
|
||||
@ -72,17 +72,10 @@ async function runAllTasks(
|
||||
projectGraph: ProjectGraph;
|
||||
nxJson: NxJsonConfiguration;
|
||||
nxArgs: NxArgs;
|
||||
taskGraph: TaskGraph;
|
||||
}
|
||||
): Promise<{ [id: string]: TaskStatus }> {
|
||||
const defaultTargetDependencies = context.nxJson.targetDependencies ?? {};
|
||||
|
||||
const taskGraphCreator = new TaskGraphCreator(
|
||||
context.projectGraph,
|
||||
defaultTargetDependencies
|
||||
);
|
||||
|
||||
const taskGraph = taskGraphCreator.createTaskGraph(tasks);
|
||||
|
||||
// TODO: vsavkin: remove this after Nx 16
|
||||
performance.mark('task-graph-created');
|
||||
|
||||
performance.measure('nx-prep-work', 'init-local', 'task-graph-created');
|
||||
@ -98,7 +91,7 @@ async function runAllTasks(
|
||||
hasher,
|
||||
context.initiatingProject,
|
||||
context.projectGraph,
|
||||
taskGraph,
|
||||
context.taskGraph,
|
||||
options,
|
||||
context.nxArgs?.nxBail
|
||||
);
|
||||
|
||||
@ -1,858 +1,7 @@
|
||||
import { TasksRunner } from './tasks-runner';
|
||||
import defaultTaskRunner from './default-tasks-runner';
|
||||
import { createTasksForProjectToRun, getRunner } from './run-command';
|
||||
import { DependencyType, ProjectGraph } from '../config/project-graph';
|
||||
import { getRunner } from './run-command';
|
||||
import { NxJsonConfiguration } from '../config/nx-json';
|
||||
|
||||
describe('createTasksForProjectToRun', () => {
|
||||
let projectGraph: ProjectGraph;
|
||||
beforeEach(() => {
|
||||
projectGraph = {
|
||||
nodes: {
|
||||
app1: {
|
||||
name: 'app1',
|
||||
type: 'app',
|
||||
data: {
|
||||
root: 'app1-root',
|
||||
files: [],
|
||||
targets: {
|
||||
prebuild: {},
|
||||
build: {},
|
||||
serve: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
lib1: {
|
||||
name: 'lib1',
|
||||
type: 'lib',
|
||||
data: {
|
||||
root: 'lib1-root',
|
||||
files: [],
|
||||
targets: {
|
||||
build: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
dependencies: {
|
||||
app1: [],
|
||||
lib1: [],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('should create the task for the project and target passed', () => {
|
||||
const tasks = createTasksForProjectToRun(
|
||||
[projectGraph.nodes.app1],
|
||||
{
|
||||
target: 'serve',
|
||||
configuration: undefined,
|
||||
overrides: {},
|
||||
},
|
||||
projectGraph,
|
||||
null
|
||||
);
|
||||
|
||||
expect(tasks).toEqual([
|
||||
{
|
||||
id: 'app1:serve',
|
||||
overrides: {},
|
||||
projectRoot: 'app1-root',
|
||||
target: {
|
||||
configuration: undefined,
|
||||
project: 'app1',
|
||||
target: 'serve',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should create the task for multiple projects passed', () => {
|
||||
const tasks = createTasksForProjectToRun(
|
||||
[projectGraph.nodes.app1, projectGraph.nodes.lib1],
|
||||
{
|
||||
target: 'build',
|
||||
configuration: undefined,
|
||||
overrides: {},
|
||||
},
|
||||
projectGraph,
|
||||
null
|
||||
);
|
||||
|
||||
expect(tasks).toEqual([
|
||||
{
|
||||
id: 'app1:build',
|
||||
overrides: {},
|
||||
projectRoot: 'app1-root',
|
||||
target: {
|
||||
configuration: undefined,
|
||||
project: 'app1',
|
||||
target: 'build',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'lib1:build',
|
||||
overrides: {},
|
||||
projectRoot: 'lib1-root',
|
||||
target: {
|
||||
configuration: undefined,
|
||||
project: 'lib1',
|
||||
target: 'build',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should create the tasks for multiple projects passed with configuration', () => {
|
||||
projectGraph.nodes.app1.data.targets.build.configurations =
|
||||
projectGraph.nodes.lib1.data.targets.build.configurations = {
|
||||
production: {},
|
||||
};
|
||||
const tasks = createTasksForProjectToRun(
|
||||
[projectGraph.nodes.app1, projectGraph.nodes.lib1],
|
||||
{
|
||||
target: 'build',
|
||||
configuration: 'production',
|
||||
overrides: {},
|
||||
},
|
||||
projectGraph,
|
||||
null
|
||||
);
|
||||
|
||||
expect(tasks).toEqual([
|
||||
{
|
||||
id: 'app1:build:production',
|
||||
overrides: {},
|
||||
projectRoot: 'app1-root',
|
||||
target: {
|
||||
configuration: 'production',
|
||||
project: 'app1',
|
||||
target: 'build',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'lib1:build:production',
|
||||
overrides: {},
|
||||
projectRoot: 'lib1-root',
|
||||
target: {
|
||||
configuration: 'production',
|
||||
project: 'lib1',
|
||||
target: 'build',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should create the tasks for multiple projects passed with configuration and fallback to default configuration', () => {
|
||||
projectGraph.nodes.app1.data.targets.build.configurations = {
|
||||
production: {},
|
||||
};
|
||||
const tasks = createTasksForProjectToRun(
|
||||
[projectGraph.nodes.app1, projectGraph.nodes.lib1],
|
||||
{
|
||||
target: 'build',
|
||||
configuration: 'production',
|
||||
overrides: {},
|
||||
},
|
||||
projectGraph,
|
||||
null
|
||||
);
|
||||
|
||||
expect(tasks).toEqual([
|
||||
{
|
||||
id: 'app1:build:production',
|
||||
overrides: {},
|
||||
projectRoot: 'app1-root',
|
||||
target: {
|
||||
configuration: 'production',
|
||||
project: 'app1',
|
||||
target: 'build',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'lib1:build',
|
||||
overrides: {},
|
||||
projectRoot: 'lib1-root',
|
||||
target: {
|
||||
project: 'lib1',
|
||||
target: 'build',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should create tasks for self dependencies', () => {
|
||||
projectGraph.nodes.app1.data.targets.serve.dependsOn = [
|
||||
{
|
||||
target: 'build',
|
||||
projects: 'self',
|
||||
},
|
||||
];
|
||||
const tasks = createTasksForProjectToRun(
|
||||
[projectGraph.nodes.app1],
|
||||
{
|
||||
target: 'serve',
|
||||
configuration: undefined,
|
||||
overrides: {},
|
||||
},
|
||||
projectGraph,
|
||||
projectGraph.nodes.app1.name
|
||||
);
|
||||
|
||||
expect(tasks).toEqual([
|
||||
{
|
||||
id: 'app1:build',
|
||||
overrides: {},
|
||||
projectRoot: 'app1-root',
|
||||
target: {
|
||||
configuration: undefined,
|
||||
project: 'app1',
|
||||
target: 'build',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'app1:serve',
|
||||
overrides: {},
|
||||
projectRoot: 'app1-root',
|
||||
target: {
|
||||
configuration: undefined,
|
||||
project: 'app1',
|
||||
target: 'serve',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should create tasks for target dependencies', () => {
|
||||
projectGraph.nodes.app1.data.targets.build.dependsOn = [
|
||||
{
|
||||
target: 'build',
|
||||
projects: 'dependencies',
|
||||
},
|
||||
];
|
||||
projectGraph.dependencies.app1.push({
|
||||
type: DependencyType.static,
|
||||
source: 'app1',
|
||||
target: 'lib1',
|
||||
});
|
||||
const tasks = createTasksForProjectToRun(
|
||||
[projectGraph.nodes.app1],
|
||||
{
|
||||
target: 'build',
|
||||
configuration: undefined,
|
||||
overrides: {},
|
||||
},
|
||||
projectGraph,
|
||||
projectGraph.nodes.app1.name
|
||||
);
|
||||
|
||||
expect(tasks).toEqual([
|
||||
{
|
||||
id: 'lib1:build',
|
||||
overrides: {},
|
||||
projectRoot: 'lib1-root',
|
||||
target: {
|
||||
configuration: undefined,
|
||||
project: 'lib1',
|
||||
target: 'build',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'app1:build',
|
||||
overrides: {},
|
||||
projectRoot: 'app1-root',
|
||||
target: {
|
||||
configuration: undefined,
|
||||
project: 'app1',
|
||||
target: 'build',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should create tasks for multiple sets of dependencies', () => {
|
||||
projectGraph.nodes.app1.data.targets.build.dependsOn = [
|
||||
{
|
||||
target: 'prebuild',
|
||||
projects: 'self',
|
||||
},
|
||||
{
|
||||
target: 'build',
|
||||
projects: 'dependencies',
|
||||
},
|
||||
];
|
||||
projectGraph.dependencies.app1.push({
|
||||
type: DependencyType.static,
|
||||
source: 'app1',
|
||||
target: 'lib1',
|
||||
});
|
||||
const tasks = createTasksForProjectToRun(
|
||||
[projectGraph.nodes.app1],
|
||||
{
|
||||
target: 'build',
|
||||
configuration: undefined,
|
||||
overrides: {},
|
||||
},
|
||||
projectGraph,
|
||||
projectGraph.nodes.app1.name
|
||||
);
|
||||
|
||||
expect(tasks).toEqual([
|
||||
{
|
||||
id: 'app1:prebuild',
|
||||
overrides: {},
|
||||
projectRoot: 'app1-root',
|
||||
target: {
|
||||
configuration: undefined,
|
||||
project: 'app1',
|
||||
target: 'prebuild',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'lib1:build',
|
||||
overrides: {},
|
||||
projectRoot: 'lib1-root',
|
||||
target: {
|
||||
configuration: undefined,
|
||||
project: 'lib1',
|
||||
target: 'build',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'app1:build',
|
||||
overrides: {},
|
||||
projectRoot: 'app1-root',
|
||||
target: {
|
||||
configuration: undefined,
|
||||
project: 'app1',
|
||||
target: 'build',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should create tasks for multiple sets of dependencies for multiple targets', () => {
|
||||
projectGraph.nodes.app1.data.targets.build.dependsOn = [
|
||||
{
|
||||
target: 'prebuild',
|
||||
projects: 'dependencies',
|
||||
},
|
||||
{
|
||||
target: 'build',
|
||||
projects: 'dependencies',
|
||||
},
|
||||
];
|
||||
projectGraph.dependencies.app1.push({
|
||||
type: DependencyType.static,
|
||||
source: 'app1',
|
||||
target: 'lib1',
|
||||
});
|
||||
|
||||
projectGraph.nodes.lib1.data.targets.prebuild = {};
|
||||
|
||||
const tasks = createTasksForProjectToRun(
|
||||
[projectGraph.nodes.app1],
|
||||
{
|
||||
target: 'build',
|
||||
configuration: undefined,
|
||||
overrides: {},
|
||||
},
|
||||
projectGraph,
|
||||
projectGraph.nodes.app1.name
|
||||
);
|
||||
|
||||
expect(tasks).toEqual([
|
||||
{
|
||||
id: 'lib1:prebuild',
|
||||
overrides: {},
|
||||
projectRoot: 'lib1-root',
|
||||
target: {
|
||||
configuration: undefined,
|
||||
project: 'lib1',
|
||||
target: 'prebuild',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'lib1:build',
|
||||
overrides: {},
|
||||
projectRoot: 'lib1-root',
|
||||
target: {
|
||||
configuration: undefined,
|
||||
project: 'lib1',
|
||||
target: 'build',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'app1:build',
|
||||
overrides: {},
|
||||
projectRoot: 'app1-root',
|
||||
target: {
|
||||
configuration: undefined,
|
||||
project: 'app1',
|
||||
target: 'build',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should include dependencies of projects without the same target', () => {
|
||||
// App 1 depends on builds of its dependencies
|
||||
projectGraph.nodes.app1.data.targets.build.dependsOn = [
|
||||
{
|
||||
target: 'build',
|
||||
projects: 'dependencies',
|
||||
},
|
||||
];
|
||||
|
||||
// App 1 depends on Lib 1
|
||||
projectGraph.dependencies.app1.push({
|
||||
type: DependencyType.static,
|
||||
source: 'app1',
|
||||
target: 'lib1',
|
||||
});
|
||||
|
||||
// Lib 1 does not have build but depends on Lib 2
|
||||
delete projectGraph.nodes.lib1.data.targets.build;
|
||||
projectGraph.dependencies.lib1.push({
|
||||
type: DependencyType.static,
|
||||
source: 'lib1',
|
||||
target: 'lib2',
|
||||
});
|
||||
|
||||
// Lib 2 has a build
|
||||
projectGraph.nodes.lib2 = {
|
||||
name: 'lib2',
|
||||
type: 'lib',
|
||||
data: {
|
||||
root: 'lib2-root',
|
||||
files: [],
|
||||
targets: {
|
||||
build: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
projectGraph.dependencies.lib2 = [];
|
||||
|
||||
const tasks = createTasksForProjectToRun(
|
||||
[projectGraph.nodes.app1],
|
||||
{
|
||||
target: 'build',
|
||||
configuration: undefined,
|
||||
overrides: {},
|
||||
},
|
||||
projectGraph,
|
||||
projectGraph.nodes.app1.name
|
||||
);
|
||||
|
||||
expect(tasks).toContainEqual({
|
||||
id: 'app1:build',
|
||||
target: { project: 'app1', target: 'build' },
|
||||
projectRoot: 'app1-root',
|
||||
overrides: {},
|
||||
});
|
||||
expect(tasks).toContainEqual({
|
||||
id: 'lib2:build',
|
||||
target: { project: 'lib2', target: 'build' },
|
||||
projectRoot: 'lib2-root',
|
||||
overrides: {},
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle APP1 <=> LIB1(no build) => LIB2', () => {
|
||||
// APP1 <=> LIB1 => LIB2
|
||||
// App 1 depends on builds of its dependencies
|
||||
projectGraph.nodes.app1.data.targets.build.dependsOn = [
|
||||
{
|
||||
target: 'build',
|
||||
projects: 'dependencies',
|
||||
},
|
||||
];
|
||||
|
||||
// App 1 depends on Lib 1
|
||||
projectGraph.dependencies.app1.push({
|
||||
type: DependencyType.static,
|
||||
source: 'app1',
|
||||
target: 'lib1',
|
||||
});
|
||||
|
||||
// Lib 1 does not have build but depends on Lib 2
|
||||
delete projectGraph.nodes.lib1.data.targets.build;
|
||||
projectGraph.dependencies.lib1.push(
|
||||
{
|
||||
type: DependencyType.static,
|
||||
source: 'lib1',
|
||||
target: 'app1',
|
||||
},
|
||||
{
|
||||
type: DependencyType.static,
|
||||
source: 'lib1',
|
||||
target: 'lib2',
|
||||
}
|
||||
);
|
||||
|
||||
// Lib 2 has a build
|
||||
projectGraph.nodes.lib2 = {
|
||||
name: 'lib2',
|
||||
type: 'lib',
|
||||
data: {
|
||||
root: 'lib2-root',
|
||||
files: [],
|
||||
targets: {
|
||||
build: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
projectGraph.dependencies.lib2 = [];
|
||||
|
||||
const tasks = createTasksForProjectToRun(
|
||||
[projectGraph.nodes.app1],
|
||||
{
|
||||
target: 'build',
|
||||
configuration: undefined,
|
||||
overrides: {},
|
||||
},
|
||||
projectGraph,
|
||||
projectGraph.nodes.app1.name
|
||||
);
|
||||
|
||||
expect(tasks).toContainEqual({
|
||||
id: 'app1:build',
|
||||
target: { project: 'app1', target: 'build' },
|
||||
projectRoot: 'app1-root',
|
||||
overrides: {},
|
||||
});
|
||||
expect(tasks).toContainEqual({
|
||||
id: 'lib2:build',
|
||||
target: { project: 'lib2', target: 'build' },
|
||||
projectRoot: 'lib2-root',
|
||||
overrides: {},
|
||||
});
|
||||
});
|
||||
|
||||
it('should error when APP1 <=> LIB1(no build) <=> LIB2', () => {
|
||||
jest.spyOn(process, 'exit').mockImplementation(() => {
|
||||
throw new Error();
|
||||
});
|
||||
|
||||
// Define Lib 2 with a build
|
||||
projectGraph.nodes.lib2 = {
|
||||
name: 'lib2',
|
||||
type: 'lib',
|
||||
data: {
|
||||
root: 'lib2-root',
|
||||
files: [],
|
||||
targets: {
|
||||
build: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Lib 1 does not have "build"
|
||||
delete projectGraph.nodes.lib1.data.targets.build;
|
||||
|
||||
// APP1 <=> LIB1 <=> LIB2
|
||||
projectGraph.nodes.app1.data.targets.build.dependsOn = [
|
||||
{
|
||||
target: 'build',
|
||||
projects: 'dependencies',
|
||||
},
|
||||
];
|
||||
|
||||
projectGraph.nodes.lib2.data.targets.build.dependsOn = [
|
||||
{
|
||||
target: 'build',
|
||||
projects: 'dependencies',
|
||||
},
|
||||
];
|
||||
|
||||
// App 1 depends on Lib 1
|
||||
projectGraph.dependencies.app1.push({
|
||||
type: DependencyType.static,
|
||||
source: 'app1',
|
||||
target: 'lib1',
|
||||
});
|
||||
|
||||
projectGraph.dependencies.lib1.push(
|
||||
{
|
||||
type: DependencyType.static,
|
||||
source: 'lib1',
|
||||
target: 'app1',
|
||||
},
|
||||
{
|
||||
type: DependencyType.static,
|
||||
source: 'lib1',
|
||||
target: 'lib2',
|
||||
}
|
||||
);
|
||||
|
||||
projectGraph.dependencies.lib2 = [
|
||||
{
|
||||
type: DependencyType.static,
|
||||
source: 'lib2',
|
||||
target: 'lib1',
|
||||
},
|
||||
];
|
||||
|
||||
try {
|
||||
createTasksForProjectToRun(
|
||||
[projectGraph.nodes.app1],
|
||||
{
|
||||
target: 'build',
|
||||
configuration: undefined,
|
||||
overrides: {},
|
||||
},
|
||||
projectGraph,
|
||||
projectGraph.nodes.app1.name
|
||||
);
|
||||
fail();
|
||||
} catch (e) {
|
||||
expect(process.exit).toHaveBeenCalledWith(1);
|
||||
}
|
||||
});
|
||||
|
||||
// Technically, this could work but it creates a lot of problems in the implementation,
|
||||
// so instead we error saying that the circular dependency cannot be handled.
|
||||
// xit('should handle APP1 => LIB1(no build) <=> LIB2', () => {
|
||||
// });
|
||||
|
||||
it('should throw an error for an invalid target', () => {
|
||||
jest.spyOn(process, 'exit').mockImplementation(() => {
|
||||
throw new Error();
|
||||
});
|
||||
try {
|
||||
createTasksForProjectToRun(
|
||||
[projectGraph.nodes.app1],
|
||||
{
|
||||
target: 'invalid',
|
||||
configuration: undefined,
|
||||
overrides: {},
|
||||
},
|
||||
projectGraph,
|
||||
null
|
||||
);
|
||||
fail();
|
||||
} catch (e) {
|
||||
expect(process.exit).toHaveBeenCalledWith(1);
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw an error for an invalid configuration for the initiating project', () => {
|
||||
jest.spyOn(process, 'exit').mockImplementation(() => {
|
||||
throw new Error();
|
||||
});
|
||||
try {
|
||||
createTasksForProjectToRun(
|
||||
[projectGraph.nodes.app1],
|
||||
{
|
||||
target: 'serve',
|
||||
configuration: 'invalid',
|
||||
overrides: {},
|
||||
},
|
||||
projectGraph,
|
||||
'app1'
|
||||
);
|
||||
fail();
|
||||
} catch (e) {
|
||||
expect(process.exit).toHaveBeenCalledWith(1);
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw an error for circular dependencies', () => {
|
||||
projectGraph.nodes.app1.data.targets.build.dependsOn = [
|
||||
{
|
||||
target: 'build',
|
||||
projects: 'dependencies',
|
||||
},
|
||||
];
|
||||
projectGraph.nodes.lib1.data.targets.build.dependsOn = [
|
||||
{
|
||||
target: 'build',
|
||||
projects: 'dependencies',
|
||||
},
|
||||
];
|
||||
projectGraph.dependencies.app1.push({
|
||||
type: DependencyType.static,
|
||||
source: 'app1',
|
||||
target: 'lib1',
|
||||
});
|
||||
projectGraph.dependencies.lib1.push({
|
||||
type: DependencyType.static,
|
||||
source: 'lib1',
|
||||
target: 'app1',
|
||||
});
|
||||
jest.spyOn(process, 'exit').mockImplementation(() => {
|
||||
throw new Error();
|
||||
});
|
||||
try {
|
||||
createTasksForProjectToRun(
|
||||
[projectGraph.nodes.app1],
|
||||
{
|
||||
target: 'build',
|
||||
configuration: undefined,
|
||||
overrides: {},
|
||||
},
|
||||
projectGraph,
|
||||
projectGraph.nodes.app1.name
|
||||
);
|
||||
fail();
|
||||
} catch (e) {
|
||||
expect(process.exit).toHaveBeenCalledWith(1);
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw an error for depending on a non-existent target of itself', () => {
|
||||
projectGraph.nodes.app1.data.targets.serve.dependsOn = [
|
||||
{
|
||||
target: 'non-existent',
|
||||
projects: 'self',
|
||||
},
|
||||
];
|
||||
jest.spyOn(process, 'exit').mockImplementation(() => {
|
||||
throw new Error();
|
||||
});
|
||||
try {
|
||||
const tasks = createTasksForProjectToRun(
|
||||
[projectGraph.nodes.app1],
|
||||
{
|
||||
target: 'serve',
|
||||
configuration: undefined,
|
||||
overrides: {},
|
||||
},
|
||||
projectGraph,
|
||||
projectGraph.nodes.app1.name
|
||||
);
|
||||
fail();
|
||||
} catch (e) {
|
||||
expect(process.exit).toHaveBeenCalledWith(1);
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw an error for circular dependencies in tasks', () => {
|
||||
projectGraph.nodes.app1.data.targets.build.dependsOn = [
|
||||
{
|
||||
target: 'serve',
|
||||
projects: 'self',
|
||||
},
|
||||
];
|
||||
projectGraph.nodes.app1.data.targets.serve.dependsOn = [
|
||||
{
|
||||
target: 'build',
|
||||
projects: 'self',
|
||||
},
|
||||
];
|
||||
jest.spyOn(process, 'exit').mockImplementation(() => {
|
||||
throw new Error();
|
||||
});
|
||||
try {
|
||||
const tasks = createTasksForProjectToRun(
|
||||
[projectGraph.nodes.app1],
|
||||
{
|
||||
target: 'build',
|
||||
configuration: undefined,
|
||||
overrides: {},
|
||||
},
|
||||
projectGraph,
|
||||
null
|
||||
);
|
||||
fail();
|
||||
} catch (e) {
|
||||
expect(process.exit).toHaveBeenCalledWith(1);
|
||||
}
|
||||
});
|
||||
|
||||
it('should forward overrides to tasks with the same target executor', () => {
|
||||
projectGraph.nodes.app1.data.targets.build.dependsOn = [
|
||||
{
|
||||
target: 'prebuild',
|
||||
projects: 'self',
|
||||
},
|
||||
{
|
||||
target: 'build',
|
||||
projects: 'dependencies',
|
||||
},
|
||||
];
|
||||
projectGraph.nodes.app1.data.targets.build.executor = 'executor1';
|
||||
projectGraph.nodes.lib1.data.targets.build.executor = 'executor1';
|
||||
projectGraph.nodes.app1.data.targets.prebuild.executor = 'executor2';
|
||||
projectGraph.nodes.lib2 = {
|
||||
data: {
|
||||
root: 'lib2-root',
|
||||
files: [],
|
||||
targets: { build: { executor: 'executor2' } },
|
||||
},
|
||||
name: 'lib2',
|
||||
type: 'lib',
|
||||
};
|
||||
projectGraph.dependencies.lib2 = [];
|
||||
projectGraph.dependencies.app1.push(
|
||||
{
|
||||
type: DependencyType.static,
|
||||
source: 'app1',
|
||||
target: 'lib1',
|
||||
},
|
||||
{
|
||||
type: DependencyType.static,
|
||||
source: 'app1',
|
||||
target: 'lib2',
|
||||
}
|
||||
);
|
||||
|
||||
const tasks = createTasksForProjectToRun(
|
||||
[projectGraph.nodes.app1],
|
||||
{
|
||||
target: 'build',
|
||||
configuration: undefined,
|
||||
overrides: { myFlag: 'flag value' },
|
||||
},
|
||||
projectGraph,
|
||||
projectGraph.nodes.app1.name
|
||||
);
|
||||
|
||||
expect(tasks).toEqual([
|
||||
{
|
||||
id: 'app1:prebuild',
|
||||
overrides: {},
|
||||
projectRoot: 'app1-root',
|
||||
target: {
|
||||
configuration: undefined,
|
||||
project: 'app1',
|
||||
target: 'prebuild',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'lib1:build',
|
||||
overrides: { myFlag: 'flag value' },
|
||||
projectRoot: 'lib1-root',
|
||||
target: {
|
||||
configuration: undefined,
|
||||
project: 'lib1',
|
||||
target: 'build',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'lib2:build',
|
||||
overrides: {},
|
||||
projectRoot: 'lib2-root',
|
||||
target: {
|
||||
configuration: undefined,
|
||||
project: 'lib2',
|
||||
target: 'build',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'app1:build',
|
||||
overrides: { myFlag: 'flag value' },
|
||||
projectRoot: 'app1-root',
|
||||
target: {
|
||||
configuration: undefined,
|
||||
project: 'app1',
|
||||
target: 'build',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRunner', () => {
|
||||
let nxJson: NxJsonConfiguration;
|
||||
let mockRunner: TasksRunner;
|
||||
@ -865,18 +14,6 @@ describe('getRunner', () => {
|
||||
mockRunner = jest.fn();
|
||||
});
|
||||
|
||||
it('gets a default runner when runner is not defined in the nx json', () => {
|
||||
const { tasksRunner, runnerOptions } = getRunner({}, nxJson);
|
||||
|
||||
expect(tasksRunner).toEqual(defaultTaskRunner);
|
||||
});
|
||||
|
||||
it('gets a default runner when default options are not configured', () => {
|
||||
const { tasksRunner, runnerOptions } = getRunner({}, nxJson);
|
||||
|
||||
expect(tasksRunner).toEqual(defaultTaskRunner);
|
||||
});
|
||||
|
||||
it('gets a custom task runner', () => {
|
||||
jest.mock('custom-runner', () => mockRunner, {
|
||||
virtual: true,
|
||||
|
||||
@ -3,12 +3,8 @@ import { join } from 'path';
|
||||
import { workspaceRoot } from '../utils/app-root';
|
||||
import { NxArgs } from '../utils/command-line-utils';
|
||||
import { isRelativePath } from '../utils/fileutils';
|
||||
import {
|
||||
projectHasTarget,
|
||||
projectHasTargetAndConfiguration,
|
||||
} from '../utils/project-graph-utils';
|
||||
import { output } from '../utils/output';
|
||||
import { getDependencyConfigs, shouldStreamOutput } from './utils';
|
||||
import { shouldStreamOutput } from './utils';
|
||||
import { CompositeLifeCycle, LifeCycle } from './life-cycle';
|
||||
import { StaticRunManyTerminalOutputLifeCycle } from './life-cycles/static-run-many-terminal-output-life-cycle';
|
||||
import { StaticRunOneTerminalOutputLifeCycle } from './life-cycles/static-run-one-terminal-output-life-cycle';
|
||||
@ -20,10 +16,8 @@ import { createRunOneDynamicOutputRenderer } from './life-cycles/dynamic-run-one
|
||||
import { ProjectGraph, ProjectGraphProjectNode } from '../config/project-graph';
|
||||
import { NxJsonConfiguration } from '../config/nx-json';
|
||||
import { Task } from '../config/task-graph';
|
||||
import {
|
||||
ProjectConfiguration,
|
||||
TargetDependencyConfig,
|
||||
} from '../config/workspace-json-project-json';
|
||||
import { createTaskGraph } from './create-task-graph';
|
||||
import { findCycle, makeAcyclic } from './task-graph-utils';
|
||||
|
||||
async function getTerminalOutputLifeCycle(
|
||||
initiatingProject: string,
|
||||
@ -90,19 +84,34 @@ export async function runCommand(
|
||||
const { tasksRunner, runnerOptions } = getRunner(nxArgs, nxJson);
|
||||
|
||||
const defaultDependencyConfigs = nxJson.targetDependencies;
|
||||
const tasks = createTasksForProjectToRun(
|
||||
projectsToRun,
|
||||
{
|
||||
target: nxArgs.target,
|
||||
configuration: nxArgs.configuration,
|
||||
overrides,
|
||||
},
|
||||
const projectNames = projectsToRun.map((t) => t.name);
|
||||
const taskGraph = createTaskGraph(
|
||||
projectGraph,
|
||||
initiatingProject,
|
||||
defaultDependencyConfigs
|
||||
defaultDependencyConfigs,
|
||||
projectNames,
|
||||
[nxArgs.target],
|
||||
nxArgs.configuration,
|
||||
overrides
|
||||
);
|
||||
|
||||
const projectNames = projectsToRun.map((t) => t.name);
|
||||
const cycle = findCycle(taskGraph);
|
||||
if (cycle) {
|
||||
if (nxArgs.nxIgnoreCycles) {
|
||||
output.warn({
|
||||
title: `The task graph has a circular dependency`,
|
||||
bodyLines: [`${cycle.join(' --> ')}`],
|
||||
});
|
||||
makeAcyclic(taskGraph);
|
||||
} else {
|
||||
output.error({
|
||||
title: `Could not execute command because the task graph has a circular dependency`,
|
||||
bodyLines: [`${cycle.join(' --> ')}`],
|
||||
});
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
const tasks = Object.values(taskGraph.tasks);
|
||||
if (nxArgs.outputStyle == 'stream') {
|
||||
process.env.NX_STREAM_OUTPUT = 'true';
|
||||
process.env.NX_PREFIX_OUTPUT = 'true';
|
||||
@ -138,6 +147,7 @@ export async function runCommand(
|
||||
projectGraph,
|
||||
nxJson,
|
||||
nxArgs,
|
||||
taskGraph,
|
||||
}
|
||||
);
|
||||
|
||||
@ -195,45 +205,6 @@ async function anyFailuresInObservable(obs: any) {
|
||||
});
|
||||
}
|
||||
|
||||
interface TaskParams {
|
||||
project: ProjectGraphProjectNode;
|
||||
target: string;
|
||||
configuration: string;
|
||||
overrides: Object;
|
||||
errorIfCannotFindConfiguration: boolean;
|
||||
}
|
||||
|
||||
export function createTasksForProjectToRun(
|
||||
projectsToRun: ProjectGraphProjectNode[],
|
||||
params: Omit<TaskParams, 'project' | 'errorIfCannotFindConfiguration'>,
|
||||
projectGraph: ProjectGraph,
|
||||
initiatingProject: string | null,
|
||||
defaultDependencyConfigs: Record<
|
||||
string,
|
||||
(TargetDependencyConfig | string)[]
|
||||
> = {}
|
||||
) {
|
||||
const tasksMap: Map<string, Task> = new Map<string, Task>();
|
||||
const seenSet = new Set<string>();
|
||||
|
||||
for (const project of projectsToRun) {
|
||||
addTasksForProjectTarget(
|
||||
{
|
||||
project,
|
||||
...params,
|
||||
errorIfCannotFindConfiguration: project.name === initiatingProject,
|
||||
},
|
||||
defaultDependencyConfigs,
|
||||
projectGraph,
|
||||
project.data.targets?.[params.target]?.executor,
|
||||
tasksMap,
|
||||
[],
|
||||
seenSet
|
||||
);
|
||||
}
|
||||
return Array.from(tasksMap.values());
|
||||
}
|
||||
|
||||
function shouldUseDynamicLifeCycle(
|
||||
tasks: Task[],
|
||||
options: any,
|
||||
@ -247,260 +218,6 @@ function shouldUseDynamicLifeCycle(
|
||||
return noForwarding;
|
||||
}
|
||||
|
||||
function addTasksForProjectTarget(
|
||||
{
|
||||
project,
|
||||
target,
|
||||
configuration,
|
||||
overrides,
|
||||
errorIfCannotFindConfiguration,
|
||||
}: TaskParams,
|
||||
defaultDependencyConfigs: Record<
|
||||
string,
|
||||
(TargetDependencyConfig | string)[]
|
||||
> = {},
|
||||
projectGraph: ProjectGraph,
|
||||
originalTargetExecutor: string,
|
||||
tasksMap: Map<string, Task>,
|
||||
path: { targetIdentifier: string; hasTarget: boolean }[],
|
||||
seenSet: Set<string>
|
||||
) {
|
||||
const task = createTask({
|
||||
project,
|
||||
target,
|
||||
configuration,
|
||||
overrides:
|
||||
project.data.targets?.[target]?.executor === originalTargetExecutor
|
||||
? overrides
|
||||
: {},
|
||||
errorIfCannotFindConfiguration,
|
||||
});
|
||||
|
||||
const dependencyConfigs = getDependencyConfigs(
|
||||
{ project: project.name, target },
|
||||
defaultDependencyConfigs,
|
||||
projectGraph
|
||||
);
|
||||
|
||||
if (dependencyConfigs) {
|
||||
for (const dependencyConfig of dependencyConfigs) {
|
||||
addTasksForProjectDependencyConfig(
|
||||
project,
|
||||
{
|
||||
target,
|
||||
configuration,
|
||||
overrides,
|
||||
},
|
||||
dependencyConfig,
|
||||
defaultDependencyConfigs,
|
||||
projectGraph,
|
||||
originalTargetExecutor,
|
||||
tasksMap,
|
||||
path,
|
||||
seenSet
|
||||
);
|
||||
}
|
||||
}
|
||||
tasksMap.set(task.id, task);
|
||||
}
|
||||
|
||||
export function createTask({
|
||||
project,
|
||||
target,
|
||||
configuration,
|
||||
overrides,
|
||||
errorIfCannotFindConfiguration,
|
||||
}: TaskParams): Task {
|
||||
if (!projectHasTarget(project, target)) {
|
||||
output.error({
|
||||
title: `Cannot find target '${target}' for project '${project.name}'`,
|
||||
});
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
configuration ??= project.data.targets?.[target]?.defaultConfiguration;
|
||||
|
||||
const config = projectHasTargetAndConfiguration(
|
||||
project,
|
||||
target,
|
||||
configuration
|
||||
)
|
||||
? configuration
|
||||
: undefined;
|
||||
|
||||
if (errorIfCannotFindConfiguration && configuration && !config) {
|
||||
output.error({
|
||||
title: `Cannot find configuration '${configuration}' for project '${project.name}:${target}'`,
|
||||
});
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const qualifiedTarget = {
|
||||
project: project.name,
|
||||
target,
|
||||
configuration: config,
|
||||
};
|
||||
return {
|
||||
id: getId(qualifiedTarget),
|
||||
target: qualifiedTarget,
|
||||
projectRoot: project.data.root,
|
||||
overrides: interpolateOverrides(overrides, project.name, project.data),
|
||||
};
|
||||
}
|
||||
|
||||
function addTasksForProjectDependencyConfig(
|
||||
project: ProjectGraphProjectNode<ProjectConfiguration>,
|
||||
{
|
||||
target,
|
||||
configuration,
|
||||
overrides,
|
||||
}: Pick<TaskParams, 'target' | 'configuration' | 'overrides'>,
|
||||
dependencyConfig: TargetDependencyConfig,
|
||||
defaultDependencyConfigs: Record<string, (TargetDependencyConfig | string)[]>,
|
||||
projectGraph: ProjectGraph,
|
||||
originalTargetExecutor: string,
|
||||
tasksMap: Map<string, Task>,
|
||||
path: { targetIdentifier: string; hasTarget: boolean }[],
|
||||
seenSet: Set<string>
|
||||
) {
|
||||
const targetIdentifier = getId({
|
||||
project: project.name,
|
||||
target,
|
||||
configuration,
|
||||
});
|
||||
|
||||
const pathFragment = {
|
||||
targetIdentifier,
|
||||
hasTarget: projectHasTarget(project, target),
|
||||
};
|
||||
|
||||
const newPath = [...path, pathFragment];
|
||||
seenSet.add(targetIdentifier);
|
||||
|
||||
if (tasksMap.has(targetIdentifier)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dependencyConfig.projects === 'dependencies') {
|
||||
const dependencies = projectGraph.dependencies[project.name];
|
||||
if (dependencies) {
|
||||
for (const dep of dependencies) {
|
||||
const depProject = projectGraph.nodes[
|
||||
dep.target
|
||||
] as ProjectGraphProjectNode;
|
||||
|
||||
if (
|
||||
depProject &&
|
||||
projectHasTarget(depProject, dependencyConfig.target)
|
||||
) {
|
||||
const depTargetId = getId({
|
||||
project: depProject.name,
|
||||
target: dependencyConfig.target,
|
||||
configuration: configuration,
|
||||
});
|
||||
exitOnCircularDep(newPath, depTargetId);
|
||||
if (seenSet.has(depTargetId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
addTasksForProjectTarget(
|
||||
{
|
||||
project: depProject,
|
||||
target: dependencyConfig.target,
|
||||
configuration,
|
||||
overrides,
|
||||
errorIfCannotFindConfiguration: false,
|
||||
},
|
||||
defaultDependencyConfigs,
|
||||
projectGraph,
|
||||
originalTargetExecutor,
|
||||
tasksMap,
|
||||
newPath,
|
||||
seenSet
|
||||
);
|
||||
} else {
|
||||
if (!depProject) {
|
||||
continue;
|
||||
}
|
||||
const depTargetId = getId({
|
||||
project: depProject.name,
|
||||
target: dependencyConfig.target,
|
||||
configuration: configuration,
|
||||
});
|
||||
|
||||
exitOnCircularDep(newPath, depTargetId);
|
||||
|
||||
if (seenSet.has(depTargetId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
addTasksForProjectDependencyConfig(
|
||||
depProject,
|
||||
{ target, configuration, overrides },
|
||||
dependencyConfig,
|
||||
defaultDependencyConfigs,
|
||||
projectGraph,
|
||||
originalTargetExecutor,
|
||||
tasksMap,
|
||||
newPath,
|
||||
seenSet
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (projectHasTarget(project, dependencyConfig.target)) {
|
||||
addTasksForProjectTarget(
|
||||
{
|
||||
project,
|
||||
target: dependencyConfig.target,
|
||||
configuration,
|
||||
overrides,
|
||||
errorIfCannotFindConfiguration: false,
|
||||
},
|
||||
defaultDependencyConfigs,
|
||||
projectGraph,
|
||||
originalTargetExecutor,
|
||||
tasksMap,
|
||||
newPath,
|
||||
seenSet
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function exitOnCircularDep(
|
||||
path: { targetIdentifier: string; hasTarget: boolean }[],
|
||||
targetIdentifier: string
|
||||
) {
|
||||
if (
|
||||
path.length > 0 &&
|
||||
path[path.length - 1].hasTarget &&
|
||||
path.filter((p) => p.targetIdentifier === targetIdentifier).length > 0
|
||||
) {
|
||||
const identifiers = path.map((p) => p.targetIdentifier);
|
||||
output.error({
|
||||
title: `Could not execute ${identifiers[0]} because it has a circular dependency`,
|
||||
bodyLines: [`${[...identifiers, targetIdentifier].join(' --> ')}`],
|
||||
});
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function getId({
|
||||
project,
|
||||
target,
|
||||
configuration,
|
||||
}: {
|
||||
project: string;
|
||||
target: string;
|
||||
configuration?: string;
|
||||
}): string {
|
||||
let id = `${project}:${target}`;
|
||||
if (configuration) {
|
||||
id += `:${configuration}`;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
export function getRunner(
|
||||
nxArgs: NxArgs,
|
||||
nxJson: NxJsonConfiguration
|
||||
@ -509,27 +226,7 @@ export function getRunner(
|
||||
runnerOptions: any;
|
||||
} {
|
||||
let runner = nxArgs.runner;
|
||||
|
||||
//TODO: vsavkin remove in Nx 12
|
||||
if (!nxJson.tasksRunnerOptions) {
|
||||
const t = require('./default-tasks-runner');
|
||||
return {
|
||||
tasksRunner: t.defaultTasksRunner,
|
||||
runnerOptions: nxArgs,
|
||||
};
|
||||
}
|
||||
|
||||
//TODO: vsavkin remove in Nx 12
|
||||
if (!runner && !nxJson.tasksRunnerOptions.default) {
|
||||
const t = require('./default-tasks-runner');
|
||||
return {
|
||||
tasksRunner: t.defaultTasksRunner,
|
||||
runnerOptions: nxArgs,
|
||||
};
|
||||
}
|
||||
|
||||
runner = runner || 'default';
|
||||
|
||||
if (nxJson.tasksRunnerOptions[runner]) {
|
||||
let modulePath: string = nxJson.tasksRunnerOptions[runner].runner;
|
||||
|
||||
@ -562,27 +259,3 @@ export function getRunner(
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function interpolateOverrides<T = any>(
|
||||
args: T,
|
||||
projectName: string,
|
||||
projectMetadata: any
|
||||
): T {
|
||||
const interpolatedArgs: T = { ...args };
|
||||
Object.entries(interpolatedArgs).forEach(([name, value]) => {
|
||||
if (typeof value === 'string') {
|
||||
const regex = /{project\.([^}]+)}/g;
|
||||
interpolatedArgs[name] = value.replace(regex, (_, group: string) => {
|
||||
if (group.includes('.')) {
|
||||
throw new Error('Only top-level properties can be interpolated');
|
||||
}
|
||||
|
||||
if (group === 'name') {
|
||||
return projectName;
|
||||
}
|
||||
return projectMetadata[group];
|
||||
});
|
||||
}
|
||||
});
|
||||
return interpolatedArgs;
|
||||
}
|
||||
|
||||
@ -1,484 +0,0 @@
|
||||
import { DependencyType, ProjectGraph } from '../config/project-graph';
|
||||
import { createTasksForProjectToRun } from './run-command';
|
||||
import { TaskGraphCreator } from './task-graph-creator';
|
||||
|
||||
describe('TaskGraphCreator', () => {
|
||||
it('should return empty for an empty array', () => {
|
||||
const empty = new TaskGraphCreator(
|
||||
{
|
||||
nodes: {},
|
||||
dependencies: {},
|
||||
},
|
||||
{}
|
||||
).createTaskGraph([]);
|
||||
expect(empty).toEqual({
|
||||
roots: [],
|
||||
tasks: {},
|
||||
dependencies: {},
|
||||
});
|
||||
});
|
||||
|
||||
it('should order tasks based on project dependencies', () => {
|
||||
const taskGraph = new TaskGraphCreator(
|
||||
{
|
||||
nodes: {
|
||||
child1: { type: 'lib' },
|
||||
child2: { type: 'lib' },
|
||||
parent: { type: 'lib' },
|
||||
grandparent: { type: 'lib' },
|
||||
} as any,
|
||||
dependencies: {
|
||||
child1: [],
|
||||
child2: [],
|
||||
parent: [
|
||||
{
|
||||
source: 'parent',
|
||||
target: 'child1',
|
||||
type: DependencyType.static,
|
||||
},
|
||||
{
|
||||
source: 'parent',
|
||||
target: 'child2',
|
||||
type: DependencyType.static,
|
||||
},
|
||||
],
|
||||
grandparent: [
|
||||
{
|
||||
source: 'grandparent',
|
||||
target: 'parent',
|
||||
type: DependencyType.static,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
build: [
|
||||
{
|
||||
target: 'build',
|
||||
projects: 'dependencies',
|
||||
},
|
||||
],
|
||||
}
|
||||
).createTaskGraph([
|
||||
{
|
||||
id: 'parent:build',
|
||||
target: { project: 'parent', target: 'build' },
|
||||
},
|
||||
{
|
||||
id: 'child1:build',
|
||||
target: { project: 'child1', target: 'build' },
|
||||
},
|
||||
{
|
||||
id: 'child2:build',
|
||||
target: { project: 'child2', target: 'build' },
|
||||
},
|
||||
{
|
||||
id: 'grandparent:build',
|
||||
target: { project: 'grandparent', target: 'build' },
|
||||
},
|
||||
] as any);
|
||||
|
||||
expect(taskGraph.roots).toEqual(['child1:build', 'child2:build']);
|
||||
|
||||
expect(Object.keys(taskGraph.tasks)).toEqual([
|
||||
'parent:build',
|
||||
'child1:build',
|
||||
'child2:build',
|
||||
'grandparent:build',
|
||||
]);
|
||||
|
||||
expect(taskGraph.dependencies).toEqual({
|
||||
'child1:build': [],
|
||||
'child2:build': [],
|
||||
'grandparent:build': ['parent:build'],
|
||||
'parent:build': ['child1:build', 'child2:build'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should support custom targets that require strict ordering', () => {
|
||||
const taskGraph = new TaskGraphCreator(
|
||||
{
|
||||
nodes: {
|
||||
child1: { type: 'lib', data: { targets: { custom: {} } } },
|
||||
parent: { type: 'lib', data: { targets: { custom: {} } } },
|
||||
} as any,
|
||||
dependencies: {
|
||||
child1: [],
|
||||
parent: [
|
||||
{
|
||||
source: 'parent',
|
||||
target: 'child1',
|
||||
type: DependencyType.static,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
custom: [
|
||||
{
|
||||
target: 'custom',
|
||||
projects: 'dependencies',
|
||||
},
|
||||
],
|
||||
}
|
||||
).createTaskGraph([
|
||||
{
|
||||
id: 'parent:custom',
|
||||
target: { project: 'parent', target: 'custom' },
|
||||
},
|
||||
{
|
||||
id: 'child1:custom',
|
||||
target: { project: 'child1', target: 'custom' },
|
||||
},
|
||||
] as any);
|
||||
|
||||
expect(taskGraph.roots).toEqual(['child1:custom']);
|
||||
|
||||
expect(Object.keys(taskGraph.tasks)).toEqual([
|
||||
'parent:custom',
|
||||
'child1:custom',
|
||||
]);
|
||||
|
||||
expect(taskGraph.dependencies).toEqual({
|
||||
'child1:custom': [],
|
||||
'parent:custom': ['child1:custom'],
|
||||
});
|
||||
|
||||
const noDeps = new TaskGraphCreator(
|
||||
{
|
||||
nodes: {
|
||||
child1: { type: 'lib', data: { targets: { custom: {} } } },
|
||||
parent: { type: 'lib', data: { targets: { custom: {} } } },
|
||||
} as any,
|
||||
dependencies: {
|
||||
child1: [],
|
||||
parent: [
|
||||
{
|
||||
source: 'parent',
|
||||
target: 'child1',
|
||||
type: DependencyType.static,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{}
|
||||
).createTaskGraph([
|
||||
{
|
||||
id: 'parent:custom',
|
||||
target: { project: 'parent', target: 'custom' },
|
||||
},
|
||||
{
|
||||
id: 'child1:custom',
|
||||
target: { project: 'child1', target: 'custom' },
|
||||
},
|
||||
] as any);
|
||||
|
||||
expect(noDeps.roots).toEqual(['parent:custom', 'child1:custom']);
|
||||
});
|
||||
|
||||
describe('(tasks with dependency configurations)', () => {
|
||||
let projectGraph: ProjectGraph;
|
||||
beforeEach(() => {
|
||||
projectGraph = {
|
||||
nodes: {
|
||||
app1: {
|
||||
type: 'app',
|
||||
name: 'app1',
|
||||
data: {
|
||||
targets: {
|
||||
build: {
|
||||
dependsOn: [
|
||||
{
|
||||
target: 'build',
|
||||
projects: 'dependencies',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
root: 'app1-root',
|
||||
files: [],
|
||||
},
|
||||
},
|
||||
app2: {
|
||||
type: 'app',
|
||||
name: 'app2',
|
||||
data: {
|
||||
targets: {
|
||||
build: {
|
||||
dependsOn: [
|
||||
{
|
||||
target: 'build',
|
||||
projects: 'dependencies',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
root: 'app2-root',
|
||||
files: [],
|
||||
},
|
||||
},
|
||||
common1: {
|
||||
type: 'lib',
|
||||
name: 'common1',
|
||||
data: {
|
||||
targets: {
|
||||
build: {
|
||||
dependsOn: [
|
||||
{
|
||||
target: 'build',
|
||||
projects: 'dependencies',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
root: 'common1-root',
|
||||
files: [],
|
||||
},
|
||||
},
|
||||
common2: {
|
||||
type: 'lib',
|
||||
name: 'common2',
|
||||
data: {
|
||||
targets: {
|
||||
build: {
|
||||
dependsOn: [
|
||||
{
|
||||
target: 'build',
|
||||
projects: 'dependencies',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
root: 'common2-root',
|
||||
files: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
dependencies: {
|
||||
app1: [
|
||||
{
|
||||
source: 'app1',
|
||||
target: 'common1',
|
||||
type: DependencyType.static,
|
||||
},
|
||||
],
|
||||
app2: [
|
||||
{
|
||||
source: 'app2',
|
||||
target: 'common2',
|
||||
type: DependencyType.static,
|
||||
},
|
||||
],
|
||||
common1: [],
|
||||
common2: [],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('should create task graph (builds depend on build of dependencies)', () => {
|
||||
const tasks = createTasksForProjectToRun(
|
||||
[projectGraph.nodes.app1, projectGraph.nodes.app2],
|
||||
{
|
||||
target: 'build',
|
||||
configuration: undefined,
|
||||
overrides: {},
|
||||
},
|
||||
projectGraph,
|
||||
null
|
||||
);
|
||||
|
||||
const taskGraph = new TaskGraphCreator(projectGraph, {}).createTaskGraph(
|
||||
tasks
|
||||
);
|
||||
|
||||
expect(taskGraph).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should create a task graph (builds depend on builds of dependencies even with intermediate projects)', () => {
|
||||
delete projectGraph.nodes.common1.data.targets.build;
|
||||
projectGraph.dependencies.common1.push({
|
||||
type: DependencyType.static,
|
||||
source: 'common1',
|
||||
target: 'common2',
|
||||
});
|
||||
|
||||
const tasks = createTasksForProjectToRun(
|
||||
[projectGraph.nodes.app1],
|
||||
{
|
||||
target: 'build',
|
||||
configuration: undefined,
|
||||
overrides: {},
|
||||
},
|
||||
projectGraph,
|
||||
null
|
||||
);
|
||||
|
||||
const taskGraph = new TaskGraphCreator(projectGraph, {}).createTaskGraph(
|
||||
tasks
|
||||
);
|
||||
|
||||
expect(taskGraph).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should create a task graph (builds depend on builds of dependencies with intermediate projects and circular dependencies between projects) 2', () => {
|
||||
delete projectGraph.nodes.common1.data.targets.build;
|
||||
projectGraph.dependencies.common1.push({
|
||||
type: DependencyType.static,
|
||||
source: 'common1',
|
||||
target: 'common2',
|
||||
});
|
||||
|
||||
delete projectGraph.nodes.common2.data.targets.build;
|
||||
projectGraph.dependencies.common2.push({
|
||||
type: DependencyType.static,
|
||||
source: 'common2',
|
||||
target: 'common3',
|
||||
});
|
||||
|
||||
projectGraph.nodes.common3 = {
|
||||
name: 'common3',
|
||||
type: 'lib',
|
||||
data: {
|
||||
root: 'common3',
|
||||
targets: {
|
||||
build: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
projectGraph.dependencies.common3 = [];
|
||||
|
||||
const tasks = createTasksForProjectToRun(
|
||||
[projectGraph.nodes.app1],
|
||||
{
|
||||
target: 'build',
|
||||
configuration: undefined,
|
||||
overrides: {},
|
||||
},
|
||||
projectGraph,
|
||||
null
|
||||
);
|
||||
|
||||
const taskGraph = new TaskGraphCreator(projectGraph, {}).createTaskGraph(
|
||||
tasks
|
||||
);
|
||||
|
||||
expect(taskGraph).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should create task graph (builds depend on build of dependencies and prebuild of self)', () => {
|
||||
projectGraph.nodes.app1.data.targets.prebuild = {};
|
||||
projectGraph.nodes.app2.data.targets.prebuild = {};
|
||||
projectGraph.nodes.app1.data.targets.build.dependsOn.push({
|
||||
projects: 'self',
|
||||
target: 'prebuild',
|
||||
});
|
||||
projectGraph.nodes.app2.data.targets.build.dependsOn.push({
|
||||
projects: 'self',
|
||||
target: 'prebuild',
|
||||
});
|
||||
const tasks = createTasksForProjectToRun(
|
||||
[projectGraph.nodes.app1, projectGraph.nodes.app2],
|
||||
{
|
||||
target: 'build',
|
||||
configuration: undefined,
|
||||
overrides: {},
|
||||
},
|
||||
projectGraph,
|
||||
null
|
||||
);
|
||||
const taskGraph = new TaskGraphCreator(projectGraph, {}).createTaskGraph(
|
||||
tasks
|
||||
);
|
||||
|
||||
expect(taskGraph).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should create task graph (builds depend on build of dependencies, builds depend on prebuilds)', () => {
|
||||
projectGraph.nodes.app1.data.targets.prebuild = {
|
||||
dependsOn: [
|
||||
{
|
||||
target: 'build',
|
||||
projects: 'dependencies',
|
||||
},
|
||||
],
|
||||
};
|
||||
projectGraph.nodes.app2.data.targets.prebuild = {
|
||||
dependsOn: [
|
||||
{
|
||||
target: 'build',
|
||||
projects: 'dependencies',
|
||||
},
|
||||
],
|
||||
};
|
||||
projectGraph.nodes.app1.data.targets.build.dependsOn.push({
|
||||
projects: 'self',
|
||||
target: 'prebuild',
|
||||
});
|
||||
projectGraph.nodes.app2.data.targets.build.dependsOn.push({
|
||||
projects: 'self',
|
||||
target: 'prebuild',
|
||||
});
|
||||
const tasks = createTasksForProjectToRun(
|
||||
[projectGraph.nodes.app1, projectGraph.nodes.app2],
|
||||
{
|
||||
target: 'build',
|
||||
configuration: undefined,
|
||||
overrides: {},
|
||||
},
|
||||
projectGraph,
|
||||
null
|
||||
);
|
||||
const taskGraph = new TaskGraphCreator(projectGraph, {}).createTaskGraph(
|
||||
tasks
|
||||
);
|
||||
|
||||
expect(taskGraph).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should create task graph (builds depend on build of dependencies, builds depend on prebuilds)', () => {
|
||||
projectGraph.nodes.common1.data.targets =
|
||||
projectGraph.nodes.common2.data.targets = {
|
||||
prebuild: {},
|
||||
build: {
|
||||
dependsOn: [
|
||||
{
|
||||
target: 'build',
|
||||
projects: 'dependencies',
|
||||
},
|
||||
{
|
||||
target: 'prebuild',
|
||||
projects: 'self',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
projectGraph.nodes.app1.data.targets =
|
||||
projectGraph.nodes.app2.data.targets = {
|
||||
build: {
|
||||
dependsOn: [
|
||||
{
|
||||
target: 'build',
|
||||
projects: 'dependencies',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
const tasks = createTasksForProjectToRun(
|
||||
[projectGraph.nodes.app1, projectGraph.nodes.app2],
|
||||
{
|
||||
target: 'build',
|
||||
configuration: undefined,
|
||||
overrides: {},
|
||||
},
|
||||
projectGraph,
|
||||
null
|
||||
);
|
||||
|
||||
const taskGraph = new TaskGraphCreator(projectGraph, {}).createTaskGraph(
|
||||
tasks
|
||||
);
|
||||
|
||||
expect(taskGraph).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,126 +0,0 @@
|
||||
import { ProjectGraph } from '../config/project-graph';
|
||||
import { Task, TaskGraph } from '../config/task-graph';
|
||||
import { getDependencyConfigs } from './utils';
|
||||
import { TargetDependencyConfig } from '../config/workspace-json-project-json';
|
||||
|
||||
export class TaskGraphCreator {
|
||||
constructor(
|
||||
private readonly projectGraph: ProjectGraph,
|
||||
private readonly defaultTargetDependencies: Record<
|
||||
string,
|
||||
(TargetDependencyConfig | string)[]
|
||||
>
|
||||
) {}
|
||||
|
||||
createTaskGraph(tasks: Task[]): TaskGraph {
|
||||
const graph: TaskGraph = {
|
||||
roots: [],
|
||||
tasks: {},
|
||||
dependencies: {},
|
||||
};
|
||||
for (const task of tasks) {
|
||||
this.addTaskToGraph(task, graph);
|
||||
|
||||
const dependencyConfigs = getDependencyConfigs(
|
||||
task.target,
|
||||
this.defaultTargetDependencies,
|
||||
this.projectGraph
|
||||
);
|
||||
|
||||
if (!dependencyConfigs) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.addTaskDependencies(task, dependencyConfigs, tasks, graph);
|
||||
}
|
||||
|
||||
graph.roots = Object.keys(graph.dependencies).filter(
|
||||
(k) => graph.dependencies[k].length === 0
|
||||
);
|
||||
|
||||
return graph;
|
||||
}
|
||||
|
||||
private addTaskDependencies(
|
||||
task: Task,
|
||||
dependencyConfigs: TargetDependencyConfig[],
|
||||
tasks: Task[],
|
||||
graph: TaskGraph
|
||||
) {
|
||||
for (const dependencyConfig of dependencyConfigs) {
|
||||
if (dependencyConfig.projects === 'self') {
|
||||
for (const t of tasks) {
|
||||
if (
|
||||
t.target.project === task.target.project &&
|
||||
t.target.target === dependencyConfig.target
|
||||
) {
|
||||
graph.dependencies[task.id].push(t.id);
|
||||
}
|
||||
}
|
||||
} else if (dependencyConfig.projects === 'dependencies') {
|
||||
const seen = new Set<string>();
|
||||
|
||||
this.addDependencies(
|
||||
task.target.project,
|
||||
dependencyConfig.target,
|
||||
tasks,
|
||||
graph,
|
||||
task.id,
|
||||
seen
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private addDependencies(
|
||||
project: string,
|
||||
target: string,
|
||||
tasks: Task[],
|
||||
graph: TaskGraph,
|
||||
taskId: string,
|
||||
seen: Set<string>
|
||||
) {
|
||||
seen.add(project);
|
||||
const dependencies = this.projectGraph.dependencies[project];
|
||||
if (dependencies) {
|
||||
const projectDependencies = dependencies.map(
|
||||
(dependency) => dependency.target
|
||||
);
|
||||
for (const projectDependency of projectDependencies) {
|
||||
if (seen.has(projectDependency)) {
|
||||
continue;
|
||||
}
|
||||
const dependency = this.findTask(
|
||||
{ project: projectDependency, target },
|
||||
tasks
|
||||
);
|
||||
if (dependency) {
|
||||
graph.dependencies[taskId].push(dependency.id);
|
||||
} else {
|
||||
this.addDependencies(
|
||||
projectDependency,
|
||||
target,
|
||||
tasks,
|
||||
graph,
|
||||
taskId,
|
||||
seen
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private findTask(
|
||||
{ project, target }: { project: string; target: string },
|
||||
tasks: Task[]
|
||||
): Task {
|
||||
return tasks.find(
|
||||
(t) => t.target.project === project && t.target.target === target
|
||||
);
|
||||
}
|
||||
|
||||
private addTaskToGraph(task: Task, graph: TaskGraph) {
|
||||
graph.tasks[task.id] = task;
|
||||
graph.dependencies[task.id] = [];
|
||||
}
|
||||
}
|
||||
60
packages/nx/src/tasks-runner/task-graph-utils.spec.ts
Normal file
60
packages/nx/src/tasks-runner/task-graph-utils.spec.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { findCycle, makeAcyclic } from './task-graph-utils';
|
||||
|
||||
describe('task graph utils', () => {
|
||||
describe('findCycles', () => {
|
||||
it('should return a cycle if it is there', () => {
|
||||
expect(
|
||||
findCycle({
|
||||
dependencies: {
|
||||
a: ['b', 'c'],
|
||||
b: ['d'],
|
||||
c: ['e'],
|
||||
d: [],
|
||||
e: ['q', 'a'],
|
||||
q: [],
|
||||
},
|
||||
} as any)
|
||||
).toEqual(['a', 'c', 'e', 'a']);
|
||||
});
|
||||
|
||||
it('should return null when no cycle', () => {
|
||||
expect(
|
||||
findCycle({
|
||||
dependencies: {
|
||||
a: ['b', 'c'],
|
||||
b: ['d'],
|
||||
c: ['e'],
|
||||
d: [],
|
||||
e: ['q'],
|
||||
q: [],
|
||||
},
|
||||
} as any)
|
||||
).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('makeAcyclic', () => {
|
||||
it('should remove cycles when they are there', () => {
|
||||
const graph = {
|
||||
roots: ['d'],
|
||||
dependencies: {
|
||||
a: ['b', 'c'],
|
||||
b: ['d'],
|
||||
c: ['e'],
|
||||
d: [],
|
||||
e: ['a'],
|
||||
},
|
||||
} as any;
|
||||
makeAcyclic(graph);
|
||||
|
||||
expect(graph.dependencies).toEqual({
|
||||
a: ['b', 'c'],
|
||||
b: ['d'],
|
||||
c: ['e'],
|
||||
d: [],
|
||||
e: [],
|
||||
});
|
||||
expect(graph.roots).toEqual(['d', 'e']);
|
||||
});
|
||||
});
|
||||
});
|
||||
65
packages/nx/src/tasks-runner/task-graph-utils.ts
Normal file
65
packages/nx/src/tasks-runner/task-graph-utils.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { TaskGraph } from '../config/task-graph';
|
||||
|
||||
function _findCycle(
|
||||
taskGraph: TaskGraph,
|
||||
taskId: string,
|
||||
visited: { [taskId: string]: boolean },
|
||||
path: string[]
|
||||
) {
|
||||
if (visited[taskId]) return null;
|
||||
visited[taskId] = true;
|
||||
|
||||
for (const d of taskGraph.dependencies[taskId]) {
|
||||
if (path.includes(d)) return [...path, d];
|
||||
const cycle = _findCycle(taskGraph, d, visited, [...path, d]);
|
||||
if (cycle) return cycle;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function findCycle(taskGraph: TaskGraph): string[] | null {
|
||||
const visited = {};
|
||||
for (const t of Object.keys(taskGraph.dependencies)) {
|
||||
visited[t] = false;
|
||||
}
|
||||
|
||||
for (const t of Object.keys(taskGraph.dependencies)) {
|
||||
const cycle = _findCycle(taskGraph, t, visited, [t]);
|
||||
if (cycle) return cycle;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function _makeAcyclic(
|
||||
taskGraph: TaskGraph,
|
||||
taskId: string,
|
||||
visited: { [taskId: string]: boolean },
|
||||
path: string[]
|
||||
) {
|
||||
if (visited[taskId]) return;
|
||||
visited[taskId] = true;
|
||||
|
||||
const deps = taskGraph.dependencies[taskId];
|
||||
for (const d of [...deps]) {
|
||||
if (path.includes(d)) {
|
||||
deps.splice(deps.indexOf(d), 1);
|
||||
} else {
|
||||
_makeAcyclic(taskGraph, d, visited, [...path, d]);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function makeAcyclic(taskGraph: TaskGraph): void {
|
||||
const visited = {};
|
||||
for (const t of Object.keys(taskGraph.dependencies)) {
|
||||
visited[t] = false;
|
||||
}
|
||||
for (const t of Object.keys(taskGraph.dependencies)) {
|
||||
_makeAcyclic(taskGraph, t, visited, [t]);
|
||||
}
|
||||
taskGraph.roots = Object.keys(taskGraph.dependencies).filter(
|
||||
(t) => taskGraph.dependencies[t].length === 0
|
||||
);
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import { NxJsonConfiguration } from '../config/nx-json';
|
||||
import { ProjectGraph } from '../config/project-graph';
|
||||
import { Task } from '../config/task-graph';
|
||||
import { Task, TaskGraph } from '../config/task-graph';
|
||||
import { NxArgs } from '../utils/command-line-utils';
|
||||
|
||||
export type TaskStatus =
|
||||
@ -24,5 +24,6 @@ export type TasksRunner<T = unknown> = (
|
||||
projectGraph: ProjectGraph;
|
||||
nxJson: NxJsonConfiguration;
|
||||
nxArgs: NxArgs;
|
||||
taskGraph?: TaskGraph;
|
||||
}
|
||||
) => any | Promise<{ [id: string]: TaskStatus }>;
|
||||
|
||||
@ -83,6 +83,7 @@ const runOne: string[] = [
|
||||
'scan',
|
||||
'outputStyle',
|
||||
'nxBail',
|
||||
'nxIgnoreCycles',
|
||||
];
|
||||
|
||||
const runMany: string[] = [...runOne, 'projects', 'all'];
|
||||
@ -127,6 +128,7 @@ export interface NxArgs {
|
||||
outputStyle?: string;
|
||||
scan?: boolean;
|
||||
nxBail?: boolean;
|
||||
nxIgnoreCycles?: boolean;
|
||||
}
|
||||
|
||||
const ignoreArgs = ['$0', '_'];
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user