Skip to content

Commit

Permalink
feat(core): provide an experimental hashing mode for jest and cyrpess
Browse files Browse the repository at this point in the history
  • Loading branch information
vsavkin committed Mar 11, 2022
1 parent 90abd6f commit a32d46c
Show file tree
Hide file tree
Showing 11 changed files with 285 additions and 68 deletions.
1 change: 1 addition & 0 deletions packages/cypress/executors.json
Expand Up @@ -10,6 +10,7 @@
"cypress": {
"implementation": "./src/executors/cypress/cypress.impl",
"schema": "./src/executors/cypress/schema.json",
"hasher": "./src/executors/cypress/hasher",
"description": "Run Cypress e2e tests"
}
}
Expand Down
27 changes: 27 additions & 0 deletions packages/cypress/src/executors/cypress/hasher.ts
@@ -0,0 +1,27 @@
import {
NxJsonConfiguration,
ProjectGraph,
Task,
TaskGraph,
WorkspaceJsonConfiguration,
} from '@nrwl/devkit';
import { Hash, Hasher } from '@nrwl/workspace/src/core/hasher/hasher';

export default async function run(
task: Task,
context: {
hasher: Hasher;
projectGraph: ProjectGraph;
taskGraph: TaskGraph;
workspaceConfig: WorkspaceJsonConfiguration & NxJsonConfiguration;
}
): Promise<Hash> {
const cypressPluginConfig = context.workspaceConfig.pluginsConfig
? (context.workspaceConfig.pluginsConfig['@nrwl/cypress'] as any)
: undefined;
const filter =
cypressPluginConfig && cypressPluginConfig.hashingExcludesTestsOfDeps
? 'exclude-tests-of-deps'
: 'all-files';
return context.hasher.hashTaskWithDepsAndContext(task, filter);
}
1 change: 1 addition & 0 deletions packages/jest/executors.json
Expand Up @@ -11,6 +11,7 @@
"implementation": "./src/executors/jest/jest.impl",
"batchImplementation": "./src/executors/jest/jest.impl#batchJest",
"schema": "./src/executors/jest/schema.json",
"hasher": "./src/executors/jest/hasher",
"description": "Run Jest unit tests"
}
}
Expand Down
27 changes: 27 additions & 0 deletions packages/jest/src/executors/jest/hasher.ts
@@ -0,0 +1,27 @@
import {
NxJsonConfiguration,
ProjectGraph,
Task,
TaskGraph,
WorkspaceJsonConfiguration,
} from '@nrwl/devkit';
import { Hash, Hasher } from '@nrwl/workspace/src/core/hasher/hasher';

export default async function run(
task: Task,
context: {
hasher: Hasher;
projectGraph: ProjectGraph;
taskGraph: TaskGraph;
workspaceConfig: WorkspaceJsonConfiguration & NxJsonConfiguration;
}
): Promise<Hash> {
const jestPluginConfig = context.workspaceConfig.pluginsConfig
? (context.workspaceConfig.pluginsConfig['@nrwl/jest'] as any)
: undefined;
const filter =
jestPluginConfig && jestPluginConfig.hashingExcludesTestsOfDeps
? 'exclude-tests-of-deps'
: 'all-files';
return context.hasher.hashTaskWithDepsAndContext(task, filter);
}
58 changes: 27 additions & 31 deletions packages/linter/src/executors/eslint/hasher.ts
@@ -1,48 +1,44 @@
import { ProjectGraph, Task, TaskGraph } from '@nrwl/devkit';
import {
ProjectGraph,
Task,
TaskGraph,
WorkspaceJsonConfiguration,
} from '@nrwl/devkit';
import { Hash, Hasher } from '@nrwl/workspace/src/core/hasher/hasher';
import { appRootPath } from '@nrwl/tao/src/utils/app-root';
import { Workspaces } from '@nrwl/tao/src/shared/workspace';
import { readCachedProjectGraph } from '@nrwl/workspace/src/core/project-graph';

