feat(core): add matching support to inputs projects (#17255)

This commit is contained in:
Craigory Coppola 2023-05-26 18:32:53 -04:00 committed by GitHub
parent d0421e7aae
commit 471616b04d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 226 additions and 180 deletions

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-restricted-imports */ /* eslint-disable @typescript-eslint/no-restricted-imports */
export * from 'nx/internal-testing-utils/assert-valid-migrations'; 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/run-migration-against-this-workspace';
export * from 'nx/internal-testing-utils/with-environment';

View File

@ -0,0 +1,29 @@
export function withEnvironmentVariables(
env: Record<string, string>,
callback: () => void
): void;
export function withEnvironmentVariables(
env: Record<string, string>,
callback: () => Promise<void>
): Promise<void>;
export function withEnvironmentVariables(
env: Record<string, string>,
callback: () => void | Promise<void>
): void | Promise<void> {
const originalValues: Record<string, string> = {};
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();
}
}

View File

@ -28,6 +28,7 @@ import {
InProcessTaskHasher, InProcessTaskHasher,
} from './task-hasher'; } from './task-hasher';
import { fileHasher } from './impl'; import { fileHasher } from './impl';
import { withEnvironmentVariables } from '../../internal-testing-utils/with-environment';
describe('TaskHasher', () => { describe('TaskHasher', () => {
const packageJson = { const packageJson = {
@ -76,88 +77,104 @@ describe('TaskHasher', () => {
vol.reset(); vol.reset();
}); });
it('should create task hash', async () => { it('should create task hash', () =>
process.env.TESTENV = 'env123'; withEnvironmentVariables({ TESTENV: 'env123' }, async () => {
const hasher = new InProcessTaskHasher( const hasher = new InProcessTaskHasher(
{ {
parent: [{ file: '/file', hash: 'file.hash' }], parent: [{ file: '/file', hash: 'file.hash' }],
unrelated: [{ file: 'libs/unrelated/filec.ts', hash: 'filec.hash' }], unrelated: [{ file: 'libs/unrelated/filec.ts', hash: 'filec.hash' }],
}, },
allWorkspaceFiles, allWorkspaceFiles,
{ {
nodes: { nodes: {
parent: { parent: {
name: 'parent', name: 'parent',
type: 'lib', type: 'lib',
data: { data: {
root: 'libs/parent', root: 'libs/parent',
targets: { targets: {
build: { build: {
executor: 'nx:run-commands', executor: 'nx:run-commands',
inputs: [ inputs: [
'default', 'default',
'^default', '^default',
{ runtime: 'echo runtime123' }, { runtime: 'echo runtime123' },
{ env: 'TESTENV' }, { env: 'TESTENV' },
{ env: 'NONEXISTENTENV' }, { env: 'NONEXISTENTENV' },
{ input: 'default', projects: ['unrelated'] }, {
], input: 'default',
projects: ['unrelated', 'tag:some-tag'],
},
],
},
}, },
}, },
}, },
}, unrelated: {
unrelated: { name: 'unrelated',
name: 'unrelated', type: 'lib',
type: 'lib', data: {
data: { root: 'libs/unrelated',
root: 'libs/unrelated', targets: { build: {} },
targets: { build: {} }, },
},
tagged: {
name: 'tagged',
type: 'lib',
data: {
root: 'libs/tagged',
targets: { build: {} },
tags: ['some-tag'],
},
}, },
}, },
dependencies: {
parent: [],
},
externalNodes: {},
}, },
dependencies: { {} as any,
parent: [], {
runtimeCacheInputs: ['echo runtime456'],
}, },
externalNodes: {}, createFileHasher()
}, );
{} as any,
{
runtimeCacheInputs: ['echo runtime456'],
},
createFileHasher()
);
const hash = await hasher.hashTask({ const hash = await hasher.hashTask({
target: { project: 'parent', target: 'build' }, target: { project: 'parent', target: 'build' },
id: 'parent-build', id: 'parent-build',
overrides: { prop: 'prop-value' }, overrides: { prop: 'prop-value' },
}); });
expect(hash.value).toContain('file.hash'); //project files expect(hash.value).toContain('file.hash'); //project files
expect(hash.value).toContain('prop-value'); //overrides expect(hash.value).toContain('prop-value'); //overrides
expect(hash.value).toContain('parent'); //project expect(hash.value).toContain('parent'); //project
expect(hash.value).toContain('build'); //target expect(hash.value).toContain('build'); //target
expect(hash.value).toContain('runtime123'); expect(hash.value).toContain('runtime123');
expect(hash.value).toContain('runtime456'); expect(hash.value).toContain('runtime456');
expect(hash.value).toContain('env123'); expect(hash.value).toContain('env123');
expect(hash.value).toContain('filec.hash'); expect(hash.value).toContain('filec.hash');
expect(hash.details.command).toEqual('parent|build||{"prop":"prop-value"}'); expect(hash.details.command).toEqual(
expect(hash.details.nodes).toEqual({ 'parent|build||{"prop":"prop-value"}'
'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"]}}}', expect(hash.details.nodes).toEqual({
target: 'nx:run-commands', 'parent:{projectRoot}/**/*':
'unrelated:{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"]}}}',
'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"]}}}', target: 'nx:run-commands',
'{workspaceRoot}/nx.json': 'nx.json.hash', 'unrelated:{projectRoot}/**/*':
'{workspaceRoot}/.gitignore': '', '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}/.nxignore': '', 'tagged:{projectRoot}/**/*':
'runtime:echo runtime123': 'runtime123', '{"root":"libs/tagged","targets":{"build":{}},"tags":["some-tag"]}|{"compilerOptions":{"paths":{"@nx/parent":["libs/parent/src/index.ts"],"@nx/child":["libs/child/src/index.ts"]}}}',
'runtime:echo runtime456': 'runtime456', '{workspaceRoot}/nx.json': 'nx.json.hash',
'env:TESTENV': 'env123', '{workspaceRoot}/.gitignore': '',
'env:NONEXISTENTENV': '', '{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 () => { it('should hash task where the project has dependencies', async () => {
const hasher = new InProcessTaskHasher( const hasher = new InProcessTaskHasher(
@ -359,111 +376,117 @@ describe('TaskHasher', () => {
}); });
it('should be able to handle multiple filesets per project', async () => { it('should be able to handle multiple filesets per project', async () => {
process.env.MY_TEST_HASH_ENV = 'MY_TEST_HASH_ENV_VALUE'; withEnvironmentVariables(
const hasher = new InProcessTaskHasher( { MY_TEST_HASH_ENV: 'MY_TEST_HASH_ENV_VALUE' },
{ async () => {
parent: [ const hasher = new InProcessTaskHasher(
{ file: 'libs/parent/filea.ts', hash: 'a.hash' }, {
{ file: 'libs/parent/filea.spec.ts', hash: 'a.spec.hash' }, parent: [
], { file: 'libs/parent/filea.ts', hash: 'a.hash' },
child: [ { file: 'libs/parent/filea.spec.ts', hash: 'a.spec.hash' },
{ file: 'libs/child/fileb.ts', hash: 'b.hash' }, ],
{ file: 'libs/child/fileb.spec.ts', hash: 'b.spec.hash' }, child: [
], { file: 'libs/child/fileb.ts', hash: 'b.hash' },
}, { file: 'libs/child/fileb.spec.ts', hash: 'b.spec.hash' },
allWorkspaceFiles, ],
{ },
nodes: { allWorkspaceFiles,
parent: { {
name: 'parent', nodes: {
type: 'lib', parent: {
data: { name: 'parent',
root: 'libs/parent', type: 'lib',
targets: { data: {
test: { root: 'libs/parent',
inputs: ['default', '^prod'], targets: {
executor: 'nx:run-commands', 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',
},
},
}, },
}, },
}, },
}, dependencies: {
child: { parent: [{ source: 'parent', target: 'child', type: 'static' }],
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: { namedInputs: {
parent: [{ source: 'parent', target: 'child', type: 'static' }], default: ['{projectRoot}/**/*', '{workspaceRoot}/global1'],
}, prod: ['!{projectRoot}/**/*.spec.ts'],
}, },
{ } as any,
namedInputs: { {},
default: ['{projectRoot}/**/*', '{workspaceRoot}/global1'], createFileHasher()
prod: ['!{projectRoot}/**/*.spec.ts'], );
},
} as any,
{},
createFileHasher()
);
const parentHash = await hasher.hashTask({ const parentHash = await hasher.hashTask({
target: { project: 'parent', target: 'test' }, target: { project: 'parent', target: 'test' },
id: 'parent-test', id: 'parent-test',
overrides: { prop: 'prop-value' }, overrides: { prop: 'prop-value' },
}); });
assertFilesets(parentHash, { assertFilesets(parentHash, {
'child:!{projectRoot}/**/*.spec.ts': { 'child:!{projectRoot}/**/*.spec.ts': {
contains: 'libs/child/fileb.ts', contains: 'libs/child/fileb.ts',
excludes: 'fileb.spec.ts', excludes: 'fileb.spec.ts',
}, },
'parent:{projectRoot}/**/*': { 'parent:{projectRoot}/**/*': {
contains: 'libs/parent/filea.ts|libs/parent/filea.spec.ts', contains: 'libs/parent/filea.ts|libs/parent/filea.spec.ts',
}, },
}); });
expect(parentHash.details.nodes['{workspaceRoot}/global1']).toEqual( expect(parentHash.details.nodes['{workspaceRoot}/global1']).toEqual(
'global1.hash' 'global1.hash'
); );
expect(parentHash.details.nodes['{workspaceRoot}/global2']).toBe( expect(parentHash.details.nodes['{workspaceRoot}/global2']).toBe(
'global2.hash' 'global2.hash'
); );
expect(parentHash.details.nodes['env:MY_TEST_HASH_ENV']).toEqual( expect(parentHash.details.nodes['env:MY_TEST_HASH_ENV']).toEqual(
'MY_TEST_HASH_ENV_VALUE' 'MY_TEST_HASH_ENV_VALUE'
); );
const childHash = await hasher.hashTask({ const childHash = await hasher.hashTask({
target: { project: 'child', target: 'test' }, target: { project: 'child', target: 'test' },
id: 'child-test', id: 'child-test',
overrides: { prop: 'prop-value' }, overrides: { prop: 'prop-value' },
}); });
assertFilesets(childHash, { assertFilesets(childHash, {
'child:{projectRoot}/**/*': { 'child:{projectRoot}/**/*': {
contains: 'libs/child/fileb.ts|libs/child/fileb.spec.ts', contains: 'libs/child/fileb.ts|libs/child/fileb.spec.ts',
}, },
}); });
expect(childHash.details.nodes['{workspaceRoot}/global1']).toEqual( expect(childHash.details.nodes['{workspaceRoot}/global1']).toEqual(
'global1.hash' '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 () => { it('should use targetDefaults from nx.json', async () => {

View File

@ -15,7 +15,7 @@ import { DaemonClient } from '../daemon/client/client';
import { FileHasher } from './impl/file-hasher-base'; import { FileHasher } from './impl/file-hasher-base';
import { hashArray } from './impl'; import { hashArray } from './impl';
import { createProjectRootMappings } from '../project-graph/utils/find-project-for-path'; 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 = type ExpandedSelfInput =
| { fileset: string } | { fileset: string }
@ -495,7 +495,11 @@ class TaskHasherImpl {
): Promise<PartialHash[]> { ): Promise<PartialHash[]> {
const partialHashes: Promise<PartialHash[]>[] = []; const partialHashes: Promise<PartialHash[]>[] = [];
for (const input of projectInputs) { 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( const namedInputs = getNamedInputs(
this.nxJson, this.nxJson,
this.projectGraph.nodes[project] this.projectGraph.nodes[project]

View File

@ -1,4 +1,5 @@
import { splitArgsIntoNxArgsAndOverrides } from './command-line-utils'; import { splitArgsIntoNxArgsAndOverrides } from './command-line-utils';
import { withEnvironmentVariables as withEnvironment } from '../../internal-testing-utils/with-environment';
jest.mock('../project-graph/file-utils'); jest.mock('../project-graph/file-utils');
@ -458,15 +459,3 @@ describe('splitArgs', () => {
}); });
}); });
}); });
function withEnvironment(env: Record<string, string>, callback: () => void) {
const originalValues: Record<string, string> = {};
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];
}
}