Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dynamic dependency manager with relocation #285

Merged
merged 20 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
b58bc4e
Download and load Adventure dependency on BungeeCord
diogotcorreia Feb 16, 2023
43815ac
Merge branch 'v4' into feature/dynamic-dependencies
diogotcorreia Apr 28, 2023
aaab236
Merge branch 'v4' into feature/dynamic-dependencies
diogotcorreia May 21, 2023
c9c94a8
Merge branch 'v4' into feature/dynamic-dependencies
diogotcorreia Nov 3, 2023
20afefa
Merge branch 'v4' into feature/dynamic-dependencies
diogotcorreia Nov 28, 2023
d0f43f7
Merge branch 'v4' into feature/dynamic-dependencies
diogotcorreia Nov 28, 2023
535df52
Merge branch 'v4' into feature/dynamic-dependencies
diogotcorreia Dec 18, 2023
5aa9fc5
chore: upgrade adventure to 4.15.0
diogotcorreia Dec 24, 2023
91e47f2
Merge branch 'v4' into feature/dynamic-dependencies
diogotcorreia Dec 24, 2023
eef2378
Merge branch 'v4' into feature/dynamic-dependencies
diogotcorreia Dec 26, 2023
4536a88
feat: use jar-in-jar loader
diogotcorreia Dec 27, 2023
d906f8d
wip: refactor: add loader modules per platform
diogotcorreia Dec 27, 2023
44f58a0
fix: api not being shaded into the final jar
diogotcorreia Jan 4, 2024
e120f0a
Merge branch 'v4' into feature/dynamic-dependencies
diogotcorreia Jan 24, 2024
471a4f0
refactor(bungee): add loader module
diogotcorreia Jan 24, 2024
01f9882
fix: relocation mismatch on spigot
diogotcorreia Jan 25, 2024
2dca1d3
Merge branch 'v4' into feature/dynamic-dependencies
diogotcorreia Jun 25, 2024
725a6e5
fix: tests build failures
diogotcorreia Jun 25, 2024
b82f1bb
feat: conditionally relocate adventure across all platforms
diogotcorreia Jun 25, 2024
a427f7b
feat: add missing relocations and add dependency manager
diogotcorreia Jun 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 31 additions & 10 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,17 @@ allprojects {
name 'velocity'
url 'https://nexus.velocitypowered.com/repository/maven-public/'
}
maven {
// Mirror other people's repositories
name 'diogotcRepositoryMirror'
url 'https://repo.diogotc.com/mirror/'
}
}
}

