This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven.git
The following commit(s) were added to refs/heads/master by this push:
new d30ce5d6de Accept Java module names as attached artifactId even if
they differ from the project's artifactId (#11549)
d30ce5d6de is described below
commit d30ce5d6def1da2c2323e497e42a8e7d5cbb75f1
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Wed Dec 17 17:17:01 2025 +0100
Accept Java module names as attached artifactId even if they differ from
the project's artifactId (#11549)
The intend is to support multi-module project where more than one artifact
may be produced.
---
.../maven/internal/impl/DefaultProjectManager.java | 58 +++++++++++++++---
.../internal/impl/DefaultProjectManagerTest.java | 71 ++++++++++++++++++++--
.../org/apache/maven/impl/DefaultSourceRoot.java | 2 +-
3 files changed, 115 insertions(+), 16 deletions(-)
diff --git
a/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProjectManager.java
b/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProjectManager.java
index 7c495a7344..aae6429912 100644
---
a/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProjectManager.java
+++
b/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProjectManager.java
@@ -119,15 +119,55 @@ public void attachArtifact(@Nonnull Project project,
@Nonnull ProducedArtifact a
artifact.getExtension(),
null);
}
- if (!Objects.equals(project.getGroupId(), artifact.getGroupId())
- || !Objects.equals(project.getArtifactId(),
artifact.getArtifactId())
- || !Objects.equals(
- project.getVersion(),
artifact.getBaseVersion().toString())) {
- throw new IllegalArgumentException(
- "The produced artifact must have the same
groupId/artifactId/version than the project it is attached to. Expecting "
- + project.getGroupId() + ":" +
project.getArtifactId() + ":" + project.getVersion()
- + " but received " + artifact.getGroupId() + ":" +
artifact.getArtifactId() + ":"
- + artifact.getBaseVersion());
+ // Verify groupId and version, intentionally allow artifactId to
differ as Maven project may be
+ // multi-module with modular sources structure that provide module
names used as artifactIds.
+ String g1 = project.getGroupId();
+ String a1 = project.getArtifactId();
+ String v1 = project.getVersion();
+ String g2 = artifact.getGroupId();
+ String a2 = artifact.getArtifactId();
+ String v2 = artifact.getBaseVersion().toString();
+
+ // ArtifactId may differ only for multi-module projects, in which case
+ // it must match the module name from a source root in modular sources.
+ boolean isMultiModule = false;
+ boolean validArtifactId = Objects.equals(a1, a2);
+ for (SourceRoot sr : getSourceRoots(project)) {
+ Optional<String> moduleName = sr.module();
+ if (moduleName.isPresent()) {
+ isMultiModule = true;
+ if (moduleName.get().equals(a2)) {
+ validArtifactId = true;
+ break;
+ }
+ }
+ }
+ boolean isSameGroupAndVersion = Objects.equals(g1, g2) &&
Objects.equals(v1, v2);
+ if (!(isSameGroupAndVersion && validArtifactId)) {
+ String message;
+ if (isMultiModule) {
+ // Multi-module project: artifactId may match any declared
module name
+ message = String.format(
+ "Cannot attach artifact to project: groupId and
version must match the project, "
+ + "and artifactId must match either the
project or a declared module name.%n"
+ + " Project coordinates: %s:%s:%s%n"
+ + " Artifact coordinates: %s:%s:%s%n",
+ g1, a1, v1, g2, a2, v2);
+ if (isSameGroupAndVersion) {
+ message += String.format(
+ " Hint: The artifactId '%s' does not match the
project artifactId '%s' "
+ + "nor any declared module name in source
roots.",
+ a2, a1);
+ }
+ } else {
+ // Non-modular project: artifactId must match exactly
+ message = String.format(
+ "Cannot attach artifact to project: groupId,
artifactId and version must match the project.%n"
+ + " Project coordinates: %s:%s:%s%n"
+ + " Artifact coordinates: %s:%s:%s",
+ g1, a1, v1, g2, a2, v2);
+ }
+ throw new IllegalArgumentException(message);
}
getMavenProject(project)
.addAttachedArtifact(
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 560fd9941b..48e87f7cda 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
@@ -20,11 +20,15 @@
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.function.Supplier;
+import org.apache.maven.api.Language;
import org.apache.maven.api.ProducedArtifact;
import org.apache.maven.api.Project;
+import org.apache.maven.api.ProjectScope;
import org.apache.maven.api.services.ArtifactManager;
import org.apache.maven.impl.DefaultModelVersionParser;
+import org.apache.maven.impl.DefaultSourceRoot;
import org.apache.maven.impl.DefaultVersionParser;
import org.apache.maven.project.MavenProject;
import org.eclipse.aether.util.version.GenericVersionScheme;
@@ -32,21 +36,30 @@
import org.mockito.Mockito;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.when;
class DefaultProjectManagerTest {
+ private DefaultProjectManager projectManager;
+
+ private Project project;
+
+ private ProducedArtifact artifact;
+
+ private Path artifactPath;
+
@Test
void attachArtifact() {
InternalMavenSession session =
Mockito.mock(InternalMavenSession.class);
ArtifactManager artifactManager = Mockito.mock(ArtifactManager.class);
MavenProject mavenProject = new MavenProject();
- Project project = new DefaultProject(session, mavenProject);
- ProducedArtifact artifact = Mockito.mock(ProducedArtifact.class);
- Path path = Paths.get("");
+ project = new DefaultProject(session, mavenProject);
+ artifact = Mockito.mock(ProducedArtifact.class);
+ artifactPath = Paths.get("");
DefaultVersionParser versionParser =
new DefaultVersionParser(new DefaultModelVersionParser(new
GenericVersionScheme()));
- DefaultProjectManager projectManager = new
DefaultProjectManager(session, artifactManager);
+ projectManager = new DefaultProjectManager(session, artifactManager);
mavenProject.setGroupId("myGroup");
mavenProject.setArtifactId("myArtifact");
@@ -54,9 +67,55 @@ void attachArtifact() {
when(artifact.getGroupId()).thenReturn("myGroup");
when(artifact.getArtifactId()).thenReturn("myArtifact");
when(artifact.getBaseVersion()).thenReturn(versionParser.parseVersion("1.0-SNAPSHOT"));
- projectManager.attachArtifact(project, artifact, path);
+ projectManager.attachArtifact(project, artifact, artifactPath);
+ // Verify that an exception is thrown when the artifactId differs
when(artifact.getArtifactId()).thenReturn("anotherArtifact");
- assertThrows(IllegalArgumentException.class, () ->
projectManager.attachArtifact(project, artifact, path));
+ assertExceptionMessageContains("myGroup:myArtifact:1.0-SNAPSHOT",
"myGroup:anotherArtifact:1.0-SNAPSHOT");
+
+ // Add a Java module. It should relax the restriction on artifactId.
+ projectManager.addSourceRoot(
+ project,
+ new DefaultSourceRoot(
+ ProjectScope.MAIN,
+ Language.JAVA_FAMILY,
+ "org.foo.bar",
+ null,
+ Path.of("myProject"),
+ null,
+ null,
+ false,
+ null,
+ true));
+
+ // Verify that we get the same exception when the artifactId does not
match the module name
+ assertExceptionMessageContains("", "anotherArtifact");
+
+ // Verify that no exception is thrown when the artifactId is the
module name
+ when(artifact.getArtifactId()).thenReturn("org.foo.bar");
+ projectManager.attachArtifact(project, artifact, artifactPath);
+
+ // Verify that an exception is thrown when the groupId differs
+ when(artifact.getGroupId()).thenReturn("anotherGroup");
+ assertExceptionMessageContains("myGroup:myArtifact:1.0-SNAPSHOT",
"anotherGroup:org.foo.bar:1.0-SNAPSHOT");
+ }
+
+ /**
+ * Verifies that {@code projectManager.attachArtifact(…)} throws an
exception,
+ * and that the expecption message contains the expected and actual
<abbr>GAV</abbr>.
+ *
+ * @param expectedGAV the actual <abbr>GAV</abbr> that the exception
message should contain
+ * @param actualGAV the actual <abbr>GAV</abbr> that the exception message
should contain
+ */
+ private void assertExceptionMessageContains(String expectedGAV, String
actualGAV) {
+ String cause = assertThrows(
+ IllegalArgumentException.class,
+ () -> projectManager.attachArtifact(project, artifact,
artifactPath))
+ .getMessage();
+ Supplier<String> message = () ->
+ String.format("The exception message does not contain the
expected GAV. Message was:%n%s%n", cause);
+
+ assertTrue(cause.contains(expectedGAV), message);
+ assertTrue(cause.contains(actualGAV), message);
}
}
diff --git
a/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultSourceRoot.java
b/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultSourceRoot.java
index 870b429517..d2b0142cfc 100644
--- a/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultSourceRoot.java
+++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultSourceRoot.java
@@ -94,7 +94,7 @@ public DefaultSourceRoot(
@Nonnull Language language,
@Nullable String moduleName,
@Nullable Version targetVersionOrNull,
- @Nullable Path directory,
+ @Nonnull Path directory,
@Nullable List<String> includes,
@Nullable List<String> excludes,
boolean stringFiltering,