Add task to Build.scala - sbt

The document http://www.scala-sbt.org/0.13.0/docs/Detailed-Topics/Tasks.html explains how to add a task to build.sbt, but how do you add one to build.scala? Thanks

The part where you declare the TaskKey is the same in either format: val myTask = taskKey....
The part where you write out your Initialize[Task[T]] is the same: myTask := ....
The only difference is the context in which the latter thing appears.
In the .sbt format, it appears by itself, separated from other things by blank lines.
In the .scala format, you have to add the setting to the project. That's documented at http://www.scala-sbt.org/release/docs/Getting-Started/Full-Def.html and is the same regardless of whether we're talking about a task or a regular setting.
Here's a complete working example:
import sbt._
object MyBuild extends Build {
val myTask = taskKey[Unit]("...")
lazy val root =
Project(id = "MyProject", base = file("."))
.settings(
myTask := { println("hello") }
)
}

When defining a task in one project of a multi project build and using a "root" project to aggregate other projects, the aggregation means that any tasks in subprojects can be run from the root project as well, in fact this will run the tasks in all subprojects - see Multi-project builds. This is generally useful, for example to compile all subprojects when the compile task is run from the root project. However in this case it is a little confusing.
So the task is not accessible in all projects, but is accessible in both the subproject where you define the task, and the aggregating (root) project. The task is still running in the project where it is defined, it can just be called when in the root project.
To demonstrate this, we can have the same "hello" task defined in multiple sub projects, which are aggregated in a root project:
import sbt._
import Keys._
object Build extends Build {
val hwsettings = Defaults.defaultSettings ++ Seq(
organization := "organization",
version := "0.1",
scalaVersion := "2.10.4"
)
val hello = TaskKey[Unit]("hello", "Prints hello.")
lazy val projectA = Project(
"a",
file("a"),
settings = hwsettings ++ Seq(
hello := {println("Hello from project A!")}
))
lazy val projectB = Project(
"b",
file("b"),
settings = hwsettings ++ Seq(
hello := {println("Hello from project B!")}
))
lazy val projectC = Project(
"c",
file("c"),
settings = hwsettings
)
lazy val root = Project (
"root",
file ("."),
settings = hwsettings
) aggregate (projectA, projectB, projectC)
}
Note that projects a and b have "hello" tasks, and c does not. When we use "hello" from the root project, the aggregation causes the task to run in projects a AND b:
> project root
[info] Set current project to root (in build file:/Users/trepidacious/temp/multiProjectTasks/)
> hello
Hello from project A!
Hello from project B!
[success] Total time: 0 s, completed 24-Dec-2014 23:00:23
We can also switch to project a or b, and running hello will only run the task in the project we are in:
> project a
[info] Set current project to a (in build file:/Users/trepidacious/temp/multiProjectTasks/)
> hello
Hello from project A!
[success] Total time: 0 s, completed 24-Dec-2014 23:00:27
> project b
[info] Set current project to b (in build file:/Users/trepidacious/temp/multiProjectTasks/)
> hello
Hello from project B!
[success] Total time: 0 s, completed 24-Dec-2014 23:00:30
Finally, if we switch to project c, hello is not defined:
> project c
[info] Set current project to c (in build file:/Users/trepidacious/temp/multiProjectTasks/)
> hello
[error] Not a valid command: hello (similar: shell, help, reload)
[error] No such setting/task
[error] hello
[error] ^
>
This aggregation can be disabled, as described here.

Related

Key in Configuration : how to list Configurations and Keys?

