プラグイン・クラスパスの隔離
目的
プラグインのクラスパスを隔離したい。例えば、Scala 2.12 アプリケーションを使いたい場合など。
手順
-
Maven Central にコマンドライン・アプリ (例えば Coursier CLI) か、独自のアプリケーションが公開されていることを前提とする。
-
コマンドライン・アプリを呼び出す合成サブプロジェクトを定義する:
package example import sbt.{ *, given } import Keys.* object BootstrapPlugin extends AutoPlugin: override lazy val requires = sbt.plugins.JvmPlugin lazy val bootstrapCs = project .settings( scalaVersion := "2.12.21", libraryDependencies += "io.get-coursier" %% "coursier-cli" % "2.1.14", Compile / run / mainClass := Some("coursier.cli.Coursier"), // sbt 2.x のみに適用される clientSide := false, ) override lazy val extraProjects = Vector(bootstrapCs) .... end BootstrapPlugin -
コマンドライン引数を表すセッティングと、合成サブプロジェクトの
runタスクを呼び出す動的タスクを定義する。
package example
import sbt.{ *, given }
import Keys.*
object BootstrapPlugin extends AutoPlugin:
override lazy val requires = sbt.plugins.JvmPlugin
object autoImport:
val packageBootstrap = taskKey[HashedVirtualFileRef]("packageBootstrap")
val packageBootstrapArgs = settingKey[Seq[String]]("packageBootstrapArgs")
val packageBootstrapOutput = settingKey[File]("packageBootstrapOutput")
end autoImport
import autoImport.*
lazy val bootstrapCs = project
.settings(
scalaVersion := "2.12.21",
libraryDependencies += "io.get-coursier" %% "coursier-cli" % "2.1.14",
Compile / run / mainClass := Some("coursier.cli.Coursier"),
// applicable to sbt 2.x only
clientSide := false,
)
override lazy val extraProjects = Vector(bootstrapCs)
override lazy val projectSettings = Vector(
packageBootstrapOutput := target.value / "bootstrap" / s"${moduleName.value}.jar",
packageBootstrapArgs := {
val coord =
s"${organization.value}:${name.value}_${scalaBinaryVersion.value}:${version.value}"
val sv = scalaVersion.value
Vector("bootstrap", "--verbose", "--bat=true",
"--scala-version", sv,
"-f", coord,
"-o", packageBootstrapOutput.value.toString)
},
packageBootstrap := Def.uncached {
// to process args before toTask, we need to use dynamic tasks
(Def.taskDyn {
// phase 1
val c = fileConverter.value
val args = packageBootstrapArgs.value
val out = packageBootstrapOutput.value
// phase 2
val outVf: HashedVirtualFileRef = c.toVirtualFile(out.toPath())
IO.createDirectory(out.getParentFile())
// phase 3
(bootstrapCs / Compile / run)
.toTask(args.mkString(" ", " ", ""))
.map(_ => outVf)
}).value
},
)
end BootstrapPlugin
コマンドライン・アプリが sys.exit(1) を呼び出す場合、run をフォークする必要がある:
// sbtn のデフォルト値
clientSide := true,
// sbt --server の場合
Compile / run / fork := true,
テスト
$ sbt app/publishLocal
$ sbt app/packageBootstrap
[info] running coursier.cli.Coursier bootstrap --verbose --bat=true
--scala-version 3.8.4 -f com.example:hello_3:0.1.0-SNAPSHOT
-o /.../isolation/target/out/jvm/scala-3.8.4/hello/bootstrap/hello.jar
Dependencies:
com.example:hello_3:0.1.0-SNAPSHOT:
Wrote target/out/jvm/scala-3.8.4/hello/bootstrap/hello.jar
Wrote target/out/jvm/scala-3.8.4/hello/bootstrap/hello.jar.bat
[success] elapsed time: 3 s, cache 100%, 17 disk cache hits
$ target/out/jvm/scala-3.8.4/hello/bootstrap/hello.jar world
hi world