SBT plugin -- execute custom task before compilation - scala

I've just written my first SBT Autoplugin which has a custom task that generates a settings file (if the file is not already present). Everything works as expected when the task is explicitly invoked, but I'd like to have it automatically invoked prior to compilation of the project using the plugin (without having the project modify it's build.sbt file). Is there a way of accomplishing this, or do I somehow need to override the compile command? If so, could anyone point me to examples of doing so? Any help would be extremely appreciated! (My apologies if I'm missing something simple!) Thanks!

You can define dependencies between tasks with dependsOn and override the behavior of a scoped task (like compile in Compile) by reassigning it.
The following lines added to a build.sbt file could serve as an example:
lazy val hello = taskKey[Unit]("says hello to everybody :)")
hello := { println("hello, world") }
(compile in Compile) := ((compile in Compile) dependsOn hello).value
Now, every time you run compile, hello, world will be printed:
[IJ]sbt:foo> compile
hello, world
[success] Total time: 0 s, completed May 18, 2018 6:53:05 PM
This example has been tested with SBT 1.1.5 and Scala 2.12.6.

val runSomeShTask = TaskKey[Unit]("runSomeSh", " run some sh")
lazy val startrunSomeShTask = TaskKey[Unit]("runSomeSh", " run some sh")
startrunSomeShTask := {
val s: TaskStreams = streams.value
val shell: Seq[String] = if (sys.props("os.name").contains("Windows")) Seq("cmd", "/c") else Seq("bash", "-c")
// watch out for those STDOUT , SDERR redirection, otherwise this one will hang after sbt test ...
val startMinioSh: Seq[String] = shell :+ " ./src/sh/some-script.sh"
s.log.info("set up run some sh...")
if (Process(startMinioSh.mkString(" ")).! == 0) {
s.log.success("run some sh setup successful!")
} else {
throw new IllegalStateException("run some sh setup failed!")
}
}
// or only in sbt test
// test := (test in Test dependsOn startrunSomeShTask).value
(compile in Compile) := ((compile in Compile) dependsOn startrunSomeShTask).value

Related

Execute sbt task with modified dependency

I want to create a custom runLocal task that executes the sbt run task with modified unmanagedClasspath.
I want the unmanagedClasspath modification to only be visible/last while running runLocal, not run.
What I've tried in build.sbt:
Runtime / unmanagedClasspath ++= Seq(new java.io.File("src/main/my_resources")).classpath
val runLocal = taskKey[Unit]("Run app with my config")
runLocal := {
(Runtime / run).toTask("").value
}
The above works but the problem is that the modification of unmanagedClasspath is "global" and affects every task that uses this value.
How can I run runLocal with modified unmanagedClasspath that is not visible outside that task?
What I ended up doing is using a Command.
Commands have access to the state and are able to modify it.
val runLocal = Command.command("runLocal") { state =>
val extracted = Project.extract(state)
val localConfigClasspath = Seq(new java.io.File("src/main/my_resources")).classpath
val newState = extracted.appendWithoutSession(Seq(Runtime / unmanagedClasspath ++= localConfigClasspath), state)
Project.extract(newState).runInputTask(Runtime / run, "", newState)._1
}
This way the config is only changed while running sbt runLocal without affecting sbt run.

SBT: Custom commands aggregation

