SBT plugin - User defined configuration for Command via their build.sbt - scala

I'm writing an SBT Plugin that adds a Command and would like users to be able to configure this Command by setting variables in their build.sbt. What is the simplest way to achieve this?
Here is an simplified example of what the Plugin looks like:
import sbt.Keys._
import sbt._
object MyPlugin extends Plugin {
override lazy val settings = Seq(commands += Command.args("mycommand", "myarg")(myCommand))
def myCommand = (state: State, args: Seq[String]) => {
//Logic for command...
state
}
}
I would like someone to be able to add the follow to their build.sbt file:
newSetting := "light"
How do I make this available as a String variable from inside the myCommand Command above?

Take a look at the example here: http://www.scala-sbt.org/release/docs/Extending/Plugins.html#example-plugin
In this example, a task and setting are defined:
val newTask = TaskKey[Unit]("new-task")
val newSetting = SettingKey[String]("new-setting")
val newSettings = Seq(
newSetting := "test",
newTask <<= newSetting map { str => println(str) }
)
A user of your plugin could then provide their own value for the newSetting setting in their build.sbt:
newSetting := "light"
EDIT
Here's another example, closer to what you're going for:
Build.scala:
import sbt._
import Keys._
object HelloBuild extends Build {
val newSetting = SettingKey[String]("new-setting", "a new setting!")
val myTask = TaskKey[State]("my-task")
val mySettings = Seq(
newSetting := "default",
myTask <<= (state, newSetting) map { (state, newSetting) =>
println("newSetting: " + newSetting)
state
}
)
lazy val root =
Project(id = "hello",
base = file("."),
settings = Project.defaultSettings ++ mySettings)
}
With this configuration, you can run my-task at the sbt prompt, and you'll see newSetting: default printed to the console.
You can override this setting in build.sbt:
newSetting := "modified"
Now, when you run my-task at the sbt prompt, you'll see newSetting: modified printed to the console.
EDIT 2
Here's a stand-alone version of the example above: https://earldouglas.com/ext/stackoverflow.com/questions/17038663/

I've accepted #James's answer as it really helped me out. I moved away from using a Commands in favour of a Task (see this mailing list thread). In the end my plugin looked something like this:
package packge.to.my.plugin
import sbt.Keys._
import sbt._
object MyPlugin extends Plugin {
import MyKeys._
object MyKeys {
val myTask = TaskKey[Unit]("runme", "This means you can run 'runme' in the SBT console")
val newSetting = SettingKey[String]("newSetting")
}
override lazy val settings = Seq (
newSetting := "light",
myTask <<= (state, newSetting) map myCommand
)
def myCommand(state: State, newSetting: String) {
//This code runs when the user types the "runme" command in the SBT console
//newSetting is "light" here unless the user overrides in their build.sbt (see below)
state.log.info(newSetting)
}
}
To override the newSetting in the build.sbt of a project that uses this plugin:
import packge.to.my.plugin.MyKeys._
newSetting := "Something else"
The missing import statement had me stuck for a while!

Related

Set task settings from build.sbt

I am writing a small sbt plugin to generate some files which should be configurable by a target path parameter. Therefore I wrote this plugin code:
object GeneratorPlugin extends AutoPlugin {
object autoImport {
val targetPath = settingKey[String]["target directory"]
val generateFiles = taskKey[Unit]["generate files"]
}
import autoImport._
override def trigger = allRequirements
override lazy val buildSettings = Seq(
targetPath := ".",
generateFiles := generateTask
)
lazy val generateTask = Def.task {
System.out.println(targetPath.value)
}
}
When importing this using addSbtPlugin in project/plugins.sbt and running it with sbt generateFiles is correctly printing .. However when I change the value of targetPath in my build.sbt the result does not change.
targetPath := "/my/new/path"
Result of sbt generateFiles is still ..
Is there a way to change the value of targetPath within my build.sbt when importing the plugin?
You can change it like so:
targetPath in ThisBuild := "/my/new/path"
or in the sbt 1.1's new slash syntax
ThisBuild / targetPath := "/my/new/path"

