how to write an sbt plugin to launch the app with an agent

南笙酒味 提交于 2020-01-02 04:00:27

问题


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

The project attaches a Java agent to the start of running an application, to instrument it for various types of profiling. The agent writes out text files for later processing.

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

  • have an alternative to run called runWithProfiling which launches a new java process, with the agent added to the argument list, and passing all the users commands.
  • on exit, I then want to invoke some arbitrary post-processing code to produce an HTML report

I know roughly how to create the new command, but I don't know how to best implement an alternative to run... I don't want to re-invent the wheel by copying all the code that run does. Is there a way I can invoke run but ensure that my parameters are passed (one time) and that it is definitely a new java process?

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

UPDATE: this is the code that I currently have, but it suffers from several problems, marked up as TODOs

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 (e.g. 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's arguments
    val args = Nil

    runner.run(m, data(cp), args, streams.log)

    // IMPL: post-process and produce the report

    println("FINISHED")
  }

}

回答1:


Plugin authors need to abide by an unwritten Hippocratic Oath, which is "first, do no harm." Your implementation currently forces itself to every subproject and mutates fork and javaOptions of the default behavior, which I think is dangerous. I think you need to duplicate the run parameters scoped to your task so the default settings are unharmed.

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

See Plugins Best Practices and existing plugins like sbt-appengine for an example.

In sbt-appengine devServer is an input task that you can set a bunch 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 see the gut of the code is actually implemented in a method under AppEngine object, so someone else can reuse your stuff potentially. Many of the parameters into the method (in this case restartDevServer) are scoped to gae.devServer task like (mainClass in gae.devServer).

How do you intend the plugin to be set up by the build users? Are they going to 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? Plugins Best Practices's recommendation is to provide baseLionSettings and lionSettings so the build user can pick and chose which subproject would have the lion task enabled.

As per actual running goes, you might want to take a look at reusing the code from sbt-revolver similar to what I did in sbt-appengine.




回答2:


See “How can I create a custom run task, in addition to run?” at http://www.scala-sbt.org/0.13.0/docs/faq.html . In your custom task, you'll want to set fork to true in order to launch a fresh JVM.



来源:https://stackoverflow.com/questions/23029910/how-to-write-an-sbt-plugin-to-launch-the-app-with-an-agent

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!