How to write an sbt plugin to run an application using an agent

I would like to create an sbt plugin for my project before opening it.

The project binds the Java agent to the start of the application so that it can be used for various types of profiling. The agent writes text files for further processing.

I would like to be able to write an sbt plugin that can

  • There is an alternative to run , called runWithProfiling , which starts a new Java process by adding an agent to the argument list and passing all user commands.
  • on exit, I then want to call some arbitrary code after processing to create an HTML report

I know how to create a new command, but I don’t know how to best implement the run alternative ... I don’t want to reinvent the wheel by copying all the code that run . Is there a way that I can call run , but make sure my parameters are passed (once) and that this is definitely a new Java process?

Also, the ability to do the same for tests would be great.

UPDATE : this is the code that I have now, but it suffers from several problems marked as TODO s

 import sbt._ import Keys._ import sbt.Attributed.data object LionPlugin extends Plugin { val lion = TaskKey[Unit]("lion", "Run a main class with lions-share profiling.") override val projectSettings = Seq( fork := true, javaOptions ++= Seq( "-Xloggc:gc.log", "-XX:+PrintGCDetails", "-XX:+PrintGCDateStamps", "-XX:+PrintTenuringDistribution", "-XX:+PrintHeapAtGC" // TODO: need to get hold of the local jar file for a particular artifact // IMPL: pass the jar as the agent ), lion <<= ( runner, fullClasspath in Runtime, mainClass in Runtime, streams in Runtime ) map runLion ) // TODO: update to a task that can take parameters (eg number of repeats, profiling settings) def runLion(runner: ScalaRun, cp: Classpath, main: Option[String], streams: TaskStreams): Unit = { assert(runner.isInstanceOf[ForkRun], "didn't get a forked runner... SBT is b0rk3d") println("RUNNING with " + runner.getClass) // TODO: ask user if main is None, like 'run' does val m = main.getOrElse("Scratch") // TODO: get the user arguments val args = Nil runner.run(m, data(cp), args, streams.log) // IMPL: post-process and produce the report println("FINISHED") } } 
+6
source share
2 answers

The authors of the plugins must abide by the unwritten oath of Hippocrates, which "firstly, does no harm." Your implementation currently forces itself to each subproject and mutates the default behavior of fork and javaOptions , which in my opinion is dangerous. I think you need to duplicate the run parameters associated with your task, so the default settings are unharmed.

 // TODO: update to a task that can take parameters (eg number of repeats, profiling settings) 

See Plug -in Recommendations and existing plugins such as sbt-appengine for an example.

Sbt-appengine devServer includes an input task in which you can set a set of parameters.

 gae.devServer := { val args = startArgsParser.parsed val x = (products in Compile).value AppEngine.restartDevServer(streams.value, (gae.reLogTag in gae.devServer).value, thisProjectRef.value, (gae.reForkOptions in gae.devServer).value, (mainClass in gae.devServer).value, (fullClasspath in gae.devServer).value, (gae.reStartArgs in gae.devServer).value, args, packageWar.value, (gae.onStartHooks in gae.devServer).value, (gae.onStopHooks in gae.devServer).value) } 

As you can see, the gut of the code is actually implemented in the method under the AppEngine object, so someone could potentially use your stuff. Many of the parameters in the method (in this case restartDevServer ) are tied to gae.devServer tasks, for example (mainClass in gae.devServer) .

How are you going to configure the plugin for assembly users? Should they enable it once as a global plugin and use the same settings everywhere, or are they going to enable it for each build in project/lion.sbt ? Best Practice Recommendations It is recommended that you provide baseLionSettings and lionSettings so that the assembly user can select and choose which subproject will have the lion task enabled.

According to the actual launch, you may need to reuse the code from sbt-revolver , similar to what I did in sbt-appengine.

+1
source

See "How to create a custom launch task in addition to a launch?" at http://www.scala-sbt.org/0.13.0/docs/faq.html . In your custom task, you need to set fork to true in order to start a new JVM.

+1
source

All Articles