Although runtime dependency resolution is generally looked at as an anti-pattern, there are cases where it can be a necessary evil.
gremlin is a Gradle plugin and Java library for resolving Gradle Configuration
s at runtime.
First, apply the gremlin-gradle
plugin to your project.
plugins {
id("xyz.jpenilla.gremlin-gradle") version "VERSION"
}
Applying the plugin will set up the following:
gremlin
: The gremlin extension allows configuring plugin-wide optionsdefaultGremlinRuntimeDependency
: Whethergremlin-runtime
(with the same version as the Gradle plugin) should be added to theimplementation
Configuration
defaultJarRelocatorDependencies
: Whether the default jar relocator dependencies should be added to thejarRelocatorRuntime
Configuration
runtimeDownload
: TheConfiguration
that is exported by the defaultwriteDependencies
taskjarRelocatorRuntime
: TheConfiguration
containing thejar-relocator
runtime, used when there are relocations
writeDependencies
: The defaultWriteDependencyTask
registered to export theruntimeDownload
configuration. The output (dependencies.txt
) is added as a resource to the main source set.
gremlin supports extending the runtime and Gradle plugin with custom JarProcessor
s, and includes the RelocationProcessor
.
Relocations set using gremlin will also need to be applied to the project output. The ShadowGremlin
utility is provided to simplify
adding the same relocations to gremlin and shadowJar
.
fun reloc(fromPkg: String, toPkg: String) {
listOf(tasks.shadowJar, tasks.writeDependencies).forEach { task ->
task.configure {
ShadowGremlin.relocate(this, fromPkg, toPkg)
}
}
}
reloc("some.package", "relocated.some.package")
To add a runtime-downloaded dependency, simply add it to the runtimeDownload
configuration in the same way you would for implementation
or compileOnly
.
Snapshot dependencies will be pinned to the currently resolved version in the output, and exclusions or other resolution rules will also apply. This is because
gremlin-gradle
stores a flattened and resolved view of the dependencies, rather than forcing the runtime to deal with transitive dependencies and other complicated logic.
A common configuration is to also add runtimeDownload
dependencies to compileOnly
and testImplementation
:
configurations.compileOnly {
extendsFrom(configurations.runtimeDownload.get())
}
configurations.testImplementation {
extendsFrom(configurations.runtimeDownload.get())
}
It is possible to register more dependency sets than the default one by manually configuring new WriteDependencySet
tasks.
The runtime is available at xyz.jpenilla:gremlin-runtime
on Maven Central.
You only need to manually add a dependency on it when you have disabled the automatic runtime dependency mentioned above.
The entrypoint to the runtime component is the DependencyResolver
. The following is an example that resolves the default dependencies.txt
set:
final DependencySet deps = DependencySet.readDefault(this.getClass().getClassLoader());
final DependencyCache cache = new DependencyCache(/* cache directory */);
try (final DependencyResolver downloader = new DependencyResolver(/* slf4j logger */)) {
final Set<Path> jars = downloader.resolve(deps, cache).jarFiles();
// ...
}
cache.cleanup();
gremlin-runtime
also provides utilities for appending to the classpath in common environments:
PaperClasspathAppender
: utility to append jars to a Paper plugin's classpath using the PaperPluginLoader
APIDefaultsPaperPluginLoader
: prebuilt PaperPluginLoader
that resolves the defaultdependencies.txt
set and appends it to the plugin classpath usingPaperClasspathAppender
.FabricClasspathAppender
: utility to append jars to the Fabric classpathVelocityClasspathAppender
: utility to append jars to a Velocity plugin's classpath