Skip to content

Commit b008431

Browse files
authored
#455 Merge scopes when adding a dependency that exists but with different scope (#533)
1 parent a4c1250 commit b008431

File tree

3 files changed

+254
-1
lines changed

3 files changed

+254
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package org.springframework.sbm.build.api;
2+
3+
import lombok.NonNull;
4+
import org.apache.commons.lang3.tuple.Pair;
5+
import org.openrewrite.maven.tree.Scope;
6+
7+
import java.util.*;
8+
import java.util.stream.Collectors;
9+
10+
import static org.openrewrite.maven.tree.Scope.*;
11+
12+
/**
13+
* Resolve the dependency change spec. Their is a ascending
14+
* order of the scopes where a higher order covers its predecessor
15+
* and more. Below is the ascending order of the scopes.
16+
* <p>
17+
* Test (lowest), Runtime, Provided, Compile (highest)
18+
* <p>
19+
* Based on the above scope, the following rule decides the fate
20+
* of a proposed dependency change spec.
21+
* <p>
22+
* Rule 1 :- If the proposed dependency already exists
23+
* transitively but its scope is lesser than the proposed
24+
* scope, the proposed dependency will be added to the
25+
* build file.
26+
* <p>
27+
* Rule 2 :- If the proposed dependency already declared
28+
* directly but its scope is lesser than the the proposed
29+
* scope, the existing dependency will be replaced.
30+
* <p>
31+
* Rule 3 :- If there is no matching dependency already exists
32+
* the proposed dependency will beadded.
33+
*/
34+
public class DependencyChangeResolver {
35+
36+
public static final EnumSet<Scope> ALL_SCOPES = EnumSet.range(None, System);
37+
@NonNull
38+
private BuildFile buildFile;
39+
private @NonNull Dependency proposedChangeSpec;
40+
private List<Dependency> potentialMatches;
41+
42+
private static Map<Scope, List<Scope>> UPGRADE_GRAPH = new HashMap<>();
43+
44+
static {
45+
// For a given scope (key), SBM will upgrade ( upsert) if any of the listed scope
46+
// exists in the directly included dependencies
47+
UPGRADE_GRAPH.put(Compile, List.of(Test, Provided, Runtime));
48+
UPGRADE_GRAPH.put(Provided, List.of(Test, Runtime));
49+
UPGRADE_GRAPH.put(Runtime, List.of(Test));
50+
UPGRADE_GRAPH.put(Test, Collections.emptyList());
51+
}
52+
53+
public DependencyChangeResolver(BuildFile buildFile, @NonNull Dependency proposedChangeSpec) {
54+
this.buildFile = buildFile;
55+
this.proposedChangeSpec = proposedChangeSpec;
56+
this.potentialMatches = buildFile.getEffectiveDependencies()
57+
.stream()
58+
.filter(d -> d.equals(this.proposedChangeSpec))
59+
.collect(Collectors.toList());
60+
}
61+
62+
/**
63+
* Return a pair of dependencies to be removed ( left) and added ( right)
64+
* @return
65+
*/
66+
public Pair<List<Dependency>, Optional<Dependency>> apply() {
67+
if (potentialMatches.isEmpty())
68+
return Pair.of(Collections.emptyList(), Optional.of(proposedChangeSpec));
69+
70+
Scope proposedDependencyScope = Scope.fromName(proposedChangeSpec.getScope());
71+
List<Scope> supersededScopes = UPGRADE_GRAPH.get(proposedDependencyScope);
72+
73+
Optional<Dependency> right = potentialMatches
74+
.stream()
75+
.filter(d -> supersededScopes.contains(fromName(d.getScope())))
76+
.findAny()
77+
.map(any -> proposedChangeSpec);
78+
79+
List<Dependency> left = buildFile
80+
.getDeclaredDependencies(ALL_SCOPES.toArray(new Scope[0]))
81+
.stream()
82+
.filter(proposedChangeSpec::equals)
83+
.collect(Collectors.toList());
84+
85+
return Pair.of(left, right);
86+
87+
}
88+
89+
}

components/sbm-core/src/main/java/org/springframework/sbm/build/migration/actions/AddDependencies.java

+23-1
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
package org.springframework.sbm.build.migration.actions;
1717

1818
import lombok.AllArgsConstructor;
19+
import org.apache.commons.lang3.tuple.Pair;
1920
import org.springframework.sbm.build.api.BuildFile;
2021
import org.springframework.sbm.build.api.Dependency;
22+
import org.springframework.sbm.build.api.DependencyChangeResolver;
2123
import org.springframework.sbm.engine.recipe.AbstractAction;
2224
import org.springframework.sbm.engine.context.ProjectContext;
2325
import lombok.Getter;
@@ -29,6 +31,9 @@
2931
import javax.validation.Valid;
3032
import java.util.ArrayList;
3133
import java.util.List;
34+
import java.util.Optional;
35+
import java.util.function.Predicate;
36+
import java.util.stream.Collectors;
3237

