From 7f44b2fce8bc487cb39269f7798857ee565aaf07 Mon Sep 17 00:00:00 2001 From: Vaclav Haisman Date: Sun, 2 Aug 2026 10:03:34 +0200 Subject: [PATCH 1/6] [MDEP-858] Migrate dependency:get to Maven Resolver Replace the Maven Artifact Transfer resolvers used by dependency:get while preserving repository and artifact-type behavior. Keep the component dependency for goals that still use it. Fixes #1355. --- pom.xml | 8 +- .../maven/plugins/dependency/GetMojo.java | 180 ++++-------------- .../dependency/utils/ResolverUtil.java | 5 +- .../maven/plugins/dependency/TestGetMojo.java | 167 ++++++++-------- .../dependency/utils/ResolverUtilTest.java | 29 +++ 5 files changed, 149 insertions(+), 240 deletions(-) diff --git a/pom.xml b/pom.xml index af6f70762..23b9b29d4 100644 --- a/pom.xml +++ b/pom.xml @@ -148,13 +148,6 @@ under the License. ${mavenVersion} provided - - org.apache.maven - maven-settings - ${mavenVersion} - provided - - eu.maveniverse.maven.domtrip @@ -396,6 +389,7 @@ under the License. ${jettyVersion} test + org.slf4j slf4j-simple diff --git a/src/main/java/org/apache/maven/plugins/dependency/GetMojo.java b/src/main/java/org/apache/maven/plugins/dependency/GetMojo.java index 48d37908d..ba6909207 100644 --- a/src/main/java/org/apache/maven/plugins/dependency/GetMojo.java +++ b/src/main/java/org/apache/maven/plugins/dependency/GetMojo.java @@ -20,36 +20,22 @@ import javax.inject.Inject; -import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import org.apache.maven.artifact.handler.ArtifactHandler; -import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager; -import org.apache.maven.artifact.repository.ArtifactRepository; -import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy; -import org.apache.maven.artifact.repository.MavenArtifactRepository; -import org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout; -import org.apache.maven.execution.MavenSession; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; -import org.apache.maven.project.DefaultProjectBuildingRequest; -import org.apache.maven.project.ProjectBuildingRequest; -import org.apache.maven.repository.RepositorySystem; -import org.apache.maven.settings.Settings; -import org.apache.maven.shared.transfer.artifact.ArtifactCoordinate; -import org.apache.maven.shared.transfer.artifact.DefaultArtifactCoordinate; -import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolver; -import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolverException; -import org.apache.maven.shared.transfer.dependencies.DefaultDependableCoordinate; -import org.apache.maven.shared.transfer.dependencies.DependableCoordinate; -import org.apache.maven.shared.transfer.dependencies.resolve.DependencyResolver; -import org.apache.maven.shared.transfer.dependencies.resolve.DependencyResolverException; +import org.apache.maven.plugins.dependency.utils.ParamArtifact; +import org.apache.maven.plugins.dependency.utils.ResolverUtil; +import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.resolution.ArtifactDescriptorException; +import org.eclipse.aether.resolution.ArtifactResolutionException; +import org.eclipse.aether.resolution.DependencyResolutionException; /** * Resolves a single artifact, eventually transitively, from the specified remote repositories. Caveat: will always @@ -57,31 +43,13 @@ */ @Mojo(name = "get", requiresProject = false, threadSafe = true) public class GetMojo extends AbstractMojo { - private static final Pattern ALT_REPO_SYNTAX_PATTERN = Pattern.compile("(.+)::(.*)::(.+)"); + private final ResolverUtil resolverUtil; - private final MavenSession session; - - private final ArtifactResolver artifactResolver; - - private final DependencyResolver dependencyResolver; - - private final ArtifactHandlerManager artifactHandlerManager; - - /** - * Map that contains the layouts. - */ - private final Map repositoryLayouts; - - /** - * The repository system. - */ - private final RepositorySystem repositorySystem; - - private final DefaultDependableCoordinate coordinate = new DefaultDependableCoordinate(); + private final ParamArtifact coordinate = new ParamArtifact(); /** - * Repositories in the format id::[layout]::url or just url, separated by comma. i.e. - * central::default::https://repo.maven.apache.org/maven2,myrepo::::https://repo.acme.com,https://repo.acme2.com. + * Repositories in the format {@code id::[layout::]url} or just URLs, separated by comma. That is, + * {@code central::default::https://repo.maven.apache.org/maven2,myrepo::https://repo.acme.com,https://repo.acme2.com}. */ @Parameter(property = "remoteRepositories") private String remoteRepositories; @@ -92,9 +60,6 @@ public class GetMojo extends AbstractMojo { @Parameter(property = "artifact") private String artifact; - @Parameter(defaultValue = "${project.remoteArtifactRepositories}", readonly = true, required = true) - private List pomRemoteRepositories; - /** * Resolve transitively, retrieving the specified artifact and all of its dependencies. */ @@ -110,19 +75,8 @@ public class GetMojo extends AbstractMojo { private boolean skip; @Inject - public GetMojo( - MavenSession session, - ArtifactResolver artifactResolver, - DependencyResolver dependencyResolver, - ArtifactHandlerManager artifactHandlerManager, - Map repositoryLayouts, - RepositorySystem repositorySystem) { - this.session = session; - this.artifactResolver = artifactResolver; - this.dependencyResolver = dependencyResolver; - this.artifactHandlerManager = artifactHandlerManager; - this.repositoryLayouts = repositoryLayouts; - this.repositorySystem = repositorySystem; + public GetMojo(ResolverUtil resolverUtil) { + this.resolverUtil = resolverUtil; } @Override @@ -132,10 +86,6 @@ public void execute() throws MojoExecutionException, MojoFailureException { return; } - if (coordinate.getArtifactId() == null && artifact == null) { - throw new MojoFailureException("You must specify an artifact, " - + "e.g. -Dartifact=org.apache.maven.plugins:maven-downloader-plugin:1.0"); - } if (artifact != null) { String[] tokens = artifact.split(":"); if (tokens.length < 3 || tokens.length > 5) { @@ -146,99 +96,43 @@ public void execute() throws MojoExecutionException, MojoFailureException { coordinate.setArtifactId(tokens[1]); coordinate.setVersion(tokens[2]); if (tokens.length >= 4) { - coordinate.setType(tokens[3]); + coordinate.setPackaging(tokens[3]); } if (tokens.length == 5) { coordinate.setClassifier(tokens[4]); } } - ArtifactRepositoryPolicy always = new ArtifactRepositoryPolicy( - true, ArtifactRepositoryPolicy.UPDATE_POLICY_ALWAYS, ArtifactRepositoryPolicy.CHECKSUM_POLICY_WARN); - - List repoList = new ArrayList<>(); - - if (pomRemoteRepositories != null) { - repoList.addAll(pomRemoteRepositories); + if (!coordinate.isDataSet()) { + throw new MojoFailureException("You must specify an artifact, " + + "e.g. -Dartifact=org.apache.maven.plugins:maven-downloader-plugin:1.0"); } - if (remoteRepositories != null) { - // Use the same format as in the deploy plugin id::layout::url - String[] repos = remoteRepositories.split(","); - for (String repo : repos) { - repoList.add(parseRepository(repo, always)); - } + List resolverRepositories; + try { + resolverRepositories = resolverUtil.remoteRepositories( + remoteRepositories == null + ? Collections.emptyList() + : Arrays.asList(remoteRepositories.split(","))); + } catch (IllegalArgumentException e) { + throw new MojoFailureException("Invalid remote repository: " + e.getMessage(), e); } try { - ProjectBuildingRequest buildingRequest = - new DefaultProjectBuildingRequest(session.getProjectBuildingRequest()); - - Settings settings = session.getSettings(); - repositorySystem.injectMirror(repoList, settings.getMirrors()); - repositorySystem.injectProxy(repoList, settings.getProxies()); - repositorySystem.injectAuthentication(repoList, settings.getServers()); - - buildingRequest.setRemoteRepositories(repoList); + Artifact targetArtifact = resolverUtil.createArtifactFromParams(coordinate); if (transitive) { - getLog().info("Resolving " + coordinate + " with transitive dependencies"); - dependencyResolver.resolveDependencies(buildingRequest, coordinate, null); + getLog().info("Resolving " + targetArtifact + " with transitive dependencies"); + resolverUtil.resolveDependencies(targetArtifact, resolverRepositories); } else { - getLog().info("Resolving " + coordinate); - artifactResolver.resolveArtifact(buildingRequest, toArtifactCoordinate(coordinate)); + getLog().info("Resolving " + targetArtifact); + resolverUtil.resolveArtifact(targetArtifact, resolverRepositories); } - } catch (ArtifactResolverException | DependencyResolverException e) { + } catch (ArtifactDescriptorException | ArtifactResolutionException | DependencyResolutionException e) { throw new MojoExecutionException("Couldn't download artifact: " + e.getMessage(), e); } } - private ArtifactCoordinate toArtifactCoordinate(DependableCoordinate dependableCoordinate) { - ArtifactHandler artifactHandler = artifactHandlerManager.getArtifactHandler(dependableCoordinate.getType()); - DefaultArtifactCoordinate artifactCoordinate = new DefaultArtifactCoordinate(); - artifactCoordinate.setGroupId(dependableCoordinate.getGroupId()); - artifactCoordinate.setArtifactId(dependableCoordinate.getArtifactId()); - artifactCoordinate.setVersion(dependableCoordinate.getVersion()); - artifactCoordinate.setClassifier(dependableCoordinate.getClassifier()); - artifactCoordinate.setExtension(artifactHandler.getExtension()); - return artifactCoordinate; - } - - ArtifactRepository parseRepository(String repo, ArtifactRepositoryPolicy policy) throws MojoFailureException { - // if it's a simple url - String id = "temp"; - ArtifactRepositoryLayout layout = getLayout("default"); - String url = repo; - - // if it's an extended repo URL of the form id::layout::url - if (repo.contains("::")) { - Matcher matcher = ALT_REPO_SYNTAX_PATTERN.matcher(repo); - if (!matcher.matches()) { - throw new MojoFailureException( - repo, - "Invalid syntax for repository: " + repo, - "Invalid syntax for repository. Use \"id::layout::url\" or \"URL\"."); - } - - id = matcher.group(1).trim(); - if (matcher.group(2) != null && !matcher.group(2).isEmpty()) { - layout = getLayout(matcher.group(2).trim()); - } - url = matcher.group(3).trim(); - } - return new MavenArtifactRepository(id, url, layout, policy, policy); - } - - private ArtifactRepositoryLayout getLayout(String id) throws MojoFailureException { - ArtifactRepositoryLayout layout = repositoryLayouts.get(id); - - if (layout == null) { - throw new MojoFailureException(id, "Invalid repository layout", "Invalid repository layout: " + id); - } - - return layout; - } - /** * @return {@link #skip} */ @@ -277,7 +171,8 @@ public void setVersion(String version) { } /** - * The classifier of the artifact to resolve. Ignored if {@link #artifact} is used. + * The classifier of the artifact to resolve. When {@link #artifact} is used, this value supplies the classifier if + * it is omitted from the artifact string. * * @param classifier the classifier to be used * @since 2.3 @@ -288,12 +183,13 @@ public void setClassifier(String classifier) { } /** - * The packaging of the artifact to resolve. Ignored if {@link #artifact} is used. + * The packaging of the artifact to resolve. When {@link #artifact} is used, this value supplies the packaging if it + * is omitted from the artifact string. * * @param type packaging */ @Parameter(property = "packaging", defaultValue = "jar") public void setPackaging(String type) { - this.coordinate.setType(type); + this.coordinate.setPackaging(type); } } diff --git a/src/main/java/org/apache/maven/plugins/dependency/utils/ResolverUtil.java b/src/main/java/org/apache/maven/plugins/dependency/utils/ResolverUtil.java index 60ca42c97..b622fb485 100644 --- a/src/main/java/org/apache/maven/plugins/dependency/utils/ResolverUtil.java +++ b/src/main/java/org/apache/maven/plugins/dependency/utils/ResolverUtil.java @@ -48,6 +48,7 @@ import org.eclipse.aether.artifact.ArtifactType; import org.eclipse.aether.artifact.ArtifactTypeRegistry; import org.eclipse.aether.artifact.DefaultArtifact; +import org.eclipse.aether.artifact.DefaultArtifactType; import org.eclipse.aether.collection.CollectRequest; import org.eclipse.aether.collection.CollectResult; import org.eclipse.aether.collection.DependencyCollectionException; @@ -363,9 +364,11 @@ private Artifact createArtifactFromString(String artifact) { } private ArtifactType getArtifactType(String packaging) { + String type = packaging != null ? packaging : "jar"; ArtifactTypeRegistry artifactTypeRegistry = mavenSessionProvider.get().getRepositorySession().getArtifactTypeRegistry(); - return artifactTypeRegistry.get(packaging != null ? packaging : "jar"); + ArtifactType artifactType = artifactTypeRegistry.get(type); + return artifactType != null ? artifactType : new DefaultArtifactType(type); } /** diff --git a/src/test/java/org/apache/maven/plugins/dependency/TestGetMojo.java b/src/test/java/org/apache/maven/plugins/dependency/TestGetMojo.java index 881679a81..a0562c85f 100644 --- a/src/test/java/org/apache/maven/plugins/dependency/TestGetMojo.java +++ b/src/test/java/org/apache/maven/plugins/dependency/TestGetMojo.java @@ -29,19 +29,19 @@ 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.artifact.repository.ArtifactRepository; -import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy; -import org.apache.maven.artifact.repository.layout.DefaultRepositoryLayout; import org.apache.maven.execution.MavenSession; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.dependency.utils.ParamArtifact; +import org.apache.maven.plugins.dependency.utils.ResolverUtil; import org.apache.maven.project.DefaultProjectBuildingRequest; -import org.apache.maven.settings.Proxy; -import org.apache.maven.settings.Server; -import org.apache.maven.settings.Settings; import org.eclipse.aether.DefaultRepositorySystemSession; import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.repository.LocalRepository; +import org.eclipse.aether.repository.Proxy; +import org.eclipse.aether.util.repository.AuthenticationBuilder; +import org.eclipse.aether.util.repository.DefaultAuthenticationSelector; +import org.eclipse.aether.util.repository.DefaultProxySelector; import org.eclipse.jetty.security.ConstraintMapping; import org.eclipse.jetty.security.ConstraintSecurityHandler; import org.eclipse.jetty.security.HashLoginService; @@ -51,16 +51,18 @@ import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ResourceHandler; import org.eclipse.jetty.util.security.Constraint; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import org.mockito.ArgumentCaptor; import static org.apache.maven.api.plugin.testing.MojoExtension.getTestPath; import static org.apache.maven.api.plugin.testing.MojoExtension.setVariableValueToObject; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @MojoTest(realRepositorySession = true) @@ -78,17 +80,7 @@ class TestGetMojo { @TempDir private Path isolatedLocalRepository; - @BeforeEach - void setUp() { - Settings settings = new Settings(); - when(session.getSettings()).thenReturn(settings); - - Server server = new Server(); - server.setId("myserver"); - server.setUsername("foo"); - server.setPassword("bar"); - settings.addServer(server); - } + private DefaultRepositorySystemSession repositorySession; /** * Test transitive parameter @@ -99,10 +91,6 @@ void setUp() { @InjectMojo(goal = "get") @MojoParameter(name = "transitive", value = "false") void testTransitive(GetMojo mojo) throws Exception { - DefaultProjectBuildingRequest pbr = new DefaultProjectBuildingRequest(); - pbr.setRepositorySession(session.getRepositorySession()); - when(session.getProjectBuildingRequest()).thenReturn(pbr); - mojo.setGroupId("org.apache.maven"); mojo.setArtifactId("maven-model"); mojo.setVersion("2.0.9"); @@ -120,12 +108,8 @@ void testTransitive(GetMojo mojo) throws Exception { @MojoParameter( name = "remoteRepositories", value = - "central::default::https://repo.maven.apache.org/maven2,central::::https://repo.maven.apache.org/maven2,https://repo.maven.apache.org/maven2") + "central::default::https://repo.maven.apache.org/maven2,central::::https://repo.maven.apache.org/maven2,central::https://repo.maven.apache.org/maven2,https://repo.maven.apache.org/maven2") void testRemoteRepositories(GetMojo mojo) throws Exception { - DefaultProjectBuildingRequest pbr = new DefaultProjectBuildingRequest(); - pbr.setRepositorySession(session.getRepositorySession()); - when(session.getProjectBuildingRequest()).thenReturn(pbr); - mojo.setGroupId("org.apache.maven"); mojo.setArtifactId("maven-model"); mojo.setVersion("2.0.9"); @@ -158,7 +142,7 @@ void testRemoteRepositoriesAuthentication(GetMojo mojo) throws Exception { } /** - * Test that an active proxy from the settings is applied to the repositories given by the + * Test that an active proxy from the repository session is applied to the repositories given by the * remoteRepositories parameter. The proxy points at a host that cannot resolve, so a successful * resolution would mean the proxy was never applied. */ @@ -171,8 +155,8 @@ void testRemoteRepositoriesProxy(GetMojo mojo) throws Exception { setVariableValueToObject(mojo, "remoteRepositories", "myserver::default::" + serverUrl(server)); - session.getSettings().addProxy(createProxy(null)); useIsolatedLocalRepository(); + repositorySession.setProxySelector(createProxySelector(null)); mojo.setGroupId("test"); mojo.setArtifactId("test"); @@ -205,8 +189,9 @@ void testRemoteRepositoriesNonProxyHosts(GetMojo mojo) throws Exception { setVariableValueToObject(mojo, "remoteRepositories", "myserver::default::" + url); - session.getSettings().addProxy(createProxy(URI.create(url).getHost())); useIsolatedLocalRepository(); + repositorySession.setProxySelector( + createProxySelector(URI.create(url).getHost())); mojo.setGroupId("test"); mojo.setArtifactId("test"); @@ -218,13 +203,67 @@ void testRemoteRepositoriesNonProxyHosts(GetMojo mojo) throws Exception { } } + @Test + void testVersionIsRequired() { + GetMojo mojo = new GetMojo(mock(ResolverUtil.class)); + mojo.setGroupId("org.apache.maven"); + mojo.setArtifactId("maven-model"); + + MojoFailureException exception = assertThrows(MojoFailureException.class, mojo::execute); + + assertEquals( + "You must specify an artifact, " + + "e.g. -Dartifact=org.apache.maven.plugins:maven-downloader-plugin:1.0", + exception.getMessage()); + } + + @Test + void testArtifactRetainsSeparatePackagingAndClassifier() throws Exception { + ResolverUtil resolverUtil = mock(ResolverUtil.class); + when(resolverUtil.remoteRepositories(anyList())).thenReturn(Collections.emptyList()); + GetMojo mojo = new GetMojo(resolverUtil); + setVariableValueToObject(mojo, "artifact", "org.apache.maven:maven-model:2.0.9"); + mojo.setPackaging("test-jar"); + mojo.setClassifier("tests"); + + mojo.execute(); + + ArgumentCaptor coordinate = ArgumentCaptor.forClass(ParamArtifact.class); + verify(resolverUtil).createArtifactFromParams(coordinate.capture()); + assertEquals("test-jar", coordinate.getValue().getPackaging()); + assertEquals("tests", coordinate.getValue().getClassifier()); + } + + @Test + void testArtifactPackagingAndClassifierOverrideSeparateParameters() throws Exception { + ResolverUtil resolverUtil = mock(ResolverUtil.class); + when(resolverUtil.remoteRepositories(anyList())).thenReturn(Collections.emptyList()); + GetMojo mojo = new GetMojo(resolverUtil); + setVariableValueToObject(mojo, "artifact", "org.apache.maven:maven-model:2.0.9:jar:sources"); + mojo.setPackaging("test-jar"); + mojo.setClassifier("tests"); + + mojo.execute(); + + ArgumentCaptor coordinate = ArgumentCaptor.forClass(ParamArtifact.class); + verify(resolverUtil).createArtifactFromParams(coordinate.capture()); + assertEquals("jar", coordinate.getValue().getPackaging()); + assertEquals("sources", coordinate.getValue().getClassifier()); + } + /** - * Points the mojo at an empty local repository, so that the tests above depend on the transfer actually + * Points the mojo at an empty local repository, so that repository tests depend on the transfer actually * happening rather than on what an earlier run left behind in the shared one. */ private void useIsolatedLocalRepository() { - DefaultRepositorySystemSession repositorySession = - new DefaultRepositorySystemSession(session.getRepositorySession()); + repositorySession = new DefaultRepositorySystemSession(session.getRepositorySession()); + repositorySession.setAuthenticationSelector(new DefaultAuthenticationSelector() + .add( + "myserver", + new AuthenticationBuilder() + .addUsername("foo") + .addPassword("bar") + .build())); repositorySession.setLocalRepositoryManager(repositorySystem.newLocalRepositoryManager( repositorySession, new LocalRepository(isolatedLocalRepository.toFile()))); when(session.getRepositorySession()).thenReturn(repositorySession); @@ -235,8 +274,8 @@ private void useIsolatedLocalRepository() { } /** - * Whether the failure came from the proxy rather than from, say, a missing artifact or a rejected login — the - * host of {@link #createProxy(String)} only ever appears if the proxy really was applied to the repository. + * Whether the failure came from the proxy rather than from, say, a missing artifact or a rejected login -- the + * host of {@link #createProxySelector(String)} only ever appears if the proxy really was applied to the repository. */ private boolean mentionsProxyHost(Throwable throwable) { for (Throwable cause = throwable; cause != null; cause = cause.getCause()) { @@ -251,14 +290,8 @@ private boolean mentionsProxyHost(Throwable throwable) { * An active HTTP proxy pointing at a name that is guaranteed not to resolve (RFC 2606 reserved TLD), so that * applying it is always observable as a failure. */ - private Proxy createProxy(String nonProxyHosts) { - Proxy proxy = new Proxy(); - proxy.setActive(true); - proxy.setProtocol("http"); - proxy.setHost(PROXY_HOST); - proxy.setPort(3128); - proxy.setNonProxyHosts(nonProxyHosts); - return proxy; + private DefaultProxySelector createProxySelector(String nonProxyHosts) { + return new DefaultProxySelector().add(new Proxy("http", PROXY_HOST, 3128), nonProxyHosts); } private String serverUrl(org.eclipse.jetty.server.Server server) throws Exception { @@ -269,52 +302,6 @@ private String serverUrl(org.eclipse.jetty.server.Server server) throws Exceptio return "http://" + host + ":" + serverConnector.getLocalPort() + "/maven"; } - /** - * Test parsing of the remote repositories parameter - * - * @throws Exception in case of errors - */ - @Test - @InjectMojo(goal = "get") - void testParseRepository(GetMojo mojo) throws Exception { - ArtifactRepositoryPolicy policy = null; - ArtifactRepository repo = - mojo.parseRepository("central::default::https://repo.maven.apache.org/maven2", policy); - assertEquals("central", repo.getId()); - assertEquals(DefaultRepositoryLayout.class, repo.getLayout().getClass()); - assertEquals("https://repo.maven.apache.org/maven2", repo.getUrl()); - - try { - repo = mojo.parseRepository("central::legacy::https://repo.maven.apache.org/maven2", policy); - fail("Exception expected: legacy repository not supported anymore"); - } catch (MojoFailureException e) { - } - - repo = mojo.parseRepository("central::::https://repo.maven.apache.org/maven2", policy); - assertEquals("central", repo.getId()); - assertEquals(DefaultRepositoryLayout.class, repo.getLayout().getClass()); - assertEquals("https://repo.maven.apache.org/maven2", repo.getUrl()); - - repo = mojo.parseRepository("https://repo.maven.apache.org/maven2", policy); - assertEquals("temp", repo.getId()); - assertEquals(DefaultRepositoryLayout.class, repo.getLayout().getClass()); - assertEquals("https://repo.maven.apache.org/maven2", repo.getUrl()); - - try { - mojo.parseRepository("::::https://repo.maven.apache.org/maven2", policy); - fail("Exception expected"); - } catch (MojoFailureException e) { - // expected - } - - try { - mojo.parseRepository("central::https://repo.maven.apache.org/maven2", policy); - fail("Exception expected"); - } catch (MojoFailureException e) { - // expected - } - } - private ContextHandler createContextHandler() { ResourceHandler resourceHandler = new ResourceHandler(); resourceHandler.setResourceBase(getTestPath("repository")); diff --git a/src/test/java/org/apache/maven/plugins/dependency/utils/ResolverUtilTest.java b/src/test/java/org/apache/maven/plugins/dependency/utils/ResolverUtilTest.java index 4359c6949..69583ef99 100644 --- a/src/test/java/org/apache/maven/plugins/dependency/utils/ResolverUtilTest.java +++ b/src/test/java/org/apache/maven/plugins/dependency/utils/ResolverUtilTest.java @@ -25,6 +25,8 @@ import org.apache.maven.execution.MavenExecutionRequest; import org.apache.maven.execution.MavenSession; import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.artifact.ArtifactTypeRegistry; import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.repository.RepositoryPolicy; import org.junit.jupiter.api.Test; @@ -50,6 +52,9 @@ class ResolverUtilTest { @Mock private RepositorySystemSession repositorySystemSession; + @Mock + private ArtifactTypeRegistry artifactTypeRegistry; + @Mock private MavenSession mavenSession; @@ -105,4 +110,28 @@ void prepareRepositoryWithNull() { .isExactlyInstanceOf(NullPointerException.class) .hasMessage("repository must be not null"); } + + @Test + void prepareRepositoryRejectsInvalidSyntax() { + assertThatCode(() -> resolverUtil.prepareRemoteRepository("central::default::url::extra")) + .isExactlyInstanceOf(IllegalArgumentException.class) + .hasMessage("Invalid repository: central::default::url::extra"); + } + + @Test + void createArtifactWithUnknownPackaging() { + when(sessionProvider.get()).thenReturn(mavenSession); + when(mavenSession.getRepositorySession()).thenReturn(repositorySystemSession); + when(repositorySystemSession.getArtifactTypeRegistry()).thenReturn(artifactTypeRegistry); + + ParamArtifact paramArtifact = new ParamArtifact(); + paramArtifact.setGroupId("org.apache.maven.plugins"); + paramArtifact.setArtifactId("custom-artifact"); + paramArtifact.setVersion("1.0"); + paramArtifact.setPackaging("custom-type"); + + Artifact artifact = resolverUtil.createArtifactFromParams(paramArtifact); + + assertThat(artifact.getExtension()).isEqualTo("custom-type"); + } } From e9a055698240321eb85ce0961ffcd5322068e1e8 Mon Sep 17 00:00:00 2001 From: Vaclav Haisman Date: Sun, 2 Aug 2026 10:30:56 +0200 Subject: [PATCH 2/6] [MDEP-858] Migrate dependency:copy-dependencies installation to Maven Resolver Replace Maven Artifact Transfer installation used by dependency:copy-dependencies in the repository-layout path while preserving Maven local-repository metadata, copied POMs, and snapshot behavior. Fixes #1355. --- .../CopyDependenciesMojo.java | 41 +++------ .../dependency/utils/ResolverUtil.java | 53 +++++++++++ .../TestCopyDependenciesMojo2.java | 42 ++++++--- .../dependency/utils/ResolverUtilTest.java | 91 +++++++++++++++++++ 4 files changed, 188 insertions(+), 39 deletions(-) diff --git a/src/main/java/org/apache/maven/plugins/dependency/fromDependencies/CopyDependenciesMojo.java b/src/main/java/org/apache/maven/plugins/dependency/fromDependencies/CopyDependenciesMojo.java index 6b820480f..064f79fde 100644 --- a/src/main/java/org/apache/maven/plugins/dependency/fromDependencies/CopyDependenciesMojo.java +++ b/src/main/java/org/apache/maven/plugins/dependency/fromDependencies/CopyDependenciesMojo.java @@ -22,7 +22,6 @@ import java.io.File; import java.io.IOException; -import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; @@ -43,11 +42,9 @@ import org.apache.maven.plugins.dependency.utils.filters.DestFileFilter; import org.apache.maven.project.MavenProject; import org.apache.maven.project.ProjectBuilder; -import org.apache.maven.project.ProjectBuildingRequest; import org.apache.maven.shared.artifact.filter.collection.ArtifactsFilter; -import org.apache.maven.shared.transfer.artifact.install.ArtifactInstaller; -import org.apache.maven.shared.transfer.artifact.install.ArtifactInstallerException; -import org.apache.maven.shared.transfer.repository.RepositoryManager; +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.installation.InstallationException; import org.eclipse.aether.resolution.ArtifactDescriptorException; import org.eclipse.aether.resolution.ArtifactResolutionException; import org.eclipse.aether.util.artifact.SubArtifact; @@ -78,10 +75,6 @@ public class CopyDependenciesMojo extends AbstractFromDependenciesMojo { private final CopyUtil copyUtil; - private final ArtifactInstaller installer; - - private final RepositoryManager repositoryManager; - /** * Either append the artifact's baseVersion or uniqueVersion to the filename. Will only be used if * {@link #isStripVersion()} is {@code false}. @@ -108,21 +101,16 @@ public class CopyDependenciesMojo extends AbstractFromDependenciesMojo { protected boolean copySignatures; @Inject - @SuppressWarnings("checkstyle:ParameterNumber") public CopyDependenciesMojo( MavenSession session, BuildContext buildContext, MavenProject project, ResolverUtil resolverUtil, - RepositoryManager repositoryManager, ProjectBuilder projectBuilder, ArtifactHandlerManager artifactHandlerManager, - CopyUtil copyUtil, - ArtifactInstaller installer) { + CopyUtil copyUtil) { super(session, buildContext, project, resolverUtil, projectBuilder, artifactHandlerManager); this.copyUtil = copyUtil; - this.installer = installer; - this.repositoryManager = repositoryManager; } /** @@ -158,10 +146,9 @@ protected void doExecute() throws MojoExecutionException { artifact, isStripVersion(), this.prependGroupId, this.useBaseVersion, this.stripClassifier); } } else { - ProjectBuildingRequest buildingRequest = - repositoryManager.setLocalRepositoryBasedir(session.getProjectBuildingRequest(), outputDirectory); + RepositorySystemSession repositorySystemSession = getResolverUtil().localRepositorySession(outputDirectory); - artifacts.forEach(artifact -> installArtifact(artifact, buildingRequest)); + artifacts.forEach(artifact -> installArtifact(artifact, repositorySystemSession)); } Set skippedArtifacts = dss.getSkippedDependencies(); @@ -179,32 +166,32 @@ protected void doExecute() throws MojoExecutionException { /** * Install the artifact and the corresponding pom if copyPoms=true. */ - private void installArtifact(Artifact artifact, ProjectBuildingRequest buildingRequest) { + private void installArtifact(Artifact artifact, RepositorySystemSession repositorySystemSession) { try { - installer.install(buildingRequest, Collections.singletonList(artifact)); - installBaseSnapshot(artifact, buildingRequest); + getResolverUtil().installArtifact(artifact, repositorySystemSession); + installBaseSnapshot(artifact, repositorySystemSession); if (!"pom".equals(artifact.getType()) && isCopyPom()) { Artifact pomArtifact = getResolvedPomArtifact(artifact); if (pomArtifact != null && pomArtifact.getFile() != null && pomArtifact.getFile().exists()) { - installer.install(buildingRequest, Collections.singletonList(pomArtifact)); - installBaseSnapshot(pomArtifact, buildingRequest); + getResolverUtil().installArtifact(pomArtifact, repositorySystemSession); + installBaseSnapshot(pomArtifact, repositorySystemSession); } } - } catch (ArtifactInstallerException e) { + } catch (InstallationException e) { getLog().warn("unable to install " + artifact, e); } } - private void installBaseSnapshot(Artifact artifact, ProjectBuildingRequest buildingRequest) - throws ArtifactInstallerException { + private void installBaseSnapshot(Artifact artifact, RepositorySystemSession repositorySystemSession) + throws InstallationException { if (artifact.isSnapshot() && !artifact.getBaseVersion().equals(artifact.getVersion())) { String version = artifact.getVersion(); try { artifact.setVersion(artifact.getBaseVersion()); - installer.install(buildingRequest, Collections.singletonList(artifact)); + getResolverUtil().installArtifact(artifact, repositorySystemSession); } finally { artifact.setVersion(version); } diff --git a/src/main/java/org/apache/maven/plugins/dependency/utils/ResolverUtil.java b/src/main/java/org/apache/maven/plugins/dependency/utils/ResolverUtil.java index b622fb485..bd67920fa 100644 --- a/src/main/java/org/apache/maven/plugins/dependency/utils/ResolverUtil.java +++ b/src/main/java/org/apache/maven/plugins/dependency/utils/ResolverUtil.java @@ -23,6 +23,7 @@ import javax.inject.Provider; import javax.inject.Singleton; +import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -42,6 +43,9 @@ import org.apache.maven.model.ReportPlugin; import org.apache.maven.model.Reporting; import org.apache.maven.project.MavenProject; +import org.apache.maven.project.artifact.ProjectArtifactMetadata; +import org.eclipse.aether.DefaultRepositoryCache; +import org.eclipse.aether.DefaultRepositorySystemSession; import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.artifact.Artifact; @@ -53,6 +57,9 @@ import org.eclipse.aether.collection.CollectResult; import org.eclipse.aether.collection.DependencyCollectionException; import org.eclipse.aether.graph.Dependency; +import org.eclipse.aether.installation.InstallRequest; +import org.eclipse.aether.installation.InstallationException; +import org.eclipse.aether.repository.LocalRepository; import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.repository.RepositoryPolicy; import org.eclipse.aether.resolution.ArtifactDescriptorException; @@ -64,6 +71,7 @@ import org.eclipse.aether.resolution.DependencyRequest; import org.eclipse.aether.resolution.DependencyResolutionException; import org.eclipse.aether.resolution.DependencyResult; +import org.eclipse.aether.util.artifact.SubArtifact; import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator; /** @@ -83,6 +91,51 @@ public ResolverUtil(RepositorySystem repositorySystem, Provider ma this.mavenSessionProvider = mavenSessionProvider; } + /** + * Returns a copy of the current repository session using the supplied local repository directory. + * + * @param localRepositoryDirectory alternate local repository directory + * @return repository system session + */ + public RepositorySystemSession localRepositorySession(File localRepositoryDirectory) { + Objects.requireNonNull(localRepositoryDirectory, "localRepositoryDirectory"); + RepositorySystemSession repositorySystemSession = + mavenSessionProvider.get().getRepositorySession(); + String contentType = repositorySystemSession + .getLocalRepositoryManager() + .getRepository() + .getContentType(); + if ("enhanced".equals(contentType)) { + contentType = "default"; + } + + DefaultRepositorySystemSession newSession = new DefaultRepositorySystemSession(repositorySystemSession); + newSession.setCache(new DefaultRepositoryCache()); + newSession.setLocalRepositoryManager(repositorySystem.newLocalRepositoryManager( + newSession, new LocalRepository(localRepositoryDirectory, contentType))); + return newSession; + } + + /** + * Installs an artifact into the local repository associated with the supplied repository session. + * + * @param artifact artifact to install + * @param repositorySystemSession repository session containing the target local repository + * @throws InstallationException if the artifact could not be installed + */ + public void installArtifact( + org.apache.maven.artifact.Artifact artifact, RepositorySystemSession repositorySystemSession) + throws InstallationException { + Artifact resolverArtifact = RepositoryUtils.toArtifact(artifact); + InstallRequest installRequest = new InstallRequest().addArtifact(resolverArtifact); + artifact.getMetadataList().stream() + .filter(ProjectArtifactMetadata.class::isInstance) + .map(ProjectArtifactMetadata.class::cast) + .map(metadata -> new SubArtifact(resolverArtifact, "", "pom").setFile(metadata.getFile())) + .forEach(installRequest::addArtifact); + repositorySystem.install(repositorySystemSession, installRequest); + } + /** * Collects the transitive dependencies. * diff --git a/src/test/java/org/apache/maven/plugins/dependency/fromDependencies/TestCopyDependenciesMojo2.java b/src/test/java/org/apache/maven/plugins/dependency/fromDependencies/TestCopyDependenciesMojo2.java index a04ff76be..2cde33e2d 100644 --- a/src/test/java/org/apache/maven/plugins/dependency/fromDependencies/TestCopyDependenciesMojo2.java +++ b/src/test/java/org/apache/maven/plugins/dependency/fromDependencies/TestCopyDependenciesMojo2.java @@ -22,10 +22,12 @@ import java.io.File; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collection; +import java.util.Collections; import java.util.Set; import org.apache.maven.api.plugin.testing.InjectMojo; @@ -44,19 +46,16 @@ import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter; import org.apache.maven.artifact.versioning.VersionRange; import org.apache.maven.bridge.MavenRepositorySystem; -import org.apache.maven.execution.MavenSession; import org.apache.maven.plugins.dependency.testUtils.DependencyArtifactStubFactory; import org.apache.maven.plugins.dependency.utils.DependencyUtil; -import org.apache.maven.project.DefaultProjectBuildingRequest; import org.apache.maven.project.MavenProject; -import org.apache.maven.project.ProjectBuildingRequest; +import org.apache.maven.project.artifact.ProjectArtifactMetadata; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.when; @MojoTest(realRepositorySession = true) class TestCopyDependenciesMojo2 { @@ -66,9 +65,6 @@ class TestCopyDependenciesMojo2 { private DependencyArtifactStubFactory stubFactory; - @Inject - private MavenSession session; - @Inject private MavenProject project; @@ -236,11 +232,6 @@ void testSubPerArtifactAndScope(CopyDependenciesMojo mojo) throws Exception { @Test @InjectMojo(goal = "copy-dependencies") void testRepositoryLayout(CopyDependenciesMojo mojo) throws Exception { - - ProjectBuildingRequest pbr = new DefaultProjectBuildingRequest(); - pbr.setRepositorySession(session.getRepositorySession()); - when(session.getProjectBuildingRequest()).thenReturn(pbr); - String baseVersion = "2.0-SNAPSHOT"; String groupId = "testGroupId"; String artifactId = "expanded-snapshot"; @@ -283,6 +274,33 @@ void testRepositoryLayout(CopyDependenciesMojo mojo) throws Exception { } } + @Test + @InjectMojo(goal = "copy-dependencies") + void testRepositoryLayoutInstallsProjectArtifactPom(CopyDependenciesMojo mojo) throws Exception { + Artifact artifact = stubFactory.createArtifact( + "testGroupId", + "artifact-with-project-pom", + VersionRange.createFromVersion("1.0"), + "compile", + "jar", + null, + false); + File pom = new File(tempDir, "artifact-with-project-pom-1.0.pom"); + Files.write( + pom.toPath(), + Collections.singletonList("4.0.0"), + StandardCharsets.UTF_8); + artifact.addMetadata(new ProjectArtifactMetadata(artifact, pom)); + mojo.getProject().setArtifacts(Collections.singleton(artifact)); + + mojo.useRepositoryLayout = true; + mojo.execute(); + + Path artifactDirectory = mojo.outputDirectory.toPath().resolve("testGroupId/artifact-with-project-pom/1.0"); + assertTrue(Files.isRegularFile(artifactDirectory.resolve("artifact-with-project-pom-1.0.jar"))); + assertTrue(Files.isRegularFile(artifactDirectory.resolve("artifact-with-project-pom-1.0.pom"))); + } + private Artifact createExpandedVersionArtifact( String baseVersion, String groupId, String artifactId, String scope, String type, String classifier) throws IOException { diff --git a/src/test/java/org/apache/maven/plugins/dependency/utils/ResolverUtilTest.java b/src/test/java/org/apache/maven/plugins/dependency/utils/ResolverUtilTest.java index 69583ef99..5322142fe 100644 --- a/src/test/java/org/apache/maven/plugins/dependency/utils/ResolverUtilTest.java +++ b/src/test/java/org/apache/maven/plugins/dependency/utils/ResolverUtilTest.java @@ -20,20 +20,32 @@ import javax.inject.Provider; +import java.io.File; +import java.util.Collections; import java.util.stream.Stream; +import org.apache.maven.RepositoryUtils; +import org.apache.maven.artifact.handler.DefaultArtifactHandler; import org.apache.maven.execution.MavenExecutionRequest; import org.apache.maven.execution.MavenSession; +import org.apache.maven.project.artifact.ProjectArtifactMetadata; +import org.eclipse.aether.DefaultRepositorySystemSession; +import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.artifact.ArtifactTypeRegistry; +import org.eclipse.aether.repository.LocalRepository; +import org.eclipse.aether.repository.LocalRepositoryManager; import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.repository.RepositoryPolicy; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -41,17 +53,26 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.junit.jupiter.params.provider.Arguments.of; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class ResolverUtilTest { + @Mock + private RepositorySystem repositorySystem; + @Mock private MavenExecutionRequest executionRequest; @Mock private RepositorySystemSession repositorySystemSession; + @Mock + private LocalRepositoryManager localRepositoryManager; + @Mock private ArtifactTypeRegistry artifactTypeRegistry; @@ -64,6 +85,9 @@ class ResolverUtilTest { @InjectMocks private ResolverUtil resolverUtil; + @TempDir + private File tempDir; + public static Stream prepareRepositoryTest() { return Stream.of( @@ -134,4 +158,71 @@ void createArtifactWithUnknownPackaging() { assertThat(artifact.getExtension()).isEqualTo("custom-type"); } + + @Test + void installArtifact() throws Exception { + org.apache.maven.artifact.Artifact artifact = new org.apache.maven.artifact.DefaultArtifact( + "org.apache.maven.plugins", "artifact", "1.0", null, "jar", null, new DefaultArtifactHandler("jar")); + artifact.setFile(new File(tempDir, "artifact-1.0.jar")); + Artifact resolverArtifact = RepositoryUtils.toArtifact(artifact); + + resolverUtil.installArtifact(artifact, repositorySystemSession); + + verify(repositorySystem) + .install( + eq(repositorySystemSession), + argThat(request -> request.getArtifacts().equals(Collections.singletonList(resolverArtifact)))); + } + + @Test + void installArtifactWithProjectArtifactMetadata() throws Exception { + org.apache.maven.artifact.Artifact artifact = new org.apache.maven.artifact.DefaultArtifact( + "org.apache.maven.plugins", "artifact", "1.0", null, "jar", null, new DefaultArtifactHandler("jar")); + File jar = new File(tempDir, "artifact-1.0.jar"); + File pom = new File(tempDir, "artifact-1.0.pom"); + artifact.setFile(jar); + artifact.addMetadata(new ProjectArtifactMetadata(artifact, pom)); + + resolverUtil.installArtifact(artifact, repositorySystemSession); + + ArgumentCaptor request = + ArgumentCaptor.forClass(org.eclipse.aether.installation.InstallRequest.class); + verify(repositorySystem).install(eq(repositorySystemSession), request.capture()); + assertThat(request.getValue().getArtifacts()) + .hasSize(2) + .anySatisfy(installed -> assertThat(installed.getFile()).isEqualTo(jar)) + .anySatisfy(installed -> { + assertThat(installed.getExtension()).isEqualTo("pom"); + assertThat(installed.getFile()).isEqualTo(pom); + }); + } + + @ParameterizedTest + @CsvSource({"simple, simple", "enhanced, default"}) + void localRepositorySessionPreservesRepositoryType(String currentType, String expectedType) { + LocalRepository currentRepository = new LocalRepository(tempDir, currentType); + LocalRepositoryManager newLocalRepositoryManager = org.mockito.Mockito.mock(LocalRepositoryManager.class); + when(sessionProvider.get()).thenReturn(mavenSession); + when(mavenSession.getRepositorySession()).thenReturn(repositorySystemSession); + when(repositorySystemSession.getLocalRepositoryManager()).thenReturn(localRepositoryManager); + when(localRepositoryManager.getRepository()).thenReturn(currentRepository); + when(repositorySystem.newLocalRepositoryManager( + any(DefaultRepositorySystemSession.class), any(LocalRepository.class))) + .thenReturn(newLocalRepositoryManager); + + RepositorySystemSession result = resolverUtil.localRepositorySession(new File(tempDir, "alternate")); + + assertThat(result.getLocalRepositoryManager()).isSameAs(newLocalRepositoryManager); + verify(repositorySystem) + .newLocalRepositoryManager( + any(DefaultRepositorySystemSession.class), + argThat(repository -> repository.getContentType().equals(expectedType))); + } + + @Test + void localRepositorySessionRequiresDirectory() { + assertThatCode(() -> resolverUtil.localRepositorySession(null)) + .isExactlyInstanceOf(NullPointerException.class) + .hasMessage("localRepositoryDirectory"); + } } From 82ead8a6364d5802825eb55df0c984d03b60a754 Mon Sep 17 00:00:00 2001 From: Vaclav Haisman Date: Sun, 2 Aug 2026 11:13:51 +0200 Subject: [PATCH 3/6] [MDEP-858] Migrate dependency:purge-local-repository to Maven Resolver Replace the Maven Artifact Transfer resolvers while preserving dependency filtering, fallback resolution, and aggregated failure reporting. Re-resolve each artifact once because Resolver descriptor resolution also restores its POM. Fixes #1355. --- .../purge-local-repository-bad-dep/pom.xml | 6 + .../verify.groovy | 3 + .../projects/purge-local-repository/pom.xml | 12 ++ .../projects/purge-local-repository/setup.bsh | 39 +++++- .../purge-local-repository/verify.groovy | 12 ++ .../dependency/PurgeLocalRepositoryMojo.java | 117 +++++++----------- .../dependency/utils/ResolverUtil.java | 36 +++++- .../dependency/utils/ResolverUtilTest.java | 55 ++++++++ 8 files changed, 202 insertions(+), 78 deletions(-) diff --git a/src/it/projects/purge-local-repository-bad-dep/pom.xml b/src/it/projects/purge-local-repository-bad-dep/pom.xml index 27526b560..6162d0ca6 100644 --- a/src/it/projects/purge-local-repository-bad-dep/pom.xml +++ b/src/it/projects/purge-local-repository-bad-dep/pom.xml @@ -43,5 +43,11 @@ purge-local-repository 1.0 + + org.apache.maven + maven-model + 2.0.9 + sources + diff --git a/src/it/projects/purge-local-repository-bad-dep/verify.groovy b/src/it/projects/purge-local-repository-bad-dep/verify.groovy index 65b44f164..3d0c55a33 100644 --- a/src/it/projects/purge-local-repository-bad-dep/verify.groovy +++ b/src/it/projects/purge-local-repository-bad-dep/verify.groovy @@ -28,10 +28,13 @@ void checkFilePresence( String path ) checkFilePresence( "org/apache/maven/its/dependency/purge-local-repository/1.0/purge-local-repository-1.0.jar" ) checkFilePresence( "org/apache/maven/its/dependency/purge-local-repository/1.0/purge-local-repository-1.0.pom" ) +checkFilePresence( "org/apache/maven/maven-model/2.0.9/maven-model-2.0.9-sources.jar" ) String buildLog = new File( basedir, "build.log" ).getText( "UTF-8" ) assert buildLog.contains( 'Unable to resolve artifact: org.apache.maven.its.dependency:i-do-not-exist:jar:1.0' ) assert buildLog.contains( 'Purging artifact: org.apache.maven.its.dependency:purge-local-repository:jar:1.0' ) assert buildLog.contains( 'Resolving artifact: org.apache.maven.its.dependency:purge-local-repository:jar:1.0' ) +assert buildLog.contains( 'Purging artifact: org.apache.maven:maven-model:jar:sources:2.0.9' ) +assert buildLog.contains( 'Resolving artifact: org.apache.maven:maven-model:jar:sources:2.0.9' ) return true diff --git a/src/it/projects/purge-local-repository/pom.xml b/src/it/projects/purge-local-repository/pom.xml index da7b3fa72..e9efbaf81 100644 --- a/src/it/projects/purge-local-repository/pom.xml +++ b/src/it/projects/purge-local-repository/pom.xml @@ -42,6 +42,18 @@ purged 1.0 + + org.apache.maven.its.dependency + not-purged-test + 1.0 + test + + + org.apache.maven.its.dependency + not-purged-provided + 1.0 + provided + diff --git a/src/it/projects/purge-local-repository/setup.bsh b/src/it/projects/purge-local-repository/setup.bsh index 92b835d1a..fa8544a83 100644 --- a/src/it/projects/purge-local-repository/setup.bsh +++ b/src/it/projects/purge-local-repository/setup.bsh @@ -19,9 +19,42 @@ import java.io.*; -File purgedJar = new File( localRepositoryPath, "org/apache/maven/its/dependency/purged/1.0/purged-1.0.jar" ); +void createJar( String artifactId ) +{ + File jar = new File( localRepositoryPath, + "org/apache/maven/its/dependency/" + artifactId + "/1.0/" + artifactId + "-1.0.jar" ); + jar.getParentFile().mkdirs(); + jar.createNewFile(); +} -purgedJar.getParentFile().mkdirs(); -purgedJar.createNewFile(); +createJar( "purged" ); +createJar( "not-purged-test" ); +createJar( "not-purged-provided" ); +createJar( "not-purged-optional" ); + +File purgedPom = new File( localRepositoryPath, + "org/apache/maven/its/dependency/purged/1.0/purged-1.0.pom" ); +PrintWriter writer = new PrintWriter( purgedPom, "UTF-8" ); +try +{ + writer.println( "" ); + writer.println( " 4.0.0" ); + writer.println( " org.apache.maven.its.dependency" ); + writer.println( " purged" ); + writer.println( " 1.0" ); + writer.println( " " ); + writer.println( " " ); + writer.println( " org.apache.maven.its.dependency" ); + writer.println( " not-purged-optional" ); + writer.println( " 1.0" ); + writer.println( " true" ); + writer.println( " " ); + writer.println( " " ); + writer.println( "" ); +} +finally +{ + writer.close(); +} return true; diff --git a/src/it/projects/purge-local-repository/verify.groovy b/src/it/projects/purge-local-repository/verify.groovy index 7c97d11a0..71c2d66d6 100644 --- a/src/it/projects/purge-local-repository/verify.groovy +++ b/src/it/projects/purge-local-repository/verify.groovy @@ -26,7 +26,19 @@ void checkFileAbsence( String path ) } } +void checkFilePresence( String path ) +{ + File depJar = new File( localRepositoryPath, path ) + if ( !depJar.exists() ) + { + throw new Exception( "Dependency jar was purged: " + depJar ) + } +} + checkFileAbsence( "org/apache/maven/its/dependency/purged/1.0/purged-1.0.jar" ) +checkFilePresence( "org/apache/maven/its/dependency/not-purged-test/1.0/not-purged-test-1.0.jar" ) +checkFilePresence( "org/apache/maven/its/dependency/not-purged-provided/1.0/not-purged-provided-1.0.jar" ) +checkFilePresence( "org/apache/maven/its/dependency/not-purged-optional/1.0/not-purged-optional-1.0.jar" ) String buildLog = new File( basedir, "build.log" ).getText( "UTF-8" ) assert buildLog.contains( 'Deleting 1 transitive dependency for project test from ' ) diff --git a/src/main/java/org/apache/maven/plugins/dependency/PurgeLocalRepositoryMojo.java b/src/main/java/org/apache/maven/plugins/dependency/PurgeLocalRepositoryMojo.java index c5cfc6e85..8325af296 100644 --- a/src/main/java/org/apache/maven/plugins/dependency/PurgeLocalRepositoryMojo.java +++ b/src/main/java/org/apache/maven/plugins/dependency/PurgeLocalRepositoryMojo.java @@ -28,10 +28,11 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; +import org.apache.maven.RepositoryUtils; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.ArtifactUtils; -import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager; import org.apache.maven.artifact.repository.ArtifactRepository; import org.apache.maven.artifact.resolver.ArtifactResolutionException; import org.apache.maven.artifact.resolver.filter.ArtifactFilter; @@ -44,6 +45,7 @@ import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.plugins.dependency.utils.ResolverUtil; import org.apache.maven.project.MavenProject; import org.apache.maven.shared.artifact.filter.resolve.AbstractFilter; import org.apache.maven.shared.artifact.filter.resolve.AndFilter; @@ -53,16 +55,14 @@ import org.apache.maven.shared.artifact.filter.resolve.ScopeFilter; import org.apache.maven.shared.artifact.filter.resolve.TransformableFilter; import org.apache.maven.shared.artifact.filter.resolve.transform.ArtifactIncludeFilterTransformer; -import org.apache.maven.shared.transfer.artifact.DefaultArtifactCoordinate; -import org.apache.maven.shared.transfer.artifact.TransferUtils; -import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolver; -import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolverException; -import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResult; -import org.apache.maven.shared.transfer.dependencies.resolve.DependencyResolver; -import org.apache.maven.shared.transfer.dependencies.resolve.DependencyResolverException; +import org.apache.maven.shared.artifact.filter.resolve.transform.EclipseAetherFilterTransformer; import org.apache.maven.shared.utils.logging.MessageBuilder; import org.apache.maven.shared.utils.logging.MessageUtils; import org.codehaus.plexus.util.FileUtils; +import org.eclipse.aether.artifact.ArtifactTypeRegistry; +import org.eclipse.aether.graph.DependencyFilter; +import org.eclipse.aether.resolution.ArtifactDescriptorException; +import org.eclipse.aether.resolution.DependencyResolutionException; /** * When run on a project, remove the project dependencies from the local repository, and optionally re-resolve them. @@ -87,20 +87,7 @@ public class PurgeLocalRepositoryMojo extends AbstractMojo { private final MavenSession session; - /** - * Artifact handler manager. - */ - private final ArtifactHandlerManager artifactHandlerManager; - - /** - * The dependency resolver. - */ - private final DependencyResolver dependencyResolver; - - /** - * The artifact resolver used to re-resolve dependencies, if that option is enabled. - */ - private final ArtifactResolver artifactResolver; + private final ResolverUtil resolverUtil; /** * The Maven projects in the reactor. @@ -221,17 +208,10 @@ public class PurgeLocalRepositoryMojo extends AbstractMojo { private boolean skip; @Inject - public PurgeLocalRepositoryMojo( - MavenProject project, - MavenSession session, - ArtifactHandlerManager artifactHandlerManager, - DependencyResolver dependencyResolver, - ArtifactResolver artifactResolver) { + public PurgeLocalRepositoryMojo(MavenProject project, MavenSession session, ResolverUtil resolverUtil) { this.session = session; this.project = project; - this.artifactHandlerManager = artifactHandlerManager; - this.dependencyResolver = dependencyResolver; - this.artifactResolver = artifactResolver; + this.resolverUtil = resolverUtil; } /** @@ -521,18 +501,31 @@ private List parseIncludes(String theInclude) { private Set getFilteredResolvedArtifacts( MavenProject theProject, List dependencies, TransformableFilter filter) { - try { - Iterable results = dependencyResolver.resolveDependencies( - session.getProjectBuildingRequest(), theProject.getModel(), filter); - - Set resolvedArtifacts = new LinkedHashSet<>(); - - for (ArtifactResult artResult : results) { - resolvedArtifacts.add(artResult.getArtifact()); - } + ArtifactTypeRegistry artifactTypeRegistry = + session.getRepositorySession().getArtifactTypeRegistry(); + List resolverDependencies = dependencies.stream() + .map(dependency -> RepositoryUtils.toDependency(dependency, artifactTypeRegistry)) + .collect(Collectors.toList()); + List managedDependencies = theProject.getDependencyManagement() == null + ? null + : theProject.getDependencyManagement().getDependencies().stream() + .map(dependency -> RepositoryUtils.toDependency(dependency, artifactTypeRegistry)) + .collect(Collectors.toList()); + DependencyFilter dependencyFilter = filter.transform(new EclipseAetherFilterTransformer()); - return resolvedArtifacts; - } catch (DependencyResolverException e) { + try { + return resolverUtil + .resolveDependenciesForArtifact( + RepositoryUtils.toArtifact(theProject.getArtifact()), + resolverDependencies, + managedDependencies, + theProject.getRemoteProjectRepositories(), + dependencyFilter) + .stream() + .map(RepositoryUtils::toArtifact) + .collect(Collectors.toCollection(LinkedHashSet::new)); + } catch (DependencyResolutionException e) { + getLog().debug("Unable to resolve all dependencies for: " + getProjectKey(theProject), e); getLog().info("Unable to resolve all dependencies for: " + getProjectKey(theProject) + ". Falling back to non-transitive mode for initial artifact resolution."); } @@ -541,23 +534,16 @@ private Set getFilteredResolvedArtifacts( ArtifactFilter artifactFilter = filter.transform(new ArtifactIncludeFilterTransformer()); - for (Dependency dependency : dependencies) { - DefaultArtifactCoordinate coordinate = new DefaultArtifactCoordinate(); - coordinate.setGroupId(dependency.getGroupId()); - coordinate.setArtifactId(dependency.getArtifactId()); - coordinate.setVersion(dependency.getVersion()); - coordinate.setExtension(artifactHandlerManager - .getArtifactHandler(dependency.getType()) - .getExtension()); + for (org.eclipse.aether.graph.Dependency dependency : resolverDependencies) { + org.eclipse.aether.artifact.Artifact coordinate = dependency.getArtifact(); try { - Artifact artifact = artifactResolver - .resolveArtifact(session.getProjectBuildingRequest(), coordinate) - .getArtifact(); + Artifact artifact = RepositoryUtils.toArtifact( + resolverUtil.resolveArtifact(coordinate, theProject.getRemoteProjectRepositories())); if (artifactFilter.include(artifact)) { resolvedArtifacts.add(artifact); } - } catch (ArtifactResolverException e) { - getLog().debug("Unable to resolve artifact: " + coordinate); + } catch (org.eclipse.aether.resolution.ArtifactResolutionException | ArtifactDescriptorException e) { + getLog().debug("Unable to resolve artifact: " + coordinate, e); } } return resolvedArtifacts; @@ -606,27 +592,14 @@ private void purgeArtifacts(MavenProject theProject, Set artifacts) { private void reResolveArtifacts(MavenProject theProject, Set artifacts) throws ArtifactResolutionException { - // Always need to re-resolve the poms in case they were purged along with the artifact - // because Maven 2 will not automatically re-resolve them when resolving the artifact - for (Artifact artifact : artifacts) { - verbose("Resolving artifact: " + artifact.getId()); - - try { - // CHECKSTYLE_OFF: LineLength - artifactResolver.resolveArtifact( - session.getProjectBuildingRequest(), TransferUtils.toArtifactCoordinate(artifact)); - // CHECKSTYLE_ON: LineLength - } catch (ArtifactResolverException e) { - verbose(e.getMessage()); - } - } - List missingArtifacts = new ArrayList<>(); for (Artifact artifact : artifacts) { + verbose("Resolving artifact: " + artifact.getId()); try { - artifactResolver.resolveArtifact(session.getProjectBuildingRequest(), artifact); - } catch (ArtifactResolverException e) { + resolverUtil.resolveArtifact( + RepositoryUtils.toArtifact(artifact), theProject.getRemoteProjectRepositories()); + } catch (org.eclipse.aether.resolution.ArtifactResolutionException | ArtifactDescriptorException e) { verbose(e.getMessage()); missingArtifacts.add(artifact); } diff --git a/src/main/java/org/apache/maven/plugins/dependency/utils/ResolverUtil.java b/src/main/java/org/apache/maven/plugins/dependency/utils/ResolverUtil.java index bd67920fa..673e6e1ff 100644 --- a/src/main/java/org/apache/maven/plugins/dependency/utils/ResolverUtil.java +++ b/src/main/java/org/apache/maven/plugins/dependency/utils/ResolverUtil.java @@ -57,6 +57,7 @@ import org.eclipse.aether.collection.CollectResult; import org.eclipse.aether.collection.DependencyCollectionException; import org.eclipse.aether.graph.Dependency; +import org.eclipse.aether.graph.DependencyFilter; import org.eclipse.aether.installation.InstallRequest; import org.eclipse.aether.installation.InstallationException; import org.eclipse.aether.repository.LocalRepository; @@ -262,12 +263,41 @@ public List resolveDependenciesForArtifact( List managedDependencies, List remoteProjectRepositories) throws DependencyResolutionException { - MavenSession session = mavenSessionProvider.get(); - CollectRequest collectRequest = new CollectRequest(dependencies, managedDependencies, remoteProjectRepositories); collectRequest.setRootArtifact(rootArtifact); - DependencyRequest request = new DependencyRequest(collectRequest, null); + return resolveDependencies(collectRequest, null); + } + + /** + * Resolve transitive dependencies for artifact with managed dependencies. + * + * @param rootArtifact a root artifact to resolve + * @param dependencies a list of dependencies for artifact + * @param managedDependencies a list of managed dependencies for artifact + * @param remoteProjectRepositories remote repositories list + * @param dependencyFilter dependency filter, or {@code null} + * @return Resolved dependencies + * @throws DependencyResolutionException if the dependency tree could not be built or any dependency artifact could + * not be resolved + */ + public List resolveDependenciesForArtifact( + Artifact rootArtifact, + List dependencies, + List managedDependencies, + List remoteProjectRepositories, + DependencyFilter dependencyFilter) + throws DependencyResolutionException { + CollectRequest collectRequest = + new CollectRequest(new Dependency(rootArtifact, null), dependencies, remoteProjectRepositories); + collectRequest.setManagedDependencies(managedDependencies); + return resolveDependencies(collectRequest, dependencyFilter); + } + + private List resolveDependencies(CollectRequest collectRequest, DependencyFilter dependencyFilter) + throws DependencyResolutionException { + MavenSession session = mavenSessionProvider.get(); + DependencyRequest request = new DependencyRequest(collectRequest, dependencyFilter); DependencyResult result = repositorySystem.resolveDependencies(session.getRepositorySession(), request); return result.getArtifactResults().stream() .map(ArtifactResult::getArtifact) diff --git a/src/test/java/org/apache/maven/plugins/dependency/utils/ResolverUtilTest.java b/src/test/java/org/apache/maven/plugins/dependency/utils/ResolverUtilTest.java index 5322142fe..9b96fe841 100644 --- a/src/test/java/org/apache/maven/plugins/dependency/utils/ResolverUtilTest.java +++ b/src/test/java/org/apache/maven/plugins/dependency/utils/ResolverUtilTest.java @@ -34,10 +34,14 @@ import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.artifact.ArtifactTypeRegistry; +import org.eclipse.aether.artifact.DefaultArtifact; +import org.eclipse.aether.graph.DependencyFilter; import org.eclipse.aether.repository.LocalRepository; import org.eclipse.aether.repository.LocalRepositoryManager; import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.repository.RepositoryPolicy; +import org.eclipse.aether.resolution.DependencyRequest; +import org.eclipse.aether.resolution.DependencyResult; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.io.TempDir; @@ -53,6 +57,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.junit.jupiter.params.provider.Arguments.of; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; @@ -76,6 +81,9 @@ class ResolverUtilTest { @Mock private ArtifactTypeRegistry artifactTypeRegistry; + @Mock + private DependencyFilter dependencyFilter; + @Mock private MavenSession mavenSession; @@ -225,4 +233,51 @@ void localRepositorySessionRequiresDirectory() { .isExactlyInstanceOf(NullPointerException.class) .hasMessage("localRepositoryDirectory"); } + + @Test + void resolveDependenciesForArtifactWithFilter() throws Exception { + Artifact rootArtifact = new DefaultArtifact("org.apache.maven.plugins", "artifact", "jar", "1.0"); + DependencyResult dependencyResult = new DependencyResult(new DependencyRequest()); + when(sessionProvider.get()).thenReturn(mavenSession); + when(mavenSession.getRepositorySession()).thenReturn(repositorySystemSession); + when(repositorySystem.resolveDependencies(eq(repositorySystemSession), any(DependencyRequest.class))) + .thenReturn(dependencyResult); + + resolverUtil.resolveDependenciesForArtifact( + rootArtifact, + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), + dependencyFilter); + + verify(repositorySystem) + .resolveDependencies( + eq(repositorySystemSession), + argThat(request -> request.getFilter() == dependencyFilter + && request.getCollectRequest().getRootArtifact() == null + && request.getCollectRequest() + .getRoot() + .getArtifact() + .equals(rootArtifact))); + } + + @Test + void resolveDependenciesForArtifactWithoutFilterDoesNotResolveRoot() throws Exception { + Artifact rootArtifact = new DefaultArtifact("org.apache.maven.plugins", "artifact", "jar", "1.0"); + DependencyResult dependencyResult = new DependencyResult(new DependencyRequest()); + when(sessionProvider.get()).thenReturn(mavenSession); + when(mavenSession.getRepositorySession()).thenReturn(repositorySystemSession); + when(repositorySystem.resolveDependencies(eq(repositorySystemSession), any(DependencyRequest.class))) + .thenReturn(dependencyResult); + + resolverUtil.resolveDependenciesForArtifact( + rootArtifact, Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); + + verify(repositorySystem) + .resolveDependencies( + eq(repositorySystemSession), + argThat(request -> request.getFilter() == null + && request.getCollectRequest().getRoot() == null + && request.getCollectRequest().getRootArtifact().equals(rootArtifact))); + } } From fd2706ef1d86bd6d01ad1aee2c9d92dbfbf907a7 Mon Sep 17 00:00:00 2001 From: Vaclav Haisman Date: Sun, 2 Aug 2026 11:35:03 +0200 Subject: [PATCH 4/6] [MDEP-858] Remove Maven Artifact Transfer dependency Drop the unused DependableCoordinate interface from ArtifactItem and remove the now-unused Maven Artifact Transfer dependency. Keep the artifact configuration getters unchanged. Fixes #1355. --- pom.xml | 15 --------------- .../fromConfiguration/ArtifactItem.java | 8 +------- 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/pom.xml b/pom.xml index 23b9b29d4..910007729 100644 --- a/pom.xml +++ b/pom.xml @@ -231,21 +231,6 @@ under the License. maven-common-artifact-filters 3.4.0 - - org.apache.maven.shared - maven-artifact-transfer - 0.13.1 - - - org.apache.maven - maven-artifact - - - org.apache.maven - maven-core - - - org.apache.maven.shared maven-shared-utils diff --git a/src/main/java/org/apache/maven/plugins/dependency/fromConfiguration/ArtifactItem.java b/src/main/java/org/apache/maven/plugins/dependency/fromConfiguration/ArtifactItem.java index b697533ce..8426185d3 100644 --- a/src/main/java/org/apache/maven/plugins/dependency/fromConfiguration/ArtifactItem.java +++ b/src/main/java/org/apache/maven/plugins/dependency/fromConfiguration/ArtifactItem.java @@ -25,7 +25,6 @@ import org.apache.maven.artifact.ArtifactUtils; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.dependency.utils.DependencyUtil; -import org.apache.maven.shared.transfer.dependencies.DependableCoordinate; import org.codehaus.plexus.components.io.filemappers.FileMapper; /** @@ -34,7 +33,7 @@ * @author Brian Fox * @since 1.0 */ -public class ArtifactItem implements DependableCoordinate { +public class ArtifactItem { /** * Group ID of artifact. */ @@ -142,7 +141,6 @@ private String filterEmptyString(String in) { /** * @return returns the artifact ID */ - @Override public String getArtifactId() { return artifactId; } @@ -157,7 +155,6 @@ public void setArtifactId(String theArtifact) { /** * @return returns the group ID */ - @Override public String getGroupId() { return groupId; } @@ -172,7 +169,6 @@ public void setGroupId(String groupId) { /** * @return returns the type */ - @Override public String getType() { return type; } @@ -187,7 +183,6 @@ public void setType(String type) { /** * @return returns the version */ - @Override public String getVersion() { return version; } @@ -209,7 +204,6 @@ public String getBaseVersion() { /** * @return classifier */ - @Override public String getClassifier() { return classifier; } From f45090bc544f0823f4e5a292e9ce20b7d6c4352a Mon Sep 17 00:00:00 2001 From: Vaclav Haisman Date: Sun, 9 Aug 2026 23:29:27 +0200 Subject: [PATCH 5/6] [MDEP-858] Preserve get repository refresh policy Keep dependency:get compatible with its historical behavior by applying the always-update policy to user-supplied repositories. Leave the existing session-controlled policy available to other ResolverUtil callers. Fixes #1355. --- .../maven/plugins/dependency/GetMojo.java | 6 +-- .../dependency/utils/ResolverUtil.java | 44 +++++++++++++++++-- .../maven/plugins/dependency/TestGetMojo.java | 27 +++++++++++- .../dependency/utils/ResolverUtilTest.java | 13 ++++++ 4 files changed, 81 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/apache/maven/plugins/dependency/GetMojo.java b/src/main/java/org/apache/maven/plugins/dependency/GetMojo.java index ba6909207..e9dddc581 100644 --- a/src/main/java/org/apache/maven/plugins/dependency/GetMojo.java +++ b/src/main/java/org/apache/maven/plugins/dependency/GetMojo.java @@ -33,6 +33,7 @@ import org.apache.maven.plugins.dependency.utils.ResolverUtil; import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.repository.RepositoryPolicy; import org.eclipse.aether.resolution.ArtifactDescriptorException; import org.eclipse.aether.resolution.ArtifactResolutionException; import org.eclipse.aether.resolution.DependencyResolutionException; @@ -111,9 +112,8 @@ public void execute() throws MojoExecutionException, MojoFailureException { List resolverRepositories; try { resolverRepositories = resolverUtil.remoteRepositories( - remoteRepositories == null - ? Collections.emptyList() - : Arrays.asList(remoteRepositories.split(","))); + remoteRepositories == null ? Collections.emptyList() : Arrays.asList(remoteRepositories.split(",")), + RepositoryPolicy.UPDATE_POLICY_ALWAYS); } catch (IllegalArgumentException e) { throw new MojoFailureException("Invalid remote repository: " + e.getMessage(), e); } diff --git a/src/main/java/org/apache/maven/plugins/dependency/utils/ResolverUtil.java b/src/main/java/org/apache/maven/plugins/dependency/utils/ResolverUtil.java index 673e6e1ff..c24090198 100644 --- a/src/main/java/org/apache/maven/plugins/dependency/utils/ResolverUtil.java +++ b/src/main/java/org/apache/maven/plugins/dependency/utils/ResolverUtil.java @@ -348,6 +348,23 @@ private Artifact toArtifact(Plugin plugin) { * @return a list of remote repositories */ public List remoteRepositories(List repositories) { + if (repositories == null || repositories.isEmpty()) { + return remoteRepositories(repositories, null); + } + MavenSession mavenSession = mavenSessionProvider.get(); + String updatePolicy = + mavenSession.getRequest().isUpdateSnapshots() ? RepositoryPolicy.UPDATE_POLICY_ALWAYS : null; + return remoteRepositories(repositories, updatePolicy); + } + + /** + * Prepare a remote repositories list for given descriptions and update policy. + * + * @param repositories remote repositories descriptions + * @param updatePolicy repository update policy, or {@code null} to use the Resolver default + * @return a list of remote repositories + */ + public List remoteRepositories(List repositories, String updatePolicy) { MavenSession mavenSession = mavenSessionProvider.get(); List projectRepositories = mavenSession.getCurrentProject().getRemoteProjectRepositories(); @@ -355,8 +372,9 @@ public List remoteRepositories(List repositories) { return projectRepositories; } - List repositoriesList = - repositories.stream().map(this::prepareRemoteRepository).collect(Collectors.toList()); + List repositoriesList = repositories.stream() + .map(repository -> prepareRemoteRepository(repository, updatePolicy)) + .collect(Collectors.toList()); repositoriesList = repositorySystem.newResolutionRepositories(mavenSession.getRepositorySession(), repositoriesList); @@ -367,8 +385,28 @@ public List remoteRepositories(List repositories) { // protected for testing purpose protected RemoteRepository prepareRemoteRepository(String repository) { + String[] items = parseRemoteRepository(repository); + MavenSession mavenSession = mavenSessionProvider.get(); + String updatePolicy = + mavenSession.getRequest().isUpdateSnapshots() ? RepositoryPolicy.UPDATE_POLICY_ALWAYS : null; + return prepareRemoteRepository(repository, items, updatePolicy); + } + + // protected for testing purpose + protected RemoteRepository prepareRemoteRepository(String repository, String updatePolicy) { + return prepareRemoteRepository(repository, parseRemoteRepository(repository), updatePolicy); + } + + private String[] parseRemoteRepository(String repository) { String[] items = Objects.requireNonNull(repository, "repository must be not null") .split("::"); + if (items.length > 3) { + throw new IllegalArgumentException("Invalid repository: " + repository); + } + return items; + } + + private RemoteRepository prepareRemoteRepository(String repository, String[] items, String updatePolicy) { String id = "temp"; String type = null; String url; @@ -400,8 +438,6 @@ protected RemoteRepository prepareRemoteRepository(String repository) { if (checksumPolicy == null) { checksumPolicy = RepositoryPolicy.CHECKSUM_POLICY_WARN; } - String updatePolicy = - mavenSession.getRequest().isUpdateSnapshots() ? RepositoryPolicy.UPDATE_POLICY_ALWAYS : null; RepositoryPolicy repositoryPolicy = new RepositoryPolicy(true, updatePolicy, checksumPolicy); RemoteRepository.Builder builder = new RemoteRepository.Builder(id, type, url); diff --git a/src/test/java/org/apache/maven/plugins/dependency/TestGetMojo.java b/src/test/java/org/apache/maven/plugins/dependency/TestGetMojo.java index a0562c85f..755f26d46 100644 --- a/src/test/java/org/apache/maven/plugins/dependency/TestGetMojo.java +++ b/src/test/java/org/apache/maven/plugins/dependency/TestGetMojo.java @@ -39,6 +39,7 @@ import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.repository.LocalRepository; import org.eclipse.aether.repository.Proxy; +import org.eclipse.aether.repository.RepositoryPolicy; import org.eclipse.aether.util.repository.AuthenticationBuilder; import org.eclipse.aether.util.repository.DefaultAuthenticationSelector; import org.eclipse.aether.util.repository.DefaultProxySelector; @@ -61,6 +62,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -117,6 +119,25 @@ void testRemoteRepositories(GetMojo mojo) throws Exception { mojo.execute(); } + @Test + void testExplicitRemoteRepositoriesAlwaysRefresh() throws Exception { + ResolverUtil resolverUtil = mock(ResolverUtil.class); + when(resolverUtil.remoteRepositories(anyList(), eq(RepositoryPolicy.UPDATE_POLICY_ALWAYS))) + .thenReturn(Collections.emptyList()); + GetMojo mojo = new GetMojo(resolverUtil); + setVariableValueToObject(mojo, "remoteRepositories", "central::default::https://repo.maven.apache.org/maven2"); + mojo.setGroupId("org.apache.maven"); + mojo.setArtifactId("maven-model"); + mojo.setVersion("2.0.9"); + + mojo.execute(); + + verify(resolverUtil) + .remoteRepositories( + Collections.singletonList("central::default::https://repo.maven.apache.org/maven2"), + RepositoryPolicy.UPDATE_POLICY_ALWAYS); + } + /** * Test remote repositories parameter with basic authentication. */ @@ -220,7 +241,8 @@ void testVersionIsRequired() { @Test void testArtifactRetainsSeparatePackagingAndClassifier() throws Exception { ResolverUtil resolverUtil = mock(ResolverUtil.class); - when(resolverUtil.remoteRepositories(anyList())).thenReturn(Collections.emptyList()); + when(resolverUtil.remoteRepositories(anyList(), eq(RepositoryPolicy.UPDATE_POLICY_ALWAYS))) + .thenReturn(Collections.emptyList()); GetMojo mojo = new GetMojo(resolverUtil); setVariableValueToObject(mojo, "artifact", "org.apache.maven:maven-model:2.0.9"); mojo.setPackaging("test-jar"); @@ -237,7 +259,8 @@ void testArtifactRetainsSeparatePackagingAndClassifier() throws Exception { @Test void testArtifactPackagingAndClassifierOverrideSeparateParameters() throws Exception { ResolverUtil resolverUtil = mock(ResolverUtil.class); - when(resolverUtil.remoteRepositories(anyList())).thenReturn(Collections.emptyList()); + when(resolverUtil.remoteRepositories(anyList(), eq(RepositoryPolicy.UPDATE_POLICY_ALWAYS))) + .thenReturn(Collections.emptyList()); GetMojo mojo = new GetMojo(resolverUtil); setVariableValueToObject(mojo, "artifact", "org.apache.maven:maven-model:2.0.9:jar:sources"); mojo.setPackaging("test-jar"); diff --git a/src/test/java/org/apache/maven/plugins/dependency/utils/ResolverUtilTest.java b/src/test/java/org/apache/maven/plugins/dependency/utils/ResolverUtilTest.java index 9b96fe841..70f2f0631 100644 --- a/src/test/java/org/apache/maven/plugins/dependency/utils/ResolverUtilTest.java +++ b/src/test/java/org/apache/maven/plugins/dependency/utils/ResolverUtilTest.java @@ -136,6 +136,19 @@ void prepareRepositoryTest(String repository, String id, String type, String url assertThat(releasePolicy.getChecksumPolicy()).isEqualTo(RepositoryPolicy.CHECKSUM_POLICY_WARN); } + @Test + void prepareRepositoryUsesExplicitUpdatePolicy() { + when(sessionProvider.get()).thenReturn(mavenSession); + when(mavenSession.getRepositorySession()).thenReturn(repositorySystemSession); + + RemoteRepository remoteRepository = resolverUtil.prepareRemoteRepository( + "central::https://repo.maven.apache.org", RepositoryPolicy.UPDATE_POLICY_ALWAYS); + + assertThat(remoteRepository.getPolicy(false).getUpdatePolicy()) + .isEqualTo(RepositoryPolicy.UPDATE_POLICY_ALWAYS); + assertThat(remoteRepository.getPolicy(true).getUpdatePolicy()).isEqualTo(RepositoryPolicy.UPDATE_POLICY_ALWAYS); + } + @Test void prepareRepositoryWithNull() { assertThatCode(() -> resolverUtil.prepareRemoteRepository(null)) From c41e92b69c7910a2725551df1d29d25ff114d6f3 Mon Sep 17 00:00:00 2001 From: Vaclav Haisman Date: Sun, 9 Aug 2026 23:30:58 +0200 Subject: [PATCH 6/6] [MDEP-858] Trim alternate repository fields Normalize comma-separated repository entries in dependency:get and trim each alternate-repository ID, layout, and URL field before constructing Resolver repositories. Fixes #1355. --- .../maven/plugins/dependency/GetMojo.java | 7 ++++++- .../dependency/utils/ResolverUtil.java | 4 ++++ .../maven/plugins/dependency/TestGetMojo.java | 21 +++++++++++++++++++ .../dependency/utils/ResolverUtilTest.java | 5 +++++ 4 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/apache/maven/plugins/dependency/GetMojo.java b/src/main/java/org/apache/maven/plugins/dependency/GetMojo.java index e9dddc581..c2a395297 100644 --- a/src/main/java/org/apache/maven/plugins/dependency/GetMojo.java +++ b/src/main/java/org/apache/maven/plugins/dependency/GetMojo.java @@ -23,6 +23,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; @@ -112,7 +113,11 @@ public void execute() throws MojoExecutionException, MojoFailureException { List resolverRepositories; try { resolverRepositories = resolverUtil.remoteRepositories( - remoteRepositories == null ? Collections.emptyList() : Arrays.asList(remoteRepositories.split(",")), + remoteRepositories == null + ? Collections.emptyList() + : Arrays.stream(remoteRepositories.split(",")) + .map(String::trim) + .collect(Collectors.toList()), RepositoryPolicy.UPDATE_POLICY_ALWAYS); } catch (IllegalArgumentException e) { throw new MojoFailureException("Invalid remote repository: " + e.getMessage(), e); diff --git a/src/main/java/org/apache/maven/plugins/dependency/utils/ResolverUtil.java b/src/main/java/org/apache/maven/plugins/dependency/utils/ResolverUtil.java index c24090198..f39cae18c 100644 --- a/src/main/java/org/apache/maven/plugins/dependency/utils/ResolverUtil.java +++ b/src/main/java/org/apache/maven/plugins/dependency/utils/ResolverUtil.java @@ -399,7 +399,11 @@ protected RemoteRepository prepareRemoteRepository(String repository, String upd private String[] parseRemoteRepository(String repository) { String[] items = Objects.requireNonNull(repository, "repository must be not null") + .trim() .split("::"); + for (int i = 0; i < items.length; i++) { + items[i] = items[i].trim(); + } if (items.length > 3) { throw new IllegalArgumentException("Invalid repository: " + repository); } diff --git a/src/test/java/org/apache/maven/plugins/dependency/TestGetMojo.java b/src/test/java/org/apache/maven/plugins/dependency/TestGetMojo.java index 755f26d46..a1cf069ce 100644 --- a/src/test/java/org/apache/maven/plugins/dependency/TestGetMojo.java +++ b/src/test/java/org/apache/maven/plugins/dependency/TestGetMojo.java @@ -138,6 +138,27 @@ void testExplicitRemoteRepositoriesAlwaysRefresh() throws Exception { RepositoryPolicy.UPDATE_POLICY_ALWAYS); } + @Test + void testRemoteRepositoryEntriesAreTrimmed() throws Exception { + ResolverUtil resolverUtil = mock(ResolverUtil.class); + when(resolverUtil.remoteRepositories(anyList(), eq(RepositoryPolicy.UPDATE_POLICY_ALWAYS))) + .thenReturn(Collections.emptyList()); + GetMojo mojo = new GetMojo(resolverUtil); + setVariableValueToObject( + mojo, "remoteRepositories", " first::default::https://repo1.example , second::https://repo2.example "); + mojo.setGroupId("org.apache.maven"); + mojo.setArtifactId("maven-model"); + mojo.setVersion("2.0.9"); + + mojo.execute(); + + verify(resolverUtil) + .remoteRepositories( + java.util.Arrays.asList( + "first::default::https://repo1.example", "second::https://repo2.example"), + RepositoryPolicy.UPDATE_POLICY_ALWAYS); + } + /** * Test remote repositories parameter with basic authentication. */ diff --git a/src/test/java/org/apache/maven/plugins/dependency/utils/ResolverUtilTest.java b/src/test/java/org/apache/maven/plugins/dependency/utils/ResolverUtilTest.java index 70f2f0631..13877707b 100644 --- a/src/test/java/org/apache/maven/plugins/dependency/utils/ResolverUtilTest.java +++ b/src/test/java/org/apache/maven/plugins/dependency/utils/ResolverUtilTest.java @@ -107,6 +107,11 @@ public static Stream prepareRepositoryTest() { "central::layout2::https://repo.maven.apache.org", "central", "layout2", + "https://repo.maven.apache.org"), + of( + " central :: layout2 :: https://repo.maven.apache.org ", + "central", + "layout2", "https://repo.maven.apache.org")); }