Skip to content

Commit e739cfa

Browse files
authored
Save attached outputs for compile-only cache entries (#394)
* Save attached outputs for compile-only cache entries
1 parent fd78767 commit e739cfa

31 files changed

Lines changed: 1616 additions & 77 deletions

File tree

src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import javax.inject.Named;
2424

2525
import java.io.File;
26+
import java.io.IOException;
2627
import java.nio.file.Path;
2728
import java.util.HashSet;
2829
import java.util.List;
@@ -142,33 +143,58 @@ public void execute(
142143

143144
boolean restorable = result.isSuccess() || result.isPartialSuccess();
144145
boolean restored = false; // if partially restored need to save increment
146+
145147
if (restorable) {
146148
CacheRestorationStatus cacheRestorationStatus =
147149
restoreProject(result, mojoExecutions, mojoExecutionRunner, cacheConfig);
148150
restored = CacheRestorationStatus.SUCCESS == cacheRestorationStatus;
149151
executeExtraCleanPhaseIfNeeded(cacheRestorationStatus, cleanPhase, mojoExecutionRunner);
150152
}
151-
if (!restored) {
152-
for (MojoExecution mojoExecution : mojoExecutions) {
153-
if (source == Source.CLI
154-
|| mojoExecution.getLifecyclePhase() == null
155-
|| lifecyclePhasesHelper.isLaterPhaseThanClean(mojoExecution.getLifecyclePhase())) {
156-
mojoExecutionRunner.run(mojoExecution);
153+
154+
try {
155+
if (!restored && !forkedExecution) {
156+
// Move pre-existing artifacts to staging directory to prevent caching stale files
157+
// from previous builds (e.g., after source changes or from cache restored
158+
// with clock skew). This ensures save() only sees fresh files built during this session.
159+
// Skip for forked executions since they don't cache and shouldn't modify artifacts.
160+
try {
161+
cacheController.stagePreExistingArtifacts(session, project);
162+
} catch (IOException e) {
163+
LOGGER.debug("Failed to stage pre-existing artifacts: {}", e.getMessage());
164+
// Continue build - if staging fails, we'll just cache what exists
157165
}
158166
}
159-
}
160167

161-
if (cacheState == INITIALIZED && (!result.isSuccess() || !restored)) {
162-
if (cacheConfig.isSkipSave()) {
163-
LOGGER.info("Cache saving is disabled.");
164-
} else if (cacheConfig.isMandatoryClean()
165-
&& lifecyclePhasesHelper
166-
.getCleanSegment(project, mojoExecutions)
167-
.isEmpty()) {
168-
LOGGER.info("Cache storing is skipped since there was no \"clean\" phase.");
169-
} else {
170-
final Map<String, MojoExecutionEvent> executionEvents = mojoListener.getProjectExecutions(project);
171-
cacheController.save(result, mojoExecutions, executionEvents);
168+
if (!restored) {
169+
for (MojoExecution mojoExecution : mojoExecutions) {
170+
if (source == Source.CLI
171+
|| mojoExecution.getLifecyclePhase() == null
172+
|| lifecyclePhasesHelper.isLaterPhaseThanClean(mojoExecution.getLifecyclePhase())) {
173+
mojoExecutionRunner.run(mojoExecution);
174+
}
175+
}
176+
}
177+
178+
if (cacheState == INITIALIZED && (!result.isSuccess() || !restored)) {
179+
if (cacheConfig.isSkipSave()) {
180+
LOGGER.debug("Cache saving is disabled.");
181+
} else if (cacheConfig.isMandatoryClean()
182+
&& lifecyclePhasesHelper
183+
.getCleanSegment(project, mojoExecutions)
184+
.isEmpty()) {
185+
LOGGER.debug("Cache storing is skipped since there was no \"clean\" phase.");
186+
} else {
187+
final Map<String, MojoExecutionEvent> executionEvents =
188+
mojoListener.getProjectExecutions(project);
189+
cacheController.save(result, mojoExecutions, executionEvents);
190+
}
191+
}
192+
} finally {
193+
// Always restore staged files after build completes (whether save ran or not).
194+
// Files that were rebuilt are discarded; files that weren't rebuilt are restored.
195+
// Skip for forked executions since they don't stage artifacts.
196+
if (!restored && !forkedExecution) {
197+
cacheController.restoreStagedArtifacts(session, project);
172198
}
173199
}
174200

src/main/java/org/apache/maven/buildcache/CacheController.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919
package org.apache.maven.buildcache;
2020

21+
import java.io.IOException;
2122
import java.util.List;
2223
import java.util.Map;
2324

@@ -45,4 +46,23 @@ void save(
4546
boolean isForcedExecution(MavenProject project, MojoExecution execution);
4647

4748
void saveCacheReport(MavenSession session);
49+
50+
/**
51+
* Move pre-existing artifacts to staging directory to prevent caching stale files.
52+
* Called before mojos run to ensure save() only sees fresh files.
53+
*
54+
* @param session the Maven session
55+
* @param project the Maven project
56+
* @throws IOException if file operations fail
57+
*/
58+
void stagePreExistingArtifacts(MavenSession session, MavenProject project) throws IOException;
59+
60+
/**
61+
* Restore staged artifacts after save() completes.
62+
* Files that were rebuilt are discarded; files that weren't rebuilt are restored.
63+
*
64+
* @param session the Maven session
65+
* @param project the Maven project
66+
*/
67+
void restoreStagedArtifacts(MavenSession session, MavenProject project);
4868
}

0 commit comments

Comments
 (0)