From 421ec24bb030b5d2a984c2d8613da107da1864a6 Mon Sep 17 00:00:00 2001 From: Sylwester Lachiewicz Date: Tue, 4 Aug 2026 13:50:07 +0200 Subject: [PATCH 1/3] Build against Maven 4.0.0-rc-6 The plugin still compiled against the 4.0.0-beta-3 API jars, but every mojo blew up at runtime on rc-6 with NoSuchMethodError: ProjectManager.getCompileSourceRoots(Project, ProjectScope) Dependencies: org.apache.maven:maven-api-meta -> org.apache.maven:maven-api-annotations org.apache.maven:maven-archiver -> org.apache.maven.shared:maven-archiver (4.0.0-beta-5) o.a.m.plugin-testing:maven-plugin-testing-harness -> org.apache.maven:maven-testing org.apache.maven:maven-api-impl (test) -> covered by maven-testing org.apache.maven:maven-core (test) -> covered by maven-testing com.google.inject:guice (test) -> dropped, comes in via maven-testing org.apache.maven:maven-impl (test) -> added, tests use DefaultSourceRoot API mapping, old -> new: ProjectManager.getCompileSourceRoots(p, scope) -> getEnabledSourceRoots(p, scope, Language.JAVA_FAMILY).map(SourceRoot::directory) ProjectManager.getResources(p, scope) -> getEnabledSourceRoots(p, scope, Language.RESOURCES) org.apache.maven.api.model.Resource (as the getResources() abstraction type) -> org.apache.maven.api.SourceRoot getDirectory() -> directory() (already a Path, no Paths.get needed) getIncludes() -> includes() getExcludes() -> excludes() getTargetPath() -> targetPath() (Optional) Project.getBuild().getResources(), used to locate maven-shared-archive-resources -> getEnabledSourceRoots(project, MAIN, Language.RESOURCES).map(SourceRoot::directory) The model getter is deprecated on rc-6 and would miss roots registered at runtime by maven-remote-resources-plugin; the ProjectManager view is a superset of it. ProjectManager.attachArtifact(Project, Artifact, Path) now takes a ProducedArtifact -> Session.createArtifact(...) -> Session.createProducedArtifact(...) org.apache.maven.archiver.* -> org.apache.maven.shared.archiver.* org.apache.maven.internal.impl.InternalSession -> org.apache.maven.impl.InternalSession org.apache.maven.api.plugin.testing.* -> org.apache.maven.testing.plugin.* The old annotations still exist as shims but MojoExtension no longer honours them, so @Basedir/@MojoParameter were silently ignored. Which files end up in the sources jar is unchanged: the resource includes, excludes and targetPath are read off SourceRoot instead of the model Resource, and excludeResources still short-circuits to an empty list. The mock ProjectManager in the mojo tests now answers getEnabledSourceRoots and falls back to src/main/java resp. src/test/java, because rc-6 no longer puts a default in the model. src/site/site.xml: dropped the workaround, redundant from rc-5 on as its own comment says. Co-Authored-By: Claude Opus 5 (1M context) --- pom.xml | 27 +++-------- .../plugins/source/AbstractSourceJarMojo.java | 46 ++++++++---------- .../plugins/source/SourceJarNoForkMojo.java | 14 ++++-- .../source/TestSourceJarNoForkMojo.java | 14 ++++-- src/site/site.xml | 13 ----- .../source/AbstractSourcePluginTestCase.java | 2 +- .../plugins/source/SourceJarMojoTest.java | 47 +++++++++++-------- .../plugins/source/TestSourceJarMojoTest.java | 47 +++++++++++-------- 8 files changed, 102 insertions(+), 108 deletions(-) diff --git a/pom.xml b/pom.xml index cf1e755..ee7fe3c 100644 --- a/pom.xml +++ b/pom.xml @@ -78,12 +78,10 @@ under the License. 17 - 4.0.0-beta-3 + 4.0.0-rc-6 - 6.0.0 - 4.0.0-beta-1 + 4.0.0-beta-5 4.0.0-beta-1 - 4.0.0-beta-1 5.23.0 4.12.0 ${mavenPluginPluginVersion} @@ -126,13 +124,13 @@ under the License. org.apache.maven - maven-api-meta + maven-api-annotations ${mavenVersion} provided - org.apache.maven + org.apache.maven.shared maven-archiver ${mavenArchiverVersion} @@ -146,30 +144,19 @@ under the License. plexus-utils - - org.apache.maven.plugin-testing - maven-plugin-testing-harness - ${mavenPluginTestingVersion} - test - org.apache.maven - maven-core + maven-testing ${mavenVersion} test + org.apache.maven - maven-api-impl + maven-impl ${mavenVersion} test - - com.google.inject - guice - ${guiceVersion} - test - org.junit.jupiter junit-jupiter-api diff --git a/src/main/java/org/apache/maven/plugins/source/AbstractSourceJarMojo.java b/src/main/java/org/apache/maven/plugins/source/AbstractSourceJarMojo.java index 802a1fd..0cd632d 100644 --- a/src/main/java/org/apache/maven/plugins/source/AbstractSourceJarMojo.java +++ b/src/main/java/org/apache/maven/plugins/source/AbstractSourceJarMojo.java @@ -21,7 +21,6 @@ import java.io.File; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -29,19 +28,22 @@ import java.util.Objects; import org.apache.maven.api.Artifact; +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.Session; +import org.apache.maven.api.SourceRoot; import org.apache.maven.api.Type; import org.apache.maven.api.di.Inject; -import org.apache.maven.api.model.Resource; import org.apache.maven.api.plugin.Log; import org.apache.maven.api.plugin.Mojo; import org.apache.maven.api.plugin.MojoException; import org.apache.maven.api.plugin.annotations.Parameter; import org.apache.maven.api.services.ArtifactManager; import org.apache.maven.api.services.ProjectManager; -import org.apache.maven.archiver.MavenArchiveConfiguration; -import org.apache.maven.archiver.MavenArchiver; +import org.apache.maven.shared.archiver.MavenArchiveConfiguration; +import org.apache.maven.shared.archiver.MavenArchiver; import org.codehaus.plexus.archiver.Archiver; import org.codehaus.plexus.archiver.ArchiverException; import org.codehaus.plexus.archiver.jar.JarArchiver; @@ -258,10 +260,10 @@ protected void doExecute() { /** * @param p {@link Project} not null - * @return the compile or test resources + * @return the compile or test resource roots * @throws MojoException in case of an error. */ - protected abstract List getResources(Project p) throws MojoException; + protected abstract List getResources(Project p) throws MojoException; /** * @param p {@link Project} @@ -321,7 +323,7 @@ protected void packageSources(List theProjects) throws MojoException { } if (attach) { - Artifact artifact = session.createArtifact( + ProducedArtifact artifact = session.createProducedArtifact( project.getGroupId(), project.getArtifactId(), project.getVersion(), @@ -385,23 +387,23 @@ protected void archiveProjectContent(Project project, Archiver archiver) throws } // MAPI: this should be taken from the resources plugin - for (Resource resource : getResources(project)) { + for (SourceRoot resource : getResources(project)) { - Path sourceDirectory = Paths.get(resource.getDirectory()); + Path sourceDirectory = resource.directory(); if (!Files.exists(sourceDirectory)) { continue; } - List resourceIncludes = resource.getIncludes(); + List resourceIncludes = resource.includes(); String[] combinedIncludes = getCombinedIncludes(resourceIncludes); - List resourceExcludes = resource.getExcludes(); + List resourceExcludes = resource.excludes(); String[] combinedExcludes = getCombinedExcludes(resourceExcludes); - String targetPath = resource.getTargetPath(); + String targetPath = resource.targetPath().map(Path::toString).orElse(null); if (targetPath != null) { if (!targetPath.trim().endsWith("/")) { targetPath += "/"; @@ -426,20 +428,12 @@ protected MavenArchiver createArchiver() throws MojoException { // configure for Reproducible Builds based on outputTimestamp value archiver.configureReproducibleBuild(outputTimestamp); - if (project.getBuild() != null) { - List resources = - project.getBuild().getResources(); - - for (org.apache.maven.api.model.Resource r : resources) { - if (r.getDirectory().endsWith("maven-shared-archive-resources")) { - addDirectory( - archiver.getArchiver(), - Paths.get(r.getDirectory()), - getCombinedIncludes(null), - getCombinedExcludes(null)); - } - } - } + projectManager + .getEnabledSourceRoots(project, ProjectScope.MAIN, Language.RESOURCES) + .map(SourceRoot::directory) + .filter(directory -> directory.endsWith("maven-shared-archive-resources")) + .forEach(directory -> addDirectory( + archiver.getArchiver(), directory, getCombinedIncludes(null), getCombinedExcludes(null))); return archiver; } diff --git a/src/main/java/org/apache/maven/plugins/source/SourceJarNoForkMojo.java b/src/main/java/org/apache/maven/plugins/source/SourceJarNoForkMojo.java index 8495998..94933fc 100644 --- a/src/main/java/org/apache/maven/plugins/source/SourceJarNoForkMojo.java +++ b/src/main/java/org/apache/maven/plugins/source/SourceJarNoForkMojo.java @@ -22,9 +22,10 @@ import java.util.Collections; import java.util.List; +import org.apache.maven.api.Language; import org.apache.maven.api.Project; import org.apache.maven.api.ProjectScope; -import org.apache.maven.api.model.Resource; +import org.apache.maven.api.SourceRoot; import org.apache.maven.api.plugin.annotations.Mojo; import org.apache.maven.api.plugin.annotations.Parameter; @@ -47,18 +48,23 @@ public class SourceJarNoForkMojo extends AbstractSourceJarMojo { * {@inheritDoc} */ protected List getSources(Project p) { - return projectManager.getCompileSourceRoots(p, ProjectScope.MAIN); + return projectManager + .getEnabledSourceRoots(p, ProjectScope.MAIN, Language.JAVA_FAMILY) + .map(SourceRoot::directory) + .toList(); } /** * {@inheritDoc} */ - protected List getResources(Project p) { + protected List getResources(Project p) { if (excludeResources) { return Collections.emptyList(); } - return projectManager.getResources(p, ProjectScope.MAIN); + return projectManager + .getEnabledSourceRoots(p, ProjectScope.MAIN, Language.RESOURCES) + .toList(); } /** diff --git a/src/main/java/org/apache/maven/plugins/source/TestSourceJarNoForkMojo.java b/src/main/java/org/apache/maven/plugins/source/TestSourceJarNoForkMojo.java index 7763e83..5f3b5f9 100644 --- a/src/main/java/org/apache/maven/plugins/source/TestSourceJarNoForkMojo.java +++ b/src/main/java/org/apache/maven/plugins/source/TestSourceJarNoForkMojo.java @@ -22,9 +22,10 @@ import java.util.Collections; import java.util.List; +import org.apache.maven.api.Language; import org.apache.maven.api.Project; import org.apache.maven.api.ProjectScope; -import org.apache.maven.api.model.Resource; +import org.apache.maven.api.SourceRoot; import org.apache.maven.api.plugin.annotations.Mojo; import org.apache.maven.api.plugin.annotations.Parameter; @@ -47,18 +48,23 @@ public class TestSourceJarNoForkMojo extends AbstractSourceJarMojo { * {@inheritDoc} */ protected List getSources(Project p) { - return projectManager.getCompileSourceRoots(p, ProjectScope.TEST); + return projectManager + .getEnabledSourceRoots(p, ProjectScope.TEST, Language.JAVA_FAMILY) + .map(SourceRoot::directory) + .toList(); } /** * {@inheritDoc} */ - protected List getResources(Project p) { + protected List getResources(Project p) { if (excludeResources) { return Collections.emptyList(); } - return projectManager.getResources(p, ProjectScope.TEST); + return projectManager + .getEnabledSourceRoots(p, ProjectScope.TEST, Language.RESOURCES) + .toList(); } /** diff --git a/src/site/site.xml b/src/site/site.xml index 9e0e193..f631ee6 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -21,19 +21,6 @@ under the License. - - - org.apache.maven.skins - maven-fluido-skin - 2.1.0 - diff --git a/src/test/java/org/apache/maven/plugins/source/AbstractSourcePluginTestCase.java b/src/test/java/org/apache/maven/plugins/source/AbstractSourcePluginTestCase.java index 400230c..fa10a78 100644 --- a/src/test/java/org/apache/maven/plugins/source/AbstractSourcePluginTestCase.java +++ b/src/test/java/org/apache/maven/plugins/source/AbstractSourcePluginTestCase.java @@ -27,7 +27,7 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipFile; -import static org.apache.maven.api.plugin.testing.MojoExtension.getBasedir; +import static org.apache.maven.testing.plugin.MojoExtension.getBasedir; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; diff --git a/src/test/java/org/apache/maven/plugins/source/SourceJarMojoTest.java b/src/test/java/org/apache/maven/plugins/source/SourceJarMojoTest.java index faed337..2014b7c 100644 --- a/src/test/java/org/apache/maven/plugins/source/SourceJarMojoTest.java +++ b/src/test/java/org/apache/maven/plugins/source/SourceJarMojoTest.java @@ -20,21 +20,23 @@ import java.io.File; import java.nio.file.Paths; -import java.util.Collections; +import java.util.stream.Stream; +import org.apache.maven.api.Language; import org.apache.maven.api.Project; import org.apache.maven.api.ProjectScope; import org.apache.maven.api.di.Provides; -import org.apache.maven.api.plugin.testing.Basedir; -import org.apache.maven.api.plugin.testing.InjectMojo; -import org.apache.maven.api.plugin.testing.MojoParameter; -import org.apache.maven.api.plugin.testing.MojoTest; -import org.apache.maven.api.plugin.testing.stubs.SessionMock; import org.apache.maven.api.services.ProjectManager; -import org.apache.maven.internal.impl.InternalSession; +import org.apache.maven.impl.DefaultSourceRoot; +import org.apache.maven.impl.InternalSession; +import org.apache.maven.testing.plugin.Basedir; +import org.apache.maven.testing.plugin.InjectMojo; +import org.apache.maven.testing.plugin.MojoParameter; +import org.apache.maven.testing.plugin.MojoTest; +import org.apache.maven.testing.plugin.stubs.SessionMock; import org.junit.jupiter.api.Test; -import static org.apache.maven.api.plugin.testing.MojoExtension.getBasedir; +import static org.apache.maven.testing.plugin.MojoExtension.getBasedir; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -182,18 +184,23 @@ InternalSession createSession() { InternalSession session = SessionMock.getMockSession("target/local-repo"); ProjectManager projectManager = mock(ProjectManager.class); when(session.getService(ProjectManager.class)).thenReturn(projectManager); - when(projectManager.getCompileSourceRoots(any(), eq(ProjectScope.MAIN))).thenAnswer(iom -> { - Project p = iom.getArgument(0, Project.class); - return Collections.singletonList( - Paths.get(getBasedir()).resolve(p.getModel().getBuild().getSourceDirectory())); - }); - when(projectManager.getResources(any(), eq(ProjectScope.MAIN))).thenAnswer(iom -> { - Project p = iom.getArgument(0, Project.class); - return p.getBuild().getResources().stream() - .map(r -> r.withDirectory( - Paths.get(getBasedir()).resolve(r.getDirectory()).toString())) - .toList(); - }); + when(projectManager.getEnabledSourceRoots(any(), eq(ProjectScope.MAIN), eq(Language.JAVA_FAMILY))) + .thenAnswer(iom -> { + Project p = iom.getArgument(0, Project.class); + // since 4.0.0-rc-x the model no longer carries a default + String sourceDirectory = p.getModel().getBuild().getSourceDirectory(); + return Stream.of(new DefaultSourceRoot( + ProjectScope.MAIN, + Language.JAVA_FAMILY, + Paths.get(getBasedir()) + .resolve(sourceDirectory != null ? sourceDirectory : "src/main/java"))); + }); + when(projectManager.getEnabledSourceRoots(any(), eq(ProjectScope.MAIN), eq(Language.RESOURCES))) + .thenAnswer(iom -> { + Project p = iom.getArgument(0, Project.class); + return p.getBuild().getResources().stream() + .map(r -> new DefaultSourceRoot(Paths.get(getBasedir()), ProjectScope.MAIN, r)); + }); return session; } } diff --git a/src/test/java/org/apache/maven/plugins/source/TestSourceJarMojoTest.java b/src/test/java/org/apache/maven/plugins/source/TestSourceJarMojoTest.java index e0bc5fe..01b2363 100644 --- a/src/test/java/org/apache/maven/plugins/source/TestSourceJarMojoTest.java +++ b/src/test/java/org/apache/maven/plugins/source/TestSourceJarMojoTest.java @@ -20,21 +20,23 @@ import java.io.File; import java.nio.file.Paths; -import java.util.Collections; +import java.util.stream.Stream; +import org.apache.maven.api.Language; import org.apache.maven.api.Project; import org.apache.maven.api.ProjectScope; import org.apache.maven.api.di.Provides; -import org.apache.maven.api.plugin.testing.Basedir; -import org.apache.maven.api.plugin.testing.InjectMojo; -import org.apache.maven.api.plugin.testing.MojoParameter; -import org.apache.maven.api.plugin.testing.MojoTest; -import org.apache.maven.api.plugin.testing.stubs.SessionMock; import org.apache.maven.api.services.ProjectManager; -import org.apache.maven.internal.impl.InternalSession; +import org.apache.maven.impl.DefaultSourceRoot; +import org.apache.maven.impl.InternalSession; +import org.apache.maven.testing.plugin.Basedir; +import org.apache.maven.testing.plugin.InjectMojo; +import org.apache.maven.testing.plugin.MojoParameter; +import org.apache.maven.testing.plugin.MojoTest; +import org.apache.maven.testing.plugin.stubs.SessionMock; import org.junit.jupiter.api.Test; -import static org.apache.maven.api.plugin.testing.MojoExtension.getBasedir; +import static org.apache.maven.testing.plugin.MojoExtension.getBasedir; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -138,18 +140,23 @@ InternalSession createSession() { InternalSession session = SessionMock.getMockSession("target/local-repo"); ProjectManager projectManager = mock(ProjectManager.class); when(session.getService(ProjectManager.class)).thenReturn(projectManager); - when(projectManager.getCompileSourceRoots(any(), eq(ProjectScope.TEST))).thenAnswer(iom -> { - Project p = iom.getArgument(0, Project.class); - return Collections.singletonList( - Paths.get(getBasedir()).resolve(p.getModel().getBuild().getTestSourceDirectory())); - }); - when(projectManager.getResources(any(), eq(ProjectScope.TEST))).thenAnswer(iom -> { - Project p = iom.getArgument(0, Project.class); - return p.getBuild().getTestResources().stream() - .map(r -> r.withDirectory( - Paths.get(getBasedir()).resolve(r.getDirectory()).toString())) - .toList(); - }); + when(projectManager.getEnabledSourceRoots(any(), eq(ProjectScope.TEST), eq(Language.JAVA_FAMILY))) + .thenAnswer(iom -> { + Project p = iom.getArgument(0, Project.class); + // since 4.0.0-rc-x the model no longer carries a default + String testSourceDirectory = p.getModel().getBuild().getTestSourceDirectory(); + return Stream.of(new DefaultSourceRoot( + ProjectScope.TEST, + Language.JAVA_FAMILY, + Paths.get(getBasedir()) + .resolve(testSourceDirectory != null ? testSourceDirectory : "src/test/java"))); + }); + when(projectManager.getEnabledSourceRoots(any(), eq(ProjectScope.TEST), eq(Language.RESOURCES))) + .thenAnswer(iom -> { + Project p = iom.getArgument(0, Project.class); + return p.getBuild().getTestResources().stream() + .map(r -> new DefaultSourceRoot(Paths.get(getBasedir()), ProjectScope.TEST, r)); + }); return session; } } From a05c3c8963786da62957c4a1e2090c283bb67bda Mon Sep 17 00:00:00 2001 From: Sylwester Lachiewicz Date: Tue, 4 Aug 2026 14:00:53 +0200 Subject: [PATCH 2/3] Fix the MSOURCES-140 assertion so the IT can pass The regex is a Groovy slashy literal containing a stray Java concatenation: /... already attached to target" + File.separator + "jar-no-fork-...jar .../ Slashy strings do not evaluate that, so `" + File.separator + "` stays in the pattern verbatim and it cannot match on any platform. It went unnoticed because invoker.properties requires 4.0.0-beta-4+, so the IT was skipped on the beta-3 this plugin used to build against. The mojo does emit exactly what the IT is looking for: [INFO] Artifact ...:jar-no-fork:jar:sources:1.0-SNAPSHOT already attached to target/jar-no-fork-1.0-SNAPSHOT-sources.jar: ignoring same re-attach (same artifact, same file) With the separator matched as a character class the IT passes. Co-Authored-By: Claude Opus 5 (1M context) --- src/it/MSOURCES-140/verify.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/it/MSOURCES-140/verify.groovy b/src/it/MSOURCES-140/verify.groovy index e9b0fd2..02ef151 100644 --- a/src/it/MSOURCES-140/verify.groovy +++ b/src/it/MSOURCES-140/verify.groovy @@ -19,4 +19,4 @@ File buildLog = new File( basedir, 'build.log' ) -assert buildLog.text =~ /\[INFO\] Artifact org.apache.maven.its.sources:jar-no-fork:jar:sources:1.0-SNAPSHOT already attached to target" + File.separator + "jar-no-fork-1.0-SNAPSHOT-sources.jar: ignoring same re-attach \(same artifact, same file\)/ +assert buildLog.text =~ /\[INFO\] Artifact org.apache.maven.its.sources:jar-no-fork:jar:sources:1.0-SNAPSHOT already attached to target[\/\\]jar-no-fork-1.0-SNAPSHOT-sources.jar: ignoring same re-attach \(same artifact, same file\)/ From bbcb8cfa05411e38417256e4487c36f044c1a0c5 Mon Sep 17 00:00:00 2001 From: Sylwester Lachiewicz Date: Tue, 4 Aug 2026 16:54:14 +0200 Subject: [PATCH 3/3] Pin the Verify workflow to rc-6 as well The pom moved to 4.0.0-rc-6 but the workflow still asked for 4.0.0-beta-3, so CI built the migrated code against the version it was migrated away from and failed. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/maven-verify.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/maven-verify.yml b/.github/workflows/maven-verify.yml index 2d51244..a9a6244 100644 --- a/.github/workflows/maven-verify.yml +++ b/.github/workflows/maven-verify.yml @@ -27,4 +27,4 @@ jobs: uses: apache/maven-gh-actions-shared/.github/workflows/maven-verify.yml@v5 with: maven4-build: true - maven4-version: '4.0.0-beta-3' # same as in project + maven4-version: '4.0.0-rc-6' # the same as used in project