Skip to content

Commit efe81b2

Browse files
committed
[MNG-5102] Add support for POM mixins
1 parent 30fb050 commit efe81b2

File tree

21 files changed

+692
-256
lines changed

21 files changed

+692
-256
lines changed

api/maven-api-model/src/main/mdo/maven.mdo

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,20 @@
111111
</association>
112112
</field>
113113

114+
<!-- ====================================================================== -->
115+
<!-- Mixins -->
116+
<!-- ====================================================================== -->
117+
118+
<field xdoc.separator="blank">
119+
<name>mixins</name>
120+
<version>4.2.0+</version>
121+
<description>Mixins...</description>
122+
<association>
123+
<type>Mixin</type>
124+
<multiplicity>*</multiplicity>
125+
</association>
126+
</field>
127+
114128
<!-- ====================================================================== -->
115129
<!-- groupId/artifactId/Version/Packaging -->
116130
<!-- ====================================================================== -->
@@ -1832,6 +1846,23 @@
18321846
</codeSegments>
18331847

18341848
</class>
1849+
<class>
1850+
<name>Mixin</name>
1851+
<version>4.1.0+</version>
1852+
<superClass>Parent</superClass>
1853+
<fields>
1854+
<field>
1855+
<name>classifier</name>
1856+
<version>4.1.0+</version>
1857+
<type>String</type>
1858+
</field>
1859+
<field>
1860+
<name>extension</name>
1861+
<version>4.1.0+</version>
1862+
<type>String</type>
1863+
</field>
1864+
</fields>
1865+
</class>
18351866
<class>
18361867
<name>Scm</name>
18371868
<version>4.0.0+</version>

compat/maven-model-builder/src/main/java/org/apache/maven/model/building/FileToRawModelMerger.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,12 @@ protected void mergeModel_Profiles(
138138
.collect(Collectors.toList()));
139139
}
140140

141+
@Override
142+
protected void mergeModel_Mixins(
143+
Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
144+
// don't merge
145+
}
146+
141147
@Override
142148
protected void mergeModelBase_Dependencies(
143149
ModelBase.Builder builder,

impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ static Model transformNonPom(Model model, MavenProject project) {
210210
.preserveModelVersion(false)
211211
.root(false)
212212
.parent(null)
213+
.mixins(null)
213214
.build(null),
214215
model)
215216
.mailingLists(null)

impl/maven-impl/src/main/java/org/apache/maven/api/services/model/ModelResolver.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,16 @@ record ModelResolverRequest(
8585
@Nonnull String groupId,
8686
@Nonnull String artifactId,
8787
@Nonnull String version,
88-
@Nullable String classifier)
88+
@Nullable String classifier,
89+
@Nullable String extension)
8990
implements Request<Session> {
91+
public ModelResolverRequest {
92+
Objects.requireNonNull(session, "session cannot be null");
93+
Objects.requireNonNull(groupId, "groupId cannot be null");
94+
Objects.requireNonNull(artifactId, "artifactId cannot be null");
95+
Objects.requireNonNull(version, "version cannot be null");
96+
}
97+
9098
@Nonnull
9199
@Override
92100
public Session getSession() {
@@ -106,12 +114,13 @@ public boolean equals(Object o) {
106114
&& Objects.equals(groupId, that.groupId)
107115
&& Objects.equals(artifactId, that.artifactId)
108116
&& Objects.equals(version, that.version)
109-
&& Objects.equals(classifier, that.classifier);
117+
&& Objects.equals(classifier, that.classifier)
118+
&& Objects.equals(extension, that.extension);
110119
}
111120

112121
@Override
113122
public int hashCode() {
114-
return Objects.hash(repositories, groupId, artifactId, version, classifier);
123+
return Objects.hash(repositories, groupId, artifactId, version, classifier, extension);
115124
}
116125

117126
@Override
@@ -123,6 +132,7 @@ public String toString() {
123132
+ ", artifactId=" + artifactId
124133
+ ", version=" + version
125134
+ ", classifier=" + classifier
135+
+ ", extension=" + extension
126136
+ ']';
127137
}
128138
}

impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultInheritanceAssembler.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,16 @@ private void concatPath(StringBuilder url, String path) {
194194
}
195195
}
196196

