Skip to content

Commit f1177e7

Browse files
committed
refactor(project): Refactor project cache validation
1 parent ed0f9c9 commit f1177e7

10 files changed

Lines changed: 630 additions & 299 deletions

File tree

packages/project/lib/build/ProjectBuilder.js

Lines changed: 37 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ class ProjectBuilder {
180180
}
181181
}
182182

183-
const projectBuildContexts = await this._createRequiredBuildContexts(requestedProjects);
183+
const projectBuildContexts = await this._buildContext.createRequiredProjectContexts(requestedProjects);
184184
let fsTarget;
185185
if (destPath) {
186186
fsTarget = resourceFactory.createAdapter({
@@ -236,7 +236,16 @@ class ProjectBuilder {
236236
}));
237237

238238
const alreadyBuilt = [];
239+
const changedDependencyResources = [];
239240
for (const projectBuildContext of queue) {
241+
if (changedDependencyResources.length) {
242+
// Notify build cache of changed resources from dependencies
243+
projectBuildContext.dependencyResourcesChanged(changedDependencyResources);
244+
}
245+
const changedResources = await projectBuildContext.determineChangedResources();
246+
for (const resourcePath of changedResources) {
247+
changedDependencyResources.push(resourcePath);
248+
}
240249
if (!await projectBuildContext.requiresBuild()) {
241250
const projectName = projectBuildContext.getProject().getName();
242251
alreadyBuilt.push(projectName);
@@ -275,19 +284,24 @@ class ProjectBuilder {
275284
try {
276285
const startTime = process.hrtime();
277286
const pWrites = [];
278-
for (const projectBuildContext of queue) {
287+
while (queue.length) {
288+
const projectBuildContext = queue.shift();
279289
const project = projectBuildContext.getProject();
280290
const projectName = project.getName();
281291
const projectType = project.getType();
282292
this.#log.verbose(`Processing project ${projectName}...`);
283293

284294
// Only build projects that are not already build (i.e. provide a matching build manifest)
285-
if (alreadyBuilt.includes(projectName)) {
295+
if (alreadyBuilt.includes(projectName) || !(await projectBuildContext.requiresBuild())) {
286296
this.#log.skipProjectBuild(projectName, projectType);
287297
} else {
288298
this.#log.startProjectBuild(projectName, projectType);
289-
await projectBuildContext.getTaskRunner().runTasks();
299+
const changedResources = await projectBuildContext.runTasks();
290300
this.#log.endProjectBuild(projectName, projectType);
301+
for (const pbc of queue) {
302+
// Propagate resource changes to following projects
303+
pbc.getBuildCache().dependencyResourcesChanged(changedResources);
304+
}
291305
}
292306
if (!requestedProjects.includes(projectName)) {
293307
// Project has not been requested
@@ -306,7 +320,7 @@ class ProjectBuilder {
306320
project,
307321
this._graph, this._buildContext.getBuildConfig(), this._buildContext.getTaskRepository(),
308322
projectBuildContext.getBuildSignature());
309-
pWrites.push(projectBuildContext.getBuildCache().storeCache(buildManifest));
323+
pWrites.push(projectBuildContext.getBuildCache().writeCache(buildManifest));
310324
}
311325
}
312326
await Promise.all(pWrites);
@@ -337,6 +351,7 @@ class ProjectBuilder {
337351

338352
async #update(projectBuildContexts, requestedProjects, fsTarget) {
339353
const queue = [];
354+
const changedDependencyResources = [];
340355
await this._graph.traverseDepthFirst(async ({project}) => {
341356
const projectName = project.getName();
342357
const projectBuildContext = projectBuildContexts.get(projectName);
@@ -345,6 +360,15 @@ class ProjectBuilder {
345360
// => This project needs to be built or, in case it has already
346361
// been built, it's build result needs to be written out (if requested)
347362
queue.push(projectBuildContext);
363+
364+
if (changedDependencyResources.length) {
365+
// Notify build cache of changed resources from dependencies
366+
await projectBuildContext.dependencyResourcesChanged(changedDependencyResources);
367+
}
368+
const changedResources = await projectBuildContext.determineChangedResources();
369+
for (const resourcePath of changedResources) {
370+
changedDependencyResources.push(resourcePath);
371+
}
348372
}
349373
});
350374

@@ -353,7 +377,8 @@ class ProjectBuilder {
353377
}));
354378

355379
const pWrites = [];
356-
for (const projectBuildContext of queue) {
380+
while (queue.length) {
381+
const projectBuildContext = queue.shift();
357382
const project = projectBuildContext.getProject();
358383
const projectName = project.getName();
359384
const projectType = project.getType();
@@ -365,8 +390,12 @@ class ProjectBuilder {
365390
}
366391

367392
this.#log.startProjectBuild(projectName, projectType);
368-
await projectBuildContext.runTasks();
393+
const changedResources = await projectBuildContext.runTasks();
369394
this.#log.endProjectBuild(projectName, projectType);
395+
for (const pbc of queue) {
396+
// Propagate resource changes to following projects
397+
pbc.getBuildCache().dependencyResourcesChanged(changedResources);
398+
}
370399
if (!requestedProjects.includes(projectName)) {
371400
// Project has not been requested
372401
// => Its resources shall not be part of the build result
@@ -386,52 +415,11 @@ class ProjectBuilder {
386415
project,
387416
this._graph, this._buildContext.getBuildConfig(), this._buildContext.getTaskRepository(),
388417
projectBuildContext.getBuildSignature());
389-
pWrites.push(projectBuildContext.getBuildCache().storeCache(buildManifest));
418+
pWrites.push(projectBuildContext.getBuildCache().writeCache(buildManifest));
390419
}
391420
await Promise.all(pWrites);
392421
}
393422

394-
async _createRequiredBuildContexts(requestedProjects) {
395-
const requiredProjects = new Set(this._graph.getProjectNames().filter((projectName) => {
396-
return requestedProjects.includes(projectName);
397-
}));
398-
399-
const projectBuildContexts = new Map();
400-
401-
for (const projectName of requiredProjects) {
402-
this.#log.verbose(`Creating build context for project ${projectName}...`);
403-
const projectBuildContext = await this._buildContext.createProjectContext({
404-
project: this._graph.getProject(projectName)
405-
});
406-
407-
projectBuildContexts.set(projectName, projectBuildContext);
408-
409-
if (await projectBuildContext.requiresBuild()) {
410-
const taskRunner = projectBuildContext.getTaskRunner();
411-
const requiredDependencies = await taskRunner.getRequiredDependencies();
412-
413-
if (requiredDependencies.size === 0) {
414-
continue;
415-
}
416-
// This project needs to be built and required dependencies to be built as well
417-
this._graph.getDependencies(projectName).forEach((depName) => {
418-
if (projectBuildContexts.has(depName)) {
419-
// Build context already exists
420-
// => Dependency will be built
421-
return;
422-
}
423-
if (!requiredDependencies.has(depName)) {
424-
return;
425-
}
426-
// Add dependency to list of projects to build
427-
requiredProjects.add(depName);
428-
});
429-
}
430-
}
431-
432-
return projectBuildContexts;
433-
}
434-
435423
async _getProjectFilter({
436424
dependencyIncludes,
437425
explicitIncludes,

packages/project/lib/build/TaskRunner.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ class TaskRunner {
131131
await this._executeTask(taskName, taskFunction);
132132
}
133133
}
134-
this._buildCache.allTasksCompleted();
134+
return await this._buildCache.allTasksCompleted();
135135
}
136136

137137
/**
@@ -485,13 +485,13 @@ class TaskRunner {
485485
* @returns {Promise} Resolves when task has finished
486486
*/
487487
async _executeTask(taskName, taskFunction, taskParams) {
488-
if (this._buildCache.isTaskCacheValid(taskName)) {
489-
// Immediately skip task if cache is valid
490-
// Continue if cache is (potentially) invalid, in which case taskFunction will
491-
// validate the cache thoroughly
492-
this._log.skipTask(taskName);
493-
return;
494-
}
488+
// if (this._buildCache.isTaskCacheValid(taskName)) {
489+
// // Immediately skip task if cache is valid
490+
// // Continue if cache is (potentially) invalid, in which case taskFunction will
491+
// // validate the cache thoroughly
492+
// this._log.skipTask(taskName);
493+
// return;
494+
// }
495495
this._taskStart = performance.now();
496496
await taskFunction(taskParams, this._log);
497497
if (this._log.isLevelEnabled("perf")) {

packages/project/lib/build/cache/BuildTaskCache.js

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export default class BuildTaskCache {
4343
#readTaskMetadataCache;
4444
#treeRegistries = [];
4545
#useDifferentialUpdate = true;
46-
#isNewOrModified;
46+
#hasNewOrModifiedCacheEntries = true;
4747

4848
// ===== LIFECYCLE =====
4949

@@ -67,7 +67,7 @@ export default class BuildTaskCache {
6767
if (!this.#readTaskMetadataCache) {
6868
// No cache reader provided, start with empty graph
6969
this.#resourceRequests = new ResourceRequestGraph();
70-
this.#isNewOrModified = true;
70+
this.#hasNewOrModifiedCacheEntries = true;
7171
return;
7272
}
7373

@@ -78,7 +78,7 @@ export default class BuildTaskCache {
7878
`of project '${this.#projectName}'`);
7979
}
8080
this.#resourceRequests = this.#restoreGraphFromCache(taskMetadata);
81-
this.#isNewOrModified = false;
81+
this.#hasNewOrModifiedCacheEntries = false; // Using cache
8282
}
8383

8484
// ===== METADATA ACCESS =====
@@ -92,8 +92,8 @@ export default class BuildTaskCache {
9292
return this.#taskName;
9393
}
9494

95-
isNewOrModified() {
96-
return this.#isNewOrModified;
95+
hasNewOrModifiedCacheEntries() {
96+
return this.#hasNewOrModifiedCacheEntries;
9797
}
9898

9999
/**
@@ -405,7 +405,7 @@ export default class BuildTaskCache {
405405
if (!relevantTree) {
406406
return;
407407
}
408-
this.#isNewOrModified = true;
408+
this.#hasNewOrModifiedCacheEntries = true;
409409

410410
// Update signatures for affected request sets
411411
const {requestSetId, signature: originalSignature} = trees.get(relevantTree);
@@ -525,6 +525,32 @@ export default class BuildTaskCache {
525525
});
526526
}
527527

528+
async isAffectedByProjectChanges(changedPaths) {
529+
await this.#initResourceRequests();
530+
const resourceRequests = this.#resourceRequests.getAllRequests();
531+
return resourceRequests.some(({type, value}) => {
532+
if (type === "path") {
533+
return changedPaths.includes(value);
534+
}
535+
if (type === "patterns") {
536+
return micromatch(changedPaths, value).length > 0;
537+
}
538+
});
539+
}
540+
541+
async isAffectedByDependencyChanges(changedPaths) {
542+
await this.#initResourceRequests();
543+
const resourceRequests = this.#resourceRequests.getAllRequests();
544+
return resourceRequests.some(({type, value}) => {
545+
if (type === "dep-path") {
546+
return changedPaths.includes(value);
547+
}
548+
if (type === "dep-patterns") {
549+
return micromatch(changedPaths, value).length > 0;
550+
}
551+
});
552+
}
553+
528554
/**
529555
* Serializes the task cache to a plain object for persistence
530556
*

packages/project/lib/build/cache/CacheManager.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -161,11 +161,12 @@ export default class CacheManager {
161161
* @private
162162
* @param {string} packageName - Package/project identifier
163163
* @param {string} buildSignature - Build signature hash
164+
* @param {string} kind "source" or "result"
164165
* @returns {string} Absolute path to the index metadata file
165166
*/
166-
#getIndexCachePath(packageName, buildSignature) {
167+
#getIndexCachePath(packageName, buildSignature, kind) {
167168
const pkgDir = getPathFromPackageName(packageName);
168-
return path.join(this.#indexDir, pkgDir, `${buildSignature}.json`);
169+
return path.join(this.#indexDir, pkgDir, `${kind}-${buildSignature}.json`);
169170
}
170171

171172
/**
@@ -176,12 +177,13 @@ export default class CacheManager {
176177
*
177178
* @param {string} projectId - Project identifier (typically package name)
178179
* @param {string} buildSignature - Build signature hash
180+
* @param {string} kind "source" or "result"
179181
* @returns {Promise<object|null>} Parsed index cache object or null if not found
180182
* @throws {Error} If file read fails for reasons other than file not existing
181183
*/
182-
async readIndexCache(projectId, buildSignature) {
184+
async readIndexCache(projectId, buildSignature, kind) {
183185
try {
184-
const metadata = await readFile(this.#getIndexCachePath(projectId, buildSignature), "utf8");
186+
const metadata = await readFile(this.#getIndexCachePath(projectId, buildSignature, kind), "utf8");
185187
return JSON.parse(metadata);
186188
} catch (err) {
187189
if (err.code === "ENOENT") {
@@ -203,11 +205,12 @@ export default class CacheManager {
203205
*
204206
* @param {string} projectId - Project identifier (typically package name)
205207
* @param {string} buildSignature - Build signature hash
208+
* @param {string} kind "source" or "result"
206209
* @param {object} index - Index object containing resource tree and task metadata
207210
* @returns {Promise<void>}
208211
*/
209-
async writeIndexCache(projectId, buildSignature, index) {
210-
const indexPath = this.#getIndexCachePath(projectId, buildSignature);
212+
async writeIndexCache(projectId, buildSignature, kind, index) {
213+
const indexPath = this.#getIndexCachePath(projectId, buildSignature, kind);
211214
await mkdir(path.dirname(indexPath), {recursive: true});
212215
await writeFile(indexPath, JSON.stringify(index, null, 2), "utf8");
213216
}

0 commit comments

Comments
 (0)