A simple utility to allow downloading libraries and relocating them at runtime without having to shade them
Simply extend pluginlib.DependentJavaPlugin instead of Bukkit's JavaPlugin.
package org.example;
import pluginlib.DependentJavaPlugin;
public class ExamplePlugin extends DependentJavaPlugin {
}
To add libraries, you have 2 ways:
Define them in your plugin.yml through the runtime-libraries
section:
name: ExamplePlugin
main: org.example.ExamplePlugin
version: 1.0-SNAPSHOT
authors: [...]
runtime-libraries:
relocation-prefix: 'org.example.libs' # Required if we want to relocate libraries
libraries-folder: 'libraries' # If not defined, it will be 'libs'
libraries:
kotlin:
groupId: org.jetbrains.kotlin
artifactId: kotlin-stdlib
version: 1.4.20
Here we want to get Kotlin's standard library.
Unless explicitly specified, dependencies will be downloaded from Maven Central. We can specify the repository we want to get from, using the repository
property:
jda:
groupId: net.dv8tion
artifactId: JDA
version: 4.2.0_222
repository: https://jcenter.bintray.com/
This way, JDA will be downloaded from Bintray JCenter's repository.
There are multiple ways to define a library, aside from defining the library properties (group id, artifact id, etc)
For the sake of productivity, and because we love being lazy, you can straight define the library's XML, much similar to Maven's format:
kotlin:
xml: '
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>1.4.20</version>
</dependency>
'
This can be especially helpful if you don't want to bother converting the XML to YML, and if you just copied it straight from a website. Warning: By time, your plugin.yml may get a little unclean if you keep doing this!
Some libraries (although rare), do not follow the standard repository:/group/artifact/version/artifact-version.jar
format, and may require us to explicitly define the URL we want to download the library from.
Let's imagine we want to use Aikar's Minecraft Timings:
timings:
url: 'http://repo.aikar.co/nexus/content/groups/aikar/co/aikar/minecraft-timings/1.0.4/minecraft-timings-1.0.4.jar'
artifactId: 'mc-timings'
version: 1.0.4
Important note: When we follow this format, we must define the artifact ID and the version.
This can also be useful if what we're looking for is to download a JAR, rather than an actual library.
Defining in your plugin.yml has a major advantage: When your plugin loads, libraries will already have been loaded beforehand. This means you can use your library components anywhere in your code, such as static blocks and initializers.
You can also load libraries programmatically, instead of defining them in your plugin.yml.
This has a few advantages, such as being able to load your library conditionally (for example, we might need to load MySQL drivers only when the chosen database type is MySQL in our config), however it also has the disadvantage of being unable to use your library until it is explicitly loaded.
For example, if we want to use Caffeine:
package org.example;
import pluginlib.DependentJavaPlugin;
import pluginlib.PluginLib;
public class ExamplePlugin extends DependentJavaPlugin {
private static final PluginLib CAFFEINE = PluginLib.builder()
.groupId("com.github.ben-manes.caffeine")
.artifactId("caffeine")
.version("2.8.6")
.build();
static {
CAFFEINE.load(ExamplePlugin.class);
}
}
Just like the plugin.yml, we can also parse from XML:
private static final PluginLib CAFFEINE = PluginLib.parseXML(
"<dependency>" +
" <groupId>com.github.ben-manes.caffeine</groupId>" +
" <artifactId>caffeine</artifactId>" +
" <version>2.8.6</version>" +
"</dependency>"
).build();
And, we can also parse the library from a URL:
private static final PluginLib TIMINGS = PluginLib
.fromURL("https://repo.aikar.co/nexus/content/groups/aikar/co/aikar/minecraft-timings/1.0.4/minecraft-timings-1.0.4.jar")
.artifactId("timings")
.version("1.0.4")
.build();
static {
TIMINGS.load(ExamplePlugin.class);
}
Relocation is some re-mapping your code and dependencies to make them unique and not conflict with other plugins or dependencies. For example, if we want to relocate okhttp3
's library, its paths would change:
okhttp3.OkHttpClient
-> org.example.okhttp3.OkHttpClient
.
And the same with all other classes that fall in okhttp3
's package. If any plugin also includes okhttp3, we will be sure that there will not be any conflicts between our plugin and the others.
In our library's definition, we simply add the relocation
section
jda:
groupId: net.dv8tion
artifactId: JDA
version: 4.2.0_222
repository: https://jcenter.bintray.com/
relocation:
net.dv8tion.jda: jda
Relocation follows the following template:
<library name>: 'path to replace'
library name would be jda
, and in relocation it would appear as <relocation prefix>.<library name>, as in, org.example.libs.jda.
We can specify relocation rules through our code in the builder:
package org.example;
import pluginlib.DependentJavaPlugin;
import pluginlib.PluginLib;
import pluginlib.Relocation;
public class ExamplePlugin extends DependentJavaPlugin {
private static final PluginLib CAFFEINE = PluginLib.builder()
.groupId("com.github.ben-manes.caffeine")
.artifactId("caffeine")
.version("2.8.6")
.relocate(new Relocation("com/github/benmanes/caffeine", "org.example.libs.caffeine"))
.build();
static {
CAFFEINE.load(ExamplePlugin.class);
}
}
Very important note: Relocating using plugin.yml or code is not enough! You must tell your build system (Maven or Gradle) to relocate references in your code as well.
Notice that we did not directly put com.github.benmanes
, and instead com/github/benmanes
. This way, we will be able to outsmart build system relocation, and will not have this very string literal relocated.