Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions packages/project/lib/build/cache/BuildStageCache.js
Original file line number Diff line number Diff line change
Expand Up @@ -389,22 +389,30 @@ export default class BuildStageCache {
* {@link #getRootSignature} for how those two are re-evaluated against the current environment and
* project root.
*
* Returns the composed exact-match signatures alongside the four components they were built from, so a
* caller that also needs the components (the delta path pairs them with changed project/dependency
* signatures) does not recompute them.
*
* @public
* @param {function(string, string, (string|undefined)): (string|undefined)} [resolveInputValue]
* Resolver for the current value of a recorded non-resource input (see {@link #getInputSignature})
* @returns {string[]} Exact-match stage signatures for the current index state
* @returns {{stageSignatures: string[], inputSignature: string, rootSignature: string,
* projectSignatures: string[], dependencySignatures: string[]}}
* The exact-match stage signatures for the current index state and the components they compose
*/
getStageSignatures(resolveInputValue) {
const inputSignature = this.getInputSignature(resolveInputValue);
const rootSignature = this.getRootSignature();
const signatures = [];
for (const projectSignature of this.getProjectIndexSignatures()) {
for (const dependencySignature of this.getDependencyIndexSignatures()) {
signatures.push(createStageSignature(
const projectSignatures = this.getProjectIndexSignatures();
const dependencySignatures = this.getDependencyIndexSignatures();
const stageSignatures = [];
for (const projectSignature of projectSignatures) {
for (const dependencySignature of dependencySignatures) {
stageSignatures.push(createStageSignature(
[projectSignature, dependencySignature, inputSignature, rootSignature]));
}
}
return signatures;
return {stageSignatures, inputSignature, rootSignature, projectSignatures, dependencySignatures};
}

/**
Expand Down
24 changes: 16 additions & 8 deletions packages/project/lib/build/cache/ProjectBuildCache.js
Original file line number Diff line number Diff line change
Expand Up @@ -670,8 +670,18 @@ export default class ProjectBuildCache {
}

/**
* Aggregates the current root signatures across all stage caches into one signature, used to detect
* whether any recorded root file changed since the cache was last validated.
* Aggregates the current root signatures across all stage caches into one signature. Order-independent:
* the per-stage signatures are sorted before hashing.
*
* Two callers compute the same value in one validateCache pass by design:
* - The change-detection gate in validateCache (conditional on #anyStageHasRootRequests()) compares it
* against #cachedRootAggregateSignature and forces result-cache revalidation when a root file moved.
* - {@link #getPossibleResultStageSignatures} / {@link #getResultStageSignature} use it (unconditionally)
* as one slot of the result stage signature, a sibling of the input slot
* ({@link #getAggregatedInputSignature}), so a changed root file invalidates the result cache.
*
* Nothing changes the root indices or stage caches between the two calls, so recomputing rather than
* memoizing keeps the conditional gate and the unconditional slot decoupled for one small hash.
*
* @returns {string} Aggregated root signature
*/
Expand Down Expand Up @@ -747,10 +757,10 @@ export default class ProjectBuildCache {
// with the current input and root signatures (BuildStageCache.getStageSignatures). The input and
// root signatures are re-evaluated against the current environment, graph, and project root, so a
// changed input or root file misses the cached stage. Root indices were refreshed in validateCache
// before this build's stages run.
const inputSignature = stageCache.getInputSignature(this.#resolveInputValue);
const rootSignature = stageCache.getRootSignature();
const stageSignatures = stageCache.getStageSignatures(this.#resolveInputValue);
// before this build's stages run. getStageSignatures returns the four components alongside the
// candidates, so the delta path below reuses them instead of recomputing.
const {stageSignatures, inputSignature, rootSignature, projectSignatures, dependencySignatures} =
stageCache.getStageSignatures(this.#resolveInputValue);

const cachedStage = this.#findStageCache(stageId, stageSignatures);
const oldStageTuple = this.#currentStageSignatures.get(stageId);
Expand Down Expand Up @@ -781,8 +791,6 @@ export default class ProjectBuildCache {
`Attempting to find delta cached stage...`);
const projectDeltas = stageCache.getProjectIndexDeltas();
const depDeltas = stageCache.getDependencyIndexDeltas();
const projectSignatures = stageCache.getProjectIndexSignatures();
const dependencySignatures = stageCache.getDependencyIndexSignatures();

// Build the delta candidates and carry each one's provenance alongside it: the resolved new
// project and dependency components and the changed-path lists. The winner is looked up by its
Expand Down
12 changes: 9 additions & 3 deletions packages/project/test/lib/build/cache/BuildStageCache.js
Original file line number Diff line number Diff line change
Expand Up @@ -469,10 +469,16 @@ test("getStageSignatures: composes the [project, dependency, input, root] tuple"
dependencyReader,
});

const stageSignatures = cache.getStageSignatures();
const {stageSignatures, inputSignature, rootSignature, projectSignatures, dependencySignatures} =
cache.getStageSignatures();

t.deepEqual(stageSignatures, [`${projectSig}-${dependencySig}-${inputSig}-${rootSig}`],
"The single exact-match signature is the four components joined in tuple order");
t.is(inputSignature, inputSig, "Returns the input component used to compose the signatures");
t.is(rootSignature, rootSig, "Returns the root component used to compose the signatures");
t.deepEqual(projectSignatures, [projectSig], "Returns the project signatures used in the product");
t.deepEqual(dependencySignatures, [dependencySig],
"Returns the dependency signatures used in the product");
});

test("getStageSignatures: empty-input and empty-root components are the stable empty-set digests",
Expand All @@ -491,7 +497,7 @@ test("getStageSignatures: empty-input and empty-root components are the stable e
dependencyReader,
});

const [signature] = cache.getStageSignatures();
const {stageSignatures: [signature]} = cache.getStageSignatures();
const [, , inputComponent, rootComponent] = signature.split("-");

t.is(inputComponent, cache.getInputSignature(), "Input slot is the empty-input digest");
Expand All @@ -513,7 +519,7 @@ test("getStageSignatures: one signature per project x dependency index-signature
dependencyReader,
});

t.is(cache.getStageSignatures().length, 1,
t.is(cache.getStageSignatures().stageSignatures.length, 1,
"One project signature times one dependency signature yields one stage signature");
});

Expand Down
Loading