20 December 2013 by Tobias Roeser

Here we describes the Plugin System of SBuild 0.7.1. This page is an revised version of the earlier published page SBuild 0.7.0 Plugin System.

Exported and private packages

Each SBuild plugin is loaded by a separate classloader and has the capability to keep its implementation (and dependencies) private. On the other hand, a plugin can export some or all of its internals on a package level (just like in OSGi). Thus, with plugins, you have fine control over what a plugin will contribute to a SBuild project. It is intended, that plugins with lots of dependencies keep those in their private classes space to avoid conflicts with other plugins and the project itself. At a minimum the plugin class must be exported. All other classes, even the plugin factory (the class implementing the Plugin trait) can be private.

To make an plugin available to your project, all you need to do is to add it to the classpath of the project, e.g. with the @classpath annotation.

No magic, just what you call for

Another focus was it, to keep SBuild magic free (tm) and the user in full control. Thus, no plugin will automatically extend or modify your project (besides the classpath). To use some plugins functionality, you have to explicitly activate it.

At any point in your SBuild buildfile you can activate and configure a plugin. Based on that configuration, the plugin will contribute additional functionality to your project, which contains most likely a new Target or a SchemeHandler. E.g. a Java compiler plugin might provide a target that compiles your Java source files whereas a Aether plugin would add a SchemeHandler that is able to resolve transitive dependencies through the Eclipse Aether (aka Maven) library.

For more flexibility each plugin can have multiple instances which are named. In most cases you will not need this feature and you simple can leave the name out (the plugin instance will become the name ""). This feature should support scenarios like using the same compile plugin to compile main and test classes, or use the same test runner plugin to run unit tests and integration tests. As the plugin instances are aware of their given name, some good defaults could be derived from that name. E.g. a compiler plugin instance with the name "test" could automatically adjust the target name and input and output directories.

Example Hello World Plugin

Imagine a simple Hello plugin, which will add a "hello" target to our project. If the plugin instance is named, the target name will be "hello-${name}".

import de.tototec.sbuild._
import org.example.hello

@version("0.7.1")
@classpath("mvn:org.example:helloworld:1.0.0") (1)
class SBuild(implicit _project: Project) {

  Plugin[Hello] (2)

  Plugin[Hello]("world") (3)

  Plugin[Hello]("city") configure (_.copy(targetName = "hello-town")) (4)

}
1 With the @classpath annotation, we make the plugin available in our buildfile.
2 With the Plugin.apply[T] method, we activate the plugin with it’s default configuration.
3 We activate another plugin instance, but this time a named instance with the name "world".
4 Here, we activate another "city" instance an configure it.

The such configured project will have three targets:

bash $ sbuild -l
hello
hello-world
hello-town

The implementation of that plugin could look like this:

package org.example.hello

import de.tototec.sbuild._

// This is the class that is seen in the project buildfile and is used for configuration
case class Hello(targetName: String)

// This is the factory
class HelloPlugin(implicit project: Project) extends Plugin[Hello] {
  override def create(name: String): Hello = new Hello(if(name == "") "hello" else s"hello-$name")
  override def applyToProject(instances: Seq[(String, Hello)]): Unit = instances foreach {
    case (name, hello) =>
      Target(hello.targetName) exec {
        println("hello " + hello.name)
      }
  }
}

The Jar, that contains the hello plugin must at least have the following Manifest entry:

SBuild-Plugin: org.example.hello.Hello=org.example.hello.HelloPlugin;version="1.0.0"

The supported Manifest entries are:

  • SBuild-Plugin - Used to declare one or more plugins.

  • SBuild-Classpath - Used to add additional dependencies (as part of the private class space). All default SBuild schemes (file, http, mvn) are supported.

  • SBuild-ExportPackage - Used to export some of the plugins private packages. If this is not present, all packages of that plugin will be exported.

comments powered by Disqus