From 1eb321db05bc6b3b3f35489da01c75fcadeb4bc0 Mon Sep 17 00:00:00 2001 From: Patrik Dudits Date: Sat, 6 Dec 2025 09:30:44 +0100 Subject: [PATCH 1/5] [MBUILDCACHE-73] Unify property value conversions in saving and restoration --- .../BuildCacheMojosExecutionStrategy.java | 44 +-------------- .../maven/buildcache/CacheControllerImpl.java | 2 +- .../apache/maven/buildcache/xml/DtoUtils.java | 53 ++++++++++++++++--- 3 files changed, 50 insertions(+), 49 deletions(-) diff --git a/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java b/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java index 863d3e41..bc4d2ea1 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; @@ -381,19 +379,8 @@ 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); - } else { - currentValue = String.valueOf(value); - } + 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 +406,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..1f3fd3ac 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) { 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..d403748c 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,8 @@ import javax.annotation.Nonnull; +import java.io.File; +import java.nio.file.Path; import java.util.List; import org.apache.commons.lang3.ArrayUtils; @@ -109,14 +111,11 @@ public static Dependency createDependency(Artifact artifact) { } public static void addProperty( - CompletedExecution execution, String propertyName, Object value, String baseDirPath, boolean tracked) { + 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 +172,46 @@ 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; + } } From ff979c43ffabfac21e43a6a88cc41bff20fc6f8d Mon Sep 17 00:00:00 2001 From: Patrik Dudits Date: Sat, 6 Dec 2025 09:53:15 +0100 Subject: [PATCH 2/5] [MBUILDCACHE-73] Allow arbitrary expressions as additional reconcilation properties --- pom.xml | 2 +- .../BuildCacheMojosExecutionStrategy.java | 16 +++++++++++++--- .../maven/buildcache/CacheControllerImpl.java | 13 +++++++++++++ .../apache/maven/buildcache/xml/DtoUtils.java | 13 +++++++++++++ src/main/mdo/build-cache-config.mdo | 13 +++++++++++++ .../BuildCacheMojosExecutionStrategyTest.java | 11 +++++++---- 6 files changed, 60 insertions(+), 8 deletions(-) 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 bc4d2ea1..d8222f70 100644 --- a/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java +++ b/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java @@ -331,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); @@ -365,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) { @@ -378,7 +383,12 @@ boolean isParamsMatched( final String currentValue; try { - Object value = ReflectionUtils.getValueIncludingSuperclasses(propertyName, mojo); + Object value; + if (trackedProperty.getExpression() != null) { + value = DtoUtils.interpolateExpression(trackedProperty.getExpression(), session, mojoExecution); + } else { + value = ReflectionUtils.getValueIncludingSuperclasses(propertyName, mojo); + } Path baseDirPath = project.getBasedir().toPath(); currentValue = DtoUtils.normalizeValue(value, baseDirPath); } catch (IllegalAccessException e) { diff --git a/src/main/java/org/apache/maven/buildcache/CacheControllerImpl.java b/src/main/java/org/apache/maven/buildcache/CacheControllerImpl.java index 1f3fd3ac..e0e72c2e 100644 --- a/src/main/java/org/apache/maven/buildcache/CacheControllerImpl.java +++ b/src/main/java/org/apache/maven/buildcache/CacheControllerImpl.java @@ -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 d403748c..dc37ea29 100644 --- a/src/main/java/org/apache/maven/buildcache/xml/DtoUtils.java +++ b/src/main/java/org/apache/maven/buildcache/xml/DtoUtils.java @@ -32,7 +32,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; @@ -214,4 +217,14 @@ private static String normalizedPath(Path path, Path baseDirPath) { } 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/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 From 8c74428ba16a15517efec807a56df0bffd84df7a Mon Sep 17 00:00:00 2001 From: Patrik Dudits Date: Fri, 5 Dec 2025 17:46:31 +0100 Subject: [PATCH 3/5] [MBUILDCACHE-73] Integration test for reconcile expressions --- .../its/ReconcileExpressionsTest.java | 249 ++++++++++++++++++ .../reconcile-expressions/.mvn/extensions.xml | 25 ++ .../.mvn/maven-build-cache-config.xml | 53 ++++ .../projects/reconcile-expressions/pom.xml | 72 +++++ .../org/apache/maven/buildcache/Test1.java | 29 ++ .../src/main/resources/resources.properties | 3 + 6 files changed, 431 insertions(+) create mode 100644 src/test/java/org/apache/maven/buildcache/its/ReconcileExpressionsTest.java create mode 100644 src/test/projects/reconcile-expressions/.mvn/extensions.xml create mode 100644 src/test/projects/reconcile-expressions/.mvn/maven-build-cache-config.xml create mode 100644 src/test/projects/reconcile-expressions/pom.xml create mode 100644 src/test/projects/reconcile-expressions/src/main/java/org/apache/maven/buildcache/Test1.java create mode 100644 src/test/projects/reconcile-expressions/src/main/resources/resources.properties 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..8df78f8b --- /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..c9df1d52 --- /dev/null +++ b/src/test/projects/reconcile-expressions/.mvn/maven-build-cache-config.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/projects/reconcile-expressions/pom.xml b/src/test/projects/reconcile-expressions/pom.xml new file mode 100644 index 00000000..6572b261 --- /dev/null +++ b/src/test/projects/reconcile-expressions/pom.xml @@ -0,0 +1,72 @@ + + + + 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..eebaab3a --- /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"; + } +} \ No newline at end of file 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..09577e70 --- /dev/null +++ b/src/test/projects/reconcile-expressions/src/main/resources/resources.properties @@ -0,0 +1,3 @@ +productName=@product.name@ +version=@project.version@ +env=@build.environment@ \ No newline at end of file From e95fc97da3a358979bbc465c399fc9a97b50394f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Dudit=C5=A1?= Date: Sun, 7 Dec 2025 18:06:12 +0100 Subject: [PATCH 4/5] Apply suggestions from code review Co-authored-by: Erik Meuwese --- .../apache/maven/buildcache/xml/DtoUtils.java | 13 ++- .../reconcile-expressions/.mvn/extensions.xml | 10 +-- .../.mvn/maven-build-cache-config.xml | 58 ++++++------ .../projects/reconcile-expressions/pom.xml | 90 +++++++++---------- .../org/apache/maven/buildcache/Test1.java | 2 +- .../src/main/resources/resources.properties | 16 ++++ 6 files changed, 106 insertions(+), 83 deletions(-) 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 dc37ea29..e3904c31 100644 --- a/src/main/java/org/apache/maven/buildcache/xml/DtoUtils.java +++ b/src/main/java/org/apache/maven/buildcache/xml/DtoUtils.java @@ -21,6 +21,7 @@ import javax.annotation.Nonnull; import java.io.File; +import java.nio.file.FileSystems; import java.nio.file.Path; import java.util.List; @@ -113,6 +114,15 @@ 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(); @@ -203,7 +213,8 @@ 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) {}", + "normalizedPath isProjectSubdir {} path '{}' - baseDirPath '{}', path.isAbsolute() {}," + + " path.startsWith(baseDirPath) {}", isProjectSubdir, path, baseDirPath, diff --git a/src/test/projects/reconcile-expressions/.mvn/extensions.xml b/src/test/projects/reconcile-expressions/.mvn/extensions.xml index 8df78f8b..a339a157 100644 --- a/src/test/projects/reconcile-expressions/.mvn/extensions.xml +++ b/src/test/projects/reconcile-expressions/.mvn/extensions.xml @@ -17,9 +17,9 @@ --> - - org.apache.maven.extensions - maven-build-cache-extension - ${projectVersion} - + + 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 index c9df1d52..d14e0559 100644 --- a/src/test/projects/reconcile-expressions/.mvn/maven-build-cache-config.xml +++ b/src/test/projects/reconcile-expressions/.mvn/maven-build-cache-config.xml @@ -22,32 +22,32 @@ under the License. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/projects/reconcile-expressions/pom.xml b/src/test/projects/reconcile-expressions/pom.xml index 6572b261..1d4fa088 100644 --- a/src/test/projects/reconcile-expressions/pom.xml +++ b/src/test/projects/reconcile-expressions/pom.xml @@ -19,54 +19,50 @@ under the License. - 4.0.0 - org.apache.maven.caching.test.reconcile - reconcile-expressions - 1.0.0-SNAPSHOT - jar + 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 - + + 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 + + + + - - - - - 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 index eebaab3a..f14c08c6 100644 --- 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 @@ -26,4 +26,4 @@ public class Test1 { public String getMessage() { return "Hello from Test1"; } -} \ No newline at end of file +} diff --git a/src/test/projects/reconcile-expressions/src/main/resources/resources.properties b/src/test/projects/reconcile-expressions/src/main/resources/resources.properties index 09577e70..2eb96eed 100644 --- a/src/test/projects/reconcile-expressions/src/main/resources/resources.properties +++ b/src/test/projects/reconcile-expressions/src/main/resources/resources.properties @@ -1,3 +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 From 6581eee5aea777912d549228daa60f5bdcba4a2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Dudits=CC=8C?= Date: Mon, 8 Dec 2025 10:38:04 +0100 Subject: [PATCH 5/5] Add example use to cache-config template --- src/site/resources/maven-build-cache-config.xml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) 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. --> - + + + +