Skip to content

Commit e91f3f3

Browse files
committed
Add support for resolving plugin versions
This commit adds support for resolving the versions of managed plugins in a POM. Closes gh-1438
1 parent 8c5c50e commit e91f3f3

File tree

7 files changed

+262
-88
lines changed

7 files changed

+262
-88
lines changed

initializr-parent/pom.xml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
<java.version>17</java.version>
1818
<commons-compress.version>1.23.0</commons-compress.version>
1919
<commons-text.version>1.10.0</commons-text.version>
20+
<maven.version>3.9.2</maven.version>
2021
<maven-resolver.version>1.9.7</maven-resolver.version>
21-
<maven-resolver-provider.version>3.9.2</maven-resolver-provider.version>
2222
<spring-boot.version>3.1.1</spring-boot.version>
2323
<spring-cloud-contract.version>4.0.2</spring-cloud-contract.version>
2424
</properties>
@@ -40,10 +40,15 @@
4040
<artifactId>commons-text</artifactId>
4141
<version>${commons-text.version}</version>
4242
</dependency>
43+
<dependency>
44+
<groupId>org.apache.maven</groupId>
45+
<artifactId>maven-core</artifactId>
46+
<version>${maven.version}</version>
47+
</dependency>
4348
<dependency>
4449
<groupId>org.apache.maven</groupId>
4550
<artifactId>maven-resolver-provider</artifactId>
46-
<version>${maven-resolver-provider.version}</version>
51+
<version>${maven.version}</version>
4752
</dependency>
4853
<dependency>
4954
<groupId>org.apache.maven.resolver</groupId>

initializr-version-resolver/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121
</scm>
2222

2323
<dependencies>
24+
<dependency>
25+
<groupId>org.apache.maven</groupId>
26+
<artifactId>maven-core</artifactId>
27+
</dependency>
2428
<dependency>
2529
<groupId>org.apache.maven</groupId>
2630
<artifactId>maven-resolver-provider</artifactId>
Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,36 +22,51 @@
2222
import java.util.List;
2323
import java.util.Map;
2424

25+
import org.apache.maven.model.Model;
26+
import org.apache.maven.model.building.DefaultModelBuilder;
27+
import org.apache.maven.model.building.DefaultModelBuilderFactory;
28+
import org.apache.maven.model.building.DefaultModelBuildingRequest;
29+
import org.apache.maven.model.building.ModelBuildingException;
30+
import org.apache.maven.model.resolution.ModelResolver;
31+
import org.apache.maven.project.ProjectBuildingRequest;
32+
import org.apache.maven.project.ProjectModelResolver;
2533
import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
2634
import org.eclipse.aether.DefaultRepositorySystemSession;
2735
import org.eclipse.aether.RepositorySystem;
2836
import org.eclipse.aether.RepositorySystemSession;
37+
import org.eclipse.aether.RequestTrace;
2938
import org.eclipse.aether.artifact.DefaultArtifact;
3039
import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
3140
import org.eclipse.aether.graph.Dependency;
3241
import org.eclipse.aether.impl.DefaultServiceLocator;
42+
import org.eclipse.aether.impl.RemoteRepositoryManager;
3343
import org.eclipse.aether.internal.impl.DefaultRepositorySystem;
3444
import org.eclipse.aether.repository.LocalRepository;
3545
import org.eclipse.aether.repository.LocalRepositoryManager;
3646
import org.eclipse.aether.repository.RemoteRepository;
3747
import org.eclipse.aether.resolution.ArtifactDescriptorException;
3848
import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
3949
import org.eclipse.aether.resolution.ArtifactDescriptorResult;
50+
import org.eclipse.aether.resolution.ArtifactRequest;
51+
import org.eclipse.aether.resolution.ArtifactResolutionException;
52+
import org.eclipse.aether.resolution.ArtifactResult;
4053
import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
4154
import org.eclipse.aether.spi.connector.transport.TransporterFactory;
4255
import org.eclipse.aether.spi.locator.ServiceLocator;
4356
import org.eclipse.aether.transport.http.HttpTransporterFactory;
4457
import org.eclipse.aether.util.repository.SimpleArtifactDescriptorPolicy;
4558

