Skip to content

Commit 95c5177

Browse files
authored
Merge pull request #43 from frengor/gradle-transitive
Improve transitive dependencies resolution
2 parents 61d275e + 30f4e8b commit 95c5177

File tree

6 files changed

+125
-28
lines changed

6 files changed

+125
-28
lines changed

core/src/main/java/com/alessiodp/libby/Library.java

+5-9
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55
import org.jetbrains.annotations.NotNull;
66
import org.jetbrains.annotations.Nullable;
77

8-
98
import java.util.Base64;
109
import java.util.Collection;
1110
import java.util.Collections;
1211
import java.util.LinkedList;
1312

13+
import static com.alessiodp.libby.Util.craftPartialPath;
14+
import static com.alessiodp.libby.Util.craftPath;
1415
import static com.alessiodp.libby.Util.hexStringToByteArray;
1516
import static com.alessiodp.libby.Util.replaceWithDots;
1617
import static java.util.Objects.requireNonNull;
@@ -146,13 +147,8 @@ private Library(@Nullable Collection<String> urls,
146147
this.checksum = checksum;
147148
this.relocations = relocations != null ? Collections.unmodifiableList(new LinkedList<>(relocations)) : Collections.emptyList();
148149

149-
this.partialPath = this.groupId.replace('.', '/') + '/' + this.artifactId + '/' + version + '/';
150-
String path = this.partialPath + this.artifactId + '-' + version;
151-
if (hasClassifier()) {
152-
path += '-' + classifier;
153-
}
154-
155-
this.path = path + ".jar";
150+
this.partialPath = craftPartialPath(this.artifactId, this.groupId, version);
151+
this.path = craftPath(this.partialPath, this.artifactId, this.version, this.classifier);
156152

157153
this.repositories = repositories != null ? Collections.unmodifiableList(new LinkedList<>(repositories)) : Collections.emptyList();
158154
relocatedPath = hasRelocations() ? path + "-relocated-" + Math.abs(this.relocations.hashCode()) + ".jar" : null;
@@ -228,7 +224,7 @@ public String getClassifier() {
228224
* @return true if library has classifier, false otherwise
229225
*/
230226
public boolean hasClassifier() {
231-
return classifier != null;
227+
return classifier != null && !classifier.isEmpty();
232228
}
233229

234230
/**

core/src/main/java/com/alessiodp/libby/LibraryManager.java

+1-5
Original file line numberDiff line numberDiff line change
@@ -424,11 +424,7 @@ protected String getURLFromMetadata(@NotNull InputStream inputStream, @NotNull L
424424
version = version.substring(0, version.length() - "-SNAPSHOT".length());
425425
}
426426

427-
String url = library.getPartialPath() + library.getArtifactId() + '-' + version + '-' + timestamp + '-' + buildNumber;
428-
if (library.hasClassifier()) {
429-
url += '-' + library.getClassifier();
430-
}
431-
return url + ".jar";
427+
return Util.craftPath(library.getPartialPath(), library.getArtifactId(), version + '-' + timestamp + '-' + buildNumber, library.getClassifier());
432428
}
433429

434430
/**

core/src/main/java/com/alessiodp/libby/Util.java

+35-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.alessiodp.libby;
22

33
import org.jetbrains.annotations.NotNull;
4+
import org.jetbrains.annotations.Nullable;
45

56
/**
67
* Libby's utility class.
@@ -28,7 +29,7 @@ public static String replaceWithDots(@NotNull String str) {
2829
* @param string The string to convert
2930
* @return The byte array
3031
*/
31-
public static byte[] hexStringToByteArray(String string) {
32+
public static byte[] hexStringToByteArray(@NotNull String string) {
3233
int len = string.length();
3334
byte[] data = new byte[len / 2];
3435
for (int i = 0; i < len; i += 2) {
@@ -37,4 +38,37 @@ public static byte[] hexStringToByteArray(String string) {
3738
}
3839
return data;
3940
}
41+
42+
/**
43+
* Constructs the partial path of a {@link Library} given its artifactId, groupId and version.
44+
*
45+
* @param artifactId The artifactId of the library.
46+
* @param groupId The groupId of the library.
47+
* @param version The version of the library.
48+
* @return The partial path of the library.
49+
* @see Library#getPartialPath()
50+
*/
51+
@NotNull
52+
public static String craftPartialPath(@NotNull String artifactId, @NotNull String groupId, @NotNull String version) {
53+
return groupId.replace('.', '/') + '/' + artifactId + '/' + version + '/';
54+
}
55+
56+
/**
57+
* Constructs the path of a {@link Library} given its partialPath, artifactId, version and classifier.
58+
*
59+
* @param partialPath The partialPath of the library.
60+
* @param artifactId The artifactId of the library.
61+
* @param version The version of the library.
62+
* @param classifier The classifier of the library. May be null.
63+
* @return The path of the library.
64+
* @see Library#getPath()
65+
*/
66+
@NotNull
67+
public static String craftPath(@NotNull String partialPath, @NotNull String artifactId, @NotNull String version, @Nullable String classifier) {
68+
String path = partialPath + artifactId + '-' + version;
69+
if (classifier != null && !classifier.isEmpty()) {
70+
path += '-' + classifier;
71+
}
72+
return path + ".jar";
73+
}
4074
}

core/src/main/java/com/alessiodp/libby/transitive/TransitiveDependencyCollector.java

+29-6
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import org.eclipse.aether.artifact.DefaultArtifact;
99
import org.eclipse.aether.collection.CollectRequest;
1010
import org.eclipse.aether.graph.Dependency;
11+
import org.eclipse.aether.repository.ArtifactRepository;
1112
import org.eclipse.aether.repository.LocalRepository;
1213
import org.eclipse.aether.repository.RemoteRepository;
1314
import org.eclipse.aether.resolution.ArtifactResult;
@@ -18,13 +19,17 @@
1819
import org.eclipse.aether.util.artifact.JavaScopes;
1920
import org.eclipse.aether.util.filter.ScopeDependencyFilter;
2021
import org.jetbrains.annotations.NotNull;
22+
import org.jetbrains.annotations.Nullable;
2123

2224
import java.nio.file.Path;
25+
import java.util.AbstractMap.SimpleEntry;
2326
import java.util.Arrays;
2427
import java.util.Collection;
2528
import java.util.Collections;
2629
import java.util.List;
30+
import java.util.Map.Entry;
2731
import java.util.Properties;
32+
import java.util.concurrent.atomic.AtomicInteger;
2833
import java.util.stream.Collectors;
2934
import java.util.stream.Stream;
3035

@@ -35,6 +40,13 @@
3540
*/
3641
class TransitiveDependencyCollector {
3742

43+
/**
44+
* Counter used to generate ids for repositories
45+
*
46+
* @see #newDefaultRepository(String)
47+
*/
48+
private static final AtomicInteger ID_GENERATOR = new AtomicInteger();
49+
3850
/**
3951
* Maven repository system
4052
*
@@ -72,7 +84,7 @@ public TransitiveDependencyCollector(@NotNull Path saveDirectory) {
7284
*/
7385
@NotNull
7486
public static RemoteRepository newDefaultRepository(@NotNull String url) {
75-
return new RemoteRepository.Builder(url, "default", url).build();
87+
return new RemoteRepository.Builder("repo" + ID_GENERATOR.getAndIncrement(), "default", url).build();
7688
}
7789

7890
/**
@@ -83,19 +95,30 @@ public static RemoteRepository newDefaultRepository(@NotNull String url) {
8395
* @param version Maven dependency version
8496
* @param classifier Maven artifact classifier. May be null
8597
* @param repositories Maven repositories that would be used for dependency resolution
86-
* @return Transitive dependencies, exception otherwise
98+
* @return Transitive dependencies paired with their repository url, exception otherwise
8799
* @throws DependencyResolutionException thrown if dependency doesn't exist on provided repositories
88100
*/
89101
@NotNull
90-
public Collection<Artifact> findTransitiveDependencies(@NotNull String groupId, @NotNull String artifactId, @NotNull String version, @NotNull String classifier, @NotNull List<RemoteRepository> repositories) throws DependencyResolutionException {
102+
public Collection<Entry<Artifact, @Nullable String>> findTransitiveDependencies(@NotNull String groupId, @NotNull String artifactId, @NotNull String version, @NotNull String classifier, @NotNull List<RemoteRepository> repositories) throws DependencyResolutionException {
91103
Artifact artifact = new DefaultArtifact(groupId, artifactId, classifier, "jar", version);
92104

93105
CollectRequest collectRequest = new CollectRequest(new Dependency(artifact, JavaScopes.COMPILE), repositories);
94106
DependencyRequest dependencyRequest = new DependencyRequest(collectRequest, new ScopeDependencyFilter(Arrays.asList(JavaScopes.COMPILE, JavaScopes.RUNTIME), Collections.emptyList()));
95107

96108
DependencyResult dependencyResult = repositorySystem.resolveDependencies(repositorySystemSession, dependencyRequest);
97109

98-
return dependencyResult.getArtifactResults().stream().filter(ArtifactResult::isResolved).map(ArtifactResult::getArtifact).collect(Collectors.toList());
110+
return dependencyResult.getArtifactResults()
111+
.stream()
112+
.filter(ArtifactResult::isResolved)
113+
.map(artifactResult -> {
114+
ArtifactRepository repo = artifactResult.getRepository();
115+
String url = null;
116+
if (repo instanceof RemoteRepository) {
117+
url = ((RemoteRepository) repo).getUrl();
118+
}
119+
return new SimpleEntry<>(artifactResult.getArtifact(), url);
120+
})
121+
.collect(Collectors.toList());
99122
}
100123

101124
/**
@@ -106,12 +129,12 @@ public Collection<Artifact> findTransitiveDependencies(@NotNull String groupId,
106129
* @param version Maven artifact version
107130
* @param classifier Maven artifact classifier. May be null
108131
* @param repositories Maven repositories for transitive dependencies search
109-
* @return Transitive dependencies, exception otherwise
132+
* @return Transitive dependencies paired with their repository url, exception otherwise
110133
* @throws DependencyResolutionException thrown if dependency doesn't exist on provided repositories
111134
* @see #findTransitiveDependencies(String, String, String, String, List)
112135
*/
113136
@NotNull
114-
public Collection<Artifact> findTransitiveDependencies(@NotNull String groupId, @NotNull String artifactId, @NotNull String version, @NotNull String classifier, @NotNull Stream<String> repositories) throws DependencyResolutionException {
137+
public Collection<Entry<Artifact, @Nullable String>> findTransitiveDependencies(@NotNull String groupId, @NotNull String artifactId, @NotNull String version, @NotNull String classifier, @NotNull Stream<String> repositories) throws DependencyResolutionException {
115138
return findTransitiveDependencies(groupId, artifactId, version, classifier, repositories.map(TransitiveDependencyCollector::newDefaultRepository).collect(Collectors.toList()));
116139
}
117140

core/src/main/java/com/alessiodp/libby/transitive/TransitiveDependencyHelper.java

+39-7
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
import com.alessiodp.libby.Library;
44
import com.alessiodp.libby.LibraryManager;
5+
import com.alessiodp.libby.Util;
56
import com.alessiodp.libby.classloader.IsolatedClassLoader;
67
import org.jetbrains.annotations.NotNull;
8+
import org.jetbrains.annotations.Nullable;
79

810
import java.io.IOException;
911
import java.lang.reflect.Constructor;
@@ -14,6 +16,7 @@
1416
import java.util.Collections;
1517
import java.util.HashSet;
1618
import java.util.List;
19+
import java.util.Map.Entry;
1720
import java.util.Set;
1821
import java.util.stream.Stream;
1922

@@ -46,7 +49,7 @@ public class TransitiveDependencyHelper {
4649
/**
4750
* Reflected getter methods of Artifact class
4851
*/
49-
private final Method artifactGetGroupIdMethod, artifactGetArtifactIdMethod, artifactGetVersionMethod, artifactGetClassifierMethod;
52+
private final Method artifactGetGroupIdMethod, artifactGetArtifactIdMethod, artifactGetVersionMethod, artifactGetBaseVersionMethod, artifactGetClassifierMethod;
5053

5154
/**
5255
* LibraryManager instance, used in {@link #findTransitiveLibraries(Library)}
@@ -95,6 +98,8 @@ public TransitiveDependencyHelper(@NotNull LibraryManager libraryManager, @NotNu
9598
artifactGetArtifactIdMethod = artifactClass.getMethod("getArtifactId");
9699
// org.eclipse.aether.artifact.Artifact#getVersion()
97100
artifactGetVersionMethod = artifactClass.getMethod("getVersion");
101+
// org.eclipse.aether.artifact.Artifact#getBaseVersion()
102+
artifactGetBaseVersionMethod = artifactClass.getMethod("getBaseVersion");
98103
// org.eclipse.aether.artifact.Artifact#getClassifier()
99104
artifactGetClassifierMethod = artifactClass.getMethod("getClassifier");
100105
} catch (ReflectiveOperationException e) {
@@ -111,7 +116,7 @@ public TransitiveDependencyHelper(@NotNull LibraryManager libraryManager, @NotNu
111116
* </p>
112117
* <p>
113118
* Note: The method merges the repositories from both the library manager and the given library
114-
* for dependency resolution. And clones all relocations into transitive libraries.
119+
* for dependency resolution. It also clones all relocations into transitive libraries.
115120
* </p>
116121
*
117122
* @param library The primary library for which transitive dependencies need to be found.
@@ -132,16 +137,20 @@ public Collection<Library> findTransitiveLibraries(@NotNull Library library) {
132137

133138
Stream<String> repositories = Stream.of(globalRepositories, libraryRepositories).flatMap(Collection::stream);
134139
try {
135-
Collection<?> artifacts = (Collection<?>) resolveTransitiveDependenciesMethod.invoke(transitiveDependencyCollectorObject,
140+
Collection<?> resolvedArtifacts = (Collection<?>) resolveTransitiveDependenciesMethod.invoke(transitiveDependencyCollectorObject,
136141
library.getGroupId(),
137142
library.getArtifactId(),
138143
library.getVersion(),
139144
library.getClassifier(),
140145
repositories);
141-
for (Object artifact : artifacts) {
146+
for (Object resolved : resolvedArtifacts) {
147+
Entry<?, ?> resolvedEntry = (Entry<?, ?>) resolved;
148+
Object artifact = resolvedEntry.getKey();
149+
@Nullable String repository = (String) resolvedEntry.getValue();
150+
142151
String groupId = (String) artifactGetGroupIdMethod.invoke(artifact);
143152
String artifactId = (String) artifactGetArtifactIdMethod.invoke(artifact);
144-
String version = (String) artifactGetVersionMethod.invoke(artifact);
153+
String baseVersion = (String) artifactGetBaseVersionMethod.invoke(artifact);
145154
String classifier = (String) artifactGetClassifierMethod.invoke(artifact);
146155

147156
if (library.getGroupId().equals(groupId) && library.getArtifactId().equals(artifactId))
@@ -153,7 +162,7 @@ public Collection<Library> findTransitiveLibraries(@NotNull Library library) {
153162
Library.Builder libraryBuilder = Library.builder()
154163
.groupId(groupId)
155164
.artifactId(artifactId)
156-
.version(version)
165+
.version(baseVersion)
157166
.isolatedLoad(library.isIsolatedLoad())
158167
.loaderId(library.getLoaderId());
159168

@@ -162,7 +171,30 @@ public Collection<Library> findTransitiveLibraries(@NotNull Library library) {
162171
}
163172

164173
library.getRelocations().forEach(libraryBuilder::relocate);
165-
library.getRepositories().forEach(libraryBuilder::repository);
174+
175+
if (repository != null) {
176+
// Construct direct download URL
177+
178+
// Add ending "/" if missing
179+
if (!repository.endsWith("/")) {
180+
repository = repository + '/';
181+
}
182+
183+
// TODO Uncomment the line below once LibraryManager#resolveLibrary stops resolving snapshots
184+
// for every repository before trying direct URLs
185+
// Make sure the repository is added as fallback if the dependency isn't found at the constructed URL
186+
// libraryBuilder.repository(repository);
187+
188+
// For snapshots, getVersion() returns version-timestamp-buildNumber instead of version-SNAPSHOT
189+
String version = (String) artifactGetVersionMethod.invoke(artifact);
190+
191+
String partialPath = Util.craftPartialPath(artifactId, groupId, baseVersion);
192+
String path = Util.craftPath(partialPath, artifactId, version, classifier);
193+
194+
libraryBuilder.url(repository + path);
195+
} else {
196+
library.getRepositories().forEach(libraryBuilder::repository);
197+
}
166198

167199
transitiveLibraries.add(libraryBuilder.build());
168200
}

core/src/test/java/com/alessiodp/libby/transitive/TransitiveDownloadingTest.java

+16
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ public class TransitiveDownloadingTest {
2828
.resolveTransitiveDependencies(true)
2929
.excludeTransitiveDependency(EXCLUDED_LIBRARY.getGroupId(), EXCLUDED_LIBRARY.getArtifactId())
3030
.build();
31+
private static final Library BUNGEECORD = Library.builder()
32+
.groupId("net{}md-5")
33+
.artifactId("bungeecord-api")
34+
.version("1.20-R0.2-SNAPSHOT")
35+
.repository("https://oss.sonatype.org/content/repositories/snapshots")
36+
.isolatedLoad(true)
37+
.loaderId("bungeecord")
38+
.resolveTransitiveDependencies(true)
39+
.build();
3140

3241
private LibraryManagerMock libraryManager;
3342

@@ -49,6 +58,13 @@ public void transitiveLoad() {
4958
checkDownloadedDependencies();
5059
}
5160

61+
@Test
62+
public void snapshotLibraryTransitiveLoad() throws Exception {
63+
libraryManager.loadLibrary(BUNGEECORD);
64+
65+
assertNotNull(libraryManager.getIsolatedClassLoaderById("bungeecord").loadClass("net.md_5.bungee.api.ProxyServer"));
66+
}
67+
5268
@Test
5369
public void transitiveWithExcludedLoad() {
5470
libraryManager.loadLibrary(MAVEN_RESOLVER_SUPPLIER_WITH_EXCLUDED);

0 commit comments

Comments
 (0)