Skip to content

Commit

Permalink
fix(core): ensure external dependency hashes are resolved in a determ…
Browse files Browse the repository at this point in the history
…inistic way (#17926)
  • Loading branch information
skrtheboss authored Jul 5, 2023
1 parent eaebcc3 commit 65adb94
Show file tree
Hide file tree
Showing 2 changed files with 175 additions and 14 deletions.
138 changes: 138 additions & 0 deletions packages/nx/src/hasher/task-hasher.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1161,6 +1161,144 @@ describe('TaskHasher', () => {
expect(hash.value).toContain('|5.0.0|');
});

it('should hash entire subtree of dependencies', 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$',
},
},
},
dependencies: {
appA: [
{
source: 'app',
target: 'npm:packageA',
type: DependencyType.static,
},
{
source: 'app',
target: 'npm:packageB',
type: DependencyType.static,
},
{
source: 'app',
target: 'npm:packageC',
type: DependencyType.static,
},
],
appB: [
{
source: 'app',
target: 'npm:packageC',
type: DependencyType.static,
},
],
'npm:packageC': [
{
source: 'app',
target: 'npm:packageA',
type: DependencyType.static,
},
{
source: 'app',
target: 'npm:packageB',
type: DependencyType.static,
},
],
'npm:packageB': [
{
source: 'app',
target: 'npm:packageA',
type: DependencyType.static,
},
],
},
},
{
roots: ['app-build'],
tasks: {
'app-build': {
id: 'app-build',
target: { project: 'app', target: 'build' },
overrides: {},
},
},
dependencies: {},
},
{} as any,
{},
fileHasher
);

const computeTaskHash = async (hasher, appName) => {
const hashAppA = await hasher.hashTask({
target: { project: appName, target: 'build' },
id: `${appName}-build`,
overrides: { prop: 'prop-value' },
});

return hashAppA.value;
};

const hasher1 = createHasher();

await computeTaskHash(hasher1, 'appA');
const hashAppB1 = await computeTaskHash(hasher1, 'appB');

const hasher2 = createHasher();

const hashAppB2 = await computeTaskHash(hasher2, 'appB');
await computeTaskHash(hasher2, 'appA');

expect(hashAppB1).toEqual(hashAppB2);
});

it('should not hash when nx:run-commands executor', async () => {
const hasher = new InProcessTaskHasher(
{},
Expand Down
51 changes: 37 additions & 14 deletions packages/nx/src/hasher/task-hasher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ class TaskHasherImpl {
visited
);
} else {
const hash = this.hashExternalDependency(d.target);
const hash = this.hashExternalDependency(d.source, d.target);
return {
value: hash,
details: {
Expand Down Expand Up @@ -408,16 +408,29 @@ class TaskHasherImpl {
return partialHashes;
}

private computeExternalDependencyIdentifier(
sourceProjectName: string,
targetProjectName: string
): `${string}->${string}` {
return `${sourceProjectName}->${targetProjectName}`;
}

private hashExternalDependency(
projectName: string,
sourceProjectName: string,
targetProjectName: string,
visited = new Set<string>()
): string {
// try to retrieve the hash from cache
if (this.externalDepsHashCache[projectName]) {
return this.externalDepsHashCache[projectName];
if (this.externalDepsHashCache[targetProjectName]) {
return this.externalDepsHashCache[targetProjectName];
}
visited.add(projectName);
const node = this.projectGraph.externalNodes[projectName];
visited.add(
this.computeExternalDependencyIdentifier(
sourceProjectName,
targetProjectName
)
);
const node = this.projectGraph.externalNodes[targetProjectName];
let partialHash: string;
if (node) {
const partialHashes: string[] = [];
Expand All @@ -429,22 +442,32 @@ class TaskHasherImpl {
partialHashes.push(node.data.version);
}
// we want to calculate the hash of the entire dependency tree
if (this.projectGraph.dependencies[projectName]) {
this.projectGraph.dependencies[projectName].forEach((d) => {
if (!visited.has(d.target)) {
partialHashes.push(this.hashExternalDependency(d.target, visited));
if (this.projectGraph.dependencies[targetProjectName]) {
this.projectGraph.dependencies[targetProjectName].forEach((d) => {
if (
!visited.has(
this.computeExternalDependencyIdentifier(
targetProjectName,
d.target
)
)
) {
partialHashes.push(
this.hashExternalDependency(targetProjectName, d.target, visited)
);
}
});
}

partialHash = hashArray(partialHashes);
} else {
// unknown dependency
// this may occur if dependency is not an npm package
// but rather symlinked in node_modules or it's pointing to a remote git repo
// in this case we have no information about the versioning of the given package
partialHash = `__${projectName}__`;
partialHash = `__${targetProjectName}__`;
}
this.externalDepsHashCache[projectName] = partialHash;
this.externalDepsHashCache[targetProjectName] = partialHash;
return partialHash;
}

Expand All @@ -470,7 +493,7 @@ class TaskHasherImpl {
const executorPackage = target.executor.split(':')[0];
const executorNodeName =
this.findExternalDependencyNodeName(executorPackage);
hash = this.hashExternalDependency(executorNodeName);
hash = this.hashExternalDependency(projectName, executorNodeName);
} else {
// use command external dependencies if available to construct the hash
const partialHashes: string[] = [];
Expand All @@ -482,7 +505,7 @@ class TaskHasherImpl {
const externalDependencies = input['externalDependencies'];
for (let dep of externalDependencies) {
dep = this.findExternalDependencyNodeName(dep);
partialHashes.push(this.hashExternalDependency(dep));
partialHashes.push(this.hashExternalDependency(projectName, dep));
}
}
}
Expand Down

1 comment on commit 65adb94

@vercel
Copy link

@vercel vercel bot commented on 65adb94 Jul 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

nx-dev – ./

nx.dev
nx-dev-git-master-nrwl.vercel.app
nx-dev-nrwl.vercel.app
nx-five.vercel.app

Please sign in to comment.