The sbt in Action book introduces a concept of Key in Configuration
It then lists the default configurations:
Compile
Test
Runtime
IntegrationTest
Q1) Is it possible to print out a list of all Configurations from a sbt session? If not, can I find information on Configurations in the sbt documentation?
Q2) For a particular Configuration, e.g. 'Compile', is it possible to print out a list of Keys for the Configuration from a sbt session? If not, can I find information on a Configuration's Keys in the sbt documentation?
List of all configurations
For this you can use a setting like so:
val allConfs = settingKey[List[String]]("Returns all configurations for the current project")
val root = (project in file("."))
.settings(
name := "scala-tests",
allConfs := {
configuration.all(ScopeFilter(inAnyProject, inAnyConfiguration)).value.toList
.map(_.name)
}
This shows the name of all configurations. You can access more details about each configuration inside the map.
Output from the interactive sbt console:
> allConfs
[info] * provided
[info] * test
[info] * compile
[info] * runtime
[info] * optional
If all you want is to print them you can have a settingKey[Unit] and use println inside the setting definition.
List of all the keys in a configuration
For this we need a task (there might be other ways, but I haven't explored, in sbt I'm satisfied if something works... ) and a parser to parse user input.
All join the above setting in this snippet:
import sbt._
import sbt.Keys._
import complete.DefaultParsers._
val allConfs = settingKey[List[String]]("Returns all configurations for the current project")
val allKeys = inputKey[List[String]]("Prints all keys of a given configuration")
val root = (project in file("."))
.settings(
name := "scala-tests",
allConfs := {
configuration.all(ScopeFilter(inAnyProject, inAnyConfiguration)).value.toList
.map(_.name)
},
allKeys := {
val configHints = s"One of: ${
configuration.all(ScopeFilter(inAnyProject, inAnyConfiguration)).value.toList.mkString(" ")
}"
val configs = spaceDelimited(configHints).parsed.map(_.toLowerCase).toSet
val extracted: Extracted = Project.extract(state.value)
val l = extracted.session.original.toList
.filter(set => set.key.scope.config.toOption.map(_.name.toLowerCase)
.exists(configs.contains))
.map(_.key.key.label)
l
}
)
Now you can use it like:
$ sbt "allKeys compile"
If you are in interactive mode you can press tab after allKeys to see the prompt:
> allKeys
One of: provided test compile runtime optional
Since allKeys is a task it's output won't appear on the sbt console if you just "return it" but you can print it.

How to combine InputKey and TaskKey into a new InputKey?

