fix(core): fix performance of hashing external dependencies up front (#18359)

This commit is contained in:
Jason Jean 2023-07-28 12:23:14 -04:00 committed by GitHub
parent 4304a451cc
commit 3eba026e39
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 38 additions and 27 deletions

View File

@ -63,7 +63,7 @@ exports[`TaskHasher hashTarget should hash entire subtree of dependencies 1`] =
}, },
"runtime": {}, "runtime": {},
}, },
"value": "17607022607820563118", "value": "14419327228911184578",
} }
`; `;
@ -83,7 +83,7 @@ exports[`TaskHasher hashTarget should hash executor dependencies of @nx packages
}, },
"runtime": {}, "runtime": {},
}, },
"value": "15096054768893599383", "value": "379625642227035180",
} }
`; `;
@ -105,7 +105,7 @@ exports[`TaskHasher hashTarget should use externalDependencies to override nx:ru
}, },
"runtime": {}, "runtime": {},
}, },
"value": "18142315317355318287", "value": "14779270419297346086",
} }
`; `;
@ -239,7 +239,7 @@ exports[`TaskHasher should hash missing dependent npm project versions 1`] = `
}, },
"runtime": {}, "runtime": {},
}, },
"value": "3668827038634092448", "value": "13210933885500739919",
} }
`; `;
@ -321,7 +321,7 @@ exports[`TaskHasher should hash npm project versions 1`] = `
}, },
"runtime": {}, "runtime": {},
}, },
"value": "3668827038634092448", "value": "13210933885500739919",
} }
`; `;

View File

@ -976,23 +976,23 @@ describe('TaskHasher', () => {
const hasher1 = createHasher(); const hasher1 = createHasher();
const hasher2 = createHasher(); const hasher2 = createHasher();
const hashA1 = hasher1.hashTask({ const hashA1 = await hasher1.hashTask({
id: 'a-build', id: 'a-build',
target: { project: 'a', target: 'build' }, target: { project: 'a', target: 'build' },
overrides: {}, overrides: {},
}); });
const hashB1 = hasher1.hashTask({ const hashB1 = await hasher1.hashTask({
id: 'b-build', id: 'b-build',
target: { project: 'b', target: 'build' }, target: { project: 'b', target: 'build' },
overrides: {}, overrides: {},
}); });
const hashB2 = hasher2.hashTask({ const hashB2 = await hasher2.hashTask({
id: 'b-build', id: 'b-build',
target: { project: 'b', target: 'build' }, target: { project: 'b', target: 'build' },
overrides: {}, overrides: {},
}); });
const hashA2 = hasher2.hashTask({ const hashA2 = await hasher2.hashTask({
id: 'a-build', id: 'a-build',
target: { project: 'a', target: 'build' }, target: { project: 'a', target: 'build' },
overrides: {}, overrides: {},

View File

@ -186,9 +186,9 @@ class TaskHasherImpl {
private runtimeHashes: { private runtimeHashes: {
[runtime: string]: Promise<PartialHash>; [runtime: string]: Promise<PartialHash>;
} = {}; } = {};
private externalDependencyHashes: Map<string, PartialHash> = new Map< private externalDependencyHashes: Map<string, PartialHash[]> = new Map<
string, string,
PartialHash PartialHash[]
>(); >();
private allExternalDependenciesHash: PartialHash; private allExternalDependenciesHash: PartialHash;
private projectRootMappings = createProjectRootMappings( private projectRootMappings = createProjectRootMappings(
@ -300,11 +300,16 @@ class TaskHasherImpl {
} }
private combinePartialHashes(partialHashes: PartialHash[]): PartialHash { private combinePartialHashes(partialHashes: PartialHash[]): PartialHash {
let details = {}; if (partialHashes.length === 1) {
for (const partial of partialHashes) { return partialHashes[0];
details = { ...details, ...partial.details };
} }
const value = hashArray(partialHashes.map(({ value }) => value)); 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 }; return { value, details };
} }
@ -416,25 +421,29 @@ class TaskHasherImpl {
} }
private getExternalDependencyHash(externalNodeName: string) { private getExternalDependencyHash(externalNodeName: string) {
return this.externalDependencyHashes.get(externalNodeName); 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 hashExternalDependency( private hashExternalDependency(
externalNodeName: string, externalNodeName: string,
visited: Set<string> visited: Set<string>
): PartialHash { ): PartialHash[] {
// try to retrieve the hash from cache // try to retrieve the hash from cache
if (this.externalDependencyHashes.has(externalNodeName)) { if (this.externalDependencyHashes.has(externalNodeName)) {
return this.externalDependencyHashes.get(externalNodeName); return this.externalDependencyHashes.get(externalNodeName);
} }
visited.add(externalNodeName); visited.add(externalNodeName);
const node = this.projectGraph.externalNodes[externalNodeName]; const node = this.projectGraph.externalNodes[externalNodeName];
let partialHash: PartialHash; const partialHashes: Set<PartialHash> = new Set<PartialHash>();
if (node) { if (node) {
const partialHashes: PartialHash[] = [];
if (node.data.hash) { if (node.data.hash) {
// we already know the hash of this dependency // we already know the hash of this dependency
partialHashes.push({ partialHashes.add({
value: node.data.hash, value: node.data.hash,
details: { details: {
[externalNodeName]: node.data.hash, [externalNodeName]: node.data.hash,
@ -442,7 +451,7 @@ class TaskHasherImpl {
}); });
} else { } else {
// we take version as a hash // we take version as a hash
partialHashes.push({ partialHashes.add({
value: node.data.version, value: node.data.version,
details: { details: {
[externalNodeName]: node.data.version, [externalNodeName]: node.data.version,
@ -453,25 +462,27 @@ class TaskHasherImpl {
if (this.projectGraph.dependencies[externalNodeName]) { if (this.projectGraph.dependencies[externalNodeName]) {
this.projectGraph.dependencies[externalNodeName].forEach((d) => { this.projectGraph.dependencies[externalNodeName].forEach((d) => {
if (!visited.has(d.target)) { if (!visited.has(d.target)) {
partialHashes.push(this.hashExternalDependency(d.target, visited)); for (const hash of this.hashExternalDependency(d.target, visited)) {
partialHashes.add(hash);
}
} }
}); });
} }
partialHash = this.combinePartialHashes(partialHashes);
} else { } else {
// unknown dependency // unknown dependency
// this may occur if dependency is not an npm package // 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 // 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 // in this case we have no information about the versioning of the given package
partialHash = { partialHashes.add({
value: `__${externalNodeName}__`, value: `__${externalNodeName}__`,
details: { details: {
[externalNodeName]: `__${externalNodeName}__`, [externalNodeName]: `__${externalNodeName}__`,
}, },
}; });
} }
this.externalDependencyHashes.set(externalNodeName, partialHash); const partialHashArray = Array.from(partialHashes);
return partialHash; this.externalDependencyHashes.set(externalNodeName, partialHashArray);
return partialHashArray;
} }
private hashTarget( private hashTarget(