So you want to write plugins in Scala? Great! Or not so great? Scala runtime classes are not on the class/module path by default. Server administrators
could add them using the -classpath commandline option, but in reality most bukkit servers run in managed environments by minecraft-specialized server hosts. The standard workaround for this problem is to include the classes in your own plugin and relocate them using a shading plugin in your build process. While this
does work, it means that Scala library classes are no longer shared between plugins, making them incompatible because the scala classes will be loaded by different classloaders. Since Spigot 1.16.5 there is the option to include the dependency in the plugin.yml, but this approach has the same problems. And there is the problem of unnecessary ram and disc usage too. As of Scala 2.12.6, the standard library has a size of 3.5 MB. The reflection library is another 5 MB. Using both libraries in multiple plugins results unnecessarily large plugins sizes, while really those Scala classes should only be loaded once. Introducing…
Able to use a true singleton for your plugin's main class, through Scala's object!
Supports multiple Scala versions at the same time! ScalaLoader uses classloader magic to make that work.
Supports custom scala version by adding/changing URLs in the config file.
Annotation-based detection of the plugin's main class - no need to write a plugin.yml. If you wish to use a plugin.yml still, you can, however I always found it a pain.
A boilerplate-free event api! No need to implement #getHandlerList(), #getHandlers(), #isCancelled() and #setCancelled(boolean)!
A boilerplate-free ConfigurationSerializable api! No need to implement #serialize() and #deserialize(Map<String,Object>)! (Scala collection support is a work in progress)
Cons
Scala library classes are only accessible to ScalaPlugins (You can still write them in Java though).
ScalaLoader uses a lot of reflection/injection hacks to make ScalaPlugins accessible to JavaPlugins. This may not work correctly if there is a SecurityManager in place.
Caveats
ScalaPlugin jars go in the <server_root>/plugins/ScalaLoader/scalaplugins/ directory. I made this choice so that ScalaLoader doesn't try to load JavaPlugins that are loaded already.
By default ScalaLoader downloads the scala libraries from over the network the first time. I made this choice to provide the best possible user experience for server admins. If you're very security-focused you might want to provide your own jars by changing the URLs to "file://some/location.jar". The scala classes aren't actually loaded until there's a plugin that needs them, so you can run ScalaLoader once without ScalaPlugins to generate the config.
Depending on a ScalaPlugin from a JavaPlugin plugin.yml:
Code (YAML):
name: DummyPlugin
version: 1.0
main: xyz.janboerman.dummy.dummyplugin.DummyPlugin
depend: [ScalaLoader
] softdepend: [ScalaExample
]#A hard dependency will not work! Your plugin will not load!
//make sure all classes from the scala plugin can be accessed IScalaPluginLoader.
openUpToJavaPlugin(plugin,
this);
//do whatever you want afterwards! getLogger
().
info("We got "+ plugin.
getInt()+" from Scala!"); }
}
Commands & Permissions Since this plugin is meant for other developers, the commands and permissions are probably not very useful to end-users. But for completeness here they are:
/resetScalaUrls <scala version>: Resets the URLs in the config for a version of Scala. Useful if you overwrote some of them. If 'all' is provided as a scala version then all URLs will be reset. Permission: scalaloader.resetscalaurls
/listScalaPlugins: Outputs the names of all scala plugins. Binary-compatible plugins are shown on the same line. Permission: scalaloader.listscalaplugins
/dumpClass <plugin> <class file> <format>?: Outpus the bytecode of a class to the console in a textual representation. This command is mostly useful for myself, but if you fancy diving into Java bytecode then more power to you! Permission: scalaloader.dumpclass
/setDebug <class name>: Marks or unmarks a class as a class that should be dumped to the console by the classloader when it is loaded. A list is saved in the debug.yml file. Permission: scalaloader.setdebug
/classMembers <class name>: Dumps fields and methods visible to ScalaLoader to the command sender. Permission: scalaloader.classmembers
All permissions are by default only granted to server operators.
Usage statistics ScalaLoader collects some anonymous metrics and sends that to bStats.org. In addition to the standard charts, you can see here which Scala versions are most in use. Opting out can be done by editing the <server_root>/plugins/bStats/config.yml file.
Chat & Support GitHub issues is the place for bug reports and feature requests, but if you just want to chat about the project, or want to know more about how to use it, you can reach me via Discord: https://discord.gg/pb2cBBKdwq