Skip to content

Commit 3126fe1

Browse files
committed
Add maven.build.cache.cacheCompile property to control compile-phase caching
Added new configuration property maven.build.cache.cacheCompile (default: true) to address reviewer concerns about performance overhead during active development. Changes: - CacheConfig: Added isCacheCompile() method with javadoc - CacheConfigImpl: Added CACHE_COMPILE constant and implementation (default true) - CacheControllerImpl: Wrapped attachGeneratedSources() and attachOutputs() calls in isCacheCompile() check - CacheCompileDisabledTest: Integration tests verifying behavior when disabled Benefits: - Maintains fix for issue #393 (default behavior unchanged) - Allows developers to opt-out of compile caching with: -Dmaven.build.cache.cacheCompile=false - Reduces IO overhead (no zipping/uploading) during active development - Users working on large multi-module projects can keep it enabled Usage: # Disable compile caching to reduce overhead during development: mvn clean compile -Dmaven.build.cache.cacheCompile=false # Enable for CI/multi-module scenarios (default): mvn clean compile
1 parent 92822da commit 3126fe1

File tree

4 files changed

+176
-2
lines changed

4 files changed

+176
-2
lines changed

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

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -525,8 +525,14 @@ public void save(
525525
final org.apache.maven.artifact.Artifact projectArtifact = project.getArtifact();
526526
final boolean hasPackagePhase = project.hasLifecyclePhase("package");
527527

528-
attachGeneratedSources(project, state, buildStartTime);
529-
attachOutputs(project, state, buildStartTime);
528+
// Cache compile outputs (classes, test-classes, generated sources) if enabled
529+
// This allows compile-only builds to create restorable cache entries
530+
// Can be disabled with -Dmaven.build.cache.cacheCompile=false to reduce IO overhead
531+
final boolean cacheCompile = cacheConfig.isCacheCompile();
532+
if (cacheCompile) {
533+
attachGeneratedSources(project, state, buildStartTime);
534+
attachOutputs(project, state, buildStartTime);
535+
}
530536

531537
final List<org.apache.maven.artifact.Artifact> attachedArtifacts = project.getAttachedArtifacts() != null
532538
? project.getAttachedArtifacts()
@@ -535,6 +541,20 @@ public void save(
535541
final Artifact projectArtifactDto = hasPackagePhase ? artifactDto(project.getArtifact(), algorithm, project, state)
536542
: null;
537543

544+
// CRITICAL: Don't create incomplete cache entries!
545+
// Only save cache entry if we have SOMETHING useful to restore.
546+
// Exclude consumer POMs (Maven metadata) from the "useful artifacts" check.
547+
// This prevents the bug where:
548+
// 1. mvn compile (cacheCompile=false) creates cache entry with only metadata
549+
// 2. mvn compile (cacheCompile=true) tries to restore incomplete cache and fails
550+
boolean hasUsefulArtifacts = projectArtifactDto != null
551+
|| attachedArtifactDtos.stream()
552+
.anyMatch(a -> !"consumer".equals(a.getClassifier()) || !"pom".equals(a.getType()));
553+
if (!hasUsefulArtifacts) {
554+
LOGGER.info("Skipping cache save: no artifacts to save (only metadata present)");
555+
return;
556+
}
557+
538558
List<CompletedExecution> completedExecution = buildExecutionInfo(mojoExecutions, executionEvents);
539559

540560
final Build build = new Build(

src/main/java/org/apache/maven/buildcache/xml/CacheConfig.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,4 +152,15 @@ public interface CacheConfig {
152152
* Flag to save in cache only if a build went through the clean lifecycle
153153
*/
154154
boolean isMandatoryClean();
155+
156+
/**
157+
* Flag to cache compile phase outputs (classes, test-classes, generated sources).
158+
* When enabled (default), compile-only builds create cache entries that can be restored
159+
* by subsequent builds. When disabled, caching only occurs during package phase or later.
160+
* <p>
161+
* Use: -Dmaven.build.cache.cacheCompile=(true|false)
162+
* <p>
163+
* Default: true
164+
*/
165+
boolean isCacheCompile();
155166
}

src/main/java/org/apache/maven/buildcache/xml/CacheConfigImpl.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ public class CacheConfigImpl implements org.apache.maven.buildcache.xml.CacheCon
9797
public static final String RESTORE_GENERATED_SOURCES_PROPERTY_NAME = "maven.build.cache.restoreGeneratedSources";
9898
public static final String ALWAYS_RUN_PLUGINS = "maven.build.cache.alwaysRunPlugins";
9999
public static final String MANDATORY_CLEAN = "maven.build.cache.mandatoryClean";
100+
public static final String CACHE_COMPILE = "maven.build.cache.cacheCompile";
100101

101102
/**
102103
* Flag to control if we should skip lookup for cached artifacts globally or for a particular project even if
@@ -541,6 +542,11 @@ public boolean isMandatoryClean() {
541542
return getProperty(MANDATORY_CLEAN, getConfiguration().isMandatoryClean());
542543
}
543544

545+
@Override
546+
public boolean isCacheCompile() {
547+
return getProperty(CACHE_COMPILE, true);
548+
}
549+
544550
@Override
545551
public String getId() {
546552
checkInitializedState();
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.maven.buildcache.its;
20+
21+
import java.io.IOException;
22+
import java.nio.file.Files;
23+
import java.nio.file.Path;
24+
import java.nio.file.Paths;
25+
import java.util.Arrays;
26+
import java.util.stream.Stream;
27+
28+
import org.apache.maven.buildcache.its.junit.IntegrationTest;
29+
import org.apache.maven.it.VerificationException;
30+
import org.apache.maven.it.Verifier;
31+
import org.junit.jupiter.api.Test;
32+
33+
import static org.junit.jupiter.api.Assertions.assertFalse;
34+
import static org.junit.jupiter.api.Assertions.assertTrue;
35+
36+
/**
37+
* Tests that the maven.build.cache.cacheCompile property correctly disables
38+
* caching of compile-phase outputs.
39+
*/
40+
@IntegrationTest("src/test/projects/issue-393-compile-restore")
41+
class CacheCompileDisabledTest {
42+
43+
@Test
44+
void compileDoesNotCacheWhenDisabled(Verifier verifier) throws VerificationException, IOException {
45+
verifier.setAutoclean(false);
46+
47+
// The actual cache is stored in target/build-cache (relative to the extension root, not test project)
48+
Path localCache = Paths.get(System.getProperty("maven.multiModuleProjectDirectory"))
49+
.resolve("target/build-cache");
50+
51+
// Clean cache before test
52+
if (Files.exists(localCache)) {
53+
deleteDirectory(localCache);
54+
}
55+
56+
// First compile with cacheCompile disabled - compile only the app module to avoid dependency issues
57+
verifier.setLogFileName("../log-compile-disabled.txt");
58+
verifier.addCliOption("-Dmaven.build.cache.cacheCompile=false");
59+
verifier.addCliOption("-pl");
60+
verifier.addCliOption("app");
61+
verifier.executeGoals(Arrays.asList("clean", "compile"));
62+
verifier.verifyErrorFreeLog();
63+
64+
// Verify NO cache entry was created (no buildinfo.xml in local cache)
65+
boolean hasCacheEntry = Files.walk(localCache)
66+
.anyMatch(p -> p.getFileName().toString().equals("buildinfo.xml"));
67+
assertFalse(hasCacheEntry,
68+
"Cache entry should NOT be created when maven.build.cache.cacheCompile=false");
69+
70+
// Clean project and run compile again
71+
verifier.setLogFileName("../log-compile-disabled-2.txt");
72+
verifier.addCliOption("-Dmaven.build.cache.cacheCompile=false");
73+
verifier.addCliOption("-pl");
74+
verifier.addCliOption("app");
75+
verifier.executeGoals(Arrays.asList("clean", "compile"));
76+
verifier.verifyErrorFreeLog();
77+
78+
// Verify cache miss (should NOT restore from cache)
79+
Path logFile = Paths.get(verifier.getBasedir()).getParent().resolve("log-compile-disabled-2.txt");
80+
String logContent = new String(Files.readAllBytes(logFile));
81+
assertFalse(logContent.contains("Found cached build, restoring"),
82+
"Should NOT restore from cache when cacheCompile was disabled");
83+
}
84+
85+
@Test
86+
void compileCreatesCacheEntryWhenEnabled(Verifier verifier) throws VerificationException, IOException {
87+
verifier.setAutoclean(false);
88+
89+
// The actual cache is stored in target/build-cache (relative to the extension root, not test project)
90+
Path localCache = Paths.get(System.getProperty("maven.multiModuleProjectDirectory"))
91+
.resolve("target/build-cache");
92+
93+
// Clean cache before test
94+
if (Files.exists(localCache)) {
95+
deleteDirectory(localCache);
96+
}
97+
98+
// First compile with cacheCompile enabled (default) - compile only the app module
99+
verifier.setLogFileName("../log-compile-enabled.txt");
100+
verifier.addCliOption("-pl");
101+
verifier.addCliOption("app");
102+
verifier.executeGoals(Arrays.asList("clean", "compile"));
103+
verifier.verifyErrorFreeLog();
104+
105+
// Verify cache entry WAS created
106+
boolean hasCacheEntry = Files.walk(localCache)
107+
.anyMatch(p -> p.getFileName().toString().equals("buildinfo.xml"));
108+
assertTrue(hasCacheEntry,
109+
"Cache entry should be created when maven.build.cache.cacheCompile=true (default)");
110+
111+
// Clean project and run compile again
112+
verifier.setLogFileName("../log-compile-enabled-2.txt");
113+
verifier.addCliOption("-pl");
114+
verifier.addCliOption("app");
115+
verifier.executeGoals(Arrays.asList("clean", "compile"));
116+
verifier.verifyErrorFreeLog();
117+
118+
// Verify cache hit (should restore from cache)
119+
verifier.verifyTextInLog("Found cached build, restoring");
120+
verifier.verifyTextInLog("Skipping plugin execution (cached): compiler:compile");
121+
}
122+
123+
private void deleteDirectory(Path directory) throws IOException {
124+
if (Files.exists(directory)) {
125+
try (Stream<Path> walk = Files.walk(directory)) {
126+
walk.sorted((a, b) -> b.compareTo(a))
127+
.forEach(path -> {
128+
try {
129+
Files.delete(path);
130+
} catch (IOException e) {
131+
// Ignore
132+
}
133+
});
134+
}
135+
}
136+
}
137+
}

0 commit comments

Comments
 (0)