fix(core): do not error when hashing executors which are local plugins (#18372)

This commit is contained in:
Jason Jean 2023-07-28 16:00:59 -04:00 committed by GitHub
parent 63521d7e13
commit 80c470ac7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 118 additions and 64 deletions

View File

@ -44,6 +44,52 @@ exports[`TaskHasher dependentTasksOutputFiles should work with dependent tasks w
}
`;
exports[`TaskHasher hashTarget should hash entire subtree in a deterministic way 1`] = `
{
"details": {
"command": "81188892120010785",
"implicitDeps": {},
"nodes": {
"ProjectConfiguration": "8474322003863204060",
"TsConfig": "8767608672024750088",
"appA:{projectRoot}/**/*": "3244421341483603138",
"npm:@nx/webpack": "$@nx/webpack0.0.0$",
"npm:packageA": "$packageA0.0.0$",
"npm:packageB": "$packageB0.0.0$",
"npm:packageC": "$packageC0.0.0$",
"{workspaceRoot}/.gitignore": "3244421341483603138",
"{workspaceRoot}/.nxignore": "3244421341483603138",
"{workspaceRoot}/nx.json": "8942239360311677987",
},
"runtime": {},
},
"value": "12756041818139421941",
}
`;
exports[`TaskHasher hashTarget should hash entire subtree in a deterministic way 2`] = `
{
"details": {
"command": "9096392622609675764",
"implicitDeps": {},
"nodes": {
"ProjectConfiguration": "17724470359684527282",
"TsConfig": "8767608672024750088",
"appB:{projectRoot}/**/*": "3244421341483603138",
"npm:@nx/webpack": "$@nx/webpack0.0.0$",
"npm:packageA": "$packageA0.0.0$",
"npm:packageB": "$packageB0.0.0$",
"npm:packageC": "$packageC0.0.0$",
"{workspaceRoot}/.gitignore": "3244421341483603138",
"{workspaceRoot}/.nxignore": "3244421341483603138",
"{workspaceRoot}/nx.json": "8942239360311677987",
},
"runtime": {},
},
"value": "15326312070983573452",
}
`;
exports[`TaskHasher hashTarget should hash entire subtree of dependencies 1`] = `
{
"details": {

View File

@ -1257,6 +1257,9 @@ describe('TaskHasher', () => {
expect(hashAppB1).toEqual(hashAppB2);
expect(hashAppA1).toEqual(hashAppA2);
expect(hashAppA1).toMatchSnapshot();
expect(hashAppB1).toMatchSnapshot();
});
it('should not hash when nx:run-commands executor', async () => {

View File

@ -20,6 +20,7 @@ import { getHashEnv } from './set-hash-env';
import { workspaceRoot } from '../utils/workspace-root';
import { join, relative } from 'path';
import { normalizePath } from '../utils/path';
import { findAllProjectNodeDependencies } from '../utils/project-graph-utils';
type ExpandedSelfInput =
| { fileset: string }
@ -429,60 +430,39 @@ class TaskHasherImpl {
return combinedHash;
}
private hashExternalDependency(
externalNodeName: string,
visited: Set<string>
): PartialHash[] {
// try to retrieve the hash from cache
if (this.externalDependencyHashes.has(externalNodeName)) {
return this.externalDependencyHashes.get(externalNodeName);
}
visited.add(externalNodeName);
private hashSingleExternalDependency(externalNodeName: string): PartialHash {
const node = this.projectGraph.externalNodes[externalNodeName];
const partialHashes: Set<PartialHash> = new Set<PartialHash>();
if (node) {
if (node.data.hash) {
// we already know the hash of this dependency
partialHashes.add({
value: node.data.hash,
details: {
[externalNodeName]: node.data.hash,
},
});
} else {
// we take version as a hash
partialHashes.add({
value: node.data.version,
details: {
[externalNodeName]: node.data.version,
},
});
}
// we want to calculate the hash of the entire dependency tree
if (this.projectGraph.dependencies[externalNodeName]) {
this.projectGraph.dependencies[externalNodeName].forEach((d) => {
if (!visited.has(d.target)) {
for (const hash of this.hashExternalDependency(d.target, visited)) {
partialHashes.add(hash);
}
}
});
}
} 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
partialHashes.add({
value: `__${externalNodeName}__`,
if (node.data.hash) {
// we already know the hash of this dependency
return {
value: node.data.hash,
details: {
[externalNodeName]: `__${externalNodeName}__`,
[externalNodeName]: node.data.hash,
},
});
};
} else {
// we take version as a hash
return {
value: node.data.version,
details: {
[externalNodeName]: node.data.version,
},
};
}
const partialHashArray = Array.from(partialHashes);
this.externalDependencyHashes.set(externalNodeName, partialHashArray);
return partialHashArray;
}
private hashExternalDependency(externalNodeName: string) {
const partialHashes: Set<PartialHash> = new Set<PartialHash>();
partialHashes.add(this.hashSingleExternalDependency(externalNodeName));
const deps = findAllProjectNodeDependencies(
externalNodeName,
this.projectGraph,
true
);
for (const dep of deps) {
partialHashes.add(this.hashSingleExternalDependency(dep));
}
return Array.from(partialHashes);
}
private hashTarget(
@ -507,6 +487,13 @@ class TaskHasherImpl {
const executorPackage = target.executor.split(':')[0];
const executorNodeName =
this.findExternalDependencyNodeName(executorPackage);
// This is either a local plugin or a non-existent executor
if (!executorNodeName) {
// TODO: This should not return null if it is a local plugin's executor
return null;
}
return this.getExternalDependencyHash(executorNodeName);
} else {
// use command external dependencies if available to construct the hash
@ -519,6 +506,12 @@ class TaskHasherImpl {
const externalDependencies = input['externalDependencies'];
for (let dep of externalDependencies) {
dep = this.findExternalDependencyNodeName(dep);
if (!dep) {
throw new Error(
`The externalDependency "${dep}" for "${projectName}:${targetName}" could not be found`
);
}
partialHashes.push(this.getExternalDependencyHash(dep));
}
}
@ -543,7 +536,7 @@ class TaskHasherImpl {
}
}
private findExternalDependencyNodeName(packageName: string): string {
private findExternalDependencyNodeName(packageName: string): string | null {
if (this.projectGraph.externalNodes[packageName]) {
return packageName;
}
@ -555,8 +548,8 @@ class TaskHasherImpl {
return node.name;
}
}
// not found, just return the package name
return packageName;
// not found
return null;
}
private async hashSingleProjectInputs(
@ -768,7 +761,10 @@ class TaskHasherImpl {
private calculateExternalDependencyHashes() {
const keys = Object.keys(this.projectGraph.externalNodes);
for (const externalNodeName of keys) {
this.hashExternalDependency(externalNodeName, new Set<string>());
this.externalDependencyHashes.set(
externalNodeName,
this.hashExternalDependency(externalNodeName)
);
}
}
}

View File

@ -75,21 +75,24 @@ export function getSourceDirOfDependentProjects(
/**
* Find all internal project dependencies.
* All the external (npm) dependencies will be filtered out
* All the external (npm) dependencies will be filtered out unless includeExternalDependencies is set to true
* @param {string} parentNodeName
* @param {ProjectGraph} projectGraph
* @param includeExternalDependencies
* @returns {string[]}
*/
export function findAllProjectNodeDependencies(
parentNodeName: string,
projectGraph = readCachedProjectGraph()
projectGraph = readCachedProjectGraph(),
includeExternalDependencies = false
): string[] {
const dependencyNodeNames = new Set<string>();
collectDependentProjectNodesNames(
projectGraph as ProjectGraph,
dependencyNodeNames,
parentNodeName
parentNodeName,
includeExternalDependencies
);
return Array.from(dependencyNodeNames);
@ -99,7 +102,8 @@ export function findAllProjectNodeDependencies(
function collectDependentProjectNodesNames(
nxDeps: ProjectGraph,
dependencyNodeNames: Set<string>,
parentNodeName: string
parentNodeName: string,
includeExternalDependencies: boolean
) {
const dependencies = nxDeps.dependencies[parentNodeName];
if (!dependencies) {
@ -111,23 +115,28 @@ function collectDependentProjectNodesNames(
for (const dependency of dependencies) {
const dependencyName = dependency.target;
// we're only interested in internal nodes, not external
if (nxDeps.externalNodes?.[dependencyName]) {
continue;
}
// skip dependencies already added (avoid circular dependencies)
if (dependencyNodeNames.has(dependencyName)) {
continue;
}
// we're only interested in internal nodes, not external
if (nxDeps.externalNodes?.[dependencyName]) {
if (includeExternalDependencies) {
dependencyNodeNames.add(dependencyName);
} else {
continue;
}
}
dependencyNodeNames.add(dependencyName);
// Get the dependencies of the dependencies
collectDependentProjectNodesNames(
nxDeps,
dependencyNodeNames,
dependencyName
dependencyName,
includeExternalDependencies
);
}
}