4659
/**
47-
* A {@link DependencyManagementVersionResolver} that resolves versions using Maven
48-
* Resolver. Maven's default {@link LocalRepositoryManager} implementation is not
49-
* thread-safe. To avoid corruption of the local repository, interaction with the
50-
* {@link RepositorySystem} is single-threaded.
60+
* A {@link MavenVersionResolver} that resolves versions using Maven Resolver. Maven's
61+
* default {@link LocalRepositoryManager} implementation is not thread-safe. To avoid
62+
* corruption of the local repository, interaction with the {@link RepositorySystem} is
63+
* single-threaded.
5164
*
5265
* @author Andy Wilkinson
66+
* @author Stephane Nicoll
5367
*/
54-
class MavenResolverDependencyManagementVersionResolver implements DependencyManagementVersionResolver {
68+
@SuppressWarnings("removal")
69+
class DefaultMavenVersionResolver implements MavenVersionResolver, DependencyManagementVersionResolver {
5570

5671
private static final RemoteRepository mavenCentral = new RemoteRepository.Builder("central", "default",
5772
"https://repo1.maven.org/maven2")
@@ -72,9 +87,11 @@ class MavenResolverDependencyManagementVersionResolver implements DependencyMana
7287

7388
private final RepositorySystemSession repositorySystemSession;
7489

90+
private final RemoteRepositoryManager remoteRepositoryManager;
91+
7592
private final RepositorySystem repositorySystem;
7693

77-
MavenResolverDependencyManagementVersionResolver(Path cacheLocation) {
94+
DefaultMavenVersionResolver(Path cacheLocation) {
7895
ServiceLocator serviceLocator = createServiceLocator();
7996
DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession();
8097
session.setArtifactDescriptorPolicy(new SimpleArtifactDescriptorPolicy(false, false));
@@ -84,10 +101,16 @@ class MavenResolverDependencyManagementVersionResolver implements DependencyMana
84101
session.setUserProperties(System.getProperties());
85102
session.setReadOnly();
86103
this.repositorySystemSession = session;
104+
this.remoteRepositoryManager = serviceLocator.getService(RemoteRepositoryManager.class);
87105
}
88106

89107
@Override
90108
public Map<String, String> resolve(String groupId, String artifactId, String version) {
109+
return resolveDependencies(groupId, artifactId, version);
110+
}
111+
112+
@Override
113+
public Map<String, String> resolveDependencies(String groupId, String artifactId, String version) {
91114
ArtifactDescriptorResult bom = resolveBom(groupId, artifactId, version);
92115
Map<String, String> managedVersions = new HashMap<>();
93116
bom.getManagedDependencies()
@@ -98,6 +121,18 @@ public Map<String, String> resolve(String groupId, String artifactId, String ver
98121
return managedVersions;
99122
}
100123

124+
@Override
125+
public Map<String, String> resolvePlugins(String groupId, String artifactId, String version) {
126+
Model model = buildEffectiveModel(groupId, artifactId, version);
127+
Map<String, String> managedPluginVersions = new HashMap<>();
128+
model.getBuild()
129+
.getPluginManagement()
130+
.getPlugins()
131+
.forEach((plugin) -> managedPluginVersions.putIfAbsent(plugin.getGroupId() + ":" + plugin.getArtifactId(),
132+
plugin.getVersion()));
133+
return managedPluginVersions;
134+
}
135+
101136
private ArtifactDescriptorResult resolveBom(String groupId, String artifactId, String version) {
102137
synchronized (this.monitor) {
103138
try {
@@ -112,6 +147,40 @@ private ArtifactDescriptorResult resolveBom(String groupId, String artifactId, S
112147
}
113148
}
114149

150+
private Model buildEffectiveModel(String groupId, String artifactId, String version) {
151+
try {
152+
ArtifactResult bom = resolvePom(groupId, artifactId, version);
153+
RequestTrace requestTrace = new RequestTrace(null);
154+
155+
ModelResolver modelResolver = new ProjectModelResolver(this.repositorySystemSession, requestTrace,
156+
this.repositorySystem, this.remoteRepositoryManager, repositories,
157+
ProjectBuildingRequest.RepositoryMerging.POM_DOMINANT, null);
158+
DefaultModelBuildingRequest modelBuildingRequest = new DefaultModelBuildingRequest();
159+
modelBuildingRequest.setSystemProperties(System.getProperties());
160+
modelBuildingRequest.setPomFile(bom.getArtifact().getFile());
161+
modelBuildingRequest.setModelResolver(modelResolver);
162+
DefaultModelBuilder modelBuilder = new DefaultModelBuilderFactory().newInstance();
163+
return modelBuilder.build(modelBuildingRequest).getEffectiveModel();
164+
}
165+
catch (ModelBuildingException ex) {
166+
throw new IllegalStateException(
167+
"Model for '" + groupId + ":" + artifactId + ":" + version + "' could not be built", ex);
168+
}
169+
}
170+
171+
private ArtifactResult resolvePom(String groupId, String artifactId, String version) {
172+
synchronized (this.monitor) {
173+
try {
174+
return this.repositorySystem.resolveArtifact(this.repositorySystemSession, new ArtifactRequest(
175+
new DefaultArtifact(groupId, artifactId, "pom", version), repositories, null));
176+
}
177+
catch (ArtifactResolutionException ex) {
178+
throw new IllegalStateException(
179+
"Pom '" + groupId + ":" + artifactId + ":" + version + "' could not be resolved", ex);
180+
}
181+
}
182+
}
183+
115184
private static ServiceLocator createServiceLocator() {
116185
DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator();
117186
locator.addService(RepositorySystem.class, DefaultRepositorySystem.class);

initializr-version-resolver/src/main/java/io/spring/initializr/versionresolver/DependencyManagementVersionResolver.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -24,7 +24,9 @@
2424
* managed dependencies of a Maven bom. Implementations must be thread-safe.
2525
*
2626
* @author Andy Wilkinson
27+
* @deprecated as of 0.20.0 in favor of {@link MavenVersionResolver}.
2728
*/
29+
@Deprecated(since = "0.20.0", forRemoval = true)
2830
public interface DependencyManagementVersionResolver {
2931

3032
/**
@@ -47,7 +49,7 @@ public interface DependencyManagementVersionResolver {
4749
* @return the resolver
4850
*/
4951
static DependencyManagementVersionResolver withCacheLocation(Path location) {
50-
return new MavenResolverDependencyManagementVersionResolver(location);
52+
return new DefaultMavenVersionResolver(location);
5153
}
5254

5355
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright 2012-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.spring.initializr.versionresolver;
18+
19+
import java.nio.file.Path;
20+
import java.util.Map;
21+
22+
/**
23+
* A {@code MavenVersionResolver} is used to resolve the versions of managed dependencies
24+
* or plugins. Implementations must be thread-safe.
25+
*
26+
* @author Andy Wilkinson
27+
* @author Stephane Nicoll
28+
*/
29+
public interface MavenVersionResolver {
30+
31+
/**
32+
* Resolves the versions in the managed dependencies of the bom identified by the
33+
* given {@code groupId}, {@code artifactId}, and {@code version}.
34+
* @param groupId bom group ID
35+
* @param artifactId bom artifact ID
36+
* @param version bom version
37+
* @return the managed dependencies as a map of {@code groupId:artifactId} to
38+
* {@code version}
39+
*/
40+
Map<String, String> resolveDependencies(String groupId, String artifactId, String version);
41+
42+
/**
43+
* Resolves the versions in the managed plugins of the pom identified by the given
44+
* {@code groupId}, {@code artifactId}, and {@code version}.
45+
* @param groupId pom group ID
46+
* @param artifactId pom artifact ID
47+
* @param version pom version
48+
* @return the managed plugins as a map of {@code groupId:artifactId} to
49+
* {@code version}
50+
*/
51+
Map<String, String> resolvePlugins(String groupId, String artifactId, String version);
52+
53+
/**
54+
* Creates a new {@code MavenVersionResolver} that uses the given {@code location} for
55+
* its local cache. To avoid multiple instances attempting to write to the same
56+
* location cache, callers should ensure that a unique location is used. The returned
57+
* resolver can then be used concurrently by multiple threads.
58+
* @param location cache location
59+
* @return the resolver
60+
*/
61+
static MavenVersionResolver withCacheLocation(Path location) {
62+
return new DefaultMavenVersionResolver(location);
63+
}
64+
65+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
* Copyright 2012-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.spring.initializr.versionresolver;
18+
19+
import java.nio.file.Path;
20+
import java.util.Map;
21+
22+
import org.junit.jupiter.api.BeforeEach;
23+
import org.junit.jupiter.api.Test;
24+
import org.junit.jupiter.api.io.TempDir;
25+
26+
import static org.assertj.core.api.Assertions.assertThat;
27+
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
28+
29+
/**
30+
* Tests for {@link DefaultMavenVersionResolver}.
31+
*
32+
* @author Andy Wilkinson
33+
* @author Stephane Nicoll
34+
*/
35+
class DefaultMavenVersionResolverTests {
36+
37+
private MavenVersionResolver resolver;
38+
39+
@BeforeEach
40+
void createResolver(@TempDir Path temp) {
41+
this.resolver = new DefaultMavenVersionResolver(temp);
42+
}
43+
44+
@Test
45+
void resolveDependenciesForSpringBoot() {
46+
Map<String, String> versions = this.resolver.resolveDependencies("org.springframework.boot",
47+
"spring-boot-dependencies", "2.1.5.RELEASE");
48+
assertThat(versions).containsEntry("org.flywaydb:flyway-core", "5.2.4");
49+
}
50+
51+
@Test
52+
void resolveDependenciesForSpringCloud() {
53+
Map<String, String> versions = this.resolver.resolveDependencies("org.springframework.cloud",
54+
"spring-cloud-dependencies", "Greenwich.SR1");
55+
assertThat(versions).containsEntry("com.netflix.ribbon:ribbon", "2.3.0");
56+
}
57+
58+
@Test
59+
void resolveDependenciesUsingMilestones() {
60+
Map<String, String> versions = this.resolver.resolveDependencies("org.springframework.boot",
61+
"spring-boot-dependencies", "2.2.0.M3");
62+
assertThat(versions).containsEntry("org.flywaydb:flyway-core", "5.2.4");
63+
}
64+
65+
@Test
66+
void resolveDependenciesUsingSnapshots() {
67+
Map<String, String> versions = this.resolver.resolveDependencies("org.springframework.boot",
68+
"spring-boot-dependencies", "2.4.0-SNAPSHOT");
69+
assertThat(versions).isNotEmpty();
70+
}
71+
72+
@Test
73+
void resolveDependenciesForNonExistentDependency() {
74+
assertThatIllegalStateException()
75+
.isThrownBy(() -> this.resolver.resolveDependencies("org.springframework.boot", "spring-boot-bom", "1.0"))
76+
.withMessage("Bom 'org.springframework.boot:spring-boot-bom:1.0' could not be resolved");
77+
}
78+
79+
@Test
80+
void resolvePluginsForSpringBoot() {
81+
Map<String, String> versions = this.resolver.resolvePlugins("org.springframework.boot",
82+
"spring-boot-starter-parent", "3.1.1");
83+
assertThat(versions).containsEntry("org.springframework.boot:spring-boot-maven-plugin", "3.1.1");
84+
}
85+
86+
@Test
87+
void resolvePluginsUsingMilestones() {
88+
Map<String, String> versions = this.resolver.resolvePlugins("org.springframework.boot",
89+
"spring-boot-dependencies", "2.2.0.M3");
90+
assertThat(versions).containsEntry("org.springframework.boot:spring-boot-maven-plugin", "2.2.0.M3");
91+
}
92+
93+
@Test
94+
void resolvePluginsUsingSnapshots() {
95+
Map<String, String> versions = this.resolver.resolvePlugins("org.springframework.boot",
96+
"spring-boot-dependencies", "2.4.0-SNAPSHOT");
97+
assertThat(versions).isNotEmpty();
98+
}
99+
100+
@Test
101+
void resolvePluginsForNonExistentDependency() {
102+
assertThatIllegalStateException()
103+
.isThrownBy(() -> this.resolver.resolvePlugins("org.springframework.boot", "spring-boot-bom", "1.0"))
104+
.withMessage("Pom 'org.springframework.boot:spring-boot-bom:1.0' could not be resolved");
105+
}
106+
107+
}

0 commit comments

Comments
 (0)