Copilot commented on code in PR #389:
URL: 
https://github.com/apache/maven-build-cache-extension/pull/389#discussion_r4438391675


##########
src/main/java/org/apache/maven/buildcache/CacheControllerImpl.java:
##########
@@ -1055,6 +1059,30 @@ private void recordMojoProperties(CompletedExecution 
execution, MojoExecutionEve
         }
     }
 
+    private Object normalizeMojoProperty(
+            String propertyName, Object value, MojoExecution mojoExecution, 
MavenProject project) {
+        if (!(value instanceof List)
+                || !"compilerArgs".equals(propertyName)
+                || mojoExecution.getPlugin() == null
+                || 
!"maven-compiler-plugin".equals(mojoExecution.getPlugin().getArtifactId())) {
+            return value;
+        }
+
+        List<?> compilerArguments = (List<?>) value;
+        List<Object> normalizedArguments = new 
ArrayList<>(compilerArguments.size());
+        for (int i = 0; i < compilerArguments.size(); i++) {
+            Object argument = compilerArguments.get(i);
+            if ("--module-version".equals(argument)
+                    && i + 1 < compilerArguments.size()
+                    && Objects.equals(project.getVersion(), 
compilerArguments.get(i + 1))) {
+                i++;
+            } else {
+                normalizedArguments.add(argument);
+            }
+        }
+        return normalizedArguments.isEmpty() ? null : normalizedArguments;

Review Comment:
   Normalization runs only when saving, so an unchanged empty `compilerArgs` 
list is stored as `"null"` but compared as `"[]"`. An explicitly configured 
`--module-version` pair matching the project version is likewise removed only 
from the saved value, causing repeated cache misses. Preserve explicitly 
configured arguments and use compatible representations when recording and 
comparing them, preferably by snapshotting inputs before execution.



##########
src/main/java/org/apache/maven/buildcache/xml/PluginParameterLoader.java:
##########
@@ -0,0 +1,283 @@
+/*
+ * 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.xml;
+
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import 
org.apache.maven.buildcache.xml.PluginParameterDefinition.GoalParameterDefinition;
+import 
org.apache.maven.buildcache.xml.PluginParameterDefinition.ParameterDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+/**
+ * Loads plugin parameter definitions from classpath resources.
+ * Definitions are stored in 
src/main/resources/plugin-parameters/{artifactId}.xml
+ */
+class PluginParameterLoader {
+
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(PluginParameterLoader.class);
+    private static final String PARAMETER_DIR = "plugin-parameters/";
+
+    private final Map<String, PluginParameterDefinition> definitions = new 
ConcurrentHashMap<>();
+
+    /**
+     * Load parameter definitions for a plugin by artifact ID only (no version 
matching)
+     */
+    PluginParameterDefinition load(String artifactId) {
+        return load(artifactId, null);
+    }
+
+    /**
+     * Load parameter definitions for a plugin by artifact ID and version.
+     * If version is provided, finds the best matching definition (highest 
minVersion &lt;= actual version).
+     * If version is null, returns any definition for the artifactId.
+     */
+    PluginParameterDefinition load(String artifactId, String pluginVersion) {
+        String cacheKey = artifactId + (pluginVersion != null ? ":" + 
pluginVersion : "");
+
+        if (definitions.containsKey(cacheKey)) {
+            return definitions.get(cacheKey);
+        }
+
+        String resourcePath = PARAMETER_DIR + artifactId + ".xml";
+        InputStream resourceStream = 
getClass().getClassLoader().getResourceAsStream(resourcePath);
+
+        if (resourceStream == null) {
+            LOGGER.debug("No parameter definition found for plugin: {}", 
artifactId);
+            return null;
+        }
+
+        try (InputStream inputStream = resourceStream) {
+            List<PluginParameterDefinition> allDefinitions = 
parseDefinitions(inputStream);
+
+            PluginParameterDefinition bestMatch = 
findBestMatch(allDefinitions, pluginVersion);
+
+            if (bestMatch != null) {
+                definitions.put(cacheKey, bestMatch);
+                LOGGER.debug(
+                        "Loaded parameter definition for {}:{} (minVersion: 
{}): {} goals, {} total parameters",
+                        artifactId,
+                        pluginVersion != null ? pluginVersion : "any",
+                        bestMatch.getMinVersion() != null ? 
bestMatch.getMinVersion() : "none",
+                        bestMatch.getGoals().size(),
+                        bestMatch.getGoals().values().stream()
+                                .mapToInt(goalDefinition ->
+                                        goalDefinition.getParameters().size())
+                                .sum());
+            }
+
+            return bestMatch;
+        } catch (ParserConfigurationException | SAXException | IOException e) {
+            LOGGER.warn("Failed to load parameter definition for {}: {}", 
artifactId, e.getMessage(), e);
+            return null;
+        }
+    }
+
+    /**
+     * Find the best matching definition for a plugin version.
+     * Returns the definition with the highest minVersion that is <= 
pluginVersion.
+     * If pluginVersion is null, returns the first definition (or the one 
without minVersion).
+     */
+    private PluginParameterDefinition 
findBestMatch(List<PluginParameterDefinition> definitions, String 
pluginVersion) {
+        if (definitions.isEmpty()) {
+            return null;
+        }
+
+        if (pluginVersion == null) {
+            // No version specified, prefer definition without minVersion, 
otherwise return first
+            return definitions.stream()
+                    .filter(definition -> definition.getMinVersion() == null)
+                    .findFirst()
+                    .orElse(definitions.get(0));
+        }
+
+        // Find highest minVersion that's <= pluginVersion
+        PluginParameterDefinition bestMatch = null;
+        String bestMinVersion = null;
+
+        for (PluginParameterDefinition definition : definitions) {
+            String minVersion = definition.getMinVersion();
+
+            // Definition without minVersion applies to all versions
+            if (minVersion == null) {
+                if (bestMatch == null) {
+                    bestMatch = definition;
+                }
+                continue;
+            }
+
+            // Check if this definition applies to the plugin version
+            if (compareVersions(minVersion, pluginVersion) <= 0) {
+                // minVersion <= pluginVersion, so this definition applies
+                if (bestMinVersion == null || compareVersions(minVersion, 
bestMinVersion) > 0) {
+                    // This is a better match (higher minVersion)
+                    bestMatch = definition;
+                    bestMinVersion = minVersion;
+                }
+            }
+        }
+
+        return bestMatch;
+    }
+
+    /**
+     * Compare two version strings.
+     * Returns: negative if the first version is older, zero if equal, 
positive if newer.
+     */
+    private int compareVersions(String firstVersion, String secondVersion) {
+        String[] firstVersionParts = firstVersion.split("\\.");
+        String[] secondVersionParts = secondVersion.split("\\.");
+
+        int maxLength = Math.max(firstVersionParts.length, 
secondVersionParts.length);
+
+        for (int i = 0; i < maxLength; i++) {
+            int firstPart = i < firstVersionParts.length ? 
parseVersionPart(firstVersionParts[i]) : 0;
+            int secondPart = i < secondVersionParts.length ? 
parseVersionPart(secondVersionParts[i]) : 0;
+
+            if (firstPart != secondPart) {
+                return Integer.compare(firstPart, secondPart);
+            }
+        }
+
+        return 0;
+    }
+
+    private int parseVersionPart(String part) {
+        try {
+            // Handle qualifiers like "3.8.0-SNAPSHOT" - just use numeric part
+            int dashIndex = part.indexOf('-');
+            if (dashIndex > 0) {
+                part = part.substring(0, dashIndex);
+            }
+            return Integer.parseInt(part);
+        } catch (NumberFormatException e) {
+            return 0;
+        }
+    }
+
+    /**
+     * Parse plugin parameter definitions from XML.
+     * Supports multiple <plugin> elements in a single file for 
version-specific definitions.
+     */
+    private List<PluginParameterDefinition> parseDefinitions(InputStream 
inputStream)
+            throws ParserConfigurationException, SAXException, IOException {
+        List<PluginParameterDefinition> definitions = new ArrayList<>();
+
+        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        factory.setNamespaceAware(true);
+        factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
+        
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl";, 
true);
+        
factory.setFeature("http://xml.org/sax/features/external-general-entities";, 
false);
+        
factory.setFeature("http://xml.org/sax/features/external-parameter-entities";, 
false);
+        
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd";,
 false);
