Executing mill run
with the following build file will start the program, pause for a debugger to connect, and then continue under the debugger's control.
import mill._, scalalib._
object foo extends RootModule with ScalaModule {
def scalaVersion = "3.3.3"
override def forkArgs = Seq("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005")
object test extends ScalaTests {
def ivyDeps = Agg(ivy"com.lihaoyi::utest:0.7.11")
def testFramework = "utest.runner.Framework"
}
}
The problem is that I'd like to easily toggle between running with the debugger and running without. That is, I'd like to replace override def forkArgs...
with
override def forkArgs = if (someMagicIncantation)
Seq("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005")
else
Seq()
Ideally, someMagicIncantation
would be connected to the command line so that mill withDebugger run
or mill withDebugger test
would connect with the debugger while just mill run
would not.
Adding the following allows me to do mill debug on
and mill debug off
, writing the option string into out/debug.json
.
def debug(arg: String) = T.command {
if (arg.trim.toLowerCase() == "on")
"-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005"
else ""
}
However, I don't know how to make progress from this because commands are not json readable. That is, I don't know how to suck that string back in on a subsequent run of mill.
Any suggestions, including completely different approaches (short of abandoning mill!) would be welcome.
Here's what I finally did for my project. This works for test
and testOnly
as well as run
with minimal extra code (note the 1 line in the test
object).
The disadvantage is that it feels a little hackish because I have to read one of the cached files directly. I'd prefer to view those cache files as private data.
Usage:
mill debug on
: Subsequent runs of the program stop for a debugger to attach.mill debug off
: Subsequent runs of the program do not stop for the debugger.import mill._
import scalalib._
object foo extends RootModule with ScalaModule with DebugSupport {
def scalaVersion = "3.3.3"
object test extends ScalaTests {
def ivyDeps = Agg(ivy"com.lihaoyi::utest:0.7.11")
def testFramework = "utest.runner.Framework"
override def forkArgs = foo.forkArgs
}
}
/** Support for attaching a debugger. Toggle between running with and without a debugger with `mill debug on`
* and `mill debug off`. Nested objects (e.g. test) will need to have `override def forkArgs = foo.forkArgs`
* for whatever value of `foo` you have as the top-level object. */
trait DebugSupport extends JavaModule {
override def forkArgs = T.input {
val debugFile = os.pwd / "out" / "debug.json"
val args = if (os.isFile(debugFile)) ujson.read(os.read(debugFile))("value").str else ""
if (args == "") super.forkArgs() else super.forkArgs() :+ args
}
/** Toggle between debugging on and debugging off. */
def debug(arg: String) = T.command {
if (arg.trim.toLowerCase() == "on")
"-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005"
else ""
}
}