diff --git a/game/build.gradle b/game/build.gradle index bcce4790..f6581a2e 100644 --- a/game/build.gradle +++ b/game/build.gradle @@ -7,59 +7,23 @@ buildscript { dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" + classpath group: 'com.moandjiezana.toml', name: 'toml4j', version: '0.7.1' } } -apply plugin: 'kotlin' - ext.pluginsDir = "$projectDir/src/plugins" -sourceSets { - plugins { - kotlin { - srcDir "$pluginsDir" - exclude 'stub.kt' - exclude '**/*.kts' - } - } -} +apply plugin: 'kotlin' +apply from: 'plugins.gradle' dependencies { compile project(':cache') compile project(':net') compile project(':util') - pluginsCompile(configurations.compile) - pluginsCompile(sourceSets.main.output) - + compile group: 'io.github.lukehutch', name: 'fast-classpath-scanner', version: '2.0.21' compile group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jre8', version: "$kotlinVersion" compile group: 'org.jetbrains.kotlin', name: 'kotlin-compiler', version: "$kotlinVersion" - runtime files("$buildDir/plugins") - runtime sourceSets.plugins.output -} - - -task compilePluginScripts(type: JavaExec, dependsOn: [classes, pluginsClasses]) { - group = LifecycleBasePlugin.BUILD_GROUP - description = 'Compile plugin script files (.plugin.kts) to java bytecode' - - def compilerClasspath = [ - configurations.compile.asPath, - configurations.runtime.asPath, - sourceSets.main.compileClasspath.asPath, - sourceSets.main.runtimeClasspath.asPath - ] - - def outputDir = "$buildDir/plugins" - def manifestPath = "$buildDir/plugins/manifest.txt" - - inputs.source "$pluginsDir" - outputs.dir outputDir - - classpath = sourceSets.main.compileClasspath + sourceSets.main.runtimeClasspath - main = 'org.apollo.game.plugin.kotlin.KotlinPluginCompiler' - args = ["$pluginsDir", outputDir, manifestPath, compilerClasspath.join(':')] -} - -assemble.dependsOn compilePluginScripts \ No newline at end of file + testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlinVersion" +} \ No newline at end of file diff --git a/game/plugins.gradle b/game/plugins.gradle new file mode 100644 index 00000000..fb405acf --- /dev/null +++ b/game/plugins.gradle @@ -0,0 +1,161 @@ +import com.moandjiezana.toml.Toml + +import java.nio.file.Paths + +def PLUGIN_VERIFICATION_GROUP = "plugin-verification" +def PLUGIN_BUILD_GROUP = "plugin-build" + +buildscript { + repositories { + mavenCentral() + } + + dependencies { + classpath group: 'com.moandjiezana.toml', name: 'toml4j', version: '0.7.1' + } +} + +sourceSets { + pluginStub { + kotlin { + srcDir "$pluginsDir/stub" + exclude 'stub.kt' + } + } +} + +dependencies { + pluginStubCompile(project(":game")) +} + +task pluginTests { + group = "plugin-verification" + + doLast { + println("Finished executing plugin tests") + } +} + +class PluginBuildData { + PluginBuildData(String normalizedName, SourceSet mainSources, SourceSet testSources, + FileCollection scriptFiles, List dependencyNames) { + this.normalizedName = normalizedName + this.mainSources = mainSources + this.testSources = testSources + this.scriptFiles = scriptFiles + this.dependencyNames = dependencyNames + } + + String normalizedName + SourceSet mainSources + SourceSet testSources + FileCollection scriptFiles + List dependencyNames +} + +Map pluginMap = new HashMap<>() + +def configurePluginDependencies(SourceSet mainSources, SourceSet testSources, + List pluginDependencies) { + + def testConfiguration = testSources.compileConfigurationName + def mainConfiguration = mainSources.compileConfigurationName + def runtimeConfiguration = mainSources.runtimeConfigurationName + + // Add this plugin as a runtime dependency to the main game project + dependencies.add(configurations.runtime.name, mainSources.output) + + pluginDependencies.each { + dependencies.add(mainConfiguration, it.mainSources.output) + dependencies.add(testConfiguration, it.testSources.output) + } + + dependencies.add(mainConfiguration, configurations.compile) + dependencies.add(mainConfiguration, sourceSets.main.output) + dependencies.add(runtimeConfiguration, sourceSets.pluginStub.output) + + dependencies.add(testConfiguration, mainSources.output) + dependencies.add(testConfiguration, configurations.testCompile) + dependencies.add(testConfiguration, sourceSets.test.output) +} + +def configurePluginTasks(String name, SourceSet mainSources, SourceSet testSources, + FileCollection scriptFiles, List pluginDependencies) { + + task("${name}Tests", type: Test) { + group = "plugin-verification" + + testClassesDir = testSources.output.classesDir + classpath = testSources.runtimeClasspath + mainSources.runtimeClasspath + + binResultsDir = file("$buildDir/plugin-test-results/binary/$name") + + reports { + html.destination = "$buildDir/reports/plugin-tests/$name" + junitXml.destination = "$buildDir/plugin-tests/$name" + } + } + + task("compile${name}Scripts", type: JavaExec) { + group = "plugin-compile" + + def outputDir = mainSources.output.classesDir.toString() + + inputs.files scriptFiles + outputs.dir outputDir + + classpath = sourceSets.main.compileClasspath + sourceSets.main.runtimeClasspath + main = 'org.apollo.game.plugin.kotlin.KotlinPluginCompiler' + args = [outputDir] + scriptFiles.collect { it.absoluteFile.toString() } + } + + def testsTask = tasks["${name}Tests"] + pluginTests.dependsOn testsTask +} + +def pluginTree = fileTree(dir: "$pluginsDir") +def pluginDefinitions = pluginTree.matching { + include '**/meta.toml' +} + +pluginDefinitions.each { file -> + def meta = new Toml() + meta.read(file.absoluteFile) + + def pluginFolder = Paths.get(file.parentFile.absolutePath) + def name = meta.getString("name", pluginFolder.getFileName().toString()) + def normalizedName = name.replaceAll("[^a-zA-Z0-9_]", '_').toLowerCase() + def packageName = meta.getString("package", "org.apollo.game.plugin") + def authors = meta.getList("authors", new ArrayList()) + def dependencies = meta.getList("dependencies", new ArrayList()) + + def scripts = fileTree(file.parentFile) { + include '**/*.plugin.kts' + } + + def srcsDir = meta.getString("config.src", "src/") + def testDir = meta.getString("config.test", "test/") + + def mainSources = sourceSets.create("${normalizedName}_main") { + kotlin { + srcDir pluginFolder.resolve(srcsDir).toString() + exclude '*.kts' + } + } + + def testSources = sourceSets.create("${normalizedName}_test") { + kotlin { + srcDir pluginFolder.resolve(testDir).toString() + } + } + + def pluginData = new PluginBuildData(normalizedName, mainSources, testSources, scripts, dependencies) + pluginMap.put(normalizedName, pluginData) +} + +pluginMap.values().each { + def dependencies = it.dependencyNames.collect { name -> pluginMap.get(name) } + + configurePluginDependencies(it.mainSources, it.testSources, dependencies) + configurePluginTasks(it.normalizedName, it.mainSources, it.testSources, it.scriptFiles, dependencies) +} diff --git a/game/src/main/java/org/apollo/game/plugin/KotlinPluginEnvironment.java b/game/src/main/java/org/apollo/game/plugin/KotlinPluginEnvironment.java index b2e74222..dba14469 100644 --- a/game/src/main/java/org/apollo/game/plugin/KotlinPluginEnvironment.java +++ b/game/src/main/java/org/apollo/game/plugin/KotlinPluginEnvironment.java @@ -15,6 +15,9 @@ import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; + +import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner; +import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult; import org.apollo.game.model.World; import org.apollo.game.plugin.kotlin.KotlinPluginCompiler; import org.apollo.game.plugin.kotlin.KotlinPluginScript; @@ -37,15 +40,14 @@ public class KotlinPluginEnvironment implements PluginEnvironment { @Override public void load(Collection plugins) { List pluginScripts = new ArrayList<>(); + List> pluginClasses = new ArrayList<>(); - try (InputStream resource = KotlinPluginEnvironment.class.getResourceAsStream("/manifest.txt")) { - BufferedReader reader = new BufferedReader(new InputStreamReader(resource)); - List pluginClassNames = reader.lines().collect(Collectors.toList()); - - for (String pluginClassName : pluginClassNames) { - Class pluginClass = - (Class) Class.forName(pluginClassName); + new FastClasspathScanner() + .matchSubclassesOf(KotlinPluginScript.class, pluginClasses::add) + .scan(); + try { + for (Class pluginClass : pluginClasses) { Constructor pluginConstructor = pluginClass.getConstructor(World.class, PluginContext.class); @@ -55,7 +57,10 @@ public class KotlinPluginEnvironment implements PluginEnvironment { throw new RuntimeException(e); } - pluginScripts.forEach(script -> script.doStart(world)); + pluginScripts.forEach(script -> { + logger.info("Starting script: " + script.getClass().getName()); + script.doStart(world); + }); } @Override diff --git a/game/src/main/kotlin/org/apollo/game/plugin/kotlin/KotlinPluginCompiler.kt b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/KotlinPluginCompiler.kt index 4c615583..ee655af3 100644 --- a/game/src/main/kotlin/org/apollo/game/plugin/kotlin/KotlinPluginCompiler.kt +++ b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/KotlinPluginCompiler.kt @@ -43,8 +43,6 @@ class KotlinPluginCompiler(val classpath: List, val messageCollector: Mess companion object { - private val maxSearchDepth = 1024; - fun currentClasspath(): List { val classLoader = Thread.currentThread().contextClassLoader as? URLClassLoader ?: throw RuntimeException("Unable to resolve classpath for current ClassLoader") @@ -66,12 +64,10 @@ class KotlinPluginCompiler(val classpath: List, val messageCollector: Mess @JvmStatic fun main(args: Array) { - if (args.size < 4) throw RuntimeException("Usage: ") + if (args.size < 2) throw RuntimeException("Usage: script1.kts script2.kts ...") - val inputDir = Paths.get(args[0]) - val outputDir = Paths.get(args[1]) - val manifestPath = Paths.get(args[2]) - val classpathEntries = args[3].split(':') + val outputDir = Paths.get(args[0]) + val inputScripts = args.slice(1..args.size - 1).map { Paths.get(it) } val classpath = mutableListOf() val runtimeBean = ManagementFactory.getRuntimeMXBean() @@ -83,15 +79,11 @@ class KotlinPluginCompiler(val classpath: List, val messageCollector: Mess } /** - * Classpath entries on the command line contain the kotlin runtime - * and plugin API code. We use our current classpath to provide - * Kotlin with access to the JRE and apollo modules. + * Our current classpath should contain all compile time dependencies for the plugin as well as Apollo's + * own sources. We can also achieve this via Gradle but doing it at runtime prevents Gradle from thinking + * the build has been modified after evaluation. */ classpath.addAll(currentClasspath()) - classpath.addAll(classpathEntries.map { File(it) }) - - val inputScriptsMatcher = { path: Path, _: BasicFileAttributes -> path.toString().endsWith(".plugin.kts") } - val inputScripts = Files.find(inputDir, maxSearchDepth, BiPredicate(inputScriptsMatcher)) val compiler = KotlinPluginCompiler(classpath, MessageCollector.NONE) val compiledScriptClasses = mutableListOf() @@ -106,8 +98,6 @@ class KotlinPluginCompiler(val classpath: List, val messageCollector: Mess inputScripts.forEach { compiledScriptClasses.add(compiler.compile(it, outputDir).fqName) } - - Files.write(manifestPath, compiledScriptClasses, CREATE, TRUNCATE_EXISTING) } catch (t: Throwable) { t.printStackTrace() System.exit(1) diff --git a/game/src/plugins/bank/meta.toml b/game/src/plugins/bank/meta.toml new file mode 100644 index 00000000..e7895a9e --- /dev/null +++ b/game/src/plugins/bank/meta.toml @@ -0,0 +1,7 @@ +name = "Banking" +package = "org.apollo.game.plugin.banking" +authors = [ "Major" ] + +[config] +srcDir = "src/" +testDir = "test/" \ No newline at end of file diff --git a/game/src/plugins/bank/plugin.xml b/game/src/plugins/bank/plugin.xml deleted file mode 100644 index 4402e5f0..00000000 --- a/game/src/plugins/bank/plugin.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - bank - 1 - Bank - Opens the bank interface when players select 'use-quickly' on a bank booth. - - Major - - - - - - diff --git a/game/src/plugins/bank/bank.plugin.kts b/game/src/plugins/bank/src/bank.plugin.kts similarity index 100% rename from game/src/plugins/bank/bank.plugin.kts rename to game/src/plugins/bank/src/bank.plugin.kts diff --git a/game/src/plugins/bank/test/BankingTests.kt b/game/src/plugins/bank/test/BankingTests.kt new file mode 100644 index 00000000..e69de29b diff --git a/game/src/plugins/chat/private-messaging/meta.toml b/game/src/plugins/chat/private-messaging/meta.toml new file mode 100644 index 00000000..e69de29b diff --git a/game/src/plugins/chat/private-messaging/friends.plugin.kts b/game/src/plugins/chat/private-messaging/src/friends.plugin.kts similarity index 100% rename from game/src/plugins/chat/private-messaging/friends.plugin.kts rename to game/src/plugins/chat/private-messaging/src/friends.plugin.kts diff --git a/game/src/plugins/chat/private-messaging/ignores.plugin.kts b/game/src/plugins/chat/private-messaging/src/ignores.plugin.kts similarity index 100% rename from game/src/plugins/chat/private-messaging/ignores.plugin.kts rename to game/src/plugins/chat/private-messaging/src/ignores.plugin.kts diff --git a/game/src/plugins/chat/private-messaging/messaging.plugin.kts b/game/src/plugins/chat/private-messaging/src/messaging.plugin.kts similarity index 100% rename from game/src/plugins/chat/private-messaging/messaging.plugin.kts rename to game/src/plugins/chat/private-messaging/src/messaging.plugin.kts diff --git a/game/src/plugins/dummy/meta.toml b/game/src/plugins/dummy/meta.toml new file mode 100644 index 00000000..e69de29b diff --git a/game/src/plugins/dummy/dummy.plugin.kts b/game/src/plugins/dummy/src/dummy.plugin.kts similarity index 100% rename from game/src/plugins/dummy/dummy.plugin.kts rename to game/src/plugins/dummy/src/dummy.plugin.kts diff --git a/game/src/plugins/entity/spawn/meta.toml b/game/src/plugins/entity/spawn/meta.toml new file mode 100644 index 00000000..3551199d --- /dev/null +++ b/game/src/plugins/entity/spawn/meta.toml @@ -0,0 +1,8 @@ +name = "spawning" +package = "org.apollo.game.plugin.entity" +authors = [ "Gary Tierney" ] +dependencies = [ "entity_lookup" ] + +[config] +srcDir = "src/" +testDir = "test/" \ No newline at end of file diff --git a/game/src/plugins/entity/spawn/spawn.kt b/game/src/plugins/entity/spawn/src/spawn.kt similarity index 100% rename from game/src/plugins/entity/spawn/spawn.kt rename to game/src/plugins/entity/spawn/src/spawn.kt diff --git a/game/src/plugins/entity/spawn/spawn.plugin.kts b/game/src/plugins/entity/spawn/src/spawn.plugin.kts similarity index 100% rename from game/src/plugins/entity/spawn/spawn.plugin.kts rename to game/src/plugins/entity/spawn/src/spawn.plugin.kts diff --git a/game/src/plugins/locations/lumbridge/meta.toml b/game/src/plugins/locations/lumbridge/meta.toml new file mode 100644 index 00000000..17ab1025 --- /dev/null +++ b/game/src/plugins/locations/lumbridge/meta.toml @@ -0,0 +1,8 @@ +name = "lumbridge npc spawns" +package = "org.apollo.game.plugin.locations" +authors = [ "Gary Tierney" ] +dependencies = [ "spawning" ] + +[config] +srcDir = "src/" +testDir = "test/" \ No newline at end of file diff --git a/game/src/plugins/locations/lumbridge/lumbridge-npcs.kts b/game/src/plugins/locations/lumbridge/src/lumbridge-npcs.kts similarity index 100% rename from game/src/plugins/locations/lumbridge/lumbridge-npcs.kts rename to game/src/plugins/locations/lumbridge/src/lumbridge-npcs.kts diff --git a/game/src/plugins/stub.kt b/game/src/plugins/stub/stub.kt similarity index 100% rename from game/src/plugins/stub.kt rename to game/src/plugins/stub/stub.kt diff --git a/game/src/plugins/util/lookup/meta.toml b/game/src/plugins/util/lookup/meta.toml new file mode 100644 index 00000000..91b1757b --- /dev/null +++ b/game/src/plugins/util/lookup/meta.toml @@ -0,0 +1,2 @@ +name = "entity lookup" +package = "org.apollo.game.plugins.util" diff --git a/game/src/plugins/util/lookup/lookup.kt b/game/src/plugins/util/lookup/src/lookup.kt similarity index 100% rename from game/src/plugins/util/lookup/lookup.kt rename to game/src/plugins/util/lookup/src/lookup.kt diff --git a/game/src/plugins/util/lookup/test/LookupTests.kt b/game/src/plugins/util/lookup/test/LookupTests.kt new file mode 100644 index 00000000..545cbc41 --- /dev/null +++ b/game/src/plugins/util/lookup/test/LookupTests.kt @@ -0,0 +1,14 @@ +import org.apollo.cache.def.ItemDefinition +import org.junit.Test +import kotlin.test.assertEquals + +class LookupTests { + @Test fun itemLookup() { + val testItem = ItemDefinition(0) + testItem.name = "sword" + + ItemDefinition.init(arrayOf(testItem)) + + assertEquals(testItem, lookup_item("sword")) + } +} \ No newline at end of file