diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index ae22ef636dd3..a329b93e54d2 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -50,7 +50,7 @@ jobs: distribution: 'temurin' - name: Checkout maven - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false @@ -163,7 +163,7 @@ jobs: run: choco install graphviz - name: Checkout maven - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false @@ -264,7 +264,7 @@ jobs: distribution: 'temurin' - name: Checkout maven - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/Constants.java b/api/maven-api-core/src/main/java/org/apache/maven/api/Constants.java index 623e7ded09cd..293b1627f860 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/Constants.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/Constants.java @@ -489,21 +489,6 @@ public final class Constants { @Config(type = "java.lang.Boolean", defaultValue = "false") public static final String MAVEN_CONSUMER_POM_FLATTEN = "maven.consumer.pom.flatten"; - /** - * User property for controlling removal of unused managed dependencies during consumer POM flattening. - * When set to {@code true} (default), managed dependencies that do not appear in the resolved - * dependency tree are removed from the consumer POM to keep it lean. This is important when using - * BOMs like Spring Boot or Quarkus that contain hundreds of managed dependency entries. - * When set to {@code false}, all managed dependencies are preserved in the consumer POM, - * which may be needed in rare cases where downstream consumers override transitive dependency - * versions and rely on the original managed dependencies for alignment. - * - * @since 4.1.0 - */ - @Config(type = "java.lang.Boolean", defaultValue = "true") - public static final String MAVEN_CONSUMER_POM_REMOVE_UNUSED_MANAGED_DEPENDENCIES = - "maven.consumer.pom.removeUnusedManagedDependencies"; - /** * User property for controlling "maven personality". If activated Maven will behave * like the previous major version, Maven 3. diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/feature/Features.java b/api/maven-api-core/src/main/java/org/apache/maven/api/feature/Features.java index 12f044f60793..52feae0cd866 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/feature/Features.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/feature/Features.java @@ -54,13 +54,6 @@ public static boolean consumerPomFlatten(@Nullable Map userProperties return doGet(userProperties, Constants.MAVEN_CONSUMER_POM_FLATTEN, false); } - /** - * Check if unused managed dependency removal is enabled during consumer POM flattening. - */ - public static boolean consumerPomRemoveUnusedManagedDependencies(@Nullable Map userProperties) { - return doGet(userProperties, Constants.MAVEN_CONSUMER_POM_REMOVE_UNUSED_MANAGED_DEPENDENCIES, true); - } - /** * Check if build POM deployment is enabled. */ diff --git a/compat/maven-compat/src/main/java/org/apache/maven/toolchain/DefaultToolchain.java b/compat/maven-compat/src/main/java/org/apache/maven/toolchain/DefaultToolchain.java index c97020602244..ef89cee08f20 100644 --- a/compat/maven-compat/src/main/java/org/apache/maven/toolchain/DefaultToolchain.java +++ b/compat/maven-compat/src/main/java/org/apache/maven/toolchain/DefaultToolchain.java @@ -26,7 +26,6 @@ import org.apache.maven.toolchain.model.ToolchainModel; import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Default abstract toolchain implementation, to be used as base class for any toolchain implementation @@ -69,27 +68,6 @@ protected DefaultToolchain(ToolchainModel model, String type, Logger logger) { this.type = type; } - /** - * @param model the model, must not be {@code null} - * @param logger the logger (ignored, an SLF4J logger is used instead) - * @deprecated Use {@link #DefaultToolchain(ToolchainModel, Logger)} with an SLF4J logger instead. - */ - @Deprecated(since = "4.0.0") - protected DefaultToolchain(ToolchainModel model, org.codehaus.plexus.logging.Logger logger) { - this(model, LoggerFactory.getLogger(DefaultToolchain.class)); - } - - /** - * @param model the model, must not be {@code null} - * @param type the type - * @param logger the logger (ignored, an SLF4J logger is used instead) - * @deprecated Use {@link #DefaultToolchain(ToolchainModel, String, Logger)} with an SLF4J logger instead. - */ - @Deprecated(since = "4.0.0") - protected DefaultToolchain(ToolchainModel model, String type, org.codehaus.plexus.logging.Logger logger) { - this(model, type, LoggerFactory.getLogger(DefaultToolchain.class)); - } - @Override public final String getType() { return type != null ? type : model.getType(); diff --git a/compat/maven-model-builder/src/test/resources/poms/inheritance/plugin-configuration-child.xml b/compat/maven-model-builder/src/test/resources/poms/inheritance/plugin-configuration-child.xml index 7f5451948d26..e39bf70e5cc9 100644 --- a/compat/maven-model-builder/src/test/resources/poms/inheritance/plugin-configuration-child.xml +++ b/compat/maven-model-builder/src/test/resources/poms/inheritance/plugin-configuration-child.xml @@ -58,20 +58,4 @@ under the License. - - - - - MNG-5115-2 - - - inherited-append - - from-child - - - - - - diff --git a/compat/maven-model-builder/src/test/resources/poms/inheritance/plugin-configuration-expected.xml b/compat/maven-model-builder/src/test/resources/poms/inheritance/plugin-configuration-expected.xml index 9c2d0deed66e..b969d4bd7c2c 100644 --- a/compat/maven-model-builder/src/test/resources/poms/inheritance/plugin-configuration-expected.xml +++ b/compat/maven-model-builder/src/test/resources/poms/inheritance/plugin-configuration-expected.xml @@ -82,18 +82,6 @@ under the License. - - MNG-5115-2 - - - inherited-append - - from-child - to-be-inherited - - - - diff --git a/compat/maven-model-builder/src/test/resources/poms/inheritance/plugin-configuration-parent.xml b/compat/maven-model-builder/src/test/resources/poms/inheritance/plugin-configuration-parent.xml index dd134650109c..4ecb22475451 100644 --- a/compat/maven-model-builder/src/test/resources/poms/inheritance/plugin-configuration-parent.xml +++ b/compat/maven-model-builder/src/test/resources/poms/inheritance/plugin-configuration-parent.xml @@ -84,17 +84,6 @@ under the License. - - MNG-5115-2 - - - inherited-append - - to-be-inherited - - - - diff --git a/doap_Maven.rdf b/doap_Maven.rdf index 9ee6b1f77fe6..dc7fd4ee1fe0 100644 --- a/doap_Maven.rdf +++ b/doap_Maven.rdf @@ -110,17 +110,6 @@ under the License. Latest stable release - 2026-05-13 - 3.9.16 - https://archive.apache.org/dist/maven/maven-3/3.9.16/binaries/apache-maven-3.9.16-bin.zip - https://archive.apache.org/dist/maven/maven-3/3.9.16/binaries/apache-maven-3.9.16-bin.tar.gz - https://archive.apache.org/dist/maven/maven-3/3.9.16/source/apache-maven-3.9.16-src.zip - https://archive.apache.org/dist/maven/maven-3/3.9.16/source/apache-maven-3.9.16-src.tar.gz - - - - - Apache Maven 3.9.15 2026-04-13 3.9.15 https://archive.apache.org/dist/maven/maven-3/3.9.15/binaries/apache-maven-3.9.15-bin.zip diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/CompatibilityFixStrategy.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/CompatibilityFixStrategy.java index 88b47193fd43..8739d1766793 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/CompatibilityFixStrategy.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/CompatibilityFixStrategy.java @@ -77,10 +77,6 @@ public class CompatibilityFixStrategy extends AbstractUpgradeStrategy { private static final Pattern EXPRESSION_PATTERN = Pattern.compile("\\$\\{([^}]+)}"); - private static final Set VALID_COMBINE_SELF_VALUES = Set.of(COMBINE_OVERRIDE, COMBINE_MERGE, "remove"); - - private static final Set VALID_COMBINE_CHILDREN_VALUES = Set.of(COMBINE_APPEND, COMBINE_MERGE); - @Override public boolean isApplicable(UpgradeContext context) { UpgradeOptions options = getOptions(context); @@ -129,7 +125,6 @@ public UpgradeResult doApply(UpgradeContext context, Map pomMap) Set errorPoms = new HashSet<>(); Set allDefinedProperties = collectAllDefinedProperties(pomMap); - allDefinedProperties.addAll(collectEffectiveProperties(context, pomMap)); for (Map.Entry entry : pomMap.entrySet()) { Path pomPath = entry.getKey(); @@ -149,7 +144,6 @@ public UpgradeResult doApply(UpgradeContext context, Map pomMap) hasIssues |= fixUnsupportedRepositoryExpressions(pomDocument, context); hasIssues |= fixIncorrectParentRelativePaths(pomDocument, pomPath, pomMap, context); hasIssues |= fixUndefinedPropertyExpressions(pomDocument, allDefinedProperties, context); - hasIssues |= fixUndefinedPropertyExpressionsInRepositories(pomDocument, allDefinedProperties, context); if (hasIssues) { context.success("Maven 4 compatibility issues fixed"); @@ -170,44 +164,44 @@ public UpgradeResult doApply(UpgradeContext context, Map pomMap) /** * Fixes unsupported combine.children attribute values. - * Maven 4 only supports 'append' and 'merge' (default is merge). - * Invalid values are removed entirely since Maven 3 silently ignored them. + * Maven 4 only supports 'append' and 'merge', not 'override'. */ private boolean fixUnsupportedCombineChildrenAttributes(Document pomDocument, UpgradeContext context) { + boolean fixed = false; Element root = pomDocument.root(); - List invalidElements = findElementsWithInvalidAttribute( - root, COMBINE_CHILDREN, VALID_COMBINE_CHILDREN_VALUES) - .toList(); - - for (Element element : invalidElements) { - String invalidValue = element.attribute(COMBINE_CHILDREN); - element.removeAttribute(COMBINE_CHILDREN); - context.detail( - "Fixed: removed invalid " + COMBINE_CHILDREN + "='" + invalidValue + "' from " + element.name()); - } + // Find all elements with combine.children="override" and change to "merge" + long fixedCombineChildrenCount = findElementsWithAttribute(root, COMBINE_CHILDREN, COMBINE_OVERRIDE) + .peek(element -> { + element.attributeObject(COMBINE_CHILDREN).value(COMBINE_MERGE); + context.detail("Fixed: " + COMBINE_CHILDREN + "='" + COMBINE_OVERRIDE + "' → '" + COMBINE_MERGE + + "' in " + element.name()); + }) + .count(); + fixed |= fixedCombineChildrenCount > 0; - return !invalidElements.isEmpty(); + return fixed; } /** * Fixes unsupported combine.self attribute values. - * Maven 4 only supports 'override', 'merge', and 'remove' (default is merge). - * Invalid values are removed entirely since Maven 3 silently ignored them. + * Maven 4 only supports 'override', 'merge', and 'remove' (default is merge), not 'append'. */ private boolean fixUnsupportedCombineSelfAttributes(Document pomDocument, UpgradeContext context) { + boolean fixed = false; Element root = pomDocument.root(); - List invalidElements = findElementsWithInvalidAttribute(root, COMBINE_SELF, VALID_COMBINE_SELF_VALUES) - .toList(); - - for (Element element : invalidElements) { - String invalidValue = element.attribute(COMBINE_SELF); - element.removeAttribute(COMBINE_SELF); - context.detail("Fixed: removed invalid " + COMBINE_SELF + "='" + invalidValue + "' from " + element.name()); - } + // Find all elements with combine.self="append" and change to "merge" + long fixedCombineSelfCount = findElementsWithAttribute(root, COMBINE_SELF, COMBINE_APPEND) + .peek(element -> { + element.attributeObject(COMBINE_SELF).value(COMBINE_MERGE); + context.detail("Fixed: " + COMBINE_SELF + "='" + COMBINE_APPEND + "' → '" + COMBINE_MERGE + "' in " + + element.name()); + }) + .count(); + fixed |= fixedCombineSelfCount > 0; - return !invalidElements.isEmpty(); + return fixed; } /** @@ -381,19 +375,6 @@ private void collectPropertiesFromDom(Document document, Set properties) propsElement.childElements().forEach(child -> properties.add(child.name()))))); } - private Set collectEffectiveProperties(UpgradeContext context, Map pomMap) { - Set properties = new HashSet<>(); - for (Path pomPath : pomMap.keySet()) { - try { - org.apache.maven.api.model.Model effectiveModel = buildEffectiveModel(pomPath); - properties.addAll(effectiveModel.getProperties().keySet()); - } catch (Exception e) { - context.debug("Failed to build effective model for " + pomPath + ": " + e.getMessage()); - } - } - return properties; - } - /** * Fixes dependencies with undefined property expressions by commenting them out. */ @@ -431,89 +412,6 @@ private boolean fixUndefinedPropertyExpressions( .reduce(false, Boolean::logicalOr); } - /** - * Fixes repositories with undefined property expressions by commenting them out. - */ - private boolean fixUndefinedPropertyExpressionsInRepositories( - Document pomDocument, Set allDefinedProperties, UpgradeContext context) { - Element root = pomDocument.root(); - - Stream repositoryContainers = Stream.concat( - Stream.of( - new RepositoryContainer( - root.childElement(REPOSITORIES).orElse(null), REPOSITORY, REPOSITORIES), - new RepositoryContainer( - root.childElement(PLUGIN_REPOSITORIES).orElse(null), - PLUGIN_REPOSITORY, - PLUGIN_REPOSITORIES)) - .filter(c -> c.element != null), - root.childElement(PROFILES).stream() - .flatMap(profiles -> profiles.childElements(PROFILE)) - .flatMap(profile -> Stream.of( - new RepositoryContainer( - profile.childElement(REPOSITORIES) - .orElse(null), - REPOSITORY, - "profile repositories"), - new RepositoryContainer( - profile.childElement(PLUGIN_REPOSITORIES) - .orElse(null), - PLUGIN_REPOSITORY, - "profile pluginRepositories")) - .filter(c -> c.element != null))); - - return repositoryContainers - .map(c -> fixUndefinedPropertyExpressionsInRepositorySection( - c.element, c.elementType, allDefinedProperties, pomDocument, context, c.sectionName)) - .reduce(false, Boolean::logicalOr); - } - - private record RepositoryContainer(Element element, String elementType, String sectionName) {} - - private boolean fixUndefinedPropertyExpressionsInRepositorySection( - Element repositoriesElement, - String elementType, - Set allDefinedProperties, - Document pomDocument, - UpgradeContext context, - String sectionName) { - boolean fixed = false; - List repositories = - repositoriesElement.childElements(elementType).toList(); - Editor editor = new Editor(pomDocument); - - for (Element repository : repositories) { - Set undefinedProps = findUndefinedPropertiesInRepository(repository, allDefinedProperties); - if (!undefinedProps.isEmpty()) { - String propLabel = undefinedProps.size() > 1 ? "properties" : "property"; - String propsStr = "'" + String.join("', '", undefinedProps) + "'"; - - Comment comment = editor.commentOutElement(repository); - String elementXml = comment.content().trim(); - comment.content( - " mvnup: commented out - undefined " + propLabel + " " + propsStr + "\n" + elementXml + " "); - - context.detail("Fixed: Commented out " + elementType + " with undefined " + propLabel + " " + propsStr - + " in " + sectionName); - fixed = true; - } - } - - return fixed; - } - - private Set findUndefinedPropertiesInRepository(Element repository, Set allDefinedProperties) { - Set undefinedProperties = new HashSet<>(); - - String id = repository.childText("id"); - String url = repository.childText("url"); - - collectUndefinedExpressions(id, allDefinedProperties, undefinedProperties); - collectUndefinedExpressions(url, allDefinedProperties, undefinedProperties); - - return undefinedProperties; - } - /** * Fixes undefined property expressions in a specific dependencies section. */ @@ -616,20 +514,6 @@ private Stream findElementsWithAttribute(Element element, String attrib .flatMap(child -> findElementsWithAttribute(child, attributeName, attributeValue))); } - /** - * Recursively finds all elements with an attribute whose value is not in the set of valid values. - */ - private Stream findElementsWithInvalidAttribute( - Element element, String attributeName, Set validValues) { - return Stream.concat( - Stream.of(element).filter(e -> { - String attr = e.attribute(attributeName); - return attr != null && !validValues.contains(attr); - }), - element.childElements() - .flatMap(child -> findElementsWithInvalidAttribute(child, attributeName, validValues))); - } - /** * Helper methods extracted from BaseUpgradeGoal for compatibility fixes. */ diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/PluginUpgradeStrategy.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/PluginUpgradeStrategy.java index 3402b78c2fa9..6a6d16cb1510 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/PluginUpgradeStrategy.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/PluginUpgradeStrategy.java @@ -67,8 +67,9 @@ public class PluginUpgradeStrategy extends AbstractUpgradeStrategy { private static final List PLUGIN_UPGRADES = List.of( new PluginUpgrade( - DEFAULT_MAVEN_PLUGIN_GROUP_ID, "maven-compiler-plugin", "3.2", MAVEN_4_COMPATIBILITY_REASON), - new PluginUpgrade("org.codehaus.mojo", "exec-maven-plugin", "3.5.0", MAVEN_4_COMPATIBILITY_REASON), + DEFAULT_MAVEN_PLUGIN_GROUP_ID, "maven-compiler-plugin", "3.2.0", MAVEN_4_COMPATIBILITY_REASON), + new PluginUpgrade( + DEFAULT_MAVEN_PLUGIN_GROUP_ID, "maven-exec-plugin", "3.2.0", MAVEN_4_COMPATIBILITY_REASON), new PluginUpgrade( DEFAULT_MAVEN_PLUGIN_GROUP_ID, "maven-enforcer-plugin", "3.5.0", MAVEN_4_COMPATIBILITY_REASON), new PluginUpgrade("org.codehaus.mojo", "flatten-maven-plugin", "1.2.7", MAVEN_4_COMPATIBILITY_REASON), @@ -91,8 +92,8 @@ public class PluginUpgradeStrategy extends AbstractUpgradeStrategy { new PluginUpgrade( "net.alchim31.maven", "scala-maven-plugin", - "4.9.5", - "Versions before 4.9.5 call add() on immutable lists returned by Maven 4 API"), + "4.9.2", + "Versions before 4.9.2 call add() on immutable lists returned by Maven 4 API"), new PluginUpgrade( DEFAULT_MAVEN_PLUGIN_GROUP_ID, "maven-resources-plugin", @@ -130,9 +131,10 @@ public UpgradeResult doApply(UpgradeContext context, Map pomMap) Path tempDir = createTempProjectStructure(context, pomMap); // Phase 2: For each POM, build effective model using the session and analyze plugins - PluginAnalysisResults analysisResults = analyzePluginsUsingEffectiveModels(context, pomMap, tempDir); + Map> pluginsNeedingManagement = + analyzePluginsUsingEffectiveModels(context, pomMap, tempDir); - // Phase 3: Add plugin management and direct overrides to the last local parent in hierarchy + // Phase 3: Add plugin management to the last local parent in hierarchy for (Map.Entry entry : pomMap.entrySet()) { Path pomPath = entry.getKey(); Document pomDocument = entry.getValue(); @@ -148,21 +150,14 @@ public UpgradeResult doApply(UpgradeContext context, Map pomMap) hasUpgrades |= upgradePluginsInDocument(pomDocument, context); // Add plugin management based on effective model analysis - Set pluginsForManagement = - analysisResults.pluginsNeedingManagement().get(pomPath); - if (pluginsForManagement != null && !pluginsForManagement.isEmpty()) { - hasUpgrades |= - addPluginManagementForEffectivePlugins(context, pomDocument, pluginsForManagement); + // Note: pluginsNeedingManagement only contains entries for POMs that should receive plugin + // management + // (i.e., the "last local parent" for each plugin that needs management) + Set pluginsForThisPom = pluginsNeedingManagement.get(pomPath); + if (pluginsForThisPom != null && !pluginsForThisPom.isEmpty()) { + hasUpgrades |= addPluginManagementForEffectivePlugins(context, pomDocument, pluginsForThisPom); context.detail("Added plugin management to " + pomPath + " (target parent for " - + pluginsForManagement.size() + " plugins)"); - } - - // Add direct plugin overrides in build/plugins for inherited plugins - // whose versions cannot be overridden via pluginManagement alone - Set pluginsForDirectOverride = - analysisResults.pluginsNeedingDirectOverride().get(pomPath); - if (pluginsForDirectOverride != null && !pluginsForDirectOverride.isEmpty()) { - hasUpgrades |= addDirectPluginOverrides(context, pomDocument, pluginsForDirectOverride); + + pluginsForThisPom.size() + " plugins)"); } if (hasUpgrades) { @@ -242,7 +237,7 @@ private Map getPluginUpgradesMap() { new PluginUpgradeInfo(DEFAULT_MAVEN_PLUGIN_GROUP_ID, "maven-compiler-plugin", "3.2")); upgrades.put( "org.codehaus.mojo:exec-maven-plugin", - new PluginUpgradeInfo("org.codehaus.mojo", "exec-maven-plugin", "3.5.0")); + new PluginUpgradeInfo("org.codehaus.mojo", "exec-maven-plugin", "3.2.0")); upgrades.put( DEFAULT_MAVEN_PLUGIN_GROUP_ID + ":maven-enforcer-plugin", new PluginUpgradeInfo(DEFAULT_MAVEN_PLUGIN_GROUP_ID, "maven-enforcer-plugin", "3.5.0")); @@ -266,7 +261,7 @@ private Map getPluginUpgradesMap() { new PluginUpgradeInfo(DEFAULT_MAVEN_PLUGIN_GROUP_ID, "maven-surefire-report-plugin", "3.5.2")); upgrades.put( "net.alchim31.maven:scala-maven-plugin", - new PluginUpgradeInfo("net.alchim31.maven", "scala-maven-plugin", "4.9.5")); + new PluginUpgradeInfo("net.alchim31.maven", "scala-maven-plugin", "4.9.2")); upgrades.put( DEFAULT_MAVEN_PLUGIN_GROUP_ID + ":maven-resources-plugin", new PluginUpgradeInfo(DEFAULT_MAVEN_PLUGIN_GROUP_ID, "maven-resources-plugin", "3.3.1")); @@ -467,13 +462,11 @@ public static List getPluginUpgrades() { /** * Analyzes plugins using effective models built from the temp directory. - * Returns analysis results with two maps: plugins needing pluginManagement entries - * and plugins needing direct build/plugins overrides. + * Returns a map of POM path to the set of plugin keys that need management. */ - private PluginAnalysisResults analyzePluginsUsingEffectiveModels( + private Map> analyzePluginsUsingEffectiveModels( UpgradeContext context, Map pomMap, Path tempDir) { - Map> managementResult = new HashMap<>(); - Map> directOverrideResult = new HashMap<>(); + Map> result = new HashMap<>(); Map pluginUpgrades = getPluginUpgradesAsMap(); for (Map.Entry entry : pomMap.entrySet()) { @@ -486,27 +479,20 @@ private PluginAnalysisResults analyzePluginsUsingEffectiveModels( Path tempPomPath = tempDir.resolve(relativePath); // Build effective model using Maven 4 API - PluginAnalysis analysis = analyzeEffectiveModelForPlugins(context, tempPomPath, pluginUpgrades); + Set pluginsNeedingUpgrade = + analyzeEffectiveModelForPlugins(context, tempPomPath, pluginUpgrades); // Determine where to add plugin management (last local parent) - Path targetPom = + Path targetPomForManagement = findLastLocalParentForPluginManagement(context, tempPomPath, pomMap, tempDir, commonRoot); - if (targetPom != null) { - managementResult - .computeIfAbsent(targetPom, k -> new HashSet<>()) - .addAll(analysis.needsManagement()); - directOverrideResult - .computeIfAbsent(targetPom, k -> new HashSet<>()) - .addAll(analysis.needsDirectOverride()); - - if (!analysis.needsManagement().isEmpty()) { - context.debug("Will add plugin management to " + targetPom + " for plugins: " - + analysis.needsManagement()); - } - if (!analysis.needsDirectOverride().isEmpty()) { - context.debug("Will add direct plugin overrides to " + targetPom + " for plugins: " - + analysis.needsDirectOverride()); + if (targetPomForManagement != null) { + result.computeIfAbsent(targetPomForManagement, k -> new HashSet<>()) + .addAll(pluginsNeedingUpgrade); + + if (!pluginsNeedingUpgrade.isEmpty()) { + context.debug("Will add plugin management to " + targetPomForManagement + " for plugins: " + + pluginsNeedingUpgrade); } } @@ -515,7 +501,7 @@ private PluginAnalysisResults analyzePluginsUsingEffectiveModels( } } - return new PluginAnalysisResults(managementResult, directOverrideResult); + return result; } /** @@ -527,7 +513,7 @@ private Map getPluginUpgradesAsMap() { upgrade -> upgrade.groupId() + ":" + upgrade.artifactId(), upgrade -> upgrade)); } - private PluginAnalysis analyzeEffectiveModelForPlugins( + private Set analyzeEffectiveModelForPlugins( UpgradeContext context, Path tempPomPath, Map pluginUpgrades) { Model effectiveModel = buildEffectiveModel(tempPomPath); return analyzePluginsFromEffectiveModel(context, effectiveModel, pluginUpgrades); @@ -535,27 +521,13 @@ private PluginAnalysis analyzeEffectiveModelForPlugins( /** * Analyzes plugins from the effective model and determines which ones need upgrades. - * Separates plugins into those overridable via pluginManagement and those requiring - * a direct build/plugins entry (because the version is set explicitly in an inherited - * parent's build/plugins, not via pluginManagement). */ - private PluginAnalysis analyzePluginsFromEffectiveModel( + private Set analyzePluginsFromEffectiveModel( UpgradeContext context, Model effectiveModel, Map pluginUpgrades) { - Set needsManagement = new HashSet<>(); - Set needsDirectOverride = new HashSet<>(); + Set pluginsNeedingUpgrade = new HashSet<>(); Build build = effectiveModel.getBuild(); if (build != null) { - // Collect managed plugin versions for comparison - Map managedVersions = new HashMap<>(); - PluginManagement pluginManagement = build.getPluginManagement(); - if (pluginManagement != null) { - for (Plugin plugin : pluginManagement.getPlugins()) { - String pluginKey = getPluginKey(plugin); - managedVersions.put(pluginKey, plugin.getVersion()); - } - } - // Check build/plugins - these are the actual plugins used in the build for (Plugin plugin : build.getPlugins()) { String pluginKey = getPluginKey(plugin); @@ -563,33 +535,23 @@ private PluginAnalysis analyzePluginsFromEffectiveModel( if (upgrade != null) { String effectiveVersion = plugin.getVersion(); if (isVersionBelow(effectiveVersion, upgrade.minVersion())) { - needsManagement.add(pluginKey); - String managedVersion = managedVersions.get(pluginKey); - if (managedVersion == null || !managedVersion.equals(effectiveVersion)) { - // Version differs from pluginManagement (or not in PM at all): - // the parent sets an explicit version in build/plugins that - // pluginManagement alone cannot override - needsDirectOverride.add(pluginKey); - context.debug("Plugin " + pluginKey + " version " + effectiveVersion - + " has explicit version in inherited build/plugins" - + " — needs direct override to " + upgrade.minVersion()); - } else { - context.debug("Plugin " + pluginKey + " version " + effectiveVersion - + " is managed via pluginManagement — needs upgrade to " + upgrade.minVersion()); - } + pluginsNeedingUpgrade.add(pluginKey); + context.debug("Plugin " + pluginKey + " version " + effectiveVersion + " needs upgrade to " + + upgrade.minVersion()); } } } - // Check build/pluginManagement/plugins for managed-only plugins + // Check build/pluginManagement/plugins - these provide version management + PluginManagement pluginManagement = build.getPluginManagement(); if (pluginManagement != null) { for (Plugin plugin : pluginManagement.getPlugins()) { String pluginKey = getPluginKey(plugin); PluginUpgrade upgrade = pluginUpgrades.get(pluginKey); - if (upgrade != null && !needsManagement.contains(pluginKey)) { + if (upgrade != null) { String effectiveVersion = plugin.getVersion(); if (isVersionBelow(effectiveVersion, upgrade.minVersion())) { - needsManagement.add(pluginKey); + pluginsNeedingUpgrade.add(pluginKey); context.debug("Managed plugin " + pluginKey + " version " + effectiveVersion + " needs upgrade to " + upgrade.minVersion()); } @@ -598,7 +560,7 @@ private PluginAnalysis analyzePluginsFromEffectiveModel( } } - return new PluginAnalysis(needsManagement, needsDirectOverride); + return pluginsNeedingUpgrade; } /** @@ -770,49 +732,6 @@ private void addPluginManagementEntryFromUpgrade( + upgrade.minVersion() + " (found through effective model analysis)"); } - /** - * Adds direct plugin entries in build/plugins for plugins inherited from remote parents. - * This is necessary when a parent POM sets an explicit version in its build/plugins - * that pluginManagement alone cannot override. - */ - private boolean addDirectPluginOverrides(UpgradeContext context, Document pomDocument, Set pluginKeys) { - Map pluginUpgrades = getPluginUpgradesAsMap(); - boolean hasUpgrades = false; - - Element root = pomDocument.root(); - - Element buildElement = root.childElement(BUILD).orElse(null); - if (buildElement == null) { - buildElement = DomUtils.insertNewElement(BUILD, root); - } - - Element pluginsElement = buildElement.childElement(PLUGINS).orElse(null); - if (pluginsElement == null) { - pluginsElement = DomUtils.insertNewElement(PLUGINS, buildElement); - } - - for (String pluginKey : pluginKeys) { - PluginUpgrade upgrade = pluginUpgrades.get(pluginKey); - if (upgrade != null) { - if (!isPluginAlreadyManagedInElement(pluginsElement, upgrade)) { - DomUtils.createPlugin( - pluginsElement, upgrade.groupId(), upgrade.artifactId(), upgrade.minVersion()); - hasUpgrades = true; - context.detail("Added " + upgrade.groupId() + ":" + upgrade.artifactId() + " version " - + upgrade.minVersion() - + " in build/plugins (overrides version locked by parent)"); - } - } - } - - return hasUpgrades; - } - - private record PluginAnalysis(Set needsManagement, Set needsDirectOverride) {} - - private record PluginAnalysisResults( - Map> pluginsNeedingManagement, Map> pluginsNeedingDirectOverride) {} - /** * Holds plugin upgrade information for Maven 4 compatibility. * This class contains the minimum version requirements for plugins diff --git a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/CompatibilityFixStrategyTest.java b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/CompatibilityFixStrategyTest.java index 3b8e7fc9a028..00dacbb1c11b 100644 --- a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/CompatibilityFixStrategyTest.java +++ b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/CompatibilityFixStrategyTest.java @@ -140,416 +140,6 @@ void shouldHandleAllOptionsDisabled() { } } - @Nested - @DisplayName("Unsupported combine.children Attribute Fixes") - class UnsupportedCombineChildrenFixesTests { - - @Test - @DisplayName("should remove combine.children='override' attribute") - void shouldRemoveCombineChildrenOverride() throws Exception { - String pomXml = """ - - - 4.0.0 - test - test - 1.0.0 - - - - org.apache.maven.plugins - maven-compiler-plugin - - - -Xlint:all - - - - - - - """; - - Document document = Document.of(pomXml); - Map pomMap = Map.of(Paths.get("pom.xml"), document); - - UpgradeContext context = createMockContext(); - UpgradeResult result = strategy.doApply(context, pomMap); - - assertTrue(result.success(), "Compatibility fix should succeed"); - assertTrue(result.modifiedCount() > 0, "Should have fixed combine.children attribute"); - - String xml = DomUtils.toXml(document); - assertFalse(xml.contains("combine.children"), "combine.children attribute should be removed entirely"); - assertTrue(xml.contains("compilerArgs"), "Element should still exist"); - } - - @Test - @DisplayName("should remove any invalid combine.children value") - void shouldRemoveAnyCombineChildrenInvalidValue() throws Exception { - String pomXml = """ - - - 4.0.0 - test - test - 1.0.0 - - - - org.apache.maven.plugins - maven-compiler-plugin - - - -Xlint:all - - - - - - - """; - - Document document = Document.of(pomXml); - Map pomMap = Map.of(Paths.get("pom.xml"), document); - - UpgradeContext context = createMockContext(); - UpgradeResult result = strategy.doApply(context, pomMap); - - assertTrue(result.success(), "Compatibility fix should succeed"); - assertTrue(result.modifiedCount() > 0, "Should have fixed combine.children attribute"); - - String xml = DomUtils.toXml(document); - assertFalse(xml.contains("combine.children"), "combine.children attribute should be removed entirely"); - } - - @Test - @DisplayName("should not remove valid combine.children values") - void shouldNotRemoveValidCombineChildrenValues() throws Exception { - String pomXml = """ - - - 4.0.0 - test - test - 1.0.0 - - - - org.apache.maven.plugins - maven-compiler-plugin - - - -Xlint:all - - - **/*.java - - - - - - - """; - - Document document = Document.of(pomXml); - Map pomMap = Map.of(Paths.get("pom.xml"), document); - - UpgradeContext context = createMockContext(); - UpgradeResult result = strategy.doApply(context, pomMap); - - assertTrue(result.success(), "Compatibility fix should succeed"); - assertEquals(0, result.modifiedCount(), "Should not have modified any POMs"); - - String xml = DomUtils.toXml(document); - assertTrue(xml.contains("combine.children=\"append\""), "append should be preserved"); - assertTrue(xml.contains("combine.children=\"merge\""), "merge should be preserved"); - } - - @Test - @DisplayName("should remove invalid combine.children in profile plugin configuration") - void shouldRemoveInvalidCombineChildrenInProfile() throws Exception { - String pomXml = """ - - - 4.0.0 - test - test - 1.0.0 - - - release - - - - org.apache.maven.plugins - maven-assembly-plugin - - - src - - - - - - - - - """; - - Document document = Document.of(pomXml); - Map pomMap = Map.of(Paths.get("pom.xml"), document); - - UpgradeContext context = createMockContext(); - UpgradeResult result = strategy.doApply(context, pomMap); - - assertTrue(result.success(), "Compatibility fix should succeed"); - assertTrue(result.modifiedCount() > 0, "Should have fixed combine.children in profile"); - - String xml = DomUtils.toXml(document); - assertFalse(xml.contains("combine.children"), "combine.children attribute should be removed from profile"); - } - } - - @Nested - @DisplayName("Unsupported combine.self Attribute Fixes") - class UnsupportedCombineSelfFixesTests { - - @Test - @DisplayName("should remove combine.self='append' attribute") - void shouldRemoveCombineSelfAppend() throws Exception { - String pomXml = """ - - - 4.0.0 - test - test - 1.0.0 - - - - org.apache.maven.plugins - maven-assembly-plugin - - - jar-with-dependencies - - - - - - - """; - - Document document = Document.of(pomXml); - Map pomMap = Map.of(Paths.get("pom.xml"), document); - - UpgradeContext context = createMockContext(); - UpgradeResult result = strategy.doApply(context, pomMap); - - assertTrue(result.success(), "Compatibility fix should succeed"); - assertTrue(result.modifiedCount() > 0, "Should have fixed combine.self attribute"); - - String xml = DomUtils.toXml(document); - assertFalse(xml.contains("combine.self"), "combine.self attribute should be removed entirely"); - assertTrue(xml.contains("descriptorRefs"), "Element should still exist"); - } - - @Test - @DisplayName("should remove any invalid combine.self value") - void shouldRemoveAnyCombineSelfInvalidValue() throws Exception { - String pomXml = """ - - - 4.0.0 - test - test - 1.0.0 - - - - org.apache.maven.plugins - maven-compiler-plugin - - - -Xlint:all - - - - - - - """; - - Document document = Document.of(pomXml); - Map pomMap = Map.of(Paths.get("pom.xml"), document); - - UpgradeContext context = createMockContext(); - UpgradeResult result = strategy.doApply(context, pomMap); - - assertTrue(result.success(), "Compatibility fix should succeed"); - assertTrue(result.modifiedCount() > 0, "Should have fixed combine.self attribute"); - - String xml = DomUtils.toXml(document); - assertFalse(xml.contains("combine.self"), "combine.self attribute should be removed entirely"); - } - - @Test - @DisplayName("should not remove valid combine.self values") - void shouldNotRemoveValidCombineSelfValues() throws Exception { - String pomXml = """ - - - 4.0.0 - test - test - 1.0.0 - - - - org.apache.maven.plugins - maven-compiler-plugin - - 11 - 11 - - - - - - - """; - - Document document = Document.of(pomXml); - Map pomMap = Map.of(Paths.get("pom.xml"), document); - - UpgradeContext context = createMockContext(); - UpgradeResult result = strategy.doApply(context, pomMap); - - assertTrue(result.success(), "Compatibility fix should succeed"); - assertEquals(0, result.modifiedCount(), "Should not have modified any POMs"); - - String xml = DomUtils.toXml(document); - assertTrue(xml.contains("combine.self=\"override\""), "override should be preserved"); - assertTrue(xml.contains("combine.self=\"merge\""), "merge should be preserved"); - assertTrue(xml.contains("combine.self=\"remove\""), "remove should be preserved"); - } - - @Test - @DisplayName("should remove invalid combine.self in profile plugin configuration") - void shouldRemoveInvalidCombineSelfInProfile() throws Exception { - String pomXml = """ - - - 4.0.0 - test - test - 1.0.0 - - - release - - - - org.apache.maven.plugins - maven-assembly-plugin - - - src - - - - - - - - - """; - - Document document = Document.of(pomXml); - Map pomMap = Map.of(Paths.get("pom.xml"), document); - - UpgradeContext context = createMockContext(); - UpgradeResult result = strategy.doApply(context, pomMap); - - assertTrue(result.success(), "Compatibility fix should succeed"); - assertTrue(result.modifiedCount() > 0, "Should have fixed combine.self in profile"); - - String xml = DomUtils.toXml(document); - assertFalse(xml.contains("combine.self"), "combine.self attribute should be removed from profile"); - } - - @Test - @DisplayName("should remove all occurrences of invalid combine.self across the POM") - void shouldRemoveAllOccurrencesOfInvalidCombineSelf() throws Exception { - String pomXml = """ - - - 4.0.0 - test - test - 1.0.0 - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - -Xlint:all - - - - - - - - org.apache.maven.plugins - maven-assembly-plugin - - - jar-with-dependencies - - - - - - - - release - - - - org.apache.maven.plugins - maven-assembly-plugin - - - src - - - - - - - - - """; - - Document document = Document.of(pomXml); - Map pomMap = Map.of(Paths.get("pom.xml"), document); - - UpgradeContext context = createMockContext(); - UpgradeResult result = strategy.doApply(context, pomMap); - - assertTrue(result.success(), "Compatibility fix should succeed"); - assertTrue(result.modifiedCount() > 0, "Should have fixed combine.self attributes"); - - String xml = DomUtils.toXml(document); - assertFalse(xml.contains("combine.self"), "All combine.self attributes should be removed"); - } - } - @Nested @DisplayName("Duplicate Dependency Fixes") class DuplicateDependencyFixesTests { @@ -1329,307 +919,6 @@ void shouldRecognizePropertyFromGrandparent() throws Exception { deps.childElements("dependency").count(), "Dependency should not be commented out when property is inherited from grandparent"); } - - @Test - @DisplayName("should not comment out when property is defined in external parent not in reactor") - void shouldNotCommentOutWhenPropertyFromExternalParentNotInReactor() throws Exception { - String externalParentPom = """ - - - 4.0.0 - com.example - external-parent - 1.0.0 - pom - - 1.62.0 - - - """; - - String childPom = """ - - - 4.0.0 - - com.example - external-parent - 1.0.0 - ../external/pom.xml - - child - - - - org.apache.jackrabbit - oak-core - ${oak.version} - - - - - """; - - Path externalDir = Files.createDirectories(tempDir.resolve("external")); - Path externalParentPath = externalDir.resolve("pom.xml"); - Path childDir = Files.createDirectories(tempDir.resolve("child")); - Path childPomPath = childDir.resolve("pom.xml"); - - Files.writeString(externalParentPath, externalParentPom); - Files.writeString(childPomPath, childPom); - - Document childDoc = Document.of(childPom); - Map pomMap = Map.of(childPomPath, childDoc); - - UpgradeContext context = createMockContext(childDir); - strategy.doApply(context, pomMap); - - Element root = childDoc.root(); - Element depMgmt = DomUtils.findChildElement(root, "dependencyManagement"); - Element deps = DomUtils.findChildElement(depMgmt, "dependencies"); - assertEquals( - 1, - deps.childElements("dependency").count(), - "Managed dependency should not be commented out when property is inherited from external parent"); - } - - @Test - @DisplayName("should comment out when property is truly undefined even in effective model") - void shouldCommentOutWhenPropertyTrulyUndefinedInEffectiveModel() throws Exception { - String parentPom = """ - - - 4.0.0 - com.example - parent - 1.0.0 - pom - - """; - - String childPom = """ - - - 4.0.0 - - com.example - parent - 1.0.0 - ../pom.xml - - child - - - - com.google.guava - guava - ${truly.undefined.prop} - - - - - """; - - Path parentPath = tempDir.resolve("pom.xml"); - Path childDir = Files.createDirectories(tempDir.resolve("child")); - Path childPomPath = childDir.resolve("pom.xml"); - - Files.writeString(parentPath, parentPom); - Files.writeString(childPomPath, childPom); - - Document childDoc = Document.of(childPom); - Map pomMap = Map.of(childPomPath, childDoc); - - UpgradeContext context = createMockContext(childDir); - strategy.doApply(context, pomMap); - - String xml = DomUtils.toXml(childDoc); - assertTrue(xml.contains("mvnup: commented out"), "Should contain comment-out marker"); - assertTrue(xml.contains("truly.undefined.prop"), "Should mention the undefined property"); - } - } - - @Nested - @DisplayName("Undefined Property Expression in Repositories Fixes") - class UndefinedPropertyExpressionInRepositoriesTests { - - @Test - @DisplayName("should comment out repository with undefined property in id") - void shouldCommentOutRepositoryWithUndefinedPropertyInId() throws Exception { - String pomXml = """ - - - 4.0.0 - test - test - 1.0.0 - - - ${eclipseP2RepoId} - https://repo.example.com - - - - """; - - Document document = Document.of(pomXml); - Map pomMap = Map.of(Paths.get("pom.xml"), document); - - UpgradeContext context = createMockContext(); - UpgradeResult result = strategy.doApply(context, pomMap); - - assertTrue(result.success(), "Compatibility fix should succeed"); - assertTrue(result.modifiedCount() > 0, "Should have commented out repository"); - - String xml = DomUtils.toXml(document); - assertTrue(xml.contains("mvnup: commented out"), "Should contain comment-out marker"); - assertTrue(xml.contains("eclipseP2RepoId"), "Should mention the undefined property"); - - Element root = document.root(); - Element repos = DomUtils.findChildElement(root, "repositories"); - assertEquals(0, repos.childElements("repository").count(), "Should have no repository elements"); - } - - @Test - @DisplayName("should comment out repository with undefined property in url") - void shouldCommentOutRepositoryWithUndefinedPropertyInUrl() throws Exception { - String pomXml = """ - - - 4.0.0 - test - test - 1.0.0 - - - my-repo - ${undefinedBaseUrl}/releases - - - - """; - - Document document = Document.of(pomXml); - Map pomMap = Map.of(Paths.get("pom.xml"), document); - - UpgradeContext context = createMockContext(); - UpgradeResult result = strategy.doApply(context, pomMap); - - assertTrue(result.success(), "Compatibility fix should succeed"); - assertTrue(result.modifiedCount() > 0, "Should have commented out repository"); - - String xml = DomUtils.toXml(document); - assertTrue(xml.contains("mvnup: commented out"), "Should contain comment-out marker"); - assertTrue(xml.contains("undefinedBaseUrl"), "Should mention the undefined property"); - } - - @Test - @DisplayName("should not comment out repository with defined property") - void shouldNotCommentOutRepositoryWithDefinedProperty() throws Exception { - String pomXml = """ - - - 4.0.0 - test - test - 1.0.0 - - my-custom-repo - - - - ${repoId} - https://repo.example.com - - - - """; - - Document document = Document.of(pomXml); - Map pomMap = Map.of(Paths.get("pom.xml"), document); - - UpgradeContext context = createMockContext(); - strategy.doApply(context, pomMap); - - Element root = document.root(); - Element repos = DomUtils.findChildElement(root, "repositories"); - assertEquals(1, repos.childElements("repository").count(), "Repository should still be present"); - } - - @Test - @DisplayName("should comment out plugin repository with undefined property") - void shouldCommentOutPluginRepositoryWithUndefinedProperty() throws Exception { - String pomXml = """ - - - 4.0.0 - test - test - 1.0.0 - - - ${eclipseP2RepoId} - https://plugins.example.com - - - - """; - - Document document = Document.of(pomXml); - Map pomMap = Map.of(Paths.get("pom.xml"), document); - - UpgradeContext context = createMockContext(); - UpgradeResult result = strategy.doApply(context, pomMap); - - assertTrue(result.success(), "Compatibility fix should succeed"); - assertTrue(result.modifiedCount() > 0, "Should have commented out plugin repository"); - - String xml = DomUtils.toXml(document); - assertTrue(xml.contains("mvnup: commented out"), "Should contain comment-out marker"); - - Element root = document.root(); - Element repos = DomUtils.findChildElement(root, "pluginRepositories"); - assertEquals( - 0, repos.childElements("pluginRepository").count(), "Should have no pluginRepository elements"); - } - - @Test - @DisplayName("should not comment out repository with well-known property") - void shouldNotCommentOutRepositoryWithWellKnownProperty() throws Exception { - String pomXml = """ - - - 4.0.0 - test - test - 1.0.0 - - - local-repo - file://${project.basedir}/repo - - - - """; - - Document document = Document.of(pomXml); - Map pomMap = Map.of(Paths.get("pom.xml"), document); - - UpgradeContext context = createMockContext(); - strategy.doApply(context, pomMap); - - Element root = document.root(); - Element repos = DomUtils.findChildElement(root, "repositories"); - assertEquals(1, repos.childElements("repository").count(), "Repository should still be present"); - } } @Nested diff --git a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/PluginUpgradeStrategyTest.java b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/PluginUpgradeStrategyTest.java index a5824e7b9f05..d1961700e76b 100644 --- a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/PluginUpgradeStrategyTest.java +++ b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/PluginUpgradeStrategyTest.java @@ -18,8 +18,6 @@ */ package org.apache.maven.cling.invoker.mvnup.goals; -import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; @@ -523,8 +521,8 @@ void shouldNotUpgradePluginWithoutVersion() throws Exception { - org.codehaus.mojo - exec-maven-plugin + org.apache.maven.plugins + maven-exec-plugin @@ -555,8 +553,8 @@ void shouldNotUpgradeWhenPropertyNotFound() throws Exception { - org.codehaus.mojo - exec-maven-plugin + org.apache.maven.plugins + maven-exec-plugin ${exec.plugin.version} @@ -739,7 +737,7 @@ void shouldHavePredefinedPluginUpgrades() throws Exception { boolean hasCompilerPlugin = upgrades.stream().anyMatch(upgrade -> "maven-compiler-plugin".equals(upgrade.artifactId())); boolean hasExecPlugin = - upgrades.stream().anyMatch(upgrade -> "exec-maven-plugin".equals(upgrade.artifactId())); + upgrades.stream().anyMatch(upgrade -> "maven-exec-plugin".equals(upgrade.artifactId())); boolean hasSurefirePlugin = upgrades.stream().anyMatch(upgrade -> "maven-surefire-plugin".equals(upgrade.artifactId())); boolean hasFailsafePlugin = @@ -748,7 +746,7 @@ void shouldHavePredefinedPluginUpgrades() throws Exception { upgrades.stream().anyMatch(upgrade -> "maven-surefire-report-plugin".equals(upgrade.artifactId())); assertTrue(hasCompilerPlugin, "Should include maven-compiler-plugin upgrade"); - assertTrue(hasExecPlugin, "Should include exec-maven-plugin upgrade"); + assertTrue(hasExecPlugin, "Should include maven-exec-plugin upgrade"); assertTrue(hasSurefirePlugin, "Should include maven-surefire-plugin upgrade"); assertTrue(hasFailsafePlugin, "Should include maven-failsafe-plugin upgrade"); assertTrue(hasSurefireReportPlugin, "Should include maven-surefire-report-plugin upgrade"); @@ -778,59 +776,8 @@ void shouldDetectInheritedPluginsFromRemoteParent() throws Exception { // org.apache:apache:23 defines maven-enforcer-plugin:1.4.1 in pluginManagement. // A child POM that inherits from this parent should get pluginManagement overrides // added by mvnup for plugins that need Maven 4 compatibility upgrades. - String pomXml = """ - - - 4.0.0 - - org.apache - apache - 23 - - org.example - test-child - 1.0.0-SNAPSHOT - - """; - - Path tempDir = Files.createTempDirectory("mvnup-test-"); - try { - Files.createDirectories(tempDir.resolve(".mvn")); - Path pomPath = tempDir.resolve("pom.xml"); - Files.writeString(pomPath, pomXml); - - Document document = Document.of(pomXml); - Map pomMap = Map.of(pomPath, document); - - UpgradeContext context = createMockContext(); - UpgradeResult result = strategy.doApply(context, pomMap); - - assertTrue(result.success(), "Strategy should succeed"); - assertTrue(result.modifiedCount() > 0, "Should have added plugin management for inherited plugins"); - - String xml = DomUtils.toXml(document); - assertTrue( - xml.contains("maven-enforcer-plugin"), - "Should add pluginManagement for maven-enforcer-plugin inherited from parent"); - } finally { - try (var walk = Files.walk(tempDir)) { - walk.sorted(java.util.Comparator.reverseOrder()).forEach(p -> { - try { - Files.delete(p); - } catch (IOException ignored) { - } - }); - } - } - } - - @Test - @DisplayName("should not add direct build/plugins override when plugin version comes from pluginManagement") - void shouldNotAddDirectOverrideWhenVersionFromPluginManagement() throws Exception { - // org.apache:apache:23 has maven-enforcer-plugin in build/plugins WITHOUT - // an explicit version — the version (1.4.1) comes from pluginManagement. - // In this case, adding a pluginManagement override in the child is sufficient; - // no direct build/plugins entry should be added for enforcer. + // Uses an absolute path because the effective model analysis path resolution + // requires it to match between phases. String pomXml = """ @@ -854,102 +801,12 @@ void shouldNotAddDirectOverrideWhenVersionFromPluginManagement() throws Exceptio UpgradeResult result = strategy.doApply(context, pomMap); assertTrue(result.success(), "Strategy should succeed"); + assertTrue(result.modifiedCount() > 0, "Should have added plugin management for inherited plugins"); - Editor editor = new Editor(document); - Element root = editor.root(); - - // Verify pluginManagement entry exists for enforcer - Element pmPlugins = - root.path("build", "pluginManagement", "plugins").orElse(null); - assertNotNull(pmPlugins, "Should have pluginManagement/plugins"); - boolean hasEnforcerInPM = pmPlugins - .childElements("plugin") - .anyMatch(p -> "maven-enforcer-plugin" - .equals(p.childElement("artifactId") - .map(Element::textContentTrimmed) - .orElse(""))); - assertTrue(hasEnforcerInPM, "Should have enforcer in pluginManagement"); - - // Verify NO direct build/plugins entry for enforcer (PM override is sufficient) - Element buildPlugins = root.childElement("build") - .flatMap(b -> b.childElement("plugins")) - .orElse(null); - if (buildPlugins != null) { - boolean hasEnforcerInPlugins = buildPlugins - .childElements("plugin") - .anyMatch(p -> "maven-enforcer-plugin" - .equals(p.childElement("artifactId") - .map(Element::textContentTrimmed) - .orElse(""))); - assertFalse( - hasEnforcerInPlugins, - "Should NOT add enforcer in build/plugins when pluginManagement override suffices"); - } - } - - @Test - @DisplayName("should not duplicate plugin in build/plugins when already locally declared") - void shouldNotDuplicatePluginInBuildPluginsWhenAlreadyDeclared() throws Exception { - String pomXml = """ - - - 4.0.0 - - org.apache - apache - 23 - - org.example - test-child - 1.0.0-SNAPSHOT - - - - org.apache.maven.plugins - maven-enforcer-plugin - 3.0.0 - - - - - """; - - Document document = Document.of(pomXml); - Path pomPath = Paths.get("/project/pom.xml").toAbsolutePath(); - Map pomMap = Map.of(pomPath, document); - - UpgradeContext context = createMockContext(); - UpgradeResult result = strategy.doApply(context, pomMap); - - assertTrue(result.success(), "Strategy should succeed"); - - Editor editor = new Editor(document); - Element root = editor.root(); - Element buildPlugins = root.childElement("build") - .flatMap(b -> b.childElement("plugins")) - .orElse(null); - assertNotNull(buildPlugins, "Should have build/plugins section"); - - long enforcerCount = buildPlugins - .childElements("plugin") - .filter(p -> "maven-enforcer-plugin" - .equals(p.childElement("artifactId") - .map(Element::textContentTrimmed) - .orElse(""))) - .count(); - assertEquals(1, enforcerCount, "Should have exactly one maven-enforcer-plugin in build/plugins"); - - String version = buildPlugins - .childElements("plugin") - .filter(p -> "maven-enforcer-plugin" - .equals(p.childElement("artifactId") - .map(Element::textContentTrimmed) - .orElse(""))) - .findFirst() - .flatMap(p -> p.childElement("version")) - .map(Element::textContentTrimmed) - .orElse(null); - assertEquals("3.5.0", version, "Existing enforcer-plugin version should be upgraded to 3.5.0"); + String xml = DomUtils.toXml(document); + assertTrue( + xml.contains("maven-enforcer-plugin"), + "Should add pluginManagement for maven-enforcer-plugin inherited from parent"); } } diff --git a/impl/maven-core/pom.xml b/impl/maven-core/pom.xml index 07ffa6662b12..da961f431eed 100644 --- a/impl/maven-core/pom.xml +++ b/impl/maven-core/pom.xml @@ -31,6 +31,11 @@ under the License. Maven 4 Core Maven Core classes. + + + FileLength + + diff --git a/impl/maven-core/src/main/java/org/apache/maven/exception/DefaultExceptionHandler.java b/impl/maven-core/src/main/java/org/apache/maven/exception/DefaultExceptionHandler.java index fdf2441becac..98db9808123c 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/exception/DefaultExceptionHandler.java +++ b/impl/maven-core/src/main/java/org/apache/maven/exception/DefaultExceptionHandler.java @@ -40,7 +40,6 @@ import org.apache.maven.plugin.PluginExecutionException; import org.apache.maven.project.ProjectBuildingException; import org.apache.maven.project.ProjectBuildingResult; -import org.eclipse.aether.transfer.ArtifactFilteredOutException; /* @@ -178,9 +177,6 @@ private String getReference(Set dejaVu, Throwable exception) { reference = ConnectException.class.getSimpleName(); } } - if (findCause(exception, ArtifactFilteredOutException.class) != null) { - reference = "https://maven.apache.org/resolver/remote-repository-filtering.html"; - } } else if (exception instanceof LinkageError) { reference = LinkageError.class.getSimpleName(); } else if (exception instanceof PluginExecutionException) { @@ -211,9 +207,7 @@ private String getReference(Set dejaVu, Throwable exception) { } } - if ((reference != null && !reference.isEmpty()) - && !reference.startsWith("http:") - && !reference.startsWith("https:")) { + if ((reference != null && !reference.isEmpty()) && !reference.startsWith("http:")) { reference = "http://cwiki.apache.org/confluence/display/MAVEN/" + reference; } @@ -235,8 +229,6 @@ private boolean isNoteworthyException(Throwable exception) { private String getMessage(String message, Throwable exception) { String fullMessage = (message != null) ? message : ""; - boolean hasArtifactFilteredOut = false; - // To break out of possible endless loop when getCause returns "this", or dejaVu for n-level recursion (n>1) Set dejaVu = Collections.newSetFromMap(new IdentityHashMap<>()); for (Throwable t = exception; t != null && t != t.getCause(); t = t.getCause()) { @@ -268,39 +260,15 @@ private String getMessage(String message, Throwable exception) { fullMessage = join(fullMessage, exceptionMessage); } - if (t instanceof ArtifactFilteredOutException) { - hasArtifactFilteredOut = true; - } - if (!dejaVu.add(t)) { fullMessage = join(fullMessage, "[CIRCULAR REFERENCE]"); break; } } - if (hasArtifactFilteredOut) { - fullMessage += """ - - This error indicates that a remote repository filter has rejected this artifact. This commonly happens with repository managers using virtual/group repositories that do not properly aggregate prefix files. - To disable remote repository filtering, add one or both of these to your command line or to .mvn/maven.config: - -Daether.remoteRepositoryFilter.prefixes=false - -Daether.remoteRepositoryFilter.groupId=false - See https://maven.apache.org/resolver/remote-repository-filtering.html"""; - } - return fullMessage.trim(); } - private static T findCause(Throwable exception, Class type) { - Set dejaVu = Collections.newSetFromMap(new IdentityHashMap<>()); - for (Throwable t = exception; t != null && dejaVu.add(t); t = t.getCause()) { - if (type.isInstance(t)) { - return type.cast(t); - } - } - return null; - } - private String join(String message1, String message2) { String message = ""; diff --git a/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultLifecycleRegistry.java b/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultLifecycleRegistry.java index 50b625e2cc8d..a0b9ff8a0aaf 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultLifecycleRegistry.java +++ b/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultLifecycleRegistry.java @@ -227,64 +227,6 @@ public String id() { @Override public Collection phases() { - List phases = new ArrayList<>(buildOwnPhases()); - // Also include phases for default-phases entries that reference phases - // not defined in this lifecycle (e.g., standard lifecycle phases like - // process-sources). This preserves plugin bindings from components.xml - // that map goals to standard lifecycle phases. - Map lfPhases = lifecycle.getDefaultLifecyclePhases(); - if (lfPhases != null) { - Set ownPhaseNames = new HashSet<>(lifecycle.getPhases()); - for (Map.Entry entry : lfPhases.entrySet()) { - if (!ownPhaseNames.contains(entry.getKey())) { - String phaseName = entry.getKey(); - LifecyclePhase lfPhase = entry.getValue(); - phases.add(new Phase() { - @Override - public String name() { - return phaseName; - } - - @Override - public List phases() { - return List.of(); - } - - @Override - public Stream allPhases() { - return Stream.of(this); - } - - @Override - public List plugins() { - Map plugins = new LinkedHashMap<>(); - DefaultPackagingRegistry.parseLifecyclePhaseDefinitions( - plugins, phaseName, lfPhase); - return plugins.values().stream().toList(); - } - - @Override - public Collection links() { - return List.of(); - } - }); - } - } - } - return phases; - } - - @Override - public Collection v3phases() { - return buildOwnPhases(); - } - - @Override - public Collection aliases() { - return Collections.emptyList(); - } - - private List buildOwnPhases() { List names = lifecycle.getPhases(); List phases = new ArrayList<>(); for (int i = 0; i < names.size(); i++) { @@ -351,6 +293,11 @@ public Type type() { } return phases; } + + @Override + public Collection aliases() { + return Collections.emptyList(); + } }; } } diff --git a/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java index b5203b282b60..d631e8fd7e2c 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java +++ b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java @@ -48,9 +48,7 @@ import org.apache.maven.api.services.ModelBuilderResult; import org.apache.maven.api.services.ModelSource; import org.apache.maven.api.services.model.LifecycleBindingsInjector; -import org.apache.maven.execution.MavenExecutionRequest; import org.apache.maven.impl.InternalSession; -import org.apache.maven.internal.impl.InternalMavenSession; import org.apache.maven.model.v4.MavenModelVersion; import org.apache.maven.project.MavenProject; import org.apache.maven.project.SourceQueries; @@ -146,28 +144,7 @@ public Model build(RepositorySystemSession session, MavenProject project, ModelS if (isBom) { return buildBomWithoutFlatten(session, project, src); } else { - Model result = buildPom(session, project, src); - // Validate POM-packaged projects (parent POMs): if the consumer POM cannot be - // downgraded to 4.0.0, Maven 3 / Gradle cannot resolve the parent. - // Non-POM projects are consumed as dependencies where unknown elements are - // ignored, so a higher model version is acceptable (only a warning is logged - // by transformNonPom/transformPom). - if (POM_PACKAGING.equals(packaging) - && !model.isPreserveModelVersion() - && !ModelBuilder.MODEL_VERSION_4_0_0.equals(result.getModelVersion())) { - throw new MavenException(""" - The consumer POM for %s cannot be downgraded to model version 4.0.0 because it contains\ - features that require a newer model version.\ - Since consumer POM flattening is disabled, the parent reference is\ - preserved, which requires consumers to resolve the parent POM. - You have the following options to resolve this: - 1. Enable flattening by setting the property 'maven.consumer.pom.flatten=true'\ - to inline parent content and produce a self-contained 4.0.0 consumer POM - 2. Preserve the model version by setting 'preserve.model.version=true'\ - on the element (Maven 4 consumers only) - 3. Remove the features that require a newer model version""".formatted(project.getId())); - } - return result; + return buildPom(session, project, src); } } // Default behavior: flatten the consumer POM @@ -184,14 +161,14 @@ on the element (Maven 4 consumers only) protected Model buildPom(RepositorySystemSession session, MavenProject project, ModelSource src) throws ModelBuilderException { - ModelBuilderResult result = buildModel(session, project, src); + ModelBuilderResult result = buildModel(session, src); Model model = result.getRawModel(); return transformPom(model, project); } protected Model buildBomWithoutFlatten(RepositorySystemSession session, MavenProject project, ModelSource src) throws ModelBuilderException { - ModelBuilderResult result = buildModel(session, project, src); + ModelBuilderResult result = buildModel(session, src); Model model = result.getRawModel(); // For BOMs without flattening, we just need to transform the packaging from "bom" to "pom" // but keep everything else from the raw model (including unresolved versions) @@ -200,24 +177,21 @@ protected Model buildBomWithoutFlatten(RepositorySystemSession session, MavenPro protected Model buildBom(RepositorySystemSession session, MavenProject project, ModelSource src) throws ModelBuilderException { - ModelBuilderResult result = buildModel(session, project, src); + ModelBuilderResult result = buildModel(session, src); Model model = result.getEffectiveModel(); return transformBom(model, project); } protected Model buildNonPom(RepositorySystemSession session, MavenProject project, ModelSource src) throws ModelBuilderException { - Model model = buildEffectiveModel(session, project, src); + Model model = buildEffectiveModel(session, src); return transformNonPom(model, project); } - private Model buildEffectiveModel(RepositorySystemSession session, MavenProject project, ModelSource src) - throws ModelBuilderException { + private Model buildEffectiveModel(RepositorySystemSession session, ModelSource src) throws ModelBuilderException { InternalSession iSession = InternalSession.from(session); - ModelBuilderResult result = buildModel(session, project, src); + ModelBuilderResult result = buildModel(session, src); Model model = result.getEffectiveModel(); - boolean removeUnusedManagedDeps = - Features.consumerPomRemoveUnusedManagedDependencies(session.getConfigProperties()); if (model.getDependencyManagement() != null && !model.getDependencyManagement().getDependencies().isEmpty()) { @@ -236,14 +210,20 @@ private Model buildEffectiveModel(RepositorySystemSession session, MavenProject this::merge, LinkedHashMap::new)); Map managedDependencies = model.getDependencyManagement().getDependencies().stream() - .filter(dependency -> !"import".equals(dependency.getScope()) - && (!removeUnusedManagedDeps || nodes.containsKey(getDependencyKey(dependency)))) + .filter(dependency -> + nodes.containsKey(getDependencyKey(dependency)) && !"import".equals(dependency.getScope())) .collect(Collectors.toMap( DefaultConsumerPomBuilder::getDependencyKey, Function.identity(), this::merge, LinkedHashMap::new)); + // for each managed dep in the model: + // * if there is no corresponding node in the tree, discard the managed dep + // * if there's a direct dependency, apply the managed dependency to it and discard the managed dep + // * else keep the managed dep + managedDependencies.keySet().retainAll(nodes.keySet()); + directDependencies.replaceAll((key, dependency) -> { var managedDependency = managedDependencies.get(key); if (managedDependency != null) { @@ -315,7 +295,7 @@ private static String getDependencyKey(Dependency dependency) { + (dependency.getClassifier() != null ? dependency.getClassifier() : ""); } - private ModelBuilderResult buildModel(RepositorySystemSession session, MavenProject project, ModelSource src) + private ModelBuilderResult buildModel(RepositorySystemSession session, ModelSource src) throws ModelBuilderException { InternalSession iSession = InternalSession.from(session); ModelBuilderRequest.ModelBuilderRequestBuilder request = ModelBuilderRequest.builder(); @@ -323,41 +303,9 @@ private ModelBuilderResult buildModel(RepositorySystemSession session, MavenProj request.session(iSession); request.source(src); request.locationTracking(false); - request.systemProperties(iSession.getSystemProperties()); - request.userProperties(iSession.getUserProperties()); + request.systemProperties(session.getSystemProperties()); + request.userProperties(session.getUserProperties()); request.lifecycleBindingsInjector(lifecycleBindingsInjector::injectLifecycleBindings); - // Pass remote repositories so that the model builder can resolve BOM imports - // from non-central repositories (e.g., repositories defined in settings.xml profiles). - // Prefer project repositories, but fall back to session repositories if the project's - // remote repository list is not populated (e.g., during install/deploy phases). - if (project != null - && project.getRemoteProjectRepositories() != null - && !project.getRemoteProjectRepositories().isEmpty()) { - request.repositories(project.getRemoteProjectRepositories().stream() - .map(iSession::getRemoteRepository) - .toList()); - } else { - request.repositories(iSession.getRemoteRepositories()); - } - // Pass profiles and active/inactive profile IDs from the execution request - // so that settings.xml profiles are applied during consumer POM model building. - if (iSession instanceof InternalMavenSession mavenSession) { - MavenExecutionRequest executionRequest = - mavenSession.getMavenSession().getRequest(); - if (executionRequest.getProfiles() != null) { - request.profiles(executionRequest.getProfiles().stream() - .map(org.apache.maven.model.Profile::getDelegate) - .toList()); - } - request.activeProfileIds(executionRequest.getActiveProfiles()); - request.inactiveProfileIds(executionRequest.getInactiveProfiles()); - } else { - LOGGER.debug( - "Session is not an InternalMavenSession ({}); settings.xml profiles will not be " - + "passed to the consumer POM model builder. BOM imports from repositories " - + "defined only in settings.xml profiles may fail to resolve.", - iSession.getClass().getName()); - } ModelBuilder.ModelBuilderSession mbSession = iSession.getData().get(SessionData.key(ModelBuilder.ModelBuilderSession.class)); return mbSession.build(request.build()); diff --git a/impl/maven-core/src/main/java/org/apache/maven/plugin/DefaultExtensionRealmCache.java b/impl/maven-core/src/main/java/org/apache/maven/plugin/DefaultExtensionRealmCache.java index ce3cb5135f3e..b20211525854 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/plugin/DefaultExtensionRealmCache.java +++ b/impl/maven-core/src/main/java/org/apache/maven/plugin/DefaultExtensionRealmCache.java @@ -149,6 +149,6 @@ public void register(MavenProject project, Key key, CacheRecord record) { @Override public void dispose() { - cache.clear(); + flush(); } } diff --git a/impl/maven-core/src/main/java/org/apache/maven/plugin/DefaultPluginRealmCache.java b/impl/maven-core/src/main/java/org/apache/maven/plugin/DefaultPluginRealmCache.java index 1e822a2ccb0c..681955f21db3 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/plugin/DefaultPluginRealmCache.java +++ b/impl/maven-core/src/main/java/org/apache/maven/plugin/DefaultPluginRealmCache.java @@ -215,6 +215,6 @@ public void register(MavenProject project, Key key, CacheRecord record) { @Override public void dispose() { - cache.clear(); + flush(); } } diff --git a/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuildingHelper.java b/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuildingHelper.java index 6bde396a97e7..f8f77c7ceab3 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuildingHelper.java +++ b/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuildingHelper.java @@ -92,13 +92,6 @@ public List createArtifactRepositories( List internalRepositories = new ArrayList<>(); for (Repository repository : pomRepositories) { - if (containsExpression(repository.getId()) || containsExpression(repository.getUrl())) { - logger.warn( - "Skipping repository '{}' (url: '{}') containing an uninterpolated property expression", - repository.getId(), - repository.getUrl()); - continue; - } internalRepositories.add(MavenRepositorySystem.buildArtifactRepository(repository)); } @@ -274,10 +267,6 @@ public void selectProjectRealm(MavenProject project) { Thread.currentThread().setContextClassLoader(projectRealm); } - private static boolean containsExpression(String value) { - return value != null && value.contains("${"); - } - private List toAetherArtifacts(final List pluginArtifacts) { return new ArrayList<>(RepositoryUtils.toArtifacts(pluginArtifacts)); } diff --git a/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectRealmCache.java b/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectRealmCache.java index 9111177c3489..82a1a814c1b0 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectRealmCache.java +++ b/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectRealmCache.java @@ -125,6 +125,6 @@ public void register(MavenProject project, Key key, CacheRecord record) { @Override public void dispose() { - cache.clear(); + flush(); } } diff --git a/impl/maven-core/src/test/java/org/apache/maven/exception/DefaultExceptionHandlerTest.java b/impl/maven-core/src/test/java/org/apache/maven/exception/DefaultExceptionHandlerTest.java index 88359e2e8f6b..c9a84014d208 100644 --- a/impl/maven-core/src/test/java/org/apache/maven/exception/DefaultExceptionHandlerTest.java +++ b/impl/maven-core/src/test/java/org/apache/maven/exception/DefaultExceptionHandlerTest.java @@ -29,15 +29,9 @@ import org.apache.maven.plugin.PluginExecutionException; import org.apache.maven.plugin.descriptor.MojoDescriptor; import org.apache.maven.plugin.descriptor.PluginDescriptor; -import org.eclipse.aether.artifact.DefaultArtifact; -import org.eclipse.aether.repository.RemoteRepository; -import org.eclipse.aether.resolution.ArtifactResolutionException; -import org.eclipse.aether.resolution.ArtifactResult; -import org.eclipse.aether.transfer.ArtifactFilteredOutException; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; /** */ @@ -130,40 +124,6 @@ public synchronized Throwable getCause() { assertEquals(expectedReference, summary.getReference()); } - @Test - void testArtifactFilteredOutException() { - RemoteRepository repo = - new RemoteRepository.Builder("my-repo", "default", "https://repo.example.com/maven").build(); - ArtifactFilteredOutException filterEx = new ArtifactFilteredOutException( - new DefaultArtifact("com.example:my-lib:jar:1.0"), - repo, - "Prefix com/example/my-lib/1.0/my-lib-1.0.jar NOT allowed from my-repo" - + " (https://repo.example.com/maven, default, releases)"); - ArtifactResult artifactResult = new ArtifactResult(new org.eclipse.aether.resolution.ArtifactRequest( - new DefaultArtifact("com.example:my-lib:jar:1.0"), java.util.List.of(repo), null)); - artifactResult.addException(filterEx); - ArtifactResolutionException resolutionEx = - new ArtifactResolutionException(java.util.List.of(artifactResult), "Could not resolve artifact"); - MojoExecutionException mojoEx = new MojoExecutionException("Resolution failed", resolutionEx); - - DefaultExceptionHandler handler = new DefaultExceptionHandler(); - ExceptionSummary summary = handler.handleException(mojoEx); - - assertTrue( - summary.getMessage().contains("-Daether.remoteRepositoryFilter.prefixes=false"), - "Message should contain the prefixes workaround property"); - assertTrue( - summary.getMessage().contains("-Daether.remoteRepositoryFilter.groupId=false"), - "Message should contain the groupId workaround property"); - assertTrue( - summary.getMessage().contains("remote repository filter"), - "Message should explain the filtering cause"); - assertEquals( - "https://maven.apache.org/resolver/remote-repository-filtering.html", - summary.getReference(), - "Reference should point to the RRF documentation"); - } - @Test void testHandleExceptionSelfReferencing() { RuntimeException boom3 = new RuntimeException("BOOM3"); diff --git a/impl/maven-core/src/test/java/org/apache/maven/internal/impl/DefaultProjectManagerTest.java b/impl/maven-core/src/test/java/org/apache/maven/internal/impl/DefaultProjectManagerTest.java index 7b8ee0c8b664..48e87f7cda65 100644 --- a/impl/maven-core/src/test/java/org/apache/maven/internal/impl/DefaultProjectManagerTest.java +++ b/impl/maven-core/src/test/java/org/apache/maven/internal/impl/DefaultProjectManagerTest.java @@ -102,9 +102,9 @@ void attachArtifact() { /** * Verifies that {@code projectManager.attachArtifact(…)} throws an exception, - * and that the exception message contains the expected and actual GAV. + * and that the expecption message contains the expected and actual GAV. * - * @param expectedGAV the expected GAV that the exception message should contain + * @param expectedGAV the actual GAV that the exception message should contain * @param actualGAV the actual GAV that the exception message should contain */ private void assertExceptionMessageContains(String expectedGAV, String actualGAV) { diff --git a/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java b/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java index 76a9752f5506..d9744cad5fcb 100644 --- a/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java +++ b/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java @@ -34,7 +34,6 @@ import org.apache.maven.api.model.Scm; import org.apache.maven.api.services.DependencyResolver; import org.apache.maven.api.services.DependencyResolverResult; -import org.apache.maven.api.services.MavenException; import org.apache.maven.api.services.ModelBuilder; import org.apache.maven.api.services.ModelBuilderRequest; import org.apache.maven.api.services.Sources; @@ -49,15 +48,12 @@ import org.apache.maven.internal.impl.InternalMavenSession; import org.apache.maven.internal.transformation.AbstractRepositoryTestCase; import org.apache.maven.project.MavenProject; -import org.eclipse.aether.repository.RemoteRepository; import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; public class ConsumerPomBuilderTest extends AbstractRepositoryTestCase { @@ -185,19 +181,6 @@ void testMultiModuleConsumerPreserveModelVersion() throws Exception { assertFalse(transformed.getDependencyManagement().getDependencies().isEmpty()); } - @Test - void testParentWithConditionsFailsConsumerPom() throws Exception { - setRootDirectory("parent-with-conditions"); - Path file = Paths.get("src/test/resources/consumer/parent-with-conditions/pom.xml"); - - MavenProject project = getEffectiveModel(file); - // A parent POM with profile conditions cannot be downgraded to 4.0.0, - // so building the consumer POM should fail with actionable guidance. - MavenException ex = - assertThrows(MavenException.class, () -> builder.build(session, project, Sources.buildSource(file))); - assertTrue(ex.getMessage().contains("cannot be downgraded to model version 4.0.0")); - } - @Test void testScmInheritance() throws Exception { Model model = Model.newBuilder() @@ -215,65 +198,4 @@ void testScmInheritance() throws Exception { assertNull(transformed.getScm().getChildScmUrlInheritAppendPath()); assertNull(transformed.getScm().getChildScmDeveloperConnectionInheritAppendPath()); } - - /** - * Verifies that the consumer POM builder passes the project's remote repositories - * to the model builder request, so that BOM imports from non-central repositories - * (e.g. repositories defined in settings.xml profiles) can be resolved. - *

- * Without the fix in {@code DefaultConsumerPomBuilder.buildModel()}, the - * {@code ModelBuilderRequest} is constructed without repositories, profiles, or - * active profile IDs. This causes the model builder to only see Maven Central - * when resolving BOM imports, leading to "Non-resolvable import POM" failures - * for artifacts hosted in private/corporate repositories. - */ - @Test - void testConsumerPomPassesProjectRepositoriesToModelBuilder() throws Exception { - setRootDirectory("trivial"); - Path file = Paths.get("src/test/resources/consumer/trivial/child/pom.xml"); - - MavenProject project = getEffectiveModel(file); - - // Add a custom remote repository to the project, simulating a repository - // injected from settings.xml profile (e.g. a corporate/private repository) - RemoteRepository customRepo = - new RemoteRepository.Builder("custom-repo", "default", "https://repo.example.com/maven2").build(); - project.getRemoteProjectRepositories().add(customRepo); - - // Spy on the ModelBuilderSession to capture the ModelBuilderRequest - ModelBuilder.ModelBuilderSession originalMbs = modelBuilder.newSession(); - ModelBuilder.ModelBuilderSession spyMbs = Mockito.spy(originalMbs); - InternalSession.from(session).getData().set(SessionData.key(ModelBuilder.ModelBuilderSession.class), spyMbs); - - // Build the consumer POM - builder.build(session, project, Sources.buildSource(file)); - - // Capture the ModelBuilderRequest passed to the ModelBuilderSession - ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(ModelBuilderRequest.class); - Mockito.verify(spyMbs, Mockito.atLeastOnce()).build(requestCaptor.capture()); - - // Find the BUILD_CONSUMER request (there may be multiple calls) - ModelBuilderRequest consumerRequest = requestCaptor.getAllValues().stream() - .filter(r -> r.getRequestType() == ModelBuilderRequest.RequestType.BUILD_CONSUMER) - .findFirst() - .orElse(null); - - assertNotNull(consumerRequest, "Expected a BUILD_CONSUMER request to be made"); - - // Verify that repositories were passed to the request. - // Without the fix, getRepositories() returns null because buildModel() never sets them. - assertNotNull( - consumerRequest.getRepositories(), - "Consumer POM model builder request should include repositories from the project. " - + "Without this, BOM imports from non-central repositories (e.g. settings.xml profiles) " - + "cannot be resolved, causing 'Non-resolvable import POM' errors."); - assertFalse( - consumerRequest.getRepositories().isEmpty(), - "Consumer POM model builder request should have at least one repository"); - - // Verify the custom repository is included - boolean hasCustomRepo = - consumerRequest.getRepositories().stream().anyMatch(r -> "custom-repo".equals(r.getId())); - assertTrue(hasCustomRepo, "Consumer POM model builder request should include the project's custom repository"); - } } diff --git a/impl/maven-core/src/test/java/org/apache/maven/lifecycle/DefaultLifecyclesTest.java b/impl/maven-core/src/test/java/org/apache/maven/lifecycle/DefaultLifecyclesTest.java index 7754e8b96e2d..0b36378871a7 100644 --- a/impl/maven-core/src/test/java/org/apache/maven/lifecycle/DefaultLifecyclesTest.java +++ b/impl/maven-core/src/test/java/org/apache/maven/lifecycle/DefaultLifecyclesTest.java @@ -23,22 +23,18 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import org.apache.maven.internal.impl.DefaultLifecycleRegistry; import org.apache.maven.internal.impl.DefaultLookup; -import org.apache.maven.lifecycle.mapping.LifecyclePhase; import org.codehaus.plexus.PlexusContainer; import org.codehaus.plexus.component.repository.exception.ComponentLookupException; import org.codehaus.plexus.testing.PlexusTest; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -100,52 +96,6 @@ void testCustomLifecycle() throws ComponentLookupException { assertEquals("etl", dl.getLifeCycles().get(3).getId()); } - /** - * Test for MNG-11796: - * custom lifecycle with {@code } binding goals to standard lifecycle phases. - */ - @Test - void testCustomLifecycleDefaultPhasesForStandardPhases() throws ComponentLookupException { - // Create a custom lifecycle with default-phases mapping to a standard lifecycle phase - Map defaultPhases = new HashMap<>(); - defaultPhases.put("process-sources", new LifecyclePhase("com.example:my-plugin:touch")); - - Lifecycle customLifecycle = new Lifecycle("my-extension", Arrays.asList("my-dummy-phase"), defaultPhases); - - List allLifecycles = new ArrayList<>(); - allLifecycles.add(customLifecycle); - allLifecycles.addAll(defaultLifeCycles.getLifeCycles()); - - Map lifeCycles = allLifecycles.stream().collect(Collectors.toMap(Lifecycle::getId, l -> l)); - PlexusContainer mockedPlexusContainer = mock(PlexusContainer.class); - when(mockedPlexusContainer.lookupMap(Lifecycle.class)).thenReturn(lifeCycles); - - DefaultLifecycles dl = new DefaultLifecycles( - new DefaultLifecycleRegistry( - List.of(new DefaultLifecycleRegistry.LifecycleWrapperProvider(mockedPlexusContainer))), - new DefaultLookup(mockedPlexusContainer)); - - // Find the custom lifecycle - Lifecycle result = dl.getLifeCycles().stream() - .filter(l -> "my-extension".equals(l.getId())) - .findFirst() - .orElseThrow(() -> new AssertionError("Custom lifecycle not found")); - - // Verify that getPhases() only contains the custom phase (not standard phases) - assertTrue(result.getPhases().contains("my-dummy-phase"), "Custom lifecycle should contain its own phase"); - - // Verify that defaultLifecyclePhases includes the standard phase binding - Map resultDefaultPhases = result.getDefaultLifecyclePhases(); - assertNotNull(resultDefaultPhases, "Default lifecycle phases should not be null"); - assertTrue( - resultDefaultPhases.containsKey("process-sources"), - "Default lifecycle phases should contain 'process-sources' binding"); - String goalSpec = resultDefaultPhases.get("process-sources").toString(); - assertTrue( - goalSpec.contains("com.example") && goalSpec.contains("my-plugin") && goalSpec.contains("touch"), - "The process-sources binding should map to the correct goal, got: " + goalSpec); - } - private Lifecycle getLifeCycleById(String id) { return defaultLifeCycles.getLifeCycles().stream() .filter(l -> id.equals(l.getId())) diff --git a/impl/maven-core/src/test/java/org/apache/maven/plugin/DefaultRealmCacheDisposeTest.java b/impl/maven-core/src/test/java/org/apache/maven/plugin/DefaultRealmCacheDisposeTest.java deleted file mode 100644 index 4edf41a61a89..000000000000 --- a/impl/maven-core/src/test/java/org/apache/maven/plugin/DefaultRealmCacheDisposeTest.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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.plugin; - -import java.util.List; - -import org.apache.maven.artifact.Artifact; -import org.codehaus.plexus.classworlds.ClassWorld; -import org.codehaus.plexus.classworlds.realm.ClassRealm; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * Verifies that dispose() does not dispose ClassRealms prematurely. - *

- * Plexus Disposable.dispose() runs before Sisu's @PreDestroy callbacks. - * If dispose() disposes ClassRealms, beans loaded from those realms will - * get ClassNotFoundException when their @PreDestroy methods execute. - * dispose() should only clear the cache map; flush() should dispose realms. - * - * @see MNG-8572 - */ -class DefaultRealmCacheDisposeTest { - - @Test - void disposeDoesNotDisposeClassRealms() throws Exception { - ClassWorld world = new ClassWorld(); - ClassRealm realm = world.newRealm("test-plugin-realm"); - - DefaultPluginRealmCache cache = new DefaultPluginRealmCache(); - PluginRealmCache.CacheRecord record = new PluginRealmCache.CacheRecord(realm, List.of()); - cache.cache.put(new TestKey(), record); - - cache.dispose(); - - assertTrue(cache.cache.isEmpty(), "dispose() should clear the cache"); - assertNotNull(world.getClassRealm("test-plugin-realm"), "dispose() should NOT dispose the ClassRealm"); - } - - @Test - void flushDisposesClassRealms() throws Exception { - ClassWorld world = new ClassWorld(); - ClassRealm realm = world.newRealm("test-plugin-realm-flush"); - - DefaultPluginRealmCache cache = new DefaultPluginRealmCache(); - PluginRealmCache.CacheRecord record = new PluginRealmCache.CacheRecord(realm, List.of()); - cache.cache.put(new TestKey(), record); - - cache.flush(); - - assertTrue(cache.cache.isEmpty(), "flush() should clear the cache"); - assertNull(world.getClassRealm("test-plugin-realm-flush"), "flush() SHOULD dispose the ClassRealm"); - } - - private static class TestKey implements PluginRealmCache.Key { - @Override - public int hashCode() { - return 1; - } - - @Override - public boolean equals(Object obj) { - return obj instanceof TestKey; - } - } -} diff --git a/impl/maven-core/src/test/java/org/apache/maven/project/PomConstructionTest.java b/impl/maven-core/src/test/java/org/apache/maven/project/PomConstructionTest.java index a6513e409183..d53247b53b9a 100644 --- a/impl/maven-core/src/test/java/org/apache/maven/project/PomConstructionTest.java +++ b/impl/maven-core/src/test/java/org/apache/maven/project/PomConstructionTest.java @@ -1534,6 +1534,82 @@ void testProfilePluginMngDependencies() throws Exception { assertEquals("a", pom.getValue("build/plugins[1]/dependencies[1]/artifactId")); } + /* MNG-3309 */ + @Test + void testCascadingProfileActivation() throws Exception { + Properties props = new Properties(); + props.put("trigger", "start"); + + PomTestWrapper pom = buildPom("cascading-profile-activation", props, null); + + // Verify that cascading profile activation works + // profile1 should be activated by trigger=start + // profile2 should be activated by profile1's cascade.level1=activate property + // profile3 should be activated by profile2's cascade.level2=activate property + + List activeProfiles = + pom.getMavenProject().getActiveProfiles(); + + // Should have 3 active profiles (profile1, profile2, profile3) + assertEquals(3, activeProfiles.size()); + + // Verify specific profiles are active + assertTrue(activeProfiles.stream().anyMatch(p -> "profile1".equals(p.getId()))); + assertTrue(activeProfiles.stream().anyMatch(p -> "profile2".equals(p.getId()))); + assertTrue(activeProfiles.stream().anyMatch(p -> "profile3".equals(p.getId()))); + + // Verify profile4 is NOT active (no trigger) + assertTrue(activeProfiles.stream().noneMatch(p -> "profile4".equals(p.getId()))); + + // Verify properties are set correctly (last profile wins) + assertEquals("profile3", pom.getValue("properties/test.property")); + assertEquals("true", pom.getValue("properties/profile1.activated")); + assertEquals("true", pom.getValue("properties/profile2.activated")); + assertEquals("true", pom.getValue("properties/profile3.activated")); + } + + /* MNG-3309 - Test circular dependency handling */ + @Test + void testCascadingProfileActivationCircular() throws Exception { + Properties props = new Properties(); + props.put("circular", "test"); + + PomTestWrapper pom = buildPom("cascading-profile-activation", props, null); + + // Verify that circular dependencies are handled gracefully + // profile5 sets trigger=start which would activate profile1 + // But this should not cause infinite loops + + List activeProfiles = + pom.getMavenProject().getActiveProfiles(); + + // Should have profile5 and profile1 active (and cascaded profiles) + assertTrue(activeProfiles.stream().anyMatch(p -> "profile5".equals(p.getId()))); + assertTrue(activeProfiles.stream().anyMatch(p -> "profile1".equals(p.getId()))); + + // Verify no infinite loop occurred (test should complete) + assertTrue(activeProfiles.size() >= 2); + } + + /* MNG-3309 - Test no cascading baseline */ + @Test + void testNoCascadingProfileActivation() throws Exception { + // Test with no trigger properties - no profiles should be activated + PomTestWrapper pom = buildPom("cascading-profile-activation"); + + List activeProfiles = + pom.getMavenProject().getActiveProfiles(); + + // No profiles should be active + assertEquals(0, activeProfiles.size()); + + // Properties should remain at default values + assertEquals("default", pom.getValue("properties/test.property")); + assertEquals("false", pom.getValue("properties/profile1.activated")); + assertEquals("false", pom.getValue("properties/profile2.activated")); + assertEquals("false", pom.getValue("properties/profile3.activated")); + } + /** MNG-4116 */ @Test void testPercentEncodedUrlsMustNotBeDecoded() throws Exception { diff --git a/impl/maven-core/src/test/java/org/apache/maven/project/ProjectBuilderTest.java b/impl/maven-core/src/test/java/org/apache/maven/project/ProjectBuilderTest.java index f7521ac819e4..c79d8cd9aaf6 100644 --- a/impl/maven-core/src/test/java/org/apache/maven/project/ProjectBuilderTest.java +++ b/impl/maven-core/src/test/java/org/apache/maven/project/ProjectBuilderTest.java @@ -996,99 +996,4 @@ void testDuplicateEnabledSources() throws Exception { testJavaRoots.get(0).module().orElse(null), "Test source root should be for com.example.dup module"); } - - @Test - void testResourceTargetPathRemainsRelativeInCompatLayer() throws Exception { - File pom = getProject("resource-target-path"); - - MavenSession mavenSession = createMavenSession(null); - ProjectBuildingRequest configuration = new DefaultProjectBuildingRequest(); - configuration.setRepositorySession(mavenSession.getRepositorySession()); - - ProjectBuildingResult result = getContainer() - .lookup(org.apache.maven.project.ProjectBuilder.class) - .build(pom, configuration); - - MavenProject project = result.getProject(); - - // Verify main resources via SourceRoot API - List mainResources = project.getEnabledSourceRoots(ProjectScope.MAIN, Language.RESOURCES) - .toList(); - assertEquals(1, mainResources.size()); - assertTrue(mainResources.get(0).targetPath().isPresent(), "Main resource should have a targetPath"); - assertFalse( - mainResources.get(0).targetPath().get().isAbsolute(), - "SourceRoot targetPath must be relative, got: " - + mainResources.get(0).targetPath().get()); - assertEquals( - Path.of("META-INF/tags/rdc"), mainResources.get(0).targetPath().get()); - - // Verify test resources via SourceRoot API - List testResources = project.getEnabledSourceRoots(ProjectScope.TEST, Language.RESOURCES) - .toList(); - assertEquals(1, testResources.size()); - assertTrue(testResources.get(0).targetPath().isPresent(), "Test resource should have a targetPath"); - assertFalse( - testResources.get(0).targetPath().get().isAbsolute(), - "SourceRoot targetPath must be relative, got: " - + testResources.get(0).targetPath().get()); - assertEquals( - Path.of("org/apache/maven/messages"), - testResources.get(0).targetPath().get()); - - // Verify compat layer: MavenProject.getResources() must return relative targetPath - List resources = project.getResources(); - assertEquals(1, resources.size()); - assertEquals( - "META-INF" + File.separator + "tags" + File.separator + "rdc", - resources.get(0).getTargetPath(), - "Resource targetPath from getResources() must remain relative"); - - // Verify compat layer: MavenProject.getTestResources() must return relative targetPath - List testResourceList = project.getTestResources(); - assertEquals(1, testResourceList.size()); - assertEquals( - "org" + File.separator + "apache" + File.separator + "maven" + File.separator + "messages", - testResourceList.get(0).getTargetPath(), - "Test resource targetPath from getTestResources() must remain relative"); - } - - @Test - void testSourceTargetPathRemainsRelative() throws Exception { - File pom = getProject("source-target-path"); - - MavenSession mavenSession = createMavenSession(null); - ProjectBuildingRequest configuration = new DefaultProjectBuildingRequest(); - configuration.setRepositorySession(mavenSession.getRepositorySession()); - - ProjectBuildingResult result = getContainer() - .lookup(org.apache.maven.project.ProjectBuilder.class) - .build(pom, configuration); - - MavenProject project = result.getProject(); - - // Verify main resources have relative targetPath preserved - List mainResources = project.getEnabledSourceRoots(ProjectScope.MAIN, Language.RESOURCES) - .toList(); - assertEquals(1, mainResources.size()); - assertTrue(mainResources.get(0).targetPath().isPresent()); - assertEquals(Path.of(".grammar"), mainResources.get(0).targetPath().get()); - - // Verify test resources have relative targetPath preserved - List testResources = project.getEnabledSourceRoots(ProjectScope.TEST, Language.RESOURCES) - .toList(); - assertEquals(1, testResources.size()); - assertTrue(testResources.get(0).targetPath().isPresent()); - assertEquals(Path.of("META-INF/test"), testResources.get(0).targetPath().get()); - - // Verify the compat layer also returns relative targetPath - List resources = project.getResources(); - assertEquals(1, resources.size()); - assertEquals(".grammar", resources.get(0).getTargetPath()); - - List testResourceList = project.getTestResources(); - assertEquals(1, testResourceList.size()); - assertEquals( - "META-INF" + File.separator + "test", testResourceList.get(0).getTargetPath()); - } } diff --git a/impl/maven-core/src/test/projects/project-builder/resource-target-path/pom.xml b/impl/maven-core/src/test/projects/project-builder/resource-target-path/pom.xml deleted file mode 100644 index aa9dede04732..000000000000 --- a/impl/maven-core/src/test/projects/project-builder/resource-target-path/pom.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - 4.0.0 - - org.apache.maven.its.mng - resource-target-path-test - 1.0-SNAPSHOT - jar - - Resource TargetPath Compat Test - Test that targetPath in legacy resource elements remains relative through the compat layer - - - - - src/main/resources - META-INF/tags/rdc - - - - - src/test/resources - org/apache/maven/messages - - - - diff --git a/impl/maven-core/src/test/projects/project-builder/source-target-path/pom.xml b/impl/maven-core/src/test/projects/project-builder/source-target-path/pom.xml deleted file mode 100644 index bd8aae771f6c..000000000000 --- a/impl/maven-core/src/test/projects/project-builder/source-target-path/pom.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - 4.1.0 - - org.apache.maven.its.mng - source-target-path-test - 1.0-SNAPSHOT - jar - - Source TargetPath Test - Test that targetPath in source elements remains relative to output directory - - - - - resources - src/main/resources - .grammar - - - resources - test - src/test/resources - META-INF/test - - - - diff --git a/impl/maven-core/src/test/resources-project-builder/cascading-profile-activation/pom.xml b/impl/maven-core/src/test/resources-project-builder/cascading-profile-activation/pom.xml new file mode 100644 index 000000000000..a94ef85cb8df --- /dev/null +++ b/impl/maven-core/src/test/resources-project-builder/cascading-profile-activation/pom.xml @@ -0,0 +1,145 @@ + + + + + 4.0.0 + + org.apache.maven.its.mng3309 + cascading-profile-activation + 1.0-SNAPSHOT + jar + + Maven Integration Test :: MNG-3309 + + Test cascading profile activation where one profile's properties trigger the activation of other profiles. + + + + + default + false + false + false + + + + + + profile1 + + + trigger + start + + + + true + + activate + profile1 + + + + + + profile2 + + + cascade.level1 + activate + + + + true + + activate + profile2 + + + + + + profile3 + + + cascade.level2 + activate + + + + true + profile3 + + + + + + profile4 + + + never.set + never + + + + true + profile4 + + + + + + profile5 + + + circular + test + + + + true + + start + profile5 + + + + + + + + + org.apache.maven.plugins + maven-help-plugin + 3.4.0 + + + show-profiles + validate + + active-profiles + + + + + + + diff --git a/impl/maven-core/src/test/resources/consumer/parent-with-conditions/pom.xml b/impl/maven-core/src/test/resources/consumer/parent-with-conditions/pom.xml deleted file mode 100644 index a5bd1832e989..000000000000 --- a/impl/maven-core/src/test/resources/consumer/parent-with-conditions/pom.xml +++ /dev/null @@ -1,32 +0,0 @@ - - org.my.group - parent - 1.0-SNAPSHOT - pom - - - - - org.slf4j - slf4j-api - 2.0.9 - - - - - - - test-profile - - ${project.artifactId} == 'parent' - - - - org.slf4j - slf4j-api - - - - - - diff --git a/impl/maven-impl/pom.xml b/impl/maven-impl/pom.xml index 693e1ac66b2f..e2005c800e61 100644 --- a/impl/maven-impl/pom.xml +++ b/impl/maven-impl/pom.xml @@ -170,11 +170,6 @@ under the License. jmh-generator-annprocess test - - org.xmlunit - xmlunit-core - test - diff --git a/impl/maven-impl/src/main/java/org/apache/maven/api/services/model/ProfileActivationContext.java b/impl/maven-impl/src/main/java/org/apache/maven/api/services/model/ProfileActivationContext.java index b7951ea3e122..43fec5176afc 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/api/services/model/ProfileActivationContext.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/api/services/model/ProfileActivationContext.java @@ -18,9 +18,12 @@ */ package org.apache.maven.api.services.model; +import java.util.Collection; + import org.apache.maven.api.annotations.Nonnull; import org.apache.maven.api.annotations.Nullable; import org.apache.maven.api.model.Model; +import org.apache.maven.api.model.Profile; import org.apache.maven.api.services.InterpolatorException; import org.apache.maven.api.services.ModelBuilderException; @@ -130,4 +133,12 @@ public interface ProfileActivationContext { * @throws InterpolatorException if an error occurs during interpolation */ boolean exists(@Nullable String path, boolean glob); + + /** + * Inject properties from newly activated profiles in order to trigger the cascading mechanism. + * This method allows profiles to contribute properties that can trigger the activation of other profiles. + * + * @param activatedProfiles The collection of profiles that have been activated that may trigger the cascading effect. + */ + void addProfileProperties(Collection activatedProfiles); } diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/SettingsUtilsV4.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/SettingsUtilsV4.java index 0290464ebb77..fabcc214a221 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/SettingsUtilsV4.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/SettingsUtilsV4.java @@ -268,7 +268,7 @@ public static org.apache.maven.api.model.Profile convertFromSettingsProfile(Prof } org.apache.maven.api.model.Profile value = profile.build(); - value.setSource("settings.xml"); + value.setSource(org.apache.maven.api.model.Profile.SOURCE_SETTINGS); return value; } diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java index 22d6c26705f8..aa282d272353 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java @@ -277,14 +277,6 @@ protected class ModelBuilderSessionState implements ModelProblemCollector { List externalRepositories; List repositories; - List getRepositories() { - return repositories; - } - - List getExternalRepositories() { - return externalRepositories; - } - // Cycle detection chain shared across all derived sessions // Contains both GAV coordinates (groupId:artifactId:version) and file paths final Set parentChain; @@ -353,23 +345,6 @@ ModelBuilderSessionState derive(ModelBuilderRequest request, DefaultModelBuilder } // Create a new parentChain for each derived session to prevent cycle detection issues // The parentChain now contains both GAV coordinates and file paths - // For BUILD_CONSUMER requests, use the request's explicit repositories so that - // BOM imports can be resolved from non-central repos (e.g., settings.xml profiles). - // This is scoped to BUILD_CONSUMER to avoid unintended side effects on other - // derived sessions (e.g., parent POM resolution during project builds). - List derivedExtRepos = externalRepositories; - List derivedRepos = repositories; - if (request.getRequestType() == ModelBuilderRequest.RequestType.BUILD_CONSUMER - && request.getRepositories() != null - && !request.getRepositories().isEmpty()) { - derivedExtRepos = List.copyOf(request.getRepositories()); - if (pomRepositories.isEmpty()) { - derivedRepos = derivedExtRepos; - } else { - RepositoryFactory repositoryFactory = session.getService(RepositoryFactory.class); - derivedRepos = repositoryFactory.aggregate(session, pomRepositories, derivedExtRepos, false); - } - } return new ModelBuilderSessionState( session, request, @@ -377,8 +352,8 @@ ModelBuilderSessionState derive(ModelBuilderRequest request, DefaultModelBuilder dag, mappedSources, pomRepositories, - derivedExtRepos, - derivedRepos, + externalRepositories, + repositories, new LinkedHashSet<>()); } @@ -759,10 +734,8 @@ private Map getPropertiesWithProfiles(Model model, Map userProps = request.getUserProperties(); - Map newProps = merge(model.getProperties(), userProps); + // Override model properties with user properties + Map newProps = merge(model.getProperties(), session.getUserProperties()); if (newProps != null) { model = model.withProperties(newProps); } - model = model.withProfiles(merge(model.getProfiles(), userProps)); + model = model.withProfiles(merge(model.getProfiles(), session.getUserProperties())); } for (var transformer : transformers) { diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelPathTranslator.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelPathTranslator.java index 52ebbbf9d9c6..ad4135d94b96 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelPathTranslator.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelPathTranslator.java @@ -134,9 +134,11 @@ private Source alignToBaseDirectory(Source source, Path basedir) { if (newDir != oldDir) { source = source.withDirectory(newDir); } - // Note: targetPath is intentionally NOT aligned to basedir. - // It is relative to the output directory (target/classes or target/test-classes), - // not to the project base directory. See maven.mdo Source.targetPath documentation. + oldDir = source.getTargetPath(); + newDir = alignToBaseDirectory(oldDir, basedir); + if (newDir != oldDir) { + source = source.withTargetPath(newDir); + } } return source; } diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelValidator.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelValidator.java index 8cc8cb3459b3..f294013cbc13 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelValidator.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelValidator.java @@ -1624,11 +1624,11 @@ private void validateRawRepository( if (matcher.find()) { addViolation( problems, - Severity.WARNING, + Severity.ERROR, Version.V40, prefix + prefix2 + "[" + repository.getId() + "].id", null, - "contains an uninterpolated expression; the repository will be skipped.", + "contains an uninterpolated expression.", repository); } } @@ -1650,11 +1650,11 @@ && validateStringNotEmpty( if (matcher.find()) { addViolation( problems, - Severity.WARNING, + Severity.ERROR, Version.V40, prefix + prefix2 + "[" + repository.getId() + "].url", null, - "contains an uninterpolated expression; the repository will be skipped.", + "contains an uninterpolated expression.", repository); } } diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultProfileActivationContext.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultProfileActivationContext.java index 9d9ba94546c8..6fcccadaf597 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultProfileActivationContext.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultProfileActivationContext.java @@ -27,6 +27,7 @@ import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -35,6 +36,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import org.apache.maven.api.model.Model; +import org.apache.maven.api.model.Profile; import org.apache.maven.api.services.Interpolator; import org.apache.maven.api.services.InterpolatorException; import org.apache.maven.api.services.ModelBuilderException; @@ -168,6 +170,7 @@ private boolean matchesExists(Map exists, DefaultProfileA private List inactiveProfileIds = Collections.emptyList(); private Map systemProperties = Collections.emptyMap(); private Map userProperties = Collections.emptyMap(); + private Map cascadedProfileProperties = Collections.emptyMap(); private Model model; final Record record; @@ -333,6 +336,24 @@ public String getModelProperty(String key) { } } + /** + * Gets a model property INCLUDING cascaded profile properties. + * This should ONLY be used for profile activation to enable cascading. + * Regular property lookups should use getModelProperty() which excludes cascaded properties. + */ + public String getModelPropertyForActivation(String key) { + // Do NOT use the usedModelProperties cache here because getModelProperty() + // may have already cached a value without cascaded properties for this key. + // Cascaded properties change between activation rounds, so we must always + // check them fresh. + String value = cascadedProfileProperties.get(key); + if (value != null) { + return value; + } + // Fall back to regular model property (which may use the cache) + return getModelProperty(key); + } + @Override public String getModelBaseDirectory() { if (record != null) { @@ -379,7 +400,7 @@ public String interpolatePath(String path) throws InterpolatorException { if ("project.rootDirectory".equals(s)) { return getModelRootDirectory(); } - String r = getModelProperty(s); + String r = getModelPropertyForActivation(s); if (r == null) { r = getUserProperty(s); } @@ -461,4 +482,34 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { private static Map unmodifiable(Map map) { return map != null ? Collections.unmodifiableMap(map) : Collections.emptyMap(); } + + // Cascading profile activation methods + + @Override + public void addProfileProperties(Collection activatedProfiles) { + // Inject properties from activated profiles for cascading activation + // Store them separately instead of mutating the model + if (activatedProfiles != null && !activatedProfiles.isEmpty()) { + Map newCascadedProperties = new HashMap<>(cascadedProfileProperties); + + // Add properties from each activated profile + for (Profile profile : activatedProfiles) { + if (profile.getProperties() != null) { + newCascadedProperties.putAll(profile.getProperties()); + } + } + + // Update the cascaded properties map + this.cascadedProfileProperties = Collections.unmodifiableMap(newCascadedProperties); + } + } + + /** + * Clear all cascaded profile properties. + * This should be called after profile activation is complete to prevent cascaded properties + * from leaking into subsequent uses of the context. + */ + public void clearCascadedProfileProperties() { + this.cascadedProfileProperties = Collections.emptyMap(); + } } diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultProfileSelector.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultProfileSelector.java index 407d35a71c23..a47fcf9653e8 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultProfileSelector.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultProfileSelector.java @@ -63,32 +63,54 @@ public DefaultProfileSelector addProfileActivator(ProfileActivator profileActiva @Override public List getActiveProfiles( Collection profiles, ProfileActivationContext context, ModelProblemCollector problems) { - List activeProfiles = new ArrayList<>(profiles.size()); + + List activeSettingsProfiles = new ArrayList<>(); + List activePomProfiles = new ArrayList<>(); List activePomProfilesByDefault = new ArrayList<>(); - boolean activatedPomProfileNotByDefault = false; - for (Profile profile : profiles) { - if (!context.isProfileInactive(profile.getId())) { - if (context.isProfileActive(profile.getId()) || isActive(profile, context, problems)) { - activeProfiles.add(profile); - if (Profile.SOURCE_POM.equals(profile.getSource())) { - activatedPomProfileNotByDefault = true; - } - } else if (isActiveByDefault(profile)) { - if (Profile.SOURCE_POM.equals(profile.getSource())) { - activePomProfilesByDefault.add(profile); - } else { - activeProfiles.add(profile); + // Cascading mode: iterate until no more profiles are activated + List remainingProfiles = new ArrayList<>(profiles); + List activatedProfiles; + do { + activatedProfiles = new ArrayList<>(); + for (Profile profile : List.copyOf(remainingProfiles)) { + if (!context.isProfileInactive(profile.getId())) { + boolean activated = context.isProfileActive(profile.getId()); + boolean active = isActive(profile, context, problems); + boolean activeByDefault = isActiveByDefault(profile); + if (activated || active || activeByDefault) { + if (Profile.SOURCE_POM.equals(profile.getSource())) { + if (activated || active) { + activePomProfiles.add(profile); + } else { + activePomProfilesByDefault.add(profile); + } + } else { + activeSettingsProfiles.add(profile); + } + remainingProfiles.remove(profile); + activatedProfiles.add(profile); } } } + // Add profile properties for cascading activation + context.addProfileProperties(activatedProfiles); + } while (!activatedProfiles.isEmpty()); + + // Clear cascaded properties to prevent them from leaking into subsequent uses of the context + if (context instanceof org.apache.maven.impl.model.DefaultProfileActivationContext dctx) { + dctx.clearCascadedProfileProperties(); } - if (!activatedPomProfileNotByDefault) { - activeProfiles.addAll(activePomProfilesByDefault); + List allActivated = new ArrayList<>(); + if (activePomProfiles.isEmpty()) { + allActivated.addAll(activePomProfilesByDefault); + } else { + allActivated.addAll(activePomProfiles); } + allActivated.addAll(activeSettingsProfiles); - return activeProfiles; + return allActivated; } private boolean isActive(Profile profile, ProfileActivationContext context, ModelProblemCollector problems) { diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/MavenModelMerger.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/MavenModelMerger.java index b9f259cced1c..9657e0d38f8a 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/MavenModelMerger.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/MavenModelMerger.java @@ -599,7 +599,7 @@ protected void mergeReportPlugin_ReportSets( Object key = getReportSetKey().apply(element); ReportSet existing = merged.get(key); if (existing != null) { - element = mergeReportSet(element, existing, sourceDominant, context); + mergeReportSet(element, existing, sourceDominant, context); } merged.put(key, element); } diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/profile/ConditionProfileActivator.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/profile/ConditionProfileActivator.java index 98aa2c037639..a251f9d6dc6b 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/profile/ConditionProfileActivator.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/profile/ConditionProfileActivator.java @@ -77,7 +77,7 @@ public boolean isActive(Profile profile, ProfileActivationContext context, Model String condition = profile.getActivation().getCondition(); try { Map functions = registerFunctions(context, versionParser); - UnaryOperator propertyResolver = s -> property(context, s); + UnaryOperator propertyResolver = s -> property(profile, context, s); return toBoolean(new ConditionParser(functions, propertyResolver).parse(condition)); } catch (Exception e) { problems.add( @@ -160,12 +160,12 @@ public Map registerFunctions( * @return The value of the property, or null if not found * @throws IllegalArgumentException if the number of arguments is not exactly one */ - String property(ProfileActivationContext context, String name) { - String value = doGetProperty(context, name); - return interpolator.interpolate(value, s -> doGetProperty(context, s)); + String property(Profile profile, ProfileActivationContext context, String name) { + String value = doGetProperty(profile, context, name); + return interpolator.interpolate(value, s -> doGetProperty(profile, context, s)); } - static String doGetProperty(ProfileActivationContext context, String name) { + static String doGetProperty(Profile profile, ProfileActivationContext context, String name) { // Handle special project-related properties if ("project.basedir".equals(name)) { return context.getModelBaseDirectory(); @@ -182,16 +182,23 @@ static String doGetProperty(ProfileActivationContext context, String name) { // Check user properties String v = context.getUserProperty(name); - if (v == null) { - // Check project properties - // TODO: this may leads to instability between file model activation and effective model activation - // as the effective model properties may be different from the file model - v = context.getModelProperty(name); - } if (v == null) { // Check system properties v = context.getSystemProperty(name); } + if (v == null) { + // Check project properties (with cascading support if available) + // ONLY for POM profiles - settings profiles should not use model properties + // TODO: this may leads to instability between file model activation and effective model activation + // as the effective model properties may be different from the file model + if (Profile.SOURCE_POM.equals(profile.getSource())) { + if (context instanceof org.apache.maven.impl.model.DefaultProfileActivationContext dctx) { + v = dctx.getModelPropertyForActivation(name); + } else { + v = context.getModelProperty(name); + } + } + } return v; } } diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/profile/PropertyProfileActivator.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/profile/PropertyProfileActivator.java index 8b2114ad3633..6fd144fdfdba 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/profile/PropertyProfileActivator.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/profile/PropertyProfileActivator.java @@ -76,6 +76,16 @@ public boolean isActive(Profile profile, ProfileActivationContext context, Model if (sysValue == null) { sysValue = context.getSystemProperty(name); } + // Check model properties last (for cascading profile activation) + // ONLY for POM profiles - settings profiles should not use model properties + if (sysValue == null && Profile.SOURCE_POM.equals(profile.getSource())) { + // Use getModelPropertyForActivation to include cascaded profile properties + if (context instanceof org.apache.maven.impl.model.DefaultProfileActivationContext dctx) { + sysValue = dctx.getModelPropertyForActivation(name); + } else { + sysValue = context.getModelProperty(name); + } + } String propValue = property.getValue(); if (propValue != null && !propValue.isEmpty()) { diff --git a/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultInheritanceAssemblerTest.java b/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultInheritanceAssemblerTest.java deleted file mode 100644 index e8ee2d37a111..000000000000 --- a/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultInheritanceAssemblerTest.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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.impl.model; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; - -import org.apache.maven.api.model.Model; -import org.apache.maven.api.services.xml.XmlReaderRequest; -import org.apache.maven.api.services.xml.XmlWriterRequest; -import org.apache.maven.impl.DefaultModelXmlFactory; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.xmlunit.builder.DiffBuilder; -import org.xmlunit.diff.Diff; - -import static org.junit.jupiter.api.Assertions.assertFalse; - -class DefaultInheritanceAssemblerTest { - - private DefaultModelXmlFactory xmlFactory; - - private DefaultInheritanceAssembler assembler; - - @BeforeEach - void setUp() { - xmlFactory = new DefaultModelXmlFactory(); - assembler = new DefaultInheritanceAssembler(); - } - - private Path getPom(String name) { - return Paths.get("../../compat/maven-model-builder/src/test/resources/poms/inheritance/" + name + ".xml"); - } - - private Model getModel(String name) throws Exception { - return xmlFactory.read(XmlReaderRequest.builder().path(getPom(name)).build()); - } - - @Test - void testPluginConfiguration() throws Exception { - testInheritance("plugin-configuration"); - } - - public void testInheritance(String baseName) throws Exception { - testInheritance(baseName, false); - testInheritance(baseName, true); - } - - public void testInheritance(String baseName, boolean fromRepo) throws Exception { - Model parent = getModel(baseName + "-parent"); - Model child = getModel(baseName + "-child"); - - if (!fromRepo) { - // when model is built from disk, pomFile is set - // (has consequences in inheritance algorithm since getProjectDirectory() returns non-null) - parent = parent.withPomFile(getPom(baseName + "-parent").toAbsolutePath()); - child = child.withPomFile(getPom(baseName + "-child").toAbsolutePath()); - } - - Model assembled = assembler.assembleModelInheritance(child, parent, null, null); - - // write baseName + "-actual" - Path actual = Paths.get( - "target/test-classes/poms/inheritance/" + baseName + (fromRepo ? "-build" : "-repo") + "-actual.xml"); - Files.createDirectories(actual.getParent()); - xmlFactory.write(XmlWriterRequest.builder() - .content(assembled) - .path(actual) - .build()); - - // check with getPom( baseName + "-expected" ) - Path expected = getPom(baseName + "-expected"); - - Diff diff = DiffBuilder.compare(expected.toFile()) - .withTest(actual.toFile()) - .ignoreComments() - .ignoreWhitespace() - .build(); - assertFalse(diff.hasDifferences(), "XML files should be identical: " + diff.toString()); - } -} diff --git a/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelBuilderTest.java b/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelBuilderTest.java index 4b5ba3fe95c7..d361faad123a 100644 --- a/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelBuilderTest.java +++ b/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelBuilderTest.java @@ -41,7 +41,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; /** * @@ -215,69 +214,6 @@ public void testDirectoryPropertiesInProfilesAndRepositories() { expectedUrl, result.getEffectiveModel().getRepositories().get(0).getUrl()); } - @Test - public void testCiFriendlyDependencyVersionInterpolation() { - // Test that ${revision} in dependency versions is interpolated using model properties - ModelBuilderRequest request = ModelBuilderRequest.builder() - .session(session) - .requestType(ModelBuilderRequest.RequestType.BUILD_PROJECT) - .source(Sources.buildSource(getPom("ci-friendly-deps"))) - .build(); - ModelBuilderResult result = builder.newSession().build(request); - assertNotNull(result); - Model effective = result.getEffectiveModel(); - assertEquals("1.0.0-SNAPSHOT", effective.getVersion()); - assertEquals(1, effective.getDependencies().size()); - assertEquals( - "1.0.0-SNAPSHOT", - effective.getDependencies().get(0).getVersion(), - "${revision} in dependency version should be interpolated"); - assertNotNull(effective.getDistributionManagement()); - assertEquals( - "releases-1.0.0-SNAPSHOT", - effective.getDistributionManagement().getRepository().getId(), - "${revision} in distributionManagement repository id should be interpolated"); - } - - @Test - public void testCiFriendlyDependencyVersionWithUserProperties() { - // Test that ${revision} in dependency versions is interpolated using user properties override - ModelBuilderRequest request = ModelBuilderRequest.builder() - .session(session) - .requestType(ModelBuilderRequest.RequestType.BUILD_PROJECT) - .userProperties(Map.of("revision", "2.0.0")) - .source(Sources.buildSource(getPom("ci-friendly-deps"))) - .build(); - ModelBuilderResult result = builder.newSession().build(request); - assertNotNull(result); - Model effective = result.getEffectiveModel(); - assertEquals("2.0.0", effective.getVersion()); - assertEquals(1, effective.getDependencies().size()); - assertEquals( - "2.0.0", - effective.getDependencies().get(0).getVersion(), - "${revision} in dependency version should be interpolated with user property"); - } - - @Test - public void testCiFriendlyDependencyVersionWithUserPropertiesOnly() { - ModelBuilderRequest request = ModelBuilderRequest.builder() - .session(session) - .requestType(ModelBuilderRequest.RequestType.BUILD_PROJECT) - .userProperties(Map.of("revision", "3.0.0")) - .source(Sources.buildSource(getPom("ci-friendly-deps-no-prop"))) - .build(); - ModelBuilderResult result = builder.newSession().build(request); - assertNotNull(result); - Model effective = result.getEffectiveModel(); - assertEquals("3.0.0", effective.getVersion(), "project version should use user property"); - assertEquals(1, effective.getDependencies().size()); - assertEquals( - "3.0.0", - effective.getDependencies().get(0).getVersion(), - "${revision} in dependency version should be interpolated with user-only property"); - } - @Test public void testMissingDependencyGroupIdInference() throws Exception { // Test that dependencies with missing groupId but present version are inferred correctly in model 4.1.0 @@ -322,56 +258,6 @@ public void testMissingDependencyGroupIdInference() throws Exception { } } - /** - * Verifies that when a BUILD_CONSUMER derived session is created with explicit - * repositories, those repositories are propagated to the derived session's - * {@code repositories} and {@code externalRepositories}. - *

- * This is critical for consumer POM building: the consumer POM builder reuses the - * existing {@code ModelBuilderSession} and calls {@code build()} with a request - * containing the project's repositories (which may include non-central repos from - * settings.xml profiles). Without this, BOM imports from non-central repositories fail. - */ - @Test - public void testBuildConsumerWithExplicitRepositories() { - // First build to create the mainSession (simulates project build phase) - ModelBuilderRequest firstRequest = ModelBuilderRequest.builder() - .session(session) - .requestType(ModelBuilderRequest.RequestType.BUILD_PROJECT) - .source(Sources.buildSource(getPom("simple-standalone"))) - .build(); - ModelBuilder.ModelBuilderSession mbs = builder.newSession(); - mbs.build(firstRequest); - - // Access the mainSession (package-private) to call derive() and verify state - DefaultModelBuilder.ModelBuilderSessionState mainState = - ((DefaultModelBuilder.ModelBuilderSessionImpl) mbs).mainSession; - - // Verify the main session only has central - assertEquals(1, mainState.getRepositories().size()); - assertEquals("central", mainState.getRepositories().get(0).getId()); - - // Derive a BUILD_CONSUMER session with explicit repositories - RemoteRepository customRepo = session.createRemoteRepository("custom-repo", "https://repo.example.com/maven2"); - ModelBuilderRequest consumerRequest = ModelBuilderRequest.builder() - .session(session) - .requestType(ModelBuilderRequest.RequestType.BUILD_CONSUMER) - .source(Sources.buildSource(getPom("simple-standalone"))) - .repositories(List.of( - customRepo, session.createRemoteRepository("central", "https://repo.maven.apache.org/maven2"))) - .build(); - - DefaultModelBuilder.ModelBuilderSessionState derived = mainState.derive(consumerRequest); - - // Verify the derived session includes the custom repository - assertTrue( - derived.getRepositories().stream().anyMatch(r -> "custom-repo".equals(r.getId())), - "Derived session repositories should include the custom repo from the request"); - assertTrue( - derived.getExternalRepositories().stream().anyMatch(r -> "custom-repo".equals(r.getId())), - "Derived session externalRepositories should include the custom repo from the request"); - } - private Path getPom(String name) { return Paths.get("src/test/resources/poms/factory/" + name + ".xml").toAbsolutePath(); } diff --git a/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelPathTranslatorTest.java b/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelPathTranslatorTest.java deleted file mode 100644 index be71eff2d5ec..000000000000 --- a/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelPathTranslatorTest.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * 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.impl.model; - -import java.nio.file.Path; - -import org.apache.maven.api.model.Build; -import org.apache.maven.api.model.Model; -import org.apache.maven.api.model.Resource; -import org.apache.maven.api.model.Source; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -class DefaultModelPathTranslatorTest { - - private DefaultModelPathTranslator translator; - private Path basedir; - - @BeforeEach - void setUp() { - translator = new DefaultModelPathTranslator(new DefaultPathTranslator()); - basedir = Path.of("/home/user/myproject").toAbsolutePath(); - } - - @Test - void sourceDirectoryIsAlignedToBasedir() { - Source source = Source.newBuilder().directory("src/main/java").build(); - Model model = Model.newBuilder() - .build(Build.newBuilder().sources(java.util.List.of(source)).build()) - .build(); - - Model result = translator.alignToBaseDirectory(model, basedir, null); - - String dir = result.getBuild().getSources().get(0).getDirectory(); - assertTrue(Path.of(dir).isAbsolute(), "directory should be absolute after alignment"); - assertTrue(Path.of(dir).endsWith(Path.of("src/main/java")), "directory should end with original path"); - } - - @Test - void sourceTargetPathIsNotAlignedToBasedir() { - Source source = Source.newBuilder() - .directory("src/main/resources") - .targetPath("META-INF/resources") - .build(); - Model model = Model.newBuilder() - .build(Build.newBuilder().sources(java.util.List.of(source)).build()) - .build(); - - Model result = translator.alignToBaseDirectory(model, basedir, null); - - String targetPath = result.getBuild().getSources().get(0).getTargetPath(); - assertEquals("META-INF/resources", targetPath, "targetPath should remain relative"); - } - - @Test - void sourceTargetPathDotPrefixRemainsRelative() { - Source source = Source.newBuilder() - .directory("src/main/resources") - .targetPath(".grammar") - .build(); - Model model = Model.newBuilder() - .build(Build.newBuilder().sources(java.util.List.of(source)).build()) - .build(); - - Model result = translator.alignToBaseDirectory(model, basedir, null); - - String targetPath = result.getBuild().getSources().get(0).getTargetPath(); - assertEquals(".grammar", targetPath, "dot-prefixed targetPath should remain relative"); - } - - @Test - void resourceDirectoryIsAlignedButTargetPathIsNot() { - Resource resource = Resource.newBuilder() - .directory("src/main/resources") - .targetPath("custom-output") - .build(); - Model model = Model.newBuilder() - .build(Build.newBuilder().resources(java.util.List.of(resource)).build()) - .build(); - - Model result = translator.alignToBaseDirectory(model, basedir, null); - - Resource aligned = result.getBuild().getResources().get(0); - assertTrue( - Path.of(aligned.getDirectory()).isAbsolute(), "resource directory should be absolute after alignment"); - assertEquals("custom-output", aligned.getTargetPath(), "resource targetPath should remain relative"); - } -} diff --git a/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelValidatorTest.java b/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelValidatorTest.java index 591191e8920f..5825e1ab4481 100644 --- a/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelValidatorTest.java +++ b/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelValidatorTest.java @@ -915,33 +915,31 @@ void repositoryWithBasedirExpression() throws Exception { SimpleProblemCollector result = validateRaw("raw-model/repository-with-basedir-expression.xml"); // This test runs on raw model without interpolation, so all expressions appear uninterpolated // In the real flow, supported expressions would be interpolated before validation - assertViolations(result, 0, 0, 3); + assertViolations(result, 0, 3, 0); } @Test void repositoryWithUnsupportedExpression() throws Exception { SimpleProblemCollector result = validateRaw("raw-model/repository-with-unsupported-expression.xml"); - // Unsupported expressions should cause validation warnings (repos will be skipped at build time) - assertViolations(result, 0, 0, 1); + // Unsupported expressions should cause validation errors + assertViolations(result, 0, 1, 0); } @Test void repositoryWithUninterpolatedId() throws Exception { SimpleProblemCollector result = validateRaw("raw-model/repository-with-uninterpolated-id.xml"); - // Uninterpolated expressions in repository IDs should cause validation warnings - // (repos will be skipped at build time) + // Uninterpolated expressions in repository IDs should cause validation errors // distributionManagement repositories skip expression check since parent properties // may not be available at file model validation stage - assertViolations(result, 0, 0, 2); + assertViolations(result, 0, 2, 0); - // Check that repository ID validation warnings are present for repositories and pluginRepositories - assertTrue(result.getWarnings().stream() - .anyMatch(warning -> warning.contains("repositories.repository.[${repository.id}].id") - && warning.contains("contains an uninterpolated expression"))); - assertTrue(result.getWarnings().stream() - .anyMatch( - warning -> warning.contains("pluginRepositories.pluginRepository.[${plugin.repository.id}].id") - && warning.contains("contains an uninterpolated expression"))); + // Check that repository ID validation errors are present for repositories and pluginRepositories + assertTrue(result.getErrors().stream() + .anyMatch(error -> error.contains("repositories.repository.[${repository.id}].id") + && error.contains("contains an uninterpolated expression"))); + assertTrue(result.getErrors().stream() + .anyMatch(error -> error.contains("pluginRepositories.pluginRepository.[${plugin.repository.id}].id") + && error.contains("contains an uninterpolated expression"))); } @Test diff --git a/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultProfileSelectorTest.java b/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultProfileSelectorTest.java new file mode 100644 index 000000000000..bac676e69a23 --- /dev/null +++ b/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultProfileSelectorTest.java @@ -0,0 +1,433 @@ +/* + * 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.impl.model; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.apache.maven.api.model.Activation; +import org.apache.maven.api.model.ActivationProperty; +import org.apache.maven.api.model.Model; +import org.apache.maven.api.model.Profile; +import org.apache.maven.api.services.model.ProfileActivationContext; +import org.apache.maven.api.services.model.ProfileActivator; +import org.apache.maven.impl.model.profile.PropertyProfileActivator; +import org.apache.maven.impl.model.profile.SimpleProblemCollector; +import org.apache.maven.impl.model.rootlocator.DefaultRootLocator; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests {@link DefaultProfileSelector} with focus on cascading activation behavior. + */ +public class DefaultProfileSelectorTest { + + private DefaultProfileSelector selector; + private SimpleProblemCollector problems; + + @BeforeEach + void setUp() { + selector = new DefaultProfileSelector(); + // Add a simple property-based activator for testing + selector.addProfileActivator(new PropertyProfileActivator()); + problems = new SimpleProblemCollector(); + } + + @Test + void testNonCascadingActivation() { + // Create profiles with property-based activation + Profile profile1 = createProfile("profile1", "prop1", "value1", Profile.SOURCE_POM); + Profile profile2 = createProfile("profile2", "prop2", "value2", Profile.SOURCE_POM); + + List profiles = Arrays.asList(profile1, profile2); + + // Create context with prop1 set + DefaultProfileActivationContext context = new DefaultProfileActivationContext( + new DefaultPathTranslator(), new DefaultRootLocator(), new DefaultInterpolator()); + context.setModel(Model.newInstance()); + context.setSystemProperties(Map.of("prop1", "value1")); + + // Test cascading mode (current implementation only supports cascading) + List activeProfiles = selector.getActiveProfiles(profiles, context, problems); + + assertEquals(1, activeProfiles.size()); + assertEquals("profile1", activeProfiles.get(0).getId()); + assertTrue(problems.getErrors().isEmpty()); + } + + @Test + void testCascadingActivation() { + // Create profiles where one activates another through properties + Profile profile1 = createProfileWithProperties( + "profile1", "prop1", "value1", Map.of("prop2", "value2"), Profile.SOURCE_POM); + Profile profile2 = createProfile("profile2", "prop2", "value2", Profile.SOURCE_POM); + + List profiles = Arrays.asList(profile1, profile2); + + // Create context with prop1 set (should activate profile1, which sets prop2, which activates profile2) + DefaultProfileActivationContext context = new DefaultProfileActivationContext( + new DefaultPathTranslator(), new DefaultRootLocator(), new DefaultInterpolator()); + context.setSystemProperties(Map.of("prop1", "value1")); + context.setModel(Model.newInstance()); // Set a model for property injection + + // Test cascading mode + List activeProfiles = selector.getActiveProfiles(profiles, context, problems); + + assertEquals(2, activeProfiles.size()); + assertTrue(activeProfiles.stream().anyMatch(p -> "profile1".equals(p.getId()))); + assertTrue(activeProfiles.stream().anyMatch(p -> "profile2".equals(p.getId()))); + assertTrue(problems.getErrors().isEmpty()); + } + + @Test + void testCascadingVsNonCascadingDifference() { + // Create profiles where cascading would activate more profiles + Profile profile1 = createProfileWithProperties( + "profile1", "prop1", "value1", Map.of("prop2", "value2"), Profile.SOURCE_POM); + Profile profile2 = createProfile("profile2", "prop2", "value2", Profile.SOURCE_POM); + + List profiles = Arrays.asList(profile1, profile2); + + DefaultProfileActivationContext context = new DefaultProfileActivationContext( + new DefaultPathTranslator(), new DefaultRootLocator(), new DefaultInterpolator()); + context.setSystemProperties(Map.of("prop1", "value1")); + context.setModel(Model.newInstance()); // Set a model for property injection + + // Cascading should activate both profile1 and profile2 + List cascading = selector.getActiveProfiles(profiles, context, problems); + assertEquals(2, cascading.size()); + } + + @Test + void testActiveByDefaultProfiles() { + Profile defaultProfile = createActiveByDefaultProfile("default-profile", Profile.SOURCE_POM); + Profile conditionalProfile = createProfile("conditional", "prop1", "value1", Profile.SOURCE_POM); + + List profiles = Arrays.asList(defaultProfile, conditionalProfile); + + DefaultProfileActivationContext context = new DefaultProfileActivationContext( + new DefaultPathTranslator(), new DefaultRootLocator(), new DefaultInterpolator()); + context.setModel(Model.newInstance()); + + // Should activate default profile when no conditions are met + List activeProfiles = selector.getActiveProfiles(profiles, context, problems); + assertEquals(1, activeProfiles.size()); + assertEquals("default-profile", activeProfiles.get(0).getId()); + + // Should not activate default profile when conditional profile is active + context.setSystemProperties(Map.of("prop1", "value1")); + activeProfiles = selector.getActiveProfiles(profiles, context, problems); + assertEquals(1, activeProfiles.size()); + assertEquals("conditional", activeProfiles.get(0).getId()); + } + + @Test + void testMixedSourceProfiles() { + Profile pomProfile = createProfile("pom-profile", "prop1", "value1", Profile.SOURCE_POM); + Profile settingsProfile = createProfile("settings-profile", "prop2", "value2", Profile.SOURCE_SETTINGS); + + List profiles = Arrays.asList(pomProfile, settingsProfile); + + DefaultProfileActivationContext context = new DefaultProfileActivationContext( + new DefaultPathTranslator(), new DefaultRootLocator(), new DefaultInterpolator()); + context.setModel(Model.newInstance()); + context.setSystemProperties(Map.of("prop1", "value1", "prop2", "value2")); + + List activeProfiles = selector.getActiveProfiles(profiles, context, problems); + assertEquals(2, activeProfiles.size()); + + // Settings profiles should come after POM profiles in the result + assertEquals("pom-profile", activeProfiles.get(0).getId()); + assertEquals("settings-profile", activeProfiles.get(1).getId()); + } + + @Test + void testEmptyProfilesList() { + List profiles = Collections.emptyList(); + DefaultProfileActivationContext context = new DefaultProfileActivationContext( + new DefaultPathTranslator(), new DefaultRootLocator(), new DefaultInterpolator()); + context.setModel(Model.newInstance()); + + List activeProfiles = selector.getActiveProfiles(profiles, context, problems); + assertTrue(activeProfiles.isEmpty()); + } + + @Test + void testExplicitlyActivatedProfiles() { + Profile profile1 = createProfile("profile1", "nonexistent", "value", Profile.SOURCE_POM); + Profile profile2 = createProfile("profile2", "prop2", "value2", Profile.SOURCE_POM); + + List profiles = Arrays.asList(profile1, profile2); + + DefaultProfileActivationContext context = new DefaultProfileActivationContext( + new DefaultPathTranslator(), + new DefaultRootLocator(), + new DefaultInterpolator(), + List.of("profile1"), + List.of(), + Map.of("prop2", "value2"), + Map.of(), + Model.newInstance()); + + List activeProfiles = selector.getActiveProfiles(profiles, context, problems); + assertEquals(2, activeProfiles.size()); + assertTrue(activeProfiles.stream().anyMatch(p -> "profile1".equals(p.getId()))); + assertTrue(activeProfiles.stream().anyMatch(p -> "profile2".equals(p.getId()))); + } + + @Test + void testCascadingActivationChain() { + // Create a chain of profiles: profile1 -> profile2 -> profile3 + Profile profile1 = createProfileWithProperties( + "profile1", "prop1", "value1", Map.of("prop2", "value2"), Profile.SOURCE_POM); + Profile profile2 = createProfileWithProperties( + "profile2", "prop2", "value2", Map.of("prop3", "value3"), Profile.SOURCE_POM); + Profile profile3 = createProfile("profile3", "prop3", "value3", Profile.SOURCE_POM); + + List profiles = Arrays.asList(profile1, profile2, profile3); + + // Create context with prop1 set + DefaultProfileActivationContext context = new DefaultProfileActivationContext( + new DefaultPathTranslator(), new DefaultRootLocator(), new DefaultInterpolator()); + context.setSystemProperties(Map.of("prop1", "value1")); + context.setModel(Model.newInstance()); + + // Test cascading mode + List activeProfiles = selector.getActiveProfiles(profiles, context, problems); + + // All three profiles should be activated through cascading + assertEquals(3, activeProfiles.size()); + assertTrue(activeProfiles.stream().anyMatch(p -> "profile1".equals(p.getId()))); + assertTrue(activeProfiles.stream().anyMatch(p -> "profile2".equals(p.getId()))); + assertTrue(activeProfiles.stream().anyMatch(p -> "profile3".equals(p.getId()))); + assertTrue(problems.getErrors().isEmpty()); + } + + @Test + void testCascadingStopCondition() { + // Test that cascading stops when no more profiles can be activated + Profile profile1 = createProfileWithProperties( + "profile1", "prop1", "value1", Map.of("prop2", "value2"), Profile.SOURCE_POM); + Profile profile2 = createProfileWithProperties( + "profile2", "prop2", "value2", Map.of("prop3", "value3"), Profile.SOURCE_POM); + // profile3 requires prop4 which is never set, so cascading should stop + Profile profile3 = createProfile("profile3", "prop4", "value4", Profile.SOURCE_POM); + + List profiles = Arrays.asList(profile1, profile2, profile3); + + // Create context with prop1 set + DefaultProfileActivationContext context = new DefaultProfileActivationContext( + new DefaultPathTranslator(), new DefaultRootLocator(), new DefaultInterpolator()); + context.setSystemProperties(Map.of("prop1", "value1")); + context.setModel(Model.newInstance()); + + // Test cascading mode + List activeProfiles = selector.getActiveProfiles(profiles, context, problems); + + // Only profile1 and profile2 should be activated, profile3 should not + assertEquals(2, activeProfiles.size()); + assertTrue(activeProfiles.stream().anyMatch(p -> "profile1".equals(p.getId()))); + assertTrue(activeProfiles.stream().anyMatch(p -> "profile2".equals(p.getId()))); + assertTrue(activeProfiles.stream().noneMatch(p -> "profile3".equals(p.getId()))); + assertTrue(problems.getErrors().isEmpty()); + } + + @Test + void testCascadingWithCircularDependency() { + // Test that cascading handles circular dependencies gracefully + Profile profile1 = createProfileWithProperties( + "profile1", "prop1", "value1", Map.of("prop2", "value2"), Profile.SOURCE_POM); + Profile profile2 = createProfileWithProperties( + "profile2", "prop2", "value2", Map.of("prop1", "value1"), Profile.SOURCE_POM); + + List profiles = Arrays.asList(profile1, profile2); + + // Create context with prop1 set + DefaultProfileActivationContext context = new DefaultProfileActivationContext( + new DefaultPathTranslator(), new DefaultRootLocator(), new DefaultInterpolator()); + context.setSystemProperties(Map.of("prop1", "value1")); + context.setModel(Model.newInstance()); + + // Test cascading mode + List activeProfiles = selector.getActiveProfiles(profiles, context, problems); + + // Both profiles should be activated, but cascading should stop after first iteration + assertEquals(2, activeProfiles.size()); + assertTrue(activeProfiles.stream().anyMatch(p -> "profile1".equals(p.getId()))); + assertTrue(activeProfiles.stream().anyMatch(p -> "profile2".equals(p.getId()))); + assertTrue(problems.getErrors().isEmpty()); + } + + @Test + void testCascadingWithInactiveProfile() { + // Create profiles where one would activate another, but the second is explicitly deactivated + Profile profile1 = createProfileWithProperties( + "profile1", "prop1", "value1", Map.of("prop2", "value2"), Profile.SOURCE_POM); + Profile profile2 = createProfile("profile2", "prop2", "value2", Profile.SOURCE_POM); + + List profiles = Arrays.asList(profile1, profile2); + + // Create context with prop1 set and profile2 explicitly deactivated + DefaultProfileActivationContext context = new DefaultProfileActivationContext( + new DefaultPathTranslator(), + new DefaultRootLocator(), + new DefaultInterpolator(), + List.of(), + List.of("profile2"), + Map.of("prop1", "value1"), + Map.of(), + Model.newInstance()); + + // Test cascading mode + List activeProfiles = selector.getActiveProfiles(profiles, context, problems); + + // Only profile1 should be activated, profile2 should be deactivated despite cascading + assertEquals(1, activeProfiles.size()); + assertTrue(activeProfiles.stream().anyMatch(p -> "profile1".equals(p.getId()))); + assertTrue(activeProfiles.stream().noneMatch(p -> "profile2".equals(p.getId()))); + assertTrue(problems.getErrors().isEmpty()); + } + + @Test + void testCascadingWithRecordImmutability() { + // Test that profile records remain immutable during cascading + Profile originalProfile1 = createProfileWithProperties( + "profile1", "prop1", "value1", Map.of("prop2", "value2"), Profile.SOURCE_POM); + Profile originalProfile2 = createProfile("profile2", "prop2", "value2", Profile.SOURCE_POM); + + List profiles = Arrays.asList(originalProfile1, originalProfile2); + + // Create context with prop1 set + DefaultProfileActivationContext context = new DefaultProfileActivationContext( + new DefaultPathTranslator(), new DefaultRootLocator(), new DefaultInterpolator()); + context.setSystemProperties(Map.of("prop1", "value1")); + context.setModel(Model.newInstance()); + + // Test cascading mode + List activeProfiles = selector.getActiveProfiles(profiles, context, problems); + + // Verify that original profiles are unchanged (immutable records) + assertEquals("profile1", originalProfile1.getId()); + assertEquals("profile2", originalProfile2.getId()); + assertEquals(Map.of("prop2", "value2"), originalProfile1.getProperties()); + assertEquals(Map.of(), originalProfile2.getProperties()); + + // Verify activation worked + assertEquals(2, activeProfiles.size()); + assertTrue(problems.getErrors().isEmpty()); + } + + // Helper methods for creating test profiles + + private Profile createProfile(String id, String propName, String propValue, String source) { + Profile profile = Profile.newBuilder() + .id(id) + .activation(Activation.newBuilder() + .property(ActivationProperty.newBuilder() + .name(propName) + .value(propValue) + .build()) + .build()) + .build(); + profile.setSource(source); + return profile; + } + + private Profile createProfileWithProperties( + String id, String propName, String propValue, Map profileProperties, String source) { + Profile profile = Profile.newBuilder() + .id(id) + .activation(Activation.newBuilder() + .property(ActivationProperty.newBuilder() + .name(propName) + .value(propValue) + .build()) + .build()) + .properties(profileProperties) + .build(); + profile.setSource(source); + return profile; + } + + private Profile createActiveByDefaultProfile(String id, String source) { + Profile profile = Profile.newBuilder() + .id(id) + .activation(Activation.newBuilder().activeByDefault(true).build()) + .build(); + profile.setSource(source); + return profile; + } + + /** + * Simple property-based profile activator for testing. + */ + private static class PropertyProfileActivator implements ProfileActivator { + @Override + public boolean isActive( + Profile profile, + ProfileActivationContext context, + org.apache.maven.api.services.ModelProblemCollector problems) { + Activation activation = profile.getActivation(); + if (activation == null || activation.getProperty() == null) { + return false; + } + + ActivationProperty property = activation.getProperty(); + String name = property.getName(); + String expectedValue = property.getValue(); + + if (name == null) { + return false; + } + + // Check user properties first, then model properties (for cascading), then system properties + String actualValue = context.getUserProperty(name); + if (actualValue == null) { + // Use getModelPropertyForActivation to include cascaded profile properties + if (context instanceof DefaultProfileActivationContext dctx) { + actualValue = dctx.getModelPropertyForActivation(name); + } else { + actualValue = context.getModelProperty(name); + } + } + if (actualValue == null) { + actualValue = context.getSystemProperty(name); + } + + if (expectedValue == null || expectedValue.isEmpty()) { + return actualValue != null; + } + + return expectedValue.equals(actualValue); + } + + @Override + public boolean presentInConfig( + Profile profile, + ProfileActivationContext context, + org.apache.maven.api.services.ModelProblemCollector problems) { + return profile.getActivation() != null && profile.getActivation().getProperty() != null; + } + } +} diff --git a/impl/maven-impl/src/test/java/org/apache/maven/impl/model/profile/ConditionParserTest.java b/impl/maven-impl/src/test/java/org/apache/maven/impl/model/profile/ConditionParserTest.java index 56d2cbb3aec9..9060cfa2499e 100644 --- a/impl/maven-impl/src/test/java/org/apache/maven/impl/model/profile/ConditionParserTest.java +++ b/impl/maven-impl/src/test/java/org/apache/maven/impl/model/profile/ConditionParserTest.java @@ -52,7 +52,7 @@ void setUp() { ConditionProfileActivator activator = new ConditionProfileActivator(versionParser, new DefaultInterpolator()); functions = activator.registerFunctions(context, versionParser); - propertyResolver = s -> activator.property(context, s); + propertyResolver = s -> activator.property(null, context, s); parser = new ConditionParser(functions, propertyResolver); } diff --git a/impl/maven-impl/src/test/resources/poms/factory/ci-friendly-deps-no-prop.xml b/impl/maven-impl/src/test/resources/poms/factory/ci-friendly-deps-no-prop.xml deleted file mode 100644 index 63f3b290e89f..000000000000 --- a/impl/maven-impl/src/test/resources/poms/factory/ci-friendly-deps-no-prop.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - 4.0.0 - - com.test - ci-friendly-deps-no-prop - ${revision} - pom - - - - com.test - some-dep - ${revision} - - - diff --git a/impl/maven-impl/src/test/resources/poms/factory/ci-friendly-deps.xml b/impl/maven-impl/src/test/resources/poms/factory/ci-friendly-deps.xml deleted file mode 100644 index f501eb35864b..000000000000 --- a/impl/maven-impl/src/test/resources/poms/factory/ci-friendly-deps.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - 4.0.0 - - com.test - ci-friendly-deps - ${revision} - pom - - - 1.0.0-SNAPSHOT - - - - - com.test - some-dep - ${revision} - - - - - - releases-${revision} - https://repo.example.com/releases - - - diff --git a/impl/maven-impl/src/test/resources/poms/factory/ci-friendly-version.xml b/impl/maven-impl/src/test/resources/poms/factory/ci-friendly-version.xml deleted file mode 100644 index c33bbe3af0a2..000000000000 --- a/impl/maven-impl/src/test/resources/poms/factory/ci-friendly-version.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - 4.0.0 - - org.apache.maven.test - ci-friendly-version-test - ${revision} - pom - - - 1.0.0-SNAPSHOT - - diff --git a/impl/maven-impl/src/test/resources/poms/factory/simple-standalone.xml b/impl/maven-impl/src/test/resources/poms/factory/simple-standalone.xml deleted file mode 100644 index 1ccfe631c16a..000000000000 --- a/impl/maven-impl/src/test/resources/poms/factory/simple-standalone.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - org.apache.maven.tests - simple-standalone - 1.0 - diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITConsumerPomBomFromSettingsRepoTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITConsumerPomBomFromSettingsRepoTest.java deleted file mode 100644 index 22f913910967..000000000000 --- a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITConsumerPomBomFromSettingsRepoTest.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * 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.it; - -import java.io.Reader; -import java.nio.file.Files; -import java.nio.file.Path; - -import org.apache.maven.api.model.Model; -import org.apache.maven.model.v4.MavenStaxReader; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * Verifies that the consumer POM builder can resolve BOM imports from repositories - * defined only in settings.xml profiles (not in the project POM itself). - *

- * This is a regression test for a bug where {@code DefaultConsumerPomBuilder.buildModel()} - * did not pass repositories, profiles, or active profile IDs to the {@code ModelBuilderRequest}, - * and {@code DefaultModelBuilder.derive()} ignored the request's repositories when creating - * derived sessions. This caused "Non-resolvable import POM" failures during the install phase - * for artifacts hosted in private/corporate repositories configured via settings.xml. - * - * @since 4.0.0 - */ -class MavenITConsumerPomBomFromSettingsRepoTest extends AbstractMavenIntegrationTestCase { - - /** - * Verifies that consumer POM flattening works when the BOM is only available - * from a repository defined in a settings.xml profile. - *

- * Without the fix, this test fails with: - *

-     * Non-resolvable import POM: Could not find artifact
-     * org.apache.maven.its.cpbom:the-bom:pom:1.0 in central
-     * 
- */ - @Test - void testConsumerPomWithBomFromSettingsProfileRepo() throws Exception { - Path basedir = extractResources("/gh-11767-consumer-pom-bom-from-settings-repo") - .toPath() - .toAbsolutePath(); - - Verifier verifier = newVerifier(basedir.toString()); - verifier.deleteArtifacts("org.apache.maven.its.cpbom"); - - // Apply settings template with the custom repository URL - verifier.filterFile("settings-template.xml", "settings.xml"); - verifier.addCliArgument("--settings"); - verifier.addCliArgument("settings.xml"); - - // Enable consumer POM flattening to trigger full BOM resolution - // during the install phase consumer POM transformation - verifier.addCliArgument("-Dmaven.consumer.pom.flatten=true"); - verifier.addCliArgument("install"); - verifier.execute(); - verifier.verifyErrorFreeLog(); - - // Verify the consumer POM was generated - Path consumerPom = basedir.resolve(Path.of( - "target", - "project-local-repo", - "org.apache.maven.its.cpbom", - "consumer-pom-bom-settings-repo", - "1.0", - "consumer-pom-bom-settings-repo-1.0-consumer.pom")); - assertTrue(Files.exists(consumerPom), "consumer POM not found at " + consumerPom); - - // Read and validate the consumer POM content - Model consumerPomModel; - try (Reader r = Files.newBufferedReader(consumerPom)) { - consumerPomModel = new MavenStaxReader().read(r); - } - - // The consumer POM should have the dependency with the version resolved from the BOM - assertNotNull(consumerPomModel.getDependencies(), "Consumer POM should have dependencies"); - assertFalse(consumerPomModel.getDependencies().isEmpty(), "Consumer POM should have at least one dependency"); - - boolean hasLibA = consumerPomModel.getDependencies().stream() - .anyMatch(d -> "lib-a".equals(d.getArtifactId()) - && "org.apache.maven.its.cpbom".equals(d.getGroupId()) - && "2.0".equals(d.getVersion())); - assertTrue( - hasLibA, - "Consumer POM should contain lib-a with version 2.0 resolved from the BOM. " - + "Actual dependencies: " + consumerPomModel.getDependencies()); - - // The BOM import should NOT appear in the consumer POM (it's been flattened) - if (consumerPomModel.getDependencyManagement() != null) { - boolean hasBomImport = consumerPomModel.getDependencyManagement().getDependencies().stream() - .anyMatch(d -> "the-bom".equals(d.getArtifactId()) && "import".equals(d.getScope())); - assertFalse(hasBomImport, "Consumer POM should not contain the BOM import after flattening"); - } - } -} diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11140RepoDmUnresolvedTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11140RepoDmUnresolvedTest.java index 3032fe769cf9..b52844511dad 100644 --- a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11140RepoDmUnresolvedTest.java +++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11140RepoDmUnresolvedTest.java @@ -30,14 +30,17 @@ class MavenITgh11140RepoDmUnresolvedTest extends AbstractMavenIntegrationTestCase { @Test - void testWarnsOnUnresolvedPlaceholders() throws Exception { + void testFailsOnUnresolvedPlaceholders() throws Exception { File testDir = extractResources("/gh-11140-repo-dm-unresolved"); Verifier verifier = newVerifier(testDir.getAbsolutePath()); - verifier.addCliArgument("validate"); - verifier.execute(); - // Build should succeed, but warn about uninterpolated expressions (repos are skipped) - verifier.verifyErrorFreeLog(); + try { + verifier.addCliArgument("validate"); + verifier.execute(); + } catch (VerificationException expected) { + // Expected to fail due to unresolved placeholders during model validation + } + // We expect error mentioning uninterpolated expression verifier.verifyTextInLog("contains an uninterpolated expression"); } } diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11140RepoInterpolationTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11140RepoInterpolationTest.java index b9a9298deef5..2c52e8046166 100644 --- a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11140RepoInterpolationTest.java +++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11140RepoInterpolationTest.java @@ -71,15 +71,18 @@ private static String getBaseUri(Path base) { } @Test - void testUnresolvedPlaceholderWarnsAndSkipsRepository() throws Exception { + void testUnresolvedPlaceholderFailsResolution() throws Exception { File testDir = extractResources("/gh-11140-repo-interpolation"); Verifier verifier = newVerifier(testDir.getAbsolutePath()); // Do NOT set env vars, so placeholders stay verifier.addCliArgument("validate"); - verifier.execute(); - // Build should succeed, but warn about uninterpolated expressions - verifier.verifyErrorFreeLog(); + try { + verifier.execute(); + } catch (VerificationException expected) { + // Expected to fail due to unresolved placeholders during model validation + } + // We expect error mentioning uninterpolated expression verifier.verifyTextInLog("contains an uninterpolated expression"); } } diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11772ConsumerPom410Test.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11772ConsumerPom410Test.java deleted file mode 100644 index d238e7a97d83..000000000000 --- a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11772ConsumerPom410Test.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * 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.it; - -import java.io.File; -import java.io.Reader; -import java.nio.file.Files; -import java.nio.file.Path; - -import org.apache.maven.api.model.Model; -import org.apache.maven.model.v4.MavenStaxReader; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * Integration test for GH-11772. - *

- * Verifies that when a parent+child project uses model version 4.1.0 namespace - * (with subprojects, root), the installed consumer POMs are model version 4.0.0, - * while the build POMs retain the original 4.1.0 content. - *

- * This ensures backward compatibility with Maven 3 and Gradle for consumer POMs - * while Maven 4 builds can resolve the full-fidelity build POM. - */ -class MavenITgh11772ConsumerPom410Test extends AbstractMavenIntegrationTestCase { - - private static final String GROUP_ID = "org.apache.maven.its.gh11772"; - - @Test - void testConsumerPomsAre400BuildPomsAre410() throws Exception { - File basedir = extractResources("/gh-11772-consumer-pom-410"); - - Verifier verifier = newVerifier(basedir.getAbsolutePath()); - verifier.deleteArtifacts(GROUP_ID); - verifier.addCliArguments("install"); - verifier.execute(); - verifier.verifyErrorFreeLog(); - - // Verify parent consumer POM (main artifact) is 4.0.0 - Path parentConsumerPom = - Path.of(verifier.getArtifactPath(GROUP_ID, "parent", "1.0.0-SNAPSHOT", "pom")); - assertTrue(Files.exists(parentConsumerPom), "Parent consumer POM should exist"); - Model parentConsumer = readModel(parentConsumerPom); - assertEquals("4.0.0", parentConsumer.getModelVersion(), "Parent consumer POM should be 4.0.0"); - - // Verify parent build POM retains 4.1.0 features - Path parentBuildPom = - Path.of(verifier.getArtifactPath(GROUP_ID, "parent", "1.0.0-SNAPSHOT", "pom", "build")); - assertTrue(Files.exists(parentBuildPom), "Parent build POM should exist"); - Model parentBuild = readModel(parentBuildPom); - // Build POM should retain subprojects (4.1.0 feature) - assertNotNull(parentBuild.getSubprojects(), "Build POM should retain subprojects"); - assertTrue(!parentBuild.getSubprojects().isEmpty(), "Build POM should retain subprojects"); - - // Verify child consumer POM is 4.0.0 - Path childConsumerPom = - Path.of(verifier.getArtifactPath(GROUP_ID, "child", "1.0.0-SNAPSHOT", "pom")); - assertTrue(Files.exists(childConsumerPom), "Child consumer POM should exist"); - Model childConsumer = readModel(childConsumerPom); - assertEquals("4.0.0", childConsumer.getModelVersion(), "Child consumer POM should be 4.0.0"); - - // Child consumer POM should have a parent reference (not flattened by default) - assertNotNull(childConsumer.getParent(), "Child consumer POM should have a parent reference"); - assertEquals(GROUP_ID, childConsumer.getParent().getGroupId()); - assertEquals("parent", childConsumer.getParent().getArtifactId()); - - // Verify child build POM exists - Path childBuildPom = - Path.of(verifier.getArtifactPath(GROUP_ID, "child", "1.0.0-SNAPSHOT", "pom", "build")); - assertTrue(Files.exists(childBuildPom), "Child build POM should exist"); - } - - private static Model readModel(Path pomFile) throws Exception { - try (Reader r = Files.newBufferedReader(pomFile)) { - return new MavenStaxReader().read(r); - } - } -} diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh12184CIFriendlyParentVersionTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh12184CIFriendlyParentVersionTest.java deleted file mode 100644 index 2ba11172c0a9..000000000000 --- a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh12184CIFriendlyParentVersionTest.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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.it; - -import java.io.File; - -import org.junit.jupiter.api.Test; - -/** - * Integration test for #12184. - * Verifies that CI-friendly {@code ${revision}} in parent version works in Maven 4 - * native mode (without maven3Personality) for model version 4.0.0 projects. - */ -class MavenITgh12184CIFriendlyParentVersionTest extends AbstractMavenIntegrationTestCase { - - /** - * Verify that a multi-module project with {@code ${revision}} in parent version - * builds successfully when the property is defined in the parent POM properties. - */ - @Test - void testCiFriendlyParentVersionFromProperties() throws Exception { - File testDir = extractResources("/gh-12184-ci-friendly-parent-version"); - - Verifier verifier = newVerifier(testDir.getAbsolutePath()); - verifier.addCliArgument("validate"); - verifier.execute(); - verifier.verifyErrorFreeLog(); - } - - /** - * Verify that a multi-module project with {@code ${revision}} in parent version - * builds successfully when the property is overridden via command line. - */ - @Test - void testCiFriendlyParentVersionFromCli() throws Exception { - File testDir = extractResources("/gh-12184-ci-friendly-parent-version"); - - Verifier verifier = newVerifier(testDir.getAbsolutePath()); - verifier.addCliArgument("-Drevision=2.0.0"); - verifier.addCliArgument("validate"); - verifier.execute(); - verifier.verifyErrorFreeLog(); - } -} diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng11796DefaultPhasesStandardLifecycleTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng11796DefaultPhasesStandardLifecycleTest.java deleted file mode 100644 index f1b96b05cc12..000000000000 --- a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng11796DefaultPhasesStandardLifecycleTest.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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.it; - -import java.io.File; - -import org.junit.jupiter.api.Test; - -/** - * This is a test for - * MNG-11796. - *

- * Verifies that {@code } in a custom lifecycle's {@code components.xml} - * correctly binds plugin goals to standard lifecycle phases (e.g., {@code process-sources}). - */ -class MavenITmng11796DefaultPhasesStandardLifecycleTest extends AbstractMavenIntegrationTestCase { - - /** - * Verify that a plugin extension with {@code } mapping a goal - * to the standard {@code process-sources} phase causes that goal to execute - * during {@code mvn compile}. - */ - @Test - void testDefaultPhasesBindToStandardLifecyclePhases() throws Exception { - File testDir = extractResources("/mng-11796-default-phases-standard-lifecycle"); - - // Install the extension plugin - Verifier verifier = newVerifier(new File(testDir, "extension-plugin").getAbsolutePath()); - verifier.addCliArgument("install"); - verifier.execute(); - verifier.verifyErrorFreeLog(); - - // Run compile on the consumer project - the touch goal should execute at process-sources - verifier = newVerifier(new File(testDir, "consumer-project").getAbsolutePath()); - verifier.addCliArgument("compile"); - verifier.execute(); - verifier.verifyErrorFreeLog(); - verifier.verifyTextInLog("MNG-11796 touch goal executed"); - verifier.verifyFilePresent("target/touch.txt"); - } -} diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng6772NestedImportScopeRepositoryOverride.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng6772NestedImportScopeRepositoryOverride.java index 7174aa0c48cb..8da340e1ca7a 100644 --- a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng6772NestedImportScopeRepositoryOverride.java +++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng6772NestedImportScopeRepositoryOverride.java @@ -20,6 +20,7 @@ import java.io.File; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; /** @@ -32,6 +33,7 @@ * The test confirms that the dominant repository definition (child) wins while resolving the import POMs. * */ +@Disabled // This IT has been disabled until it is decided how the solution shall look like public class MavenITmng6772NestedImportScopeRepositoryOverride extends AbstractMavenIntegrationTestCase { // This will test the behavior using ProjectModelResolver diff --git a/its/core-it-suite/src/test/resources/gh-11767-consumer-pom-bom-from-settings-repo/pom.xml b/its/core-it-suite/src/test/resources/gh-11767-consumer-pom-bom-from-settings-repo/pom.xml deleted file mode 100644 index 4f92898f82c3..000000000000 --- a/its/core-it-suite/src/test/resources/gh-11767-consumer-pom-bom-from-settings-repo/pom.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - - 4.0.0 - - org.apache.maven.its.cpbom - consumer-pom-bom-settings-repo - 1.0 - jar - - Maven Integration Test :: Consumer POM BOM from Settings Repo - Verifies that the consumer POM builder can resolve BOM imports from - repositories defined only in settings.xml profiles (not in the project POM). - - - - - org.apache.maven.its.cpbom - the-bom - 1.0 - pom - import - - - - - - - org.apache.maven.its.cpbom - lib-a - - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 3.5.4 - - - - - diff --git a/its/core-it-suite/src/test/resources/gh-11767-consumer-pom-bom-from-settings-repo/repo/org/apache/maven/its/cpbom/lib-a/2.0/lib-a-2.0.jar b/its/core-it-suite/src/test/resources/gh-11767-consumer-pom-bom-from-settings-repo/repo/org/apache/maven/its/cpbom/lib-a/2.0/lib-a-2.0.jar deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/its/core-it-suite/src/test/resources/gh-11767-consumer-pom-bom-from-settings-repo/repo/org/apache/maven/its/cpbom/lib-a/2.0/lib-a-2.0.pom b/its/core-it-suite/src/test/resources/gh-11767-consumer-pom-bom-from-settings-repo/repo/org/apache/maven/its/cpbom/lib-a/2.0/lib-a-2.0.pom deleted file mode 100644 index 3151f79f1191..000000000000 --- a/its/core-it-suite/src/test/resources/gh-11767-consumer-pom-bom-from-settings-repo/repo/org/apache/maven/its/cpbom/lib-a/2.0/lib-a-2.0.pom +++ /dev/null @@ -1,9 +0,0 @@ - - - 4.0.0 - - org.apache.maven.its.cpbom - lib-a - 2.0 - jar - diff --git a/its/core-it-suite/src/test/resources/gh-11767-consumer-pom-bom-from-settings-repo/repo/org/apache/maven/its/cpbom/the-bom/1.0/the-bom-1.0.pom b/its/core-it-suite/src/test/resources/gh-11767-consumer-pom-bom-from-settings-repo/repo/org/apache/maven/its/cpbom/the-bom/1.0/the-bom-1.0.pom deleted file mode 100644 index e1ed38ccc002..000000000000 --- a/its/core-it-suite/src/test/resources/gh-11767-consumer-pom-bom-from-settings-repo/repo/org/apache/maven/its/cpbom/the-bom/1.0/the-bom-1.0.pom +++ /dev/null @@ -1,19 +0,0 @@ - - - 4.0.0 - - org.apache.maven.its.cpbom - the-bom - 1.0 - pom - - - - - org.apache.maven.its.cpbom - lib-a - 2.0 - - - - diff --git a/its/core-it-suite/src/test/resources/gh-11767-consumer-pom-bom-from-settings-repo/settings-template.xml b/its/core-it-suite/src/test/resources/gh-11767-consumer-pom-bom-from-settings-repo/settings-template.xml deleted file mode 100644 index e8a6f6d85a74..000000000000 --- a/its/core-it-suite/src/test/resources/gh-11767-consumer-pom-bom-from-settings-repo/settings-template.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - custom-repo - - - custom-repo - @baseurl@/repo - - ignore - - - false - - - - - - - custom-repo - - diff --git a/its/core-it-suite/src/test/resources/gh-11772-consumer-pom-410/child/pom.xml b/its/core-it-suite/src/test/resources/gh-11772-consumer-pom-410/child/pom.xml deleted file mode 100644 index 55601e85b640..000000000000 --- a/its/core-it-suite/src/test/resources/gh-11772-consumer-pom-410/child/pom.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - org.apache.maven.its.gh11772 - parent - 1.0.0-SNAPSHOT - - - child - pom - - - - org.slf4j - slf4j-api - - - - diff --git a/its/core-it-suite/src/test/resources/gh-11772-consumer-pom-410/pom.xml b/its/core-it-suite/src/test/resources/gh-11772-consumer-pom-410/pom.xml deleted file mode 100644 index e6061f924212..000000000000 --- a/its/core-it-suite/src/test/resources/gh-11772-consumer-pom-410/pom.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - org.apache.maven.its.gh11772 - parent - 1.0.0-SNAPSHOT - pom - - - child - - - - - - org.slf4j - slf4j-api - 2.0.9 - - - - - diff --git a/its/core-it-suite/src/test/resources/gh-12184-ci-friendly-parent-version/child/pom.xml b/its/core-it-suite/src/test/resources/gh-12184-ci-friendly-parent-version/child/pom.xml deleted file mode 100644 index c826300db9e6..000000000000 --- a/its/core-it-suite/src/test/resources/gh-12184-ci-friendly-parent-version/child/pom.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - 4.0.0 - - - org.apache.maven.its.gh12184 - ci-friendly-parent - ${revision} - - - ci-friendly-child - diff --git a/its/core-it-suite/src/test/resources/gh-12184-ci-friendly-parent-version/pom.xml b/its/core-it-suite/src/test/resources/gh-12184-ci-friendly-parent-version/pom.xml deleted file mode 100644 index 07ff1a29d9dc..000000000000 --- a/its/core-it-suite/src/test/resources/gh-12184-ci-friendly-parent-version/pom.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - 4.0.0 - - org.apache.maven.its.gh12184 - ci-friendly-parent - ${revision} - pom - - - child - - - - 1.0.0-SNAPSHOT - - diff --git a/its/core-it-suite/src/test/resources/mng-11796-default-phases-standard-lifecycle/consumer-project/pom.xml b/its/core-it-suite/src/test/resources/mng-11796-default-phases-standard-lifecycle/consumer-project/pom.xml deleted file mode 100644 index 75b1e9ef8dd6..000000000000 --- a/its/core-it-suite/src/test/resources/mng-11796-default-phases-standard-lifecycle/consumer-project/pom.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - 4.0.0 - - org.apache.maven.its.mng11796 - consumer-project - 1.0-SNAPSHOT - jar - - MNG-11796 Consumer Project - - - - - org.apache.maven.its.mng11796 - extension-plugin - 1.0-SNAPSHOT - true - - - - diff --git a/its/core-it-suite/src/test/resources/mng-11796-default-phases-standard-lifecycle/extension-plugin/pom.xml b/its/core-it-suite/src/test/resources/mng-11796-default-phases-standard-lifecycle/extension-plugin/pom.xml deleted file mode 100644 index 68be77394e15..000000000000 --- a/its/core-it-suite/src/test/resources/mng-11796-default-phases-standard-lifecycle/extension-plugin/pom.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - 4.0.0 - - org.apache.maven.its.mng11796 - extension-plugin - 1.0-SNAPSHOT - maven-plugin - MNG-11796 Extension Plugin - - - - org.apache.maven - maven-plugin-api - 3.2.5 - provided - - - - - - - org.apache.maven.plugins - maven-plugin-plugin - 3.6.4 - - - - descriptor - - - mng11796 - - - - - - - diff --git a/its/core-it-suite/src/test/resources/mng-11796-default-phases-standard-lifecycle/extension-plugin/src/main/java/org/apache/maven/its/mng11796/TouchMojo.java b/its/core-it-suite/src/test/resources/mng-11796-default-phases-standard-lifecycle/extension-plugin/src/main/java/org/apache/maven/its/mng11796/TouchMojo.java deleted file mode 100644 index abed805eaf0e..000000000000 --- a/its/core-it-suite/src/test/resources/mng-11796-default-phases-standard-lifecycle/extension-plugin/src/main/java/org/apache/maven/its/mng11796/TouchMojo.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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.its.mng11796; - -import java.io.File; -import java.io.IOException; - -import org.apache.maven.plugin.AbstractMojo; -import org.apache.maven.plugin.MojoExecutionException; - -/** - * Creates a marker file to prove the goal was executed. - * @goal touch - */ -public class TouchMojo extends AbstractMojo { - /** - * @parameter default-value="${project.build.directory}" - */ - private File outputDirectory; - - public void execute() throws MojoExecutionException { - getLog().info("MNG-11796 touch goal executed"); - File touchFile = new File(outputDirectory, "touch.txt"); - touchFile.getParentFile().mkdirs(); - try { - touchFile.createNewFile(); - } catch (IOException e) { - throw new MojoExecutionException("Failed to create touch file", e); - } - } -} diff --git a/its/core-it-suite/src/test/resources/mng-11796-default-phases-standard-lifecycle/extension-plugin/src/main/resources/META-INF/plexus/components.xml b/its/core-it-suite/src/test/resources/mng-11796-default-phases-standard-lifecycle/extension-plugin/src/main/resources/META-INF/plexus/components.xml deleted file mode 100644 index dffe42cf06f5..000000000000 --- a/its/core-it-suite/src/test/resources/mng-11796-default-phases-standard-lifecycle/extension-plugin/src/main/resources/META-INF/plexus/components.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - org.apache.maven.lifecycle.Lifecycle - mng11796 - org.apache.maven.lifecycle.Lifecycle - - mng11796 - - mng11796-dummy-phase - - - - org.apache.maven.its.mng11796:extension-plugin:touch - - - - - - diff --git a/its/core-it-suite/src/test/resources/mng-5224/maven-it-plugin-settings/pom.xml b/its/core-it-suite/src/test/resources/mng-5224/maven-it-plugin-settings/pom.xml index 884bc005f95c..c288496be2b3 100644 --- a/its/core-it-suite/src/test/resources/mng-5224/maven-it-plugin-settings/pom.xml +++ b/its/core-it-suite/src/test/resources/mng-5224/maven-it-plugin-settings/pom.xml @@ -56,7 +56,7 @@ under the License. org.codehaus.plexus plexus-utils - 4.0.3 + 3.5.1 diff --git a/pom.xml b/pom.xml index 168cbfd7c7db..eaa1674dbc81 100644 --- a/pom.xml +++ b/pom.xml @@ -143,7 +143,7 @@ under the License. 2025-06-24T20:18:00Z 9.10.1 - 1.18.10 + 1.18.8 2.12.0 1.11.0 1.5.2 @@ -170,7 +170,7 @@ under the License. 4.3.0 3.5.3 7.2.0 - 2.12.0 + 2.11.0 @@ -786,7 +786,7 @@ under the License. net.sourceforge.pmd pmd-core - 7.25.0 + 7.24.0