fix(core): do not error when hashing executors which are local plugins (#18372)
This commit is contained in:
parent
63521d7e13
commit
80c470ac7f
@ -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`] = `
|
exports[`TaskHasher hashTarget should hash entire subtree of dependencies 1`] = `
|
||||||
{
|
{
|
||||||
"details": {
|
"details": {
|
||||||
|
|||||||
@ -1257,6 +1257,9 @@ describe('TaskHasher', () => {
|
|||||||
|
|
||||||
expect(hashAppB1).toEqual(hashAppB2);
|
expect(hashAppB1).toEqual(hashAppB2);
|
||||||
expect(hashAppA1).toEqual(hashAppA2);
|
expect(hashAppA1).toEqual(hashAppA2);
|
||||||
|
|
||||||
|
expect(hashAppA1).toMatchSnapshot();
|
||||||
|
expect(hashAppB1).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not hash when nx:run-commands executor', async () => {
|
it('should not hash when nx:run-commands executor', async () => {
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import { getHashEnv } from './set-hash-env';
|
|||||||
import { workspaceRoot } from '../utils/workspace-root';
|
import { workspaceRoot } from '../utils/workspace-root';
|
||||||
import { join, relative } from 'path';
|
import { join, relative } from 'path';
|
||||||
import { normalizePath } from '../utils/path';
|
import { normalizePath } from '../utils/path';
|
||||||
|
import { findAllProjectNodeDependencies } from '../utils/project-graph-utils';
|
||||||
|
|
||||||
type ExpandedSelfInput =
|
type ExpandedSelfInput =
|
||||||
| { fileset: string }
|
| { fileset: string }
|
||||||
@ -429,60 +430,39 @@ class TaskHasherImpl {
|
|||||||
return combinedHash;
|
return combinedHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
private hashExternalDependency(
|
private hashSingleExternalDependency(externalNodeName: string): PartialHash {
|
||||||
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);
|
|
||||||
const node = this.projectGraph.externalNodes[externalNodeName];
|
const node = this.projectGraph.externalNodes[externalNodeName];
|
||||||
const partialHashes: Set<PartialHash> = new Set<PartialHash>();
|
if (node.data.hash) {
|
||||||
if (node) {
|
// we already know the hash of this dependency
|
||||||
if (node.data.hash) {
|
return {
|
||||||
// we already know the hash of this dependency
|
value: node.data.hash,
|
||||||
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}__`,
|
|
||||||
details: {
|
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(
|
private hashTarget(
|
||||||
@ -507,6 +487,13 @@ class TaskHasherImpl {
|
|||||||
const executorPackage = target.executor.split(':')[0];
|
const executorPackage = target.executor.split(':')[0];
|
||||||
const executorNodeName =
|
const executorNodeName =
|
||||||
this.findExternalDependencyNodeName(executorPackage);
|
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);
|
return this.getExternalDependencyHash(executorNodeName);
|
||||||
} else {
|
} else {
|
||||||
// use command external dependencies if available to construct the hash
|
// use command external dependencies if available to construct the hash
|
||||||
@ -519,6 +506,12 @@ class TaskHasherImpl {
|
|||||||
const externalDependencies = input['externalDependencies'];
|
const externalDependencies = input['externalDependencies'];
|
||||||
for (let dep of externalDependencies) {
|
for (let dep of externalDependencies) {
|
||||||
dep = this.findExternalDependencyNodeName(dep);
|
dep = this.findExternalDependencyNodeName(dep);
|
||||||
|
if (!dep) {
|
||||||
|
throw new Error(
|
||||||
|
`The externalDependency "${dep}" for "${projectName}:${targetName}" could not be found`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
partialHashes.push(this.getExternalDependencyHash(dep));
|
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]) {
|
if (this.projectGraph.externalNodes[packageName]) {
|
||||||
return packageName;
|
return packageName;
|
||||||
}
|
}
|
||||||
@ -555,8 +548,8 @@ class TaskHasherImpl {
|
|||||||
return node.name;
|
return node.name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// not found, just return the package name
|
// not found
|
||||||
return packageName;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async hashSingleProjectInputs(
|
private async hashSingleProjectInputs(
|
||||||
@ -768,7 +761,10 @@ class TaskHasherImpl {
|
|||||||
private calculateExternalDependencyHashes() {
|
private calculateExternalDependencyHashes() {
|
||||||
const keys = Object.keys(this.projectGraph.externalNodes);
|
const keys = Object.keys(this.projectGraph.externalNodes);
|
||||||
for (const externalNodeName of keys) {
|
for (const externalNodeName of keys) {
|
||||||
this.hashExternalDependency(externalNodeName, new Set<string>());
|
this.externalDependencyHashes.set(
|
||||||
|
externalNodeName,
|
||||||
|
this.hashExternalDependency(externalNodeName)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -75,21 +75,24 @@ export function getSourceDirOfDependentProjects(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Find all internal project dependencies.
|
* 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 {string} parentNodeName
|
||||||
* @param {ProjectGraph} projectGraph
|
* @param {ProjectGraph} projectGraph
|
||||||
|
* @param includeExternalDependencies
|
||||||
* @returns {string[]}
|
* @returns {string[]}
|
||||||
*/
|
*/
|
||||||
export function findAllProjectNodeDependencies(
|
export function findAllProjectNodeDependencies(
|
||||||
parentNodeName: string,
|
parentNodeName: string,
|
||||||
projectGraph = readCachedProjectGraph()
|
projectGraph = readCachedProjectGraph(),
|
||||||
|
includeExternalDependencies = false
|
||||||
): string[] {
|
): string[] {
|
||||||
const dependencyNodeNames = new Set<string>();
|
const dependencyNodeNames = new Set<string>();
|
||||||
|
|
||||||
collectDependentProjectNodesNames(
|
collectDependentProjectNodesNames(
|
||||||
projectGraph as ProjectGraph,
|
projectGraph as ProjectGraph,
|
||||||
dependencyNodeNames,
|
dependencyNodeNames,
|
||||||
parentNodeName
|
parentNodeName,
|
||||||
|
includeExternalDependencies
|
||||||
);
|
);
|
||||||
|
|
||||||
return Array.from(dependencyNodeNames);
|
return Array.from(dependencyNodeNames);
|
||||||
@ -99,7 +102,8 @@ export function findAllProjectNodeDependencies(
|
|||||||
function collectDependentProjectNodesNames(
|
function collectDependentProjectNodesNames(
|
||||||
nxDeps: ProjectGraph,
|
nxDeps: ProjectGraph,
|
||||||
dependencyNodeNames: Set<string>,
|
dependencyNodeNames: Set<string>,
|
||||||
parentNodeName: string
|
parentNodeName: string,
|
||||||
|
includeExternalDependencies: boolean
|
||||||
) {
|
) {
|
||||||
const dependencies = nxDeps.dependencies[parentNodeName];
|
const dependencies = nxDeps.dependencies[parentNodeName];
|
||||||
if (!dependencies) {
|
if (!dependencies) {
|
||||||
@ -111,23 +115,28 @@ function collectDependentProjectNodesNames(
|
|||||||
for (const dependency of dependencies) {
|
for (const dependency of dependencies) {
|
||||||
const dependencyName = dependency.target;
|
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)
|
// skip dependencies already added (avoid circular dependencies)
|
||||||
if (dependencyNodeNames.has(dependencyName)) {
|
if (dependencyNodeNames.has(dependencyName)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// we're only interested in internal nodes, not external
|
||||||
|
if (nxDeps.externalNodes?.[dependencyName]) {
|
||||||
|
if (includeExternalDependencies) {
|
||||||
|
dependencyNodeNames.add(dependencyName);
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dependencyNodeNames.add(dependencyName);
|
dependencyNodeNames.add(dependencyName);
|
||||||
|
|
||||||
// Get the dependencies of the dependencies
|
// Get the dependencies of the dependencies
|
||||||
collectDependentProjectNodesNames(
|
collectDependentProjectNodesNames(
|
||||||
nxDeps,
|
nxDeps,
|
||||||
dependencyNodeNames,
|
dependencyNodeNames,
|
||||||
dependencyName
|
dependencyName,
|
||||||
|
includeExternalDependencies
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user