197+
@Override
198+
protected void mergeModel_Mixins(
199+
Model.Builder builder,
200+
Model target,
201+
Model source,
202+
boolean sourceDominant,
203+
Map<Object, Object> context) {
204+
// do not merge
205+
}
206+
197207
@Override
198208
protected void mergeModelBase_Properties(
199209
ModelBase.Builder builder,

impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java

Lines changed: 60 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
import org.apache.maven.api.model.Exclusion;
6767
import org.apache.maven.api.model.InputLocation;
6868
import org.apache.maven.api.model.InputSource;
69+
import org.apache.maven.api.model.Mixin;
6970
import org.apache.maven.api.model.Model;
7071
import org.apache.maven.api.model.Parent;
7172
import org.apache.maven.api.model.Profile;
@@ -851,12 +852,11 @@ void buildEffectiveModel(Collection<String> importIds) throws ModelBuilderExcept
851852
}
852853
}
853854

854-
Model readParent(Model childModel, DefaultProfileActivationContext profileActivationContext) {
855+
Model readParent(Model childModel, Parent parent, DefaultProfileActivationContext profileActivationContext) {
855856
Model parentModel;
856857

857-
Parent parent = childModel.getParent();
858858
if (parent != null) {
859-
parentModel = resolveParent(childModel, profileActivationContext);
859+
parentModel = resolveParent(childModel, parent, profileActivationContext);
860860

861861
if (!"pom".equals(parentModel.getPackaging())) {
862862
add(
@@ -881,23 +881,26 @@ Model readParent(Model childModel, DefaultProfileActivationContext profileActiva
881881
return parentModel;
882882
}
883883

884-
private Model resolveParent(Model childModel, DefaultProfileActivationContext profileActivationContext)
884+
private Model resolveParent(
885+
Model childModel, Parent parent, DefaultProfileActivationContext profileActivationContext)
885886
throws ModelBuilderException {
886887
Model parentModel = null;
887888
if (isBuildRequest()) {
888-
parentModel = readParentLocally(childModel, profileActivationContext);
889+
parentModel = readParentLocally(childModel, parent, profileActivationContext);
889890
}
890891
if (parentModel == null) {
891-
parentModel = resolveAndReadParentExternally(childModel, profileActivationContext);
892+
parentModel = resolveAndReadParentExternally(childModel, parent, profileActivationContext);
892893
}
893894
return parentModel;
894895
}
895896

896-
private Model readParentLocally(Model childModel, DefaultProfileActivationContext profileActivationContext)
897+
private Model readParentLocally(
898+
Model childModel, Parent parent, DefaultProfileActivationContext profileActivationContext)
897899
throws ModelBuilderException {
898900
ModelSource candidateSource;
899901

900-
Parent parent = childModel.getParent();
902+
boolean isParentOrSimpleMixin =
903+
!(parent instanceof Mixin mixin) || (mixin.getClassifier() == null && mixin.getExtension() == null);
901904
String parentPath = parent.getRelativePath();
902905
if (request.getRequestType() == ModelBuilderRequest.RequestType.BUILD_PROJECT) {
903906
if (parentPath != null && !parentPath.isEmpty()) {
@@ -906,14 +909,16 @@ private Model readParentLocally(Model childModel, DefaultProfileActivationContex
906909
wrongParentRelativePath(childModel);
907910
return null;
908911
}
909-
} else {
912+
} else if (isParentOrSimpleMixin) {
910913
candidateSource =
911914
resolveReactorModel(parent.getGroupId(), parent.getArtifactId(), parent.getVersion());
912915
if (candidateSource == null && parentPath == null) {
913916
candidateSource = request.getSource().resolve(modelProcessor::locateExistingPom, "..");
914917
}
918+
} else {
919+
candidateSource = null;
915920
}
916-
} else {
921+
} else if (isParentOrSimpleMixin) {
917922
candidateSource = resolveReactorModel(parent.getGroupId(), parent.getArtifactId(), parent.getVersion());
918923
if (candidateSource == null) {
919924
if (parentPath == null) {
@@ -923,6 +928,8 @@ private Model readParentLocally(Model childModel, DefaultProfileActivationContex
923928
candidateSource = request.getSource().resolve(modelProcessor::locateExistingPom, parentPath);
924929
}
925930
}
931+
} else {
932+
candidateSource = null;
926933
}
927934

928935
if (candidateSource == null) {
@@ -938,11 +945,10 @@ private Model readParentLocally(Model childModel, DefaultProfileActivationContex
938945
String version = getVersion(candidateModel);
939946

940947
// Ensure that relative path and GA match, if both are provided
941-
if (groupId == null
942-
|| !groupId.equals(parent.getGroupId())
943-
|| artifactId == null
944-
|| !artifactId.equals(parent.getArtifactId())) {
945-
mismatchRelativePathAndGA(childModel, groupId, artifactId);
948+
if (parent.getGroupId() != null && (groupId == null || !groupId.equals(parent.getGroupId()))
949+
|| parent.getArtifactId() != null
950+
&& (artifactId == null || !artifactId.equals(parent.getArtifactId()))) {
951+
mismatchRelativePathAndGA(childModel, parent, groupId, artifactId);
946952
return null;
947953
}
948954

@@ -981,8 +987,7 @@ private Model readParentLocally(Model childModel, DefaultProfileActivationContex
981987
return candidateModel;
982988
}
983989

984-
private void mismatchRelativePathAndGA(Model childModel, String groupId, String artifactId) {
985-
Parent parent = childModel.getParent();
990+
private void mismatchRelativePathAndGA(Model childModel, Parent parent, String groupId, String artifactId) {
986991
StringBuilder buffer = new StringBuilder(256);
987992
buffer.append("'parent.relativePath'");
988993
if (childModel != getRootModel()) {
@@ -1013,16 +1018,17 @@ private void wrongParentRelativePath(Model childModel) {
10131018
add(Severity.FATAL, Version.BASE, buffer.toString(), parent.getLocation(""));
10141019
}
10151020

1016-
Model resolveAndReadParentExternally(Model childModel, DefaultProfileActivationContext profileActivationContext)
1021+
Model resolveAndReadParentExternally(
1022+
Model childModel, Parent parent, DefaultProfileActivationContext profileActivationContext)
10171023
throws ModelBuilderException {
10181024
ModelBuilderRequest request = this.request;
10191025
setSource(childModel);
10201026

1021-
Parent parent = childModel.getParent();
1022-
10231027
String groupId = parent.getGroupId();
10241028
String artifactId = parent.getArtifactId();
10251029
String version = parent.getVersion();
1030+
String classifier = parent instanceof Mixin mixin ? mixin.getClassifier() : null;
1031+
String extension = parent instanceof Mixin mixin ? mixin.getExtension() : null;
10261032

10271033
// add repositories specified by the current model so that we can resolve the parent
10281034
if (!childModel.getRepositories().isEmpty()) {
@@ -1040,12 +1046,23 @@ Model resolveAndReadParentExternally(Model childModel, DefaultProfileActivationC
10401046

10411047
ModelSource modelSource;
10421048
try {
1043-
modelSource = resolveReactorModel(parent.getGroupId(), parent.getArtifactId(), parent.getVersion());
1049+
modelSource = classifier == null && extension == null
1050+
? resolveReactorModel(parent.getGroupId(), parent.getArtifactId(), parent.getVersion())
1051+
: null;
10441052
if (modelSource == null) {
1045-
AtomicReference<Parent> modified = new AtomicReference<>();
1046-
modelSource = modelResolver.resolveModel(request.getSession(), repositories, parent, modified);
1047-
if (modified.get() != null) {
1048-
parent = modified.get();
1053+
ModelResolver.ModelResolverRequest req = new ModelResolver.ModelResolverRequest(
1054+
request.getSession(),
1055+
null,
1056+
repositories,
1057+
parent.getGroupId(),
1058+
parent.getArtifactId(),
1059+
parent.getVersion(),
1060+
classifier,
1061+
extension != null ? extension : "pom");
1062+
ModelResolver.ModelResolverResult result = modelResolver.resolveModel(req);
1063+
modelSource = result.source();
1064+
if (result.version() != null) {
1065+
parent = parent.withVersion(result.version());
10491066
}
10501067
}
10511068
} catch (ModelResolverException e) {
@@ -1161,7 +1178,8 @@ private Model readEffectiveModel() throws ModelBuilderException {
11611178
profileActivationContext.setUserProperties(profileProps);
11621179
}
11631180

1164-
Model parentModel = readParent(activatedFileModel, profileActivationContext);
1181+
Model parentModel =
1182+
readParent(activatedFileModel, activatedFileModel.getParent(), profileActivationContext);
11651183

11661184
// Now that we have read the parent, we can set the relative
11671185
// path correctly if it was not set in the input model
@@ -1183,6 +1201,12 @@ private Model readEffectiveModel() throws ModelBuilderException {
11831201

11841202
Model model = inheritanceAssembler.assembleModelInheritance(inputModel, parentModel, request, this);
11851203

1204+
// Mixins
1205+
for (Mixin mixin : model.getMixins()) {
1206+
Model parent = resolveParent(model, mixin, profileActivationContext);
1207+
model = inheritanceAssembler.assembleModelInheritance(model, parent, request, this);
1208+
}
1209+
11861210
// model normalization
11871211
model = modelNormalizer.mergeDuplicates(model, request, this);
11881212

@@ -1367,7 +1391,7 @@ Model doReadFileModel() throws ModelBuilderException {
13671391
.version(parentVersion)
13681392
.build());
13691393
} else {
1370-
mismatchRelativePathAndGA(model, parentGroupId, parentArtifactId);
1394+
mismatchRelativePathAndGA(model, parent, parentGroupId, parentArtifactId);
13711395
}
13721396
} else {
13731397
if (!MODEL_VERSION_4_0_0.equals(model.getModelVersion()) && path != null) {
@@ -1555,8 +1579,9 @@ Model readAsParentModel(DefaultProfileActivationContext profileActivationContext
15551579
private Model doReadAsParentModel(DefaultProfileActivationContext profileActivationContext)
15561580
throws ModelBuilderException {
15571581
Model raw = readRawModel();
1558-
Model parentData = readParent(raw, profileActivationContext);
1559-
Model parent = new DefaultInheritanceAssembler(new DefaultInheritanceAssembler.InheritanceModelMerger() {
1582+
Model parentData = readParent(raw, raw.getParent(), profileActivationContext);
1583+
DefaultInheritanceAssembler defaultInheritanceAssembler =
1584+
new DefaultInheritanceAssembler(new DefaultInheritanceAssembler.InheritanceModelMerger() {
15601585
@Override
15611586
protected void mergeModel_Modules(
15621587
Model.Builder builder,
@@ -1572,8 +1597,12 @@ protected void mergeModel_Subprojects(
15721597
Model source,
15731598
boolean sourceDominant,
15741599
Map<Object, Object> context) {}
1575-
})
1576-
.assembleModelInheritance(raw, parentData, request, this);
1600+
});
1601+
Model parent = defaultInheritanceAssembler.assembleModelInheritance(raw, parentData, request, this);
1602+
for (Mixin mixin : parent.getMixins()) {
1603+
Model parentModel = resolveParent(parent, mixin, profileActivationContext);
1604+
parent = defaultInheritanceAssembler.assembleModelInheritance(parent, parentModel, request, this);
1605+
}
15771606

15781607
// activate profiles
15791608
List<Profile> parentActivePomProfiles = getActiveProfiles(parent.getProfiles(), profileActivationContext);

impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelValidator.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,28 @@ public void validateFileModel(Model m, int validationLevel, ModelProblemCollecto
354354
}
355355
}
356356

357+
// Validate mixins
358+
if (!m.getMixins().isEmpty()) {
359+
// Ensure model version is at least 4.2.0 when using mixins
360+
if (compareModelVersions("4.2.0", m.getModelVersion()) < 0) {
361+
addViolation(
362+
problems,
363+
Severity.ERROR,
364+
Version.V40,
365+
"mixins",
366+
null,
367+
"Mixins are only supported in modelVersion 4.2.0 or higher, but found '" + m.getModelVersion()
368+
+ "'.",
369+
m);
370+
}
371+
372+
// Validate each mixin
373+
for (Parent mixin : m.getMixins()) {
374+
// TODO: additional mixin validation
375+
mixin.getId();
376+
}
377+
}
378+
357379
if (validationLevel == ModelValidator.VALIDATION_LEVEL_MINIMAL) {
358380
// profiles: they are essential for proper model building (may contribute profiles, dependencies...)
359381
HashSet<String> minProfileIds = new HashSet<>();

0 commit comments

Comments
 (0)