From 0287bcb4c03b61ad2483566495ee339612b96235 Mon Sep 17 00:00:00 2001 From: Gerd Aschemann Date: Fri, 9 Oct 2026 23:47:01 +0200 Subject: [PATCH] fix: skip main artifact whose file is a directory (module source hierarchy) A project built with the Maven 4 module source hierarchy produces one JAR per Java module, attached as separate artifacts, and never assigns a file to the main artifact -- its path stays the (directory) build output. With allowIncompleteProjects=true the install goal warned about the missing main file but still passed the directory to the installer, which failed with "is a folder". Drop the main artifact from the install request in that case and install the POM and the attached per-module JARs only. Co-Authored-By: Claude Opus 4.8 --- .../maven/plugins/install/InstallMojo.java | 18 ++++++------ .../plugins/install/InstallMojoTest.java | 28 +++++++++++++++++++ 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/apache/maven/plugins/install/InstallMojo.java b/src/main/java/org/apache/maven/plugins/install/InstallMojo.java index 1eda589..798a670 100644 --- a/src/main/java/org/apache/maven/plugins/install/InstallMojo.java +++ b/src/main/java/org/apache/maven/plugins/install/InstallMojo.java @@ -249,6 +249,7 @@ private ArtifactInstallerRequest processProject(Project project) { ProjectManager projectManager = getProjectManager(); Collection installables = projectManager.getAllArtifacts(project); Collection attachedArtifacts = projectManager.getAttachedArtifacts(project); + List toInstall = new ArrayList<>(installables); getArtifactManager().setPath(project.getPomArtifact(), project.getPomPath()); @@ -260,13 +261,14 @@ private ArtifactInstallerRequest processProject(Project project) { "The packaging for this project did not assign a file to the build artifact"); } else { if (allowIncompleteProjects) { - getLog().warn(""); - getLog().warn("The packaging plugin for this project did not assign"); - getLog().warn( - "a main file to the project but it has attachments. Change packaging to 'pom'."); - getLog().warn(""); - getLog().warn("Incomplete projects like this will fail in future Maven versions!"); - getLog().warn(""); + // The main artifact has no file: its path is e.g. the output directory of a module + // source hierarchy, where the JAR plugin attaches one JAR per Java module instead of + // assigning a single main JAR. Install the POM and the attachments only; do not hand + // the directory to the installer, which would otherwise fail with "is a folder". + getLog().info("The project's main artifact has no file" + + " (e.g. a module source hierarchy produces one artifact per Java module);" + + " installing the POM and the attached artifacts only."); + toInstall.remove(installable); } else { throw new MojoException("The packaging plugin for this project did not assign " + "a main file to the project but it has attachments. Change packaging to 'pom'."); @@ -279,7 +281,7 @@ private ArtifactInstallerRequest processProject(Project project) { } } - return ArtifactInstallerRequest.build(session, installables); + return ArtifactInstallerRequest.build(session, toInstall); } private boolean isValidPath(Artifact a) { diff --git a/src/test/java/org/apache/maven/plugins/install/InstallMojoTest.java b/src/test/java/org/apache/maven/plugins/install/InstallMojoTest.java index df6a474..6ba580f 100644 --- a/src/test/java/org/apache/maven/plugins/install/InstallMojoTest.java +++ b/src/test/java/org/apache/maven/plugins/install/InstallMojoTest.java @@ -154,6 +154,34 @@ void basicInstallWithAttachedArtifacts(InstallMojo mojo) throws Exception { artifacts.stream().map(Artifact::key).toList()); } + @Test + @InjectMojo(goal = "install") + @MojoParameter(name = "installAtEnd", value = "false") + @MojoParameter(name = "allowIncompleteProjects", value = "true") + void mainArtifactWithDirectoryPathIsSkippedWhenAttachmentsExist(InstallMojo mojo) throws Exception { + // Module source hierarchy: the JAR plugin attaches one JAR per Java module and never assigns a + // file to the main artifact, whose path stays the (directory) build output. Install must install + // the POM + attachments and must NOT hand the directory to the installer (which fails with + // "is a folder"). So the main artifact is dropped from the install request. + assertNotNull(mojo); + Project project = (Project) getVariableValueFromObject(mojo, "project"); + projectManager.attachArtifact( + project, + new ProducedArtifactStub("org.apache.maven.test", "foo.bar", "", "1.0-SNAPSHOT", "jar"), + Paths.get(getBasedir(), "target/test-classes/unit/attached-artifact-test-1.0-SNAPSHOT.jar")); + artifactManager.setPath(project.getMainArtifact().get(), Paths.get(getBasedir(), "target/test-classes/unit")); + + ArtifactInstallerRequest request = execute(mojo); + + assertNotNull(request); + Collection artifacts = request.getArtifacts(); + assertEquals( + Arrays.asList( + "org.apache.maven.test:maven-install-test:pom:1.0-SNAPSHOT", + "org.apache.maven.test:foo.bar:jar:1.0-SNAPSHOT"), + artifacts.stream().map(Artifact::key).toList()); + } + @Test @InjectMojo(goal = "install") void installIfArtifactFileIsNull(InstallMojo mojo) throws Exception {