mirror of https://github.com/sbt/sbt.git
274 lines
8.4 KiB
ReStructuredText
274 lines
8.4 KiB
ReStructuredText
======================
|
|
Plugins Best Practices
|
|
======================
|
|
|
|
*This page is intended primarily for SBT plugin authors.*
|
|
|
|
A plugin developer should strive for consistency and ease of use.
|
|
Specifically:
|
|
|
|
- Plugins should play well with other plugins. Avoiding namespace
|
|
clashes (in both SBT and Scala) is paramount.
|
|
- Plugins should follow consistent conventions. The experiences of an
|
|
SBT *user* should be consistent, no matter what plugins are pulled
|
|
in.
|
|
|
|
Here are some current plugin best practices. **NOTE:** Best practices
|
|
are evolving, so check back frequently.
|
|
|
|
Avoid overriding ``settings``
|
|
-----------------------------
|
|
|
|
SBT will automatically load your plugin's ``settings`` into the build.
|
|
Overriding ``val settings`` should only be done by plugins intending to
|
|
provide commands. Regular plugins defining tasks and settings should
|
|
provide a sequence named after the plugin like so:
|
|
|
|
::
|
|
|
|
val obfuscateSettings = Seq(...)
|
|
|
|
This allows build user to choose which subproject the plugin would be
|
|
used. See later section for how the settings should be scoped.
|
|
|
|
Reuse existing keys
|
|
-------------------
|
|
|
|
SBT has a number of `predefined keys <../../api/sbt/Keys%24.html>`_.
|
|
Where possible, reuse them in your plugin. For instance, don't define:
|
|
|
|
::
|
|
|
|
val sourceFiles = settingKey[Seq[File]]("Some source files")
|
|
|
|
Instead, simply reuse SBT's existing ``sources`` key.
|
|
|
|
Avoid namespace clashes
|
|
-----------------------
|
|
|
|
Sometimes, you need a new key, because there is no existing SBT key. In
|
|
this case, use a plugin-specific prefix, both in the (string) key name
|
|
used in the SBT namespace and in the Scala ``val``. There are two
|
|
acceptable ways to accomplish this goal.
|
|
|
|
Just use a ``val`` prefix
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
::
|
|
|
|
package sbtobfuscate
|
|
object Plugin extends sbt.Plugin {
|
|
val obfuscateStylesheet = settingKey[File]("Obfuscate stylesheet")
|
|
}
|
|
|
|
In this approach, every ``val`` starts with ``obfuscate``. A user of the
|
|
plugin would refer to the settings like this:
|
|
|
|
::
|
|
|
|
obfuscateStylesheet := ...
|
|
|
|
Use a nested object
|
|
~~~~~~~~~~~~~~~~~~~
|
|
|
|
::
|
|
|
|
package sbtobfuscate
|
|
object Plugin extends sbt.Plugin {
|
|
object ObfuscateKeys {
|
|
val stylesheet = SettingKey[File]("obfuscateStylesheet")
|
|
}
|
|
}
|
|
|
|
In this approach, all non-common settings are in a nested object. A user
|
|
of the plugin would refer to the settings like this:
|
|
|
|
::
|
|
|
|
import ObfuscateKeys._ // place this at the top of build.sbt
|
|
|
|
stylesheet := ...
|
|
|
|
Configuration Advice
|
|
--------------------
|
|
|
|
Due to usability concerns from the shell, you could opt out of
|
|
task-scoping described in this section, if your plugin makes heavy use
|
|
of the shell. Using configuration-scoping the user could discover your
|
|
tasks using tab completion:
|
|
|
|
.. code-block:: console
|
|
|
|
coffee:[tab]
|
|
|
|
This method no longer works with per-task keys, but there's a pending
|
|
case, so hopefully it will be addressed in the future.
|
|
|
|
When to define your own configuration
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
If your plugin introduces a new concept (even if that concept reuses an
|
|
existing key), you want your own configuration. For instance, suppose
|
|
you've built a plugin that produces PDF files from some kind of markup,
|
|
and your plugin defines a target directory to receive the resulting
|
|
PDFs. That target directory is scoped in its own configuration, so it is
|
|
distinct from other target directories. Thus, these two definitions use
|
|
the same *key*, but they represent distinct *values*. So, in a user's
|
|
``build.sbt``, we might see:
|
|
|
|
::
|
|
|
|
target in PDFPlugin := baseDirectory.value / "mytarget" / "pdf"
|
|
target in Compile := baseDirectory.value / "mytarget"
|
|
|
|
In the PDF plugin, this is achieved with an ``inConfig`` definition:
|
|
|
|
::
|
|
|
|
val settings: Seq[sbt.Project.Setting[_]] = inConfig(LWM)(Seq(
|
|
target := baseDirectory.value / "target" / "docs" # the default value
|
|
))
|
|
|
|
When *not* to define your own configuration.
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
If you're merely adding to existing definitions, don't define your own
|
|
configuration. Instead, reuse an existing one *or* scope by the main
|
|
task (see below).
|
|
|
|
::
|
|
|
|
val akka = config("akka") // This isn't needed.
|
|
val akkaStartCluster = TaskKey[Unit]("akkaStartCluster")
|
|
|
|
target in akkaStartCluster := ... // This is ok.
|
|
akkaStartCluster in akka := ... // BAD. No need for a Config for plugin-specific task.
|
|
|
|
Configuration Cat says "Configuration is for configuration"
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
When defining a new type of configuration, e.g.
|
|
|
|
::
|
|
|
|
val Config = config("profile")
|
|
|
|
should be used to create a "cross-task" configuration. The task
|
|
definitions don't change in this case, but the default configuration
|
|
does. For example, the ``profile`` configuration can extend the test
|
|
configuration with additional settings and changes to allow profiling in
|
|
SBT. Plugins should not create arbitrary Configurations, but utilize
|
|
them for specific purposes and builds.
|
|
|
|
Configurations actually tie into dependency resolution (with Ivy) and
|
|
can alter generated pom files.
|
|
|
|
Configurations should *not* be used to namespace keys for a plugin. e.g.
|
|
|
|
::
|
|
|
|
val Config = config("my-plugin")
|
|
val pluginKey = settingKey[String]("A plugin specific key")
|
|
val settings = pluginKey in Config // DON'T DO THIS!
|
|
|
|
Playing nice with configurations
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Whether you ship with a configuration or not, a plugin should strive to
|
|
support multiple configurations, including those created by the build
|
|
user. Some tasks that are tied to a particular configuration can be
|
|
re-used in other configurations. While you may not see the need
|
|
immediately in your plugin, some project may and will ask you for the
|
|
flexibility.
|
|
|
|
Provide raw settings and configured settings
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
Split your settings by the configuration axis like so:
|
|
|
|
::
|
|
|
|
val obfuscate = TaskKey[Seq[File]]("obfuscate")
|
|
val obfuscateSettings = inConfig(Compile)(baseObfuscateSettings)
|
|
val baseObfuscateSettings: Seq[Setting[_]] = Seq(
|
|
obfuscate := ... (sources in obfuscate).value ...,
|
|
sources in obfuscate := sources.value
|
|
)
|
|
|
|
The ``baseObfuscateSettings`` value provides base configuration for the
|
|
plugin's tasks. This can be re-used in other configurations if projects
|
|
require it. The ``obfuscateSettings`` value provides the default
|
|
``Compile`` scoped settings for projects to use directly. This gives the
|
|
greatest flexibility in using features provided by a plugin. Here's how
|
|
the raw settings may be reused:
|
|
|
|
::
|
|
|
|
Project.inConfig(Test)(sbtObfuscate.Plugin.baseObfuscateSettings)
|
|
|
|
Alternatively, one could provide a utility method to load settings in a
|
|
given configuration:
|
|
|
|
::
|
|
|
|
def obfuscateSettingsIn(c: Configuration): Seq[Project.Setting[_]] =
|
|
inConfig(c)(baseObfuscateSettings)
|
|
|
|
This could be used as follows:
|
|
|
|
::
|
|
|
|
seq(obfuscateSettingsIn(Test): _*)
|
|
|
|
Using a 'main' task scope for settings
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
Sometimes you want to define some settings for a particular 'main' task
|
|
in your plugin. In this instance, you can scope your settings using the
|
|
task itself.
|
|
|
|
::
|
|
|
|
val obfuscate = TaskKey[Seq[File]]("obfuscate")
|
|
val obfuscateSettings = inConfig(Compile)(baseObfuscateSettings)
|
|
val baseObfuscateSettings: Seq[Setting[_]] = Seq(
|
|
obfuscate := ... (sources in obfuscate).value ...,
|
|
sources in obfuscate := sources.value
|
|
)
|
|
|
|
In the above example, ``sources in obfuscate`` is scoped under the main
|
|
task, ``obfuscate``.
|
|
|
|
Mucking with Global build state
|
|
-------------------------------
|
|
|
|
There may be times when you need to muck with global build state. The
|
|
general rule is *be careful what you touch*.
|
|
|
|
First, make sure your user does not include global build configuration in
|
|
*every* project but rather in the build itself. e.g.
|
|
|
|
::
|
|
|
|
object MyBuild extends Build {
|
|
override lazy val settings = super.settings ++ MyPlugin.globalSettings
|
|
val main = project(file("."), "root") settings(MyPlugin.globalSettings:_*) // BAD!
|
|
}
|
|
|
|
Global settings should *not* be placed into a ``build.sbt`` file.
|
|
|
|
When overriding global settings, care should be taken to ensure previous
|
|
settings from other plugins are not ignored. e.g. when creating a new
|
|
``onLoad`` handler, ensure that the previous ``onLoad`` handler is not
|
|
removed.
|
|
|
|
::
|
|
|
|
object MyPlugin extends Plugin {
|
|
val globalSettigns: Seq[Setting[_]] = Seq(
|
|
onLoad in Global := (onLoad in Global).value andThen { state =>
|
|
... return new state ...
|
|
}
|
|
)
|
|
}
|