Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to define sbt plugin task within prefix and without conflicts with global scope?

Tags:

scala

sbt

I'm trying to write plugin with tasks within plugin scope (for example: "my-plugin:update"). This is my sample code (sbt 0.13.0):

import sbt._
import Keys._

object MyPlugin extends Plugin
{
  lazy val conf = config("my-plugin")
  val update = taskKey[Unit]("Wow!.") in conf
  override lazy val settings = inConfig(conf)(Seq(
    update := println("wow")
  ))
}

but when I'm trying to use this plugin I got this error:

AttributeKey ID collisions detected for: 'update' (sbt.Task[Unit], sbt.Task[sbt.UpdateReport])

Is it possible or not to define task only within plugin scope without collisions?

like image 721
Timothy Klim Avatar asked Oct 03 '22 06:10

Timothy Klim


1 Answers

A quick answer to your question is, no you can't define a task key with the name "update".

To work around this issue, those who participated in Plugin namespacing: A good solution? coauthored Plugins Best Practices, which still stands today. See Avoid namespace clashes section:

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 := ...

I think the choice depends your preference and the number of keys you are going to define. For sbt-buildinfo I've opted to use "Just use a val prefix" approach for the sake of simplicity, but for any other plugins I've gone for "Use a nested object" approach including sbt-assembly.

For more details, see also Mark's post to the thread:

:3. Related, configurations are the wrong axis to group settings for a plugin. Use the main task of your plugin (say 'assembly') to do this. Otherwise I can only easily use your plugin once per project. This may be fine for some plugins, but it is unnecessarily limiting.

:4. Scopes are not namespaces. For any given key name, there can only be one type. That is, you cannot have:

SettingKey[Int]("value")

and

SettingKey[Double]("value")

Putting keys in nested objects only helps when you have the same key/type defined by two different Plugins and so at the Scala identifier level, it is ambiguous:

object A extends Plugin {
  val a = SettingKey[Int]("a")
}
object B extends Plugin {
  val a = SettingKey[Int]("a")
}

// ambiguous in a build.sbt
a := 3

The proper solution is a) reusing built-in keys and b) defining a common library of keys. Keys are the interfaces of the settings system.

like image 163
Eugene Yokota Avatar answered Oct 05 '22 15:10

Eugene Yokota