export default async function run(
task: Task,
taskGraph: TaskGraph,
hasher: Hasher
context: {
hasher: Hasher;
projectGraph: ProjectGraph;
taskGraph: TaskGraph;
workspaceConfig: WorkspaceJsonConfiguration;
}
): Promise<Hash> {
if (task.overrides['hasTypeAwareRules'] === true) {
return hasher.hashTaskWithDepsAndContext(task);
return context.hasher.hashTaskWithDepsAndContext(task);
}
if (!(global as any).projectGraph) {
try {
(global as any).projectGraph = readCachedProjectGraph();
} catch {
// do nothing, if project graph is unavailable we fallback to using all projects
}
}
const projectGraph = (global as any).projectGraph;
const command = hasher.hashCommand(task);
const sources = await hasher.hashSource(task);
const workspace = new Workspaces(appRootPath).readWorkspaceConfiguration();
const deps = projectGraph
? allDeps(task.id, taskGraph, projectGraph)
: Object.keys(workspace.projects);
const tags = hasher.hashArray(
deps.map((d) => (workspace.projects[d].tags || []).join('|'))

const command = context.hasher.hashCommand(task);
const source = await context.hasher.hashSource(task);
const deps = allDeps(task.id, context.taskGraph, context.projectGraph);
const tags = context.hasher.hashArray(
deps.map((d) => (context.workspaceConfig.projects[d].tags || []).join('|'))
);
const context = await hasher.hashContext();
const taskContext = await context.hasher.hashContext();
return {
value: hasher.hashArray([
value: context.hasher.hashArray([
command,
sources,
source,
tags,
context.implicitDeps.value,
context.runtime.value,
taskContext.implicitDeps.value,
taskContext.runtime.value,
]),
details: {
command,
nodes: { [task.target.project]: sources, tags },
implicitDeps: context.implicitDeps.files,
runtime: context.runtime.runtime,
nodes: { [task.target.project]: source, tags },
implicitDeps: taskContext.implicitDeps.files,
runtime: taskContext.runtime.runtime,
},
};
}
Expand Down
7 changes: 6 additions & 1 deletion packages/tao/src/shared/workspace.ts
Expand Up @@ -263,6 +263,9 @@ export interface ExecutorContext {
}

export class Workspaces {
private cachedWorkspaceConfig: WorkspaceJsonConfiguration &
NxJsonConfiguration;

constructor(private root: string) {}

relativeCwd(cwd: string) {
Expand All @@ -289,6 +292,7 @@ export class Workspaces {

readWorkspaceConfiguration(): WorkspaceJsonConfiguration &
NxJsonConfiguration {
if (this.cachedWorkspaceConfig) return this.cachedWorkspaceConfig;
const nxJsonPath = path.join(this.root, 'nx.json');
const nxJson = readNxJson(nxJsonPath);
const workspaceFile = workspaceConfigName(this.root);
Expand All @@ -305,7 +309,8 @@ export class Workspaces {
);

assertValidWorkspaceConfiguration(nxJson);
return { ...workspace, ...nxJson };
this.cachedWorkspaceConfig = { ...workspace, ...nxJson };
return this.cachedWorkspaceConfig;
}

isNxExecutor(nodeModule: string, executor: string) {
Expand Down
130 changes: 116 additions & 14 deletions packages/workspace/src/core/hasher/hasher.spec.ts
Expand Up @@ -72,7 +72,6 @@ describe('Hasher', () => {
});

it('should create project hash', async () => {
hashes['/file'] = 'file.hash';
const hasher = new Hasher(
{
nodes: {
Expand Down Expand Up @@ -128,7 +127,6 @@ describe('Hasher', () => {
});

it('should create project hash with tsconfig.base.json cache', async () => {
hashes['/file'] = 'file.hash';
const hasher = new Hasher(
{
nodes: {
Expand Down Expand Up @@ -227,8 +225,6 @@ describe('Hasher', () => {
});

it('should hash projects with dependencies', async () => {
hashes['/filea'] = 'a.hash';
hashes['/fileb'] = 'b.hash';
const hasher = new Hasher(
{
nodes: {
Expand All @@ -237,15 +233,21 @@ describe('Hasher', () => {
type: 'lib',
data: {
root: '',
files: [{ file: '/filea.ts', hash: 'a.hash' }],
files: [
{ file: '/filea.ts', hash: 'a.hash' },
{ file: '/filea.spec.ts', hash: 'a.spec.hash' },
],
},
},
child: {
name: 'child',
type: 'lib',
data: {
root: '',
files: [{ file: '/fileb.ts', hash: 'b.hash' }],
files: [
{ file: '/fileb.ts', hash: 'b.hash' },
{ file: '/fileb.spec.ts', hash: 'b.spec.hash' },
],
},
},
},
Expand All @@ -264,6 +266,114 @@ describe('Hasher', () => {
overrides: { prop: 'prop-value' },
});

// note that the parent hash is based on parent source files only!
expect(hash.details.nodes).toEqual({
child:
'/fileb.ts|/fileb.spec.ts|b.hash|b.spec.hash|{"root":"libs/child"}|{"compilerOptions":{"paths":{"@nrwl/parent":["libs/parent/src/index.ts"],"@nrwl/child":["libs/child/src/index.ts"]}}}',
parent:
'/filea.ts|/filea.spec.ts|a.hash|a.spec.hash|{"root":"libs/parent"}|{"compilerOptions":{"paths":{"@nrwl/parent":["libs/parent/src/index.ts"],"@nrwl/child":["libs/child/src/index.ts"]}}}',
});
});

it('should hash projects with dependencies (exclude spec files of dependencies)', async () => {
const hasher = new Hasher(
{
nodes: {
parent: {
name: 'parent',
type: 'lib',
data: {
root: '',
files: [
{ file: '/filea.ts', hash: 'a.hash' },
{ file: '/filea.spec.ts', hash: 'a.spec.hash' },
],
},
},
child: {
name: 'child',
type: 'lib',
data: {
root: '',
files: [
{ file: '/fileb.ts', hash: 'b.hash' },
{ file: '/fileb.spec.ts', hash: 'b.spec.hash' },
],
},
},
},
dependencies: {
parent: [{ source: 'parent', target: 'child', type: 'static' }],
},
},
{} as any,
{},
createHashing()
);

const hash = await hasher.hashTaskWithDepsAndContext(
{
target: { project: 'parent', target: 'build' },
id: 'parent-build',
overrides: { prop: 'prop-value' },
},
'exclude-tests-of-deps'
);

// note that the parent hash is based on parent source files only!
expect(hash.details.nodes).toEqual({
child:
'/fileb.ts|b.hash|{"root":"libs/child"}|{"compilerOptions":{"paths":{"@nrwl/parent":["libs/parent/src/index.ts"],"@nrwl/child":["libs/child/src/index.ts"]}}}',
parent:
'/filea.ts|/filea.spec.ts|a.hash|a.spec.hash|{"root":"libs/parent"}|{"compilerOptions":{"paths":{"@nrwl/parent":["libs/parent/src/index.ts"],"@nrwl/child":["libs/child/src/index.ts"]}}}',
});
});

it('should hash projects with dependencies (exclude spec files of all projects)', async () => {
const hasher = new Hasher(
{
nodes: {
parent: {
name: 'parent',
type: 'lib',
data: {
root: '',
files: [
{ file: '/filea.ts', hash: 'a.hash' },
{ file: '/filea.spec.ts', hash: 'a.spec.hash' },
],
},
},
child: {
name: 'child',
type: 'lib',
data: {
root: '',
files: [
{ file: '/fileb.ts', hash: 'b.hash' },
{ file: '/fileb.spec.ts', hash: 'b.spec.hash' },
],
},
},
},
dependencies: {
parent: [{ source: 'parent', target: 'child', type: 'static' }],
},
},
{} as any,
{},
createHashing()
);

const hash = await hasher.hashTaskWithDepsAndContext(
{
target: { project: 'parent', target: 'build' },
id: 'parent-build',
overrides: { prop: 'prop-value' },
},
'exclude-tests-of-all'
);

// note that the parent hash is based on parent source files only!
expect(hash.details.nodes).toEqual({
child:
Expand All @@ -274,8 +384,6 @@ describe('Hasher', () => {
});

it('should hash dependent npm project versions', async () => {
hashes['/filea'] = 'a.hash';
hashes['/fileb'] = 'b.hash';
const hasher = new Hasher(
{
nodes: {
Expand Down Expand Up @@ -324,8 +432,6 @@ describe('Hasher', () => {
});

it('should hash when circular dependencies', async () => {
hashes['/filea'] = 'a.hash';
hashes['/fileb'] = 'b.hash';
const hasher = new Hasher(
{
nodes: {
Expand Down Expand Up @@ -396,8 +502,6 @@ describe('Hasher', () => {
});

it('should hash implicit deps', async () => {
hashes['/filea'] = 'a.hash';
hashes['/fileb'] = 'b.hash';
const hasher = new Hasher(
{
nodes: {
Expand Down Expand Up @@ -442,8 +546,6 @@ describe('Hasher', () => {
});

it('should hash missing dependent npm project versions', async () => {
hashes['/filea'] = 'a.hash';
hashes['/fileb'] = 'b.hash';
const hasher = new Hasher(
{
nodes: {
Expand Down

0 comments on commit a32d46c

Please sign in to comment.