sbt - Adding a task key defined outside of build.sbt

If I define a SBT task key outside of my build.sbt file as a Scala class in the project folder, how can I import that task
So in ./project/MyTask.scala I have;
import sbt.Keys._
import sbt._
object MyTask {
lazy val uname = settingKey[String]("Your name")
lazy val printHi = taskKey[Unit]("print Hi")
printHi := { println(s"hi ${name.value}") }
}
Then in ./build.sbt I have;
import MyTask._
uname := "Joe"
Then when I run sbt printHi I get an error that the task cannot be found. Running show uname also works. When I define printHi in build.sbt directly without the object import everything works as expected.
I need so somehow add this task to the build.sbt file. How can I do this?
The issue is that your expression printHi := { println(s"hi ${name.value}") } isn't associated to anything.
First off, everything in sbt is a transformation, in this case (:=) overrides any previous setting of printHi to the definition you give (println(s"hi ${name.value}")). But by not associating that expression (which is a Setting[Task[Unit]]) to anything (for instance to a project, or as a value that then gets attached to a project) it just gets evaluated in the construction of the MyTask object and then thrown away.
One way to do this is to put that setting (printHi := println(s"hi ${name.value}")), in a Seq[Setting[_]] that you then pull into build.sbt:
project/MyTask.scala
import sbt._, Keys._
object MyTask {
val printHi = taskKey[Unit]("prints Hi")
val myTaskSettings = Seq[Setting[_]](
printHi := println(s"hi ${name.value}")
)
}
build.sbt
import MyTask._
myTaskSettings
Another way is to define MyTask to be a mini plugin that lives in project/. You can see an example of this in PgpCommonSettings.

How do you share a custom task in a SBT multi-project

I have a project set up as a SBT multi-build. That looks like this:
- project
Dependencies.scala
- core
build.sbt
- server
build.sbt
build.sbt
I want to use Dependencies.scala as a container for version numbers of libraries that are shared between the sub-projects.
sealed trait Dependencies {
val commonsIo = "2.4"
}
object DependencyVersions extends Dependencies
In the root build.sbt I added a Setting that is given to each sub-project.
lazy val dependencies = settingKey[Dependencies]("versions")
val defaultSettings = Defaults.coreDefaultSettings ++ Seq(
dependencies := DependencyVersions)
def projectFolder(name: String, theSettings: Seq[Def.Setting[_]] = Nil) = Project(name, file(name), settings = theSettings)
lazy val core = projectFolder("core", defaultSettings)
I can't access the dependencies setting in core/build.sbt.
"commons-io" % "commons-io" % dependencies.value.commonsIo, <-- doesn't work
How can I get this to work?
You can define common settings (dependencies) in an object Common extends AutoPlugin (in project/Common.scala), and then use .enablePlugin(Common) on sub-project descriptor (see it in Anorm).
Thanks #cchantep got it working now using the AutoPlugin below
import sbt._
sealed trait Dependencies {
val commonsIo = "2.4"
}
object DependencyVersions extends Dependencies
object DependencyVersionsPlugin extends AutoPlugin {
override def trigger = allRequirements
object autoImport {
lazy val dependencies = settingKey[Dependencies]("Bundles dependency versions")
}
import autoImport._
override def projectSettings = Seq(
dependencies := DependencyVersions
)
}

SBT 0.13 Build.scala References to undefined settings

