diff --git a/pom.xml b/pom.xml
index bca56f5d..817008a3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -345,7 +345,7 @@ under the License.
src/main/mdo/build-cache-diff.mdo
src/main/mdo/build-cache-report.mdo
- 1.2.0
+ 1.3.0
diff --git a/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java b/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java
index 863d3e41..d8222f70 100644
--- a/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java
+++ b/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java
@@ -22,14 +22,12 @@
import javax.inject.Inject;
import javax.inject.Named;
-import java.io.File;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
-import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.Strings;
import org.apache.maven.SessionScoped;
import org.apache.maven.buildcache.artifact.ArtifactRestorationReport;
@@ -333,7 +331,8 @@ private boolean verifyCacheConsistency(
final CompletedExecution completedExecution = cachedBuild.findMojoExecutionInfo(cacheCandidate);
final String fullGoalName = cacheCandidate.getMojoDescriptor().getFullGoalName();
- if (completedExecution != null && !isParamsMatched(project, cacheCandidate, mojo, completedExecution)) {
+ if (completedExecution != null
+ && !isParamsMatched(project, session, cacheCandidate, mojo, completedExecution)) {
LOGGER.info(
"Mojo cached parameters mismatch with actual, forcing full project build. Mojo: {}",
fullGoalName);
@@ -367,7 +366,11 @@ private boolean verifyCacheConsistency(
}
boolean isParamsMatched(
- MavenProject project, MojoExecution mojoExecution, Mojo mojo, CompletedExecution completedExecution) {
+ MavenProject project,
+ MavenSession session,
+ MojoExecution mojoExecution,
+ Mojo mojo,
+ CompletedExecution completedExecution) {
List tracked = cacheConfig.getTrackedProperties(mojoExecution);
for (TrackedProperty trackedProperty : tracked) {
@@ -380,20 +383,14 @@ boolean isParamsMatched(
final String currentValue;
try {
- Object value = ReflectionUtils.getValueIncludingSuperclasses(propertyName, mojo);
-
- if (value instanceof File) {
- Path baseDirPath = project.getBasedir().toPath();
- Path path = ((File) value).toPath();
- currentValue = normalizedPath(path, baseDirPath);
- } else if (value instanceof Path) {
- Path baseDirPath = project.getBasedir().toPath();
- currentValue = normalizedPath(((Path) value), baseDirPath);
- } else if (value != null && value.getClass().isArray()) {
- currentValue = ArrayUtils.toString(value);
+ Object value;
+ if (trackedProperty.getExpression() != null) {
+ value = DtoUtils.interpolateExpression(trackedProperty.getExpression(), session, mojoExecution);
} else {
- currentValue = String.valueOf(value);
+ value = ReflectionUtils.getValueIncludingSuperclasses(propertyName, mojo);
}
+ Path baseDirPath = project.getBasedir().toPath();
+ currentValue = DtoUtils.normalizeValue(value, baseDirPath);
} catch (IllegalAccessException e) {
LOGGER.error("Cannot extract plugin property {} from mojo {}", propertyName, mojo, e);
return false;
@@ -419,33 +416,6 @@ boolean isParamsMatched(
return true;
}
- /**
- * Best effort to normalize paths from Mojo fields.
- * - all absolute paths under project root to be relativized for portability
- * - redundant '..' and '.' to be removed to have consistent views on all paths
- * - all relative paths are considered portable and should not be touched
- * - absolute paths outside of project directory could not be deterministically
- * relativized and not touched
- */
- private static String normalizedPath(Path path, Path baseDirPath) {
- boolean isProjectSubdir = path.isAbsolute() && path.startsWith(baseDirPath);
- if (LOGGER.isDebugEnabled()) {
- LOGGER.debug(
- "normalizedPath isProjectSubdir {} path '{}' - baseDirPath '{}', path.isAbsolute() {}, path.startsWith(baseDirPath) {}",
- isProjectSubdir,
- path,
- baseDirPath,
- path.isAbsolute(),
- path.startsWith(baseDirPath));
- }
- Path preparedPath = isProjectSubdir ? baseDirPath.relativize(path) : path;
- String normalizedPath = preparedPath.normalize().toString();
- if (LOGGER.isDebugEnabled()) {
- LOGGER.debug("normalizedPath '{}' - {} return {}", path, baseDirPath, normalizedPath);
- }
- return normalizedPath;
- }
-
private enum CacheRestorationStatus {
SUCCESS,
FAILURE,
diff --git a/src/main/java/org/apache/maven/buildcache/CacheControllerImpl.java b/src/main/java/org/apache/maven/buildcache/CacheControllerImpl.java
index 6dfee156..e0e72c2e 100644
--- a/src/main/java/org/apache/maven/buildcache/CacheControllerImpl.java
+++ b/src/main/java/org/apache/maven/buildcache/CacheControllerImpl.java
@@ -685,7 +685,7 @@ private void recordMojoProperties(CompletedExecution execution, MojoExecutionEve
final Object mojo = executionEvent.getMojo();
final File baseDir = executionEvent.getProject().getBasedir();
- final String baseDirPath = FilenameUtils.normalizeNoEndSeparator(baseDir.getAbsolutePath()) + File.separator;
+ final Path baseDirPath = baseDir.toPath();
final List parameters = mojoExecution.getMojoDescriptor().getParameters();
for (Parameter parameter : parameters) {
@@ -730,6 +730,19 @@ private void recordMojoProperties(CompletedExecution execution, MojoExecutionEve
}
}
}
+ // add properties with expressions
+ for (TrackedProperty trackedProperty : trackedProperties) {
+ if (trackedProperty.getExpression() != null) {
+ String propertyName = trackedProperty.getPropertyName();
+ if (!isExcluded(propertyName, logAll, noLogProperties, forceLogProperties)) {
+ Object value = DtoUtils.interpolateExpression(
+ trackedProperty.getExpression(),
+ executionEvent.getSession(),
+ executionEvent.getExecution());
+ DtoUtils.addProperty(execution, propertyName, value, baseDirPath, true);
+ }
+ }
+ }
}
private static Method getGetter(String fieldName, Class> clazz) {
diff --git a/src/main/java/org/apache/maven/buildcache/xml/DtoUtils.java b/src/main/java/org/apache/maven/buildcache/xml/DtoUtils.java
index 3c8b3a7c..e3904c31 100644
--- a/src/main/java/org/apache/maven/buildcache/xml/DtoUtils.java
+++ b/src/main/java/org/apache/maven/buildcache/xml/DtoUtils.java
@@ -20,6 +20,9 @@
import javax.annotation.Nonnull;
+import java.io.File;
+import java.nio.file.FileSystems;
+import java.nio.file.Path;
import java.util.List;
import org.apache.commons.lang3.ArrayUtils;
@@ -30,7 +33,10 @@
import org.apache.maven.buildcache.xml.build.DigestItem;
import org.apache.maven.buildcache.xml.build.PropertyValue;
import org.apache.maven.buildcache.xml.config.TrackedProperty;
+import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Dependency;
+import org.apache.maven.plugin.MojoExecution;
+import org.apache.maven.plugin.PluginParameterExpressionEvaluator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -108,15 +114,21 @@ public static Dependency createDependency(Artifact artifact) {
return dependency;
}
+ /**
+ * @deprecated use {@link #addProperty(CompletedExecution, String, Object, Path, boolean)}
+ */
+ @Deprecated
public static void addProperty(
CompletedExecution execution, String propertyName, Object value, String baseDirPath, boolean tracked) {
+ addProperty(execution, propertyName, value, FileSystems.getDefault().getPath(baseDirPath), tracked);
+ }
+
+ public static void addProperty(
+ CompletedExecution execution, String propertyName, Object value, Path baseDirPath, boolean tracked) {
final PropertyValue valueType = new PropertyValue();
valueType.setName(propertyName);
- if (value != null && value.getClass().isArray()) {
- value = ArrayUtils.toString(value);
- }
- final String valueText = String.valueOf(value);
- valueType.setValue(Strings.CS.remove(valueText, baseDirPath));
+ final String valueText = normalizeValue(value, baseDirPath);
+ valueType.setValue(valueText);
valueType.setTracked(tracked);
execution.addProperty(valueType);
}
@@ -173,4 +185,57 @@ public static Artifact copy(Artifact artifact) {
copy.setFileSize(artifact.getFileSize());
return copy;
}
+
+ public static String normalizeValue(Object value, Path baseDirPath) {
+ final String currentValue;
+ if (value instanceof File) {
+ Path path = ((File) value).toPath();
+ currentValue = normalizedPath(path, baseDirPath);
+ } else if (value instanceof Path) {
+ currentValue = normalizedPath(((Path) value), baseDirPath);
+ } else if (value != null && value.getClass().isArray()) {
+ currentValue = ArrayUtils.toString(value);
+ } else {
+ currentValue = String.valueOf(value);
+ }
+ return currentValue;
+ }
+
+ /**
+ * Best effort to normalize paths from Mojo fields.
+ * - all absolute paths under project root to be relativized for portability
+ * - redundant '..' and '.' to be removed to have consistent views on all paths
+ * - all relative paths are considered portable and should not be touched
+ * - absolute paths outside of project directory could not be deterministically
+ * relativized and not touched
+ */
+ private static String normalizedPath(Path path, Path baseDirPath) {
+ boolean isProjectSubdir = path.isAbsolute() && path.startsWith(baseDirPath);
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug(
+ "normalizedPath isProjectSubdir {} path '{}' - baseDirPath '{}', path.isAbsolute() {},"
+ + " path.startsWith(baseDirPath) {}",
+ isProjectSubdir,
+ path,
+ baseDirPath,
+ path.isAbsolute(),
+ path.startsWith(baseDirPath));
+ }
+ Path preparedPath = isProjectSubdir ? baseDirPath.relativize(path) : path;
+ String normalizedPath = preparedPath.normalize().toString();
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("normalizedPath '{}' - {} return {}", path, baseDirPath, normalizedPath);
+ }
+ return normalizedPath;
+ }
+
+ public static Object interpolateExpression(String expression, MavenSession session, MojoExecution execution) {
+ try {
+ PluginParameterExpressionEvaluator evaluator = new PluginParameterExpressionEvaluator(session, execution);
+ return evaluator.evaluate(expression);
+ } catch (Exception e) {
+ LOGGER.warn("Cannot interpolate expression '{}': {}", expression, e.getMessage(), e);
+ return expression; // return the expression as is if interpolation fails
+ }
+ }
}
diff --git a/src/main/mdo/build-cache-config.mdo b/src/main/mdo/build-cache-config.mdo
index d060dbcb..617dcb9c 100644
--- a/src/main/mdo/build-cache-config.mdo
+++ b/src/main/mdo/build-cache-config.mdo
@@ -1441,6 +1441,13 @@ under the License.
+
+
+
+ Interpolated expression to use as cached value.
+
+
+
@@ -1465,6 +1472,11 @@ under the License.
defaultValue
String
+
+ expression
+ String
+ Interpolated expression to use as cached value.
+
@@ -1497,3 +1509,4 @@ under the License.
-->
+
diff --git a/src/site/resources/maven-build-cache-config.xml b/src/site/resources/maven-build-cache-config.xml
index 1fb0ab97..8cbc37e7 100644
--- a/src/site/resources/maven-build-cache-config.xml
+++ b/src/site/resources/maven-build-cache-config.xml
@@ -15,8 +15,8 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
+
+
+
+
diff --git a/src/test/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategyTest.java b/src/test/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategyTest.java
index 976a3f2a..839e74c5 100644
--- a/src/test/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategyTest.java
+++ b/src/test/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategyTest.java
@@ -30,6 +30,7 @@
import org.apache.maven.buildcache.xml.build.CompletedExecution;
import org.apache.maven.buildcache.xml.build.PropertyValue;
import org.apache.maven.buildcache.xml.config.TrackedProperty;
+import org.apache.maven.execution.MavenSession;
import org.apache.maven.execution.scope.internal.MojoExecutionScope;
import org.apache.maven.plugin.MavenPluginManager;
import org.apache.maven.plugin.MojoExecution;
@@ -54,6 +55,7 @@ class ParametersMatchingTest {
private MojoExecution executionMock;
private CompletedExecution cacheRecordMock;
private CacheConfig cacheConfigMock;
+ private MavenSession sessionMock;
@BeforeEach
void setUp() {
@@ -69,6 +71,7 @@ void setUp() {
projectMock = mock(MavenProject.class);
executionMock = mock(MojoExecution.class);
cacheRecordMock = mock(CompletedExecution.class);
+ sessionMock = mock(MavenSession.class);
}
@Test
@@ -106,7 +109,7 @@ void testBasicParamsMatching() {
Arrays.asList("a", "b", "c"),
new String[] {"c", "d", "e"});
- assertTrue(strategy.isParamsMatched(projectMock, executionMock, testMojo, cacheRecordMock));
+ assertTrue(strategy.isParamsMatched(projectMock, sessionMock, executionMock, testMojo, cacheRecordMock));
}
@Test
@@ -133,7 +136,7 @@ void testSkipValue() {
testMojo.setAnyObject("true");
assertTrue(
- strategy.isParamsMatched(projectMock, executionMock, testMojo, cacheRecordMock),
+ strategy.isParamsMatched(projectMock, sessionMock, executionMock, testMojo, cacheRecordMock),
"If property set to 'skipValue' mismatch could be ignored because cached build"
+ " is more complete than requested build");
}
@@ -162,7 +165,7 @@ void testDefaultValue() {
testMojo.setAnyObject("defaultValue");
assertTrue(
- strategy.isParamsMatched(projectMock, executionMock, testMojo, cacheRecordMock),
+ strategy.isParamsMatched(projectMock, sessionMock, executionMock, testMojo, cacheRecordMock),
"If property has defaultValue it must be matched even if cache record has no this field");
}
@@ -188,7 +191,7 @@ void testMismatch() {
TestMojo testMojo = new TestMojo();
testMojo.setAnyObject("2");
- assertFalse(strategy.isParamsMatched(projectMock, executionMock, testMojo, cacheRecordMock));
+ assertFalse(strategy.isParamsMatched(projectMock, sessionMock, executionMock, testMojo, cacheRecordMock));
}
@NotNull
diff --git a/src/test/java/org/apache/maven/buildcache/its/ReconcileExpressionsTest.java b/src/test/java/org/apache/maven/buildcache/its/ReconcileExpressionsTest.java
new file mode 100644
index 00000000..1ece1448
--- /dev/null
+++ b/src/test/java/org/apache/maven/buildcache/its/ReconcileExpressionsTest.java
@@ -0,0 +1,249 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.buildcache.its;
+
+import java.nio.file.Paths;
+import java.util.List;
+
+import org.apache.maven.buildcache.its.junit.BeforeEach;
+import org.apache.maven.buildcache.its.junit.IntegrationTest;
+import org.apache.maven.buildcache.its.junit.IntegrationTestExtension;
+import org.apache.maven.it.VerificationException;
+import org.apache.maven.it.Verifier;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Integration tests for the reconcile expression feature.
+ * This feature allows plugin execution cache to depend on arbitrary Maven expressions
+ * such as ${project.version}, ${project.groupId}, or any custom property.
+ */
+@IntegrationTest("src/test/projects/reconcile-expressions")
+class ReconcileExpressionsTest {
+
+ private static final String PROJECT_NAME = "org.apache.maven.caching.test.reconcile:reconcile-expressions";
+ private static final String RESTORED_MESSAGE = "Found cached build, restoring " + PROJECT_NAME + " from cache";
+
+ // start every test case with clear cache
+ @BeforeEach
+ void setUp() {
+ IntegrationTestExtension.deleteDir(Paths.get("target/build-cache/"));
+ }
+ /**
+ * Test basic expression reconciliation - cache should be restored when expressions match.
+ */
+ @Test
+ void cacheRestoredWhenExpressionsMatch(Verifier verifier) throws VerificationException {
+ verifier.setAutoclean(false);
+
+ // First build - should create cache
+ verifier.setLogFileName("../log-1.txt");
+ verifier.executeGoal("compile");
+ verifier.verifyErrorFreeLog();
+ verifyTextNotInLog(verifier, RESTORED_MESSAGE);
+
+ // Second build with same expressions - should restore from cache
+ verifier.setLogFileName("../log-2.txt");
+ verifier.executeGoal("compile");
+ verifier.verifyErrorFreeLog();
+ verifier.verifyTextInLog(RESTORED_MESSAGE);
+ }
+
+ /**
+ * Test that changing project.version invalidates cache through expression reconciliation.
+ */
+ @Test
+ void cacheInvalidatedWhenVersionChanges(Verifier verifier) throws VerificationException {
+ verifier.setAutoclean(false);
+
+ // First build with initial version
+ verifier.setLogFileName("../log-version-1.txt");
+ verifier.executeGoal("compile");
+ verifier.verifyErrorFreeLog();
+ verifyTextNotInLog(verifier, RESTORED_MESSAGE);
+
+ // Second build - should restore from cache
+ verifier.setLogFileName("../log-version-2.txt");
+ verifier.executeGoal("compile");
+ verifier.verifyErrorFreeLog();
+ verifier.verifyTextInLog(RESTORED_MESSAGE);
+
+ // Change version using versions-maven-plugin
+ verifier.setLogFileName("../log-version-set.txt");
+ verifier.getCliOptions().clear();
+ verifier.addCliOption("-DoldVersion=1.0.0-SNAPSHOT");
+ verifier.addCliOption("-DnewVersion=2.0.0-SNAPSHOT");
+ verifier.addCliOption("-DgenerateBackupPoms=false");
+ verifier.executeGoal("versions:set");
+ verifier.verifyErrorFreeLog();
+
+ // Third build with changed version - cache should be invalidated
+ verifier.getCliOptions().clear();
+ verifier.setLogFileName("../log-version-3.txt");
+ verifier.executeGoal("compile");
+ verifier.verifyErrorFreeLog();
+ // The project.version expression changed, so mojo parameters don't match
+ verifier.verifyTextInLog("Plugin parameter mismatch found");
+
+ // Restore original version for other tests
+ verifier.setLogFileName("../log-version-restore.txt");
+ verifier.addCliOption("-DoldVersion=2.0.0-SNAPSHOT");
+ verifier.addCliOption("-DnewVersion=1.0.0-SNAPSHOT");
+ verifier.addCliOption("-DgenerateBackupPoms=false");
+ verifier.executeGoal("versions:set");
+ verifier.verifyErrorFreeLog();
+ }
+
+ /**
+ * Test that changing a custom property invalidates cache through expression reconciliation.
+ */
+ @Test
+ void cacheInvalidatedWhenCustomPropertyChanges(Verifier verifier) throws VerificationException {
+ verifier.setAutoclean(false);
+
+ // First build with default product.name
+ verifier.setLogFileName("../log-prop-1.txt");
+ verifier.executeGoal("process-resources");
+ verifier.verifyErrorFreeLog();
+ verifyTextNotInLog(verifier, RESTORED_MESSAGE);
+
+ // Second build with same property - should restore from cache
+ verifier.setLogFileName("../log-prop-2.txt");
+ verifier.executeGoal("process-resources");
+ verifier.verifyErrorFreeLog();
+ verifier.verifyTextInLog(RESTORED_MESSAGE);
+
+ // Third build with different property value via command line
+ verifier.setLogFileName("../log-prop-3.txt");
+ verifier.getCliOptions().clear();
+ verifier.addCliOption("-Dproduct.name=DifferentProduct");
+ verifier.executeGoal("process-resources");
+ verifier.verifyErrorFreeLog();
+ // The property changed, so mojo parameters don't match
+ verifier.verifyTextInLog("Plugin parameter mismatch found");
+ }
+
+ /**
+ * Test that multiple expressions are all evaluated correctly.
+ */
+ @Test
+ void multipleExpressionsEvaluated(Verifier verifier) throws VerificationException {
+ verifier.setAutoclean(false);
+ verifier.setMavenDebug(true);
+
+ // Build with debug to see expression evaluation
+ verifier.setLogFileName("../log-multi-1.txt");
+ verifier.executeGoal("compile");
+ verifier.verifyErrorFreeLog();
+
+ // Verify that the expressions are being tracked
+ // The build info should contain the evaluated expression values
+ verifier.setLogFileName("../log-multi-2.txt");
+ verifier.executeGoal("compile");
+ verifier.verifyErrorFreeLog();
+ verifier.verifyTextInLog(RESTORED_MESSAGE);
+ }
+
+ /**
+ * Test that expressions with undefined properties are handled gracefully.
+ * When an expression references an undefined property, it should not crash
+ * and should handle the missing value appropriately.
+ */
+ @Test
+ void undefinedPropertyInExpressionHandledGracefully(Verifier verifier) throws VerificationException {
+ verifier.setAutoclean(false);
+
+ // First build - expressions with undefined properties should not cause failure
+ verifier.setLogFileName("../log-undefined-1.txt");
+ verifier.executeGoal("compile");
+ verifier.verifyErrorFreeLog();
+
+ // Second build - should still work with cache
+ verifier.setLogFileName("../log-undefined-2.txt");
+ verifier.executeGoal("compile");
+ verifier.verifyErrorFreeLog();
+ }
+
+ /**
+ * Test that expressions correctly track changes across multiple consecutive builds.
+ */
+ @Test
+ void expressionChangesTrackedAcrossBuilds(Verifier verifier) throws VerificationException {
+ verifier.setAutoclean(false);
+
+ // Build 1: with environment=development (default)
+ verifier.setLogFileName("../log-track-1.txt");
+ verifier.executeGoal("process-resources");
+ verifier.verifyErrorFreeLog();
+ verifyTextNotInLog(verifier, RESTORED_MESSAGE);
+
+ // Build 2: same environment - should use cache
+ verifier.setLogFileName("../log-track-2.txt");
+ verifier.executeGoal("process-resources");
+ verifier.verifyErrorFreeLog();
+ verifier.verifyTextInLog(RESTORED_MESSAGE);
+
+ // Build 3: change to production - should invalidate cache
+ verifier.setLogFileName("../log-track-3.txt");
+ verifier.getCliOptions().clear();
+ verifier.addCliOption("-Dbuild.environment=production");
+ verifier.executeGoal("process-resources");
+ verifier.verifyErrorFreeLog();
+ verifier.verifyTextInLog("Plugin parameter mismatch found");
+
+ // Build 4: same production environment - should use cache
+ verifier.setLogFileName("../log-track-4.txt");
+ verifier.getCliOptions().clear();
+ verifier.addCliOption("-Dbuild.environment=production");
+ verifier.executeGoal("process-resources");
+ verifier.verifyErrorFreeLog();
+ // Note: May or may not use cache depending on how the cache key is calculated
+ // The important thing is it doesn't fail
+ }
+
+ /**
+ * Test that expressions work correctly with the resources:resources goal.
+ */
+ @Test
+ void expressionsWorkWithResourcesGoal(Verifier verifier) throws VerificationException {
+ verifier.setAutoclean(false);
+
+ // First build targeting resources specifically
+ verifier.setLogFileName("../log-resources-1.txt");
+ verifier.executeGoal("process-resources");
+ verifier.verifyErrorFreeLog();
+
+ // Second build - should restore from cache
+ verifier.setLogFileName("../log-resources-2.txt");
+ verifier.executeGoal("process-resources");
+ verifier.verifyErrorFreeLog();
+ verifier.verifyTextInLog(RESTORED_MESSAGE);
+ }
+
+ /**
+ * Helper method to verify that a specific text is NOT present in the log.
+ */
+ private static void verifyTextNotInLog(Verifier verifier, String text) throws VerificationException {
+ List lines = verifier.loadFile(verifier.getBasedir(), verifier.getLogFileName(), false);
+ for (String line : lines) {
+ if (Verifier.stripAnsi(line).contains(text)) {
+ throw new VerificationException("Text found in log but should not be present: " + text);
+ }
+ }
+ }
+}
diff --git a/src/test/projects/reconcile-expressions/.mvn/extensions.xml b/src/test/projects/reconcile-expressions/.mvn/extensions.xml
new file mode 100644
index 00000000..a339a157
--- /dev/null
+++ b/src/test/projects/reconcile-expressions/.mvn/extensions.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ org.apache.maven.extensions
+ maven-build-cache-extension
+ ${projectVersion}
+
+
diff --git a/src/test/projects/reconcile-expressions/.mvn/maven-build-cache-config.xml b/src/test/projects/reconcile-expressions/.mvn/maven-build-cache-config.xml
new file mode 100644
index 00000000..d14e0559
--- /dev/null
+++ b/src/test/projects/reconcile-expressions/.mvn/maven-build-cache-config.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/projects/reconcile-expressions/pom.xml b/src/test/projects/reconcile-expressions/pom.xml
new file mode 100644
index 00000000..1d4fa088
--- /dev/null
+++ b/src/test/projects/reconcile-expressions/pom.xml
@@ -0,0 +1,68 @@
+
+
+
+ 4.0.0
+ org.apache.maven.caching.test.reconcile
+ reconcile-expressions
+ 1.0.0-SNAPSHOT
+ jar
+
+
+ 1.8
+ 1.8
+ UTF-8
+
+ TestProduct
+ development
+ 2024-01-01
+
+
+
+
+ src/main/resources
+ true
+
+
+
+
+
+
+ org.codehaus.mojo
+ versions-maven-plugin
+ 2.18.0
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+ 3.3.1
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.11.0
+
+
+
+
+
+
+
+
diff --git a/src/test/projects/reconcile-expressions/src/main/java/org/apache/maven/buildcache/Test1.java b/src/test/projects/reconcile-expressions/src/main/java/org/apache/maven/buildcache/Test1.java
new file mode 100644
index 00000000..f14c08c6
--- /dev/null
+++ b/src/test/projects/reconcile-expressions/src/main/java/org/apache/maven/buildcache/Test1.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.buildcache;
+
+/**
+ * Simple test class for reconcile-expressions integration test.
+ */
+public class Test1 {
+
+ public String getMessage() {
+ return "Hello from Test1";
+ }
+}
diff --git a/src/test/projects/reconcile-expressions/src/main/resources/resources.properties b/src/test/projects/reconcile-expressions/src/main/resources/resources.properties
new file mode 100644
index 00000000..2eb96eed
--- /dev/null
+++ b/src/test/projects/reconcile-expressions/src/main/resources/resources.properties
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+productName=@product.name@
+version=@project.version@
+env=@build.environment@
\ No newline at end of file