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

该项目将Java代理附加到开始运行应用程序的过程中,以对其进行各种类型的性能分析进行检测。代理会写出文本文件以供以后处理。

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

  • 有一个称为runrunWithProfiling的替代品,它会启动一个新的Java进程,并将代理添加到参数列表中,并传递所有用户命令。
  • 在退出时,然后我想调用一些任意的后处理代码以生成HTML报告

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

    同样,能够为测试做同样的事情也很棒。

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

    最佳答案

    插件作者需要遵守未成文的希波克拉底誓言,这是“第一,不伤害”。您的实现当前将自身强制到每个子项目,并对默认行为的forkjavaOptions进行变异,我认为这很危险。我认为您需要复制作用域范围内的run参数,以便默认设置不受损害。



    有关示例,请参见Plugins Best Practices和现有的插件(例如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)
    }
    

    如您所见,代码的实质实际上是在AppEngine对象下的方法中实现的,因此其他人可能会重用您的东西。
    方法中的许多参数(在本例中为restartDevServer)都适用于gae.devServer任务,例如(mainClass in gae.devServer)

    您打算如何由构建用户设置插件?他们是要一次性将其作为全局插件启用并在各处使用相同的设置,还是要为project/lion.sbt中的每个版本启用它? Plugins Best Practices的建议是提供baseLionSettingslionSettings,以便构建用户可以选择要启用lion任务的子项目。

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

    10-08 09:19
    查看更多