3338
@Setter
3439
@Getter
@@ -51,6 +56,23 @@ public AddDependencies(List<Dependency> dependencies) {
5156
@Override
5257
public void apply(ProjectContext context) {
5358
BuildFile buildFile = context.getBuildFile();
54-
buildFile.addDependencies(dependencies);
59+
List<Pair<List<Dependency>, Optional<Dependency>>> pairs = dependencies.stream()
60+
.map(d -> new DependencyChangeResolver(buildFile, d))
61+
.map(DependencyChangeResolver::apply)
62+
.collect(Collectors.toList());
63+
64+
List<Dependency> removeList = pairs.stream()
65+
.map(Pair::getLeft)
66+
.flatMap(List::stream)
67+
.collect(Collectors.toList());
68+
69+
List<Dependency> addList = pairs.stream()
70+
.map(Pair::getRight)
71+
.filter(Optional::isPresent)
72+
.map(Optional::get)
73+
.collect(Collectors.toList());
74+
75+
buildFile.removeDependencies(removeList);
76+
buildFile.addDependencies(addList);
5577
}
5678
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
package org.springframework.sbm.build.api;
2+
3+
import org.apache.commons.lang3.tuple.Pair;
4+
import org.junit.jupiter.api.Test;
5+
import org.junit.jupiter.api.extension.ExtendWith;
6+
import org.mockito.Mock;
7+
import org.mockito.junit.jupiter.MockitoExtension;
8+
import org.springframework.sbm.build.impl.OpenRewriteMavenBuildFile;
9+
10+
import java.util.Collections;
11+
import java.util.List;
12+
import java.util.Optional;
13+
import java.util.Set;
14+
15+
import static org.assertj.core.api.Assertions.assertThat;
16+
import static org.mockito.Mockito.any;
17+
import static org.mockito.Mockito.when;
18+
19+
@ExtendWith(MockitoExtension.class)
20+
public class DependencyChangeResolverTest {
21+
22+
@Mock
23+
OpenRewriteMavenBuildFile buildFile;
24+
25+
@Test
26+
public void givenBuildFile_addNewDependency_withScopeCompile_expectNewDependencyAdded(){
27+
Dependency proposedDependency =
28+
new Dependency.DependencyBuilder()
29+
.groupId("org.springframework.sbm")
30+
.artifactId("directDependency")
31+
.version("1.0")
32+
.scope("compile")
33+
.build();
34+
35+
when(buildFile.getEffectiveDependencies())
36+
.thenReturn(Collections.emptySet());
37+
38+
Pair<List<Dependency>, Optional<Dependency>> pair =
39+
new DependencyChangeResolver(buildFile, proposedDependency).apply();
40+
41+
assertThat(pair.getLeft().isEmpty());
42+
assertThat(pair.getRight().isPresent());
43+
assertThat(pair.getRight().get().equals(proposedDependency));
44+
}
45+
46+
@Test
47+
public void givenBuildFile_addExistingTransitiveDependency_withLowerScope_expectNoOp(){
48+
Dependency proposedDependency =
49+
new Dependency.DependencyBuilder()
50+
.groupId("org.springframework.sbm")
51+
.artifactId("directDependency")
52+
.version("1.0")
53+
.scope("test")
54+
.build();
55+
56+
Dependency existingDependency =
57+
new Dependency.DependencyBuilder()
58+
.groupId("org.springframework.sbm")
59+
.artifactId("directDependency")
60+
.version("1.0")
61+
.scope("compile")
62+
.build();
63+
64+
when(buildFile.getEffectiveDependencies())
65+
.thenReturn(Set.of(existingDependency));
66+
67+
when(buildFile.getDeclaredDependencies(any()))
68+
.thenReturn(Collections.emptyList());
69+
70+
Pair<List<Dependency>, Optional<Dependency>> pair =
71+
new DependencyChangeResolver(buildFile, proposedDependency).apply();
72+
73+
assertThat(pair.getLeft().isEmpty());
74+
assertThat(pair.getRight().isEmpty());
75+
}
76+
77+
@Test
78+
public void givenBuildFile_addExistingTransitiveDependency_withHigherScope_expectNoOp(){
79+
Dependency proposedDependency =
80+
new Dependency.DependencyBuilder()
81+
.groupId("org.springframework.sbm")
82+
.artifactId("directDependency")
83+
.version("1.0")
84+
.scope("compile")
85+
.build();
86+
87+
Dependency existingDependency =
88+
new Dependency.DependencyBuilder()
89+
.groupId("org.springframework.sbm")
90+
.artifactId("directDependency")
91+
.version("1.0")
92+
.scope("test")
93+
.build();
94+
95+
when(buildFile.getEffectiveDependencies())
96+
.thenReturn(Set.of(existingDependency));
97+
98+
when(buildFile.getDeclaredDependencies(any()))
99+
.thenReturn(Collections.emptyList());
100+
101+
Pair<List<Dependency>, Optional<Dependency>> pair =
102+
new DependencyChangeResolver(buildFile, proposedDependency).apply();
103+
104+
assertThat(pair.getLeft().isEmpty());
105+
assertThat(pair.getRight().isPresent());
106+
assertThat(pair.getRight().get().equals(proposedDependency));
107+
}
108+
109+
@Test
110+
public void givenBuildFile_addExistingDirectDependency_withHigherScope_expectNoOp(){
111+
Dependency proposedDependency =
112+
new Dependency.DependencyBuilder()
113+
.groupId("org.springframework.sbm")
114+
.artifactId("directDependency")
115+
.version("1.0")
116+
.scope("compile")
117+
.build();
118+
119+
Dependency existingDependency =
120+
new Dependency.DependencyBuilder()
121+
.groupId("org.springframework.sbm")
122+
.artifactId("directDependency")
123+
.version("1.0")
124+
.scope("test")
125+
.build();
126+
127+
when(buildFile.getEffectiveDependencies())
128+
.thenReturn(Set.of(existingDependency));
129+
130+
when(buildFile.getDeclaredDependencies(any()))
131+
.thenReturn(List.of(existingDependency));
132+
133+
Pair<List<Dependency>, Optional<Dependency>> pair =
134+
new DependencyChangeResolver(buildFile, proposedDependency).apply();
135+
136+
assertThat(!pair.getLeft().isEmpty());
137+
assertThat(pair.getLeft().get(0).getScope().equals("test"));
138+
assertThat(pair.getRight().isPresent());
139+
assertThat(pair.getRight().get().getScope().equals("compile"));
140+
}
141+
142+
}

0 commit comments

Comments
 (0)