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,

Reply via email to