如何编写sbt插件以使用代理启动应用程序

fom*_*mil 6 plugins scala sbt

我想在开源之前为我的项目创建一个sbt插件.

该项目将Java代理程序附加到运行应用程序的开始,以便对其进行各种类型的分析.代理写出文本文件以供以后处理.

我希望能够编写一个可以的sbt插件

  • 有一个run被调用的替代方法,runWithProfiling它会启动一个新的java进程,并将代理添加到参数列表中,并传递所有用户命令.
  • 在退出时,我想调用一些任意的后处理代码来生成HTML报告

我大致知道如何创建新命令,但我不知道如何最好地实现替代run...我不想通过复制所有代码来重新发明轮子run.有没有一种方法可以调用,run但确保我的参数传递(一次),它肯定是一个新的java进程?

此外,能够为测试做同样的事情会很棒.

更新:这是我目前拥有的代码,但它遇到了几个问题,标记为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")
  }

}
Run Code Online (Sandbox Code Playgroud)

Eug*_*ota 1

插件作者需要遵守不成文的希波克拉底誓言,即“首先,不伤害”。目前,您的实现会强制每个子项目自行改变forkjavaOptions改变默认行为,我认为这是危险的。我认为您需要复制run任务范围内的参数,以便默认设置不受损害。

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

有关示例,请参阅插件最佳实践和现有插件(例如sbt-appengine) 。

在 sbt-appengine 中devServer是一个输入任务,您可以设置一堆参数。

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)
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,代码的核心实际上是在AppEngine对象下的方法中实现的,因此其他人可以重用您的东西。该方法中的许多参数(在本例中restartDevServer)的作用域都是gae.devServer任务,例如(mainClass in gae.devServer).

您打算如何由构建用户设置该插件?他们是否会将其作为全局插件启用一次并在各处使用相同的设置,或者他们是否会为每个构建启用它project/lion.sbt插件最佳实践的建议是提供baseLionSettings插件,lionSettings以便构建用户可以选择哪个子项目将启用该lion任务。

根据实际运行情况,您可能想看看重用sbt-revolver中的代码,类似于我在 sbt-appengine 中所做的。