Merge pull request #6673 from kxbmap/scripted-java-home

Make javaHome that forks scripted tests configurable
This commit is contained in:
eugene yokota 2021-10-05 11:08:53 -04:00 committed by GitHub
commit adc217000b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 359 additions and 41 deletions

View File

@ -8,7 +8,6 @@
package sbt package sbt
import java.io.File import java.io.File
import java.lang.reflect.Method
import sbt.Def._ import sbt.Def._
import sbt.Keys._ import sbt.Keys._
@ -47,7 +46,7 @@ object ScriptedPlugin extends AutoPlugin {
val scriptedParallelInstances = settingKey[Int]( val scriptedParallelInstances = settingKey[Int](
"Configures the number of scripted instances for parallel testing, only used in batch mode." "Configures the number of scripted instances for parallel testing, only used in batch mode."
) )
val scriptedRun = taskKey[Method]("") val scriptedRun = taskKey[ScriptedRun]("")
val scriptedLaunchOpts = val scriptedLaunchOpts =
settingKey[Seq[String]]("options to pass to jvm launching scripted tasks") settingKey[Seq[String]]("options to pass to jvm launching scripted tasks")
val scriptedDependencies = taskKey[Unit]("") val scriptedDependencies = taskKey[Unit]("")
@ -114,21 +113,8 @@ object ScriptedPlugin extends AutoPlugin {
} }
} }
private[sbt] def scriptedRunTask: Initialize[Task[Method]] = Def.taskDyn { private[sbt] def scriptedRunTask: Initialize[Task[ScriptedRun]] = Def.task {
val fCls = classOf[File] ScriptedRun.of(scriptedTests.value, scriptedBatchExecution.value)
val bCls = classOf[Boolean]
val asCls = classOf[Array[String]]
val lfCls = classOf[java.util.List[File]]
val iCls = classOf[Int]
val clazz = scriptedTests.value.getClass
val method =
if (scriptedBatchExecution.value)
clazz.getMethod("runInParallel", fCls, bCls, asCls, fCls, asCls, lfCls, iCls)
else
clazz.getMethod("run", fCls, bCls, asCls, fCls, asCls, lfCls)
Def.task(method)
} }
private[sbt] final case class ScriptedTestPage(page: Int, total: Int) private[sbt] final case class ScriptedTestPage(page: Int, total: Int)
@ -191,21 +177,16 @@ object ScriptedPlugin extends AutoPlugin {
private[sbt] def scriptedTask: Initialize[InputTask[Unit]] = Def.inputTask { private[sbt] def scriptedTask: Initialize[InputTask[Unit]] = Def.inputTask {
val args = scriptedParser(sbtTestDirectory.value).parsed val args = scriptedParser(sbtTestDirectory.value).parsed
Def.unit(scriptedDependencies.value) Def.unit(scriptedDependencies.value)
try { scriptedRun.value.run(
val method = scriptedRun.value sbtTestDirectory.value,
val scriptedInstance = scriptedTests.value scriptedBufferLog.value,
val dir = sbtTestDirectory.value args,
val log = Boolean box scriptedBufferLog.value sbtLauncher.value,
val launcher = sbtLauncher.value Fork.javaCommand((scripted / javaHome).value, "java").getAbsolutePath,
val opts = scriptedLaunchOpts.value.toArray scriptedLaunchOpts.value,
val empty = new java.util.ArrayList[File]() new java.util.ArrayList[File](),
val instances = Int box scriptedParallelInstances.value scriptedParallelInstances.value
)
if (scriptedBatchExecution.value)
method.invoke(scriptedInstance, dir, log, args.toArray, launcher, opts, empty, instances)
else method.invoke(scriptedInstance, dir, log, args.toArray, launcher, opts, empty)
()
} catch { case e: java.lang.reflect.InvocationTargetException => throw e.getCause }
} }
private[this] def getJars(config: Configuration): Initialize[Task[PathFinder]] = Def.task { private[this] def getJars(config: Configuration): Initialize[Task[PathFinder]] = Def.task {

View File

@ -0,0 +1,179 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt
import java.io.File
import java.lang.reflect.Method
import scala.annotation.unused
sealed trait ScriptedRun {
final def run(
resourceBaseDirectory: File,
bufferLog: Boolean,
tests: Seq[String],
launcherJar: File,
javaCommand: String,
launchOpts: Seq[String],
prescripted: java.util.List[File],
instances: Int,
): Unit = {
try {
invoke(
resourceBaseDirectory,
bufferLog,
tests.toArray,
launcherJar,
javaCommand,
launchOpts.toArray,
prescripted,
instances,
)
()
} catch { case e: java.lang.reflect.InvocationTargetException => throw e.getCause }
}
protected def invoke(
resourceBaseDirectory: File,
bufferLog: java.lang.Boolean,
tests: Array[String],
launcherJar: File,
javaCommand: String,
launchOpts: Array[String],
prescripted: java.util.List[File],
instances: java.lang.Integer,
): AnyRef
}
object ScriptedRun {
def of(scriptedTests: AnyRef, batchExecution: Boolean): ScriptedRun = {
val fCls = classOf[File]
val bCls = classOf[Boolean]
val asCls = classOf[Array[String]]
val sCls = classOf[String]
val lfCls = classOf[java.util.List[File]]
val iCls = classOf[Int]
val clazz = scriptedTests.getClass
if (batchExecution)
try new RunInParallelV2(
scriptedTests,
clazz.getMethod("runInParallel", fCls, bCls, asCls, fCls, sCls, asCls, lfCls, iCls)
)
catch {
case _: NoSuchMethodException =>
new RunInParallelV1(
scriptedTests,
clazz.getMethod("runInParallel", fCls, bCls, asCls, fCls, asCls, lfCls, iCls)
)
}
else
try new RunV2(
scriptedTests,
clazz.getMethod("run", fCls, bCls, asCls, fCls, sCls, asCls, lfCls)
)
catch {
case _: NoSuchMethodException =>
new RunV1(scriptedTests, clazz.getMethod("run", fCls, bCls, asCls, fCls, asCls, lfCls))
}
}
private class RunV1(scriptedTests: AnyRef, run: Method) extends ScriptedRun {
override protected def invoke(
resourceBaseDirectory: File,
bufferLog: java.lang.Boolean,
tests: Array[String],
launcherJar: File,
@unused javaCommand: String,
launchOpts: Array[String],
prescripted: java.util.List[File],
@unused instances: java.lang.Integer,
): AnyRef =
run.invoke(
scriptedTests,
resourceBaseDirectory,
bufferLog,
tests,
launcherJar,
launchOpts,
prescripted,
)
}
private class RunInParallelV1(scriptedTests: AnyRef, runInParallel: Method) extends ScriptedRun {
override protected def invoke(
resourceBaseDirectory: File,
bufferLog: java.lang.Boolean,
tests: Array[String],
launcherJar: File,
@unused javaCommand: String,
launchOpts: Array[String],
prescripted: java.util.List[File],
instances: Integer,
): AnyRef =
runInParallel.invoke(
scriptedTests,
resourceBaseDirectory,
bufferLog,
tests,
launcherJar,
launchOpts,
prescripted,
instances,
)
}
private class RunV2(scriptedTests: AnyRef, run: Method) extends ScriptedRun {
override protected def invoke(
resourceBaseDirectory: File,
bufferLog: java.lang.Boolean,
tests: Array[String],
launcherJar: File,
javaCommand: String,
launchOpts: Array[String],
prescripted: java.util.List[File],
@unused instances: java.lang.Integer,
): AnyRef =
run.invoke(
scriptedTests,
resourceBaseDirectory,
bufferLog,
tests,
launcherJar,
javaCommand,
launchOpts,
prescripted,
)
}
private class RunInParallelV2(scriptedTests: AnyRef, runInParallel: Method) extends ScriptedRun {
override protected def invoke(
resourceBaseDirectory: File,
bufferLog: java.lang.Boolean,
tests: Array[String],
launcherJar: File,
javaCommand: String,
launchOpts: Array[String],
prescripted: java.util.List[File],
instances: Integer,
): AnyRef =
runInParallel.invoke(
scriptedTests,
resourceBaseDirectory,
bufferLog,
tests,
launcherJar,
javaCommand,
launchOpts,
prescripted,
instances,
)
}
}

View File

@ -0,0 +1,14 @@
[@kxbmap]: https://github.com/kxbmap
[#6673]: https://github.com/sbt/sbt/pull/6673
### Improvements
- Make javaHome that forks scripted tests configurable. [#6673][] by [@kxbmap][]
- Normally scripted tests are forked using the JVM that is running sbt. If set `scripted / javaHome`, forked using it.
- Or use `java++` command before scripted.
### Fixes with compatibility implications
- Change type of `scriptedRun` task key from `TaskKey[java.lang.reflect.Method]` to `TaskKey[sbt.ScriptedRun]`
- `sbt.ScriptedRun` is a new interface for hiding substance of scripted invocation.

View File

@ -114,7 +114,7 @@ object Fork {
(classpathOption, newOptions) (classpathOption, newOptions)
} }
private def javaCommand(javaHome: Option[File], name: String): File = { private[sbt] def javaCommand(javaHome: Option[File], name: String): File = {
val home = javaHome.getOrElse(new File(System.getProperty("java.home"))) val home = javaHome.getOrElse(new File(System.getProperty("java.home")))
new File(new File(home, "bin"), name) new File(new File(home, "bin"), name)
} }

View File

@ -0,0 +1,23 @@
lazy val scriptedJavaVersion = settingKey[Long]("")
lazy val root = (project in file("."))
.enablePlugins(SbtPlugin)
.settings(
scriptedJavaVersion := {
val versions = discoveredJavaHomes.value
.map { case (jv, _) => JavaVersion(jv).numbers }
.collect {
case Vector(1L, ver, _*) => ver
case Vector(ver, _*) => ver
}
if (versions.isEmpty) sys.error("No Java versions discovered")
else versions.max
},
commands += Command.command("setJavaVersion") { state =>
val extracted = Project.extract(state)
import extracted._
val jv = (currentRef / scriptedJavaVersion).get(structure.data).get
s"java++ $jv!" :: state
},
scriptedLaunchOpts += s"-Dscripted.java.version=${scriptedJavaVersion.value}"
)

View File

@ -0,0 +1,13 @@
lazy val check = taskKey[Unit]("check")
lazy val root = (project in file("."))
.settings(
check := {
val version = sys.props("java.version").stripPrefix("1.").takeWhile(_.isDigit)
val expected = sys.props("scripted.java.version")
assert(
version == expected,
s"Expected Java version is '$expected', but actual is '$version'"
)
}
)

View File

@ -0,0 +1 @@
> check

View File

@ -0,0 +1,5 @@
$ copy-file changes/build.sbt src/sbt-test/group/name/build.sbt
$ copy-file changes/test src/sbt-test/group/name/test
> setJavaVersion
> scripted

View File

@ -27,14 +27,22 @@ final class LauncherBasedRemoteSbtCreator(
directory: File, directory: File,
launcher: File, launcher: File,
log: Logger, log: Logger,
launchOpts: Seq[String] = Nil, javaCommand: String,
launchOpts: Seq[String],
) extends RemoteSbtCreator { ) extends RemoteSbtCreator {
def newRemote(server: IPC.Server) = { def this(
directory: File,
launcher: File,
log: Logger,
launchOpts: Seq[String] = Nil,
) = this(directory, launcher, log, "java", launchOpts)
def newRemote(server: IPC.Server): Process = {
val launcherJar = launcher.getAbsolutePath val launcherJar = launcher.getAbsolutePath
val globalBase = "-Dsbt.global.base=" + (new File(directory, "global")).getAbsolutePath val globalBase = "-Dsbt.global.base=" + (new File(directory, "global")).getAbsolutePath
val scripted = "-Dsbt.scripted=true" val scripted = "-Dsbt.scripted=true"
val args = List("<" + server.port) val args = List("<" + server.port)
val cmd = "java" :: launchOpts.toList ::: globalBase :: scripted :: "-jar" :: launcherJar :: args ::: Nil val cmd = javaCommand :: launchOpts.toList ::: globalBase :: scripted :: "-jar" :: launcherJar :: args ::: Nil
val io = BasicIO(false, log).withInput(_.close()) val io = BasicIO(false, log).withInput(_.close())
val p = Process(cmd, directory) run (io) val p = Process(cmd, directory) run (io)
val thread = new Thread() { override def run() = { p.exitValue(); server.close() } } val thread = new Thread() { override def run() = { p.exitValue(); server.close() } }
@ -46,11 +54,21 @@ final class LauncherBasedRemoteSbtCreator(
final class RunFromSourceBasedRemoteSbtCreator( final class RunFromSourceBasedRemoteSbtCreator(
directory: File, directory: File,
log: Logger, log: Logger,
launchOpts: Seq[String] = Nil, javaCommand: String,
launchOpts: Seq[String],
scalaVersion: String, scalaVersion: String,
sbtVersion: String, sbtVersion: String,
classpath: Seq[File], classpath: Seq[File],
) extends RemoteSbtCreator { ) extends RemoteSbtCreator {
def this(
directory: File,
log: Logger,
launchOpts: Seq[String] = Nil,
scalaVersion: String,
sbtVersion: String,
classpath: Seq[File],
) = this(directory, log, "java", launchOpts, scalaVersion, sbtVersion, classpath)
def newRemote(server: IPC.Server): Process = { def newRemote(server: IPC.Server): Process = {
val globalBase = "-Dsbt.global.base=" + new File(directory, "global").getAbsolutePath val globalBase = "-Dsbt.global.base=" + new File(directory, "global").getAbsolutePath
val scripted = "-Dsbt.scripted=true" val scripted = "-Dsbt.scripted=true"
@ -58,7 +76,7 @@ final class RunFromSourceBasedRemoteSbtCreator(
val cpString = classpath.mkString(java.io.File.pathSeparator) val cpString = classpath.mkString(java.io.File.pathSeparator)
val args = val args =
List(mainClassName, directory.toString, scalaVersion, sbtVersion, cpString, "<" + server.port) List(mainClassName, directory.toString, scalaVersion, sbtVersion, cpString, "<" + server.port)
val cmd = "java" :: launchOpts.toList ::: globalBase :: scripted :: "-cp" :: cpString :: args ::: Nil val cmd = javaCommand :: launchOpts.toList ::: globalBase :: scripted :: "-cp" :: cpString :: args ::: Nil
val io = BasicIO(false, log).withInput(_.close()) val io = BasicIO(false, log).withInput(_.close())
val p = Process(cmd, directory) run (io) val p = Process(cmd, directory) run (io)
val thread = new Thread() { override def run() = { p.exitValue(); server.close() } } val thread = new Thread() { override def run() = { p.exitValue(); server.close() } }

View File

@ -26,8 +26,15 @@ import scala.util.control.NonFatal
final class ScriptedTests( final class ScriptedTests(
resourceBaseDirectory: File, resourceBaseDirectory: File,
bufferLog: Boolean, bufferLog: Boolean,
javaCommand: String,
launchOpts: Seq[String], launchOpts: Seq[String],
) { ) {
def this(
resourceBaseDirectory: File,
bufferLog: Boolean,
launchOpts: Seq[String],
) = this(resourceBaseDirectory, bufferLog, "java", launchOpts)
import ScriptedTests.TestRunner import ScriptedTests.TestRunner
private val testResources = new Resources(resourceBaseDirectory) private val testResources = new Resources(resourceBaseDirectory)
@ -80,11 +87,12 @@ final class ScriptedTests(
val remoteSbtCreator = val remoteSbtCreator =
prop match { prop match {
case LauncherBased(launcherJar) => case LauncherBased(launcherJar) =>
new LauncherBasedRemoteSbtCreator(testDir, launcherJar, buffered, launchOpts) new LauncherBasedRemoteSbtCreator(testDir, launcherJar, buffered, javaCommand, launchOpts)
case RunFromSourceBased(scalaVersion, sbtVersion, classpath) => case RunFromSourceBased(scalaVersion, sbtVersion, classpath) =>
new RunFromSourceBasedRemoteSbtCreator( new RunFromSourceBasedRemoteSbtCreator(
testDir, testDir,
buffered, buffered,
javaCommand,
launchOpts, launchOpts,
scalaVersion, scalaVersion,
sbtVersion, sbtVersion,
@ -383,6 +391,7 @@ class ScriptedRunner {
bufferLog, bufferLog,
tests, tests,
logger, logger,
javaCommand = "java",
launchOpts, launchOpts,
prescripted = new java.util.ArrayList[File], prescripted = new java.util.ArrayList[File],
LauncherBased(launcherJar), LauncherBased(launcherJar),
@ -411,6 +420,36 @@ class ScriptedRunner {
bufferLog, bufferLog,
tests, tests,
logger, logger,
javaCommand = "java",
launchOpts,
prescripted,
LauncherBased(launcherJar),
Int.MaxValue,
parallelExecution = false,
)
}
/**
* This is the entry point used by SbtPlugin in sbt 2.0.x etc.
* Removing this method will break scripted and sbt plugin cross building.
* See https://github.com/sbt/sbt/issues/3245
*/
def run(
resourceBaseDirectory: File,
bufferLog: Boolean,
tests: Array[String],
launcherJar: File,
javaCommand: String,
launchOpts: Array[String],
prescripted: java.util.List[File],
): Unit = {
val logger = TestConsoleLogger()
run(
resourceBaseDirectory,
bufferLog,
tests,
logger,
javaCommand,
launchOpts, launchOpts,
prescripted, prescripted,
LauncherBased(launcherJar), LauncherBased(launcherJar),
@ -440,6 +479,36 @@ class ScriptedRunner {
bufferLog, bufferLog,
tests, tests,
logger, logger,
javaCommand = "java",
launchOpts,
prescripted,
LauncherBased(launcherJar),
instance,
)
}
/**
* This is the entry point used by SbtPlugin in sbt 2.0.x etc.
* Removing this method will break scripted and sbt plugin cross building.
* See https://github.com/sbt/sbt/issues/3245
*/
def runInParallel(
resourceBaseDirectory: File,
bufferLog: Boolean,
tests: Array[String],
launcherJar: File,
javaCommand: String,
launchOpts: Array[String],
prescripted: java.util.List[File],
instance: Int,
): Unit = {
val logger = TestConsoleLogger()
runInParallel(
resourceBaseDirectory,
bufferLog,
tests,
logger,
javaCommand,
launchOpts, launchOpts,
prescripted, prescripted,
LauncherBased(launcherJar), LauncherBased(launcherJar),
@ -466,6 +535,7 @@ class ScriptedRunner {
bufferLog, bufferLog,
tests, tests,
logger, logger,
javaCommand = "java",
launchOpts, launchOpts,
prescripted, prescripted,
RunFromSourceBased(scalaVersion, sbtVersion, classpath), RunFromSourceBased(scalaVersion, sbtVersion, classpath),
@ -477,11 +547,24 @@ class ScriptedRunner {
bufferLog: Boolean, bufferLog: Boolean,
tests: Array[String], tests: Array[String],
logger: Logger, logger: Logger,
javaCommand: String,
launchOpts: Array[String], launchOpts: Array[String],
prescripted: java.util.List[File], prescripted: java.util.List[File],
prop: RemoteSbtCreatorProp, prop: RemoteSbtCreatorProp,
instances: Int instances: Int
) = run(baseDir, bufferLog, tests, logger, launchOpts, prescripted, prop, instances, true) ): Unit =
run(
baseDir,
bufferLog,
tests,
logger,
javaCommand,
launchOpts,
prescripted,
prop,
instances,
parallelExecution = true
)
@nowarn @nowarn
private[this] def run( private[this] def run(
@ -489,6 +572,7 @@ class ScriptedRunner {
bufferLog: Boolean, bufferLog: Boolean,
tests: Array[String], tests: Array[String],
logger: Logger, logger: Logger,
javaCommand: String,
launchOpts: Array[String], launchOpts: Array[String],
prescripted: java.util.List[File], prescripted: java.util.List[File],
prop: RemoteSbtCreatorProp, prop: RemoteSbtCreatorProp,
@ -496,7 +580,7 @@ class ScriptedRunner {
parallelExecution: Boolean, parallelExecution: Boolean,
): Unit = { ): Unit = {
val addTestFile = (f: File) => { prescripted.add(f); () } val addTestFile = (f: File) => { prescripted.add(f); () }
val runner = new ScriptedTests(baseDir, bufferLog, launchOpts) val runner = new ScriptedTests(baseDir, bufferLog, javaCommand, launchOpts)
val sbtVersion = val sbtVersion =
prop match { prop match {
case LauncherBased(launcherJar) => case LauncherBased(launcherJar) =>