Thursday, April 23, 2015

Running with alternate JVM args in sbt

It's pretty easy in sbt to run a main class in a forked JVM:

// Ensure we run outside of sbt. This is especially useful for setting JVM-level flags
fork in run := true

// Set flags for java. Memory, GC settings, properties, etc.
javaOptions ++= Seq("-Xmx4g")

mainClass in run := Some("mypackage.MyMainClass")

That lets you use "run" to spin up a forked JVM with your provided main class and arguments. (Aside: sbt-revolver is a MUCH friendlier way to run main classes).

But what if you want to run a main class with a different set of JVM flags? Maybe you have a memory hog, or you want to specify a debug logback config - or any other number of reasons. Or maybe you just want to have a shortcut for a long class name, or to have a default set of arguments for your main class.

This can be done with the Fork API:

// Create a task for your new main class. This is an input task so that you
// can provide arguments to it via the sbt console.
lazy val runMyOtherMain = inputKey[Unit]("run MyOtherMain")

runMyOtherMain := {
  // Parse the arguments typed on the sbt console.
  val args = sbt.complete.Parsers.spaceDelimited("[main args]").parsed
  // Build up the classpath for the subprocess. Yes, this must be done manually.
  // This also ensures that your code will be compiled before you run.
  val classpath = (fullClasspath in Compile).value
  val classpathString = Path.makeString(classpath map { _.data })
  // Any JVM args you want. You could use javaOptions.value to get your default
  // list, if you like.
  val jvmArgs = Seq("-Xmx4g", "-Dlogback.configurationFile=debug_logback.xml")
  Fork.java(
    // Full options include whatever you want to add, plus the classpath.
    ForkOptions(runJVMOptions = jvmArgs ++ Seq("-classpath", classpathString)),
    // You could also add other default arguments here.
    "mypackage.MyOtherMainClass" +: args
  )
}

Now, you can type runMyOtherMain arg1 arg2 from the sbt console, and it'll execute:

java -Xmx4g -Dlogback.configurationFile=debug_logback.xml -classpath [actual classpath] mypackage.MyOtherMainClass arg1 arg2

Sweet!