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


##########
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:
   Fixed in f8dc720: descriptor parsing now accepts both XML boolean spellings 
(`true`/`1` and `false`/`0`) and rejects invalid values. Added a loader test 
covering all four spellings.



##########
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:
   Fixed in f8dc720: `testCompile` now tracks `testSource`, `testTarget`, and 
`testRelease` alongside the inherited fields. An integration test changes only 
`maven.compiler.testRelease` and verifies reconciliation rejects the stale 
cache entry.



##########
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:
   Fixed in f8dc720: tracked values are captured as normalized strings before 
mojo execution, and cache comparison uses the same normalization. The 
`--module-version` removal is gone; unit tests cover empty arguments and an 
explicitly configured module-version pair, and the issue #375 integration test 
passes.



##########
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:
   Fixed in f8dc720: descriptor lookup now verifies group ID and artifact ID 
before version selection. Both automatic reconciliation and explicit-config 
validation use that lookup; a unit test rejects a 
`com.example:maven-compiler-plugin` collision.



##########
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:
   Fixed in f8dc720: the before-execution listener now saves immutable 
parameter values, so `TestCompilerMojo` changes to `compilerArgs` cannot alter 
the recorded inputs. A JPMS integration test confirms the second clean build 
skips `compiler:testCompile`.



-- 
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