I am new to SBT and I have been trying to build a custom task for this build.
I have a simple build project:
import sbt._
import Keys._
object JsonBuild extends Build{
lazy val barTask = taskKey[Unit]("some simple task")
val afterTestTask1 = barTask := { println("tests ran!") }
val afterTestTask2 = barTask <<= barTask.dependsOn(test in Test)
lazy val myBarTask = taskKey[Unit]("some simple task")
//val afterMyBarTask1 = myBarTask := { println("tests ran!") }
lazy val afterMyBarTask2 = myBarTask <<= (myBarTask).dependsOn(test in Test) map { _ => println("tests ran!") }
//settings ++ Seq(afterMyBarTask2)
override lazy val settings = super.settings ++ Seq(afterMyBarTask2)
}
I keep getting the error:
References to undefined settings:
{.}/*:myBarTask from {.}/*:myBarTask (C:\Users\haques\Documents\workspace\SBT\jsonParser\project\Build.scala:13)
{.}/test:test from {.}/*:myBarTask (C:\Users\haques\Documents\workspace\SBT\jsonParser\project\Build.scala:13)
Did you mean test:test ?
I have googled around and I cannot find a solution.
Can you explain why it is not working?
lazy val myBarTask = taskKey[Unit]("some simple task")
override lazy val settings = super.settings ++ Seq(myBarTask := { (test in Test).value; println("tests ran!") } )
myBarTask is undefined when you call dependsOn. you should define it before using dependsOn. also value call on key (task/setting) is now preferred way to depend on other keys. you can still use your version, but define myBarTask
This has been bothering.
I did a bit more reading.
I think I know why the above code does not work.
lazy val afterMyBarTask2 = myBarTask <<= (myBarTask).dependsOn(test in Test) map { _ => println("tests ran!") }
When I write (myBarTask).dependsOn(test in Test), the project scope for test is chosen by SBT as ThisBuild.
{.}/test:test from {.}/*:myBarTask (C:\Users\haques\Documents\workspace\SBT\jsonParser\project\Build.scala:13)
ThisBuild project scope does not have the setting test in configuration Test.
Only projects have the setting test.
The key I think that setting is added by some default SBT plugin to the projects settings.
You check what scopes settings exist in SBT by using the inspect command.
If you type in the SBT REPL:
{.}/test:test
The output is:
inspect {.}/test:test
[info] No entry for key.
SBT correctly suggests:
test:test which is:
{file:/C:/Users/haques/Documents/workspace/SBT/jsonParser/}jsonparser/test:test
If the project is not specified in the project scope axis, SBT chooses the current project by default.
Every SBT project if not specified has its own project settings.

Play Framework and scala.tools.nsc

I have to use scala parser inside Play Framework application.
import scala.tools.nsc._
trait Foo
class Parser {
def parse(code: String) = {
val settings = new Settings
settings.embeddedDefaults[Foo]
val interpreter = new Interpreter(settings)
interpreter.parse(code)
}
}
I have following dependency in Build.scala
"org.scala-lang" % "scala-compiler" % "2.9.1"
This code works when build using SBT. In Play it ends with NullPointerException and:
Failed to initialize compiler: object scala not found.
** Note that as of 2.8 scala does not assume use of the java classpath.
** For the old behavior pass -usejavacp to scala, or if using a Settings
** object programatically, settings.usejavacp.value = true.
Build.scala
import sbt._
import Keys._
import PlayProject._
object ApplicationBuild extends Build {
val appName = "com.qwerty.utils"
val appVersion = "1.0-SNAPSHOT"
val scalaVersion = "2.9.1"
val appDependencies = Seq(
"org.scala-lang" % "scala-compiler" % "2.9.1"
)
val main = PlayProject(appName, appVersion, appDependencies, mainLang = SCALA).settings(
// Add your own project settings here
)
}
For background on embeddedDefaults, see the original proposal.
The container (Play) must define the 'app.class.path' and 'boot.class.path' resources and then embeddedDefaults will use them to configure the interpreter properly for the environment. So, this is an enhancement for Play.
If you can pass the necessary classpaths into your application, you can configure classpaths and classloaders explicitly yourself with something like:
val settings = new Settings
settings.classpath.value = "<classpath>"
settings.bootclasspath.value =
settings.bootclasspath.value + File.pathSeparator +
"<extra-bootclasspath>"
val interpreter = new Interpreter(settings) {
override def parentClassLoader = classOf[Foo].getClassLoader
}
interpreter.parse(code)
The bootclasspath should generally contain scala-library.jar and the classpath should contain the application jars.