From e99c008ef346f0920a085c64d97d21e39f493168 Mon Sep 17 00:00:00 2001 From: cowwoc Date: Sun, 5 Oct 2025 18:08:30 -0400 Subject: [PATCH 1/2] =?UTF-8?q?Add=20default=20executionControl=20reconcil?= =?UTF-8?q?iation=20for=20common=20Maven=20plugins=20##=20Problem=20Withou?= =?UTF-8?q?t=20executionControl=20configuration,=20the=20build=20cache=20d?= =?UTF-8?q?oes=20not=20track=20critical=20plugin=20properties=20that=20are?= =?UTF-8?q?=20often=20specified=20via=20command-line=20arguments=20(e.g.,?= =?UTF-8?q?=20-Dmaven.compiler.release).=20This=20leads=20to=20incorrect?= =?UTF-8?q?=20cache=20reuse=20when=20these=20properties=20change=20between?= =?UTF-8?q?=20builds.=20In=20multi-module=20JPMS=20projects,=20this=20mani?= =?UTF-8?q?fests=20as=20compilation=20failures=20or=20bytecode=20version?= =?UTF-8?q?=20mismatches:=201.=20Build=20with=20`-Dmaven.compiler.release?= =?UTF-8?q?=3D17`=20=E2=86=92=20cache=20stores=20Java=2017=20bytecode=20(m?= =?UTF-8?q?ajor=20version=2061)=202.=20Build=20with=20`-Dmaven.compiler.re?= =?UTF-8?q?lease=3D21`=20=E2=86=92=20cache=20incorrectly=20reuses=20Java?= =?UTF-8?q?=2017=20bytecode=203.=20Result:=20Bytecode=20remains=20major=20?= =?UTF-8?q?version=2061=20instead=20of=20expected=2065=20This=20is=20parti?= =?UTF-8?q?cularly=20problematic=20for=20module-info.class=20files=20which?= =?UTF-8?q?=20are=20sensitive=20to=20Java=20version.=20##=20Solution=20Imp?= =?UTF-8?q?lemented=20default=20reconciliation=20configs=20for=20common=20?= =?UTF-8?q?plugins=20when=20executionControl=20is=20not=20specified:=20-?= =?UTF-8?q?=20**maven-compiler-plugin**=20(compile=20&=20testCompile=20goa?= =?UTF-8?q?ls):=20=20=20-=20Tracks=20`source`,=20`target`,=20and=20`releas?= =?UTF-8?q?e`=20properties=20=20=20-=20Ensures=20cache=20invalidation=20wh?= =?UTF-8?q?en=20Java=20version=20changes=20-=20**maven-install-plugin**=20?= =?UTF-8?q?(install=20goal):=20=20=20-=20Tracked=20to=20ensure=20local=20r?= =?UTF-8?q?epository=20is=20updated=20when=20needed=20##=20Testing=20Verif?= =?UTF-8?q?ied=20with=20multi-module=20JPMS=20test=20project:=20**Before?= =?UTF-8?q?=20(broken):**=20```=20mvn=20clean=20verify=20-Dmaven.compiler.?= =?UTF-8?q?release=3D17=20=20#=20Caches=20Java=2017=20bytecode=20mvn=20cle?= =?UTF-8?q?an=20verify=20-Dmaven.compiler.release=3D21=20=20#=20Incorrectl?= =?UTF-8?q?y=20reuses=20Java=2017=20bytecode=20javap=20-v=20module-info.cl?= =?UTF-8?q?ass=20|=20grep=20"major=20version"=20=20#=20Shows=2061=20(wrong?= =?UTF-8?q?!)=20```=20**After=20(fixed):**=20```=20mvn=20clean=20verify=20?= =?UTF-8?q?-Dmaven.compiler.release=3D17=20=20#=20Caches=20Java=2017=20byt?= =?UTF-8?q?ecode=20mvn=20clean=20verify=20-Dmaven.compiler.release=3D21=20?= =?UTF-8?q?=20#=20Detects=20change,=20recompiles=20javap=20-v=20module-inf?= =?UTF-8?q?o.class=20|=20grep=20"major=20version"=20=20#=20Shows=2065=20(c?= =?UTF-8?q?orrect!)=20```=20##=20Impact=20-=20Users=20no=20longer=20need?= =?UTF-8?q?=20to=20manually=20configure=20executionControl=20for=20basic?= =?UTF-8?q?=20scenarios=20-=20Prevents=20silent=20bytecode=20version=20mis?= =?UTF-8?q?matches=20in=20JPMS=20projects=20-=20Backward=20compatible:=20e?= =?UTF-8?q?xplicit=20executionControl=20config=20still=20takes=20precedenc?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../maven/buildcache/xml/CacheConfigImpl.java | 65 ++++++++++++++++--- src/site/markdown/how-to.md | 11 ++++ 2 files changed, 67 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/apache/maven/buildcache/xml/CacheConfigImpl.java b/src/main/java/org/apache/maven/buildcache/xml/CacheConfigImpl.java index cd6e87c0..53cfd0b2 100644 --- a/src/main/java/org/apache/maven/buildcache/xml/CacheConfigImpl.java +++ b/src/main/java/org/apache/maven/buildcache/xml/CacheConfigImpl.java @@ -248,18 +248,15 @@ public boolean isLogAllProperties(MojoExecution mojoExecution) { } private GoalReconciliation findReconciliationConfig(MojoExecution mojoExecution) { - if (cacheConfig.getExecutionControl() == null) { - return null; - } + List reconciliation; - final ExecutionControl executionControl = cacheConfig.getExecutionControl(); - if (executionControl.getReconcile() == null) { - return null; + if (cacheConfig.getExecutionControl() == null || cacheConfig.getExecutionControl().getReconcile() == null) { + // Use default reconciliation configs for common plugins + reconciliation = getDefaultReconciliationConfigs(); + } else { + reconciliation = cacheConfig.getExecutionControl().getReconcile().getPlugins(); } - final List reconciliation = - executionControl.getReconcile().getPlugins(); - for (GoalReconciliation goalReconciliationConfig : reconciliation) { final String goal = mojoExecution.getGoal(); @@ -271,6 +268,56 @@ private GoalReconciliation findReconciliationConfig(MojoExecution mojoExecution) return null; } + private List getDefaultReconciliationConfigs() { + List defaults = new ArrayList<>(); + + // maven-compiler-plugin:compile - track source, target, release + GoalReconciliation compilerCompile = new GoalReconciliation(); + compilerCompile.setArtifactId("maven-compiler-plugin"); + compilerCompile.setGoal("compile"); + + TrackedProperty source = new TrackedProperty(); + source.setPropertyName("source"); + compilerCompile.addReconcile(source); + + TrackedProperty target = new TrackedProperty(); + target.setPropertyName("target"); + compilerCompile.addReconcile(target); + + TrackedProperty release = new TrackedProperty(); + release.setPropertyName("release"); + compilerCompile.addReconcile(release); + + defaults.add(compilerCompile); + + // maven-compiler-plugin:testCompile - track source, target, release + GoalReconciliation compilerTestCompile = new GoalReconciliation(); + compilerTestCompile.setArtifactId("maven-compiler-plugin"); + compilerTestCompile.setGoal("testCompile"); + + TrackedProperty testSource = new TrackedProperty(); + testSource.setPropertyName("source"); + compilerTestCompile.addReconcile(testSource); + + TrackedProperty testTarget = new TrackedProperty(); + testTarget.setPropertyName("target"); + compilerTestCompile.addReconcile(testTarget); + + TrackedProperty testRelease = new TrackedProperty(); + testRelease.setPropertyName("release"); + compilerTestCompile.addReconcile(testRelease); + + defaults.add(compilerTestCompile); + + // maven-install-plugin:install - always run (empty reconciliation means it's tracked) + GoalReconciliation install = new GoalReconciliation(); + install.setArtifactId("maven-install-plugin"); + install.setGoal("install"); + defaults.add(install); + + return defaults; + } + @Nonnull @Override public List getLoggedProperties(MojoExecution mojoExecution) { diff --git a/src/site/markdown/how-to.md b/src/site/markdown/how-to.md index a4ebbb78..e036c9c1 100644 --- a/src/site/markdown/how-to.md +++ b/src/site/markdown/how-to.md @@ -160,6 +160,17 @@ Add `executionControl/runAlways` section: ``` +### Default Reconciliation Behavior + +The build cache extension automatically tracks certain critical plugin properties by default, even without explicit +`executionControl` configuration: + +* **maven-compiler-plugin** (`compile` and `testCompile` goals): Tracks `source`, `target`, and `release` properties +* **maven-install-plugin** (`install` goal): Tracked to ensure artifacts are installed when needed + +This default behavior prevents common cache invalidation issues, particularly in multi-module JPMS (Java Platform Module System) +projects where compiler version changes can cause compilation failures. + ### I occasionally cached build with `-DskipTests=true`, and tests do not run now If you add command line flags to your build, they do not participate in effective pom - Maven defers the final value From ab2653c0345d111413b97b44f2c4ccc0977883b0 Mon Sep 17 00:00:00 2001 From: cowwoc Date: Sun, 5 Oct 2025 18:45:18 -0400 Subject: [PATCH 2/2] Add ignorePattern attribute to TrackedProperty for array filtering Fixes #375 - Maven 4 auto-injects --module-version to compilerArgs during cache storage but not during validation, causing parameter mismatches. Changes: - Add ignorePattern field to TrackedProperty in MDO model - Implement regex-based filtering in BuildCacheMojosExecutionStrategy - Filter arrays before comparison (both runtime and cached values) - Update default reconciliation configs to filter --module-version The ignorePattern attribute allows filtering specific array elements before comparison, solving the Maven 4 module-version problem while still detecting legitimate compilerArgs changes. Tested with multi-module JPMS project using Maven 4.0.0-rc-4. --- .../BuildCacheMojosExecutionStrategy.java | 59 ++++++++++++++++++- .../maven/buildcache/xml/CacheConfigImpl.java | 16 ++++- src/main/mdo/build-cache-config.mdo | 5 ++ 3 files changed, 76 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java b/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java index 0a2d4d73..b3d00be7 100644 --- a/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java +++ b/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java @@ -379,7 +379,7 @@ boolean isParamsMatched( Path baseDirPath = project.getBasedir().toPath(); currentValue = normalizedPath(((Path) value), baseDirPath); } else if (value != null && value.getClass().isArray()) { - currentValue = ArrayUtils.toString(value); + currentValue = filterAndStringifyArray(value, trackedProperty.getIgnorePattern()); } else { currentValue = String.valueOf(value); } @@ -388,7 +388,13 @@ boolean isParamsMatched( return false; } - if (!Strings.CS.equals(currentValue, expectedValue)) { + // Apply ignorePattern filtering to expected value if it's an array string representation + String filteredExpectedValue = expectedValue; + if (trackedProperty.getIgnorePattern() != null && expectedValue.startsWith("[") && expectedValue.endsWith("]")) { + filteredExpectedValue = filterArrayString(expectedValue, trackedProperty.getIgnorePattern()); + } + + if (!Strings.CS.equals(currentValue, filteredExpectedValue)) { if (!Strings.CS.equals(currentValue, trackedProperty.getSkipValue())) { LOGGER.info( "Plugin parameter mismatch found. Parameter: {}, expected: {}, actual: {}", @@ -434,6 +440,55 @@ private static String normalizedPath(Path path, Path baseDirPath) { return normalizedPath; } + /** + * Filters array values based on ignore pattern and converts to string representation. + */ + private static String filterAndStringifyArray(Object array, String ignorePattern) { + if (ignorePattern == null) { + return ArrayUtils.toString(array); + } + + java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(ignorePattern); + java.util.List filtered = new java.util.ArrayList<>(); + + int length = java.lang.reflect.Array.getLength(array); + for (int i = 0; i < length; i++) { + Object element = java.lang.reflect.Array.get(array, i); + String elementStr = String.valueOf(element); + if (!pattern.matcher(elementStr).find()) { + filtered.add(element); + } + } + + return filtered.toString(); + } + + /** + * Filters an array string representation (e.g., "[a, b, c]") based on ignore pattern. + */ + private static String filterArrayString(String arrayStr, String ignorePattern) { + if (ignorePattern == null || !arrayStr.startsWith("[") || !arrayStr.endsWith("]")) { + return arrayStr; + } + + java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(ignorePattern); + String content = arrayStr.substring(1, arrayStr.length() - 1); + if (content.trim().isEmpty()) { + return "[]"; + } + + String[] elements = content.split(",\\s*"); + java.util.List filtered = new java.util.ArrayList<>(); + + for (String element : elements) { + if (!pattern.matcher(element.trim()).find()) { + filtered.add(element.trim()); + } + } + + return filtered.toString(); + } + private enum CacheRestorationStatus { SUCCESS, FAILURE, diff --git a/src/main/java/org/apache/maven/buildcache/xml/CacheConfigImpl.java b/src/main/java/org/apache/maven/buildcache/xml/CacheConfigImpl.java index 53cfd0b2..bfe591a1 100644 --- a/src/main/java/org/apache/maven/buildcache/xml/CacheConfigImpl.java +++ b/src/main/java/org/apache/maven/buildcache/xml/CacheConfigImpl.java @@ -271,7 +271,7 @@ private GoalReconciliation findReconciliationConfig(MojoExecution mojoExecution) private List getDefaultReconciliationConfigs() { List defaults = new ArrayList<>(); - // maven-compiler-plugin:compile - track source, target, release + // maven-compiler-plugin:compile - track source, target, release, compilerArgs GoalReconciliation compilerCompile = new GoalReconciliation(); compilerCompile.setArtifactId("maven-compiler-plugin"); compilerCompile.setGoal("compile"); @@ -288,9 +288,15 @@ private List getDefaultReconciliationConfigs() { release.setPropertyName("release"); compilerCompile.addReconcile(release); + // Track compilerArgs but filter out --module-version to handle Maven 4 auto-injection (issue #375) + TrackedProperty compilerArgs = new TrackedProperty(); + compilerArgs.setPropertyName("compilerArgs"); + compilerArgs.setIgnorePattern("--module-version"); + compilerCompile.addReconcile(compilerArgs); + defaults.add(compilerCompile); - // maven-compiler-plugin:testCompile - track source, target, release + // maven-compiler-plugin:testCompile - track source, target, release, compilerArgs GoalReconciliation compilerTestCompile = new GoalReconciliation(); compilerTestCompile.setArtifactId("maven-compiler-plugin"); compilerTestCompile.setGoal("testCompile"); @@ -307,6 +313,12 @@ private List getDefaultReconciliationConfigs() { testRelease.setPropertyName("release"); compilerTestCompile.addReconcile(testRelease); + // Track compilerArgs but filter out --module-version to handle Maven 4 auto-injection (issue #375) + TrackedProperty testCompilerArgs = new TrackedProperty(); + testCompilerArgs.setPropertyName("compilerArgs"); + testCompilerArgs.setIgnorePattern("--module-version"); + compilerTestCompile.addReconcile(testCompilerArgs); + defaults.add(compilerTestCompile); // maven-install-plugin:install - always run (empty reconciliation means it's tracked) diff --git a/src/main/mdo/build-cache-config.mdo b/src/main/mdo/build-cache-config.mdo index 52ae0da0..25034119 100644 --- a/src/main/mdo/build-cache-config.mdo +++ b/src/main/mdo/build-cache-config.mdo @@ -1459,6 +1459,11 @@ under the License. defaultValue String + + ignorePattern + String + Regular expression pattern to filter out matching values from array/list properties before comparison. Useful for filtering auto-injected values like Maven 4's --module-version +