Skip to content

Commit 8e3dad7

Browse files
authored
[MBUILDCACHE-90] Mandatory clean option to store in cache (#103)
1 parent 48ca87c commit 8e3dad7

File tree

14 files changed

+422
-0
lines changed

14 files changed

+422
-0
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,11 @@ public void execute(
150150
if (cacheState == INITIALIZED && (!result.isSuccess() || !restored)) {
151151
if (cacheConfig.isSkipSave()) {
152152
LOGGER.info("Cache saving is disabled.");
153+
} else if (cacheConfig.isMandatoryClean()
154+
&& lifecyclePhasesHelper
155+
.getCleanSegment(project, mojoExecutions)
156+
.isEmpty()) {
157+
LOGGER.info("Cache storing is skipped since there was no \"clean\" phase.");
153158
} else {
154159
final Map<String, MojoExecutionEvent> executionEvents = mojoListener.getProjectExecutions(project);
155160
cacheController.save(result, mojoExecutions, executionEvents);

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,4 +139,9 @@ public interface CacheConfig {
139139
* Flag to disable cache saving
140140
*/
141141
boolean isSkipSave();
142+
143+
/**
144+
* Flag to save in cache only if a build went through the clean lifecycle
145+
*/
146+
boolean isMandatoryClean();
142147
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ public class CacheConfigImpl implements org.apache.maven.buildcache.xml.CacheCon
9292
public static final String LAZY_RESTORE_PROPERTY_NAME = "maven.build.cache.lazyRestore";
9393
public static final String RESTORE_GENERATED_SOURCES_PROPERTY_NAME = "maven.build.cache.restoreGeneratedSources";
9494
public static final String ALWAYS_RUN_PLUGINS = "maven.build.cache.alwaysRunPlugins";
95+
public static final String MANDATORY_CLEAN = "maven.build.cache.mandatoryClean";
9596

9697
/**
9798
* Flag to control if we should skip lookup for cached artifacts globally or for a particular project even if
@@ -513,6 +514,11 @@ public boolean isSkipSave() {
513514
return getProperty(SKIP_SAVE, false);
514515
}
515516

517+
@Override
518+
public boolean isMandatoryClean() {
519+
return getProperty(MANDATORY_CLEAN, getConfiguration().isMandatoryClean());
520+
}
521+
516522
@Override
517523
public String getId() {
518524
checkInitializedState();

src/main/mdo/build-cache-config.mdo

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,12 @@ under the License.
194194
TODO: not implemented
195195
</description>
196196
</field>
197+
<field>
198+
<name>mandatoryClean</name>
199+
<type>boolean</type>
200+
<defaultValue>false</defaultValue>
201+
<description>Enable the cache storing ability only if a build went through the clean lifecycle.</description>
202+
</field>
197203
<field>
198204
<name>multiModule</name>
199205
<association>

src/site/markdown/parameters.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ This document contains various configuration parameters supported by the cache e
3737
| `-Dmaven.build.cache.alwaysRunPlugins=<list of plugins>` | Comma separated list of plugins to always run regardless of cache state. An example: `maven-deploy-plugin:*,maven-install-plugin:install` | Remote cache setup/tuning/troubleshooting |
3838
| `-Dmaven.build.cache.skipCache=(true/false)` | Skip looking up artifacts in caches. Does not affect writing artifacts to caches, disables only reading when set to `true` | May be used to trigger a forced rebuild when matching artifacts do exist in caches |
3939
| `-Dmaven.build.cache.skipSave=(true/false)` | Skip writing build result in caches. Does not affect reading from the cache. | Configuring MR builds to benefits from the cache, but restricting writes to the `master` branch |
40+
| `-Dmaven.build.cache.mandatoryClean=(true/false)` | Enable or disable the necessity to execute the `clean` phase in order to store the build result in cache | Reducing the risk to save "wrong" files in cache in a local dev environnement |
4041

4142
### Project-level properties
4243

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
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.util.Arrays;
25+
import java.util.List;
26+
27+
import org.apache.maven.buildcache.its.junit.IntegrationTest;
28+
import org.apache.maven.buildcache.util.LogFileUtils;
29+
import org.apache.maven.buildcache.xml.CacheConfigImpl;
30+
import org.apache.maven.it.VerificationException;
31+
import org.apache.maven.it.Verifier;
32+
import org.junit.jupiter.api.Assertions;
33+
import org.junit.jupiter.api.Test;
34+
35+
import static org.apache.maven.buildcache.xml.CacheConfigImpl.CACHE_LOCATION_PROPERTY_NAME;
36+
import static org.junit.jupiter.api.Assertions.assertThrows;
37+
38+
/**
39+
* Test the "mandatoryClean" parameter : saving in cache should be done only if a clean phase has been executed.
40+
*/
41+
@IntegrationTest("src/test/projects/mandatory-clean")
42+
public class MandatoryCleanTest {
43+
44+
private static final String MODULE_NAME_1 = "org.apache.maven.caching.test.simple:non-forked-module";
45+
private static final String MODULE_NAME_2 = "org.apache.maven.caching.test.simple:forked-module";
46+
private static final String CACHE_BUILD_LOG = "Found cached build, restoring %s from cache";
47+
48+
@Test
49+
void simple(Verifier verifier) throws VerificationException, IOException {
50+
51+
verifier.setAutoclean(false);
52+
Path tempDirectory = Files.createTempDirectory("simple-mandatory-clean");
53+
verifier.getCliOptions().clear();
54+
verifier.addCliOption("-D" + CACHE_LOCATION_PROPERTY_NAME + "=" + tempDirectory.toAbsolutePath());
55+
56+
verifier.setLogFileName("../log-1.txt");
57+
verifier.executeGoal("verify");
58+
verifier.verifyErrorFreeLog();
59+
List<String> cacheSkippedBuild1 = LogFileUtils.findLinesContainingTextsInLogs(
60+
verifier, "Cache storing is skipped since there was no \"clean\" phase.");
61+
Assertions.assertEquals(2, cacheSkippedBuild1.size(), "Expected 2 skipped module caching");
62+
63+
assertThrows(
64+
VerificationException.class,
65+
() -> verifier.verifyTextInLog(String.format(CACHE_BUILD_LOG, MODULE_NAME_1)),
66+
"not expected to be loaded from the cache");
67+
assertThrows(
68+
VerificationException.class,
69+
() -> verifier.verifyTextInLog(String.format(CACHE_BUILD_LOG, MODULE_NAME_2)),
70+
"not expected to be loaded from the cache");
71+
72+
verifier.setLogFileName("../log-2.txt");
73+
verifier.executeGoals(Arrays.asList("clean", "verify"));
74+
verifier.verifyErrorFreeLog();
75+
List<String> cacheSkippedBuild2 = LogFileUtils.findLinesContainingTextsInLogs(
76+
verifier, "Cache storing is skipped since there was no \"clean\" phase.");
77+
Assertions.assertEquals(0, cacheSkippedBuild2.size(), "Expected 2 skipped module caching");
78+
assertThrows(
79+
VerificationException.class,
80+
() -> verifier.verifyTextInLog(String.format(CACHE_BUILD_LOG, MODULE_NAME_1)),
81+
"not expected to be loaded from the cache");
82+
assertThrows(
83+
VerificationException.class,
84+
() -> verifier.verifyTextInLog(String.format(CACHE_BUILD_LOG, MODULE_NAME_2)),
85+
"not expected to be loaded from the cache");
86+
87+
verifier.setLogFileName("../log-3.txt");
88+
verifier.executeGoal("verify");
89+
verifier.verifyErrorFreeLog();
90+
List<String> cacheSkippedBuild3 = LogFileUtils.findLinesContainingTextsInLogs(
91+
verifier, "Cache storing is skipped since there was no \"clean\" phase.");
92+
Assertions.assertEquals(0, cacheSkippedBuild3.size(), "loading from cache, no more caching required");
93+
// Expect to find and restore cached project
94+
verifier.verifyTextInLog(String.format(CACHE_BUILD_LOG, MODULE_NAME_1));
95+
verifier.verifyTextInLog(String.format(CACHE_BUILD_LOG, MODULE_NAME_2));
96+
}
97+
98+
@Test
99+
void disabledViaProperty(Verifier verifier) throws VerificationException {
100+
101+
verifier.setAutoclean(false);
102+
103+
verifier.setLogFileName("../log-1.txt");
104+
verifier.executeGoal("verify");
105+
verifier.verifyErrorFreeLog();
106+
List<String> cacheSkippedBuild1 = LogFileUtils.findLinesContainingTextsInLogs(
107+
verifier, "Cache storing is skipped since there was no \"clean\" phase.");
108+
Assertions.assertEquals(2, cacheSkippedBuild1.size(), "Expected 2 skipped module caching");
109+
110+
assertThrows(
111+
VerificationException.class,
112+
() -> verifier.verifyTextInLog(String.format(CACHE_BUILD_LOG, MODULE_NAME_1)),
113+
"not expected to be loaded from the cache");
114+
assertThrows(
115+
VerificationException.class,
116+
() -> verifier.verifyTextInLog(String.format(CACHE_BUILD_LOG, MODULE_NAME_2)),
117+
"not expected to be loaded from the cache");
118+
119+
verifier.setLogFileName("../log-2.txt");
120+
verifier.getCliOptions().clear();
121+
// With "true", we do not change the initially expected behaviour
122+
verifier.addCliOption("-D" + CacheConfigImpl.MANDATORY_CLEAN + "=true");
123+
verifier.executeGoal("verify");
124+
verifier.verifyErrorFreeLog();
125+
List<String> cacheSkippedBuild2 = LogFileUtils.findLinesContainingTextsInLogs(
126+
verifier, "Cache storing is skipped since there was no \"clean\" phase.");
127+
Assertions.assertEquals(2, cacheSkippedBuild2.size(), "Expected 2 skipped module caching");
128+
129+
assertThrows(
130+
VerificationException.class,
131+
() -> verifier.verifyTextInLog(String.format(CACHE_BUILD_LOG, MODULE_NAME_1)),
132+
"not expected to be loaded from the cache");
133+
assertThrows(
134+
VerificationException.class,
135+
() -> verifier.verifyTextInLog(String.format(CACHE_BUILD_LOG, MODULE_NAME_2)),
136+
"not expected to be loaded from the cache");
137+
138+
// With "false", we remove the need for the clean phase
139+
verifier.getCliOptions().clear();
140+
verifier.addCliOption("-D" + CacheConfigImpl.MANDATORY_CLEAN + "=false");
141+
verifier.setLogFileName("../log-3.txt");
142+
verifier.executeGoal("verify");
143+
verifier.verifyErrorFreeLog();
144+
List<String> cacheSkippedBuild3 = LogFileUtils.findLinesContainingTextsInLogs(
145+
verifier, "Cache storing is skipped since there was no \"clean\" phase.");
146+
Assertions.assertEquals(0, cacheSkippedBuild3.size(), "Expected 2 skipped module caching");
147+
assertThrows(
148+
VerificationException.class,
149+
() -> verifier.verifyTextInLog(String.format(CACHE_BUILD_LOG, MODULE_NAME_1)),
150+
"not expected to be loaded from the cache");
151+
assertThrows(
152+
VerificationException.class,
153+
() -> verifier.verifyTextInLog(String.format(CACHE_BUILD_LOG, MODULE_NAME_2)),
154+
"not expected to be loaded from the cache");
155+
}
156+
}

src/test/java/org/apache/maven/buildcache/util/LogFileUtils.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.Arrays;
2222
import java.util.Iterator;
2323
import java.util.List;
24+
import java.util.stream.Collectors;
2425

2526
import org.apache.maven.it.VerificationException;
2627
import org.apache.maven.it.Verifier;
@@ -60,4 +61,20 @@ public static String findFirstLineContainingTextsInLogs(final Verifier verifier,
6061

6162
return null;
6263
}
64+
65+
/**
66+
* Find lines matching all the strings given as parameter in the log file attached to a verifier
67+
* @param verifier the maven verifier instance
68+
* @param texts all the matching strings to find
69+
* @return a list of matching strings
70+
* @throws VerificationException
71+
*/
72+
public static List<String> findLinesContainingTextsInLogs(final Verifier verifier, final String... texts)
73+
throws VerificationException {
74+
List<String> lines = verifier.loadFile(verifier.getBasedir(), verifier.getLogFileName(), false);
75+
return lines.stream()
76+
.map(s -> verifier.stripAnsi(s))
77+
.filter(s -> Arrays.stream(texts).allMatch(text -> s.contains(text)))
78+
.collect(Collectors.toList());
79+
}
6380
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
4+
Copyright 2021 the original author or authors.
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
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, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
18+
-->
19+
<extensions>
20+
<extension>
21+
<groupId>org.apache.maven.extensions</groupId>
22+
<artifactId>maven-build-cache-extension</artifactId>
23+
<version>${projectVersion}</version>
24+
</extension>
25+
</extensions>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
3+
<!--
4+
Licensed to the Apache Software Foundation (ASF) under one
5+
or more contributor license agreements. See the NOTICE file
6+
distributed with this work for additional information
7+
regarding copyright ownership. The ASF licenses this file
8+
to you under the Apache License, Version 2.0 (the
9+
"License"); you may not use this file except in compliance
10+
with the License. You may obtain a copy of the License at
11+
12+
http://www.apache.org/licenses/LICENSE-2.0
13+
14+
Unless required by applicable law or agreed to in writing,
15+
software distributed under the License is distributed on an
16+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
KIND, either express or implied. See the License for the
18+
specific language governing permissions and limitations
19+
under the License.
20+
-->
21+
22+
<cache xmlns="http://maven.apache.org/CACHE-CONFIG/1.0.0">
23+
24+
<configuration>
25+
<mandatoryClean>true</mandatoryClean>
26+
</configuration>
27+
28+
</cache>
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<!--
2+
3+
Copyright 2021 the original author or authors.
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
17+
-->
18+
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
19+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
20+
21+
<parent>
22+
<groupId>org.apache.maven.caching.test.simple</groupId>
23+
<artifactId>parent-multi-module</artifactId>
24+
<version>0.0.1-SNAPSHOT</version>
25+
</parent>
26+
<modelVersion>4.0.0</modelVersion>
27+
<groupId>org.apache.maven.caching.test.simple</groupId>
28+
<artifactId>forked-module</artifactId>
29+
<version>0.0.1-SNAPSHOT</version>
30+
<packaging>jar</packaging>
31+
32+
<build>
33+
<plugins>
34+
<plugin>
35+
<groupId>org.apache.maven.plugins</groupId>
36+
<artifactId>maven-pmd-plugin</artifactId>
37+
<version>3.19.0</version>
38+
<executions>
39+
<execution>
40+
<goals>
41+
<goal>check</goal>
42+
<goal>pmd</goal>
43+
</goals>
44+
</execution>
45+
</executions>
46+
<dependencies>
47+
<dependency>
48+
<groupId>org.apache.maven.wagon</groupId>
49+
<artifactId>wagon-http</artifactId>
50+
<version>3.5.2</version>
51+
</dependency>
52+
</dependencies>
53+
</plugin>
54+
</plugins>
55+
</build>
56+
</project>

0 commit comments

Comments
 (0)