subprojects {
dependencies {
implementation 'org.jetbrains:annotations:23.0.0'
compileOnly 'org.jetbrains:annotations:23.0.0'

compileOnly 'org.projectlombok:lombok:1.18.22'
annotationProcessor 'org.projectlombok:lombok:1.18.22'
Expand All @@ -52,18 +57,34 @@ subprojects {
}

dependencies {
implementation project(':api')
implementation project(':core:triton-core-loader')
implementation project(':triton-bungeecord:triton-bungeecord-loader')
implementation project(':triton-spigot:triton-spigot-loader')
implementation project(':triton-velocity:triton-velocity-loader')

implementation project(path: ":core")
implementation project(path: ":triton-bungeecord")
implementation project(path: ":triton-spigot")
implementation project(path: ":triton-velocity")
compileOnly project(':core')
compileOnly project(':triton-bungeecord')
compileOnly project(':triton-spigot')
compileOnly project(':triton-velocity')
}

shadowJar {
getArchiveBaseName().set(rootProject.name)
relocate 'org.bstats', 'com.rexcantor64.triton.metrics'
//relocate 'org.slf4j', 'com.rexcantor64.shaded.slf4j'
relocate 'net.kyori.adventure.text.minimessage', 'com.rexcantor64.shaded.minimessage'
relocate 'com.tananaev.jsonpatch', 'com.rexcantor64.shaded.jsonpatch'
relocate 'com.zaxxer.hikari', 'com.rexcantor64.shaded.hikari'

from {
project(':core').tasks.shadowJar.archiveFile
}
from {
project(':triton-bungeecord').tasks.shadowJar.archiveFile
}
from {
project(':triton-spigot').tasks.shadowJar.archiveFile
}
from {
project(':triton-velocity').tasks.shadowJar.archiveFile
}

relocate 'org.objectweb.asm', 'com.rexcantor64.triton.lib.objectweb.asm'
relocate 'me.lucko.jarrelocator', 'com.rexcantor64.triton.lib.jarrelocator'
}
28 changes: 25 additions & 3 deletions core/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
plugins {
id 'java'
id 'com.github.johnrengelman.shadow' version '7.1.0'
}

group 'com.rexcantor64.triton'
Expand All @@ -9,14 +10,14 @@ test {
}

dependencies {
implementation project(path: ":api")
compileOnly project(':core:triton-core-loader')
compileOnly project(":api")

// Adventure / MiniMessage
// TODO check which of these should be compileOnly or implementation
compileOnly 'net.kyori:adventure-text-serializer-gson:4.15.0'
compileOnly 'net.kyori:adventure-text-serializer-legacy:4.15.0'
compileOnly 'net.kyori:adventure-text-serializer-plain:4.15.0'
implementation('net.kyori:adventure-text-minimessage:4.15.0') {
compileOnly('net.kyori:adventure-text-minimessage:4.15.0') {
exclude group: 'net.kyori', module: 'adventure-api'
exclude group: 'net.kyori', module: 'adventure-key'
exclude group: 'net.kyori', module: 'adventure-text-serialize-gson'
Expand All @@ -37,7 +38,11 @@ dependencies {
}
compileOnly 'com.google.code.gson:gson:2.9.0'

implementation 'net.byteflux:libby-core:1.3.0'

// Test dependencies
testImplementation project(":api")

testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.0'

Expand All @@ -46,3 +51,20 @@ dependencies {
testImplementation 'net.kyori:adventure-text-serializer-legacy:4.15.0'
testImplementation 'net.kyori:adventure-text-serializer-plain:4.15.0'
}

shadowJar {
archiveFileName = 'triton-core.jarinjar'

relocate 'org.bstats', 'com.rexcantor64.triton.metrics'
//relocate 'org.slf4j', 'com.rexcantor64.shaded.slf4j'
relocate 'com.tananaev.jsonpatch', 'com.rexcantor64.shaded.jsonpatch'
relocate 'com.zaxxer.hikari', 'com.rexcantor64.shaded.hikari'

relocate 'net.kyori.adventure.text.minimessage', 'com.rexcantor64.triton.lib.adventure.text.minimessage'
relocate 'net.kyori.adventure.text.serializer.gson', 'com.rexcantor64.triton.lib.adventure.text.serializer.gson'
relocate 'net.kyori.adventure.text.serializer.legacy', 'com.rexcantor64.triton.lib.adventure.text.serializer.legacy'
relocate 'net.kyori.adventure.text.serializer.plain', 'com.rexcantor64.triton.lib.adventure.text.serializer.plain'
relocate 'net.kyori.adventure.text.serializer.bungeecord', 'com.rexcantor64.triton.lib.adventure.text.serializer.bungeecord'

relocate 'net.byteflux.libby', 'com.rexcantor64.triton.lib.libby'
}
11 changes: 11 additions & 0 deletions core/loader/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
plugins {
id 'java-library'
}

group 'com.rexcantor64.triton'

dependencies {
api 'org.ow2.asm:asm-commons:9.2'
api 'org.ow2.asm:asm:9.2'
api 'me.lucko:jar-relocator:1.7'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.rexcantor64.triton.loader.utils;

import lombok.Builder;
import lombok.Singular;
import me.lucko.jarrelocator.Relocation;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

@Builder
public class CommonLoader {
private static final String CORE_JAR_NAME = "triton-core.jarinjar";

private final String jarInJarName;
private final String bootstrapClassName;
@Singular
private final Set<LoaderFlag> flags;
@Singular
private final List<Class<?>> constructorTypes;
@Singular
private final List<Object> constructorValues;

public LoaderBootstrap loadPlugin() {
List<Relocation> relocations = new ArrayList<>();
if (flags.contains(LoaderFlag.RELOCATE_ADVENTURE)) {
relocations.add(new Relocation("net/kyori/adventure", "com/rexcantor64/triton/lib/adventure"));
}

@SuppressWarnings("resource")
JarInJarClassLoader loader = new JarInJarClassLoader(getClass().getClassLoader(), relocations, CORE_JAR_NAME, jarInJarName);

Class<?>[] constructorTypes = this.constructorTypes.toArray(new Class<?>[this.constructorTypes.size() + 1]);
constructorTypes[constructorTypes.length - 1] = Set.class;
Object[] constructorValues = this.constructorValues.toArray(new Object[this.constructorValues.size() + 1]);
constructorValues[constructorValues.length - 1] = Collections.unmodifiableSet(this.flags);
return loader.instantiatePlugin(bootstrapClassName, constructorTypes, constructorValues);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package com.rexcantor64.triton.loader.utils;

import me.lucko.jarrelocator.JarRelocator;
import me.lucko.jarrelocator.Relocation;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Arrays;
import java.util.List;

/**
* Classloader that can load a jar from within another jar file.
*
* <p>The "loader" jar contains the loading code & public API classes,
* and is class-loaded by the platform.</p>
*
* <p>The inner "plugin" jar contains the plugin itself, and is class-loaded
* by the loading code & this classloader.</p>
*/
public class JarInJarClassLoader extends URLClassLoader {
static {
ClassLoader.registerAsParallelCapable();
}

/**
* Creates a new jar-in-jar class loader.
*
* @param loaderClassLoader the loader plugin's classloader (setup and created by the platform)
* @param jarResourcePaths one or more paths to the jar-in-jar resources within the loader jar
* @throws LoadingException if something unexpectedly bad happens
*/
public JarInJarClassLoader(ClassLoader loaderClassLoader, List<Relocation> relocations, String... jarResourcePaths) throws LoadingException {
super(
Arrays.stream(jarResourcePaths)
.map(path -> extractJar(loaderClassLoader, path, relocations))
.toArray(URL[]::new),
loaderClassLoader);
}

public void addJarToClasspath(URL url) {
addURL(url);
}

public void deleteJarResource() {
URL[] urls = getURLs();
if (urls.length == 0) {
return;
}

try {
Path path = Paths.get(urls[0].toURI());
Files.deleteIfExists(path);
} catch (Exception e) {
// ignore
}
}

/**
* Creates a new plugin instance.
*
* @param bootstrapClass the name of the bootstrap plugin class
* @param loaderPluginType the type of the loader plugin, the only parameter of the bootstrap
* plugin constructor
* @param loaderPlugin the loader plugin instance
* @param <T> the type of the loader plugin
* @return the instantiated bootstrap plugin
*/
public <T> LoaderBootstrap instantiatePlugin(String bootstrapClass, Class<T> loaderPluginType, T loaderPlugin) throws LoadingException {
return this.instantiatePlugin(bootstrapClass, new Class[]{loaderPluginType}, new Object[]{loaderPlugin});
}

/**
* Creates a new plugin instance.
*
* @param bootstrapClass the name of the bootstrap plugin class
* @param constructorTypes the types of the constructor of the bootstrap plugin class
* @param constructorArgs the values to pass to the constructor
* @return the instantiated bootstrap plugin
*/
public LoaderBootstrap instantiatePlugin(String bootstrapClass, Class<?>[] constructorTypes, Object[] constructorArgs) throws LoadingException {
Class<? extends LoaderBootstrap> plugin;
try {
plugin = loadClass(bootstrapClass).asSubclass(LoaderBootstrap.class);
} catch (ReflectiveOperationException e) {
throw new LoadingException("Unable to load bootstrap class", e);
}

Constructor<? extends LoaderBootstrap> constructor;
try {
constructor = plugin.getConstructor(constructorTypes);
} catch (ReflectiveOperationException e) {
throw new LoadingException("Unable to get bootstrap constructor", e);
}

try {
return constructor.newInstance(constructorArgs);
} catch (ReflectiveOperationException e) {
throw new LoadingException("Unable to create bootstrap plugin instance", e);
}
}

/**
* Extracts the "jar-in-jar" from the loader plugin into a temporary file,
* then returns a URL that can be used by the {@link JarInJarClassLoader}.
*
* @param loaderClassLoader the classloader for the "host" loader plugin
* @param jarResourcePath the inner jar resource path
* @return a URL to the extracted file
*/
private static URL extractJar(ClassLoader loaderClassLoader, String jarResourcePath, List<Relocation> relocations) throws LoadingException {
// get the jar-in-jar resource
URL jarInJar = loaderClassLoader.getResource(jarResourcePath);
if (jarInJar == null) {
throw new LoadingException("Could not locate jar-in-jar");
}

// create a temporary file
// on posix systems by default this is only read/writable by the process owner
Path path;
Path pathRelocated;
try {
path = Files.createTempFile(jarResourcePath.replace('.', '-'), ".jar.tmp");
pathRelocated = Files.createTempFile(jarResourcePath.replace('.', '-'), "-relocated.jar.tmp");
} catch (IOException e) {
throw new LoadingException("Unable to create a temporary file", e);
}

// mark that the file should be deleted on exit
path.toFile().deleteOnExit();
pathRelocated.toFile().deleteOnExit();

// copy the jar-in-jar to the temporary file path
try (InputStream in = jarInJar.openStream()) {
Files.copy(in, path, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
throw new LoadingException("Unable to copy jar-in-jar to temporary path", e);
}

if (!relocations.isEmpty()) {
try {
new JarRelocator(path.toFile(), pathRelocated.toFile(), relocations).run();
} catch (IOException e) {
throw new LoadingException("Unable to apply relocations to jar", e);
}
}

try {
if (relocations.isEmpty()) {
return path.toUri().toURL();
} else {
return pathRelocated.toUri().toURL();
}
} catch (MalformedURLException e) {
throw new LoadingException("Unable to get URL from path", e);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.rexcantor64.triton.loader.utils;

/**
* Minimal bootstrap plugin, called by the loader plugin.
*/
public interface LoaderBootstrap {

default void onEnable() {}

default void onDisable() {}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.rexcantor64.triton.loader.utils;

public enum LoaderFlag {
RELOCATE_ADVENTURE,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.rexcantor64.triton.loader.utils;

/**
* Runtime exception used if there is a problem during loading
*/
public class LoadingException extends RuntimeException {

public LoadingException(String message) {
super(message);
}

public LoadingException(String message, Throwable cause) {
super(message, cause);
}

}
Loading
Loading