scalac Plugins with Pants
The Scala compiler, scalac, has a plugin mechanism.
Plugins allow you to hook into the compiler while it's running, to perform various tasks such as custom error checking, code analysis and so on.
Pants supports both building and using scalac plugins.
Building scalac plugins
scalac plugins are specified using a scalac_plugin
target:
scalac_plugin( name = 'plugin', plugin = 'simple_scalac_plugin', sources = ['SimpleScalacPlugin.scala'], dependencies = [], classname = 'org.pantsbuild.example.scalac.plugin.SimpleScalacPlugin', scope='compile', )
A scalac plugin target has the same fields as a scala_library
target,
plus two extra:
classname
: The name of thePlugin
implementation class. Required.plugin
: The logical name of the plugin, as returned by thePlugin
class'sgetName()
method. If unspecified, this field defaults to the target name.
Building a plugin target will, in addition to compiling the code, generate
the appropriate metadata into scalac-plugin.xml
, so
that scalac can load the plugin by name at runtime.
A plugin may be published for later consumption in any repo. It can also be consumed (with some restrictions) from source, in the same repo.
Using scalac plugins
Plugins can be integrated in one of two ways:
- Global plugins: specified in
pants.toml
and used on all Scala code. - Per-target plugins: specified on a Scala target and used only when compiling that target.
Global plugins
Global plugins are specified using the scalac_plugins
key in the scala
section of pants.toml
:
[scala] scalac_plugins = ['simple_scalac_plugin']
Plugins can optionally take arguments, specified like this:
[scala] scalac_plugins = ['simple_scalac_plugin'] scalac_plugin_args = "{'simple_scalac_plugin': ['arg1', 'arg2']}"
Per-target plugins
These are specified like this:
scala_library( ... scalac_plugins=['simple_scalac_plugin'], scalac_plugin_args={ 'simple_scalac_plugin': ['arg1', 'arg2'] } )
Depending on plugins
In order to load a plugin, it has to be on scalac's classpath. This can be achieved in one of two ways:
- Have targets that must be compiled with a plugin depend (directly or indirectly)
either on the
scalac_plugin
target, or on ajar_library
pointing to a published version of the plugin. - Have a
scalac-plugin-dep
target inBUILD.tools
:
jar_library(name='scalac-plugin-dep', jars = [jar(org='com.foo', name='foo_plugin', rev='1.2.3')],
Note that, as always with BUILD.tools
, plugin locations specified via scala-plugin-dep
must be published jars. They cannot be local scalac_plugin
targets.
Usually, it will make more sense to use scala-plugin-dep
with global plugins, to avoid
laborious repetition of that dependency, and to use target dependencies for per-target plugins,
to keep the dependencies selective.
Depending directly on the scalac_plugin
has the added advantage of allowing plugin changes
to be picked up when compiling the target that uses the plugin, with no need for an intermediate
publishing step.
Note that, to avoid a chicken-and-egg problem, an in-repo plugin will not be used when
compiling its own scalac_plugin
target, or any of that target's dependencies.
To use a plugin on its own code, you must publish it and consume the published plugin
via scala-plugin-dep
.