Load extension dependencies

This commit is contained in:
jglrxavpok 2020-10-25 19:58:19 +01:00
parent e474640687
commit 6df285723a
5 changed files with 91 additions and 27 deletions

@ -1 +1 @@
Subproject commit 08dfbb32bdb0c0f6ee2c5229f92fef3132668405
Subproject commit 472b3c041c58bd0a4397fb9144b1035682022cbe

View File

@ -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<URL> 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);

View File

@ -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<String, DiscoveredExtension> extensionMap = new HashMap<>();
public ExtensionDependencyResolver(List<DiscoveredExtension> 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<ResolvedDependency> 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 + "]";
}
}

View File

@ -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<DiscoveredExtension> 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.");

View File

@ -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();