sbt: run task on subproject - sbt

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)

Related

In a sbt project, how to get full list of dependencies with scope?

In a sbt project, how to get full list of dependencies(including transitive dependencies) with scope?
For Example:
xxx.jar(Compile)
yyy.jar(Provided)
zzz.jar(Test)
...
From the sbt shell you can execute one or all of the following commands:
Test / fullClasspath
Runtime / fullClasspath
Compile / fullClasspath
Which will output the jars associated with the scope (Test/Runtime/Compile).
If you want to get a bit more fancy, sbt provides a number of ways of interacting with the outputs generated through the dependency management system. The documentation is here.
For example, you could add this to your build.sbt file:
lazy val printReport = taskKey[Unit]("Report which jars are in each scope.")
printReport := {
val updateReport: UpdateReport = update.value
val jarFilter: ArtifactFilter = artifactFilter(`type` = "jar")
val testFilter = configurationFilter(name = "test")
val compileFilter = configurationFilter(name = "compile")
val testJars = updateReport.matching(jarFilter && testFilter)
val compileJars = updateReport.matching(jarFilter && compileFilter)
println("Test jars:\n===")
for (jar <- testJars.sorted) yield {println(jar.getName)}
println("\n\n******\n\n")
println("compile jars:\n===")
for (jar <- compileJars.sorted) yield {println(jar.getName)}
}
It creates a new task printReport which can be executed like a normal sbt command with sbt printReport. It takes the value of the UpdateReport which is generated by the update task, and then filters for jar files in the respective test/compile scopes before printing the results.

How to setup sbt-native-packager in a single module project with multiple mains

I have a single module client-server project with a main for each.
I'm trying to use sbt-native-packager to generate start-script for both.
project/P.scala
object Tactic extends Build {
lazy val root =
(project in file(".")).
configs(Client, Server)
.settings( inConfig(Client)(Defaults.configTasks) : _*)
.settings( inConfig(Server)(Defaults.configTasks) : _*)
lazy val Client = config("client") extend Compile
lazy val Server = config("server") extend Compile
}
build.sbt
mainClass in Client := Some("myProject.Client")
mainClass in Server := Some("myProject.Server")
enablePlugins(JavaAppPackaging)
When I run client:stage the directory target/universal/stage/lib is created with all the necessary jars but the bin directory is missing. What am I doing wrong?
Subsidiary question: what is the key to set the starting script name?
I would recommend setting up your project as a multi-module build, instead of creating and using new configurations. I tried your multiple configuration route and it gets hairy very quickly.
For example (I created a shared project for anything shared between client & server):
def commonSettings(module: String) = Seq[Setting[_]](
organization := "org.tactic",
name := s"tactic-$module",
version := "1.0-SNAPSHOT",
scalaVersion := "2.11.6"
)
lazy val root = (project in file(".")
settings(commonSettings("root"))
dependsOn (shared, client, server)
aggregate (shared, client, server)
)
val shared = (project
settings(commonSettings("shared"))
)
val client = (project
settings(commonSettings("client"))
enablePlugins JavaAppPackaging
dependsOn shared
)
val server = (project
settings(commonSettings("server"))
enablePlugins JavaAppPackaging
dependsOn shared
)
Note I'm enabling sbt-native-packager's JavaAppPackaging in the client and server.
Then run stage.
Also, the key for the starting script name is executableScriptName.

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 execute task that calls clean on subprojects without using aggregation?

I want make a take cleanAll that executes the clean task on a number of subprojects. I don't want to use aggregation just for the sake of clean.
We've run into issues with play's asset routes when we've been using submodules.
It's well documented how to create a new task, but how do I call a task on a subproject?
Based on the example by Jacek Laskowski I've come up with the following plugin that should be placed in your /project folder:
import sbt._
import sbt.AutoPlugin
import sbt.Keys._
import sbt.plugins.JvmPlugin
object CleanAllPlugin extends AutoPlugin {
val cleanAll = taskKey[Unit]("Cleans all projects in a build, regardless of dependencies")
override def requires = JvmPlugin
override def projectSettings = Seq(
cleanAllTask
)
def cleanAllTask = cleanAll := Def.taskDyn {
val allProjects = ScopeFilter(inAnyProject)
clean.all(allProjects)
}.value
}
The plugin can now be added to the root project for usage:
val main = Project("root", file("."))
.enablePlugins(CleanAllPlugin)
It can be executed in SBT by calling: cleanAll
Use the following build.sbt:
val selectDeps = ScopeFilter(inDependencies(ThisProject))
clean in Compile := clean.all(selectDeps).value
It assumes that the build.sbt file is in the project that has clean executed on itself and dependsOn projects.
If you need it in project/Build.scala, just add the following:
val selectDeps = ScopeFilter(inDependencies(ThisProject))
val cleanInSubprojects = Seq(
clean in Compile := clean.all(selectDeps).value
)
and add cleanInSubprojects to settings of every project:
// definition of a project goes here
.settings(cleanInSubprojects: _*)

Add task to Build.scala

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.

Resources