如何覆盖 SBT 中的任务“run"和“runMain"以使用我自己的“ForkOptions"? [英] How can I override tasks ``run`` and ``runMain`` in SBT to use my own ``ForkOptions``?

查看:62
本文介绍了如何覆盖 SBT 中的任务“run"和“runMain"以使用我自己的“ForkOptions"?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在多模块构建中,每个模块都有自己的 baseDirectory 但我想启动在模块中定义的应用程序,这些应用程序使用根项目的 baseDirectory 而不是 >baseDirectory 相对于所涉及的模块.

In a multimodule build, each module has it's own baseDirectory but I would like to launch applications defined in modules employing the baseDirectory of the root project instead of the baseDirectory relative to modules involved.

这样,应用程序总是会从根文件夹中获取相关文件名,这是一种非常常见的模式.

This way, applications always would take relative file names from the root folder, which is a very common pattern.

问题在于 ForkOptions 强制模块中的 baseDirectory 并且显然没有简单的方法来改变它,因为 forkOptions 是私有的.我想从根项目传递一个 forkOptions 填充了 baseDirectory .

The problem is that ForkOptions enforces the baseDirectory from the module and apparently there's no easy way to change that because forkOptions is private. I would like to pass a forkOptions populated with the baseDirectory from the root project instead.

此外,还有包含两个或多个应用程序的模块.因此,我想为包含两个或多个应用程序的给定模块中的每个应用程序单独配置.

Besides, there are modules which contain two or more applications. So, I'd like to have separate configurations for each application in a given module which contains two or more applications.

一个例子说明了 1000 多个单词:

An example tells more than 1000 words:

import sbt._
import Keys._

lazy val buildSettings: Seq[Setting[_]] = Defaults.defaultSettings
lazy val forkRunOptions: Seq[Setting[_]] = Seq(fork := true)

addCommandAlias("r1",       "ModuleA/RunnerR1:run")
addCommandAlias("r2",       "ModuleA/RunnerR2:run")

lazy val RunnerR1 = sbt.config("RunnerR1").extend(Compile)
lazy val RunnerR2 = sbt.config("RunnerR2").extend(Compile)

lazy val root =
  project
    .in(file("."))
    .settings(buildSettings:_*)
    .aggregate(ModuleA)

lazy val ModuleA =
  project
    .in(file("ModuleA"))
    .settings(buildSettings:_*)
    .configs(RunnerR1,RunnerR2)
    .settings(inConfig(RunnerR1)(
      forkRunOptions ++
        Seq(
          mainClass in Compile :=  Option("sbt.tests.issueX.Application1"))):_*)
    .settings(inConfig(RunnerR2)(
      forkRunOptions ++
        Seq(
          mainClass in Compile :=  Option("sbt.tests.issueX.Application2"))):_*)

在 SBT 控制台中,我希望这样:

In SBT console, I would expect this:

> r1
This is Application1
> r2
This is Application2

但我看到了:

> r1
This is Application2
> r2
This is Application2

有什么收获?

不仅如此... SBT 正在运行应用程序.这不是分叉他们.为什么 fork := true 没有任何效果?

Not only that... SBT is running applications in process. It's not forking them. Why fork := true is not taking any effect?

推荐答案

说明

参见:https://github.com/frgomes/sbt-issue-2247

事实证明,配置并不像人们认为的那样工作.

Turns out that configurations do not work the way one might think they work.

问题在于,在下面的代码片段中,配置 RunnerR1 并没有像您期望的那样从模块 ModuleA 继承任务.因此,当您键入 r1r2(即:ModuleA/RunnerR1:runModuleA/RunnerR2:run),SBT 将使用委托算法来查找任务和设置,根据这些任务和设置的定义方式,它将最终从您不期望的范围运行任务,或者从您不期望的范围查找设置.

The problem is that, in the snippet below, configuration RunnerR1 does not inherit tasks from module ModuleA as you might expect. So, when you type r1 or r2 (i.e: ModuleA/RunnerR1:run or ModuleA/RunnerR2:run), SBT will employ the delegaton algorithm in order to find tasks and settings which, depending on how these tasks and settings were defined, it will end up running tasks from scopes you do not expect, or finding settings from scopes you do not expect.

lazy val ModuleA =
  project
    .in(file("ModuleA"))
    .settings(buildSettings:_*)
    .configs(RunnerR1,RunnerR2)
    .settings(inConfig(RunnerR1)(
      forkRunOptions ++
        Seq(
          mainClass in Compile :=  Option("sbt.tests.issueX.Application1"))):_*)

此问题与可用性有关,因为 SBT 提供的 API 具有误导性.最终,这种模式可以得到改进或更好地记录,但它更像是一个可用性问题.

This issue is related to usability, since the API provided by SBT is misleading. Eventually this pattern can be improved or better documented, but it's more a usability problem than anything else.

请在下面找到如何规避此问题.

Please find below how this issue can be circumvented.

由于 ForkOptions 是私有的,我们必须尽可能提供我们自己的运行应用程序的方式,该方式基于 SBT 代码.

Since ForkOptions is private, we have to provide our own way of running applications, which is based on SBT code, as much as possible.

简而言之,我们必须保证在我们拥有的所有配置中重新定义 runrunMainrunner.

