问题
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
calledrunWithProfiling
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 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 (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