Processors  

sbt Processors

Existing Processors

See Integration Support for IDE-related processors.

Basics

A processor is complementary to a plugin. The table below compares the two.

Processors Plugins
contribute new commands new Project types and actions
operate at level of command handling task execution
associated with user project
declared at/in command line plugin/project definitions
distribution binary source

Processor Setup/Management Commands

To use a processor, you first tell sbt about it. There are two pieces of required information: the processor's ID and the repository it is published to. By default, sbt knows about the local Ivy repository, Scala Tools Releases, and Maven Central. These do not need to be declared.

Commands that manage processors are prefixed with '*'.

To add a repository:

 *<id> at <url>

For example, at the interactive prompt, this would look like:

> *db at http://databinder.net/repo/

To define a processor, select an alphanumeric name for it and declare it like:

 *<name> is <group> <module> <version>

For example:

> *generate is org.scala-tools.sbt generate 1.0
or
> *gen is org.scala-tools.sbt generate 1.0

sbt will try to retrieve the defined processor at this point. If it is unsuccessful, check that the ID is correct and you have added any necessary repositories for the processor and its dependencies. sbt uses Ivy for this, so a downloaded processor is cached in Ivy's cache.

You can see currently defined processors and repositories with the show command:

 *show

To undefine a processor or repository, use:

 *remove <label>

For example:

> *remove generate

The *help command lists the above commands with a short description.

The processor definitions managed with these commands are associated with a user and not a project, unlike plugins. Although you manage processors while running sbt on a project, the actual configuration is stored globally for the current user. If you add a processor while working on one project on a machine, you do not need to define it again to use it in another project. If you use the same project on a different machine, you need to define the processor again.

Processor Usage

Run a processor like a normal sbt command or action. You call a processor using whatever name you gave it and pass the arguments it expects. In the example above, we bound a processor to generate. It would be run like:

 generate arg1 arg2

Creating a Processor

A processor is a public, concrete implementation of the sbt.processor.Processor trait with a no-argument constructor. Processors that do not need to modify command processing can implement the simpler sbt.processor.BasicProcessor class. These classes are:

trait Processor {
   def apply(label: String, project: Project, onFailure: Option[String], args: String): ProcessorResult
}
abstract class BasicProcessor extends Processor {
   def apply(project: Project, args: String): Unit
}

sealed trait ProcessorResult extends NotNull
final class Success(project: Project, onFailure: Option[String], insertArgs: String*) extends ProcessorResult
final class Exit(val code: Int) extends ProcessorResult
final class Reload(insertArgs: String*) extends ProcessorResult

A BasicProcessor accepts a Project and the arguments passed to it on the command line and performs some action. A full Processor gets access to the label that the user invoked it by and the action that will run if a command fails (usually shell or exit). It can also return new commands to insert into the execution, change the current Project, or request that sbt exit or reload the project definition.

As an example, consider a processor that accepts an integer argument and calls test that many times. It might look like:

class TestN extends Processor {
   def apply(label: String, project: Project, onFailure: Option[String], args: String): ProcessorResult =
      new Success(project, onFailure, List.make(args.toInt, "test") : _* )
}

In addition, a Processor includes a descriptor file called sbt.processor that contains the name of the class implementing Processor. For example, if TestN is in the example package, sbt.processor would contain the single line:

example.TestN

For the project definition, extend ProcessorProject. This will put sbt classes on the classpath and fix the Scala version to the version currently running sbt. Then, publish the project using publish-local or publish. You can then use the processor from another project as described in the sections above.

Notes

  • A processor project can define dependencies like a normal project.
  • A processor is built against the version of sbt building the processor project`.
  • The project passed to the processor is the user's current project, not necessarily the root project. Use project.rootProject to get the root.
  • The example processor is very naive. It assumes a 'test' action exists. A real processor would want to check whether the provided Project has such an action.
  • One intended use of processors is for project or file generation. If the project definition is modified, be sure to insert a 'reload' command.
  • The arguments are passed unparsed for more flexibility. If handling multiple arguments, you will need to parse the arguments string. The sbt.impl.Arguments class exists for this purpose.

Minimal Example

Project definition:

 import sbt._
 class MyProcessorProject(info: ProjectInfo) extends ProcessorProject(info)

Processor source:

 package example

 import sbt._
 import processor.BasicProcessor

 class TestProcessor extends BasicProcessor {
   def apply(project: Project, args: String) {
      project.log.info("Processor test.  args: '" + args + "'")
   }
 }

Processor descriptor resource sbt.processor (goes in src/main/resources/):

 example.TestProcessor