+        factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
+        factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
+        factory.setXIncludeAware(false);
+        factory.setExpandEntityReferences(false);
+        DocumentBuilder builder = factory.newDocumentBuilder();
+        Document document = builder.parse(inputStream);
+
+        Element root = document.getDocumentElement();
+
+        // Check if root is a single <plugin> or if we need to look for 
multiple
+        if ("plugin".equals(root.getLocalName())) {
+            // Single plugin definition
+            definitions.add(parsePluginDefinition(root));
+        } else {
+            // Look for multiple <plugin> elements
+            NodeList pluginNodes = root.getElementsByTagNameNS("*", "plugin");
+            for (int i = 0; i < pluginNodes.getLength(); i++) {
+                Element pluginElement = (Element) pluginNodes.item(i);
+                definitions.add(parsePluginDefinition(pluginElement));
+            }
+        }
+
+        return definitions;
+    }
+
+    private PluginParameterDefinition parsePluginDefinition(Element 
pluginElement) {
+        String groupId = getTextContent(pluginElement, "groupId");
+        String actualArtifactId = getTextContent(pluginElement, "artifactId");
+        String minVersion = getTextContent(pluginElement, "minVersion");
+
+        PluginParameterDefinition definition = new 
PluginParameterDefinition(groupId, actualArtifactId, minVersion);
+
+        NodeList goalsNodes = pluginElement.getElementsByTagNameNS("*", 
"goals");
+        if (goalsNodes.getLength() > 0) {
+            Element goalsElement = (Element) goalsNodes.item(0);
+            NodeList goalNodes = goalsElement.getElementsByTagNameNS("*", 
"goal");
+
+            for (int i = 0; i < goalNodes.getLength(); i++) {
+                Element goalElement = (Element) goalNodes.item(i);
+                parseGoal(goalElement, definition);
+            }
+        }
+
+        return definition;
+    }
+
+    private void parseGoal(Element goalElement, PluginParameterDefinition 
definition) {
+        String goalName = getTextContent(goalElement, "name");
+        GoalParameterDefinition goal = new GoalParameterDefinition(goalName);
+
+        NodeList parametersNodes = goalElement.getElementsByTagNameNS("*", 
"parameters");
+        if (parametersNodes.getLength() > 0) {
+            Element parametersElement = (Element) parametersNodes.item(0);
+            NodeList parameterNodes = 
parametersElement.getElementsByTagNameNS("*", "parameter");
+
+            for (int i = 0; i < parameterNodes.getLength(); i++) {
+                Element parameterElement = (Element) parameterNodes.item(i);
+                ParameterDefinition parameterDefinition = 
parseParameter(parameterElement);
+                goal.addParameter(parameterDefinition);
+            }
+        }
+
+        definition.addGoal(goalName, goal);
+    }
+
+    private ParameterDefinition parseParameter(Element parameterElement) {
+        String name = getTextContent(parameterElement, "name");
+        boolean cacheKey = 
Boolean.parseBoolean(getTextContent(parameterElement, "cache-key"));

Review Comment:
   The supplied schema declares `cache-key` as `xs:boolean`, for which `1` is a 
valid spelling of true. `Boolean.parseBoolean("1")` returns false, so a 
schema-valid descriptor using that spelling silently disables automatic 
tracking for the parameter. Accept both XML boolean spellings of true.



##########
src/main/resources/plugin-parameters/maven-compiler-plugin.xml:
##########
@@ -0,0 +1,318 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<plugin xmlns="http://maven.apache.org/PLUGIN-PARAMETERS/1.0.0";
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+        xsi:schemaLocation="http://maven.apache.org/PLUGIN-PARAMETERS/1.0.0 
plugin-parameters.xsd">
+  <groupId>org.apache.maven.plugins</groupId>
+  <artifactId>maven-compiler-plugin</artifactId>
+  <minVersion>3.11.0</minVersion>
+
+  <goals>
+    <goal>
+      <name>compile</name>
+      <parameters>
+        <!-- cache-key=true entries explain why a value change must cause a 
cache miss. -->
+        <parameter>
+          <name>source</name>
+          <cache-key>true</cache-key>
+          <description>Source JDK version for compilation; changing it can 
change generated bytecode or compilation success</description>
+        </parameter>
+        <parameter>
+          <name>target</name>
+          <cache-key>true</cache-key>
+          <description>Target JDK version for compiled bytecode; changing it 
changes generated bytecode</description>
+        </parameter>
+        <parameter>
+          <name>release</name>
+          <cache-key>true</cache-key>
+          <description>JDK release version (combines source and target); 
changing it changes generated bytecode and API compatibility</description>
+        </parameter>
+        <parameter>
+          <name>encoding</name>
+          <cache-key>true</cache-key>
+          <description>Source file encoding; changing it can change parsed 
source and generated bytecode</description>
+        </parameter>
+        <parameter>
+          <name>debug</name>
+          <cache-key>true</cache-key>
+          <description>Include debugging information in compiled bytecode; 
changing it changes artifact contents</description>
+        </parameter>
+        <parameter>
+          <name>debuglevel</name>
+          <cache-key>true</cache-key>
+          <description>Level of debugging information (lines, vars, source); 
changing it changes artifact contents</description>
+        </parameter>
+        <parameter>
+          <name>optimize</name>
+          <cache-key>true</cache-key>
+          <description>Optimize compiled bytecode; changing it changes 
artifact contents</description>
+        </parameter>
+        <parameter>
+          <name>compilerArgs</name>
+          <cache-key>true</cache-key>
+          <description>Additional compiler arguments; changing them can change 
generated bytecode or compilation success</description>
+        </parameter>
+        <parameter>
+          <name>compilerArgument</name>
+          <cache-key>true</cache-key>
+          <description>Single additional compiler argument; changing it can 
change generated bytecode or compilation success</description>
+        </parameter>
+        <parameter>
+          <name>annotationProcessorPaths</name>
+          <cache-key>true</cache-key>
+          <description>Classpath for annotation processors; changing it can 
change generated sources or compilation success</description>
+        </parameter>
+        <parameter>
+          <name>annotationProcessors</name>
+          <cache-key>true</cache-key>
+          <description>Annotation processors to run; changing them can change 
generated sources or compilation success</description>
+        </parameter>
+        <parameter>
+          <name>proc</name>
+          <cache-key>true</cache-key>
+          <description>Annotation processing mode (none, only, proc); changing 
it can change generated sources or compilation success</description>
+        </parameter>
+        <parameter>
+          <name>executable</name>
+          <cache-key>true</cache-key>
+          <description>Path to javac executable; changing it can change the 
compiler output or compilation success</description>
+        </parameter>
+        <parameter>
+          <name>parameters</name>
+          <cache-key>true</cache-key>
+          <description>Generate metadata for method parameters; changing it 
changes bytecode contents</description>
+        </parameter>
+        <parameter>
+          <name>enablePreview</name>
+          <cache-key>true</cache-key>
+          <description>Enable preview features; changing it can change 
generated bytecode or compilation success</description>
+        </parameter>
+
+        <!-- No cache-key annotation means false; these settings do not 
invalidate the cache. -->
+        <parameter>
+          <name>verbose</name>
+          <description>Verbose compiler output</description>
+        </parameter>
+        <parameter>
+          <name>showWarnings</name>
+          <cache-key>true</cache-key>
+          <description>Show compilation warnings (can change the build outcome 
with failOnWarning)</description>
+        </parameter>
+        <parameter>
+          <name>showDeprecation</name>
+          <cache-key>true</cache-key>
+          <description>Show deprecation warnings (can change the build outcome 
with failOnWarning)</description>
+        </parameter>
+        <parameter>
+          <name>skipMain</name>
+          <cache-key>true</cache-key>
+          <description>Skip compiling main sources; changing it changes 
whether main bytecode is produced</description>
+        </parameter>
+        <parameter>
+          <name>failOnError</name>
+          <cache-key>true</cache-key>
+          <description>Fail build on compilation error (changes the build 
outcome)</description>
+        </parameter>
+        <parameter>
+          <name>failOnWarning</name>
+          <cache-key>true</cache-key>
+          <description>Fail build on compilation warning (changes the build 
outcome)</description>
+        </parameter>
+        <parameter>
+          <name>fork</name>
+          <cache-key>true</cache-key>
+          <description>Fork compiler into separate process (can change the 
compiler, output, or outcome)</description>
+        </parameter>
+        <parameter>
+          <name>maxmem</name>
+          <cache-key>true</cache-key>
+          <description>Maximum memory for compiler process (can change the 
build outcome)</description>
+        </parameter>
+        <parameter>
+          <name>meminitial</name>
+          <cache-key>true</cache-key>
+          <description>Initial memory for compiler process (can change the 
build outcome)</description>
+        </parameter>
+        <parameter>
+          <name>compilerReuseStrategy</name>
+          <description>Strategy for reusing compiler instances</description>
+        </parameter>
+        <parameter>
+          <name>forceJavacCompilerUse</name>
+          <cache-key>true</cache-key>
+          <description>Force use of javac compiler; changing it can affect 
compiler output</description>
+        </parameter>
+        <parameter>
+          <name>staleMillis</name>
+          <cache-key>true</cache-key>
+          <description>Staleness check interval for incremental compilation 
(can change generated files)</description>
+        </parameter>
+        <parameter>
+          <name>useIncrementalCompilation</name>
+          <cache-key>true</cache-key>
+          <description>Enable incremental compilation (can change generated 
files and build outcome)</description>
+        </parameter>
+      </parameters>
+    </goal>
+
+    <goal>
+      <name>testCompile</name>
+      <parameters>
+        <!-- Same parameters as compile goal -->
+        <parameter>
+          <name>source</name>
+          <cache-key>true</cache-key>
+          <description>Source JDK version for test compilation; changing it 
can change generated test bytecode or compilation success</description>
+        </parameter>
+        <parameter>
+          <name>target</name>
+          <cache-key>true</cache-key>
+          <description>Target JDK version for compiled test bytecode; changing 
it changes generated test bytecode</description>
+        </parameter>
+        <parameter>
+          <name>release</name>
+          <cache-key>true</cache-key>
+          <description>JDK release version for tests; changing it changes 
generated test bytecode and API compatibility</description>
+        </parameter>

Review Comment:
   The `testCompile` mojo uses `testSource`, `testTarget`, and `testRelease` in 
preference to the inherited `source`, `target`, and `release` fields. Those 
test-specific parameters are missing here, so changing only 
`-Dmaven.compiler.testRelease` can still reuse test bytecode compiled for the 
previous release. Track the test-specific fields as well as the inherited 
fallbacks.



##########
src/main/java/org/apache/maven/buildcache/xml/CacheConfigImpl.java:
##########
@@ -253,27 +256,181 @@ public boolean isLogAllProperties(MojoExecution 
mojoExecution) {
     }
 
     private GoalReconciliation findReconciliationConfig(MojoExecution 
mojoExecution) {
-        if (cacheConfig.getExecutionControl() == null) {
+        if (mojoExecution == null) {
             return null;
         }
 
-        final ExecutionControl executionControl = 
cacheConfig.getExecutionControl();
-        if (executionControl.getReconcile() == null) {
+        final String goal = mojoExecution.getGoal();
+        final Plugin plugin = mojoExecution.getPlugin();
+
+        if (plugin == null) {
             return null;
         }
 
-        final List<GoalReconciliation> reconciliation =
-                executionControl.getReconcile().getPlugins();
+        // First check explicit configuration
+        if (cacheConfig.getExecutionControl() != null
+                && cacheConfig.getExecutionControl().getReconcile() != null) {
+            List<GoalReconciliation> explicitConfigs =
+                    
cacheConfig.getExecutionControl().getReconcile().getPlugins();
+            for (GoalReconciliation config : explicitConfigs) {
+                if (isPluginMatch(plugin, config) && Strings.CS.equals(goal, 
config.getGoal())) {
+                    // Validate explicit config against parameter definitions 
(with version)
+                    validateReconciliationConfig(config, plugin);
+                    return config;
+                }
+            }
+        }
 
-        for (GoalReconciliation goalReconciliationConfig : reconciliation) {
-            final String goal = mojoExecution.getGoal();
+        // Auto-generate from parameter definitions (track all cache-key 
parameters)
+        GoalReconciliation autoGenerated = 
generateReconciliationFromParameters(plugin, goal);
+        if (autoGenerated != null) {
+            LOGGER.debug(
+                    "Auto-generated reconciliation config for {}:{} with {} 
cache-key parameters",
+                    plugin.getArtifactId(),
+                    goal,
+                    autoGenerated.getReconciles() != null
+                            ? autoGenerated.getReconciles().size()
+                            : 0);
+        }
+        return autoGenerated;
+    }
 
-            if (isPluginMatch(mojoExecution.getPlugin(), 
goalReconciliationConfig)
-                    && Strings.CS.equals(goal, 
goalReconciliationConfig.getGoal())) {
-                return goalReconciliationConfig;
+    /**
+     * Validates a single reconciliation config against plugin parameter 
definitions.
+     * Uses plugin version to load the appropriate parameter definition.
+     */
+    private void validateReconciliationConfig(GoalReconciliation config, 
Plugin plugin) {
+        String artifactId = config.getArtifactId();
+        String goal = config.getGoal();
+        String pluginVersion = plugin.getVersion();
+
+        // Load parameter definition for this plugin with version
+        PluginParameterDefinition pluginDefinition = 
parameterLoader.load(artifactId, pluginVersion);
+
+        if (pluginDefinition == null) {
+            LOGGER.warn(
+                    "No parameter definition found for plugin {}:{} version 
{}. "
+                            + "Cannot validate reconciliation configuration. "
+                            + "Consider adding a parameter definition file to 
plugin-parameters/{}.xml",
+                    artifactId,
+                    goal,
+                    pluginVersion != null ? pluginVersion : "unknown",
+                    artifactId);
+            return;
+        }
+
+        // Get goal definition
+        GoalParameterDefinition goalDefinition = 
pluginDefinition.getGoal(goal);
+        if (goalDefinition == null) {
+            LOGGER.warn(
+                    "Goal '{}' not found in parameter definition for plugin {} 
version {}. "
+                            + "Cannot validate reconciliation configuration.",
+                    goal,
+                    artifactId,
+                    pluginVersion != null ? pluginVersion : "unknown");
+            return;
+        }
+
+        // Validate each tracked property
+        List<TrackedProperty> properties = config.getReconciles();
+
+        for (TrackedProperty property : properties) {
+            String propertyName = property.getPropertyName();
+
+            if (!goalDefinition.hasParameter(propertyName)) {
+                LOGGER.warn(
+                        "Unknown parameter '{}' in reconciliation config for 
{}:{} version {}. "
+                                + "This may indicate a plugin version mismatch 
or renamed parameter. "
+                                + "Consider updating parameter definition or 
removing from reconciliation.",
+                        propertyName,
+                        artifactId,
+                        goal,
+                        pluginVersion != null ? pluginVersion : "unknown");
+            } else {
+                ParameterDefinition parameterDefinition = 
goalDefinition.getParameter(propertyName);
+                if (!parameterDefinition.isCacheKey()) {
+                    LOGGER.warn(
+                            "Parameter '{}' in reconciliation config for {}:{} 
is not marked as a cache key. "
+                                    + "Changing it will not invalidate the 
cache according to the plugin definition.",
+                            propertyName,
+                            artifactId,
+                            goal);
+                }
             }
         }
-        return null;
+    }
+
+    /**
+     * Auto-generates a reconciliation config by tracking all cache-key 
parameters
+     * from the plugin parameter definition.
+     * This provides automatic default tracking for any plugin with parameter 
definitions.
+     *
+     * @param plugin The plugin to generate config for
+     * @param goal The goal name
+     * @return Auto-generated config tracking all cache-key parameters, or 
null if no parameter definition exists
+     */
+    private GoalReconciliation generateReconciliationFromParameters(Plugin 
plugin, String goal) {
+        String artifactId = plugin.getArtifactId();
+        String pluginVersion = plugin.getVersion();
+
+        LOGGER.debug(
+                "Attempting to auto-generate reconciliation config for {}:{} 
version {}",
+                artifactId,
+                goal,
+                pluginVersion);
+
+        // Load parameter definition for this plugin
+        PluginParameterDefinition pluginDefinition = 
parameterLoader.load(artifactId, pluginVersion);
+        if (pluginDefinition == null) {

Review Comment:
   Descriptor lookup uses only the artifact ID, so a different plugin such as 
`com.example:maven-compiler-plugin` inherits the Apache compiler's tracked 
fields and can have every cached build rejected for missing properties. 
Explicit reconciliation is also validated against the wrong plugin's descriptor 
in `validateReconciliationConfig()`. Check the descriptor's group ID against 
the actual plugin in both paths before using it.



##########
src/main/resources/plugin-parameters/maven-compiler-plugin.xml:
##########
@@ -0,0 +1,318 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<plugin xmlns="http://maven.apache.org/PLUGIN-PARAMETERS/1.0.0";
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+        xsi:schemaLocation="http://maven.apache.org/PLUGIN-PARAMETERS/1.0.0 
plugin-parameters.xsd">
+  <groupId>org.apache.maven.plugins</groupId>
+  <artifactId>maven-compiler-plugin</artifactId>
+  <minVersion>3.11.0</minVersion>
+
+  <goals>
+    <goal>
+      <name>compile</name>
+      <parameters>
+        <!-- cache-key=true entries explain why a value change must cause a 
cache miss. -->
+        <parameter>
+          <name>source</name>
+          <cache-key>true</cache-key>
+          <description>Source JDK version for compilation; changing it can 
change generated bytecode or compilation success</description>
+        </parameter>
+        <parameter>
+          <name>target</name>
+          <cache-key>true</cache-key>
+          <description>Target JDK version for compiled bytecode; changing it 
changes generated bytecode</description>
+        </parameter>
+        <parameter>
+          <name>release</name>
+          <cache-key>true</cache-key>
+          <description>JDK release version (combines source and target); 
changing it changes generated bytecode and API compatibility</description>
+        </parameter>
+        <parameter>
+          <name>encoding</name>
+          <cache-key>true</cache-key>
+          <description>Source file encoding; changing it can change parsed 
source and generated bytecode</description>
+        </parameter>
+        <parameter>
+          <name>debug</name>
+          <cache-key>true</cache-key>
+          <description>Include debugging information in compiled bytecode; 
changing it changes artifact contents</description>
+        </parameter>
+        <parameter>
+          <name>debuglevel</name>
+          <cache-key>true</cache-key>
+          <description>Level of debugging information (lines, vars, source); 
changing it changes artifact contents</description>
+        </parameter>
+        <parameter>
+          <name>optimize</name>
+          <cache-key>true</cache-key>
+          <description>Optimize compiled bytecode; changing it changes 
artifact contents</description>
+        </parameter>
+        <parameter>
+          <name>compilerArgs</name>
+          <cache-key>true</cache-key>
+          <description>Additional compiler arguments; changing them can change 
generated bytecode or compilation success</description>
+        </parameter>
+        <parameter>
+          <name>compilerArgument</name>
+          <cache-key>true</cache-key>
+          <description>Single additional compiler argument; changing it can 
change generated bytecode or compilation success</description>
+        </parameter>
+        <parameter>
+          <name>annotationProcessorPaths</name>
+          <cache-key>true</cache-key>
+          <description>Classpath for annotation processors; changing it can 
change generated sources or compilation success</description>
+        </parameter>
+        <parameter>
+          <name>annotationProcessors</name>
+          <cache-key>true</cache-key>
+          <description>Annotation processors to run; changing them can change 
generated sources or compilation success</description>
+        </parameter>
+        <parameter>
+          <name>proc</name>
+          <cache-key>true</cache-key>
+          <description>Annotation processing mode (none, only, proc); changing 
it can change generated sources or compilation success</description>
+        </parameter>
+        <parameter>
+          <name>executable</name>
+          <cache-key>true</cache-key>
+          <description>Path to javac executable; changing it can change the 
compiler output or compilation success</description>
+        </parameter>
+        <parameter>
+          <name>parameters</name>
+          <cache-key>true</cache-key>
+          <description>Generate metadata for method parameters; changing it 
changes bytecode contents</description>
+        </parameter>
+        <parameter>
+          <name>enablePreview</name>
+          <cache-key>true</cache-key>
+          <description>Enable preview features; changing it can change 
generated bytecode or compilation success</description>
+        </parameter>
+
+        <!-- No cache-key annotation means false; these settings do not 
invalidate the cache. -->
+        <parameter>
+          <name>verbose</name>
+          <description>Verbose compiler output</description>
+        </parameter>
+        <parameter>
+          <name>showWarnings</name>
+          <cache-key>true</cache-key>
+          <description>Show compilation warnings (can change the build outcome 
with failOnWarning)</description>
+        </parameter>
+        <parameter>
+          <name>showDeprecation</name>
+          <cache-key>true</cache-key>
+          <description>Show deprecation warnings (can change the build outcome 
with failOnWarning)</description>
+        </parameter>
+        <parameter>
+          <name>skipMain</name>
+          <cache-key>true</cache-key>
+          <description>Skip compiling main sources; changing it changes 
whether main bytecode is produced</description>
+        </parameter>
+        <parameter>
+          <name>failOnError</name>
+          <cache-key>true</cache-key>
+          <description>Fail build on compilation error (changes the build 
outcome)</description>
+        </parameter>
+        <parameter>
+          <name>failOnWarning</name>
+          <cache-key>true</cache-key>
+          <description>Fail build on compilation warning (changes the build 
outcome)</description>
+        </parameter>
+        <parameter>
+          <name>fork</name>
+          <cache-key>true</cache-key>
+          <description>Fork compiler into separate process (can change the 
compiler, output, or outcome)</description>
+        </parameter>
+        <parameter>
+          <name>maxmem</name>
+          <cache-key>true</cache-key>
+          <description>Maximum memory for compiler process (can change the 
build outcome)</description>
+        </parameter>
+        <parameter>
+          <name>meminitial</name>
+          <cache-key>true</cache-key>
+          <description>Initial memory for compiler process (can change the 
build outcome)</description>
+        </parameter>
+        <parameter>
+          <name>compilerReuseStrategy</name>
+          <description>Strategy for reusing compiler instances</description>
+        </parameter>
+        <parameter>
+          <name>forceJavacCompilerUse</name>
+          <cache-key>true</cache-key>
+          <description>Force use of javac compiler; changing it can affect 
compiler output</description>
+        </parameter>
+        <parameter>
+          <name>staleMillis</name>
+          <cache-key>true</cache-key>
+          <description>Staleness check interval for incremental compilation 
(can change generated files)</description>
+        </parameter>
+        <parameter>
+          <name>useIncrementalCompilation</name>
+          <cache-key>true</cache-key>
+          <description>Enable incremental compilation (can change generated 
files and build outcome)</description>
+        </parameter>
+      </parameters>
+    </goal>
+
+    <goal>
+      <name>testCompile</name>
+      <parameters>
+        <!-- Same parameters as compile goal -->
+        <parameter>
+          <name>source</name>
+          <cache-key>true</cache-key>
+          <description>Source JDK version for test compilation; changing it 
can change generated test bytecode or compilation success</description>
+        </parameter>
+        <parameter>
+          <name>target</name>
+          <cache-key>true</cache-key>
+          <description>Target JDK version for compiled test bytecode; changing 
it changes generated test bytecode</description>
+        </parameter>
+        <parameter>
+          <name>release</name>
+          <cache-key>true</cache-key>
+          <description>JDK release version for tests; changing it changes 
generated test bytecode and API compatibility</description>
+        </parameter>
+        <parameter>
+          <name>encoding</name>
+          <cache-key>true</cache-key>
+          <description>Test source file encoding; changing it can change 
parsed source and generated test bytecode</description>
+        </parameter>
+        <parameter>
+          <name>debug</name>
+          <cache-key>true</cache-key>
+          <description>Include debugging information in test bytecode; 
changing it changes artifact contents</description>
+        </parameter>
+        <parameter>
+          <name>debuglevel</name>
+          <cache-key>true</cache-key>
+          <description>Level of debugging information for tests; changing it 
changes artifact contents</description>
+        </parameter>
+        <parameter>
+          <name>optimize</name>
+          <cache-key>true</cache-key>
+          <description>Optimize test bytecode; changing it changes artifact 
contents</description>
+        </parameter>
+        <parameter>
+          <name>compilerArgs</name>
+          <cache-key>true</cache-key>

Review Comment:
   For JPMS modules with ordinary test sources, 
`TestCompilerMojo.preparePaths()` appends `--patch-module` and `--add-reads` to 
`compilerArgs` during execution. These defaults record the mutated list, but 
reconciliation compares a freshly configured mojo, so unchanged builds 
repeatedly miss the cache; removing only `--module-version` does not fix this. 
Snapshot tracked inputs before execution, including copies of mutable values, 
and record those snapshots instead.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to