Let’s suppose that there’s a task already that does the browser opening called openbrowser because of a plugin. Here’s how we can sequence a task after an input tasks.
lazy val runopen = inputKey[Unit]("run and then open the browser")
lazy val openbrowser = taskKey[Unit]("open the browser")
lazy val root = (project in file("."))
.settings(
runopen := (Def.inputTaskDyn {
import sbt.complete.Parsers.spaceDelimited
val args = spaceDelimited("<args>").parsed
Def.taskDyn {
(Compile / run).toTask(" " + args.mkString(" ")).value
openbrowser
}
}).evaluated,
openbrowser := {
println("open browser!")
}
)
Trying to rewire Compile / run is going to be complicated. Since the reference to the inner Compile / run is already inside the continuation task, simply rewiring runopen to Compile / run will create a cyclic reference.
To break the cycle, we will introduce a clone of Compile / run called Compile / actualRun:
lazy val actualRun = inputKey[Unit]("The actual run task")
lazy val openbrowser = taskKey[Unit]("open the browser")
lazy val root = (project in file("."))
.settings(
Compile / run := (Def.inputTaskDyn {
import sbt.complete.Parsers.spaceDelimited
val args = spaceDelimited("<args>").parsed
Def.taskDyn {
(Compile / actualRun).toTask(" " + args.mkString(" ")).value
openbrowser
}
}).evaluated,
Comile / actualRun := Defaults.runTask(
Runtime / fullClasspath,
Compile / run / mainClass,
Compile / run / runner
).evaluated,
openbrowser := {
println("open browser!")
}
)
* Note that some tasks (ie. testOnly) will fail with trailing spaces, so a right trim (.replaceAll("\s+$", "")) of the string built for toTask might be needed to handle empty args.\
The Compile / actualRun’s implementation was copy-pasted from run task’s implementation in Defaults.scala.
Now we can call run foo from the shell and it will evaluate Compile / actualRun with the passed in argument, and then evaluate the openbrowser task.