I'm looking for a way to create a custom command in sbt for multi-project structure in a such way:
This command shouldn't do anything for the root project
This command should make some action in subprojects
Executing command for the root project should trigger execution such commands for subproject
I have this in my build.sbt
lazy val root = (project in file("."))
.settings(
name := "Custom command",
commands += Command.command("customCommand") { state =>
state
}
)
.aggregate(a, b)
lazy val a = project
.settings(
commands += Command.command("customCommand") { state =>
println("Hello")
state
}
)
lazy val b = project
.settings(
commands += Command.command("customCommand") { state =>
println("Hey there")
state
}
)
Unfortunately, I get nothing in the sbt shell:
sbt:Custom command> customCommand
sbt:Custom command>
The expected output is:
sbt:Custom command> customCommand
Hello
Hey there
sbt:Custom command>
The order doesn't matter.
So it looks like my command is executed for the root project only. Is there any way to tell sbt to execute customCommand for subproject first?
Most of the time you don't really need a command and can do everything you want with a task. You can define a task key, override it in your subprojects and the aggregating project will do what you expect (aggregate):
lazy val customTask = taskKey[Unit]("My custom task")
lazy val root = (project in file("."))
.aggregate(a, b)
lazy val a = project
.settings(
customTask := { println("Hello") }
)
lazy val b = project
.settings(
customTask := { println("Hey there") }
)
Here's how it works:
> a/customTask
Hello
[success] Total time: 0 s, completed Apr 5, 2018 9:31:43 PM
> b/customTask
Hey there
[success] Total time: 0 s, completed Apr 5, 2018 9:31:47 PM
> customTask
Hey there
Hello
[success] Total time: 0 s, completed Apr 5, 2018 9:31:50 PM
Check sbt documentation on Commands, you need it only if you need to manipulate the build state explicitly (which is hard to do carefully), call other existing commands or modify settings (which you normally shouldn't):
Typically, you would resort to a command when you need to do something that’s impossible in a regular task.
If you still need a command, I think you cannot override it in subprojects and aggregate like a task, but you can probably define it once and inside it dispatch on the project it is called from. I really doubt you want it.

Task triggered by run

If in my build.sbt I have the following code:
val example = TaskKey[Unit]("example")
example := Def.task[Unit] {
streams.value.log.info("EXAMPLE")
}.triggeredBy(compile in Compile).value
When I execute ~ compile, whenever a source changes I'll see in the console EXAMPLE.
How to get the same behavior for ~ run arg1 arg2?
The difficulty I'm having in implementing this is because run is an InputKey as opposed to compile in Compile which is a TaskKey.
You can have :
example in run := Def.task[Unit] {
streams.value.log.info("EXAMPLE")
}.triggeredBy(compile in Compile).value

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: Running separate module of multi-module application

I'm trying to create a multi-module application and run one of it's modules separately from the others (from another machine).
Project structure looks like this:
main
/ \
module1 module2
I want to run a module1 as a separate jar file (or there is a better way of doing this?), which I will run from another machine (I want to connect it to the main app using Akka remoting).
What I'm doing:
Running "play dist" command
Unzipping module1.zip from universal folder
Setting +x mode to bin/module1 executable.
Setting my main class (will paste it below): instead of play.core.server.NettyServer im putting my main class: declare -r app_mainclass="module1.foo.Launcher"
Running with external application.conf file.
Here is my main class:
class LauncherActor extends Actor {
def receive = {
case a => println(s"Received msg: $a ")
}
}
object Launcher extends App {
val system = ActorSystem("testsystem")
val listener = system.actorOf(Props[LauncherActor], name = "listener")
println(listener.path)
listener ! "hi!"
println("Server ready")
}
Here is the console output:
#pavel bin$ ./module1 -Dconfig.file=/Users/pavel/projects/foobar/conf/application.conf
[WARN] [10/18/2013 18:56:03.036] [main] [EventStream(akka://testsystem)] [akka.event-handlers] config is deprecated, use [akka.loggers]
akka://testsystem/user/listener
Server ready
Received msg: hi!
#pavel bin$
So the system switches off as soon as it gets to the last line of the main method. If I run this code without Play - it works as expected, the object is loaded and it waits for messages, which is expected behavior.
Maybe I'm doing something wrong? Or should I set some options in module1 executable? Other ideas?
Thanks in advance!
Update:
Versions:
Scala - 2.10.3
Play! - 2.2.0
SBT - 0.13.0
Akka - 2.2.1
Java 1.7 and 1.6 (tried both)
Build properties:
lazy val projectSettings = buildSettings ++ play.Project.playScalaSettings ++ Seq(resolvers := buildResolvers,
libraryDependencies ++= dependencies) ++ Seq(scalacOptions += "-language:postfixOps",
javaOptions in run ++= Seq(
"-XX:MaxPermSize=1024m",
"-Xmx4048m"
),
Keys.fork in run := true)
lazy val common = play.Project("common", buildVersion, dependencies, path = file("modules/common"))
lazy val root = play.Project(appName, buildVersion, settings = projectSettings).settings(
resolvers ++= buildResolvers
).dependsOn(common, module1, module2).aggregate(common, module1, module2)
lazy val module1 = play.Project("module1", buildVersion, path = file("modules/module1")).dependsOn(common).aggregate(common)
lazy val module2: Project = play.Project("module2", buildVersion, path = file("modules/module2")).dependsOn(common).aggregate(common)
So I found a dirty workaround and I will use it until I will find a better solution. In case someone is interested, I've added this code at the bottom of the Server object:
val shutdown = Future {
readLine("Press 'ENTER' key to shutdown")
}.map { q =>
println("**** Shutting down ****")
System.exit(0)
}
import scala.concurrent.duration._
Await.result(shutdown, 100 days)
And now system works until I will hit the ENTER key in the console. Dirty, I agree, but didn't find a better solution.
If there will be something better, of course I will mark it as an answer.