Skip to content

Commit 786e574

Browse files
authored
Accept Java module names as attached artifactId even if they differ from the project's artifactId (#11573)
This is a backport of #11549. The intend is to support multi-module project where more than one artifact may be produced.
1 parent 87ff342 commit 786e574

File tree

3 files changed

+115
-16
lines changed

3 files changed

+115
-16
lines changed

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

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -119,15 +119,55 @@ public void attachArtifact(@Nonnull Project project, @Nonnull ProducedArtifact a
119119
artifact.getExtension(),
120120
null);
121121
}
122-
if (!Objects.equals(project.getGroupId(), artifact.getGroupId())
123-
|| !Objects.equals(project.getArtifactId(), artifact.getArtifactId())
124-
|| !Objects.equals(
125-
project.getVersion(), artifact.getBaseVersion().toString())) {
126-
throw new IllegalArgumentException(
127-
"The produced artifact must have the same groupId/artifactId/version than the project it is attached to. Expecting "
128-
+ project.getGroupId() + ":" + project.getArtifactId() + ":" + project.getVersion()
129-
+ " but received " + artifact.getGroupId() + ":" + artifact.getArtifactId() + ":"
130-
+ artifact.getBaseVersion());
122+
// Verify groupId and version, intentionally allow artifactId to differ as Maven project may be
123+
// multi-module with modular sources structure that provide module names used as artifactIds.
124+
String g1 = project.getGroupId();
125+
String a1 = project.getArtifactId();
126+
String v1 = project.getVersion();
127+
String g2 = artifact.getGroupId();
128+
String a2 = artifact.getArtifactId();
129+
String v2 = artifact.getBaseVersion().toString();
130+
131+
// ArtifactId may differ only for multi-module projects, in which case
132+
// it must match the module name from a source root in modular sources.
133+
boolean isMultiModule = false;
134+
boolean validArtifactId = Objects.equals(a1, a2);
135+
for (SourceRoot sr : getSourceRoots(project)) {
136+
Optional<String> moduleName = sr.module();
137+
if (moduleName.isPresent()) {
138+
isMultiModule = true;
139+
if (moduleName.get().equals(a2)) {
140+
validArtifactId = true;
141+
break;
142+
}
143+
}
144+
}
145+
boolean isSameGroupAndVersion = Objects.equals(g1, g2) && Objects.equals(v1, v2);
146+
if (!(isSameGroupAndVersion && validArtifactId)) {
147+
String message;
148+
if (isMultiModule) {
149+
// Multi-module project: artifactId may match any declared module name
150+
message = String.format(
151+
"Cannot attach artifact to project: groupId and version must match the project, "
152+
+ "and artifactId must match either the project or a declared module name.%n"
153+
+ " Project coordinates: %s:%s:%s%n"
154+
+ " Artifact coordinates: %s:%s:%s%n",
155+
g1, a1, v1, g2, a2, v2);
156+
if (isSameGroupAndVersion) {
157+
message += String.format(
158+
" Hint: The artifactId '%s' does not match the project artifactId '%s' "
159+
+ "nor any declared module name in source roots.",
160+
a2, a1);
161+
}
162+
} else {
163+
// Non-modular project: artifactId must match exactly
164+
message = String.format(
165+
"Cannot attach artifact to project: groupId, artifactId and version must match the project.%n"
166+
+ " Project coordinates: %s:%s:%s%n"
167+
+ " Artifact coordinates: %s:%s:%s",
168+
g1, a1, v1, g2, a2, v2);
169+
}
170+
throw new IllegalArgumentException(message);
131171
}
132172
getMavenProject(project)
133173
.addAttachedArtifact(

impl/maven-core/src/test/java/org/apache/maven/internal/impl/DefaultProjectManagerTest.java

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,43 +20,102 @@
2020

2121
import java.nio.file.Path;
2222
import java.nio.file.Paths;
23+
import java.util.function.Supplier;
2324

25+
import org.apache.maven.api.Language;
2426
import org.apache.maven.api.ProducedArtifact;
2527
import org.apache.maven.api.Project;
28+
import org.apache.maven.api.ProjectScope;
2629
import org.apache.maven.api.services.ArtifactManager;
2730
import org.apache.maven.impl.DefaultModelVersionParser;
31+
import org.apache.maven.impl.DefaultSourceRoot;
2832
import org.apache.maven.impl.DefaultVersionParser;
2933
import org.apache.maven.project.MavenProject;
3034
import org.eclipse.aether.util.version.GenericVersionScheme;
3135
import org.junit.jupiter.api.Test;
3236
import org.mockito.Mockito;
3337

3438
import static org.junit.jupiter.api.Assertions.assertThrows;
39+
import static org.junit.jupiter.api.Assertions.assertTrue;
3540
import static org.mockito.Mockito.when;
3641

3742
class DefaultProjectManagerTest {
3843

44+
private DefaultProjectManager projectManager;
45+
46+
private Project project;
47+
48+
private ProducedArtifact artifact;
49+
50+
private Path artifactPath;
51+
3952
@Test
4053
void attachArtifact() {
4154
InternalMavenSession session = Mockito.mock(InternalMavenSession.class);
4255
ArtifactManager artifactManager = Mockito.mock(ArtifactManager.class);
4356
MavenProject mavenProject = new MavenProject();
44-
Project project = new DefaultProject(session, mavenProject);
45-
ProducedArtifact artifact = Mockito.mock(ProducedArtifact.class);
46-
Path path = Paths.get("");
57+
project = new DefaultProject(session, mavenProject);
58+
artifact = Mockito.mock(ProducedArtifact.class);
59+
artifactPath = Paths.get("");
4760
DefaultVersionParser versionParser =
4861
new DefaultVersionParser(new DefaultModelVersionParser(new GenericVersionScheme()));
49-
DefaultProjectManager projectManager = new DefaultProjectManager(session, artifactManager);
62+
projectManager = new DefaultProjectManager(session, artifactManager);
5063

5164
mavenProject.setGroupId("myGroup");
5265
mavenProject.setArtifactId("myArtifact");
5366
mavenProject.setVersion("1.0-SNAPSHOT");
5467
when(artifact.getGroupId()).thenReturn("myGroup");
5568
when(artifact.getArtifactId()).thenReturn("myArtifact");
5669
when(artifact.getBaseVersion()).thenReturn(versionParser.parseVersion("1.0-SNAPSHOT"));
57-
projectManager.attachArtifact(project, artifact, path);
70+
projectManager.attachArtifact(project, artifact, artifactPath);
5871

72+
// Verify that an exception is thrown when the artifactId differs
5973
when(artifact.getArtifactId()).thenReturn("anotherArtifact");
60-
assertThrows(IllegalArgumentException.class, () -> projectManager.attachArtifact(project, artifact, path));
74+
assertExceptionMessageContains("myGroup:myArtifact:1.0-SNAPSHOT", "myGroup:anotherArtifact:1.0-SNAPSHOT");
75+
76+
// Add a Java module. It should relax the restriction on artifactId.
77+
projectManager.addSourceRoot(
78+
project,
79+
new DefaultSourceRoot(
80+
ProjectScope.MAIN,
81+
Language.JAVA_FAMILY,
82+
"org.foo.bar",
83+
null,
84+
Path.of("myProject"),
85+
null,
86+
null,
87+
false,
88+
null,
89+
true));
90+
91+
// Verify that we get the same exception when the artifactId does not match the module name
92+
assertExceptionMessageContains("", "anotherArtifact");
93+
94+
// Verify that no exception is thrown when the artifactId is the module name
95+
when(artifact.getArtifactId()).thenReturn("org.foo.bar");
96+
projectManager.attachArtifact(project, artifact, artifactPath);
97+
98+
// Verify that an exception is thrown when the groupId differs
99+
when(artifact.getGroupId()).thenReturn("anotherGroup");
100+
assertExceptionMessageContains("myGroup:myArtifact:1.0-SNAPSHOT", "anotherGroup:org.foo.bar:1.0-SNAPSHOT");
101+
}
102+
103+
/**
104+
* Verifies that {@code projectManager.attachArtifact(…)} throws an exception,
105+
* and that the expecption message contains the expected and actual <abbr>GAV</abbr>.
106+
*
107+
* @param expectedGAV the actual <abbr>GAV</abbr> that the exception message should contain
108+
* @param actualGAV the actual <abbr>GAV</abbr> that the exception message should contain
109+
*/
110+
private void assertExceptionMessageContains(String expectedGAV, String actualGAV) {
111+
String cause = assertThrows(
112+
IllegalArgumentException.class,
113+
() -> projectManager.attachArtifact(project, artifact, artifactPath))
114+
.getMessage();
115+
Supplier<String> message = () ->
116+
String.format("The exception message does not contain the expected GAV. Message was:%n%s%n", cause);
117+
118+
assertTrue(cause.contains(expectedGAV), message);
119+
assertTrue(cause.contains(actualGAV), message);
61120
}
62121
}

impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultSourceRoot.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ public DefaultSourceRoot(
9494
@Nonnull Language language,
9595
@Nullable String moduleName,
9696
@Nullable Version targetVersionOrNull,
97-
@Nullable Path directory,
97+
@Nonnull Path directory,
9898
@Nullable List<String> includes,
9999
@Nullable List<String> excludes,
100100
boolean stringFiltering,

0 commit comments

Comments
 (0)