From 29bbed5d06b96a41791cfcd119c06fc15fb5accb Mon Sep 17 00:00:00 2001 From: Jason Jean Date: Fri, 27 Sep 2024 16:15:15 -0400 Subject: [PATCH] feat(core): remove node task hasher (#28147) ## Current Behavior The native task hasher is faster and has been default for quite a while. There's still a node hasher implementation though. ## Expected Behavior The native task hasher is the only task hasher implementation. The node task hasher is no more. ## Related Issue(s) Fixes # --- .../nx/src/daemon/server/handle-hash-tasks.ts | 2 - .../__snapshots__/task-hasher.spec.ts.snap | 430 ---- packages/nx/src/hasher/create-task-hasher.ts | 2 - .../hasher/native-task-hasher-impl.spec.ts | 27 +- .../nx/src/hasher/node-task-hasher-impl.ts | 702 ------ packages/nx/src/hasher/task-hasher.spec.ts | 1938 +---------------- packages/nx/src/hasher/task-hasher.ts | 79 +- packages/nx/src/native/tasks/hash_planner.rs | 13 +- .../nx/src/native/tasks/hashers/hash_env.rs | 8 +- packages/nx/src/native/tasks/task_hasher.rs | 2 +- .../tests/__snapshots__/planner.spec.ts.snap | 100 +- packages/nx/src/native/tests/planner.spec.ts | 221 +- 12 files changed, 146 insertions(+), 3378 deletions(-) delete mode 100644 packages/nx/src/hasher/__snapshots__/task-hasher.spec.ts.snap delete mode 100644 packages/nx/src/hasher/node-task-hasher-impl.ts diff --git a/packages/nx/src/daemon/server/handle-hash-tasks.ts b/packages/nx/src/daemon/server/handle-hash-tasks.ts index 9d99fa88a97e2..211c5f6af576f 100644 --- a/packages/nx/src/daemon/server/handle-hash-tasks.ts +++ b/packages/nx/src/daemon/server/handle-hash-tasks.ts @@ -39,8 +39,6 @@ export async function handleHashTasks(payload: { if (projectGraph !== storedProjectGraph) { storedProjectGraph = projectGraph; storedHasher = new InProcessTaskHasher( - fileMap?.projectFileMap, - allWorkspaceFiles, projectGraph, nxJson, rustReferences, diff --git a/packages/nx/src/hasher/__snapshots__/task-hasher.spec.ts.snap b/packages/nx/src/hasher/__snapshots__/task-hasher.spec.ts.snap deleted file mode 100644 index 87f36aaa4562e..0000000000000 --- a/packages/nx/src/hasher/__snapshots__/task-hasher.spec.ts.snap +++ /dev/null @@ -1,430 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`TaskHasher dependentTasksOutputFiles should depend on dependent tasks output files 1`] = ` -{ - "details": { - "command": "4062279404379299270", - "implicitDeps": {}, - "nodes": { - "**/*.d.ts:dist/libs/child": "10143056223373694518", - "**/*.d.ts:dist/libs/grandchild": "10143056223373694518", - "AllExternalDependencies": "3244421341483603138", - "parent:!{projectRoot}/**/*.spec.ts": "17962802443644575456", - "parent:ProjectConfiguration": "16743571606168248002", - "parent:TsConfig": "8767608672024750088", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "8942239360311677987", - }, - "runtime": {}, - }, - "value": "13814139315500456230", -} -`; - -exports[`TaskHasher dependentTasksOutputFiles should work with dependent tasks with globs as outputs 1`] = ` -{ - "details": { - "command": "4062279404379299270", - "implicitDeps": {}, - "nodes": { - "**/*.d.ts:dist/libs/child/**/*": "10143056223373694518", - "**/*.d.ts:dist/libs/grandchild": "10143056223373694518", - "AllExternalDependencies": "3244421341483603138", - "parent:!{projectRoot}/**/*.spec.ts": "17962802443644575456", - "parent:ProjectConfiguration": "16743571606168248002", - "parent:TsConfig": "8767608672024750088", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "8942239360311677987", - }, - "runtime": {}, - }, - "value": "13814139315500456230", -} -`; - -exports[`TaskHasher hashTarget should hash entire subtree in a deterministic way 1`] = ` -{ - "details": { - "command": "81188892120010785", - "implicitDeps": {}, - "nodes": { - "appA:ProjectConfiguration": "8474322003863204060", - "appA:TsConfig": "8767608672024750088", - "appA:{projectRoot}/**/*": "3244421341483603138", - "npm:@nx/webpack": "$@nx/webpack0.0.0$", - "npm:packageA": "$packageA0.0.0$", - "npm:packageB": "$packageB0.0.0$", - "npm:packageC": "$packageC0.0.0$", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "8942239360311677987", - }, - "runtime": {}, - }, - "value": "13769080483891190943", -} -`; - -exports[`TaskHasher hashTarget should hash entire subtree in a deterministic way 2`] = ` -{ - "details": { - "command": "9096392622609675764", - "implicitDeps": {}, - "nodes": { - "appB:ProjectConfiguration": "17724470359684527282", - "appB:TsConfig": "8767608672024750088", - "appB:{projectRoot}/**/*": "3244421341483603138", - "npm:@nx/webpack": "$@nx/webpack0.0.0$", - "npm:packageA": "$packageA0.0.0$", - "npm:packageB": "$packageB0.0.0$", - "npm:packageC": "$packageC0.0.0$", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "8942239360311677987", - }, - "runtime": {}, - }, - "value": "16958448572045958975", -} -`; - -exports[`TaskHasher hashTarget should hash entire subtree of dependencies 1`] = ` -{ - "details": { - "command": "14389236043839781668", - "implicitDeps": {}, - "nodes": { - "app:ProjectConfiguration": "12026883044296863450", - "app:TsConfig": "8767608672024750088", - "app:{projectRoot}/**/*": "3244421341483603138", - "npm:@nx/devkit": "$nx/devkit16$", - "npm:@nx/webpack": "$nx/webpack16$", - "npm:nx": "$nx16$", - "npm:webpack": "5.0.0", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "8942239360311677987", - }, - "runtime": {}, - }, - "value": "10473676482354003739", -} -`; - -exports[`TaskHasher hashTarget should hash executor dependencies of @nx packages 1`] = ` -{ - "details": { - "command": "14389236043839781668", - "implicitDeps": {}, - "nodes": { - "app:ProjectConfiguration": "12026883044296863450", - "app:TsConfig": "8767608672024750088", - "app:{projectRoot}/**/*": "3244421341483603138", - "npm:@nx/webpack": "16.0.0", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "8942239360311677987", - }, - "runtime": {}, - }, - "value": "12591571137663595931", -} -`; - -exports[`TaskHasher hashTarget should use externalDependencies to override nx:run-commands 1`] = ` -{ - "details": { - "command": "14389236043839781668", - "implicitDeps": {}, - "nodes": { - "app:ProjectConfiguration": "17956886683554891195", - "app:TsConfig": "8767608672024750088", - "app:{projectRoot}/**/*": "3244421341483603138", - "env:undefined": "3244421341483603138", - "npm:react": "17.0.0", - "npm:webpack": "5.0.0", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "8942239360311677987", - }, - "runtime": {}, - }, - "value": "12439359954646788830", -} -`; - -exports[`TaskHasher hashTarget should use externalDependencies with empty array to ignore all deps 1`] = ` -{ - "details": { - "command": "14389236043839781668", - "implicitDeps": {}, - "nodes": { - "app:ProjectConfiguration": "9179552940021403596", - "app:TsConfig": "8767608672024750088", - "app:{projectRoot}/**/*": "3244421341483603138", - "env:undefined": "3244421341483603138", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "8942239360311677987", - }, - "runtime": {}, - }, - "value": "12962045506359360703", -} -`; - -exports[`TaskHasher should be able to handle multiple filesets per project 1`] = ` -{ - "details": { - "command": "13785966310271077209", - "implicitDeps": {}, - "nodes": { - "AllExternalDependencies": "3244421341483603138", - "child:!{projectRoot}/**/*.spec.ts": "17508782620731849000", - "child:ProjectConfiguration": "17211930887387929067", - "child:TsConfig": "8767608672024750088", - "env:MY_TEST_HASH_ENV": "17357374746554314488", - "parent:ProjectConfiguration": "11407237178360979685", - "parent:TsConfig": "8767608672024750088", - "parent:{projectRoot}/**/*": "7263479247245830838", - "workspace:[{workspaceRoot}/global1]": "3052102066027208710", - "workspace:[{workspaceRoot}/global2]": "8197394511443659629", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "8942239360311677987", - }, - "runtime": {}, - }, - "value": "225950192185398171", -} -`; - -exports[`TaskHasher should be able to handle multiple filesets per project 2`] = ` -{ - "details": { - "command": "6958627266354933907", - "implicitDeps": {}, - "nodes": { - "AllExternalDependencies": "3244421341483603138", - "child:ProjectConfiguration": "17211930887387929067", - "child:TsConfig": "8767608672024750088", - "child:{projectRoot}/**/*": "2300207741412661544", - "workspace:[{workspaceRoot}/global1]": "3052102066027208710", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "8942239360311677987", - }, - "runtime": {}, - }, - "value": "6479833018413810049", -} -`; - -exports[`TaskHasher should be able to include only a part of the base tsconfig 1`] = ` -{ - "details": { - "command": "4062279404379299270", - "implicitDeps": {}, - "nodes": { - "AllExternalDependencies": "3244421341483603138", - "parent:ProjectConfiguration": "18166168584521190546", - "parent:TsConfig": "4035819825874039301", - "parent:{projectRoot}/**/*": "8263681721738113012", - "runtime:echo runtime123": "29846575039086708", - "runtime:echo runtime456": "9687767313975325934", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "8942239360311677987", - }, - "runtime": {}, - }, - "value": "2591807031038621869", -} -`; - -exports[`TaskHasher should create task hash 1`] = ` -{ - "details": { - "command": "4062279404379299270", - "implicitDeps": {}, - "nodes": { - "AllExternalDependencies": "3244421341483603138", - "env:NONEXISTENTENV": "3244421341483603138", - "env:TESTENV": "6544740722075256274", - "parent:ProjectConfiguration": "12175533918137189298", - "parent:TsConfig": "8767608672024750088", - "parent:{projectRoot}/**/*": "8263681721738113012", - "runtime:echo runtime123": "29846575039086708", - "runtime:echo runtime456": "9687767313975325934", - "tagged:ProjectConfiguration": "4875698716044094030", - "tagged:TsConfig": "8767608672024750088", - "tagged:{projectRoot}/**/*": "3244421341483603138", - "unrelated:ProjectConfiguration": "9857815511578358695", - "unrelated:TsConfig": "8767608672024750088", - "unrelated:{projectRoot}/**/*": "10091615118977982257", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "8942239360311677987", - }, - "runtime": {}, - }, - "value": "13363019766081493373", -} -`; - -exports[`TaskHasher should hash missing dependent npm project versions 1`] = ` -{ - "details": { - "command": "14389236043839781668", - "implicitDeps": {}, - "nodes": { - "AllExternalDependencies": "9871338282813031846", - "app:ProjectConfiguration": "8128657069648957137", - "app:TsConfig": "8767608672024750088", - "app:{projectRoot}/**/*": "9104199730100321982", - "npm:react": "17.0.0", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "8942239360311677987", - }, - "runtime": {}, - }, - "value": "1259505935462026504", -} -`; - -exports[`TaskHasher should hash multiple filesets of a project 1`] = ` -{ - "details": { - "command": "13785966310271077209", - "implicitDeps": {}, - "nodes": { - "AllExternalDependencies": "3244421341483603138", - "parent:ProjectConfiguration": "10499856664466672714", - "parent:TsConfig": "8767608672024750088", - "parent:{projectRoot}/**/*": "7263479247245830838", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "8942239360311677987", - }, - "runtime": {}, - }, - "value": "18165704585933725119", -} -`; - -exports[`TaskHasher should hash multiple filesets of a project 2`] = ` -{ - "details": { - "command": "4062279404379299270", - "implicitDeps": {}, - "nodes": { - "AllExternalDependencies": "3244421341483603138", - "parent:!{projectRoot}/**/*.spec.ts": "17962802443644575456", - "parent:ProjectConfiguration": "10499856664466672714", - "parent:TsConfig": "8767608672024750088", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "8942239360311677987", - }, - "runtime": {}, - }, - "value": "8235745364524428309", -} -`; - -exports[`TaskHasher should hash non-default filesets 1`] = ` -{ - "details": { - "command": "4062279404379299270", - "implicitDeps": {}, - "nodes": { - "AllExternalDependencies": "3244421341483603138", - "child:ProjectConfiguration": "8196293273405506196", - "child:TsConfig": "8767608672024750088", - "child:{projectRoot}/**/*": "2300207741412661544", - "parent:!{projectRoot}/**/*.spec.ts": "17962802443644575456", - "parent:ProjectConfiguration": "7903062777922836147", - "parent:TsConfig": "8767608672024750088", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "8942239360311677987", - }, - "runtime": {}, - }, - "value": "15360225209266319721", -} -`; - -exports[`TaskHasher should hash npm project versions 1`] = ` -{ - "details": { - "command": "14389236043839781668", - "implicitDeps": {}, - "nodes": { - "AllExternalDependencies": "9871338282813031846", - "app:ProjectConfiguration": "8128657069648957137", - "app:TsConfig": "8767608672024750088", - "app:{projectRoot}/**/*": "9104199730100321982", - "npm:react": "17.0.0", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "8942239360311677987", - }, - "runtime": {}, - }, - "value": "1259505935462026504", -} -`; - -exports[`TaskHasher should hash task where the project has dependencies 1`] = ` -{ - "details": { - "command": "4062279404379299270", - "implicitDeps": {}, - "nodes": { - "AllExternalDependencies": "3244421341483603138", - "child:ProjectConfiguration": "8876282510060012181", - "child:TsConfig": "8767608672024750088", - "child:{projectRoot}/**/*": "5484012818475684626", - "parent:ProjectConfiguration": "1818520398670323053", - "parent:TsConfig": "8767608672024750088", - "parent:{projectRoot}/**/*": "14822394489351823627", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "8942239360311677987", - }, - "runtime": {}, - }, - "value": "9343642962263515428", -} -`; - -exports[`TaskHasher should hash tasks where the project graph has circular dependencies 1`] = ` -{ - "details": { - "command": "4062279404379299270", - "implicitDeps": {}, - "nodes": { - "AllExternalDependencies": "3244421341483603138", - "child:ProjectConfiguration": "9892649345820140726", - "child:TsConfig": "8767608672024750088", - "child:{projectRoot}/**/*": "8973015561538144423", - "parent:ProjectConfiguration": "18166168584521190546", - "parent:TsConfig": "8767608672024750088", - "parent:{projectRoot}/**/*": "9104199730100321982", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "8942239360311677987", - }, - "runtime": {}, - }, - "value": "7263142938698693594", -} -`; - -exports[`TaskHasher should hash tasks where the project graph has circular dependencies 2`] = ` -{ - "details": { - "command": "7833005669885463868", - "implicitDeps": {}, - "nodes": { - "AllExternalDependencies": "3244421341483603138", - "child:ProjectConfiguration": "9892649345820140726", - "child:TsConfig": "8767608672024750088", - "child:{projectRoot}/**/*": "8973015561538144423", - "parent:ProjectConfiguration": "18166168584521190546", - "parent:TsConfig": "8767608672024750088", - "parent:{projectRoot}/**/*": "9104199730100321982", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "8942239360311677987", - }, - "runtime": {}, - }, - "value": "3664247485971662150", -} -`; - -exports[`TaskHasher should use targetDefaults from nx.json 1`] = ` -{ - "details": { - "command": "4062279404379299270", - "implicitDeps": {}, - "nodes": { - "AllExternalDependencies": "3244421341483603138", - "child:!{projectRoot}/**/*.spec.ts": "17508782620731849000", - "child:ProjectConfiguration": "9892649345820140726", - "child:TsConfig": "8767608672024750088", - "parent:!{projectRoot}/**/*.spec.ts": "17962802443644575456", - "parent:ProjectConfiguration": "18166168584521190546", - "parent:TsConfig": "8767608672024750088", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "8942239360311677987", - }, - "runtime": {}, - }, - "value": "4674721715858855274", -} -`; diff --git a/packages/nx/src/hasher/create-task-hasher.ts b/packages/nx/src/hasher/create-task-hasher.ts index deda1cf4434fc..f0b6323d8b2b1 100644 --- a/packages/nx/src/hasher/create-task-hasher.ts +++ b/packages/nx/src/hasher/create-task-hasher.ts @@ -18,8 +18,6 @@ export function createTaskHasher( } else { const { fileMap, allWorkspaceFiles, rustReferences } = getFileMap(); return new InProcessTaskHasher( - fileMap?.projectFileMap, - allWorkspaceFiles, projectGraph, nxJson, rustReferences, diff --git a/packages/nx/src/hasher/native-task-hasher-impl.spec.ts b/packages/nx/src/hasher/native-task-hasher-impl.spec.ts index 99da1924a6137..f606427054dbc 100644 --- a/packages/nx/src/hasher/native-task-hasher-impl.spec.ts +++ b/packages/nx/src/hasher/native-task-hasher-impl.spec.ts @@ -154,6 +154,7 @@ describe('native task hasher', () => { "details": { "AllExternalDependencies": "3244421341483603138", "env:NONEXISTENTENV": "3244421341483603138", + "env:NX_CLOUD_ENCRYPTION_KEY": "3244421341483603138", "env:TESTENV": "11441948532827618368", "parent:ProjectConfiguration": "3608670998275221195", "parent:TsConfig": "2264969541778889434", @@ -167,7 +168,7 @@ describe('native task hasher', () => { "unrelated:{projectRoot}/**/*": "4127219831408253695", "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "18099427347122160586", }, - "value": "14471888706892195399", + "value": "391066910278240047", }, ] `); @@ -227,12 +228,13 @@ describe('native task hasher', () => { "child:ProjectConfiguration": "710102491746666394", "child:TsConfig": "2264969541778889434", "child:{projectRoot}/**/*": "3347149359534435991", + "env:NX_CLOUD_ENCRYPTION_KEY": "3244421341483603138", "parent:ProjectConfiguration": "8031122597231773116", "parent:TsConfig": "2264969541778889434", "parent:{projectRoot}/**/*": "17059468255294227635", "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "18099427347122160586", }, - "value": "2877551238604232699", + "value": "2068118780828544905", } `); }); @@ -306,12 +308,13 @@ describe('native task hasher', () => { "child:ProjectConfiguration": "13051054958929525761", "child:TsConfig": "2264969541778889434", "child:{projectRoot}/**/*": "3347149359534435991", + "env:NX_CLOUD_ENCRYPTION_KEY": "3244421341483603138", "parent:!{projectRoot}/**/*.spec.ts": "8911122541468969799", "parent:ProjectConfiguration": "3608670998275221195", "parent:TsConfig": "2264969541778889434", "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "11114659294156087056", }, - "value": "5146047476750743843", + "value": "7780216706447676384", } `); }); @@ -372,22 +375,24 @@ describe('native task hasher', () => { { "details": { "AllExternalDependencies": "3244421341483603138", + "env:NX_CLOUD_ENCRYPTION_KEY": "3244421341483603138", "parent:!{projectRoot}/**/*.spec.ts": "8911122541468969799", "parent:ProjectConfiguration": "16402137858974842465", "parent:TsConfig": "2264969541778889434", "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "11114659294156087056", }, - "value": "845448225466199915", + "value": "16063851723942996830", }, { "details": { "AllExternalDependencies": "3244421341483603138", + "env:NX_CLOUD_ENCRYPTION_KEY": "3244421341483603138", "parent:ProjectConfiguration": "16402137858974842465", "parent:TsConfig": "2264969541778889434", "parent:{projectRoot}/**/*": "17059468255294227635", "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "11114659294156087056", }, - "value": "16342992587503026008", + "value": "1153029350223570014", }, ] `); @@ -471,6 +476,7 @@ describe('native task hasher', () => { "child:ProjectConfiguration": "10085593111011845427", "child:TsConfig": "2264969541778889434", "env:MY_TEST_HASH_ENV": "17357374746554314488", + "env:NX_CLOUD_ENCRYPTION_KEY": "3244421341483603138", "parent:ProjectConfiguration": "14398811678394411425", "parent:TsConfig": "2264969541778889434", "parent:{projectRoot}/**/*": "17059468255294227635", @@ -478,7 +484,7 @@ describe('native task hasher', () => { "workspace:[{workspaceRoot}/global2]": "12932836274958677781", "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "12076281115618125366", }, - "value": "8928030752507058", + "value": "11623032905580707496", }, ] `); @@ -527,12 +533,13 @@ describe('native task hasher', () => { { "details": { "AllExternalDependencies": "3244421341483603138", + "env:NX_CLOUD_ENCRYPTION_KEY": "3244421341483603138", "parent:ProjectConfiguration": "3608670998275221195", "parent:TsConfig": "8661678577354855152", "parent:{projectRoot}/**/*": "17059468255294227635", "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "18099427347122160586", }, - "value": "10182005362255577288", + "value": "15449891577656158381", } `); }); @@ -605,12 +612,13 @@ describe('native task hasher', () => { "child:ProjectConfiguration": "13748859057138736105", "child:TsConfig": "2264969541778889434", "child:{projectRoot}/**/*": "3347149359534435991", + "env:NX_CLOUD_ENCRYPTION_KEY": "3244421341483603138", "parent:ProjectConfiguration": "3608670998275221195", "parent:TsConfig": "2264969541778889434", "parent:{projectRoot}/**/*": "17059468255294227635", "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "18099427347122160586", }, - "value": "12868980333884890989", + "value": "7701541978018526456", } `); @@ -627,12 +635,13 @@ describe('native task hasher', () => { "child:ProjectConfiguration": "13748859057138736105", "child:TsConfig": "2264969541778889434", "child:{projectRoot}/**/*": "3347149359534435991", + "env:NX_CLOUD_ENCRYPTION_KEY": "3244421341483603138", "parent:ProjectConfiguration": "3608670998275221195", "parent:TsConfig": "2264969541778889434", "parent:{projectRoot}/**/*": "17059468255294227635", "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "18099427347122160586", }, - "value": "12868980333884890989", + "value": "7701541978018526456", } `); }); diff --git a/packages/nx/src/hasher/node-task-hasher-impl.ts b/packages/nx/src/hasher/node-task-hasher-impl.ts deleted file mode 100644 index cc7b242560fea..0000000000000 --- a/packages/nx/src/hasher/node-task-hasher-impl.ts +++ /dev/null @@ -1,702 +0,0 @@ -import { NxJsonConfiguration } from '../config/nx-json'; -import { - FileData, - ProjectFileMap, - ProjectGraph, - ProjectGraphDependency, -} from '../config/project-graph'; -import { createProjectRootMappings } from '../project-graph/utils/find-project-for-path'; -import { Task, TaskGraph } from '../config/task-graph'; -import { hashArray, hashObject } from './file-hasher'; -import { getOutputsForTargetAndConfiguration } from '../tasks-runner/utils'; -import { workspaceRoot } from '../utils/workspace-root'; -import { minimatch } from 'minimatch'; -import { join } from 'path'; -import { hashFile } from '../native'; -import { findAllProjectNodeDependencies } from '../utils/project-graph-utils'; -import { findMatchingProjects } from '../utils/find-matching-projects'; -import { exec } from 'child_process'; -import { - ExpandedDepsOutput, - ExpandedInput, - ExpandedSelfInput, - expandNamedInput, - expandSingleProjectInputs, - extractPatternsFromFileSets, - filterUsingGlobPatterns, - getInputs, - getNamedInputs, - isDepsOutput, - isSelfInput, - PartialHash, - TaskHasher, - TaskHasherImpl, -} from './task-hasher'; -import { hashTsConfig } from '../plugins/js/hasher/hasher'; - -export class NodeTaskHasherImpl implements TaskHasherImpl { - private filesetHashes: { - [taskId: string]: Promise; - } = {}; - private runtimeHashes: { - [runtime: string]: Promise; - } = {}; - private externalDependencyHashes: Map = new Map< - string, - PartialHash[] - >(); - private allExternalDependenciesHash: PartialHash; - private projectRootMappings = createProjectRootMappings( - this.projectGraph.nodes - ); - - constructor( - private readonly nxJson: NxJsonConfiguration, - private readonly legacyRuntimeInputs: { runtime: string }[], - private readonly legacyFilesetInputs: { fileset: string }[], - private readonly projectFileMap: ProjectFileMap, - private readonly allWorkspaceFiles: FileData[], - private readonly projectGraph: ProjectGraph, - private readonly options: { selectivelyHashTsConfig: boolean } - ) { - // External Dependencies are all calculated up front in a deterministic order - this.calculateExternalDependencyHashes(); - } - - hashTasks( - tasks: Task[], - taskGraph: TaskGraph, - env: NodeJS.ProcessEnv - ): Promise { - return Promise.all(tasks.map((t) => this.hashTask(t, taskGraph, env, []))); - } - - async hashTask( - task: Task, - taskGraph: TaskGraph, - env: NodeJS.ProcessEnv, - visited: string[] = [] - ): Promise { - return Promise.resolve().then(async () => { - const { selfInputs, depsInputs, depsOutputs, projectInputs } = getInputs( - task, - this.projectGraph, - this.nxJson - ); - - const selfAndInputs = await this.hashSelfAndDepsInputs( - task.target.project, - task, - selfInputs, - depsInputs, - depsOutputs, - projectInputs, - taskGraph, - env, - visited - ); - - const target = this.hashTarget( - task.target.project, - task.target.target, - selfInputs - ); - if (target) { - return this.combinePartialHashes([selfAndInputs, target]); - } - return selfAndInputs; - }); - } - - private async hashNamedInputForDependencies( - projectName: string, - task: Task, - namedInput: string, - taskGraph: TaskGraph, - env: NodeJS.ProcessEnv, - visited: string[] - ): Promise { - const projectNode = this.projectGraph.nodes[projectName]; - const namedInputs = { - default: [{ fileset: '{projectRoot}/**/*' }], - ...this.nxJson.namedInputs, - ...projectNode.data.namedInputs, - }; - - const expandedInputs = expandNamedInput(namedInput, namedInputs); - const selfInputs = expandedInputs.filter(isSelfInput); - const depsOutputs = expandedInputs.filter(isDepsOutput); - const depsInputs = [{ input: namedInput, dependencies: true as true }]; // true is boolean by default - return this.hashSelfAndDepsInputs( - projectName, - task, - selfInputs, - depsInputs, - depsOutputs, - [], - taskGraph, - env, - visited - ); - } - - private async hashSelfAndDepsInputs( - projectName: string, - task: Task, - selfInputs: ExpandedSelfInput[], - depsInputs: { input: string; dependencies: true }[], - depsOutputs: ExpandedDepsOutput[], - projectInputs: { input: string; projects: string[] }[], - taskGraph: TaskGraph, - env: NodeJS.ProcessEnv, - visited: string[] - ) { - const projectGraphDeps = this.projectGraph.dependencies[projectName] ?? []; - // we don't want random order of dependencies to change the hash - projectGraphDeps.sort((a, b) => a.target.localeCompare(b.target)); - - const self = await this.hashSingleProjectInputs( - projectName, - selfInputs, - env - ); - const deps = await this.hashDepsInputs( - task, - depsInputs, - projectGraphDeps, - taskGraph, - env, - visited - ); - const depsOut = await this.hashDepsOutputs(task, depsOutputs, taskGraph); - const projects = await this.hashProjectInputs(projectInputs, env); - - return this.combinePartialHashes([ - ...self, - ...deps, - ...projects, - ...depsOut, - ]); - } - - private combinePartialHashes(partialHashes: PartialHash[]): PartialHash { - if (partialHashes.length === 1) { - return partialHashes[0]; - } - const details = {}; - const hashValues: string[] = []; - for (const partial of partialHashes) { - hashValues.push(partial.value); - Object.assign(details, partial.details); - } - const value = hashArray(hashValues); - - return { value, details }; - } - - private async hashDepsInputs( - task: Task, - inputs: { input: string }[], - projectGraphDeps: ProjectGraphDependency[], - taskGraph: TaskGraph, - env: NodeJS.ProcessEnv, - visited: string[] - ): Promise { - return ( - await Promise.all( - inputs.map(async (input) => { - return await Promise.all( - projectGraphDeps.map(async (d) => { - if (visited.indexOf(d.target) > -1) { - return null; - } else { - visited.push(d.target); - if (this.projectGraph.nodes[d.target]) { - return await this.hashNamedInputForDependencies( - d.target, - task, - input.input || 'default', - taskGraph, - env, - visited - ); - } else { - return this.getExternalDependencyHash(d.target); - } - } - }) - ); - }) - ) - ) - .flat() - .filter((r) => !!r); - } - - private async hashDepsOutputs( - task: Task, - depsOutputs: ExpandedDepsOutput[], - taskGraph: TaskGraph - ): Promise { - if (depsOutputs.length === 0) { - return []; - } - const result: PartialHash[] = []; - for (const { dependentTasksOutputFiles, transitive } of depsOutputs) { - result.push( - ...(await this.hashDepOuputs( - task, - dependentTasksOutputFiles, - taskGraph, - transitive - )) - ); - } - return result; - } - - private async hashDepOuputs( - task: Task, - dependentTasksOutputFiles: string, - taskGraph: TaskGraph, - transitive?: boolean - ): Promise { - // task has no dependencies - if (!taskGraph.dependencies[task.id]) { - return []; - } - - const partialHashes: PartialHash[] = []; - for (const d of taskGraph.dependencies[task.id]) { - const childTask = taskGraph.tasks[d]; - const outputs = getOutputsForTargetAndConfiguration( - childTask.target, - childTask.overrides, - this.projectGraph.nodes[childTask.target.project] - ); - const { getFilesForOutputs } = - require('../native') as typeof import('../native'); - const outputFiles = getFilesForOutputs(workspaceRoot, outputs); - const filteredFiles = outputFiles.filter( - (p) => - p === dependentTasksOutputFiles || - minimatch(p, dependentTasksOutputFiles, { dot: true }) - ); - const hashDetails = {}; - const hashes: string[] = []; - for (const [file, hash] of this.hashFiles( - filteredFiles.map((p) => join(workspaceRoot, p)) - )) { - hashes.push(hash); - } - - let hash = hashArray(hashes); - partialHashes.push({ - value: hash, - details: { - [`${dependentTasksOutputFiles}:${outputs.join(',')}`]: hash, - }, - }); - if (transitive) { - partialHashes.push( - ...(await this.hashDepOuputs( - childTask, - dependentTasksOutputFiles, - taskGraph, - transitive - )) - ); - } - } - return partialHashes; - } - - private hashFiles(files: string[]): Map { - const r = new Map(); - for (let f of files) { - r.set(f, hashFile(f)); - } - return r; - } - - private getExternalDependencyHash(externalNodeName: string) { - const combinedHash = this.combinePartialHashes( - this.externalDependencyHashes.get(externalNodeName) - ); - // Set the combined hash into the hashes so it's not recalculated next time - this.externalDependencyHashes.set(externalNodeName, [combinedHash]); - return combinedHash; - } - - private hashSingleExternalDependency(externalNodeName: string): PartialHash { - const node = this.projectGraph.externalNodes[externalNodeName]; - if (node.data.hash) { - // we already know the hash of this dependency - return { - value: node.data.hash, - details: { - [externalNodeName]: node.data.hash, - }, - }; - } else { - // we take version as a hash - return { - value: node.data.version, - details: { - [externalNodeName]: node.data.version, - }, - }; - } - } - - private hashExternalDependency(externalNodeName: string) { - const partialHashes: Set = new Set(); - partialHashes.add(this.hashSingleExternalDependency(externalNodeName)); - const deps = findAllProjectNodeDependencies( - externalNodeName, - this.projectGraph, - true - ); - for (const dep of deps) { - partialHashes.add(this.hashSingleExternalDependency(dep)); - } - return Array.from(partialHashes); - } - - private hashTarget( - projectName: string, - targetName: string, - selfInputs: ExpandedSelfInput[] - ): PartialHash { - const projectNode = this.projectGraph.nodes[projectName]; - const target = projectNode.data.targets[targetName]; - - if (!target) { - return; - } - - let hash: string; - // we can only vouch for @nx packages's executor dependencies - // if it's "run commands" or third-party we skip traversing since we have no info what this command depends on - if ( - target.executor.startsWith(`@nrwl/`) || - target.executor.startsWith(`@nx/`) - ) { - const executorPackage = target.executor.split(':')[0]; - const executorNodeName = - this.findExternalDependencyNodeName(executorPackage); - - // This is either a local plugin or a non-existent executor - if (!executorNodeName) { - // TODO: This should not return null if it is a local plugin's executor - return null; - } - - return this.getExternalDependencyHash(executorNodeName); - } else { - // use command external dependencies if available to construct the hash - const partialHashes: PartialHash[] = []; - let hasCommandExternalDependencies = false; - for (const input of selfInputs) { - if (input['externalDependencies']) { - // if we have externalDependencies with empty array we still want to override the default hash - hasCommandExternalDependencies = true; - const externalDependencies = input['externalDependencies']; - for (let dep of externalDependencies) { - dep = this.findExternalDependencyNodeName(dep); - if (!dep) { - throw new Error( - `The externalDependency "${dep}" for "${projectName}:${targetName}" could not be found` - ); - } - - partialHashes.push(this.getExternalDependencyHash(dep)); - } - } - } - if (hasCommandExternalDependencies) { - return this.combinePartialHashes(partialHashes); - } else { - // cache the hash of the entire external dependencies tree - if (this.allExternalDependenciesHash) { - return this.allExternalDependenciesHash; - } else { - hash = hashObject(this.projectGraph.externalNodes); - this.allExternalDependenciesHash = { - value: hash, - details: { - AllExternalDependencies: hash, - }, - }; - return this.allExternalDependenciesHash; - } - } - } - } - - private findExternalDependencyNodeName(packageName: string): string | null { - if (this.projectGraph.externalNodes[packageName]) { - return packageName; - } - if (this.projectGraph.externalNodes[`npm:${packageName}`]) { - return `npm:${packageName}`; - } - for (const node of Object.values(this.projectGraph.externalNodes)) { - if (node.data.packageName === packageName) { - return node.name; - } - } - // not found - return null; - } - - private async hashSingleProjectInputs( - projectName: string, - inputs: ExpandedInput[], - env: NodeJS.ProcessEnv - ): Promise { - const filesets = extractPatternsFromFileSets(inputs); - - const projectFilesets = []; - const workspaceFilesets = []; - let invalidFilesetNoPrefix = null; - let invalidFilesetWorkspaceRootNegative = null; - - for (let f of filesets) { - if (f.startsWith('{projectRoot}/') || f.startsWith('!{projectRoot}/')) { - projectFilesets.push(f); - } else if ( - f.startsWith('{workspaceRoot}/') || - f.startsWith('!{workspaceRoot}/') - ) { - workspaceFilesets.push(f); - } else { - invalidFilesetNoPrefix = f; - } - } - - if (invalidFilesetNoPrefix) { - throw new Error( - [ - `"${invalidFilesetNoPrefix}" is an invalid fileset.`, - 'All filesets have to start with either {workspaceRoot} or {projectRoot}.', - 'For instance: "!{projectRoot}/**/*.spec.ts" or "{workspaceRoot}/package.json".', - `If "${invalidFilesetNoPrefix}" is a named input, make sure it is defined in, for instance, nx.json.`, - ].join('\n') - ); - } - if (invalidFilesetWorkspaceRootNegative) { - throw new Error( - [ - `"${invalidFilesetWorkspaceRootNegative}" is an invalid fileset.`, - 'It is not possible to negative filesets starting with {workspaceRoot}.', - ].join('\n') - ); - } - - const notFilesets = inputs.filter((r) => !r['fileset']); - return Promise.all([ - this.hashProjectFileset(projectName, projectFilesets), - this.hashProjectConfig(projectName), - this.hashTsConfig(projectName), - ...(workspaceFilesets.length - ? [this.hashRootFilesets(workspaceFilesets)] - : []), - this.hashRootFilesets(this.legacyFilesetInputs.map((r) => r.fileset)), - ...[...notFilesets, ...this.legacyRuntimeInputs].map((r) => - r['runtime'] - ? this.hashRuntime(env, r['runtime']) - : this.hashEnv(env, r['env']) - ), - ]); - } - - private async hashProjectInputs( - projectInputs: { input: string; projects: string[] }[], - env: NodeJS.ProcessEnv - ): Promise { - const partialHashes: Promise[] = []; - for (const input of projectInputs) { - const projects = findMatchingProjects( - input.projects, - this.projectGraph.nodes - ); - for (const project of projects) { - const namedInputs = getNamedInputs( - this.nxJson, - this.projectGraph.nodes[project] - ); - const expandedInput = expandSingleProjectInputs( - [{ input: input.input }], - namedInputs - ); - partialHashes.push( - this.hashSingleProjectInputs(project, expandedInput, env) - ); - } - } - return Promise.all(partialHashes).then((hashes) => hashes.flat()); - } - - private async hashRootFilesets(filesets: string[]): Promise { - const mapKey = `workspace:[${filesets.join(',')}]`; - if (!this.filesetHashes[mapKey]) { - this.filesetHashes[mapKey] = new Promise(async (res) => { - const parts = []; - const negativePatterns = []; - const positivePatterns = []; - for (const fileset of filesets) { - if (fileset.startsWith('!')) { - negativePatterns.push(fileset.substring(17)); - } else { - positivePatterns.push(fileset.substring(16)); - } - } - for (const fileset of positivePatterns) { - const withoutWorkspaceRoot = fileset; - // Used to shortcut minimatch if not necessary - const matchingFile = this.allWorkspaceFiles.find( - (t) => t.file === withoutWorkspaceRoot - ); - // shortcut because there is a direct match - if (matchingFile) { - if ( - !negativePatterns.some((p) => minimatch(matchingFile.file, p)) - ) { - parts.push(matchingFile.hash); - } - // No direct match, check if pattern matched - } else { - this.allWorkspaceFiles - .filter( - (f) => - minimatch(f.file, withoutWorkspaceRoot) && - !negativePatterns.some((p) => minimatch(f.file, p)) - ) - .forEach((f) => { - parts.push(f.hash); - }); - } - } - const value = hashArray(parts); - res({ - value, - details: { [mapKey]: value }, - }); - }); - } - return this.filesetHashes[mapKey]; - } - - private hashProjectConfig(projectName: string): PartialHash { - const p = this.projectGraph.nodes[projectName]; - const projectConfig = hashArray([ - JSON.stringify({ ...p.data, files: undefined }), - ]); - - return { - value: projectConfig, - details: { - [`${projectName}:ProjectConfiguration`]: projectConfig, - }, - }; - } - - private hashTsConfig(projectName: string): PartialHash { - const p = this.projectGraph.nodes[projectName]; - const tsConfig = hashArray([ - hashTsConfig(p, this.projectRootMappings, this.options), - ]); - return { - value: tsConfig, - details: { - [`${projectName}:TsConfig`]: tsConfig, - }, - }; - } - - private async hashProjectFileset( - projectName: string, - filesetPatterns: string[] - ): Promise { - const mapKey = `${projectName}:${filesetPatterns.join(',')}`; - if (!this.filesetHashes[mapKey]) { - this.filesetHashes[mapKey] = new Promise(async (res) => { - const p = this.projectGraph.nodes[projectName]; - const filteredFiles = filterUsingGlobPatterns( - p.data.root, - this.projectFileMap[projectName] || [], - filesetPatterns - ); - const files: string[] = []; - for (const { file, hash } of filteredFiles) { - files.push(file, hash); - } - - const value = hashArray(files); - res({ - value, - details: { [mapKey]: value }, - }); - }); - } - return this.filesetHashes[mapKey]; - } - - private async hashRuntime( - env: NodeJS.ProcessEnv, - runtime: string - ): Promise { - const env_key = JSON.stringify(env); - const mapKey = `runtime:${runtime}-${env_key}`; - if (!this.runtimeHashes[mapKey]) { - this.runtimeHashes[mapKey] = new Promise((res, rej) => { - exec( - runtime, - { - windowsHide: true, - cwd: workspaceRoot, - env, - }, - (err, stdout, stderr) => { - if (err) { - rej( - new Error( - `Nx failed to execute {runtime: '${runtime}'}. ${err}.` - ) - ); - } else { - const value = hashArray([`${stdout}${stderr}`.trim()]); - res({ - details: { [`runtime:${runtime}`]: value }, - value, - }); - } - } - ); - }); - } - return this.runtimeHashes[mapKey]; - } - - private async hashEnv( - env: NodeJS.ProcessEnv, - envVarName: string - ): Promise { - const value = hashArray([env[envVarName] ?? '']); - return { - details: { [`env:${envVarName}`]: value }, - value, - }; - } - - private calculateExternalDependencyHashes() { - const keys = Object.keys(this.projectGraph.externalNodes); - for (const externalNodeName of keys) { - this.externalDependencyHashes.set( - externalNodeName, - this.hashExternalDependency(externalNodeName) - ); - } - } -} diff --git a/packages/nx/src/hasher/task-hasher.spec.ts b/packages/nx/src/hasher/task-hasher.spec.ts index 45b695515babc..1c004c35d6882 100644 --- a/packages/nx/src/hasher/task-hasher.spec.ts +++ b/packages/nx/src/hasher/task-hasher.spec.ts @@ -1,1944 +1,8 @@ // This must come before the Hasher import -import { TempFs } from '../internal-testing-utils/temp-fs'; -let tempFs = new TempFs('TaskHasher'); - -import { DependencyType } from '../config/project-graph'; -import { - expandNamedInput, - filterUsingGlobPatterns, - InProcessTaskHasher, -} from './task-hasher'; +import { expandNamedInput, filterUsingGlobPatterns } from './task-hasher'; describe('TaskHasher', () => { - process.env.NX_NATIVE_TASK_HASHER = 'false'; - const packageJson = { - name: 'nrwl', - }; - - const tsConfigBaseJson = JSON.stringify({ - compilerOptions: { - paths: { - '@nx/parent': ['libs/parent/src/index.ts'], - '@nx/child': ['libs/child/src/index.ts'], - }, - }, - }); - const allWorkspaceFiles = [ - { file: 'yarn.lock', hash: 'yarn.lock.hash' }, - { file: 'nx.json', hash: 'nx.json.hash' }, - { file: 'package-lock.json', hash: 'package-lock.json.hash' }, - { file: 'package.json', hash: 'package.json.hash' }, - { file: 'pnpm-lock.yaml', hash: 'pnpm-lock.yaml.hash' }, - { file: 'tsconfig.base.json', hash: tsConfigBaseJson }, - { file: 'workspace.json', hash: 'workspace.json.hash' }, - { file: 'global1', hash: 'global1.hash' }, - { file: 'global2', hash: 'global2.hash' }, - ]; - - beforeEach(async () => { - await tempFs.createFiles({ - 'tsconfig.base.json': tsConfigBaseJson, - 'yarn.lock': 'content', - 'package.json': JSON.stringify(packageJson), - }); - }); - - afterEach(() => { - tempFs.reset(); - }); - - afterAll(() => { - tempFs.cleanup(); - }); - - it('should create task hash', async () => { - const hasher = new InProcessTaskHasher( - { - parent: [{ file: '/file', hash: 'file.hash' }], - unrelated: [{ file: 'libs/unrelated/filec.ts', hash: 'filec.hash' }], - }, - allWorkspaceFiles, - { - nodes: { - parent: { - name: 'parent', - type: 'lib', - data: { - root: 'libs/parent', - targets: { - build: { - executor: 'nx:run-commands', - inputs: [ - 'default', - '^default', - { runtime: 'echo runtime123' }, - { env: 'TESTENV' }, - { env: 'NONEXISTENTENV' }, - { - input: 'default', - projects: ['unrelated', 'tag:some-tag'], - }, - ], - }, - }, - }, - }, - unrelated: { - name: 'unrelated', - type: 'lib', - data: { - root: 'libs/unrelated', - targets: { build: {} }, - }, - }, - tagged: { - name: 'tagged', - type: 'lib', - data: { - root: 'libs/tagged', - targets: { build: {} }, - tags: ['some-tag'], - }, - }, - }, - dependencies: { - parent: [], - }, - externalNodes: {}, - }, - - {} as any, - null, - { - runtimeCacheInputs: ['echo runtime456'], - } - ); - - const hash = await hasher.hashTask( - { - target: { project: 'parent', target: 'build' }, - id: 'parent-build', - overrides: { prop: 'prop-value' }, - outputs: [], - parallelism: true, - }, - { - roots: ['parent-build'], - tasks: { - 'parent-build': { - id: 'parent-build', - target: { project: 'parent', target: 'build' }, - overrides: {}, - outputs: [], - parallelism: true, - }, - }, - dependencies: {}, - }, - { TESTENV: 'env123' } - ); - - expect(hash).toMatchSnapshot(); - }); - - it('should hash task where the project has dependencies', async () => { - const hasher = new InProcessTaskHasher( - { - parent: [ - { file: '/filea.ts', hash: 'a.hash' }, - { file: '/filea.spec.ts', hash: 'a.spec.hash' }, - ], - child: [ - { file: '/fileb.ts', hash: 'b.hash' }, - { file: '/fileb.spec.ts', hash: 'b.spec.hash' }, - ], - }, - allWorkspaceFiles, - { - nodes: { - parent: { - name: 'parent', - type: 'lib', - data: { - root: 'libs/parent', - targets: { build: { executor: 'unknown' } }, - }, - }, - child: { - name: 'child', - type: 'lib', - data: { - root: 'libs/child', - targets: { build: {} }, - }, - }, - }, - externalNodes: {}, - dependencies: { - parent: [{ source: 'parent', target: 'child', type: 'static' }], - }, - }, - {} as any, - null, - {} - ); - - const hash = await hasher.hashTask( - { - target: { project: 'parent', target: 'build' }, - id: 'parent-build', - overrides: { prop: 'prop-value' }, - outputs: [], - parallelism: true, - }, - { - roots: ['child-build'], - tasks: { - 'parent-build': { - id: 'parent-build', - target: { project: 'parent', target: 'build' }, - overrides: {}, - outputs: [], - parallelism: true, - }, - 'child-build': { - id: 'child-build', - target: { project: 'child', target: 'build' }, - overrides: {}, - outputs: [], - parallelism: true, - }, - }, - dependencies: { - 'parent-build': ['child-build'], - }, - }, - {} - ); - - expect(hash).toMatchSnapshot(); - }); - - it('should hash non-default filesets', async () => { - const hasher = new InProcessTaskHasher( - { - parent: [ - { file: 'libs/parent/filea.ts', hash: 'a.hash' }, - { file: 'libs/parent/filea.spec.ts', hash: 'a.spec.hash' }, - ], - child: [ - { file: 'libs/child/fileb.ts', hash: 'b.hash' }, - { file: 'libs/child/fileb.spec.ts', hash: 'b.spec.hash' }, - ], - }, - allWorkspaceFiles, - { - nodes: { - parent: { - name: 'parent', - type: 'lib', - data: { - root: 'libs/parent', - targets: { - build: { - inputs: ['prod', '^prod'], - executor: 'nx:run-commands', - }, - }, - }, - }, - child: { - name: 'child', - type: 'lib', - data: { - root: 'libs/child', - namedInputs: { - prod: ['default'], - }, - targets: { build: { executor: 'unknown' } }, - }, - }, - }, - externalNodes: {}, - dependencies: { - parent: [{ source: 'parent', target: 'child', type: 'static' }], - }, - }, - { - namedInputs: { - prod: ['!{projectRoot}/**/*.spec.ts'], - }, - } as any, - null, - {} - ); - - const hash = await hasher.hashTask( - { - target: { project: 'parent', target: 'build' }, - id: 'parent-build', - overrides: { prop: 'prop-value' }, - outputs: [], - parallelism: true, - }, - { - roots: ['child-build'], - tasks: { - 'parent-build': { - id: 'parent-build', - target: { project: 'parent', target: 'build' }, - overrides: {}, - outputs: [], - parallelism: true, - }, - 'child-build': { - id: 'child-build', - target: { project: 'child', target: 'build' }, - overrides: {}, - outputs: [], - parallelism: true, - }, - }, - dependencies: { - 'parent-build': ['child-build'], - }, - }, - {} - ); - - expect(hash).toMatchSnapshot(); - }); - - it('should hash multiple filesets of a project', async () => { - const hasher = new InProcessTaskHasher( - { - parent: [ - { file: 'libs/parent/filea.ts', hash: 'a.hash' }, - { file: 'libs/parent/filea.spec.ts', hash: 'a.spec.hash' }, - ], - }, - allWorkspaceFiles, - { - nodes: { - parent: { - name: 'parent', - type: 'lib', - data: { - root: 'libs/parent', - targets: { - build: { - inputs: ['prod'], - executor: 'nx:run-commands', - }, - test: { - inputs: ['default'], - dependsOn: ['build'], - executor: 'nx:run-commands', - }, - }, - }, - }, - }, - externalNodes: {}, - dependencies: { - parent: [], - }, - }, - { - namedInputs: { - prod: ['!{projectRoot}/**/*.spec.ts'], - }, - } as any, - null, - {} - ); - - const taskGraph = { - roots: ['parent-test'], - tasks: { - 'parent-test': { - id: 'parent-test', - target: { project: 'parent', target: 'test' }, - overrides: {}, - outputs: [], - parallelism: true, - }, - }, - dependencies: {}, - }; - - const test = await hasher.hashTask( - { - target: { project: 'parent', target: 'test' }, - id: 'parent-test', - overrides: { prop: 'prop-value' }, - outputs: [], - parallelism: true, - }, - taskGraph, - {} - ); - - expect(test).toMatchSnapshot(); - - const build = await hasher.hashTask( - { - target: { project: 'parent', target: 'build' }, - id: 'parent-build', - overrides: { prop: 'prop-value' }, - outputs: [], - parallelism: true, - }, - taskGraph, - {} - ); - - expect(build).toMatchSnapshot(); - }); - - it('should be able to handle multiple filesets per project', async () => { - const hasher = new InProcessTaskHasher( - { - parent: [ - { file: 'libs/parent/filea.ts', hash: 'a.hash' }, - { file: 'libs/parent/filea.spec.ts', hash: 'a.spec.hash' }, - ], - child: [ - { file: 'libs/child/fileb.ts', hash: 'b.hash' }, - { file: 'libs/child/fileb.spec.ts', hash: 'b.spec.hash' }, - ], - }, - allWorkspaceFiles, - { - nodes: { - parent: { - name: 'parent', - type: 'lib', - data: { - root: 'libs/parent', - targets: { - test: { - inputs: ['default', '^prod'], - executor: 'nx:run-commands', - }, - }, - }, - }, - child: { - name: 'child', - type: 'lib', - data: { - root: 'libs/child', - namedInputs: { - prod: [ - '!{projectRoot}/**/*.spec.ts', - '{workspaceRoot}/global2', - { env: 'MY_TEST_HASH_ENV' }, - ], - }, - targets: { - test: { - inputs: ['default'], - executor: 'nx:run-commands', - }, - }, - }, - }, - }, - externalNodes: {}, - dependencies: { - parent: [{ source: 'parent', target: 'child', type: 'static' }], - }, - }, - - { - namedInputs: { - default: ['{projectRoot}/**/*', '{workspaceRoot}/global1'], - prod: ['!{projectRoot}/**/*.spec.ts'], - }, - } as any, - null, - {} - ); - - const taskGraph = { - roots: ['child-test'], - tasks: { - 'parent-test': { - id: 'parent-test', - target: { project: 'parent', target: 'test' }, - overrides: {}, - outputs: [], - parallelism: true, - }, - 'child-test': { - id: 'child-test', - target: { project: 'child', target: 'test' }, - overrides: {}, - outputs: [], - parallelism: true, - }, - }, - dependencies: { - 'parent-test': ['child-test'], - }, - }; - - const parentHash = await hasher.hashTask( - { - target: { project: 'parent', target: 'test' }, - id: 'parent-test', - overrides: { prop: 'prop-value' }, - outputs: [], - parallelism: true, - }, - taskGraph, - { MY_TEST_HASH_ENV: 'MY_TEST_HASH_ENV_VALUE' } - ); - - expect(parentHash).toMatchSnapshot(); - - const childHash = await hasher.hashTask( - { - target: { project: 'child', target: 'test' }, - id: 'child-test', - overrides: { prop: 'prop-value' }, - outputs: [], - parallelism: true, - }, - taskGraph, - { MY_TEST_HASH_ENV: 'MY_TEST_HASH_ENV_VALUE' } - ); - - expect(childHash).toMatchSnapshot(); - }); - - it('should use targetDefaults from nx.json', async () => { - const hasher = new InProcessTaskHasher( - { - parent: [ - { file: 'libs/parent/filea.ts', hash: 'a.hash' }, - { file: 'libs/parent/filea.spec.ts', hash: 'a.spec.hash' }, - ], - child: [ - { file: 'libs/child/fileb.ts', hash: 'b.hash' }, - { file: 'libs/child/fileb.spec.ts', hash: 'b.spec.hash' }, - ], - }, - allWorkspaceFiles, - { - nodes: { - parent: { - name: 'parent', - type: 'lib', - data: { - root: 'libs/parent', - targets: { - build: { executor: 'nx:run-commands' }, - }, - }, - }, - child: { - name: 'child', - type: 'lib', - data: { - root: 'libs/child', - targets: { build: { executor: 'nx:run-commands' } }, - }, - }, - }, - dependencies: { - parent: [{ source: 'parent', target: 'child', type: 'static' }], - }, - externalNodes: {}, - }, - - { - namedInputs: { - prod: ['!{projectRoot}/**/*.spec.ts'], - }, - targetDefaults: { - build: { - inputs: ['prod', '^prod'], - }, - }, - } as any, - null, - {} - ); - - const hash = await hasher.hashTask( - { - target: { project: 'parent', target: 'build' }, - id: 'parent-build', - overrides: { prop: 'prop-value' }, - outputs: [], - parallelism: true, - }, - { - roots: ['child-build'], - tasks: { - 'parent-build': { - id: 'parent-build', - target: { project: 'parent', target: 'build' }, - overrides: {}, - outputs: [], - parallelism: true, - }, - 'child-build': { - id: 'child-build', - target: { project: 'child', target: 'build' }, - overrides: {}, - outputs: [], - parallelism: true, - }, - }, - dependencies: { - 'parent-build': ['child-build'], - }, - }, - {} - ); - expect(hash).toMatchSnapshot(); - }); - - it('should be able to include only a part of the base tsconfig', async () => { - const hasher = new InProcessTaskHasher( - { - parent: [{ file: '/file', hash: 'file.hash' }], - }, - allWorkspaceFiles, - { - nodes: { - parent: { - name: 'parent', - type: 'lib', - data: { - root: 'libs/parent', - targets: { build: { executor: 'nx:run-commands' } }, - }, - }, - }, - dependencies: { - parent: [], - }, - externalNodes: {}, - }, - - { npmScope: 'nrwl' } as any, - null, - { - runtimeCacheInputs: ['echo runtime123', 'echo runtime456'], - selectivelyHashTsConfig: true, - } - ); - - const hash = await hasher.hashTask( - { - target: { project: 'parent', target: 'build' }, - id: 'parent-build', - overrides: { prop: 'prop-value' }, - outputs: [], - parallelism: true, - }, - { - roots: ['parent:build'], - tasks: { - 'parent-build': { - id: 'parent-build', - target: { project: 'parent', target: 'build' }, - overrides: {}, - outputs: [], - parallelism: true, - }, - }, - dependencies: {}, - }, - {} - ); - - expect(hash).toMatchSnapshot(); - }); - - it('should hash tasks where the project graph has circular dependencies', async () => { - const hasher = new InProcessTaskHasher( - { - parent: [{ file: '/filea.ts', hash: 'a.hash' }], - child: [{ file: '/fileb.ts', hash: 'b.hash' }], - }, - allWorkspaceFiles, - { - nodes: { - parent: { - name: 'parent', - type: 'lib', - data: { - root: 'libs/parent', - targets: { build: { executor: 'nx:run-commands' } }, - }, - }, - child: { - name: 'child', - type: 'lib', - data: { - root: 'libs/child', - targets: { build: { executor: 'nx:run-commands' } }, - }, - }, - }, - dependencies: { - parent: [{ source: 'parent', target: 'child', type: 'static' }], - child: [{ source: 'child', target: 'parent', type: 'static' }], - }, - externalNodes: {}, - }, - - {} as any, - null, - {} - ); - - const taskGraph = { - roots: ['child-build'], - tasks: { - 'parent-build': { - id: 'parent-build', - target: { project: 'parent', target: 'build' }, - overrides: {}, - outputs: [], - parallelism: true, - }, - 'child-build': { - id: 'child-build', - target: { project: 'child', target: 'build' }, - overrides: {}, - outputs: [], - parallelism: true, - }, - }, - dependencies: { - 'parent-build': ['child-build'], - }, - }; - - const tasksHash = await hasher.hashTask( - { - target: { project: 'parent', target: 'build' }, - id: 'parent-build', - overrides: { prop: 'prop-value' }, - outputs: [], - parallelism: true, - }, - taskGraph, - {} - ); - - expect(tasksHash).toMatchSnapshot(); - - const hashb = await hasher.hashTask( - { - target: { project: 'child', target: 'build' }, - id: 'child-build', - overrides: { prop: 'prop-value' }, - outputs: [], - parallelism: true, - }, - taskGraph, - {} - ); - - expect(hashb).toMatchSnapshot(); - }); - - it('should throw an error when failed to execute runtimeCacheInputs', async () => { - const hasher = new InProcessTaskHasher( - { - parent: [{ file: '/file', hash: 'some-hash' }], - }, - allWorkspaceFiles, - { - nodes: { - parent: { - name: 'parent', - type: 'lib', - data: { - root: 'libs/parent', - targets: { build: { executor: 'nx:run-commands' } }, - }, - }, - }, - externalNodes: {}, - dependencies: { - parent: [], - }, - }, - {} as any, - null, - { - runtimeCacheInputs: ['boom'], - } - ); - - try { - await hasher.hashTask( - { - target: { project: 'parent', target: 'build' }, - id: 'parent-build', - overrides: {}, - outputs: [], - parallelism: true, - }, - { - roots: ['parent:build'], - tasks: { - 'parent-build': { - id: 'parent-build', - target: { project: 'parent', target: 'build' }, - overrides: {}, - outputs: [], - parallelism: true, - }, - }, - dependencies: {}, - }, - {} - ); - fail('Should not be here'); - } catch (e) { - expect(e.message).toContain('Nx failed to execute'); - expect(e.message).toContain('boom'); - expect( - e.message.includes(' not found') || e.message.includes('not recognized') - ).toBeTruthy(); - } - }); - - it('should hash npm project versions', async () => { - const hasher = new InProcessTaskHasher( - { - app: [{ file: '/filea.ts', hash: 'a.hash' }], - }, - allWorkspaceFiles, - { - nodes: { - app: { - name: 'app', - type: 'app', - data: { - root: 'apps/app', - targets: { build: { executor: 'nx:run-commands' } }, - }, - }, - }, - externalNodes: { - 'npm:react': { - name: 'npm:react', - type: 'npm', - data: { - version: '17.0.0', - packageName: 'react', - }, - }, - }, - dependencies: { - 'npm:react': [], - app: [ - { source: 'app', target: 'npm:react', type: DependencyType.static }, - ], - }, - }, - - {} as any, - null, - {} - ); - - const hash = await hasher.hashTask( - { - target: { project: 'app', target: 'build' }, - id: 'app-build', - overrides: { prop: 'prop-value' }, - outputs: [], - parallelism: true, - }, - { - roots: ['app-build'], - tasks: { - 'app-build': { - id: 'app-build', - target: { project: 'app', target: 'build' }, - overrides: {}, - outputs: [], - parallelism: true, - }, - }, - dependencies: {}, - }, - {} - ); - expect(hash).toMatchSnapshot(); - }); - - it('should hash missing dependent npm project versions', async () => { - const hasher = new InProcessTaskHasher( - { - app: [{ file: '/filea.ts', hash: 'a.hash' }], - }, - allWorkspaceFiles, - { - nodes: { - app: { - name: 'app', - type: 'app', - data: { - root: 'apps/app', - targets: { build: { executor: 'nx:run-commands' } }, - }, - }, - }, - externalNodes: { - 'npm:react': { - name: 'npm:react', - type: 'npm', - data: { - version: '17.0.0', - packageName: 'react', - }, - }, - }, - dependencies: { - 'npm:react': [], - app: [ - { - source: 'app', - target: 'npm:react', - type: DependencyType.static, - }, - ], - }, - }, - - {} as any, - null, - {} - ); - - const hash = await hasher.hashTask( - { - target: { project: 'app', target: 'build' }, - id: 'app-build', - overrides: { prop: 'prop-value' }, - outputs: [], - parallelism: true, - }, - { - roots: ['app-build'], - tasks: { - 'app-build': { - id: 'app-build', - target: { project: 'app', target: 'build' }, - overrides: {}, - outputs: [], - parallelism: true, - }, - }, - dependencies: {}, - }, - {} - ); - - // note that the parent hash is based on parent source files only! - expect(hash).toMatchSnapshot(); - }); - - describe('hashTarget', () => { - it('should hash executor dependencies of @nx packages', async () => { - const hasher = new InProcessTaskHasher( - {}, - allWorkspaceFiles, - { - nodes: { - app: { - name: 'app', - type: 'app', - data: { - root: 'apps/app', - targets: { build: { executor: '@nx/webpack:webpack' } }, - }, - }, - }, - externalNodes: { - 'npm:@nx/webpack': { - name: 'npm:@nx/webpack', - type: 'npm', - data: { - packageName: '@nx/webpack', - version: '16.0.0', - }, - }, - }, - dependencies: {}, - }, - {} as any, - null, - {} - ); - - const hash = await hasher.hashTask( - { - target: { project: 'app', target: 'build' }, - id: 'app-build', - overrides: { prop: 'prop-value' }, - outputs: [], - parallelism: true, - }, - { - roots: ['app-build'], - tasks: { - 'app-build': { - id: 'app-build', - target: { project: 'app', target: 'build' }, - overrides: {}, - outputs: [], - parallelism: true, - }, - }, - dependencies: {}, - }, - {} - ); - - expect(hash).toMatchSnapshot(); - }); - - it('should hash entire subtree of dependencies deterministically', async () => { - function createHasher() { - return new InProcessTaskHasher( - { - a: [{ file: 'a/filea.ts', hash: 'a.hash' }], - b: [{ file: 'b/fileb.ts', hash: 'b.hash' }], - }, - allWorkspaceFiles, - { - nodes: { - a: { - name: 'a', - type: 'lib', - data: { - root: 'a', - targets: { build: { executor: '@nx/webpack:webpack' } }, - }, - }, - b: { - name: 'b', - type: 'lib', - data: { - root: 'b', - targets: { build: { executor: '@nx/webpack:webpack' } }, - }, - }, - }, - externalNodes: { - 'npm:@nx/webpack': { - name: 'npm:@nx/webpack', - type: 'npm', - data: { - packageName: '@nx/webpack', - version: '16.0.0', - hash: '$nx/webpack16$', - }, - }, - }, - dependencies: { - a: [ - { - source: 'a', - target: 'b', - type: DependencyType.static, - }, - ], - b: [ - { - source: 'b', - target: 'a', - type: DependencyType.static, - }, - ], - 'npm:@nx/webpack': [], - }, - }, - - {} as any, - null, - {} - ); - } - - const taskGraph = { - roots: [], - tasks: { - 'a-build': { - id: 'a-build', - target: { project: 'a', target: 'build' }, - overrides: {}, - outputs: [], - parallelism: true, - }, - 'b-build': { - id: 'b-build', - target: { project: 'b', target: 'build' }, - overrides: {}, - outputs: [], - parallelism: true, - }, - }, - dependencies: {}, - }; - - const hasher1 = createHasher(); - const hasher2 = createHasher(); - - const hashA1 = await hasher1.hashTask( - { - id: 'a-build', - target: { project: 'a', target: 'build' }, - overrides: {}, - outputs: [], - parallelism: true, - }, - taskGraph, - {} - ); - const hashB1 = await hasher1.hashTask( - { - id: 'b-build', - target: { project: 'b', target: 'build' }, - overrides: {}, - outputs: [], - parallelism: true, - }, - taskGraph, - {} - ); - - const hashB2 = await hasher2.hashTask( - { - id: 'b-build', - target: { project: 'b', target: 'build' }, - overrides: {}, - outputs: [], - parallelism: true, - }, - taskGraph, - {} - ); - const hashA2 = await hasher2.hashTask( - { - id: 'a-build', - target: { project: 'a', target: 'build' }, - overrides: {}, - outputs: [], - parallelism: true, - }, - taskGraph, - {} - ); - - expect(hashA1).toEqual(hashA2); - expect(hashB1).toEqual(hashB2); - }); - - it('should hash entire subtree of dependencies', async () => { - const hasher = new InProcessTaskHasher( - {}, - allWorkspaceFiles, - { - nodes: { - app: { - name: 'app', - type: 'app', - data: { - root: 'apps/app', - targets: { build: { executor: '@nx/webpack:webpack' } }, - }, - }, - }, - externalNodes: { - 'npm:@nx/webpack': { - name: 'npm:@nx/webpack', - type: 'npm', - data: { - packageName: '@nx/webpack', - version: '16.0.0', - hash: '$nx/webpack16$', - }, - }, - 'npm:@nx/devkit': { - name: 'npm:@nx/devkit', - type: 'npm', - data: { - packageName: '@nx/devkit', - version: '16.0.0', - hash: '$nx/devkit16$', - }, - }, - 'npm:nx': { - name: 'npm:nx', - type: 'npm', - data: { - packageName: 'nx', - version: '16.0.0', - hash: '$nx16$', - }, - }, - 'npm:webpack': { - name: 'npm:webpack', - type: 'npm', - data: { - packageName: 'webpack', - version: '5.0.0', // no hash intentionally - }, - }, - }, - dependencies: { - 'npm:@nx/webpack': [ - { - source: 'npm:@nx/webpack', - target: 'npm:@nx/devkit', - type: DependencyType.static, - }, - { - source: 'npm:@nx/webpack', - target: 'npm:nx', - type: DependencyType.static, - }, - { - source: 'npm:@nx/webpack', - target: 'npm:webpack', - type: DependencyType.static, - }, - ], - 'npm:@nx/devkit': [ - { - source: 'npm:@nx/devkit', - target: 'npm:nx', - type: DependencyType.static, - }, - ], - }, - }, - {} as any, - null, - {} - ); - - const hash = await hasher.hashTask( - { - target: { project: 'app', target: 'build' }, - id: 'app-build', - overrides: { prop: 'prop-value' }, - outputs: [], - parallelism: true, - }, - { - roots: ['app-build'], - tasks: { - 'app-build': { - id: 'app-build', - target: { project: 'app', target: 'build' }, - overrides: {}, - outputs: [], - parallelism: true, - }, - }, - dependencies: {}, - }, - {} - ); - - expect(hash).toMatchSnapshot(); - }); - - it('should hash entire subtree in a deterministic way', async () => { - const createHasher = () => - new InProcessTaskHasher( - {}, - allWorkspaceFiles, - { - nodes: { - appA: { - name: 'appA', - type: 'app', - data: { - root: 'apps/appA', - targets: { build: { executor: '@nx/webpack:webpack' } }, - }, - }, - appB: { - name: 'appB', - type: 'app', - data: { - root: 'apps/appB', - targets: { build: { executor: '@nx/webpack:webpack' } }, - }, - }, - }, - externalNodes: { - 'npm:packageA': { - name: 'npm:packageA', - type: 'npm', - data: { - packageName: 'packageA', - version: '0.0.0', - hash: '$packageA0.0.0$', - }, - }, - 'npm:packageB': { - name: 'npm:packageB', - type: 'npm', - data: { - packageName: 'packageB', - version: '0.0.0', - hash: '$packageB0.0.0$', - }, - }, - 'npm:packageC': { - name: 'npm:packageC', - type: 'npm', - data: { - packageName: 'packageC', - version: '0.0.0', - hash: '$packageC0.0.0$', - }, - }, - 'npm:@nx/webpack': { - name: 'npm:@nx/webpack', - type: 'npm', - data: { - packageName: '@nx/webpack', - version: '0.0.0', - hash: '$@nx/webpack0.0.0$', - }, - }, - }, - dependencies: { - appA: [ - { - source: 'appA', - target: 'npm:packageA', - type: DependencyType.static, - }, - { - source: 'appA', - target: 'npm:packageB', - type: DependencyType.static, - }, - { - source: 'appA', - target: 'npm:packageC', - type: DependencyType.static, - }, - ], - appB: [ - { - source: 'appB', - target: 'npm:packageC', - type: DependencyType.static, - }, - ], - 'npm:packageC': [ - { - source: 'npm:packageC', - target: 'npm:packageA', - type: DependencyType.static, - }, - { - source: 'npm:packageC', - target: 'npm:packageB', - type: DependencyType.static, - }, - ], - 'npm:packageB': [ - { - source: 'npm:packageB', - target: 'npm:packageA', - type: DependencyType.static, - }, - ], - 'npm:packageA': [ - { - source: 'npm:packageA', - target: 'npm:packageC', - type: DependencyType.static, - }, - ], - }, - }, - - {} as any, - null, - {} - ); - - const computeTaskHash = async (hasher, appName) => { - return await hasher.hashTask( - { - target: { project: appName, target: 'build' }, - id: `${appName}-build`, - overrides: { prop: 'prop-value' }, - }, - { - roots: ['app-build'], - tasks: { - 'app-build': { - id: 'app-build', - target: { project: 'app', target: 'build' }, - overrides: {}, - }, - }, - dependencies: {}, - } - ); - }; - - const hasher1 = createHasher(); - - const hashAppA1 = await computeTaskHash(hasher1, 'appA'); - const hashAppB1 = await computeTaskHash(hasher1, 'appB'); - - const hasher2 = createHasher(); - - const hashAppB2 = await computeTaskHash(hasher2, 'appB'); - const hashAppA2 = await computeTaskHash(hasher2, 'appA'); - - expect(hashAppB1).toEqual(hashAppB2); - expect(hashAppA1).toEqual(hashAppA2); - - expect(hashAppA1).toMatchSnapshot(); - expect(hashAppB1).toMatchSnapshot(); - }); - - it('should not hash when nx:run-commands executor', async () => { - const hasher = new InProcessTaskHasher( - {}, - [], - { - nodes: { - app: { - name: 'app', - type: 'app', - data: { - root: 'apps/app', - targets: { build: { executor: 'nx:run-commands' } }, - }, - }, - }, - externalNodes: { - 'npm:nx': { - name: 'npm:nx', - type: 'npm', - data: { - packageName: 'nx', - version: '16.0.0', - }, - }, - }, - dependencies: {}, - }, - - {} as any, - null, - {} - ); - - const hash = await hasher.hashTask( - { - target: { project: 'app', target: 'build' }, - id: 'app-build', - overrides: { prop: 'prop-value' }, - outputs: [], - parallelism: true, - }, - { - roots: ['app-build'], - tasks: { - 'app-build': { - id: 'app-build', - target: { project: 'app', target: 'build' }, - overrides: {}, - outputs: [], - parallelism: true, - }, - }, - dependencies: {}, - }, - {} - ); - - expect(hash.details.nodes['AllExternalDependencies']).toEqual( - '5189537834781127994' - ); - }); - - it('should use externalDependencies to override nx:run-commands', async () => { - const hasher = new InProcessTaskHasher( - {}, - allWorkspaceFiles, - { - nodes: { - app: { - name: 'app', - type: 'app', - data: { - root: 'apps/app', - targets: { - build: { - executor: 'nx:run-commands', - inputs: [ - { fileset: '{projectRoot}/**/*' }, - { externalDependencies: ['webpack', 'react'] }, - ], - }, - }, - }, - }, - }, - externalNodes: { - 'npm:nx': { - name: 'npm:nx', - type: 'npm', - data: { - packageName: 'nx', - version: '16.0.0', - }, - }, - 'npm:webpack': { - name: 'npm:webpack', - type: 'npm', - data: { - packageName: 'webpack', - version: '5.0.0', - }, - }, - 'npm:react': { - name: 'npm:react', - type: 'npm', - data: { - packageName: 'react', - version: '17.0.0', - }, - }, - }, - dependencies: {}, - }, - - {} as any, - null, - {} - ); - - const hash = await hasher.hashTask( - { - target: { project: 'app', target: 'build' }, - id: 'app-build', - overrides: { prop: 'prop-value' }, - outputs: [], - parallelism: true, - }, - { - roots: ['app-build'], - tasks: { - 'app-build': { - id: 'app-build', - target: { project: 'app', target: 'build' }, - overrides: {}, - outputs: [], - parallelism: true, - }, - }, - dependencies: {}, - }, - {} - ); - - expect(hash).toMatchSnapshot(); - }); - - it('should use externalDependencies with empty array to ignore all deps', async () => { - const hasher = new InProcessTaskHasher( - {}, - allWorkspaceFiles, - { - nodes: { - app: { - name: 'app', - type: 'app', - data: { - root: 'apps/app', - targets: { - build: { - executor: 'nx:run-commands', - inputs: [ - { fileset: '{projectRoot}/**/*' }, - { externalDependencies: [] }, // intentionally empty - ], - }, - }, - }, - }, - }, - externalNodes: { - 'npm:nx': { - name: 'npm:nx', - type: 'npm', - data: { - packageName: 'nx', - version: '16.0.0', - }, - }, - 'npm:webpack': { - name: 'npm:webpack', - type: 'npm', - data: { - packageName: 'webpack', - version: '5.0.0', - }, - }, - 'npm:react': { - name: 'npm:react', - type: 'npm', - data: { - packageName: 'react', - version: '17.0.0', - }, - }, - }, - dependencies: {}, - }, - - {} as any, - null, - {} - ); - - const hash = await hasher.hashTask( - { - target: { project: 'app', target: 'build' }, - id: 'app-build', - overrides: { prop: 'prop-value' }, - outputs: [], - parallelism: true, - }, - { - roots: ['app-build'], - tasks: { - 'app-build': { - id: 'app-build', - target: { project: 'app', target: 'build' }, - overrides: {}, - outputs: [], - parallelism: true, - }, - }, - dependencies: {}, - }, - {} - ); - - expect(hash).toMatchSnapshot(); - }); - }); - - describe('dependentTasksOutputFiles', () => { - it('should depend on dependent tasks output files', async () => { - const hasher = new InProcessTaskHasher( - { - parent: [ - { file: 'libs/parent/filea.ts', hash: 'a.hash' }, - { file: 'libs/parent/filea.spec.ts', hash: 'a.spec.hash' }, - ], - child: [ - { file: 'libs/child/fileb.ts', hash: 'b.hash' }, - { file: 'libs/child/fileb.spec.ts', hash: 'b.spec.hash' }, - ], - grandchild: [ - { file: 'libs/grandchild/filec.ts', hash: 'c.hash' }, - { file: 'libs/grandchild/filec.spec.ts', hash: 'c.spec.hash' }, - ], - }, - allWorkspaceFiles, - { - nodes: { - parent: { - name: 'parent', - type: 'lib', - data: { - root: 'libs/parent', - targets: { - build: { - dependsOn: ['^build'], - inputs: ['prod', 'deps'], - executor: 'nx:run-commands', - outputs: ['{workspaceRoot}/dist/{projectRoot}'], - }, - }, - }, - }, - child: { - name: 'child', - type: 'lib', - data: { - root: 'libs/child', - targets: { - build: { - dependsOn: ['^build'], - inputs: ['prod', 'deps'], - executor: 'nx:run-commands', - outputs: ['{workspaceRoot}/dist/{projectRoot}'], - }, - }, - }, - }, - grandchild: { - name: 'grandchild', - type: 'lib', - data: { - root: 'libs/grandchild', - targets: { - build: { - dependsOn: ['^build'], - inputs: ['prod', 'deps'], - executor: 'nx:run-commands', - outputs: ['{workspaceRoot}/dist/{projectRoot}'], - }, - }, - }, - }, - }, - externalNodes: {}, - dependencies: { - parent: [{ source: 'parent', target: 'child', type: 'static' }], - child: [{ source: 'child', target: 'grandchild', type: 'static' }], - }, - }, - { - namedInputs: { - prod: ['!{projectRoot}/**/*.spec.ts'], - deps: [ - { dependentTasksOutputFiles: '**/*.d.ts', transitive: true }, - ], - }, - targetDefaults: { - build: { - dependsOn: ['^build'], - inputs: ['prod', 'deps'], - executor: 'nx:run-commands', - options: { - outputPath: 'dist/libs/{projectRoot}', - }, - outputs: ['{options.outputPath}'], - }, - }, - } as any, - null, - {} - ); - - await tempFs.createFiles({ - 'dist/libs/child/index.d.ts': '', - 'dist/libs/grandchild/index.d.ts': '', - }); - - const hash = await hasher.hashTask( - { - target: { project: 'parent', target: 'build' }, - id: 'parent-build', - overrides: { prop: 'prop-value' }, - outputs: [], - parallelism: true, - }, - { - roots: ['grandchild-build'], - tasks: { - 'parent-build': { - id: 'parent-build', - target: { project: 'parent', target: 'build' }, - overrides: {}, - outputs: ['dist/libs/libs/parent'], - parallelism: true, - }, - 'child-build': { - id: 'child-build', - target: { project: 'child', target: 'build' }, - overrides: {}, - outputs: ['dist/libs/libs/child'], - parallelism: true, - }, - 'grandchild-build': { - id: 'grandchild-build', - target: { project: 'grandchild', target: 'build' }, - overrides: {}, - outputs: ['dist/libs/libs/grandchild'], - parallelism: true, - }, - }, - dependencies: { - 'parent-build': ['child-build'], - 'child-build': ['grandchild-build'], - }, - }, - {} - ); - - expect(hash).toMatchSnapshot(); - }); - - it('should work with dependent tasks with globs as outputs', async () => { - const hasher = new InProcessTaskHasher( - { - parent: [ - { file: 'libs/parent/filea.ts', hash: 'a.hash' }, - { file: 'libs/parent/filea.spec.ts', hash: 'a.spec.hash' }, - ], - child: [ - { file: 'libs/child/fileb.ts', hash: 'b.hash' }, - { file: 'libs/child/fileb.spec.ts', hash: 'b.spec.hash' }, - ], - grandchild: [ - { file: 'libs/grandchild/filec.ts', hash: 'c.hash' }, - { file: 'libs/grandchild/filec.spec.ts', hash: 'c.spec.hash' }, - ], - }, - allWorkspaceFiles, - { - nodes: { - parent: { - name: 'parent', - type: 'lib', - data: { - root: 'libs/parent', - targets: { - build: { - dependsOn: ['^build'], - inputs: ['prod', 'deps'], - executor: 'nx:run-commands', - outputs: ['{workspaceRoot}/dist/{projectRoot}'], - }, - }, - }, - }, - child: { - name: 'child', - type: 'lib', - data: { - root: 'libs/child', - targets: { - build: { - dependsOn: ['^build'], - inputs: ['prod', 'deps'], - executor: 'nx:run-commands', - outputs: ['{workspaceRoot}/dist/{projectRoot}/**/*'], - }, - }, - }, - }, - grandchild: { - name: 'grandchild', - type: 'lib', - data: { - root: 'libs/grandchild', - targets: { - build: { - dependsOn: ['^build'], - inputs: ['prod', 'deps'], - executor: 'nx:run-commands', - outputs: ['{workspaceRoot}/dist/{projectRoot}'], - }, - }, - }, - }, - }, - externalNodes: {}, - dependencies: { - parent: [{ source: 'parent', target: 'child', type: 'static' }], - child: [{ source: 'child', target: 'grandchild', type: 'static' }], - }, - }, - - { - namedInputs: { - prod: ['!{projectRoot}/**/*.spec.ts'], - deps: [ - { dependentTasksOutputFiles: '**/*.d.ts', transitive: true }, - ], - }, - targetDefaults: { - build: { - dependsOn: ['^build'], - inputs: ['prod', 'deps'], - executor: 'nx:run-commands', - options: { - outputPath: 'dist/libs/{projectRoot}', - }, - outputs: ['{options.outputPath}'], - }, - }, - } as any, - null, - {} - ); - - await tempFs.createFiles({ - 'dist/libs/child/index.d.ts': '', - 'dist/libs/grandchild/index.d.ts': '', - }); - - const hash = await hasher.hashTask( - { - target: { project: 'parent', target: 'build' }, - id: 'parent-build', - overrides: { prop: 'prop-value' }, - outputs: [], - parallelism: true, - }, - { - roots: ['grandchild-build'], - tasks: { - 'parent-build': { - id: 'parent-build', - target: { project: 'parent', target: 'build' }, - overrides: {}, - outputs: [], - parallelism: true, - }, - 'child-build': { - id: 'child-build', - target: { project: 'child', target: 'build' }, - overrides: {}, - outputs: [], - parallelism: true, - }, - 'grandchild-build': { - id: 'grandchild-build', - target: { project: 'grandchild', target: 'build' }, - overrides: {}, - outputs: [], - parallelism: true, - }, - }, - dependencies: { - 'parent-build': ['child-build'], - 'child-build': ['grandchild-build'], - }, - }, - {} - ); - - expect(hash).toMatchSnapshot(); - }); - }); - describe('expandNamedInput', () => { it('should expand named inputs', () => { const expanded = expandNamedInput('c', { diff --git a/packages/nx/src/hasher/task-hasher.ts b/packages/nx/src/hasher/task-hasher.ts index 93bdbb65636f6..4617490114e43 100644 --- a/packages/nx/src/hasher/task-hasher.ts +++ b/packages/nx/src/hasher/task-hasher.ts @@ -1,6 +1,5 @@ import { FileData, - ProjectFileMap, ProjectGraph, ProjectGraphProjectNode, } from '../config/project-graph'; @@ -8,7 +7,6 @@ import { NxJsonConfiguration } from '../config/nx-json'; import { Task, TaskGraph } from '../config/task-graph'; import { DaemonClient } from '../daemon/client/client'; import { hashArray } from './file-hasher'; -import { NodeTaskHasherImpl } from './node-task-hasher-impl'; import { InputDefinition } from '../config/workspace-json-project-json'; import { minimatch } from 'minimatch'; import { NativeTaskHasherImpl } from './native-task-hasher-impl'; @@ -127,60 +125,23 @@ export class DaemonBasedTaskHasher implements TaskHasher { } export class InProcessTaskHasher implements TaskHasher { - static version = '3.0'; private taskHasher: TaskHasherImpl; - private useNativeTaskHasher = process.env.NX_NATIVE_TASK_HASHER !== 'false'; - constructor( - private readonly projectFileMap: ProjectFileMap, - private readonly allWorkspaceFiles: FileData[], private readonly projectGraph: ProjectGraph, private readonly nxJson: NxJsonConfiguration, private readonly externalRustReferences: NxWorkspaceFilesExternals | null, private readonly options: any ) { - const legacyRuntimeInputs = ( - this.options && this.options.runtimeCacheInputs - ? this.options.runtimeCacheInputs - : [] - ).map((r) => ({ runtime: r })); - - if (process.env.NX_CLOUD_ENCRYPTION_KEY) { - legacyRuntimeInputs.push({ env: 'NX_CLOUD_ENCRYPTION_KEY' }); - } - - const legacyFilesetInputs = [ - 'nx.json', - - // ignore files will change the set of inputs to the hasher - '.gitignore', - '.nxignore', - ].map((d) => ({ fileset: `{workspaceRoot}/${d}` })); - - this.taskHasher = !this.useNativeTaskHasher - ? new NodeTaskHasherImpl( - nxJson, - legacyRuntimeInputs, - legacyFilesetInputs, - this.projectFileMap, - this.allWorkspaceFiles, - this.projectGraph, - { - selectivelyHashTsConfig: - this.options?.selectivelyHashTsConfig ?? false, - } - ) - : new NativeTaskHasherImpl( - workspaceRoot, - nxJson, - this.projectGraph, - this.externalRustReferences, - { - selectivelyHashTsConfig: - this.options?.selectivelyHashTsConfig ?? false, - } - ); + this.taskHasher = new NativeTaskHasherImpl( + workspaceRoot, + this.nxJson, + this.projectGraph, + this.externalRustReferences, + { + selectivelyHashTsConfig: this.options?.selectivelyHashTsConfig ?? false, + } + ); } async hashTasks( @@ -188,20 +149,14 @@ export class InProcessTaskHasher implements TaskHasher { taskGraph?: TaskGraph, env?: NodeJS.ProcessEnv ): Promise { - if (this.useNativeTaskHasher) { - const hashes = await this.taskHasher.hashTasks( - tasks, - taskGraph, - env ?? process.env - ); - return tasks.map((task, index) => - this.createHashDetails(task, hashes[index]) - ); - } else { - return await Promise.all( - tasks.map((t) => this.hashTask(t, taskGraph, env)) - ); - } + const hashes = await this.taskHasher.hashTasks( + tasks, + taskGraph, + env ?? process.env + ); + return tasks.map((task, index) => + this.createHashDetails(task, hashes[index]) + ); } async hashTask( diff --git a/packages/nx/src/native/tasks/hash_planner.rs b/packages/nx/src/native/tasks/hash_planner.rs index 6a38be96aa9ee..c840d673546f9 100644 --- a/packages/nx/src/native/tasks/hash_planner.rs +++ b/packages/nx/src/native/tasks/hash_planner.rs @@ -71,11 +71,14 @@ impl HashPlanner { let mut inputs: Vec = target .unwrap_or(vec![]) .into_iter() - .chain(vec![HashInstruction::WorkspaceFileSet(vec![ - "{workspaceRoot}/nx.json".to_string(), - "{workspaceRoot}/.gitignore".to_string(), - "{workspaceRoot}/.nxignore".to_string(), - ])]) + .chain(vec![ + HashInstruction::Environment("NX_CLOUD_ENCRYPTION_KEY".into()), + HashInstruction::WorkspaceFileSet(vec![ + "{workspaceRoot}/nx.json".to_string(), + "{workspaceRoot}/.gitignore".to_string(), + "{workspaceRoot}/.nxignore".to_string(), + ]), + ]) .chain(self_inputs) .collect(); diff --git a/packages/nx/src/native/tasks/hashers/hash_env.rs b/packages/nx/src/native/tasks/hashers/hash_env.rs index 6ff302a6997a7..fc093f8057c18 100644 --- a/packages/nx/src/native/tasks/hashers/hash_env.rs +++ b/packages/nx/src/native/tasks/hashers/hash_env.rs @@ -1,9 +1,9 @@ use crate::native::hasher::hash; use std::collections::HashMap; -pub fn hash_env(env_name: &str, env: &HashMap) -> anyhow::Result { +pub fn hash_env(env_name: &str, env: &HashMap) -> String { let env_value = env.get(env_name).map(|s| s.as_str()).unwrap_or(""); - Ok(hash(env_value.as_bytes())) + hash(env_value.as_bytes()) } #[cfg(test)] @@ -15,7 +15,7 @@ mod test { let mut env = HashMap::new(); env.insert("foo".to_string(), "bar".to_string()); env.insert("baz".to_string(), "qux".to_string()); - let hash = hash_env("foo", &env).unwrap(); + let hash = hash_env("foo", &env); assert_eq!(hash, "15304296276065178466"); } @@ -23,7 +23,7 @@ mod test { #[test] fn should_provide_a_default_hash_if_one_does_not_exist() { let env = HashMap::new(); - let hash = hash_env("foo", &env).unwrap(); + let hash = hash_env("foo", &env); assert_eq!(hash, "3244421341483603138"); } diff --git a/packages/nx/src/native/tasks/task_hasher.rs b/packages/nx/src/native/tasks/task_hasher.rs index 333a23e13422d..5e8055a6d4635 100644 --- a/packages/nx/src/native/tasks/task_hasher.rs +++ b/packages/nx/src/native/tasks/task_hasher.rs @@ -186,7 +186,7 @@ impl TaskHasher { hashed_runtime } HashInstruction::Environment(env) => { - let hashed_env = hash_env(env, js_env)?; + let hashed_env = hash_env(env, js_env); trace!(parent: &span, "hash_env: {:?}", now.elapsed()); hashed_env } diff --git a/packages/nx/src/native/tests/__snapshots__/planner.spec.ts.snap b/packages/nx/src/native/tests/__snapshots__/planner.spec.ts.snap index 6b85614467105..10e27e0fd7596 100644 --- a/packages/nx/src/native/tests/__snapshots__/planner.spec.ts.snap +++ b/packages/nx/src/native/tests/__snapshots__/planner.spec.ts.snap @@ -3,13 +3,14 @@ exports[`task planner dependentTasksOutputFiles should depend on dependent tasks output files 1`] = ` { "parent:build": [ - "**/*.d.ts:dist/libs/child", - "**/*.d.ts:dist/libs/grandchild", - "AllExternalDependencies", + "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]", + "env:NX_CLOUD_ENCRYPTION_KEY", "parent:!{projectRoot}/**/*.spec.ts", "parent:ProjectConfiguration", "parent:TsConfig", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]", + "**/*.d.ts:dist/libs/child", + "**/*.d.ts:dist/libs/grandchild", + "AllExternalDependencies", ], } `; @@ -17,17 +18,18 @@ exports[`task planner dependentTasksOutputFiles should depend on dependent tasks exports[`task planner should be able to handle multiple filesets per project 1`] = ` { "parent:test": [ - "AllExternalDependencies", + "workspace:[{workspaceRoot}/global1]", + "workspace:[{workspaceRoot}/global2]", + "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]", + "env:MY_TEST_HASH_ENV", + "env:NX_CLOUD_ENCRYPTION_KEY", "child:!{projectRoot}/**/*.spec.ts", + "parent:{projectRoot}/**/*", "child:ProjectConfiguration", - "child:TsConfig", - "env:MY_TEST_HASH_ENV", "parent:ProjectConfiguration", + "child:TsConfig", "parent:TsConfig", - "parent:{projectRoot}/**/*", - "workspace:[{workspaceRoot}/global1]", - "workspace:[{workspaceRoot}/global2]", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]", + "AllExternalDependencies", ], } `; @@ -35,24 +37,26 @@ exports[`task planner should be able to handle multiple filesets per project 1`] exports[`task planner should build plans where the project graph has circular dependencies 1`] = ` { "child:build": [ - "AllExternalDependencies", - "child:ProjectConfiguration", - "child:TsConfig", + "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]", + "env:NX_CLOUD_ENCRYPTION_KEY", "child:{projectRoot}/**/*", + "parent:{projectRoot}/**/*", + "child:ProjectConfiguration", "parent:ProjectConfiguration", + "child:TsConfig", "parent:TsConfig", - "parent:{projectRoot}/**/*", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]", + "AllExternalDependencies", ], "parent:build": [ - "AllExternalDependencies", - "child:ProjectConfiguration", - "child:TsConfig", + "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]", + "env:NX_CLOUD_ENCRYPTION_KEY", "child:{projectRoot}/**/*", + "parent:{projectRoot}/**/*", + "child:ProjectConfiguration", "parent:ProjectConfiguration", + "child:TsConfig", "parent:TsConfig", - "parent:{projectRoot}/**/*", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]", + "AllExternalDependencies", ], } `; @@ -60,13 +64,14 @@ exports[`task planner should build plans where the project graph has circular de exports[`task planner should hash executors 1`] = ` { "proj:lint": [ - "npm:@nx/devkit", - "npm:@nx/eslint", - "proj:ProjectConfiguration", - "proj:TsConfig", - "proj:{projectRoot}/**/*", "workspace:[{workspaceRoot}/global1]", "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]", + "env:NX_CLOUD_ENCRYPTION_KEY", + "proj:{projectRoot}/**/*", + "proj:ProjectConfiguration", + "proj:TsConfig", + "npm:@nx/devkit", + "npm:@nx/eslint", ], } `; @@ -74,12 +79,13 @@ exports[`task planner should hash executors 1`] = ` exports[`task planner should include npm projects 1`] = ` { "app:build": [ - "AllExternalDependencies", + "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]", + "env:NX_CLOUD_ENCRYPTION_KEY", + "app:{projectRoot}/**/*", "app:ProjectConfiguration", "app:TsConfig", - "app:{projectRoot}/**/*", "npm:react", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]", + "AllExternalDependencies", ], } `; @@ -87,18 +93,20 @@ exports[`task planner should include npm projects 1`] = ` exports[`task planner should make a plan with multiple filesets of a project 1`] = ` { "parent:build": [ - "AllExternalDependencies", + "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]", + "env:NX_CLOUD_ENCRYPTION_KEY", "parent:!{projectRoot}/**/*.spec.ts", "parent:ProjectConfiguration", "parent:TsConfig", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]", + "AllExternalDependencies", ], "parent:test": [ - "AllExternalDependencies", + "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]", + "env:NX_CLOUD_ENCRYPTION_KEY", + "parent:{projectRoot}/**/*", "parent:ProjectConfiguration", "parent:TsConfig", - "parent:{projectRoot}/**/*", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]", + "AllExternalDependencies", ], } `; @@ -106,14 +114,15 @@ exports[`task planner should make a plan with multiple filesets of a project 1`] exports[`task planner should plan non-default filesets 1`] = ` { "parent:build": [ - "AllExternalDependencies", - "child:ProjectConfiguration", - "child:TsConfig", + "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]", + "env:NX_CLOUD_ENCRYPTION_KEY", "child:{projectRoot}/**/*", "parent:!{projectRoot}/**/*.spec.ts", + "child:ProjectConfiguration", "parent:ProjectConfiguration", + "child:TsConfig", "parent:TsConfig", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]", + "AllExternalDependencies", ], } `; @@ -121,17 +130,18 @@ exports[`task planner should plan non-default filesets 1`] = ` exports[`task planner should plan the task where the project has dependencies 1`] = ` { "parent:build": [ - "AllExternalDependencies", - "child:ProjectConfiguration", - "child:TsConfig", + "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]", + "env:NX_CLOUD_ENCRYPTION_KEY", "child:{projectRoot}/**/*", - "grandchild:ProjectConfiguration", - "grandchild:TsConfig", "grandchild:{projectRoot}/**/*", + "parent:{projectRoot}/**/*", + "child:ProjectConfiguration", + "grandchild:ProjectConfiguration", "parent:ProjectConfiguration", + "child:TsConfig", + "grandchild:TsConfig", "parent:TsConfig", - "parent:{projectRoot}/**/*", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]", + "AllExternalDependencies", ], } `; diff --git a/packages/nx/src/native/tests/planner.spec.ts b/packages/nx/src/native/tests/planner.spec.ts index c9ac344e6479f..7a1e17614632c 100644 --- a/packages/nx/src/native/tests/planner.spec.ts +++ b/packages/nx/src/native/tests/planner.spec.ts @@ -1,7 +1,5 @@ import { TempFs } from '../../internal-testing-utils/temp-fs'; import { HashPlanner, transferProjectGraph } from '../index'; -import { Task, TaskGraph } from '../../config/task-graph'; -import { InProcessTaskHasher } from '../../hasher/task-hasher'; import { withEnvironmentVariables } from '../../internal-testing-utils/with-environment'; import { ProjectGraphBuilder } from '../../project-graph/project-graph-builder'; import { createTaskGraph } from '../../tasks-runner/create-task-graph'; @@ -11,9 +9,6 @@ import { DependencyType } from '../../config/project-graph'; let tempFs = new TempFs('task-planner'); describe('task planner', () => { - // disable NX_NATIVE_TASK_HASHER for this test because we need to compare the results of the new planner with the old task hasher - process.env.NX_NATIVE_TASK_HASHER = 'false'; - const packageJson = { name: 'nrwl', }; @@ -27,60 +22,6 @@ describe('task planner', () => { }, }, }); - const allWorkspaceFiles = [ - { file: 'yarn.lock', hash: 'yarn.lock.hash' }, - { file: 'nx.json', hash: 'nx.json.hash' }, - { file: 'package-lock.json', hash: 'package-lock.json.hash' }, - { file: 'package.json', hash: 'package.json.hash' }, - { file: 'pnpm-lock.yaml', hash: 'pnpm-lock.yaml.hash' }, - { file: 'tsconfig.base.json', hash: tsConfigBaseJson }, - { file: 'workspace.json', hash: 'workspace.json.hash' }, - { file: 'global1', hash: 'global1.hash' }, - { file: 'global2', hash: 'global2.hash' }, - ]; - - // TODO(cammisuli): This function is temporary until the new file hashing is implemented - // This should just match snapshots of the planner - async function assertHashPlan( - task: Task | Task[], - taskGraph: TaskGraph, - taskHasher: InProcessTaskHasher, - hashPlanner: HashPlanner - ) { - if (!Array.isArray(task)) task = [task]; - - function getHashPlans( - tasks: Task[], - taskGraph: TaskGraph - ): Record { - const plans = hashPlanner.getPlans( - tasks.map((task) => task.id), - taskGraph - ); - - for (const planId of Object.keys(plans)) { - plans[planId] = plans[planId].sort(); - } - - return plans; - } - - const hashes = await taskHasher.hashTasks(task, taskGraph); - const plans = getHashPlans(task, taskGraph); - - // hashNodes here are completed in order because this is run with javascript. - // we can then map the task id's by their index number with the hash nodes - let hashNodes: Record = task.reduce((acc, task, index) => { - acc[task.id] = Object.keys(hashes[index].details.nodes).sort(); - return acc; - }, {}); - - for (let taskId of task.map((task) => task.id)) { - expect(plans[taskId]).toEqual(hashNodes[taskId]); - } - - return plans; - } beforeEach(async () => { await tempFs.createFiles({ @@ -94,13 +35,8 @@ describe('task planner', () => { tempFs.reset(); }); - it('should build a plan that matches the original task-hasher', async () => { + it('should build a plan', async () => { await withEnvironmentVariables({ TESTENV: 'env123' }, async () => { - let projectFileMap = { - parent: [{ file: '/file', hash: 'file.hash' }], - unrelated: [{ file: 'libs/unrelated/filec.ts', hash: 'filec.hash' }], - }; - const builder = new ProjectGraphBuilder(); builder.addNode({ @@ -156,26 +92,33 @@ describe('task planner', () => { let nxJson = {} as any; - const hasher = new InProcessTaskHasher( - projectFileMap, - allWorkspaceFiles, - projectGraph, - nxJson, - null, - {} - ); - const ref = transferProjectGraph( transformProjectGraphForRust(projectGraph) ); const planner = new HashPlanner(nxJson as any, ref); - await assertHashPlan( - taskGraph.tasks['parent:build'], - taskGraph, - hasher, - planner - ); + const plans = planner.getPlans(['parent:build'], taskGraph); + expect(plans).toMatchInlineSnapshot(` + { + "parent:build": [ + "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]", + "runtime:echo runtime123", + "env:NONEXISTENTENV", + "env:NX_CLOUD_ENCRYPTION_KEY", + "env:TESTENV", + "parent:{projectRoot}/**/*", + "tagged:{projectRoot}/**/*", + "unrelated:{projectRoot}/**/*", + "parent:ProjectConfiguration", + "tagged:ProjectConfiguration", + "unrelated:ProjectConfiguration", + "parent:TsConfig", + "tagged:TsConfig", + "unrelated:TsConfig", + "AllExternalDependencies", + ], + } + `); }); }); @@ -236,26 +179,13 @@ describe('task planner', () => { {} ); let nxJson = {} as any; - const hasher = new InProcessTaskHasher( - projectFileMap, - allWorkspaceFiles, - projectGraph, - nxJson, - null, - {} - ); const planner = new HashPlanner( nxJson as any, transferProjectGraph(transformProjectGraphForRust(projectGraph)) ); - const hashPlan = await assertHashPlan( - taskGraph.tasks['parent:build'], - taskGraph, - hasher, - planner - ); + const plans = planner.getPlans(['parent:build'], taskGraph); - expect(hashPlan).toMatchSnapshot(); + expect(plans).toMatchSnapshot(); }); it('should plan non-default filesets', async () => { @@ -313,26 +243,13 @@ describe('task planner', () => { prod: ['!{projectRoot}/**/*.spec.ts'], }, } as any; - const hasher = new InProcessTaskHasher( - projectFileMap, - allWorkspaceFiles, - projectGraph, - nxJson, - null, - {} - ); const planner = new HashPlanner( nxJson as any, transferProjectGraph(transformProjectGraphForRust(projectGraph)) ); - let hashPlans = await assertHashPlan( - taskGraph.tasks['parent:build'], - taskGraph, - hasher, - planner - ); + const plans = planner.getPlans(['parent:build'], taskGraph); - expect(hashPlans).toMatchSnapshot(); + expect(plans).toMatchSnapshot(); }); it('should make a plan with multiple filesets of a project', async () => { @@ -376,21 +293,13 @@ describe('task planner', () => { prod: ['!{projectRoot}/**/*.spec.ts'], }, } as any; - const hasher = new InProcessTaskHasher( - projectFileMap, - allWorkspaceFiles, - projectGraph, - nxJson, - null, - {} - ); const planner = new HashPlanner( nxJson as any, transferProjectGraph(transformProjectGraphForRust(projectGraph)) ); - const tasks = Object.values(taskGraph.tasks); + const taskIds = Object.keys(taskGraph.tasks); - let plans = await assertHashPlan(tasks, taskGraph, hasher, planner); + const plans = planner.getPlans(taskIds, taskGraph); expect(plans).toMatchSnapshot(); }); @@ -458,21 +367,14 @@ describe('task planner', () => { prod: ['!{projectRoot}/**/*.spec.ts'], }, }; - const hasher = new InProcessTaskHasher( - projectFileMap, - allWorkspaceFiles, - projectGraph, - nxJson as any, - null, - {} - ); const planner = new HashPlanner( nxJson as any, transferProjectGraph(transformProjectGraphForRust(projectGraph)) ); - const tasks = Object.values(taskGraph.tasks); - let plans = await assertHashPlan(tasks, taskGraph, hasher, planner); + const taskIds = Object.keys(taskGraph.tasks); + + const plans = planner.getPlans(taskIds, taskGraph); expect(plans).toMatchSnapshot(); } ); @@ -535,21 +437,14 @@ describe('task planner', () => { prod: ['!{projectRoot}/**/*.spec.ts'], }, }; - const hasher = new InProcessTaskHasher( - projectFileMap, - allWorkspaceFiles, - projectGraph, - nxJson as any, - null, - {} - ); const planner = new HashPlanner( nxJson as any, transferProjectGraph(transformProjectGraphForRust(projectGraph)) ); - const tasks = Object.values(taskGraph.tasks); - let plans = await assertHashPlan(tasks, taskGraph, hasher, planner); + const taskIds = Object.keys(taskGraph.tasks); + + const plans = planner.getPlans(taskIds, taskGraph); expect(plans).toMatchSnapshot(); }); @@ -587,20 +482,13 @@ describe('task planner', () => { {} ); let nxJson = {} as any; - const hasher = new InProcessTaskHasher( - projectFileMap, - allWorkspaceFiles, - projectGraph, - nxJson, - null, - {} - ); const planner = new HashPlanner( nxJson as any, transferProjectGraph(transformProjectGraphForRust(projectGraph)) ); - let tasks = Object.values(taskGraph.tasks); - let plans = await assertHashPlan(tasks, taskGraph, hasher, planner); + const taskIds = Object.keys(taskGraph.tasks); + + const plans = planner.getPlans(taskIds, taskGraph); expect(plans).toMatchSnapshot(); }); @@ -636,24 +524,12 @@ describe('task planner', () => { {} ); let nxJson = {} as any; - const hasher = new InProcessTaskHasher( - projectFileMap, - allWorkspaceFiles, - projectGraph, - nxJson, - null, - {} - ); const transformed = transferProjectGraph( transformProjectGraphForRust(projectGraph) ); const planner = new HashPlanner(nxJson as any, transformed); - let plans = await assertHashPlan( - taskGraph.tasks['app:build'], - taskGraph, - hasher, - planner - ); + + const plans = planner.getPlans(['app:build'], taskGraph); expect(plans).toMatchSnapshot(); }); @@ -758,25 +634,12 @@ describe('task planner', () => { 'dist/libs/grandchild/index.d.ts': '', }); - const hasher = new InProcessTaskHasher( - projectFileMap, - allWorkspaceFiles, - projectGraph, - nxJson, - null, - {} - ); - const transformed = transferProjectGraph( transformProjectGraphForRust(projectGraph) ); const planner = new HashPlanner(nxJson, transformed); - let plans = await assertHashPlan( - taskGraph.tasks['parent:build'], - taskGraph, - hasher, - planner - ); + + const plans = planner.getPlans(['parent:build'], taskGraph); expect(plans).toMatchSnapshot(); }); });