diff --git a/common/addons/manifest-addon-loader/src/main/java/com/dfsek/terra/addons/manifest/impl/ManifestAddon.java b/common/addons/manifest-addon-loader/src/main/java/com/dfsek/terra/addons/manifest/impl/ManifestAddon.java index 36db78805..fb87da3b2 100644 --- a/common/addons/manifest-addon-loader/src/main/java/com/dfsek/terra/addons/manifest/impl/ManifestAddon.java +++ b/common/addons/manifest-addon-loader/src/main/java/com/dfsek/terra/addons/manifest/impl/ManifestAddon.java @@ -24,20 +24,21 @@ import com.dfsek.terra.api.inject.annotations.Inject; public class ManifestAddon implements BaseAddon { + private static final Logger logger = LoggerFactory.getLogger(ManifestAddon.class); private final AddonManifest manifest; - private final List initializers; - @Inject private Platform platform; - private static final Logger logger = LoggerFactory.getLogger(ManifestAddon.class); - public ManifestAddon(AddonManifest manifest, List initializers) { this.manifest = manifest; this.initializers = initializers; } + public AddonManifest getManifest() { + return manifest; + } + @Override public String getID() { return manifest.getID(); @@ -46,12 +47,12 @@ public class ManifestAddon implements BaseAddon { public void initialize() { Injector addonInjector = Injector.get(this); addonInjector.addExplicitTarget(BaseAddon.class); - + Injector platformInjector = Injector.get(platform); platformInjector.addExplicitTarget(Platform.class); - - logger.info("Initializing addon {}", getID()); - + + logger.debug("Initializing addon {}", getID()); + initializers.forEach(initializer -> { logger.debug("Invoking entry point {}", initializer.getClass()); addonInjector.inject(initializer); @@ -60,10 +61,6 @@ public class ManifestAddon implements BaseAddon { }); } - public AddonManifest getManifest() { - return manifest; - } - @Override public Map getDependencies() { return manifest.getDependencies(); diff --git a/common/addons/manifest-addon-loader/src/main/java/com/dfsek/terra/addons/manifest/impl/ManifestAddonLoader.java b/common/addons/manifest-addon-loader/src/main/java/com/dfsek/terra/addons/manifest/impl/ManifestAddonLoader.java index fc4da5fad..50bd8ca55 100644 --- a/common/addons/manifest-addon-loader/src/main/java/com/dfsek/terra/addons/manifest/impl/ManifestAddonLoader.java +++ b/common/addons/manifest-addon-loader/src/main/java/com/dfsek/terra/addons/manifest/impl/ManifestAddonLoader.java @@ -13,16 +13,21 @@ import ca.solostudios.strata.version.VersionRange; import com.dfsek.tectonic.exception.LoadException; import com.dfsek.tectonic.loading.ConfigLoader; import com.dfsek.tectonic.yaml.YamlConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.UncheckedIOException; import java.lang.reflect.InvocationTargetException; import java.net.URL; +import java.nio.file.FileVisitOption; import java.nio.file.Files; import java.nio.file.Path; +import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.stream.Collectors; +import java.util.stream.Stream; import com.dfsek.terra.addons.manifest.api.AddonInitializer; import com.dfsek.terra.addons.manifest.impl.config.AddonManifest; @@ -32,75 +37,76 @@ import com.dfsek.terra.addons.manifest.impl.config.loaders.VersionRangeLoader; import com.dfsek.terra.addons.manifest.impl.exception.AddonException; import com.dfsek.terra.addons.manifest.impl.exception.ManifestException; import com.dfsek.terra.addons.manifest.impl.exception.ManifestNotPresentException; -import com.dfsek.terra.api.Platform; import com.dfsek.terra.api.addon.bootstrap.BootstrapBaseAddon; -import com.dfsek.terra.api.inject.annotations.Inject; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public class ManifestAddonLoader implements BootstrapBaseAddon { - private static final Logger LOGGER = LoggerFactory.getLogger(ManifestAddonLoader.class); - + private static final Logger logger = LoggerFactory.getLogger(ManifestAddonLoader.class); private static final Version VERSION = Versions.getVersion(1, 0, 0); + private final ConfigLoader manifestLoader = new ConfigLoader() + .registerLoader(Version.class, new VersionLoader()) + .registerLoader(VersionRange.class, new VersionRangeLoader()) + .registerLoader(WebsiteConfig.class, WebsiteConfig::new); + + public ManifestAddon loadAddon(Path addonPath) { + try(JarFile jar = new JarFile(addonPath.toFile())) { + logger.debug("Loading addon from JAR {}", addonPath); + + JarEntry manifestEntry = jar.getJarEntry("terra.addon.yml"); + if(manifestEntry == null) { + throw new ManifestNotPresentException("Addon " + addonPath + " does not contain addon manifest."); + } + + + //noinspection NestedTryStatement + try { + AddonManifest manifest = manifestLoader.load(new AddonManifest(), + new YamlConfiguration(jar.getInputStream(manifestEntry), + "terra.addon.yml")); + + logger.debug("Loading addon {}@{}", manifest.getID(), manifest.getVersion()); + + @SuppressWarnings({ "IOResourceOpenedButNotSafelyClosed", "resource" }) + ManifestAddonClassLoader loader = new ManifestAddonClassLoader(new URL[]{ addonPath.toUri().toURL() }, + getClass().getClassLoader()); + + List initializers = manifest.getEntryPoints().stream().map(entryPoint -> { + try { + Object in = loader.loadClass(entryPoint).getConstructor().newInstance(); + if(!(in instanceof AddonInitializer)) { + throw new AddonException(in.getClass() + " does not extend " + AddonInitializer.class); + } + return (AddonInitializer) in; + } catch(InvocationTargetException e) { + throw new AddonException("Exception occurred while instantiating addon", e); + } catch(NoSuchMethodException | IllegalAccessException | InstantiationException e) { + throw new AddonException(String.format("No valid default constructor found in entry point %s", entryPoint), e); + } catch(ClassNotFoundException e) { + throw new AddonException(String.format("Entry point %s not found in JAR.", entryPoint), e); + } + }).collect(Collectors.toList()); + + return new ManifestAddon(manifest, initializers); + + } catch(LoadException e) { + throw new ManifestException("Failed to load addon manifest", e); + } + } catch(IOException e) { + throw new UncheckedIOException(e); + } + } + @Override public Iterable loadAddons(Path addonsFolder, ClassLoader parent) { - LOGGER.info("Loading addons..."); + logger.debug("Loading addons..."); - ConfigLoader manifestLoader = new ConfigLoader(); - manifestLoader.registerLoader(Version.class, new VersionLoader()) - .registerLoader(VersionRange.class, new VersionRangeLoader()) - .registerLoader(WebsiteConfig.class, WebsiteConfig::new); - - try { - return Files.walk(addonsFolder, 1) - .filter(path -> path.toFile().isFile() && path.toString().endsWith(".jar")) - .map(path -> { - try { - LOGGER.debug("Loading addon from JAR {}", path); - JarFile jar = new JarFile(path.toFile()); - - JarEntry manifestEntry = jar.getJarEntry("terra.addon.yml"); - if(manifestEntry == null) { - throw new ManifestNotPresentException("Addon " + path + " does not contain addon manifest."); - } - - - try { - AddonManifest manifest = manifestLoader.load(new AddonManifest(), - new YamlConfiguration(jar.getInputStream(manifestEntry), - "terra.addon.yml")); - - LOGGER.info("Loading addon {}", manifest.getID()); - - ManifestAddonClassLoader loader = new ManifestAddonClassLoader(new URL[]{ path.toUri().toURL() }, - getClass().getClassLoader()); - - return new ManifestAddon(manifest, manifest.getEntryPoints().stream().map(entryPoint -> { - try { - Object in = loader.loadClass(entryPoint).getConstructor().newInstance(); - if(!(in instanceof AddonInitializer)) { - throw new AddonException(in.getClass() + " does not extend " + AddonInitializer.class); - } - return (AddonInitializer) in; - } catch(InvocationTargetException e) { - throw new AddonException("Exception occurred while instantiating addon: ", e); - } catch(NoSuchMethodException | IllegalAccessException | InstantiationException e) { - throw new AddonException("No valid default constructor found in entry point " + entryPoint); - } catch(ClassNotFoundException e) { - throw new AddonException("Entry point " + entryPoint + " not found in JAR."); - } - }).collect(Collectors.toList())); - - } catch(LoadException e) { - throw new ManifestException("Failed to load addon manifest", e); - } - } catch(IOException e) { - throw new UncheckedIOException(e); - } - }).collect(Collectors.toList()); + try(Stream addons = Files.walk(addonsFolder, 1, FileVisitOption.FOLLOW_LINKS)) { + return addons.filter(path -> path.toFile().isFile()) + .filter(path -> path.toFile().canRead()) + .filter(path -> path.toString().endsWith(".jar")) + .map(this::loadAddon) + .collect(Collectors.toList()); } catch(IOException e) { throw new UncheckedIOException(e); } diff --git a/common/implementation/base/src/main/java/com/dfsek/terra/AbstractPlatform.java b/common/implementation/base/src/main/java/com/dfsek/terra/AbstractPlatform.java index b4302e67f..0275f8696 100644 --- a/common/implementation/base/src/main/java/com/dfsek/terra/AbstractPlatform.java +++ b/common/implementation/base/src/main/java/com/dfsek/terra/AbstractPlatform.java @@ -180,17 +180,35 @@ public abstract class AbstractPlatform implements Platform { BootstrapAddonLoader bootstrapAddonLoader = new BootstrapAddonLoader(this); Path addonsFolder = getDataFolder().toPath().resolve("addons"); - + Injector platformInjector = new InjectorImpl<>(this); platformInjector.addExplicitTarget(Platform.class); - + bootstrapAddonLoader.loadAddons(addonsFolder, getClass().getClassLoader()) - .forEach(bootstrap -> { - platformInjector.inject(bootstrap); - bootstrap.loadAddons(addonsFolder, getClass().getClassLoader()) - .forEach(addonList::add); - }); + .forEach(bootstrapAddon -> { + platformInjector.inject(bootstrapAddon); + bootstrapAddon.loadAddons(addonsFolder, getClass().getClassLoader()) + .forEach(addonList::add); + }); + + if(logger.isInfoEnabled()) { + StringBuilder builder = new StringBuilder(); + builder.append("Loading ") + .append(addonList.size()) + .append(" Terra addons:"); + + for(BaseAddon addon : addonList) { + builder.append("\n ") + .append("- ") + .append(addon.getID()) + .append("@") + .append(addon.getVersion().getFormatted()); + } + + logger.info(builder.toString()); + } + DependencySorter sorter = new DependencySorter(); addonList.forEach(sorter::add); sorter.sort().forEach(addon -> { @@ -198,20 +216,19 @@ public abstract class AbstractPlatform implements Platform { addon.initialize(); addonRegistry.register(addon.getID(), addon); }); - - eventManager - .getHandler(FunctionalEventHandler.class) - .register(internalAddon, PlatformInitializationEvent.class) - .then(event -> { - logger.info("Loading config packs..."); - getRawConfigRegistry().loadAll(this); - logger.info("Loaded packs."); - }) - .global(); - - - logger.info("Loaded addons."); - + + eventManager.getHandler(FunctionalEventHandler.class) + .register(internalAddon, PlatformInitializationEvent.class) + .then(event -> { + logger.info("Loading config packs..."); + configRegistry.loadAll(this); + logger.info("Loaded packs."); + }) + .global(); + + + logger.info("Terra addons successfully loaded."); + try { CommandUtil.registerAll(manager); } catch(MalformedCommandException e) { diff --git a/common/implementation/bootstrap-addon-loader/src/main/java/com/dfsek/terra/addon/BootstrapAddonLoader.java b/common/implementation/bootstrap-addon-loader/src/main/java/com/dfsek/terra/addon/BootstrapAddonLoader.java index 75871c23a..64432692a 100644 --- a/common/implementation/bootstrap-addon-loader/src/main/java/com/dfsek/terra/addon/BootstrapAddonLoader.java +++ b/common/implementation/bootstrap-addon-loader/src/main/java/com/dfsek/terra/addon/BootstrapAddonLoader.java @@ -26,10 +26,12 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.lang.reflect.InvocationTargetException; import java.net.URL; +import java.nio.file.FileVisitOption; import java.nio.file.Files; import java.nio.file.Path; import java.util.jar.JarFile; import java.util.stream.Collectors; +import java.util.stream.Stream; import com.dfsek.terra.addon.exception.AddonLoadException; import com.dfsek.terra.api.Platform; @@ -44,44 +46,54 @@ public class BootstrapAddonLoader implements BootstrapBaseAddon loadAddon(Path addonPath, ClassLoader parent) { + logger.debug("Loading bootstrap addon from JAR {}", addonPath); + try(JarFile jar = new JarFile(addonPath.toFile())) { + String entry = jar.getManifest().getMainAttributes().getValue("Bootstrap-Addon-Entry-Point"); + + if(entry == null) { + throw new AddonLoadException("No Bootstrap-Addon-Entry-Point attribute defined in addon manifest."); + } + + //noinspection NestedTryStatement + try { + @SuppressWarnings({ "resource", "IOResourceOpenedButNotSafelyClosed" }) + AddonClassLoader loader = new AddonClassLoader(new URL[]{ addonPath.toUri().toURL() }, parent); + + Object addonObject = loader.loadClass(entry).getConstructor().newInstance(); + + if(!(addonObject instanceof BootstrapBaseAddon addon)) { + throw new AddonLoadException( + addonObject.getClass() + " does not extend " + BootstrapBaseAddon.class); + } + + logger.debug("Loaded bootstrap addon {}@{} with entry point {}", + addon.getID(), addon.getVersion().getFormatted(), addonObject.getClass()); + return addon; + } catch(InvocationTargetException e) { + throw new AddonLoadException("Exception occurred while instantiating addon", e); + } catch(NoSuchMethodException | IllegalAccessException | InstantiationException e) { + throw new AddonLoadException(String.format("No valid default constructor found in entry point %s", entry), e); + } catch(ClassNotFoundException | NoClassDefFoundError e) { + throw new AddonLoadException(String.format("Entry point %s not found in JAR.", entry), e); + } + + } catch(IOException e) { + throw new UncheckedIOException(e); + } + } + @Override public Iterable> loadAddons(Path addonsFolder, ClassLoader parent) { - Path bootstrapAddons = addonsFolder.resolve("bootstrap"); - logger.info("Loading bootstrap addons from {}", bootstrapAddons); - try { - return Files.walk(bootstrapAddons, 1) - .filter(path -> path.toFile().isFile() && path.toString().endsWith(".jar")) - .map(path -> { - try { - logger.info("Loading bootstrap addon from JAR {}", path); - JarFile jar = new JarFile(path.toFile()); - String entry = jar.getManifest().getMainAttributes().getValue("Bootstrap-Addon-Entry-Point"); - - if(entry == null) { - throw new AddonLoadException("No Bootstrap-Addon-Entry-Point attribute defined in addon manifest."); - } - - AddonClassLoader loader = new AddonClassLoader(new URL[]{ path.toUri().toURL() }, parent); - - try { - Object in = loader.loadClass(entry).getConstructor().newInstance(); - if(!(in instanceof BootstrapBaseAddon)) { - throw new AddonLoadException(in.getClass() + " does not extend " + BootstrapBaseAddon.class); - } - logger.info("Loaded bootstrap addon {}", ((BootstrapBaseAddon) in).getID()); - return (BootstrapBaseAddon) in; - } catch(InvocationTargetException e) { - throw new AddonLoadException("Exception occurred while instantiating addon: ", e); - } catch(NoSuchMethodException | IllegalAccessException | InstantiationException e) { - throw new AddonLoadException("No valid default constructor found in entry point " + entry); - } catch(ClassNotFoundException e) { - throw new AddonLoadException("Entry point " + entry + " not found in JAR."); - } - - } catch(IOException e) { - throw new UncheckedIOException(e); - } - }).collect(Collectors.toList()); + Path bootstrapFolder = addonsFolder.resolve("bootstrap"); + logger.debug("Loading bootstrap addons from {}", bootstrapFolder); + + try(Stream bootstrapAddons = Files.walk(bootstrapFolder, 1, FileVisitOption.FOLLOW_LINKS)) { + return bootstrapAddons.filter(path -> path.toFile().isFile()) + .filter(path -> path.toFile().canRead()) + .filter(path -> path.toString().endsWith(".jar")) + .map(path -> loadAddon(path, parent)) + .collect(Collectors.toList()); } catch(IOException e) { throw new UncheckedIOException(e); } diff --git a/platforms/fabric/build.gradle.kts b/platforms/fabric/build.gradle.kts index 46c03aa55..56a4a5b4c 100644 --- a/platforms/fabric/build.gradle.kts +++ b/platforms/fabric/build.gradle.kts @@ -25,10 +25,10 @@ dependencies { shadedApi(project(":common:implementation:base")) shadedApi("org.slf4j:slf4j-api:1.7.32") { - because("Minecraft 1.17+ includes slf4j 1.8.0-beta4, so we need to shade it for other versions.") + because("Minecraft 1.17+ includes slf4j 1.8.0-beta4, but we want slf4j 1.7.") } - shadedImplementation("org.apache.logging.log4j:log4j-slf4j-impl:2.14.1") { - because("Minecraft 1.17+ includes slf4j 1.8.0-beta4, so we need to shade it for other versions.") + shaded("org.apache.logging.log4j:log4j-slf4j-impl:2.14.1") { + because("Minecraft 1.17+ includes slf4j 1.8.0-beta4, but we want slf4j 1.7.") exclude("org.apache.logging.log4j") } @@ -71,4 +71,4 @@ tasks.register("publishModrinth") { releaseType = "beta" addGameVersion(minecraft) addLoader("fabric") -} +} \ No newline at end of file