From 5c1032337ea1b880028021ebc94f741e116ce32b Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Tue, 29 Apr 2025 14:45:36 +0200 Subject: [PATCH 1/2] Create a new modelVersion 4.2.0 --- .../maven/api/services/ModelBuilder.java | 4 +- .../maven/api/services/ModelProblem.java | 7 +- api/maven-api-model/pom.xml | 2 +- api/maven-api-model/src/main/mdo/maven.mdo | 4 +- .../org/apache/maven/model/pom-4.1.0.xml | 6 +- .../org/apache/maven/model/pom-4.2.0.xml | 68 +++++++++++++++++++ .../mvnup/goals/ModelUpgradeStrategy.java | 6 +- .../mvnup/goals/ModelVersionUtils.java | 35 ++++++++-- .../invoker/mvnup/goals/UpgradeConstants.java | 10 +++ .../mvnup/goals/ModelVersionUtilsTest.java | 12 ++-- .../api/services/model/ModelValidator.java | 10 ++- .../org/apache/maven/model/pom-4.1.0.xml | 6 +- .../org/apache/maven/model/pom-4.2.0.xml | 68 +++++++++++++++++++ impl/maven-support/pom.xml | 2 +- 14 files changed, 215 insertions(+), 25 deletions(-) create mode 100644 compat/maven-model-builder/src/main/resources/org/apache/maven/model/pom-4.2.0.xml create mode 100644 impl/maven-impl/src/main/resources/org/apache/maven/model/pom-4.2.0.xml diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilder.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilder.java index f13a60def826..ea8e263392ed 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilder.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilder.java @@ -29,7 +29,9 @@ public interface ModelBuilder extends Service { String MODEL_VERSION_4_1_0 = "4.1.0"; - List KNOWN_MODEL_VERSIONS = List.of(MODEL_VERSION_4_0_0, MODEL_VERSION_4_1_0); + String MODEL_VERSION_4_2_0 = "4.2.0"; + + List KNOWN_MODEL_VERSIONS = List.of(MODEL_VERSION_4_0_0, MODEL_VERSION_4_1_0, MODEL_VERSION_4_2_0); ModelBuilderSession newSession(); diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelProblem.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelProblem.java index 07ba7bbe92b9..63bad258bf1d 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelProblem.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelProblem.java @@ -66,7 +66,12 @@ enum Version { /** * Validation for Maven 4.1 POM format. */ - V41 + V41, + + /** + * Validation for Maven 4.2 POM format. + */ + V42 } /** diff --git a/api/maven-api-model/pom.xml b/api/maven-api-model/pom.xml index 85848fa45b3f..25a7648278db 100644 --- a/api/maven-api-model/pom.xml +++ b/api/maven-api-model/pom.xml @@ -48,7 +48,7 @@ under the License. org.codehaus.modello modello-maven-plugin - 4.1.0 + 4.2.0 ${project.basedir}/../../src/mdo src/main/mdo/maven.mdo diff --git a/api/maven-api-model/src/main/mdo/maven.mdo b/api/maven-api-model/src/main/mdo/maven.mdo index 48df570aec9f..6d8914e82dbb 100644 --- a/api/maven-api-model/src/main/mdo/maven.mdo +++ b/api/maven-api-model/src/main/mdo/maven.mdo @@ -526,7 +526,7 @@ modules - 4.0.0/4.1.0 + 4.0.0/4.2.0 @deprecated Use {@link #subprojects} instead. @@ -540,7 +540,7 @@ subprojects - 4.1.0 + 4.1.0+ The subprojects (formerly called modules) to build as a part of this project. Each subproject listed is a relative path to the directory containing the subproject. To be consistent with the way default URLs are calculated from parent, it is recommended diff --git a/compat/maven-model-builder/src/main/resources/org/apache/maven/model/pom-4.1.0.xml b/compat/maven-model-builder/src/main/resources/org/apache/maven/model/pom-4.1.0.xml index ff7738614f9e..754912f099ca 100644 --- a/compat/maven-model-builder/src/main/resources/org/apache/maven/model/pom-4.1.0.xml +++ b/compat/maven-model-builder/src/main/resources/org/apache/maven/model/pom-4.1.0.xml @@ -20,8 +20,10 @@ under the License. --> - - 4.0.0 + + 4.1.0 UTF-8 diff --git a/compat/maven-model-builder/src/main/resources/org/apache/maven/model/pom-4.2.0.xml b/compat/maven-model-builder/src/main/resources/org/apache/maven/model/pom-4.2.0.xml new file mode 100644 index 000000000000..9b1b358f80d5 --- /dev/null +++ b/compat/maven-model-builder/src/main/resources/org/apache/maven/model/pom-4.2.0.xml @@ -0,0 +1,68 @@ + + + + + + + 4.2.0 + + + UTF-8 + UTF-8 + + 1980-02-01T00:00:00Z + + + + ${project.basedir}/target + ${project.build.directory}/classes + ${project.artifactId}-${project.version} + ${project.build.directory}/test-classes + ${project.basedir}/src/main/java + ${project.basedir}/src/main/scripts + ${project.basedir}/src/test/java + + + ${project.basedir}/src/main/resources + + + ${project.basedir}/src/main/resources-filtered + true + + + + + ${project.basedir}/src/test/resources + + + ${project.basedir}/src/test/resources-filtered + true + + + + + + ${project.build.directory}/site + + + + diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/ModelUpgradeStrategy.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/ModelUpgradeStrategy.java index aeff300581bf..3a1392c43e6b 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/ModelUpgradeStrategy.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/ModelUpgradeStrategy.java @@ -37,8 +37,10 @@ import static org.apache.maven.cling.invoker.mvnup.goals.ModelVersionUtils.getSchemaLocationForModelVersion; import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.ModelVersions.MODEL_VERSION_4_0_0; import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.ModelVersions.MODEL_VERSION_4_1_0; +import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.ModelVersions.MODEL_VERSION_4_2_0; import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.Namespaces.MAVEN_4_0_0_NAMESPACE; import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.Namespaces.MAVEN_4_1_0_NAMESPACE; +import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.Namespaces.MAVEN_4_2_0_NAMESPACE; import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlAttributes.SCHEMA_LOCATION; import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlAttributes.XSI_NAMESPACE_PREFIX; import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlAttributes.XSI_NAMESPACE_URI; @@ -246,7 +248,9 @@ private String determineTargetModelVersion(UpgradeContext context) { * Gets the namespace URI for a model version. */ private String getNamespaceForModelVersion(String modelVersion) { - if (MODEL_VERSION_4_1_0.equals(modelVersion)) { + if (MODEL_VERSION_4_2_0.equals(modelVersion)) { + return MAVEN_4_2_0_NAMESPACE; + } else if (MODEL_VERSION_4_1_0.equals(modelVersion)) { return MAVEN_4_1_0_NAMESPACE; } else { return MAVEN_4_0_0_NAMESPACE; diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/ModelVersionUtils.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/ModelVersionUtils.java index 8d3740778909..1a916bd23ab5 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/ModelVersionUtils.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/ModelVersionUtils.java @@ -24,9 +24,12 @@ import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.ModelVersions.MODEL_VERSION_4_0_0; import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.ModelVersions.MODEL_VERSION_4_1_0; +import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.ModelVersions.MODEL_VERSION_4_2_0; import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.Namespaces.MAVEN_4_0_0_NAMESPACE; import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.Namespaces.MAVEN_4_1_0_NAMESPACE; +import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.Namespaces.MAVEN_4_2_0_NAMESPACE; import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.SchemaLocations.MAVEN_4_1_0_SCHEMA_LOCATION; +import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.SchemaLocations.MAVEN_4_2_0_SCHEMA_LOCATION; import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlElements.MODEL_VERSION; /** @@ -60,7 +63,9 @@ public static String detectModelVersion(Document pomDocument) { // Fallback to namespace URI detection String namespaceUri = namespace.getURI(); - if (MAVEN_4_1_0_NAMESPACE.equals(namespaceUri)) { + if (MAVEN_4_2_0_NAMESPACE.equals(namespaceUri)) { + return MODEL_VERSION_4_2_0; + } else if (MAVEN_4_1_0_NAMESPACE.equals(namespaceUri)) { return MODEL_VERSION_4_1_0; } else if (MAVEN_4_0_0_NAMESPACE.equals(namespaceUri)) { return MODEL_VERSION_4_0_0; @@ -72,13 +77,15 @@ public static String detectModelVersion(Document pomDocument) { /** * Checks if a model version is valid for upgrade operations. - * Currently only supports 4.0.0 and 4.1.0. + * Currently supports 4.0.0, 4.1.0, and 4.2.0. * * @param modelVersion the model version to validate * @return true if the model version is valid */ public static boolean isValidModelVersion(String modelVersion) { - return MODEL_VERSION_4_0_0.equals(modelVersion) || MODEL_VERSION_4_1_0.equals(modelVersion); + return MODEL_VERSION_4_0_0.equals(modelVersion) + || MODEL_VERSION_4_1_0.equals(modelVersion) + || MODEL_VERSION_4_2_0.equals(modelVersion); } /** @@ -93,8 +100,15 @@ public static boolean canUpgrade(String fromVersion, String toVersion) { return false; } - // Currently only support 4.0.0 → 4.1.0 upgrade - return MODEL_VERSION_4_0_0.equals(fromVersion) && MODEL_VERSION_4_1_0.equals(toVersion); + // Support upgrades: 4.0.0 → 4.1.0, 4.0.0 → 4.2.0, 4.1.0 → 4.2.0 + if (MODEL_VERSION_4_0_0.equals(fromVersion)) { + return MODEL_VERSION_4_1_0.equals(toVersion) || MODEL_VERSION_4_2_0.equals(toVersion); + } + if (MODEL_VERSION_4_1_0.equals(fromVersion)) { + return MODEL_VERSION_4_2_0.equals(toVersion); + } + + return false; } /** @@ -105,7 +119,9 @@ public static boolean canUpgrade(String fromVersion, String toVersion) { * @return true if eligible for inference */ public static boolean isEligibleForInference(String modelVersion) { - return MODEL_VERSION_4_0_0.equals(modelVersion) || MODEL_VERSION_4_1_0.equals(modelVersion); + return MODEL_VERSION_4_0_0.equals(modelVersion) + || MODEL_VERSION_4_1_0.equals(modelVersion) + || MODEL_VERSION_4_2_0.equals(modelVersion); } /** @@ -220,8 +236,13 @@ public static boolean removeModelVersion(Document pomDocument) { * @return the schema location */ public static String getSchemaLocationForModelVersion(String modelVersion) { - if (MODEL_VERSION_4_1_0.equals(modelVersion) || isNewerThan410(modelVersion)) { + if (MODEL_VERSION_4_2_0.equals(modelVersion)) { + return MAVEN_4_2_0_SCHEMA_LOCATION; + } else if (MODEL_VERSION_4_1_0.equals(modelVersion)) { return MAVEN_4_1_0_SCHEMA_LOCATION; + } else if (isNewerThan410(modelVersion)) { + // For versions newer than 4.1.0 but not specifically 4.2.0, use 4.2.0 schema + return MAVEN_4_2_0_SCHEMA_LOCATION; } return UpgradeConstants.SchemaLocations.MAVEN_4_0_0_SCHEMA_LOCATION; } diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/UpgradeConstants.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/UpgradeConstants.java index 8d49fcc76b7a..253728c508dc 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/UpgradeConstants.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/UpgradeConstants.java @@ -38,6 +38,9 @@ public static final class ModelVersions { /** Maven 4.1.0 model version */ public static final String MODEL_VERSION_4_1_0 = "4.1.0"; + /** Maven 4.2.0 model version */ + public static final String MODEL_VERSION_4_2_0 = "4.2.0"; + private ModelVersions() { // Utility class } @@ -183,6 +186,9 @@ public static final class Namespaces { /** Maven 4.1.0 namespace URI */ public static final String MAVEN_4_1_0_NAMESPACE = "http://maven.apache.org/POM/4.1.0"; + /** Maven 4.2.0 namespace URI */ + public static final String MAVEN_4_2_0_NAMESPACE = "http://maven.apache.org/POM/4.2.0"; + private Namespaces() { // Utility class } @@ -200,6 +206,10 @@ public static final class SchemaLocations { public static final String MAVEN_4_1_0_SCHEMA_LOCATION = Namespaces.MAVEN_4_1_0_NAMESPACE + " https://maven.apache.org/xsd/maven-4.1.0.xsd"; + /** Schema location for 4.2.0 models */ + public static final String MAVEN_4_2_0_SCHEMA_LOCATION = + Namespaces.MAVEN_4_2_0_NAMESPACE + " https://maven.apache.org/xsd/maven-4.2.0.xsd"; + private SchemaLocations() { // Utility class } diff --git a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/ModelVersionUtilsTest.java b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/ModelVersionUtilsTest.java index 215e6b8e48c9..026cd5579b98 100644 --- a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/ModelVersionUtilsTest.java +++ b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/ModelVersionUtilsTest.java @@ -129,14 +129,14 @@ void shouldDetectVersionFromNamespaceWhenModelVersionMissing() throws Exception class ModelVersionValidationTests { @ParameterizedTest - @ValueSource(strings = {"4.0.0", "4.1.0"}) + @ValueSource(strings = {"4.0.0", "4.1.0", "4.2.0"}) @DisplayName("should validate supported model versions") void shouldValidateSupportedModelVersions(String version) { assertTrue(ModelVersionUtils.isValidModelVersion(version)); } @ParameterizedTest - @ValueSource(strings = {"3.0.0", "5.0.0", "4.2.0", "2.0.0", "6.0.0"}) + @ValueSource(strings = {"3.0.0", "5.0.0", "2.0.0", "6.0.0"}) @DisplayName("should reject unsupported model versions") void shouldRejectUnsupportedModelVersions(String version) { assertFalse(ModelVersionUtils.isValidModelVersion(version)); @@ -381,11 +381,11 @@ void shouldGetSchemaLocationFor400() { @DisplayName("should handle unknown model version in schema location") void shouldHandleUnknownModelVersionInSchemaLocation() { String schemaLocation = ModelVersionUtils.getSchemaLocationForModelVersion("5.0.0"); - assertNotNull(schemaLocation); // Should return 4.1.0 schema for newer versions - // The method returns the 4.1.0 schema location for versions newer than 4.1.0 + assertNotNull(schemaLocation); // Should return 4.2.0 schema for newer versions + // The method returns the 4.2.0 schema location for versions newer than 4.1.0 assertTrue( - schemaLocation.contains("4.1.0"), - "Expected schema location to contain '4.1.0', but was: " + schemaLocation); + schemaLocation.contains("4.2.0"), + "Expected schema location to contain '4.2.0', but was: " + schemaLocation); } } diff --git a/impl/maven-impl/src/main/java/org/apache/maven/api/services/model/ModelValidator.java b/impl/maven-impl/src/main/java/org/apache/maven/api/services/model/ModelValidator.java index 8d0e5cbdac7b..116b918959de 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/api/services/model/ModelValidator.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/api/services/model/ModelValidator.java @@ -49,10 +49,18 @@ public interface ModelValidator { * Denotes validation as performed by Maven 4.0. This validation level is meant for new projects. */ int VALIDATION_LEVEL_MAVEN_4_0 = 40; + /** + * Denotes validation as performed by Maven 4.1. This validation level is meant for new projects. + */ + int VALIDATION_LEVEL_MAVEN_4_1 = 41; + /** + * Denotes validation as performed by Maven 4.2. This validation level is meant for new projects. + */ + int VALIDATION_LEVEL_MAVEN_4_2 = 42; /** * Denotes strict validation as recommended by the current Maven version. */ - int VALIDATION_LEVEL_STRICT = VALIDATION_LEVEL_MAVEN_4_0; + int VALIDATION_LEVEL_STRICT = VALIDATION_LEVEL_MAVEN_4_2; /** * Checks the specified file model for missing or invalid values. This model is directly created from the POM diff --git a/impl/maven-impl/src/main/resources/org/apache/maven/model/pom-4.1.0.xml b/impl/maven-impl/src/main/resources/org/apache/maven/model/pom-4.1.0.xml index ff7738614f9e..754912f099ca 100644 --- a/impl/maven-impl/src/main/resources/org/apache/maven/model/pom-4.1.0.xml +++ b/impl/maven-impl/src/main/resources/org/apache/maven/model/pom-4.1.0.xml @@ -20,8 +20,10 @@ under the License. --> - - 4.0.0 + + 4.1.0 UTF-8 diff --git a/impl/maven-impl/src/main/resources/org/apache/maven/model/pom-4.2.0.xml b/impl/maven-impl/src/main/resources/org/apache/maven/model/pom-4.2.0.xml new file mode 100644 index 000000000000..9b1b358f80d5 --- /dev/null +++ b/impl/maven-impl/src/main/resources/org/apache/maven/model/pom-4.2.0.xml @@ -0,0 +1,68 @@ + + + + + + + 4.2.0 + + + UTF-8 + UTF-8 + + 1980-02-01T00:00:00Z + + + + ${project.basedir}/target + ${project.build.directory}/classes + ${project.artifactId}-${project.version} + ${project.build.directory}/test-classes + ${project.basedir}/src/main/java + ${project.basedir}/src/main/scripts + ${project.basedir}/src/test/java + + + ${project.basedir}/src/main/resources + + + ${project.basedir}/src/main/resources-filtered + true + + + + + ${project.basedir}/src/test/resources + + + ${project.basedir}/src/test/resources-filtered + true + + + + + + ${project.build.directory}/site + + + + diff --git a/impl/maven-support/pom.xml b/impl/maven-support/pom.xml index ae38304e0ca2..234d50eb9f54 100644 --- a/impl/maven-support/pom.xml +++ b/impl/maven-support/pom.xml @@ -185,7 +185,7 @@ generate-sources - 4.1.0 + 4.2.0 ${project.basedir}/../../api/maven-api-model ${project.basedir}/../../src/mdo From 64d4eb5fa9d7c9e5dac08e64a8ed5576467b4d25 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Thu, 12 Jun 2025 15:05:56 +0000 Subject: [PATCH 2/2] [MNG-5102] Add support for POM mixins This commit implements Maven Mixins, a powerful mechanism for sharing common POM configuration across multiple projects without the limitations of traditional inheritance. Key features: - Compose project configuration from multiple sources - Overcome single inheritance limitation - Reduce configuration duplication - Enable better separation of concerns Changes include: - New model version 4.2.0 support for mixins - Mixin and mixinManagement elements in POM model - Mixin resolution and composition logic - Integration with existing inheritance system - Comprehensive documentation and examples - Maven 3 compatibility fixes for maven.config The implementation allows projects to declare mixins that are resolved and merged in order, with later mixins overriding earlier ones, and the current POM having final precedence. --- .mvn/maven.config | 1 - api/maven-api-model/src/main/mdo/maven.mdo | 31 ++ .../model/building/FileToRawModelMerger.java | 6 + .../impl/DefaultConsumerPomBuilder.java | 1 + .../api/services/model/ModelResolver.java | 16 +- .../model/DefaultInheritanceAssembler.java | 10 + .../maven/impl/model/DefaultModelBuilder.java | 94 ++-- .../impl/model/DefaultModelValidator.java | 37 ++ .../impl/model/FileToRawModelMerger.java | 216 ------- .../impl/resolver/DefaultModelResolver.java | 17 +- .../maven/it/MavenITmng5102MixinsTest.java | 147 +++++ .../apache/maven/it/TestSuiteOrdering.java | 1 + .../classifier/mixin-4/mixin.xml | 24 + .../classifier/mixin-4/pom.xml | 53 ++ .../classifier/project/pom.xml | 63 +++ .../mng-5102-mixins/gav/mixin-2/pom.xml | 30 + .../mng-5102-mixins/gav/project/pom.xml | 61 ++ .../mng-5102-mixins/path/child/pom.xml | 38 ++ .../src/main/resources/simple-resource.txt | 0 .../mng-5102-mixins/path/mixins/mixin-1.xml | 29 + .../mng-5102-mixins/path/mixins/mixin-3.xml | 29 + .../resources/mng-5102-mixins/path/pom.xml | 63 +++ src/site/markdown/mixins.md | 527 ++++++++++++++++++ src/site/site.xml | 5 + 24 files changed, 1242 insertions(+), 257 deletions(-) delete mode 100644 impl/maven-impl/src/main/java/org/apache/maven/impl/model/FileToRawModelMerger.java create mode 100644 its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng5102MixinsTest.java create mode 100644 its/core-it-suite/src/test/resources/mng-5102-mixins/classifier/mixin-4/mixin.xml create mode 100644 its/core-it-suite/src/test/resources/mng-5102-mixins/classifier/mixin-4/pom.xml create mode 100644 its/core-it-suite/src/test/resources/mng-5102-mixins/classifier/project/pom.xml create mode 100644 its/core-it-suite/src/test/resources/mng-5102-mixins/gav/mixin-2/pom.xml create mode 100644 its/core-it-suite/src/test/resources/mng-5102-mixins/gav/project/pom.xml create mode 100644 its/core-it-suite/src/test/resources/mng-5102-mixins/path/child/pom.xml create mode 100644 its/core-it-suite/src/test/resources/mng-5102-mixins/path/child/src/main/resources/simple-resource.txt create mode 100644 its/core-it-suite/src/test/resources/mng-5102-mixins/path/mixins/mixin-1.xml create mode 100644 its/core-it-suite/src/test/resources/mng-5102-mixins/path/mixins/mixin-3.xml create mode 100644 its/core-it-suite/src/test/resources/mng-5102-mixins/path/pom.xml create mode 100644 src/site/markdown/mixins.md diff --git a/.mvn/maven.config b/.mvn/maven.config index f3b0cd90b1c8..c6f922ecc298 100644 --- a/.mvn/maven.config +++ b/.mvn/maven.config @@ -1,2 +1 @@ -# A hack to pass on this property for Maven 3 as well; Maven 4 supports this property out of the box -DsessionRootDirectory=${session.rootDirectory} \ No newline at end of file diff --git a/api/maven-api-model/src/main/mdo/maven.mdo b/api/maven-api-model/src/main/mdo/maven.mdo index 6d8914e82dbb..8863ef01641f 100644 --- a/api/maven-api-model/src/main/mdo/maven.mdo +++ b/api/maven-api-model/src/main/mdo/maven.mdo @@ -111,6 +111,20 @@ + + + + + + mixins + 4.2.0+ + Mixins... + + Mixin + * + + + @@ -1836,6 +1850,23 @@ + + Mixin + 4.1.0+ + Parent + + + classifier + 4.1.0+ + String + + + extension + 4.1.0+ + String + + + Scm 4.0.0+ diff --git a/compat/maven-model-builder/src/main/java/org/apache/maven/model/building/FileToRawModelMerger.java b/compat/maven-model-builder/src/main/java/org/apache/maven/model/building/FileToRawModelMerger.java index 9055ca8b910c..d623217abed6 100644 --- a/compat/maven-model-builder/src/main/java/org/apache/maven/model/building/FileToRawModelMerger.java +++ b/compat/maven-model-builder/src/main/java/org/apache/maven/model/building/FileToRawModelMerger.java @@ -138,6 +138,12 @@ protected void mergeModel_Profiles( .collect(Collectors.toList())); } + @Override + protected void mergeModel_Mixins( + Model.Builder builder, Model target, Model source, boolean sourceDominant, Map context) { + // don't merge + } + @Override protected void mergeModelBase_Dependencies( ModelBase.Builder builder, 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 4056746ae9ff..3a17f00ed701 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 @@ -209,6 +209,7 @@ static Model transformNonPom(Model model, MavenProject project) { .preserveModelVersion(false) .root(false) .parent(null) + .mixins(null) .build(null), model) .mailingLists(null) diff --git a/impl/maven-impl/src/main/java/org/apache/maven/api/services/model/ModelResolver.java b/impl/maven-impl/src/main/java/org/apache/maven/api/services/model/ModelResolver.java index 0a7cbb621c7c..668f450bc7d9 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/api/services/model/ModelResolver.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/api/services/model/ModelResolver.java @@ -85,8 +85,16 @@ record ModelResolverRequest( @Nonnull String groupId, @Nonnull String artifactId, @Nonnull String version, - @Nullable String classifier) + @Nullable String classifier, + @Nullable String extension) implements Request { + public ModelResolverRequest { + Objects.requireNonNull(session, "session cannot be null"); + Objects.requireNonNull(groupId, "groupId cannot be null"); + Objects.requireNonNull(artifactId, "artifactId cannot be null"); + Objects.requireNonNull(version, "version cannot be null"); + } + @Nonnull @Override public Session getSession() { @@ -106,12 +114,13 @@ public boolean equals(Object o) { && Objects.equals(groupId, that.groupId) && Objects.equals(artifactId, that.artifactId) && Objects.equals(version, that.version) - && Objects.equals(classifier, that.classifier); + && Objects.equals(classifier, that.classifier) + && Objects.equals(extension, that.extension); } @Override public int hashCode() { - return Objects.hash(repositories, groupId, artifactId, version, classifier); + return Objects.hash(repositories, groupId, artifactId, version, classifier, extension); } @Override @@ -123,6 +132,7 @@ public String toString() { + ", artifactId=" + artifactId + ", version=" + version + ", classifier=" + classifier + + ", extension=" + extension + ']'; } } diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultInheritanceAssembler.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultInheritanceAssembler.java index f9c4a9883ab2..aeacef7b530a 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultInheritanceAssembler.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultInheritanceAssembler.java @@ -194,6 +194,16 @@ private void concatPath(StringBuilder url, String path) { } } + @Override + protected void mergeModel_Mixins( + Model.Builder builder, + Model target, + Model source, + boolean sourceDominant, + Map context) { + // do not merge + } + @Override protected void mergeModelBase_Properties( ModelBase.Builder builder, 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 5a5735a7922e..c55c1ccadf33 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 @@ -66,6 +66,7 @@ import org.apache.maven.api.model.Exclusion; import org.apache.maven.api.model.InputLocation; import org.apache.maven.api.model.InputSource; +import org.apache.maven.api.model.Mixin; import org.apache.maven.api.model.Model; import org.apache.maven.api.model.Parent; import org.apache.maven.api.model.Profile; @@ -838,12 +839,11 @@ void buildEffectiveModel(Collection importIds) throws ModelBuilderExcept } } - Model readParent(Model childModel, DefaultProfileActivationContext profileActivationContext) { + Model readParent(Model childModel, Parent parent, DefaultProfileActivationContext profileActivationContext) { Model parentModel; - Parent parent = childModel.getParent(); if (parent != null) { - parentModel = resolveParent(childModel, profileActivationContext); + parentModel = resolveParent(childModel, parent, profileActivationContext); if (!"pom".equals(parentModel.getPackaging())) { add( @@ -868,23 +868,26 @@ Model readParent(Model childModel, DefaultProfileActivationContext profileActiva return parentModel; } - private Model resolveParent(Model childModel, DefaultProfileActivationContext profileActivationContext) + private Model resolveParent( + Model childModel, Parent parent, DefaultProfileActivationContext profileActivationContext) throws ModelBuilderException { Model parentModel = null; if (isBuildRequest()) { - parentModel = readParentLocally(childModel, profileActivationContext); + parentModel = readParentLocally(childModel, parent, profileActivationContext); } if (parentModel == null) { - parentModel = resolveAndReadParentExternally(childModel, profileActivationContext); + parentModel = resolveAndReadParentExternally(childModel, parent, profileActivationContext); } return parentModel; } - private Model readParentLocally(Model childModel, DefaultProfileActivationContext profileActivationContext) + private Model readParentLocally( + Model childModel, Parent parent, DefaultProfileActivationContext profileActivationContext) throws ModelBuilderException { ModelSource candidateSource; - Parent parent = childModel.getParent(); + boolean isParentOrSimpleMixin = !(parent instanceof Mixin) + || (((Mixin) parent).getClassifier() == null && ((Mixin) parent).getExtension() == null); String parentPath = parent.getRelativePath(); if (request.getRequestType() == ModelBuilderRequest.RequestType.BUILD_PROJECT) { if (parentPath != null && !parentPath.isEmpty()) { @@ -893,14 +896,16 @@ private Model readParentLocally(Model childModel, DefaultProfileActivationContex wrongParentRelativePath(childModel); return null; } - } else { + } else if (isParentOrSimpleMixin) { candidateSource = resolveReactorModel(parent.getGroupId(), parent.getArtifactId(), parent.getVersion()); if (candidateSource == null && parentPath == null) { candidateSource = request.getSource().resolve(modelProcessor::locateExistingPom, ".."); } + } else { + candidateSource = null; } - } else { + } else if (isParentOrSimpleMixin) { candidateSource = resolveReactorModel(parent.getGroupId(), parent.getArtifactId(), parent.getVersion()); if (candidateSource == null) { if (parentPath == null) { @@ -910,6 +915,8 @@ private Model readParentLocally(Model childModel, DefaultProfileActivationContex candidateSource = request.getSource().resolve(modelProcessor::locateExistingPom, parentPath); } } + } else { + candidateSource = null; } if (candidateSource == null) { @@ -925,11 +932,10 @@ private Model readParentLocally(Model childModel, DefaultProfileActivationContex String version = getVersion(candidateModel); // Ensure that relative path and GA match, if both are provided - if (groupId == null - || !groupId.equals(parent.getGroupId()) - || artifactId == null - || !artifactId.equals(parent.getArtifactId())) { - mismatchRelativePathAndGA(childModel, groupId, artifactId); + if (parent.getGroupId() != null && (groupId == null || !groupId.equals(parent.getGroupId())) + || parent.getArtifactId() != null + && (artifactId == null || !artifactId.equals(parent.getArtifactId()))) { + mismatchRelativePathAndGA(childModel, parent, groupId, artifactId); return null; } @@ -968,8 +974,7 @@ private Model readParentLocally(Model childModel, DefaultProfileActivationContex return candidateModel; } - private void mismatchRelativePathAndGA(Model childModel, String groupId, String artifactId) { - Parent parent = childModel.getParent(); + private void mismatchRelativePathAndGA(Model childModel, Parent parent, String groupId, String artifactId) { StringBuilder buffer = new StringBuilder(256); buffer.append("'parent.relativePath'"); if (childModel != getRootModel()) { @@ -1000,16 +1005,17 @@ private void wrongParentRelativePath(Model childModel) { add(Severity.FATAL, Version.BASE, buffer.toString(), parent.getLocation("")); } - Model resolveAndReadParentExternally(Model childModel, DefaultProfileActivationContext profileActivationContext) + Model resolveAndReadParentExternally( + Model childModel, Parent parent, DefaultProfileActivationContext profileActivationContext) throws ModelBuilderException { ModelBuilderRequest request = this.request; setSource(childModel); - Parent parent = childModel.getParent(); - String groupId = parent.getGroupId(); String artifactId = parent.getArtifactId(); String version = parent.getVersion(); + String classifier = parent instanceof Mixin ? ((Mixin) parent).getClassifier() : null; + String extension = parent instanceof Mixin ? ((Mixin) parent).getExtension() : null; // add repositories specified by the current model so that we can resolve the parent if (!childModel.getRepositories().isEmpty()) { @@ -1027,12 +1033,23 @@ Model resolveAndReadParentExternally(Model childModel, DefaultProfileActivationC ModelSource modelSource; try { - modelSource = resolveReactorModel(parent.getGroupId(), parent.getArtifactId(), parent.getVersion()); + modelSource = classifier == null && extension == null + ? resolveReactorModel(groupId, artifactId, version) + : null; if (modelSource == null) { - AtomicReference modified = new AtomicReference<>(); - modelSource = modelResolver.resolveModel(request.getSession(), repositories, parent, modified); - if (modified.get() != null) { - parent = modified.get(); + ModelResolver.ModelResolverRequest req = new ModelResolver.ModelResolverRequest( + request.getSession(), + null, + repositories, + groupId, + artifactId, + version, + classifier, + extension != null ? extension : "pom"); + ModelResolver.ModelResolverResult result = modelResolver.resolveModel(req); + modelSource = result.source(); + if (result.version() != null) { + parent = parent.withVersion(result.version()); } } } catch (ModelResolverException e) { @@ -1148,7 +1165,8 @@ private Model readEffectiveModel() throws ModelBuilderException { profileActivationContext.setUserProperties(profileProps); } - Model parentModel = readParent(activatedFileModel, profileActivationContext); + Model parentModel = + readParent(activatedFileModel, activatedFileModel.getParent(), profileActivationContext); // Now that we have read the parent, we can set the relative // path correctly if it was not set in the input model @@ -1170,6 +1188,15 @@ private Model readEffectiveModel() throws ModelBuilderException { Model model = inheritanceAssembler.assembleModelInheritance(inputModel, parentModel, request, this); + // Mixins + for (Mixin mixin : model.getMixins()) { + Model parent = resolveParent(model, mixin, profileActivationContext); + model = inheritanceAssembler.assembleModelInheritance(model, parent, request, this); + } + + // model normalization + model = modelNormalizer.mergeDuplicates(model, request, this); + // profile activation profileActivationContext.setModel(model); @@ -1354,7 +1381,7 @@ Model doReadFileModel() throws ModelBuilderException { .version(parentVersion) .build()); } else { - mismatchRelativePathAndGA(model, parentGroupId, parentArtifactId); + mismatchRelativePathAndGA(model, parent, parentGroupId, parentArtifactId); } } else { if (!MODEL_VERSION_4_0_0.equals(model.getModelVersion()) && path != null) { @@ -1575,8 +1602,9 @@ Model readAsParentModel(DefaultProfileActivationContext profileActivationContext private ParentModelWithProfiles doReadAsParentModel( DefaultProfileActivationContext childProfileActivationContext) throws ModelBuilderException { Model raw = readRawModel(); - Model parentData = readParent(raw, childProfileActivationContext); - Model parent = new DefaultInheritanceAssembler(new DefaultInheritanceAssembler.InheritanceModelMerger() { + Model parentData = readParent(raw, raw.getParent(), childProfileActivationContext); + DefaultInheritanceAssembler defaultInheritanceAssembler = + new DefaultInheritanceAssembler(new DefaultInheritanceAssembler.InheritanceModelMerger() { @Override protected void mergeModel_Modules( Model.Builder builder, @@ -1592,8 +1620,12 @@ protected void mergeModel_Subprojects( Model source, boolean sourceDominant, Map context) {} - }) - .assembleModelInheritance(raw, parentData, request, this); + }); + Model parent = defaultInheritanceAssembler.assembleModelInheritance(raw, parentData, request, this); + for (Mixin mixin : parent.getMixins()) { + Model parentModel = resolveParent(parent, mixin, childProfileActivationContext); + parent = defaultInheritanceAssembler.assembleModelInheritance(parent, parentModel, request, this); + } // Profile injection SHOULD be performed on parent models to ensure // that profile content becomes part of the parent model before inheritance. 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 c0cff0286a05..2463905cb83a 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 @@ -355,6 +355,43 @@ && equals(parent.getArtifactId(), model.getArtifactId())) { } } + // Validate mixins + if (!model.getMixins().isEmpty()) { + // Ensure model version is at least 4.2.0 when using mixins + if (compareModelVersions("4.2.0", model.getModelVersion()) < 0) { + addViolation( + problems, + Severity.ERROR, + Version.V40, + "mixins", + null, + "Mixins are only supported in modelVersion 4.2.0 or higher, but found '" + + model.getModelVersion() + "'.", + model); + } + + // Validate each mixin + for (Parent mixin : model.getMixins()) { + if (mixin.getRelativePath() != null + && !mixin.getRelativePath().isEmpty() + && (mixin.getGroupId() != null && !mixin.getGroupId().isEmpty() + || mixin.getArtifactId() != null + && !mixin.getArtifactId().isEmpty()) + && validationLevel >= ModelValidator.VALIDATION_LEVEL_MAVEN_4_0 + && ModelBuilder.KNOWN_MODEL_VERSIONS.contains(model.getModelVersion()) + && !Objects.equals(model.getModelVersion(), ModelBuilder.MODEL_VERSION_4_0_0)) { + addViolation( + problems, + Severity.WARNING, + Version.BASE, + "mixins.mixin.relativePath", + null, + "only specify relativePath or groupId/artifactId for mixin", + mixin); + } + } + } + if (validationLevel == ModelValidator.VALIDATION_LEVEL_MINIMAL) { // profiles: they are essential for proper model building (may contribute profiles, dependencies...) HashSet minProfileIds = new HashSet<>(); diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/FileToRawModelMerger.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/FileToRawModelMerger.java deleted file mode 100644 index b98d057b30ba..000000000000 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/FileToRawModelMerger.java +++ /dev/null @@ -1,216 +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.util.Iterator; -import java.util.Map; -import java.util.stream.Collectors; - -import org.apache.maven.api.model.Build; -import org.apache.maven.api.model.BuildBase; -import org.apache.maven.api.model.CiManagement; -import org.apache.maven.api.model.Dependency; -import org.apache.maven.api.model.DependencyManagement; -import org.apache.maven.api.model.Model; -import org.apache.maven.api.model.ModelBase; -import org.apache.maven.api.model.Plugin; -import org.apache.maven.api.model.PluginContainer; -import org.apache.maven.api.model.Profile; -import org.apache.maven.api.model.ReportPlugin; -import org.apache.maven.api.model.Reporting; -import org.apache.maven.model.v4.MavenMerger; - -/** - * As long as Maven controls the BuildPomXMLFilter, the entities that need merging are known. - * All others can simply be copied from source to target to restore the locationTracker - * - * @since 4.0.0 - */ -class FileToRawModelMerger extends MavenMerger { - - @Override - protected void mergeBuild_Extensions( - Build.Builder builder, Build target, Build source, boolean sourceDominant, Map context) { - // don't merge - } - - @Override - protected void mergeBuildBase_Resources( - BuildBase.Builder builder, - BuildBase target, - BuildBase source, - boolean sourceDominant, - Map context) { - // don't merge - } - - @Override - protected void mergeBuildBase_TestResources( - BuildBase.Builder builder, - BuildBase target, - BuildBase source, - boolean sourceDominant, - Map context) { - // don't merge - } - - @Override - protected void mergeCiManagement_Notifiers( - CiManagement.Builder builder, - CiManagement target, - CiManagement source, - boolean sourceDominant, - Map context) { - // don't merge - } - - @Override - protected void mergeDependencyManagement_Dependencies( - DependencyManagement.Builder builder, - DependencyManagement target, - DependencyManagement source, - boolean sourceDominant, - Map context) { - Iterator sourceIterator = source.getDependencies().iterator(); - builder.dependencies(target.getDependencies().stream() - .map(d -> mergeDependency(d, sourceIterator.next(), sourceDominant, context)) - .collect(Collectors.toList())); - } - - @Override - protected void mergeDependency_Exclusions( - Dependency.Builder builder, - Dependency target, - Dependency source, - boolean sourceDominant, - Map context) { - // don't merge - } - - @Override - protected void mergeModel_Contributors( - Model.Builder builder, Model target, Model source, boolean sourceDominant, Map context) { - // don't merge - } - - @Override - protected void mergeModel_Developers( - Model.Builder builder, Model target, Model source, boolean sourceDominant, Map context) { - // don't merge - } - - @Override - protected void mergeModel_Licenses( - Model.Builder builder, Model target, Model source, boolean sourceDominant, Map context) { - // don't merge - } - - @Override - protected void mergeModel_MailingLists( - Model.Builder builder, Model target, Model source, boolean sourceDominant, Map context) { - // don't merge - } - - @Override - protected void mergeModel_Profiles( - Model.Builder builder, Model target, Model source, boolean sourceDominant, Map context) { - Iterator sourceIterator = source.getProfiles().iterator(); - builder.profiles(target.getProfiles().stream() - .map(d -> mergeProfile(d, sourceIterator.next(), sourceDominant, context)) - .collect(Collectors.toList())); - } - - @Override - protected void mergeModelBase_Dependencies( - ModelBase.Builder builder, - ModelBase target, - ModelBase source, - boolean sourceDominant, - Map context) { - Iterator sourceIterator = source.getDependencies().iterator(); - builder.dependencies(target.getDependencies().stream() - .map(d -> mergeDependency(d, sourceIterator.next(), sourceDominant, context)) - .collect(Collectors.toList())); - } - - @Override - protected void mergeModelBase_PluginRepositories( - ModelBase.Builder builder, - ModelBase target, - ModelBase source, - boolean sourceDominant, - Map context) { - builder.pluginRepositories(source.getPluginRepositories()); - } - - @Override - protected void mergeModelBase_Repositories( - ModelBase.Builder builder, - ModelBase target, - ModelBase source, - boolean sourceDominant, - Map context) { - // don't merge - } - - @Override - protected void mergePlugin_Dependencies( - Plugin.Builder builder, Plugin target, Plugin source, boolean sourceDominant, Map context) { - Iterator sourceIterator = source.getDependencies().iterator(); - builder.dependencies(target.getDependencies().stream() - .map(d -> mergeDependency(d, sourceIterator.next(), sourceDominant, context)) - .collect(Collectors.toList())); - } - - @Override - protected void mergePlugin_Executions( - Plugin.Builder builder, Plugin target, Plugin source, boolean sourceDominant, Map context) { - // don't merge - } - - @Override - protected void mergeReporting_Plugins( - Reporting.Builder builder, - Reporting target, - Reporting source, - boolean sourceDominant, - Map context) { - // don't merge - } - - @Override - protected void mergeReportPlugin_ReportSets( - ReportPlugin.Builder builder, - ReportPlugin target, - ReportPlugin source, - boolean sourceDominant, - Map context) { - // don't merge - } - - @Override - protected void mergePluginContainer_Plugins( - PluginContainer.Builder builder, - PluginContainer target, - PluginContainer source, - boolean sourceDominant, - Map context) { - // don't merge - } -} diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/DefaultModelResolver.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/DefaultModelResolver.java index 03973c981eac..ecc01543d77a 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/DefaultModelResolver.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/DefaultModelResolver.java @@ -68,7 +68,8 @@ public ModelSource resolveModel( parent.getGroupId(), parent.getArtifactId(), parent.getVersion(), - null), + null, + "pom"), parent.getLocation("version"), "parent"); if (result.version() != null) { @@ -93,7 +94,8 @@ public ModelSource resolveModel( dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion(), - dependency.getClassifier()), + dependency.getClassifier(), + "pom"), dependency.getLocation("version"), "dependency"); if (result.version() != null) { @@ -122,12 +124,13 @@ public ModelResolverResult doResolveModel( String artifactId = request.artifactId(); String version = request.version(); String classifier = request.classifier(); + String extension = request.extension(); List repositories = request.repositories(); RequestTraceHelper.ResolverTrace trace = RequestTraceHelper.enter(session, request); try { ArtifactCoordinates coords = - session.createArtifactCoordinates(groupId, artifactId, version, classifier, "pom", null); + session.createArtifactCoordinates(groupId, artifactId, version, classifier, extension, null); if (coords.getVersionConstraint().getVersionRange() != null && coords.getVersionConstraint().getVersionRange().getUpperBoundary() == null) { // Message below is checked for in the MNG-2199 core IT. @@ -149,7 +152,7 @@ public ModelResolverResult doResolveModel( version)) .toString(); String resultVersion = version.equals(newVersion) ? null : newVersion; - Path path = getPath(session, repositories, groupId, artifactId, newVersion, classifier); + Path path = getPath(session, repositories, groupId, artifactId, newVersion, classifier, extension); return new ModelResolverResult( request, Sources.resolvedSource(path, groupId + ":" + artifactId + ":" + newVersion), @@ -175,9 +178,11 @@ protected Path getPath( String groupId, String artifactId, String version, - String classifier) { + String classifier, + String extension) { DownloadedArtifact resolved = session.resolveArtifact( - session.createArtifactCoordinates(groupId, artifactId, version, classifier, "pom", null), repositories); + session.createArtifactCoordinates(groupId, artifactId, version, classifier, extension, null), + repositories); return resolved.getPath(); } } diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng5102MixinsTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng5102MixinsTest.java new file mode 100644 index 000000000000..3e65254837d7 --- /dev/null +++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng5102MixinsTest.java @@ -0,0 +1,147 @@ +/* + * 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.util.List; +import java.util.Properties; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * This is a test set for MNG-5135. + * + * @author Benjamin Bentmann + */ +public class MavenITmng5102MixinsTest extends AbstractMavenIntegrationTestCase { + + public MavenITmng5102MixinsTest() { + super("(4.0.0-alpha-7,)"); + } + + /** + * Verify that mixins can be loaded from the file system. + * + * @throws Exception in case of failure + */ + @Test + public void testWithPath() throws Exception { + File testDir = extractResources("/mng-5102-mixins/path"); + + Verifier verifier = newVerifier(testDir.getAbsolutePath()); + verifier.setAutoclean(false); + verifier.deleteDirectory("target"); + verifier.deleteArtifacts("org.apache.maven.its.mng5102"); + verifier.addCliArgument("install"); + verifier.execute(); + verifier.verifyErrorFreeLog(); + + verifier.verifyFilePresent("target/model.properties"); + Properties props = verifier.loadProperties("target/model.properties"); + assertEquals("true", props.getProperty("project.properties.mixin1")); + assertNull(props.getProperty("project.properties.mixin3")); + + verifier.verifyFilePresent("child/target/model.properties"); + props = verifier.loadProperties("child/target/model.properties"); + assertEquals("true", props.getProperty("project.properties.mixin1")); + assertEquals("true", props.getProperty("project.properties.mixin3")); + + verifier.verifyFilePresent( + "target/project-local-repo/org.apache.maven.its.mng5102/child/0.1/child-0.1-consumer.pom"); + List lines = verifier.loadLines( + "target/project-local-repo/org.apache.maven.its.mng5102/child/0.1/child-0.1-consumer.pom"); + assertTrue(lines.stream().noneMatch(l -> l.contains(""))); + } + + /** + * Verify that mixins can be loaded from the repositories. + * + * @throws Exception in case of failure + */ + @Test + public void testWithGav() throws Exception { + File testDir = extractResources("/mng-5102-mixins/gav"); + + Verifier verifier = newVerifier(new File(testDir, "mixin-2").getAbsolutePath()); + + verifier.setAutoclean(false); + verifier.deleteDirectory("target"); + verifier.deleteArtifacts("org.apache.maven.its.mng5102"); + verifier.addCliArgument("install"); + verifier.execute(); + verifier.verifyErrorFreeLog(); + + verifier = newVerifier(new File(testDir, "project").getAbsolutePath()); + verifier.setAutoclean(false); + verifier.deleteDirectory("target"); + verifier.addCliArgument("install"); + verifier.execute(); + verifier.verifyErrorFreeLog(); + + verifier.verifyFilePresent("target/model.properties"); + Properties props = verifier.loadProperties("target/model.properties"); + assertEquals("true", props.getProperty("project.properties.mixin2")); + + verifier.verifyFilePresent( + "target/project-local-repo/org.apache.maven.its.mng5102/gav/0.1/gav-0.1-consumer.pom"); + List lines = verifier.loadLines( + "target/project-local-repo/org.apache.maven.its.mng5102/gav/0.1/gav-0.1-consumer.pom"); + assertTrue(lines.stream().anyMatch(l -> l.contains(""))); + } + + /** + * Verify that mixins can be loaded from the repositories with a classifier. + * + * @throws Exception in case of failure + */ + @Test + public void testWithClassifier() throws Exception { + File testDir = extractResources("/mng-5102-mixins/classifier"); + + Verifier verifier = newVerifier(new File(testDir, "mixin-4").getAbsolutePath()); + + verifier.setAutoclean(false); + verifier.deleteDirectory("target"); + verifier.deleteArtifacts("org.apache.maven.its.mng5102"); + verifier.addCliArgument("install"); + verifier.execute(); + verifier.verifyErrorFreeLog(); + + verifier = newVerifier(new File(testDir, "project").getAbsolutePath()); + verifier.setAutoclean(false); + verifier.deleteDirectory("target"); + verifier.addCliArgument("install"); + verifier.execute(); + verifier.verifyErrorFreeLog(); + + verifier.verifyFilePresent("target/model.properties"); + Properties props = verifier.loadProperties("target/model.properties"); + assertEquals("true", props.getProperty("project.properties.mixin4")); + + verifier.verifyFilePresent( + "target/project-local-repo/org.apache.maven.its.mng5102/classifier/0.1/classifier-0.1-consumer.pom"); + List lines = verifier.loadLines( + "target/project-local-repo/org.apache.maven.its.mng5102/classifier/0.1/classifier-0.1-consumer.pom"); + assertTrue(lines.stream().anyMatch(l -> l.contains(""))); + } +} diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/TestSuiteOrdering.java b/its/core-it-suite/src/test/java/org/apache/maven/it/TestSuiteOrdering.java index d931454b0636..5fccd2755e22 100644 --- a/its/core-it-suite/src/test/java/org/apache/maven/it/TestSuiteOrdering.java +++ b/its/core-it-suite/src/test/java/org/apache/maven/it/TestSuiteOrdering.java @@ -108,6 +108,7 @@ public TestSuiteOrdering() { suite.addTestSuite(MavenITmng8736ConcurrentFileActivationTest.class); suite.addTestSuite(MavenITmng8744CIFriendlyTest.class); suite.addTestSuite(MavenITmng8572DITypeHandlerTest.class); + suite.addTestSuite(MavenITmng5102MixinsTest.class); suite.addTestSuite(MavenITmng3558PropertyEscapingTest.class); suite.addTestSuite(MavenITmng4559MultipleJvmArgsTest.class); suite.addTestSuite(MavenITmng4559SpacesInJvmOptsTest.class); diff --git a/its/core-it-suite/src/test/resources/mng-5102-mixins/classifier/mixin-4/mixin.xml b/its/core-it-suite/src/test/resources/mng-5102-mixins/classifier/mixin-4/mixin.xml new file mode 100644 index 000000000000..c02997c66d15 --- /dev/null +++ b/its/core-it-suite/src/test/resources/mng-5102-mixins/classifier/mixin-4/mixin.xml @@ -0,0 +1,24 @@ + + + + + true + + diff --git a/its/core-it-suite/src/test/resources/mng-5102-mixins/classifier/mixin-4/pom.xml b/its/core-it-suite/src/test/resources/mng-5102-mixins/classifier/mixin-4/pom.xml new file mode 100644 index 000000000000..25d6470a87a3 --- /dev/null +++ b/its/core-it-suite/src/test/resources/mng-5102-mixins/classifier/mixin-4/pom.xml @@ -0,0 +1,53 @@ + + + + 4.2.0 + org.apache.maven.its.mng5102 + mixin-4 + 0.1 + pom + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.2.0 + + + attach-mixin + + attach-artifact + + + + + mixin.xml + xml + mixin + + + + + + + + + diff --git a/its/core-it-suite/src/test/resources/mng-5102-mixins/classifier/project/pom.xml b/its/core-it-suite/src/test/resources/mng-5102-mixins/classifier/project/pom.xml new file mode 100644 index 000000000000..74e7c60b24a3 --- /dev/null +++ b/its/core-it-suite/src/test/resources/mng-5102-mixins/classifier/project/pom.xml @@ -0,0 +1,63 @@ + + + + 4.2.0 + org.apache.maven.its.mng5102 + classifier + 0.1 + pom + + Maven Integration Test :: MNG-5102 + Mixins tests. + + + + + org.apache.maven.its.plugins + maven-it-plugin-expression + 2.1-SNAPSHOT + + + + eval + + validate + + target/model.properties + + project/properties + + + + + + + + + + + org.apache.maven.its.mng5102 + mixin-4 + 0.1 + mixin + xml + + + diff --git a/its/core-it-suite/src/test/resources/mng-5102-mixins/gav/mixin-2/pom.xml b/its/core-it-suite/src/test/resources/mng-5102-mixins/gav/mixin-2/pom.xml new file mode 100644 index 000000000000..9a57fcfa592d --- /dev/null +++ b/its/core-it-suite/src/test/resources/mng-5102-mixins/gav/mixin-2/pom.xml @@ -0,0 +1,30 @@ + + + + 4.2.0 + org.apache.maven.its.mng5102 + mixin-2 + 0.1 + pom + + + true + + diff --git a/its/core-it-suite/src/test/resources/mng-5102-mixins/gav/project/pom.xml b/its/core-it-suite/src/test/resources/mng-5102-mixins/gav/project/pom.xml new file mode 100644 index 000000000000..745ddf7ae4f6 --- /dev/null +++ b/its/core-it-suite/src/test/resources/mng-5102-mixins/gav/project/pom.xml @@ -0,0 +1,61 @@ + + + + 4.2.0 + org.apache.maven.its.mng5102 + gav + 0.1 + pom + + Maven Integration Test :: MNG-5102 + Mixins tests. + + + + + org.apache.maven.its.plugins + maven-it-plugin-expression + 2.1-SNAPSHOT + + + + eval + + validate + + target/model.properties + + project/properties + + + + + + + + + + + org.apache.maven.its.mng5102 + mixin-2 + 0.1 + + + diff --git a/its/core-it-suite/src/test/resources/mng-5102-mixins/path/child/pom.xml b/its/core-it-suite/src/test/resources/mng-5102-mixins/path/child/pom.xml new file mode 100644 index 000000000000..2b435b0f150d --- /dev/null +++ b/its/core-it-suite/src/test/resources/mng-5102-mixins/path/child/pom.xml @@ -0,0 +1,38 @@ + + + + 4.2.0 + + org.apache.maven.its.mng5102 + path + + + child + jar + + Maven Integration Test :: MNG-5102 :: Child + Mixins tests. + + + + ../mixins/mixin-3.xml + + + diff --git a/its/core-it-suite/src/test/resources/mng-5102-mixins/path/child/src/main/resources/simple-resource.txt b/its/core-it-suite/src/test/resources/mng-5102-mixins/path/child/src/main/resources/simple-resource.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/its/core-it-suite/src/test/resources/mng-5102-mixins/path/mixins/mixin-1.xml b/its/core-it-suite/src/test/resources/mng-5102-mixins/path/mixins/mixin-1.xml new file mode 100644 index 000000000000..d912823b5551 --- /dev/null +++ b/its/core-it-suite/src/test/resources/mng-5102-mixins/path/mixins/mixin-1.xml @@ -0,0 +1,29 @@ + + + + org.apache.maven.its.mng5102 + mixin-1 + 0.1 + pom + + + true + + diff --git a/its/core-it-suite/src/test/resources/mng-5102-mixins/path/mixins/mixin-3.xml b/its/core-it-suite/src/test/resources/mng-5102-mixins/path/mixins/mixin-3.xml new file mode 100644 index 000000000000..a65a074665b9 --- /dev/null +++ b/its/core-it-suite/src/test/resources/mng-5102-mixins/path/mixins/mixin-3.xml @@ -0,0 +1,29 @@ + + + + org.apache.maven.its.mng5102 + mixin-3 + 0.1 + pom + + + true + + diff --git a/its/core-it-suite/src/test/resources/mng-5102-mixins/path/pom.xml b/its/core-it-suite/src/test/resources/mng-5102-mixins/path/pom.xml new file mode 100644 index 000000000000..f57e09554a37 --- /dev/null +++ b/its/core-it-suite/src/test/resources/mng-5102-mixins/path/pom.xml @@ -0,0 +1,63 @@ + + + + 4.2.0 + org.apache.maven.its.mng5102 + path + 0.1 + pom + + Maven Integration Test :: MNG-5102 + Mixins tests. + + + + + org.apache.maven.its.plugins + maven-it-plugin-expression + 2.1-SNAPSHOT + + + + eval + + validate + + target/model.properties + + project/properties + + + + + + + + + + + mixins/mixin-1.xml + + + + + child + + diff --git a/src/site/markdown/mixins.md b/src/site/markdown/mixins.md new file mode 100644 index 000000000000..ba5e34c20f86 --- /dev/null +++ b/src/site/markdown/mixins.md @@ -0,0 +1,527 @@ + +# Maven Mixins + +Maven Mixins provide a powerful mechanism for sharing common POM configuration across multiple projects without the limitations of traditional inheritance. Mixins allow you to compose your project configuration from multiple sources, promoting better code reuse and maintainability. + +## Overview + +Maven Mixins address several limitations of traditional POM inheritance: + +- **Single inheritance limitation**: Maven POMs can only inherit from one parent, limiting reusability +- **Deep inheritance hierarchies**: Complex inheritance chains can be difficult to understand and maintain +- **Configuration duplication**: Common configurations often need to be duplicated across projects +- **Tight coupling**: Changes to parent POMs can have unintended effects on child projects + +Mixins solve these problems by allowing you to include configuration from multiple sources in a composable way. + +## Basic Usage + +### Declaring Mixins + +Mixins are declared in the `` section of your POM: + +```xml + + 4.2.0 + + com.example + my-project + 1.0.0 + + + + com.example.mixins + java-mixin + 1.0.0 + + + com.example.mixins + testing-mixin + 1.0.0 + + + + + +``` + +### Model Version Requirement + +Mixins require **model version 4.2.0** or higher. This new model version was introduced specifically to support the mixins feature while maintaining backward compatibility. + +## Creating Mixin POMs + +A mixin is simply a regular Maven POM that contains reusable configuration. Here's an example of a Java compilation mixin: + +```xml + + 4.2.0 + + com.example.mixins + java-mixin + 1.0.0 + pom + + Java Compilation Mixin + Common Java compilation settings + + + 17 + 17 + UTF-8 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 17 + + -Xlint:all + -Werror + + + + + + +``` + +## Advanced Features + +### Mixin Composition Order + +Mixins are applied in the order they are declared. Later mixins can override configuration from earlier mixins: + +```xml + + + com.example.mixins + base-mixin + 1.0.0 + + + com.example.mixins + override-mixin + 1.0.0 + + +``` + +### Combining with Inheritance + +Mixins work alongside traditional POM inheritance. The resolution order is: + +1. Parent POM configuration +2. Mixin configurations (in declaration order) +3. Current POM configuration + +```xml + + 4.2.0 + + + com.example + parent-pom + 1.0.0 + + + + + com.example.mixins + java-mixin + 1.0.0 + + + + my-project + + +``` + +### Version Management + +Like dependencies, mixin versions can be managed centrally: + +```xml + + 4.2.0 + + + + + com.example.mixins + java-mixin + 1.0.0 + + + com.example.mixins + testing-mixin + 2.0.0 + + + + + + + com.example.mixins + java-mixin + + + + +``` + +## Common Use Cases + +### 1. Technology Stack Mixins + +Create mixins for specific technology stacks: + +```xml + + + 4.2.0 + com.example.mixins + spring-boot-mixin + 1.0.0 + pom + + + 3.1.0 + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + + +``` + +### 2. Quality Assurance Mixins + +Standardize code quality tools across projects: + +```xml + + + 4.2.0 + com.example.mixins + quality-mixin + 1.0.0 + pom + + + + + org.sonarsource.scanner.maven + sonar-maven-plugin + 3.9.1.2184 + + + com.github.spotbugs + spotbugs-maven-plugin + 4.7.3.0 + + + org.jacoco + jacoco-maven-plugin + 0.8.8 + + + + prepare-agent + + + + report + test + + report + + + + + + + +``` + +### 3. Testing Mixins + +Standardize testing configurations: + +```xml + + + 4.2.0 + com.example.mixins + testing-mixin + 1.0.0 + pom + + + 5.9.2 + 5.1.1 + + + + + + org.junit.jupiter + junit-jupiter + ${junit.version} + test + + + org.mockito + mockito-core + ${mockito.version} + test + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M9 + + + **/*Test.java + **/*Tests.java + + + + + + +``` + +## Best Practices + +### 1. Keep Mixins Focused + +Each mixin should have a single, well-defined purpose: + +- ✅ `java-17-mixin` - Java 17 compilation settings +- ✅ `spring-boot-mixin` - Spring Boot configuration +- ✅ `testing-mixin` - Testing framework setup +- ❌ `everything-mixin` - Kitchen sink approach + +### 2. Use Semantic Versioning + +Version your mixins using semantic versioning to communicate compatibility: + +- `1.0.0` - Initial release +- `1.1.0` - New features, backward compatible +- `2.0.0` - Breaking changes + +### 3. Document Your Mixins + +Include clear documentation in your mixin POMs: + +```xml + + 4.2.0 + com.example.mixins + java-mixin + 1.0.0 + pom + + Java Compilation Mixin + + Provides standardized Java compilation settings including: + - Java 17 source/target compatibility + - UTF-8 encoding + - Compiler warnings and error handling + - Code quality checks + + + https://github.com/example/maven-mixins + + + +``` + +### 4. Test Your Mixins + +Create test projects that use your mixins to ensure they work correctly: + +``` +maven-mixins/ +├── java-mixin/ +│ └── pom.xml +├── testing-mixin/ +│ └── pom.xml +└── test-projects/ + ├── simple-java-project/ + │ └── pom.xml (uses java-mixin) + └── spring-boot-project/ + └── pom.xml (uses java-mixin + spring-boot-mixin) +``` + +### 5. Organize Mixins in a Dedicated Repository + +Consider organizing your mixins in a dedicated repository or module: + +``` +company-maven-mixins/ +├── pom.xml (parent for all mixins) +├── java-mixin/ +├── spring-boot-mixin/ +├── testing-mixin/ +├── quality-mixin/ +└── README.md +``` + +## Migration from Parent POMs + +### Before (Traditional Inheritance) + +```xml + + + 4.0.0 + com.example + parent-pom + 1.0.0 + pom + + + + 17 + 3.1.0 + 5.9.2 + + + + + + + + 4.0.0 + + + com.example + parent-pom + 1.0.0 + + + my-project + + +``` + +### After (With Mixins) + +```xml + + + 4.2.0 + + com.example + my-project + 1.0.0 + + + + com.example.mixins + java-mixin + 1.0.0 + + + com.example.mixins + spring-boot-mixin + 1.0.0 + + + com.example.mixins + testing-mixin + 1.0.0 + + + + + +``` + +## Troubleshooting + +### Common Issues + +1. **Model Version Error** + ``` + Error: mixins require model version 4.2.0 or higher + ``` + Solution: Update your `` to `4.2.0` + +2. **Mixin Not Found** + ``` + Error: Could not resolve mixin com.example:my-mixin:1.0.0 + ``` + Solution: Ensure the mixin is deployed to your repository + +3. **Circular Dependencies** + ``` + Error: Circular mixin dependency detected + ``` + Solution: Review your mixin dependencies and remove cycles + +### Debugging Mixin Resolution + +Use the Maven help plugin to see the effective POM after mixin resolution: + +```bash +mvn help:effective-pom +``` + +## Conclusion + +Maven Mixins provide a powerful and flexible way to share configuration across projects while avoiding the limitations of traditional inheritance. By composing your project configuration from focused, reusable mixins, you can: + +- Reduce configuration duplication +- Improve maintainability +- Enable better separation of concerns +- Facilitate standardization across teams and organizations + +Start small by creating mixins for your most common configuration patterns, and gradually build a library of reusable components that can accelerate your development process. diff --git a/src/site/site.xml b/src/site/site.xml index d5643feb9a77..0cf8c7d17419 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -44,6 +44,11 @@ under the License. + + + + +