JeanMarc
JeanMarc

Reputation: 326

How can I set javaOptions in a custom sbt command

I am trying to add a custom command to SBT (using version 1.2.8 at the moment) to start my application with different javaOptions. I managed to create 3 separate tasks to accomplish this for three of the startup possibilities, but now I want to generalize this to allow any startup parameter. So far I managed to create the following build.sbt, but the console output shows that the sbt runNode node3 still uses the original classpath as set on line 13 (run / javaOptions ++= Seq(...). The run / javaOptions += s"-Djava.library.path=./target/native/$arg" on line 46 is ignored apparently.

import com.typesafe.sbt.SbtMultiJvm.multiJvmSettings
import com.typesafe.sbt.SbtMultiJvm.MultiJvmKeys.MultiJvm
import Dependencies._

lazy val `akka-crdt-features` = project
  .in(file("."))
  .settings(multiJvmSettings: _*)
  .settings(
    organization := "nl.about42.akkamavericks",
    scalaVersion := "2.12.8",
    Compile / scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked", "-Xlog-reflective-calls", "-Xlint"),
    Compile / javacOptions ++= Seq("-Xlint:unchecked", "-Xlint:deprecation"),
    run / javaOptions ++= Seq("-Xms128m", "-Xmx1024m", "-Djava.library.path=./target/native"),
    //run / javaOptions ++= Seq("-agentlib:hprof=heap=dump,format=b"),
    libraryDependencies ++= akkaDependencies ++ otherDependencies,
    run / fork := true,
    Compile / run / mainClass := Some("nl.about42.akkamavericks.cluster.ClusterCrdtApp"),
    // disable parallel tests
    Test / parallelExecution := false,
    licenses := Seq(("CC BY 4.0", url("https://creativecommons.org/licenses/by/4.0/"))),
    commands ++= Seq(runNodeCommand),
    Global / cancelable := true
  )
  .configs (MultiJvm)

// setup commands to run each individual node, using a separate folder for the extracted libsigar
lazy val runNode1 = taskKey[Unit]("Run node 1")
lazy val runNode2 = taskKey[Unit]("Run node 2")
lazy val runNode3 = taskKey[Unit]("Run node 3")

runNode1 / fork := true
runNode2 / fork := true
runNode3 / fork := true

runNode1 / javaOptions += "-Djava.library.path=./target/native/node1"
runNode2 / javaOptions += "-Djava.library.path=./target/native/node2"
runNode3 / javaOptions += "-Djava.library.path=./target/native/node3"

fullRunTask(runNode1, Compile, "nl.about42.akkamavericks.cluster.ClusterCrdtApp", "node1")
fullRunTask(runNode2, Compile, "nl.about42.akkamavericks.cluster.ClusterCrdtApp", "node2")
fullRunTask(runNode3, Compile, "nl.about42.akkamavericks.cluster.ClusterCrdtApp", "node3")

// setup command to start a single node, using separate folder for the extracted libsigar
// assumes sane node names that can be used as folder names
val runNodeAction: (State, String) => State = { (state, arg) =>
  run / javaOptions += s"-Djava.library.path=./target/native/$arg"
  val runCommand: Exec = Exec.apply(s"run $arg", state.source)
  state.copy(
    remainingCommands = runCommand +: state.remainingCommands
  )
}

val runNodeCommand: Command = Command.single("runNode")(runNodeAction)

The output of sbt runNode node3 shows (relevant lines):

[error] no libsigar-amd64-linux.so in java.library.path: [./target/native]
[error] org.hyperic.sigar.SigarException: no libsigar-amd64-linux.so in java.library.path: [./target/native]

I expect it to mention ./target/native/node3.

My goal is to have just the sbt command definition, so I can call runNode [anyNodeName] and have SBT start my application with the appropriate classpath setting and startup argument.

Update: I partially succeeded with the following:

val runNodeAction: (State, String) => State = { (state, arg) =>
  val stateWithNewOptions = Project.extract(state).appendWithSession(
    Seq(
      run / javaOptions += s"-Djava.library.path=./target/native/$arg"
    ),
    state
  )
  val runCommand: Exec = Exec.apply(s"run $arg", stateWithNewOptions.source)
  stateWithNewOptions.copy(
    remainingCommands = runCommand +: state.remainingCommands
  )
}

But that leaves the classpath set to the latest run (does not revert back to the default).

Upvotes: 2

Views: 1036

Answers (1)

JeanMarc
JeanMarc

Reputation: 326

With the help of Mario Galic (https://stackoverflow.com/a/54488121/2037054) who answered Conditional scalacSettings / settingKey, I managed to get it working:

// setup command to start a single node, using separate folder for the extracted libsigar
// assumes sane node names that can be used as folder names
val runNodeAction: (State, String) => State = { (state, arg) =>
  val stateWithNewOptions = Project.extract(state).appendWithSession(
    Seq(
      run / javaOptions += s"-Djava.library.path=./target/native/$arg"
    ),
    state
  )
  val (s, _) = Project.extract(stateWithNewOptions).runInputTask(Compile / run, s" $arg", stateWithNewOptions)
  s
}

val runNodeCommand: Command = Command.single("runNode")(runNodeAction)

Upvotes: 2

Related Questions