I have a SBT multi project which includes two sub projects. One is an ordinary Scala web server project and the other is just some web files. With my self written SBT plugin I can run Gulp on the web project. This Gulp task runs asynchronous. So with
sbt "web/webAppStart" "server/run"
I can start the Gulp development web server and my Scala backend server in parallel. Now I want to create a new task, that combines them both. So afterwards
sbt dev
for example should do the same. Here is what I tried so far:
// Build.sbt (only the relevant stuff)
object Build extends sbt.Build {
lazy val runServer: InputKey[Unit] = run in server in Compile
lazy val runWeb: TaskKey[Unit] = de.choffmeister.sbt.WebAppPlugin.webAppStart
lazy val dev = InputKey[Unit]("dev", "Starts a development web server")
// Scala backend project
lazy val server = (project in file("project-server"))
// Web frontend project
lazy val web = (project in file("project-web"))
// Root project
lazy val root = (project in file("."))
.settings(dev <<= (runServer) map { (_) => {
// do nothing
})
.aggregate(server, web)
This works so far. Now I don't have any idea, how to make dev also depend on the runWeb task. If I just add the runWeb task like
.settings(dev <<= (runWeb, runServer) map { (_, _) => {
// do nothing
})
then I get the error
[error] /Users/choffmeister/Development/shop/project/Build.scala:59:value map is not a member of (sbt.TaskKey[Unit], sbt.InputKey[Unit])
[error] .settings(dev <<= (runWeb, runServer) map { (_, _) =>
[error] ^
[error] one error found
[error] (compile:compile) Compilation failed
Can anyone help me with this please?
The optimal solution would pass the arguments given to dev to the runServer task. But I could also live with making dev a TaskKey[Unit] and then hard code to run runServer with no arguments.
tl;dr Use .value macro to execute dependent tasks or just alias the task sequence.
Using .value macro
Your case seems overly complicated to my eyes because of the pre-0.13 syntax (<<=) and the use of project/Build.scala (that often confuse not help people new to sbt).
You should just execute the two tasks in another as follows:
dev := {
runWeb.value
runServer.value
}
The complete example:
lazy val server = project
lazy val runServer = taskKey[Unit]("runServer")
runServer := {
println("runServer")
(run in server in Compile).value
}
lazy val runWeb = taskKey[Unit]("runWeb")
runWeb := {
println("runWeb")
}
lazy val dev = taskKey[Unit]("dev")
dev := {
println("dev")
}
dev <<= dev dependsOn (runServer, runWeb)
Using alias command
sbt offers alias command that...
[sbt-learning-space]> help alias
alias
Prints a list of defined aliases.
alias name
Prints the alias defined for `name`.
alias name=value
Sets the alias `name` to `value`, replacing any existing alias with that name.
Whenever `name` is entered, the corresponding `value` is run.
If any argument is provided to `name`, it is appended as argument to `value`.
alias name=
Removes the alias for `name`.
Just define what tasks/command you want to execute in an alias as follows:
addCommandAlias("devAlias", ";runServer;runWeb")
Use devAlias as if it were a built-in task:
[sbt-learning-space]> devAlias
runServer
[success] Total time: 0 s, completed Jan 25, 2015 6:30:15 PM
runWeb
[success] Total time: 0 s, completed Jan 25, 2015 6:30:15 PM

how to set sbt plugins invoke scope?

val webAssemblyTask = TaskKey[Unit](
"web-assembly",
"assembly web/war like run-time package"
)
var out: TaskStreams = _
val baseSettings: Seq[Setting[_]] = Seq(
webAssemblyOutputDir <<= (sourceManaged) { _ / "build" },
webAssemblyTask <<= (
streams,
target,
sourceDirectory,
outputDirProjectName
) map {
(out_log, targetDir, sourceDir, outputDirProjectName) => {
out_log.log.info("web-assembly start")
out_log.log.info("sourceDir:" + sourceDir.getAbsolutePath)
out_log.log.info("targetDir:" + targetDir.getAbsolutePath)
val sourceAssetsDir = (sourceDir / "webapp" / "assets").toPath
val classesAssetsDir = (targetDir / "scala-2.10" / "classes" / "assets").toPath
Files.createSymbolicLink(classesAssetsDir, sourceAssetsDir)
}
}
)
val webAssemblySettings = inConfig(Runtime)(baseSettings)
I wrote a plugin of sbt.
I type webAssembly in sbt console, the plugin run ok.
But I want to run after compile, before runtime, how can I do it?
how to set sbt plugins invoke scope?
I think you're confusing the configuration (also known as Maven scope) name with tasks like compile and run. They happen to have related configuration, but that doesn't mean compile task is identical to Compile configuration.
I could interpret this question to be how can a plugin setting invoke tasks scoped in some other configuration. For that you use in method like: key in (Config) or key in (Config, task). Another way to interpret it may be how can plugin tasks be scoped in a configuration. You use inConfig(Config)(...), which you're already doing. But you'd typically want plugins to be configuration neutral. See my blog post for more details on this.
I want to run after compile, before run, how can I do it?
This makes much more sense. In sbt you mostly focus on the preconditions of the tasks. One of the useful command is inspect tree key. You can run that for run tasks and get the entire tasks/settings that it depends on. Here's where you see it calling compile:compile (another notation for compile in Compile):
helloworld> inspect tree run
[info] compile:run = InputTask[Unit]
[info] +-runtime:fullClasspath = Task[scala.collection.Seq[sbt.Attributed[java.io.File]]]
[info] | +-runtime:exportedProducts = Task[scala.collection.Seq[sbt.Attributed[java.io.File]]]
[info] | | +-compile:packageBin::artifact = Artifact(sbt-sequential,jar,jar,None,List(compile),None,Map())
[info] | | +-runtime:configuration = runtime
[info] | | +-runtime:products = Task[scala.collection.Seq[java.io.File]]
[info] | | | +-compile:classDirectory = target/scala-2.10/sbt-0.13/classes
[info] | | | +-compile:copyResources = Task[scala.collection.Seq[scala.Tuple2[java.io.File, java.io.File]]]
[info] | | | +-compile:compile = Task[sbt.inc.Analysis]
This is useful in discovering compile:products, which "Build products that get packaged" according to help products command:
helloworld> help products
Build products that get packaged.
Since runtime:products happens before compile:run, if it depended on your task, your task will be called before compile:run (inspect tree also shows that run resolved to that).
To simplify your plugin task, I'm just going to call it sayHello:
val sayHello = taskKey[Unit]("something")
sayHello := {
println("hello")
}
You can rewire products in Runtime as follows:
products in Runtime := {
val old = (products in Runtime).value
sayHello.value
old
}
This will satisfy "before run" part. You want to make sure that this runs after compile. Again, just add task dependency to it:
sayHello := {
(compile in Compile).value
println("hello")
}
When the user runs run task, sbt will correct calculate the dependencies and runs sayHello task somewhere between compile and run.

sbt: run task on subproject

I have the following project structure:
lazy val root = project.aggregate(rest,backend)
lazy val rest = project
lazy val backend = project
When I execute the "run" task from the parent, I want a specific class from the "backend" project to have its main method executed. How would I accomplish this?
lazy val root = project.aggregate(rest,backend).dependsOn(rest,backend) //<- don't forget dependsOn
lazy val rest = project
lazy val backend = project.settings(mainClass in (Compile, run) := Some("fully.qualified.path.to.MainClass"))
run in Compile <<= (run in Compile in backend)

Unzipping an artifact with SBT

As part of my project build, I'd like to unzip a zip artifact of a managed dependency into a specific directory of the project. Before starting to use SBT I was doing this via an ANT script that would fetch the zip artifact from a maven dependency and unzip it.
My question(s) are:
how to specify that I want to depend on the zip dependency? I have defined it like so:
"eu.delving" % "sip-creator" % "0.4.6-SNAPSHOT"
but this doesn't fetch the zip artifact
where / how to hook into the build process to run the unzip (and how to refer to the artifact file in that context)?
If you want to extract a set of managed dependencies, the code below should work. I tested it in sbt 0.12.0, but it should also work with 0.11.x.
import sbt._
import Keys._
import Classpaths.managedJars
object TestBuild extends Build {
lazy val jarsToExtract = TaskKey[Seq[File]]("jars-to-extract", "JAR files to be extracted")
lazy val extractJarsTarget = SettingKey[File]("extract-jars-target", "Target directory for extracted JAR files")
lazy val extractJars = TaskKey[Unit]("extract-jars", "Extracts JAR files")
lazy val testSettings = Defaults.defaultSettings ++ Seq(
// define dependencies
libraryDependencies ++= Seq(
"com.newrelic" % "newrelic-api" % "2.2.1"
),
// collect jar files to be extracted from managed jar dependencies
jarsToExtract <<= (classpathTypes, update) map { (ct, up) =>
managedJars(Compile, ct, up) map { _.data } filter { _.getName.startsWith("newrelic-api") }
},
// define the target directory
extractJarsTarget <<= (baseDirectory)(_ / "extracted"),
// task to extract jar files
extractJars <<= (jarsToExtract, extractJarsTarget, streams) map { (jars, target, streams) =>
jars foreach { jar =>
streams.log.info("Extracting " + jar.getName + " to " + target)
IO.unzip(jar, target)
}
},
// make it run before compile
compile in Compile <<= extractJars map { _ => sbt.inc.Analysis.Empty }
)
lazy val test: Project = Project("test", file(".")) settings (testSettings: _*)
}
If you simply have jar files to extract, you can add them as unmanaged dependencies, ie. putting them into the /lib folder. See: https://github.com/harrah/xsbt/wiki/Getting-Started-Library-Dependencies
If you really have zip files (or want to extract the unmanaged dependencies), you can change the above code to list them:
// list jar files to be extracted
jarsToExtract <<= (baseDirectory) map { dir => Seq(dir / "lib" / "newrelic-api-2.2.1.zip") },
You should now be able to manually extract them from sbt and they should automatically be extracted before compile:
> clean
[success] Total time: 0 s, completed Oct 12, 2012 5:39:16 PM
> extract-jars
[info] Extracting newrelic-api-2.2.1.zip to /Users/balagez/Sites/test/extracted
[success] Total time: 0 s, completed Oct 12, 2012 5:39:22 PM
> compile
[info] Extracting newrelic-api-2.2.1.zip to /Users/balagez/Sites/test/extracted
[success] Total time: 0 s, completed Oct 12, 2012 5:39:24 PM
Now you can add a new task or extend the existing one which extracts the zip file from the extracted dependency. If you don't need the contents of the dependency, you can use the task-temporary-directory setting which gives you a temporary directory writable by sbt:
// define the target directory
extractJarsTarget <<= taskTemporaryDirectory,

Resources