Skip to content

Commit 760272e

Browse files
bmuschkotimtebeek
andauthored
First Maven dependency declaration wins for equal distance (#6002)
* First Maven dependency declaration wins for equal distance * Apply suggestions from code review --------- Co-authored-by: Tim te Beek <tim@moderne.io>
1 parent 4999c71 commit 760272e

3 files changed

Lines changed: 73 additions & 7 deletions

File tree

rewrite-maven/src/main/java/org/openrewrite/maven/tree/ResolvedPom.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -994,13 +994,13 @@ public List<ResolvedDependency> resolveDependencies(Scope scope, Map<GroupArtifa
994994
MavenPomDownloader downloader, ExecutionContext ctx) throws MavenDownloadingExceptions {
995995
List<ResolvedDependency> dependencies = new ArrayList<>();
996996

997-
Map<GroupArtifact, DependencyAndDependent> rootDependencies = new HashMap<>();
997+
Map<GroupArtifact, DependencyAndDependent> rootDependencies = new LinkedHashMap<>();
998998
for (Dependency requestedDependency : getRequestedDependencies()) {
999999
Dependency d = getValues(requestedDependency, 0);
10001000
Scope dScope = Scope.fromName(d.getScope());
10011001
if (dScope == scope || dScope.transitiveOf(scope) == scope) {
1002-
// TODO can we always use the Map.put approach? Using the latest one is Maven specific, but this resolving is also used for gradle which does use highest version.
1003-
// We could introduce a ResolutionStrategy to handle this and use Map.merge where we take later occurring one for LAST_WINS/MAVEN and higher version one for LATEST_WINS/GRADLE
1002+
// For direct dependencies that are duplicated, last declaration wins
1003+
// TODO: We could introduce a ResolutionStrategy to handle this differently for Gradle which uses highest version
10041004
rootDependencies.put(d.getGav().asGroupArtifact(), new DependencyAndDependent(requestedDependency, Scope.Compile, null, requestedDependency, this));
10051005
}
10061006
}
@@ -1009,7 +1009,7 @@ public List<ResolvedDependency> resolveDependencies(Scope scope, Map<GroupArtifa
10091009
int depth = 0;
10101010
Collection<DependencyAndDependent> dependenciesAtDepth = rootDependencies.values();
10111011
while (!dependenciesAtDepth.isEmpty()) {
1012-
List<DependencyAndDependent> dependenciesAtNextDepth = new ArrayList<>();
1012+
Map<GroupArtifact, DependencyAndDependent> dependenciesAtNextDepthMap = new LinkedHashMap<>();
10131013

10141014
for (DependencyAndDependent dd : dependenciesAtDepth) {
10151015
// First get the dependency (relative to the pom it was defined in)
@@ -1139,15 +1139,17 @@ public List<ResolvedDependency> resolveDependencies(Scope scope, Map<GroupArtifa
11391139

11401140
Scope d2Scope = getDependencyScope(d2, resolvedPom);
11411141
if (d2Scope.isInClasspathOf(dd.getScope())) {
1142-
dependenciesAtNextDepth.add(new DependencyAndDependent(d2, d2Scope, resolved, dd.getRootDependent(), resolvedPom));
1142+
// For transitive dependencies at same depth, first parent declaration wins
1143+
GroupArtifact d2Ga = new GroupArtifact(d2.getGroupId() == null ? "" : d2.getGroupId(), d2.getArtifactId());
1144+
dependenciesAtNextDepthMap.putIfAbsent(d2Ga, new DependencyAndDependent(d2, d2Scope, resolved, dd.getRootDependent(), resolvedPom));
11431145
}
11441146
}
11451147
} catch (MavenDownloadingException e) {
11461148
exceptions = MavenDownloadingExceptions.append(exceptions, e.setRoot(dd.getRootDependent().getGav()));
11471149
}
11481150
}
11491151

1150-
dependenciesAtDepth = dependenciesAtNextDepth;
1152+
dependenciesAtDepth = dependenciesAtNextDepthMap.values();
11511153
depth++;
11521154
}
11531155

rewrite-maven/src/test/java/org/openrewrite/maven/AddManagedDependencyTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,7 @@ void doesNotDowngradeVersionIfTransitiveDependencyAppearsOnceInTree() {
397397
}
398398

399399
@ParameterizedTest
400-
@ValueSource(strings = {"2.14.0", "2.15.3"})
400+
@ValueSource(strings = {"2.14.0", "2.15.3", "2.18.0"})
401401
void doesNotDowngradeVersionIfTransitiveDependencyAppearsInTreeWithDifferentVersions(String requestedVersion) {
402402
rewriteRun(
403403
spec -> spec.recipe(new AddManagedDependency("com.fasterxml.jackson.core", "jackson-databind", requestedVersion, null,

rewrite-maven/src/test/java/org/openrewrite/maven/MavenParserTest.java

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@
2929
import org.junit.jupiter.api.Nested;
3030
import org.junit.jupiter.api.Test;
3131
import org.junit.jupiter.params.ParameterizedTest;
32+
import org.junit.jupiter.params.provider.Arguments;
3233
import org.junit.jupiter.params.provider.CsvSource;
34+
import org.junit.jupiter.params.provider.MethodSource;
3335
import org.junit.jupiter.params.provider.ValueSource;
3436
import org.openrewrite.*;
3537
import org.openrewrite.maven.http.OkHttpSender;
@@ -45,6 +47,7 @@
4547
import java.util.List;
4648
import java.util.Map;
4749
import java.util.Objects;
50+
import java.util.stream.Stream;
4851

4952
import static java.util.stream.Collectors.groupingBy;
5053
import static org.assertj.core.api.Assertions.*;
@@ -4097,6 +4100,67 @@ void lastListedDependencyIsUsedForTransitiveScope(String firstVersion, String se
40974100
);
40984101
}
40994102

4103+
@MethodSource("jacksonDependencies")
4104+
@ParameterizedTest
4105+
void firstDeclarationWinsForEqualDistanceOfTransitiveDependencies(String dependencies, String resolvedTransitiveVersion) {
4106+
rewriteRun(
4107+
pomXml(
4108+
//language=xml
4109+
"""
4110+
<project>
4111+
<groupId>com.mycompany.app</groupId>
4112+
<artifactId>my-app</artifactId>
4113+
<version>1</version>
4114+
<dependencies>
4115+
%s
4116+
</dependencies>
4117+
</project>
4118+
""".formatted(dependencies),
4119+
spec -> spec.afterRecipe(pom -> {
4120+
MavenResolutionResult resolution = pom.getMarkers().findFirst(MavenResolutionResult.class).orElseThrow();
4121+
assertThat(resolution.findDependencies("com.fasterxml.jackson.core", "jackson-databind", Scope.Compile))
4122+
.hasSize(1)
4123+
.extracting(ResolvedDependency::getGav)
4124+
.extracting(ResolvedGroupArtifactVersion::getVersion)
4125+
.containsExactly(resolvedTransitiveVersion);
4126+
})
4127+
)
4128+
);
4129+
}
4130+
4131+
private static Stream<Arguments> jacksonDependencies() {
4132+
return Stream.of(
4133+
Arguments.of(
4134+
"""
4135+
<dependency>
4136+
<groupId>com.fasterxml.jackson.dataformat</groupId>
4137+
<artifactId>jackson-dataformat-xml</artifactId>
4138+
<version>2.18.0</version>
4139+
</dependency>
4140+
<dependency>
4141+
<groupId>com.fasterxml.jackson.datatype</groupId>
4142+
<artifactId>jackson-datatype-jsr310</artifactId>
4143+
<version>2.15.3</version>
4144+
</dependency>
4145+
""",
4146+
"2.18.0"),
4147+
Arguments.of(
4148+
"""
4149+
<dependency>
4150+
<groupId>com.fasterxml.jackson.datatype</groupId>
4151+
<artifactId>jackson-datatype-jsr310</artifactId>
4152+
<version>2.15.3</version>
4153+
</dependency>
4154+
<dependency>
4155+
<groupId>com.fasterxml.jackson.dataformat</groupId>
4156+
<artifactId>jackson-dataformat-xml</artifactId>
4157+
<version>2.18.0</version>
4158+
</dependency>
4159+
""",
4160+
"2.15.3")
4161+
);
4162+
}
4163+
41004164
@ParameterizedTest
41014165
@ValueSource(strings = {"latest", "release"})
41024166
void latestOrReleaseVersionInDependencyManagement(String version) {

0 commit comments

Comments
 (0)