Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import javax.inject.Named;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.List;
Expand Down Expand Up @@ -138,33 +139,58 @@ public void execute(

boolean restorable = result.isSuccess() || result.isPartialSuccess();
boolean restored = false; // if partially restored need to save increment

if (restorable) {
CacheRestorationStatus cacheRestorationStatus =
restoreProject(result, mojoExecutions, mojoExecutionRunner, cacheConfig);
restored = CacheRestorationStatus.SUCCESS == cacheRestorationStatus;
executeExtraCleanPhaseIfNeeded(cacheRestorationStatus, cleanPhase, mojoExecutionRunner);
}
if (!restored) {
for (MojoExecution mojoExecution : mojoExecutions) {
if (source == Source.CLI
|| mojoExecution.getLifecyclePhase() == null
|| lifecyclePhasesHelper.isLaterPhaseThanClean(mojoExecution.getLifecyclePhase())) {
mojoExecutionRunner.run(mojoExecution);

try {
if (!restored && !forkedExecution) {
// Move pre-existing artifacts to staging directory to prevent caching stale files
// from previous builds (e.g., after git branch switch, or from cache restored
// with clock skew). This ensures save() only sees fresh files built during this session.
// Skip for forked executions since they don't cache and shouldn't modify artifacts.
try {
cacheController.stagePreExistingArtifacts(session, project);
} catch (IOException e) {
LOGGER.debug("Failed to stage pre-existing artifacts: {}", e.getMessage());
// Continue build - if staging fails, we'll just cache what exists
}
}
}

if (cacheState == INITIALIZED && (!result.isSuccess() || !restored)) {
if (cacheConfig.isSkipSave()) {
LOGGER.info("Cache saving is disabled.");
} else if (cacheConfig.isMandatoryClean()
&& lifecyclePhasesHelper
.getCleanSegment(project, mojoExecutions)
.isEmpty()) {
LOGGER.info("Cache storing is skipped since there was no \"clean\" phase.");
} else {
final Map<String, MojoExecutionEvent> executionEvents = mojoListener.getProjectExecutions(project);
cacheController.save(result, mojoExecutions, executionEvents);
if (!restored) {
for (MojoExecution mojoExecution : mojoExecutions) {
if (source == Source.CLI
|| mojoExecution.getLifecyclePhase() == null
|| lifecyclePhasesHelper.isLaterPhaseThanClean(mojoExecution.getLifecyclePhase())) {
mojoExecutionRunner.run(mojoExecution);
}
}
}

if (cacheState == INITIALIZED && (!result.isSuccess() || !restored)) {
if (cacheConfig.isSkipSave()) {
LOGGER.debug("Cache saving is disabled.");
} else if (cacheConfig.isMandatoryClean()
&& lifecyclePhasesHelper
.getCleanSegment(project, mojoExecutions)
.isEmpty()) {
LOGGER.debug("Cache storing is skipped since there was no \"clean\" phase.");
} else {
final Map<String, MojoExecutionEvent> executionEvents =
mojoListener.getProjectExecutions(project);
cacheController.save(result, mojoExecutions, executionEvents);
}
}
} finally {
// Always restore staged files after build completes (whether save ran or not).
// Files that were rebuilt are discarded; files that weren't rebuilt are restored.
// Skip for forked executions since they don't stage artifacts.
if (!restored && !forkedExecution) {
cacheController.restoreStagedArtifacts(session, project);
}
}

Expand Down
20 changes: 20 additions & 0 deletions src/main/java/org/apache/maven/buildcache/CacheController.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
package org.apache.maven.buildcache;

import java.io.IOException;
import java.util.List;
import java.util.Map;

Expand Down Expand Up @@ -45,4 +46,23 @@ void save(
boolean isForcedExecution(MavenProject project, MojoExecution execution);

void saveCacheReport(MavenSession session);

/**
* Move pre-existing artifacts to staging directory to prevent caching stale files.
* Called before mojos run to ensure save() only sees fresh files.
*
* @param session the Maven session
* @param project the Maven project
* @throws IOException if file operations fail
*/
void stagePreExistingArtifacts(MavenSession session, MavenProject project) throws IOException;

/**
* Restore staged artifacts after save() completes.
* Files that were rebuilt are discarded; files that weren't rebuilt are restored.
*
* @param session the Maven session
* @param project the Maven project
*/
void restoreStagedArtifacts(MavenSession session, MavenProject project);
}
Loading