SBTのタスク「run」と「runMain」をオーバーライドして独自の「ForkOptions」を使用するにはどうすればよいですか?

リチャード・ゴメス

問題

マルチモジュールビルドでは、各モジュールに独自のモジュールがありますが関連するモジュールに関連するのではなく、ルートプロジェクトbaseDirectoryを使用するモジュールで定義されたアプリケーションを起動したいと思います。baseDirectorybaseDirectory

このように、アプリケーションは常にルートフォルダから相対ファイル名を取得します。これは非常に一般的なパターンです。

問題は、モジュールからForkOptions強制するbaseDirectoryことでforkOptionsあり、プライベートであるため、それを変更する簡単な方法がないようです。代わりに、ルートプロジェクトからのをforkOptions入力して渡したいと思いbaseDirectoryます。

また、2つ以上のアプリケーションを含むモジュールがあります。そのため、2つ以上のアプリケーションを含む特定のモジュール内のアプリケーションごとに個別の構成を作成したいと思います。

例は1000以上の単語を伝えます:

build.sbt

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コンソールでは、次のことを期待します。

> r1
This is Application1
> r2
This is Application2

しかし、私はこれを見ます:

> r1
This is Application2
> r2
This is Application2

キャッチは何ですか?

それだけではありません... SBTは処理中のアプリケーションを実行しています。それは彼らをフォークしていません。なぜfork := true効果がないのですか?

リチャード・ゴメス

説明

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

構成が機能すると思われる方法で機能しないことが判明しました。

問題は、以下のスニペットでは、構成RunnerR1がモジュールからタスクを継承しないことですModuleAしたがって、r1or r2(つまり:ModuleA/RunnerR1:runまたはModuleA/RunnerR2:run)と入力すると、SBTは委任アルゴリズムを使用してタスクと設定を検索します。これらのタスクと設定の定義方法によっては、予期しないスコープからタスクが実行されることになります。または、予期しないスコープから設定を検索します。

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は誤解を招くため、この問題はユーザビリティに関連しています。最終的に、このパターンは改善されるか、より適切に文書化される可能性がありますが、それは他の何よりも使いやすさの問題です。

困難を回避する

この問題を回避する方法については、以下をご覧ください。

ForkOptionsはプライベートなので、可能な限りSBTコードに基づいた独自のアプリケーション実行方法を提供する必要があります。

一言で言えば、私たちは再定義することを保証する必要がありますrunrunMainそしてrunner私たちが持っているすべての構成で。

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"))):_*)

この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。

侵害の場合は、連絡してください[email protected]

編集
0

コメントを追加

0

関連記事

Related 関連記事

ホットタグ

アーカイブ