diff --git a/packages/devkit/internal-testing-utils.ts b/packages/devkit/internal-testing-utils.ts index c6e81cc8b6..2a887f34e7 100644 --- a/packages/devkit/internal-testing-utils.ts +++ b/packages/devkit/internal-testing-utils.ts @@ -1,3 +1,4 @@ /* eslint-disable @typescript-eslint/no-restricted-imports */ export * from 'nx/internal-testing-utils/assert-valid-migrations'; export * from 'nx/internal-testing-utils/run-migration-against-this-workspace'; +export * from 'nx/internal-testing-utils/with-environment'; diff --git a/packages/nx/internal-testing-utils/with-environment.ts b/packages/nx/internal-testing-utils/with-environment.ts new file mode 100644 index 0000000000..95c4f1da45 --- /dev/null +++ b/packages/nx/internal-testing-utils/with-environment.ts @@ -0,0 +1,29 @@ +export function withEnvironmentVariables( + env: Record, + callback: () => void +): void; +export function withEnvironmentVariables( + env: Record, + callback: () => Promise +): Promise; +export function withEnvironmentVariables( + env: Record, + callback: () => void | Promise +): void | Promise { + const originalValues: Record = {}; + for (const key in env) { + originalValues[key] = process.env[key]; + process.env[key] = env[key]; + } + const cleanup = () => { + for (const key in env) { + process.env[key] = originalValues[key]; + } + }; + const p = callback(); + if (p instanceof Promise) { + return p.finally(cleanup); + } else { + cleanup(); + } +} diff --git a/packages/nx/src/hasher/task-hasher.spec.ts b/packages/nx/src/hasher/task-hasher.spec.ts index 8ef9929867..4fd53215c1 100644 --- a/packages/nx/src/hasher/task-hasher.spec.ts +++ b/packages/nx/src/hasher/task-hasher.spec.ts @@ -28,6 +28,7 @@ import { InProcessTaskHasher, } from './task-hasher'; import { fileHasher } from './impl'; +import { withEnvironmentVariables } from '../../internal-testing-utils/with-environment'; describe('TaskHasher', () => { const packageJson = { @@ -76,88 +77,104 @@ describe('TaskHasher', () => { vol.reset(); }); - it('should create task hash', async () => { - process.env.TESTENV = 'env123'; - 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'] }, - ], + it('should create task hash', () => + withEnvironmentVariables({ TESTENV: 'env123' }, 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: {} }, + 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: {}, }, - dependencies: { - parent: [], + {} as any, + { + runtimeCacheInputs: ['echo runtime456'], }, - externalNodes: {}, - }, - {} as any, - { - runtimeCacheInputs: ['echo runtime456'], - }, - createFileHasher() - ); + createFileHasher() + ); - const hash = await hasher.hashTask({ - target: { project: 'parent', target: 'build' }, - id: 'parent-build', - overrides: { prop: 'prop-value' }, - }); + const hash = await hasher.hashTask({ + target: { project: 'parent', target: 'build' }, + id: 'parent-build', + overrides: { prop: 'prop-value' }, + }); - expect(hash.value).toContain('file.hash'); //project files - expect(hash.value).toContain('prop-value'); //overrides - expect(hash.value).toContain('parent'); //project - expect(hash.value).toContain('build'); //target - expect(hash.value).toContain('runtime123'); - expect(hash.value).toContain('runtime456'); - expect(hash.value).toContain('env123'); - expect(hash.value).toContain('filec.hash'); + expect(hash.value).toContain('file.hash'); //project files + expect(hash.value).toContain('prop-value'); //overrides + expect(hash.value).toContain('parent'); //project + expect(hash.value).toContain('build'); //target + expect(hash.value).toContain('runtime123'); + expect(hash.value).toContain('runtime456'); + expect(hash.value).toContain('env123'); + expect(hash.value).toContain('filec.hash'); - expect(hash.details.command).toEqual('parent|build||{"prop":"prop-value"}'); - expect(hash.details.nodes).toEqual({ - 'parent:{projectRoot}/**/*': - '/file|file.hash|{"root":"libs/parent","targets":{"build":{"executor":"nx:run-commands","inputs":["default","^default",{"runtime":"echo runtime123"},{"env":"TESTENV"},{"env":"NONEXISTENTENV"},{"input":"default","projects":["unrelated"]}]}}}|{"compilerOptions":{"paths":{"@nx/parent":["libs/parent/src/index.ts"],"@nx/child":["libs/child/src/index.ts"]}}}', - target: 'nx:run-commands', - 'unrelated:{projectRoot}/**/*': - 'libs/unrelated/filec.ts|filec.hash|{"root":"libs/unrelated","targets":{"build":{}}}|{"compilerOptions":{"paths":{"@nx/parent":["libs/parent/src/index.ts"],"@nx/child":["libs/child/src/index.ts"]}}}', - '{workspaceRoot}/nx.json': 'nx.json.hash', - '{workspaceRoot}/.gitignore': '', - '{workspaceRoot}/.nxignore': '', - 'runtime:echo runtime123': 'runtime123', - 'runtime:echo runtime456': 'runtime456', - 'env:TESTENV': 'env123', - 'env:NONEXISTENTENV': '', - }); - }); + expect(hash.details.command).toEqual( + 'parent|build||{"prop":"prop-value"}' + ); + expect(hash.details.nodes).toEqual({ + 'parent:{projectRoot}/**/*': + '/file|file.hash|{"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"]}]}}}|{"compilerOptions":{"paths":{"@nx/parent":["libs/parent/src/index.ts"],"@nx/child":["libs/child/src/index.ts"]}}}', + target: 'nx:run-commands', + 'unrelated:{projectRoot}/**/*': + 'libs/unrelated/filec.ts|filec.hash|{"root":"libs/unrelated","targets":{"build":{}}}|{"compilerOptions":{"paths":{"@nx/parent":["libs/parent/src/index.ts"],"@nx/child":["libs/child/src/index.ts"]}}}', + 'tagged:{projectRoot}/**/*': + '{"root":"libs/tagged","targets":{"build":{}},"tags":["some-tag"]}|{"compilerOptions":{"paths":{"@nx/parent":["libs/parent/src/index.ts"],"@nx/child":["libs/child/src/index.ts"]}}}', + '{workspaceRoot}/nx.json': 'nx.json.hash', + '{workspaceRoot}/.gitignore': '', + '{workspaceRoot}/.nxignore': '', + 'runtime:echo runtime123': 'runtime123', + 'runtime:echo runtime456': 'runtime456', + 'env:TESTENV': 'env123', + 'env:NONEXISTENTENV': '', + }); + })); it('should hash task where the project has dependencies', async () => { const hasher = new InProcessTaskHasher( @@ -359,111 +376,117 @@ describe('TaskHasher', () => { }); it('should be able to handle multiple filesets per project', async () => { - process.env.MY_TEST_HASH_ENV = 'MY_TEST_HASH_ENV_VALUE'; - 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', + withEnvironmentVariables( + { MY_TEST_HASH_ENV: 'MY_TEST_HASH_ENV_VALUE' }, + 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', + }, + }, }, }, }, - }, - 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', - }, - }, + dependencies: { + parent: [{ source: 'parent', target: 'child', type: 'static' }], }, }, - }, - dependencies: { - parent: [{ source: 'parent', target: 'child', type: 'static' }], - }, - }, - { - namedInputs: { - default: ['{projectRoot}/**/*', '{workspaceRoot}/global1'], - prod: ['!{projectRoot}/**/*.spec.ts'], - }, - } as any, - {}, - createFileHasher() - ); + { + namedInputs: { + default: ['{projectRoot}/**/*', '{workspaceRoot}/global1'], + prod: ['!{projectRoot}/**/*.spec.ts'], + }, + } as any, + {}, + createFileHasher() + ); - const parentHash = await hasher.hashTask({ - target: { project: 'parent', target: 'test' }, - id: 'parent-test', - overrides: { prop: 'prop-value' }, - }); + const parentHash = await hasher.hashTask({ + target: { project: 'parent', target: 'test' }, + id: 'parent-test', + overrides: { prop: 'prop-value' }, + }); - assertFilesets(parentHash, { - 'child:!{projectRoot}/**/*.spec.ts': { - contains: 'libs/child/fileb.ts', - excludes: 'fileb.spec.ts', - }, - 'parent:{projectRoot}/**/*': { - contains: 'libs/parent/filea.ts|libs/parent/filea.spec.ts', - }, - }); + assertFilesets(parentHash, { + 'child:!{projectRoot}/**/*.spec.ts': { + contains: 'libs/child/fileb.ts', + excludes: 'fileb.spec.ts', + }, + 'parent:{projectRoot}/**/*': { + contains: 'libs/parent/filea.ts|libs/parent/filea.spec.ts', + }, + }); - expect(parentHash.details.nodes['{workspaceRoot}/global1']).toEqual( - 'global1.hash' - ); - expect(parentHash.details.nodes['{workspaceRoot}/global2']).toBe( - 'global2.hash' - ); - expect(parentHash.details.nodes['env:MY_TEST_HASH_ENV']).toEqual( - 'MY_TEST_HASH_ENV_VALUE' - ); + expect(parentHash.details.nodes['{workspaceRoot}/global1']).toEqual( + 'global1.hash' + ); + expect(parentHash.details.nodes['{workspaceRoot}/global2']).toBe( + 'global2.hash' + ); + expect(parentHash.details.nodes['env:MY_TEST_HASH_ENV']).toEqual( + 'MY_TEST_HASH_ENV_VALUE' + ); - const childHash = await hasher.hashTask({ - target: { project: 'child', target: 'test' }, - id: 'child-test', - overrides: { prop: 'prop-value' }, - }); + const childHash = await hasher.hashTask({ + target: { project: 'child', target: 'test' }, + id: 'child-test', + overrides: { prop: 'prop-value' }, + }); - assertFilesets(childHash, { - 'child:{projectRoot}/**/*': { - contains: 'libs/child/fileb.ts|libs/child/fileb.spec.ts', - }, - }); - expect(childHash.details.nodes['{workspaceRoot}/global1']).toEqual( - 'global1.hash' + assertFilesets(childHash, { + 'child:{projectRoot}/**/*': { + contains: 'libs/child/fileb.ts|libs/child/fileb.spec.ts', + }, + }); + expect(childHash.details.nodes['{workspaceRoot}/global1']).toEqual( + 'global1.hash' + ); + expect(childHash.details.nodes['{workspaceRoot}/global2']).toBe( + undefined + ); + expect(childHash.details.nodes['env:MY_TEST_HASH_ENV']).toBeUndefined(); + } ); - expect(childHash.details.nodes['{workspaceRoot}/global2']).toBe(undefined); - expect(childHash.details.nodes['env:MY_TEST_HASH_ENV']).toBeUndefined(); }); it('should use targetDefaults from nx.json', async () => { diff --git a/packages/nx/src/hasher/task-hasher.ts b/packages/nx/src/hasher/task-hasher.ts index 63e7f3b8c7..a170bce6e4 100644 --- a/packages/nx/src/hasher/task-hasher.ts +++ b/packages/nx/src/hasher/task-hasher.ts @@ -15,7 +15,7 @@ import { DaemonClient } from '../daemon/client/client'; import { FileHasher } from './impl/file-hasher-base'; import { hashArray } from './impl'; import { createProjectRootMappings } from '../project-graph/utils/find-project-for-path'; -import { registerPluginTSTranspiler } from '../utils/nx-plugin'; +import { findMatchingProjects } from '../utils/find-matching-projects'; type ExpandedSelfInput = | { fileset: string } @@ -495,7 +495,11 @@ class TaskHasherImpl { ): Promise { const partialHashes: Promise[] = []; for (const input of projectInputs) { - for (const project of input.projects) { + const projects = findMatchingProjects( + input.projects, + this.projectGraph.nodes + ); + for (const project of projects) { const namedInputs = getNamedInputs( this.nxJson, this.projectGraph.nodes[project] diff --git a/packages/nx/src/utils/command-line-utils.spec.ts b/packages/nx/src/utils/command-line-utils.spec.ts index 9534480d26..71d3328597 100644 --- a/packages/nx/src/utils/command-line-utils.spec.ts +++ b/packages/nx/src/utils/command-line-utils.spec.ts @@ -1,4 +1,5 @@ import { splitArgsIntoNxArgsAndOverrides } from './command-line-utils'; +import { withEnvironmentVariables as withEnvironment } from '../../internal-testing-utils/with-environment'; jest.mock('../project-graph/file-utils'); @@ -458,15 +459,3 @@ describe('splitArgs', () => { }); }); }); - -function withEnvironment(env: Record, callback: () => void) { - const originalValues: Record = {}; - for (const key in env) { - originalValues[key] = process.env[key]; - process.env[key] = env[key]; - } - callback(); - for (const key in env) { - process.env[key] = originalValues[key]; - } -}