From 6df285723a8d87724f37780998db5075056aedcb Mon Sep 17 00:00:00 2001 From: jglrxavpok Date: Sun, 25 Oct 2020 19:58:19 +0100 Subject: [PATCH] Load extension dependencies --- prismarine-minecraft-data | 2 +- .../extensions/DiscoveredExtension.java | 9 ++-- .../ExtensionDependencyResolver.java | 49 ++++++++++++++++++ .../server/extensions/ExtensionManager.java | 51 +++++++++++-------- .../MinestomOverwriteClassLoader.java | 7 ++- 5 files changed, 91 insertions(+), 27 deletions(-) create mode 100644 src/main/java/net/minestom/server/extensions/ExtensionDependencyResolver.java diff --git a/prismarine-minecraft-data b/prismarine-minecraft-data index 08dfbb32b..472b3c041 160000 --- a/prismarine-minecraft-data +++ b/prismarine-minecraft-data @@ -1 +1 @@ -Subproject commit 08dfbb32bdb0c0f6ee2c5229f92fef3132668405 +Subproject commit 472b3c041c58bd0a4397fb9144b1035682022cbe diff --git a/src/main/java/net/minestom/server/extensions/DiscoveredExtension.java b/src/main/java/net/minestom/server/extensions/DiscoveredExtension.java index 63a009006..17d4d424f 100644 --- a/src/main/java/net/minestom/server/extensions/DiscoveredExtension.java +++ b/src/main/java/net/minestom/server/extensions/DiscoveredExtension.java @@ -4,6 +4,9 @@ import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import java.io.File; +import java.net.URL; +import java.util.LinkedList; +import java.util.List; @Slf4j(topic = "minestom-extensions") final class DiscoveredExtension { @@ -16,7 +19,7 @@ final class DiscoveredExtension { private String[] codeModifiers; private String[] dependencies; private ExternalDependencies externalDependencies; - transient File[] files = new File[0]; + transient List files = new LinkedList<>(); transient LoadStatus loadStatus = LoadStatus.LOAD_SUCCESS; @NotNull @@ -65,8 +68,8 @@ final class DiscoveredExtension { static void verifyIntegrity(@NotNull DiscoveredExtension extension) { if (extension.name == null) { StringBuilder fileList = new StringBuilder(); - for (File f : extension.files) { - fileList.append(f.getAbsolutePath()).append(", "); + for (URL f : extension.files) { + fileList.append(f.toExternalForm()).append(", "); } log.error("Extension with no name. (at {}})", fileList); log.error("Extension at ({}) will not be loaded.", fileList); diff --git a/src/main/java/net/minestom/server/extensions/ExtensionDependencyResolver.java b/src/main/java/net/minestom/server/extensions/ExtensionDependencyResolver.java new file mode 100644 index 000000000..a3a0a2415 --- /dev/null +++ b/src/main/java/net/minestom/server/extensions/ExtensionDependencyResolver.java @@ -0,0 +1,49 @@ +package net.minestom.server.extensions; + +import net.minestom.dependencies.DependencyResolver; +import net.minestom.dependencies.ResolvedDependency; +import net.minestom.dependencies.UnresolvedDependencyException; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.net.URL; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Does NOT relocate extensions + */ +public class ExtensionDependencyResolver implements DependencyResolver { + + private Map extensionMap = new HashMap<>(); + + public ExtensionDependencyResolver(List extensions) { + for(DiscoveredExtension ext : extensions) { + extensionMap.put(ext.getName(), ext); + } + } + + @NotNull + @Override + public ResolvedDependency resolve(@NotNull String extensionName, @NotNull File file) throws UnresolvedDependencyException { + if(extensionMap.containsKey(extensionName)) { + DiscoveredExtension ext = extensionMap.get(extensionName); + // convert extension URLs to subdependencies + List deps = new LinkedList<>(); + for(URL u : ext.files) { + deps.add(new ResolvedDependency(u.toExternalForm(), u.toExternalForm(), "", u, new LinkedList<>())); + } + return new ResolvedDependency(ext.getName(), ext.getName(), ext.getVersion(), ext.files.get(0), deps); + } + throw new UnresolvedDependencyException("No extension named "+extensionName); + } + + @Override + public String toString() { + String list = extensionMap.values().stream().map(entry -> entry.getName()).collect(Collectors.joining(", ")); + return "ExtensionDependencyResolver[" + list + "]"; + } +} diff --git a/src/main/java/net/minestom/server/extensions/ExtensionManager.java b/src/main/java/net/minestom/server/extensions/ExtensionManager.java index fa922972b..d47153f90 100644 --- a/src/main/java/net/minestom/server/extensions/ExtensionManager.java +++ b/src/main/java/net/minestom/server/extensions/ExtensionManager.java @@ -74,16 +74,8 @@ public class ExtensionManager { for (DiscoveredExtension discoveredExtension : discoveredExtensions) { URLClassLoader loader; - URL[] urls = new URL[discoveredExtension.files.length]; - try { - for (int i = 0; i < urls.length; i++) { - urls[i] = discoveredExtension.files[i].toURI().toURL(); - } - loader = newClassLoader(urls); - } catch (MalformedURLException e) { - log.error("Failed to get URL.", e); - continue; - } + URL[] urls = discoveredExtension.files.toArray(new URL[0]); + loader = newClassLoader(urls); // Create ExtensionDescription (authors, version etc.) String extensionName = discoveredExtension.getName(); @@ -187,7 +179,7 @@ public class ExtensionManager { InputStreamReader reader = new InputStreamReader(f.getInputStream(f.getEntry("extension.json")))) { DiscoveredExtension extension = GSON.fromJson(reader, DiscoveredExtension.class); - extension.files = new File[]{file}; + extension.files.add(file.toURI().toURL()); // Verify integrity and ensure defaults DiscoveredExtension.verifyIntegrity(extension); @@ -207,7 +199,8 @@ public class ExtensionManager { final String extensionResources = System.getProperty(INDEV_RESOURCES_FOLDER); try (InputStreamReader reader = new InputStreamReader(new FileInputStream(new File(extensionResources, "extension.json")))) { DiscoveredExtension extension = GSON.fromJson(reader, DiscoveredExtension.class); - extension.files = new File[]{new File(extensionClasses), new File(extensionResources)}; + extension.files.add(new File(extensionClasses).toURI().toURL()); + extension.files.add(new File(extensionResources).toURI().toURL()); // Verify integrity and ensure defaults DiscoveredExtension.verifyIntegrity(extension); @@ -293,6 +286,7 @@ public class ExtensionManager { } private void loadDependencies(List extensions) { + ExtensionDependencyResolver extensionDependencyResolver = new ExtensionDependencyResolver(extensions); for (DiscoveredExtension ext : extensions) { try { DependencyGetter getter = new DependencyGetter(); @@ -314,12 +308,19 @@ public class ExtensionManager { repoList.add(new MavenRepository(repository.name, repository.url)); } getter.addMavenResolver(repoList); + getter.addResolver(extensionDependencyResolver); for (var artifact : externalDependencies.artifacts) { var resolved = getter.get(artifact, dependenciesFolder); injectIntoClasspath(resolved.getContentsLocation(), ext); log.trace("Dependency of extension {}: {}", ext.getName(), resolved); } + + for (var dependencyName : ext.getDependencies()) { + var resolved = getter.get(dependencyName, dependenciesFolder); + injectIntoClasspath(resolved.getContentsLocation(), ext); + log.trace("Dependency of extension {}: {}", ext.getName(), resolved); + } } catch (Exception e) { ext.loadStatus = DiscoveredExtension.LoadStatus.MISSING_DEPENDENCIES; log.error("Failed to load dependencies for extension {}", ext.getName()); @@ -330,17 +331,23 @@ public class ExtensionManager { } private void injectIntoClasspath(URL dependency, DiscoveredExtension extension) { - final ClassLoader cl = getClass().getClassLoader(); + extension.files.add(dependency); + log.trace("Added dependency {} to extension {} classpath", dependency.toExternalForm(), extension.getName()); + /*final ClassLoader cl = getClass().getClassLoader(); if (!(cl instanceof URLClassLoader)) { throw new IllegalStateException("Current class loader is not a URLClassLoader, but " + cl + ". This prevents adding URLs into the classpath at runtime."); } - try { - Method addURL = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); - addURL.setAccessible(true); - addURL.invoke(cl, dependency); - } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { - throw new RuntimeException("Failed to inject URL " + dependency + " into classpath. From extension " + extension.getName(), e); - } + if(cl instanceof MinestomOverwriteClassLoader) { + ((MinestomOverwriteClassLoader) cl).addURL(dependency); // no reflection warnings for us! + } else { + try { + Method addURL = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); + addURL.setAccessible(true); + addURL.invoke(cl, dependency); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new RuntimeException("Failed to inject URL " + dependency + " into classpath. From extension " + extension.getName(), e); + } + }*/ } /** @@ -387,7 +394,7 @@ public class ExtensionManager { for (DiscoveredExtension extension : extensions) { try { for (String codeModifierClass : extension.getCodeModifiers()) { - modifiableClassLoader.loadModifier(extension.files, codeModifierClass); + modifiableClassLoader.loadModifier(extension.files.toArray(new File[0]), codeModifierClass); } if (!extension.getMixinConfig().isEmpty()) { final String mixinConfigFile = extension.getMixinConfig(); @@ -396,7 +403,7 @@ public class ExtensionManager { } } catch (Exception e) { e.printStackTrace(); - log.error("Failed to load code modifier for extension in files: " + Arrays.toString(extension.files), e); + log.error("Failed to load code modifier for extension in files: " + extension.files.stream().map(u -> u.toExternalForm()).collect(Collectors.joining(", ")), e); } } log.info("Done loading code modifiers."); diff --git a/src/main/java/net/minestom/server/extras/selfmodification/MinestomOverwriteClassLoader.java b/src/main/java/net/minestom/server/extras/selfmodification/MinestomOverwriteClassLoader.java index 951f20862..0ce77b25e 100644 --- a/src/main/java/net/minestom/server/extras/selfmodification/MinestomOverwriteClassLoader.java +++ b/src/main/java/net/minestom/server/extras/selfmodification/MinestomOverwriteClassLoader.java @@ -8,6 +8,7 @@ import org.objectweb.asm.tree.ClassNode; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; import java.net.URL; @@ -158,7 +159,11 @@ public class MinestomOverwriteClassLoader extends URLClassLoader { if (name == null) throw new ClassNotFoundException(); String path = name.replace(".", "/") + ".class"; - byte[] bytes = getResourceAsStream(path).readAllBytes(); + InputStream input = getResourceAsStream(path); + if(input == null) { + throw new ClassNotFoundException("Could not find resource "+path); + } + byte[] bytes = input.readAllBytes(); if (transform && !isProtected(name)) { ClassReader reader = new ClassReader(bytes); ClassNode node = new ClassNode();