In a nutshell, we have to guarantee that we redefine run, runMain and runner in all configurations we have.

import sbt._
import Keys._


//-------------------------------------------------------------
// This file contains a solution for the problem presented by
// https://github.com/sbt/sbt/issues/2247
//-------------------------------------------------------------


lazy val buildSettings: Seq[Setting[_]] = Defaults.defaultSettings ++ runSettings

lazy val runSettings: Seq[Setting[_]] =
  Seq(
    fork in (Compile, run) := true)


def forkRunOptions(s: Scope): Seq[Setting[_]] =
  Seq(
    // see: https://github.com/sbt/sbt/issues/2247
    // see: https://github.com/sbt/sbt/issues/2244
    runner in run in s := {
      val forkOptions: ForkOptions =
        ForkOptions(
          workingDirectory = Some((baseDirectory in ThisBuild).value),
          bootJars         = Nil,
          javaHome         = (javaHome       in s).value,
          connectInput     = (connectInput   in s).value,
          outputStrategy   = (outputStrategy in s).value,
          runJVMOptions    = (javaOptions    in s).value,
          envVars          = (envVars        in s).value)
      new {
        val fork_ = (fork in run).value
        val config: ForkOptions = forkOptions
      } with ScalaRun {
        override def run(mainClass: String, classpath: Seq[File], options: Seq[String], log: Logger): Option[String] =
          javaRunner(
            Option(mainClass), Option(classpath), options,
            Some("java"), Option(log), fork_,
            config.runJVMOptions, config.javaHome, config.workingDirectory, config.envVars, config.connectInput, config.outputStrategy)
      }
    },
    runner  in runMain in (s) := (runner in run in (s)).value,
    run     in (s) <<= Defaults.runTask    (fullClasspath in s, mainClass in run in s, runner in run in s),
    runMain in (s) <<= Defaults.runMainTask(fullClasspath in s,                        runner in runMain in s)
  )


def javaRunner(mainClass: Option[String] = None,
               classpath: Option[Seq[File]] = None,
               options: Seq[String],
               javaTool: Option[String] = None,
               log: Option[Logger] = None,
               fork: Boolean = false,
               jvmOptions: Seq[String] = Nil,
               javaHome: Option[File] = None,
               cwd: Option[File] = None,
               envVars: Map[String, String] = Map.empty,
               connectInput: Boolean = false,
               outputStrategy: Option[OutputStrategy] = Some(StdoutOutput)): Option[String] = {

  def runner(app: String,
             args: Seq[String],
             cwd: Option[File] = None,
             env: Map[String, String] = Map.empty): Int = {
    import scala.collection.JavaConverters._

    val cmd: Seq[String] = app +: args
    val pb = new java.lang.ProcessBuilder(cmd.asJava)
    if (cwd.isDefined) pb.directory(cwd.get)
    pb.inheritIO
    //FIXME: set environment
    val process = pb.start()
    if (fork) 0
    else {
      def cancel() = {
        if(log.isDefined) log.get.warn("Background process cancelled.")
        process.destroy()
        15
      }
      try process.waitFor catch {
        case e: InterruptedException => cancel()
      }
    }
  }

  val app: String = javaHome.fold("") { p => p.absolutePath + "/bin/" } + javaTool.getOrElse("java")
  val jvm: Seq[String] = jvmOptions.map(p => p.toString)
  val cp: Seq[String] =
    classpath
      .fold(Seq.empty[String]) { paths =>
        Seq(
          "-cp",
          paths
            .map(p => p.absolutePath)
            .mkString(java.io.File.pathSeparator))
      }
  val klass = mainClass.fold(Seq.empty[String]) { name => Seq(name) }
  val xargs: Seq[String] = jvm ++ cp ++ klass ++ options

  if(log.isDefined)
    if(fork) {
      log.get.info(s"Forking: ${app} " + xargs.mkString(" "))
    } else {
      log.get.info(s"Running: ${app} " + xargs.mkString(" "))
    }

  if (cwd.isDefined) IO.createDirectory(cwd.get)
  val exitCode = runner(app, xargs, cwd, envVars)
  if (exitCode == 0)
    None
  else
    Some("Nonzero exit code returned from " + app + ": " + exitCode)
}


addCommandAlias("r1",       "ModuleA/RunnerR1:run")
addCommandAlias("r2",       "ModuleA/RunnerR2:run")


lazy val RunnerR1 = sbt.config("RunnerR1").extend(Compile)
lazy val RunnerR2 = sbt.config("RunnerR2").extend(Compile)


lazy val root =
  project
    .in(file("."))
    .settings(buildSettings:_*)
    .aggregate(ModuleA)

lazy val ModuleA =
  project
    .in(file("ModuleA"))
    .settings(buildSettings:_*)
    .configs(RunnerR1,RunnerR2)
    .settings(inConfig(RunnerR1)(
      forkRunOptions(ThisScope) ++
        Seq(
          mainClass :=  Option("sbt.tests.issueX.Application1"))):_*)
    .settings(inConfig(RunnerR2)(
      forkRunOptions(ThisScope) ++
        Seq(
          mainClass :=  Option("sbt.tests.issueX.Application2"))):_*)

这篇关于如何覆盖 SBT 中的任务“run"和“runMain"以使用我自己的“ForkOptions"?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