Skip to content
Henri Schubin edited this page Jul 5, 2024 · 12 revisions

How to apply for Sponge

Maven Central Sonatype Nexus (Snapshots)

Due to Sponge not exposing a way to load dependencies in during runtime, we need to have our own ClassLoader load them in, for that we'll need that ClassLoader to load in our plugin's code (that's going to use those dependencies) as well. We also need to make a interface that can post state changes to the bootstrap from the loader class.

This guide is for Gradle, that is also the only way to use the DependencyDownload metadata generation plugin

Loader

sponge/loader/build.gradle

 plugins {
     id 'java'
     id 'com.github.johnrengelman.shadow' version '7.0.0'
 }

 repositories {
     mavenCentral()
     maven {
         url 'https://repo.spongepowered.org'
     }
 }

 dependencies {
     compileOnly 'org.spongepowered:spongeapi:7.3.0'
     annotationProcessor 'org.spongepowered:spongeapi:7.3.0'

+     implementation 'dev.vankka:dependencydownload-jarinjar-loader:VERSION'
 }

 jar {
     finalizedBy shadowJar
 }

 shadowJar {
+    from {
+        findProject(':sponge').tasks.shadowJar.archiveFile
+    }
 }

If the main part of the plugin is the root project, you can use rootProject instead of findProject(':sponge')

sponge/loader/src/main/java/com/example/sponge/boostrap/ISpongeBootstrap.java

package com.example.sponge.bootstrap;

...

public interface ISpongeBootstrap {

    void onServerStart(GameStartedServerEvent event);

}

sponge/loader/src/main/java/com/example/sponge/loader/SpongeLoader.java

 package com.example.sponge;

...

 @Plugin(
         id = "example-plugin",
         name = "ExamplePlugin"
 )
 public class SpongeLoader implements ILoader {

+    private ISpongeBootstrap bootstrap;

     private final Logger logger;
     private final PluginContainer container;
     private final Path configDirectory;

     @Inject
     public SpongeLoader(Logger logger, PluginContainer container, @ConfigDir(sharedRoot = false) Path configDirectory) {
         this.logger = logger;
         this.container = container;
         this.configDirectory = configDirectory;
+        initialize();
     }

+    private Optional<ISpongeBootstrap> bootstrap() {
+        return Optional.ofNullable(bootstrap);
+    }

+    @Override
+    public String getBootstrapClassName() {
+        return "com.example.sponge.bootstrap.SpongeBootstrap";
+    }

+    @Override
+    public void initiateBootstrap(Class<?> aClass, JarInJarClassLoader jarInJarClassLoader) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
+        Constructor<?> constructor = aClass.getConstructor(JarInJarClassLoader.class, Logger.class, PluginContainer.class, Path.class);
+        bootstrap = (ISpongeBootstrap) constructor.newInstance(jarInJarClassLoader, logger, container, configDirectory);
+    }

+    @Override
+    public String getName() {
+        return container.getName();
+    }

+    @Override
+    public ClassLoader getParentClassLoader() {
+        return getClass().getClassLoader();
+    }

+    @Override
+    public URL getJarInJarResource() {
+        return getParentClassLoader().getResource("sponge.jarinjar");
+    }

     @Listener
     public void onServerStart(GameStartedServerEvent event) {
-        // plugin enable logic
+        bootstrap().ifPresent(bootstrap -> bootstrap.onServerStart(event));
     }
}

Bootstrap

sponge/build.gradle

 plugins {
     id 'java'
     id 'com.github.johnrengelman.shadow' version '7.0.0'
+    id 'dev.vankka.dependencydownload.plugin' version 'VERSION'
 }

 repositories {
     mavenCentral() 
     maven {
         url 'https://repo.spongepowered.org'
     }
 }

 dependencies {
     compileOnly 'org.spongepowered:spongeapi:7.3.0'
     annotationProcessor 'org.spongepowered:spongeapi:7.3.0'

+    compileOnly project(':sponge:loader')
+    implementation 'dev.vankka:dependencydownload-jarinjar-bootstrap:1.0.0'
 }

 jar {
+    dependsOn generateRuntimeDownloadResourceForRuntimeDownload
     finalizedBy shadowJar
 }

 shadowJar {
+    archiveFileName = 'sponge.jarinjar'
 }

sponge/src/main/java/com/example/sponge/SpongeBootstrap

package com.example.sponge;

...

public class SpongeBootstrap extends AbstractBootstrap implements ISpongeBootstrap {

    private final Logger logger;
    private final PluginContainer pluginContainer;
    private final Path configDirectory;

    // If this is changed the SpongeLoader needs to be changed too
    public SpongeBootstrap(JarInJarClassLoader classLoader, Logger logger, PluginContainer pluginContainer, Path configDirectory) {
        super(classLoader);
        this.logger = logger;
        this.pluginContainer = pluginContainer;
        this.configDirectory = configDirectory;
    }

    @Override
    public void onServerStart(GameStartedServerEvent event) {
        try {
            DependencyManager manager = new DependencyManager(configDirectory.resolve("cache"));
            manager.loadFromResource(getClass().getClassLoader().getResource("runtimeDownload.txt"));

            Executor executor = Executors.newCachedThreadPool();
            manager.downloadAll(executor, Collections.singletonList(new StandardRepository("https://repo.example.com/maven2"))).join();
            manager.relocateAll(executor).join();
            manager.loadAll(executor, new JarInJarClasspathAppender(super.getClassLoader())).join();
        } catch (Throwable t) {
            logger.error("Failed to load dependencies", t);
            return;
        }

        // plugin enable logic
    }
}