mirror of https://github.com/sbt/sbt.git
Implement client-side run
**Problem** `run` task has been emulated via function call inside of a sandboxed classloader, and blocking the command processing of sbt server loop. This poses isolation and availability issues. **Solution** This implements client-side run where the server creates a sandbox environment, and sends the information to the client, and the client forks a new JVM to perform the run. The client-side behavior has been implemented in sbtn side already.
This commit is contained in:
parent
e595914edc
commit
8db8c79d33
|
|
@ -61,10 +61,13 @@ final case class State(
|
||||||
}
|
}
|
||||||
|
|
||||||
def source: Option[CommandSource] =
|
def source: Option[CommandSource] =
|
||||||
currentCommand match {
|
currentCommand match
|
||||||
case Some(x) => x.source
|
case Some(x) => x.source
|
||||||
case _ => None
|
case _ => None
|
||||||
}
|
def isNetworkCommand: Boolean =
|
||||||
|
source match
|
||||||
|
case Some(s) => s.channelName.startsWith("network")
|
||||||
|
case _ => false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,6 @@ import sbt.internal.server.{
|
||||||
BspCompileTask,
|
BspCompileTask,
|
||||||
BuildServerProtocol,
|
BuildServerProtocol,
|
||||||
BuildServerReporter,
|
BuildServerReporter,
|
||||||
ClientJob,
|
|
||||||
Definition,
|
Definition,
|
||||||
LanguageServerProtocol,
|
LanguageServerProtocol,
|
||||||
ServerHandler,
|
ServerHandler,
|
||||||
|
|
@ -141,6 +140,7 @@ import xsbti.compile.{
|
||||||
TransactionalManagerType
|
TransactionalManagerType
|
||||||
}
|
}
|
||||||
import sbt.internal.IncrementalTest
|
import sbt.internal.IncrementalTest
|
||||||
|
import sbt.internal.RunUtil
|
||||||
|
|
||||||
object Defaults extends BuildCommon {
|
object Defaults extends BuildCommon {
|
||||||
final val CacheDirectoryName = "cache"
|
final val CacheDirectoryName = "cache"
|
||||||
|
|
@ -241,7 +241,7 @@ object Defaults extends BuildCommon {
|
||||||
getRootPaths(out, app) + ("CSR_CACHE" -> coursierCache)
|
getRootPaths(out, app) + ("CSR_CACHE" -> coursierCache)
|
||||||
},
|
},
|
||||||
fileConverter := MappedFileConverter(rootPaths.value, allowMachinePath.value)
|
fileConverter := MappedFileConverter(rootPaths.value, allowMachinePath.value)
|
||||||
) ++ BuildServerProtocol.globalSettings ++ ClientJob.globalSettings
|
) ++ BuildServerProtocol.globalSettings
|
||||||
|
|
||||||
private[sbt] def getRootPaths(out: NioPath, app: AppConfiguration): ListMap[String, NioPath] =
|
private[sbt] def getRootPaths(out: NioPath, app: AppConfiguration): ListMap[String, NioPath] =
|
||||||
val base = app.baseDirectory.getCanonicalFile.toPath
|
val base = app.baseDirectory.getCanonicalFile.toPath
|
||||||
|
|
@ -391,6 +391,7 @@ object Defaults extends BuildCommon {
|
||||||
aggregate :== true,
|
aggregate :== true,
|
||||||
maxErrors :== 100,
|
maxErrors :== 100,
|
||||||
fork :== false,
|
fork :== false,
|
||||||
|
clientSide :== true,
|
||||||
initialize :== {},
|
initialize :== {},
|
||||||
templateResolverInfos :== Nil,
|
templateResolverInfos :== Nil,
|
||||||
templateDescriptions :== TemplateCommandUtil.defaultTemplateDescriptions,
|
templateDescriptions :== TemplateCommandUtil.defaultTemplateDescriptions,
|
||||||
|
|
@ -1035,27 +1036,12 @@ object Defaults extends BuildCommon {
|
||||||
})
|
})
|
||||||
pickMainClassOrWarn(discoveredMainClasses.value, streams.value.log, logWarning)
|
pickMainClassOrWarn(discoveredMainClasses.value, streams.value.log, logWarning)
|
||||||
},
|
},
|
||||||
runMain := foregroundRunMainTask.evaluated,
|
|
||||||
run := foregroundRunTask.evaluated,
|
|
||||||
runBlock := {
|
|
||||||
val r = run.evaluated
|
|
||||||
val service = bgJobService.value
|
|
||||||
service.waitForTry(r.handle).get
|
|
||||||
()
|
|
||||||
},
|
|
||||||
runMainBlock := {
|
|
||||||
val r = runMain.evaluated
|
|
||||||
val service = bgJobService.value
|
|
||||||
service.waitForTry(r.handle).get
|
|
||||||
()
|
|
||||||
},
|
|
||||||
fgRun := runTask(fullClasspath, (run / mainClass), (run / runner)).evaluated,
|
fgRun := runTask(fullClasspath, (run / mainClass), (run / runner)).evaluated,
|
||||||
fgRunMain := runMainTask(fullClasspath, (run / runner)).evaluated,
|
fgRunMain := runMainTask(fullClasspath, (run / runner)).evaluated,
|
||||||
copyResources := copyResourcesTask.value,
|
copyResources := copyResourcesTask.value,
|
||||||
// note that we use the same runner and mainClass as plain run
|
) ++ RunUtil.configTasks(This) ++ inTask(run)(
|
||||||
mainBgRunMainTaskForConfig(This),
|
runnerSettings ++ newRunnerSettings
|
||||||
mainBgRunTaskForConfig(This)
|
) ++ compileIncrementalTaskSettings
|
||||||
) ++ inTask(run)(runnerSettings ++ newRunnerSettings) ++ compileIncrementalTaskSettings
|
|
||||||
|
|
||||||
private lazy val configGlobal = globalDefaults(
|
private lazy val configGlobal = globalDefaults(
|
||||||
Seq(
|
Seq(
|
||||||
|
|
@ -1869,122 +1855,6 @@ object Defaults extends BuildCommon {
|
||||||
/** Implements `cleanFiles` task. */
|
/** Implements `cleanFiles` task. */
|
||||||
private[sbt] def cleanFilesTask: Initialize[Task[Vector[File]]] = Def.task { Vector.empty[File] }
|
private[sbt] def cleanFilesTask: Initialize[Task[Vector[File]]] = Def.task { Vector.empty[File] }
|
||||||
|
|
||||||
private def termWrapper(canonical: Boolean, echo: Boolean): (() => Unit) => (() => Unit) =
|
|
||||||
(f: () => Unit) =>
|
|
||||||
() => {
|
|
||||||
val term = ITerminal.get
|
|
||||||
if (!canonical) {
|
|
||||||
term.enterRawMode()
|
|
||||||
if (echo) term.setEchoEnabled(echo)
|
|
||||||
} else if (!echo) term.setEchoEnabled(false)
|
|
||||||
try f()
|
|
||||||
finally {
|
|
||||||
if (!canonical) term.exitRawMode()
|
|
||||||
if (!echo) term.setEchoEnabled(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
def bgRunMainTask(
|
|
||||||
products: Initialize[Task[Classpath]],
|
|
||||||
classpath: Initialize[Task[Classpath]],
|
|
||||||
copyClasspath: Initialize[Boolean],
|
|
||||||
scalaRun: Initialize[Task[ScalaRun]]
|
|
||||||
): Initialize[InputTask[JobHandle]] = {
|
|
||||||
val parser = Defaults.loadForParser(discoveredMainClasses)((s, names) =>
|
|
||||||
Defaults.runMainParser(s, names getOrElse Nil)
|
|
||||||
)
|
|
||||||
Def.inputTask {
|
|
||||||
val service = bgJobService.value
|
|
||||||
val (mainClass, args) = parser.parsed
|
|
||||||
val hashClasspath = (bgRunMain / bgHashClasspath).value
|
|
||||||
val wrapper = termWrapper(canonicalInput.value, echoInput.value)
|
|
||||||
val converter = fileConverter.value
|
|
||||||
service.runInBackgroundWithLoader(resolvedScoped.value, state.value) { (logger, workingDir) =>
|
|
||||||
val cp =
|
|
||||||
if copyClasspath.value then
|
|
||||||
service.copyClasspath(
|
|
||||||
products.value,
|
|
||||||
classpath.value,
|
|
||||||
workingDir,
|
|
||||||
hashClasspath,
|
|
||||||
converter,
|
|
||||||
)
|
|
||||||
else classpath.value
|
|
||||||
given FileConverter = fileConverter.value
|
|
||||||
scalaRun.value match
|
|
||||||
case r: Run =>
|
|
||||||
val loader = r.newLoader(cp.files)
|
|
||||||
(
|
|
||||||
Some(loader),
|
|
||||||
wrapper(() => r.runWithLoader(loader, cp.files, mainClass, args, logger).get)
|
|
||||||
)
|
|
||||||
case sr =>
|
|
||||||
(None, wrapper(() => sr.run(mainClass, cp.files, args, logger).get))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def bgRunTask(
|
|
||||||
products: Initialize[Task[Classpath]],
|
|
||||||
classpath: Initialize[Task[Classpath]],
|
|
||||||
mainClassTask: Initialize[Task[Option[String]]],
|
|
||||||
copyClasspath: Initialize[Boolean],
|
|
||||||
scalaRun: Initialize[Task[ScalaRun]]
|
|
||||||
): Initialize[InputTask[JobHandle]] =
|
|
||||||
val parser = Def.spaceDelimited()
|
|
||||||
Def.inputTask {
|
|
||||||
val args = parser.parsed
|
|
||||||
val service = bgJobService.value
|
|
||||||
val mainClass = mainClassTask.value getOrElse sys.error("No main class detected.")
|
|
||||||
val hashClasspath = (bgRun / bgHashClasspath).value
|
|
||||||
val wrapper = termWrapper(canonicalInput.value, echoInput.value)
|
|
||||||
val converter = fileConverter.value
|
|
||||||
service.runInBackgroundWithLoader(resolvedScoped.value, state.value) { (logger, workingDir) =>
|
|
||||||
val cp =
|
|
||||||
if copyClasspath.value then
|
|
||||||
service.copyClasspath(
|
|
||||||
products.value,
|
|
||||||
classpath.value,
|
|
||||||
workingDir,
|
|
||||||
hashClasspath,
|
|
||||||
converter
|
|
||||||
)
|
|
||||||
else classpath.value
|
|
||||||
given FileConverter = converter
|
|
||||||
scalaRun.value match
|
|
||||||
case r: Run =>
|
|
||||||
val loader = r.newLoader(cp.files)
|
|
||||||
(
|
|
||||||
Some(loader),
|
|
||||||
wrapper(() => r.runWithLoader(loader, cp.files, mainClass, args, logger).get)
|
|
||||||
)
|
|
||||||
case sr =>
|
|
||||||
(None, wrapper(() => sr.run(mainClass, cp.files, args, logger).get))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// `runMain` calls bgRunMain in the background and pauses the current channel
|
|
||||||
def foregroundRunMainTask: Initialize[InputTask[EmulateForeground]] =
|
|
||||||
Def.inputTask {
|
|
||||||
val handle = bgRunMain.evaluated
|
|
||||||
handle match
|
|
||||||
case threadJobHandle: AbstractBackgroundJobService#ThreadJobHandle =>
|
|
||||||
threadJobHandle.isAutoCancel = true
|
|
||||||
case _ => ()
|
|
||||||
EmulateForeground(handle)
|
|
||||||
}
|
|
||||||
|
|
||||||
// `run` task calls bgRun in the background and pauses the current channel
|
|
||||||
def foregroundRunTask: Initialize[InputTask[EmulateForeground]] =
|
|
||||||
Def.inputTask {
|
|
||||||
val handle = bgRun.evaluated
|
|
||||||
handle match {
|
|
||||||
case threadJobHandle: AbstractBackgroundJobService#ThreadJobHandle =>
|
|
||||||
threadJobHandle.isAutoCancel = true
|
|
||||||
case _ =>
|
|
||||||
}
|
|
||||||
EmulateForeground(handle)
|
|
||||||
}
|
|
||||||
|
|
||||||
def runMainTask(
|
def runMainTask(
|
||||||
classpath: Initialize[Task[Classpath]],
|
classpath: Initialize[Task[Classpath]],
|
||||||
scalaRun: Initialize[Task[ScalaRun]]
|
scalaRun: Initialize[Task[ScalaRun]]
|
||||||
|
|
@ -2005,15 +1875,7 @@ object Defaults extends BuildCommon {
|
||||||
classpath: Initialize[Task[Classpath]],
|
classpath: Initialize[Task[Classpath]],
|
||||||
mainClassTask: Initialize[Task[Option[String]]],
|
mainClassTask: Initialize[Task[Option[String]]],
|
||||||
scalaRun: Initialize[Task[ScalaRun]]
|
scalaRun: Initialize[Task[ScalaRun]]
|
||||||
): Initialize[InputTask[Unit]] =
|
): Initialize[InputTask[Unit]] = RunUtil.serverSideRunTask(classpath, mainClassTask, scalaRun)
|
||||||
val parser = Def.spaceDelimited()
|
|
||||||
Def.inputTask {
|
|
||||||
val in = parser.parsed
|
|
||||||
val mainClass = mainClassTask.value getOrElse sys.error("No main class detected.")
|
|
||||||
val cp = classpath.value
|
|
||||||
given FileConverter = fileConverter.value
|
|
||||||
scalaRun.value.run(mainClass, cp.files, in, streams.value.log).get
|
|
||||||
}
|
|
||||||
|
|
||||||
def runnerTask: Setting[Task[ScalaRun]] = runner := runnerInit.value
|
def runnerTask: Setting[Task[ScalaRun]] = runner := runnerInit.value
|
||||||
|
|
||||||
|
|
@ -2173,26 +2035,6 @@ object Defaults extends BuildCommon {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def mainBgRunTask = mainBgRunTaskForConfig(Select(Runtime))
|
|
||||||
def mainBgRunMainTask = mainBgRunMainTaskForConfig(Select(Runtime))
|
|
||||||
|
|
||||||
private def mainBgRunTaskForConfig(c: ScopeAxis[ConfigKey]) =
|
|
||||||
bgRun := bgRunTask(
|
|
||||||
exportedProductJars,
|
|
||||||
This / c / This / fullClasspathAsJars,
|
|
||||||
run / mainClass,
|
|
||||||
bgRun / bgCopyClasspath,
|
|
||||||
run / runner
|
|
||||||
).evaluated
|
|
||||||
|
|
||||||
private def mainBgRunMainTaskForConfig(c: ScopeAxis[ConfigKey]) =
|
|
||||||
bgRunMain := bgRunMainTask(
|
|
||||||
exportedProductJars,
|
|
||||||
This / c / This / fullClasspathAsJars,
|
|
||||||
bgRunMain / bgCopyClasspath,
|
|
||||||
run / runner
|
|
||||||
).evaluated
|
|
||||||
|
|
||||||
def discoverMainClasses(analysis: CompileAnalysis): Seq[String] = analysis match {
|
def discoverMainClasses(analysis: CompileAnalysis): Seq[String] = analysis match {
|
||||||
case analysis: Analysis =>
|
case analysis: Analysis =>
|
||||||
analysis.infos.allInfos.values.map(_.getMainClasses).flatten.toSeq.sorted
|
analysis.infos.allInfos.values.map(_.getMainClasses).flatten.toSeq.sorted
|
||||||
|
|
@ -2649,10 +2491,10 @@ object Defaults extends BuildCommon {
|
||||||
lazy val configSettings: Seq[Setting[?]] =
|
lazy val configSettings: Seq[Setting[?]] =
|
||||||
Classpaths.configSettings ++ configTasks ++ configPaths ++ packageConfig ++
|
Classpaths.configSettings ++ configTasks ++ configPaths ++ packageConfig ++
|
||||||
Classpaths.compilerPluginConfig ++ deprecationSettings ++
|
Classpaths.compilerPluginConfig ++ deprecationSettings ++
|
||||||
BuildServerProtocol.configSettings ++ ClientJob.configSettings
|
BuildServerProtocol.configSettings
|
||||||
|
|
||||||
lazy val compileSettings: Seq[Setting[?]] =
|
lazy val compileSettings: Seq[Setting[?]] =
|
||||||
configSettings ++ (mainBgRunMainTask +: mainBgRunTask) ++ Classpaths.addUnmanagedLibrary
|
configSettings ++ RunUtil.configTasks(Select(Runtime)) ++ Classpaths.addUnmanagedLibrary
|
||||||
|
|
||||||
lazy val testSettings: Seq[Setting[?]] = configSettings ++ testTasks
|
lazy val testSettings: Seq[Setting[?]] = configSettings ++ testTasks
|
||||||
|
|
||||||
|
|
@ -4685,19 +4527,6 @@ trait BuildExtra extends BuildCommon with DefExtra {
|
||||||
r.run(mainClass, cp.files, baseArguments ++ args, streams.value.log).get
|
r.run(mainClass, cp.files, baseArguments ++ args, streams.value.log).get
|
||||||
}
|
}
|
||||||
|
|
||||||
def runTask(
|
|
||||||
config: Configuration,
|
|
||||||
mainClass: String,
|
|
||||||
arguments: String*
|
|
||||||
): Initialize[Task[Unit]] =
|
|
||||||
Def.task {
|
|
||||||
given FileConverter = fileConverter.value
|
|
||||||
val cp = (config / fullClasspath).value
|
|
||||||
val r = (config / run / runner).value
|
|
||||||
val s = streams.value
|
|
||||||
r.run(mainClass, cp.files, arguments, s.log).get
|
|
||||||
}
|
|
||||||
|
|
||||||
// public API
|
// public API
|
||||||
/** Returns a vector of settings that create custom run input task. */
|
/** Returns a vector of settings that create custom run input task. */
|
||||||
@nowarn
|
@nowarn
|
||||||
|
|
|
||||||
|
|
@ -316,10 +316,8 @@ object Keys {
|
||||||
// Run Keys
|
// Run Keys
|
||||||
val selectMainClass = taskKey[Option[String]]("Selects the main class to run.").withRank(BMinusTask)
|
val selectMainClass = taskKey[Option[String]]("Selects the main class to run.").withRank(BMinusTask)
|
||||||
val mainClass = taskKey[Option[String]]("Defines the main class for packaging or running.").withRank(BPlusTask)
|
val mainClass = taskKey[Option[String]]("Defines the main class for packaging or running.").withRank(BPlusTask)
|
||||||
val run = inputKey[EmulateForeground]("Runs a main class, passing along arguments provided on the command line.").withRank(APlusTask)
|
val run = inputKey[Unit | ClientJobParams]("Runs a main class, passing along arguments provided on the command line.").withRank(APlusTask)
|
||||||
val runBlock = inputKey[Unit]("Runs a main class, and blocks until it's done.").withRank(DTask)
|
val runMain = inputKey[Unit | ClientJobParams]("Runs the main class selected by the first argument, passing the remaining arguments to the main method.").withRank(ATask)
|
||||||
val runMain = inputKey[EmulateForeground]("Runs the main class selected by the first argument, passing the remaining arguments to the main method.").withRank(ATask)
|
|
||||||
val runMainBlock = inputKey[Unit]("Runs the main class selected by the first argument, passing the remaining arguments to the main method.").withRank(DTask)
|
|
||||||
val discoveredMainClasses = taskKey[Seq[String]]("Auto-detects main classes.").withRank(BMinusTask)
|
val discoveredMainClasses = taskKey[Seq[String]]("Auto-detects main classes.").withRank(BMinusTask)
|
||||||
val runner = taskKey[ScalaRun]("Implementation used to run a main class.").withRank(DTask)
|
val runner = taskKey[ScalaRun]("Implementation used to run a main class.").withRank(DTask)
|
||||||
val trapExit = settingKey[Boolean]("If true, enables exit trapping and thread management for 'run'-like tasks. This was removed in sbt 1.6.0 due to JDK 17 deprecating Security Manager.").withRank(CSetting)
|
val trapExit = settingKey[Boolean]("If true, enables exit trapping and thread management for 'run'-like tasks. This was removed in sbt 1.6.0 due to JDK 17 deprecating Security Manager.").withRank(CSetting)
|
||||||
|
|
@ -332,6 +330,7 @@ object Keys {
|
||||||
val discoveredJavaHomes = settingKey[Map[String, File]]("Discovered Java home directories")
|
val discoveredJavaHomes = settingKey[Map[String, File]]("Discovered Java home directories")
|
||||||
val javaHomes = settingKey[Map[String, File]]("The user-defined additional Java home directories")
|
val javaHomes = settingKey[Map[String, File]]("The user-defined additional Java home directories")
|
||||||
val fullJavaHomes = settingKey[Map[String, File]]("Combines discoveredJavaHomes and custom javaHomes.").withRank(CTask)
|
val fullJavaHomes = settingKey[Map[String, File]]("Combines discoveredJavaHomes and custom javaHomes.").withRank(CTask)
|
||||||
|
val clientSide = settingKey[Boolean]("If true, takes the action on the client-side")
|
||||||
|
|
||||||
val javaOptions = taskKey[Seq[String]]("Options passed to a new JVM when forking.").withRank(BPlusTask)
|
val javaOptions = taskKey[Seq[String]]("Options passed to a new JVM when forking.").withRank(BPlusTask)
|
||||||
val envVars = taskKey[Map[String, String]]("Environment variables used when forking a new JVM").withRank(BTask)
|
val envVars = taskKey[Map[String, String]]("Environment variables used when forking a new JVM").withRank(BTask)
|
||||||
|
|
@ -484,8 +483,6 @@ object Keys {
|
||||||
|
|
||||||
@cacheLevel(include = Array.empty)
|
@cacheLevel(include = Array.empty)
|
||||||
val bspReporter = taskKey[BuildServerReporter]("").withRank(DTask)
|
val bspReporter = taskKey[BuildServerReporter]("").withRank(DTask)
|
||||||
val clientJob = inputKey[ClientJobParams]("Translates a task into a job specification").withRank(Invisible)
|
|
||||||
val clientJobRunInfo = inputKey[ClientJobParams]("Translates the run task into a job specification").withRank(Invisible)
|
|
||||||
|
|
||||||
val csrCacheDirectory = settingKey[File]("Coursier cache directory. Uses -Dsbt.coursier.home or Coursier's default.").withRank(CSetting)
|
val csrCacheDirectory = settingKey[File]("Coursier cache directory. Uses -Dsbt.coursier.home or Coursier's default.").withRank(CSetting)
|
||||||
val csrMavenProfiles = settingKey[Set[String]]("").withRank(CSetting)
|
val csrMavenProfiles = settingKey[Set[String]]("").withRank(CSetting)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,302 @@
|
||||||
|
package sbt
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
import sbt.BuildExtra.*
|
||||||
|
import sbt.Def.*
|
||||||
|
import sbt.Keys.*
|
||||||
|
import sbt.ScopeAxis.This
|
||||||
|
import sbt.SlashSyntax0.*
|
||||||
|
import sbt.internal.util.{ Terminal as ITerminal }
|
||||||
|
import sbt.internal.worker.{ ClientJobParams, FilePath, JvmRunInfo, RunInfo }
|
||||||
|
import sbt.io.IO
|
||||||
|
import sbt.protocol.Serialization
|
||||||
|
import sbt.util.CacheImplicits.given
|
||||||
|
import xsbti.FileConverter
|
||||||
|
|
||||||
|
object RunUtil:
|
||||||
|
/**
|
||||||
|
* Conventional server-side run implementation.
|
||||||
|
*/
|
||||||
|
def serverSideRunTask(
|
||||||
|
classpath: Initialize[Task[Classpath]],
|
||||||
|
mainClassTask: Initialize[Task[Option[String]]],
|
||||||
|
scalaRun: Initialize[Task[ScalaRun]]
|
||||||
|
): Initialize[InputTask[Unit]] =
|
||||||
|
val parser = Def.spaceDelimited()
|
||||||
|
Def.inputTask {
|
||||||
|
val in = parser.parsed
|
||||||
|
val mainClass = getMainClass(mainClassTask.value)
|
||||||
|
val cp = classpath.value
|
||||||
|
given FileConverter = fileConverter.value
|
||||||
|
scalaRun.value.run(mainClass, cp.files, in, streams.value.log).get
|
||||||
|
}
|
||||||
|
|
||||||
|
def configTasks(c: ScopeAxis[ConfigKey]): Seq[Setting[?]] = Seq(
|
||||||
|
bgRunMain := bgRunMainTask(
|
||||||
|
exportedProductJars,
|
||||||
|
This / c / This / fullClasspathAsJars,
|
||||||
|
bgRunMain / bgCopyClasspath,
|
||||||
|
run / runner
|
||||||
|
).evaluated,
|
||||||
|
// note that we use the same runner and mainClass as plain run
|
||||||
|
bgRun := bgRunTask(
|
||||||
|
exportedProductJars,
|
||||||
|
This / c / This / fullClasspathAsJars,
|
||||||
|
run / mainClass,
|
||||||
|
bgRun / bgCopyClasspath,
|
||||||
|
run / runner
|
||||||
|
).evaluated,
|
||||||
|
runMain := defaultRunMainTask(
|
||||||
|
exportedProductJars,
|
||||||
|
This / c / This / fullClasspathAsJars,
|
||||||
|
run / runner,
|
||||||
|
runMain / clientSide
|
||||||
|
).evaluated,
|
||||||
|
run := defaultRunTask(
|
||||||
|
exportedProductJars,
|
||||||
|
This / c / This / fullClasspathAsJars,
|
||||||
|
run / mainClass,
|
||||||
|
run / runner,
|
||||||
|
run / clientSide
|
||||||
|
).evaluated,
|
||||||
|
run / connectInput := true,
|
||||||
|
)
|
||||||
|
|
||||||
|
private def termWrapper(canonical: Boolean, echo: Boolean): (() => Unit) => (() => Unit) =
|
||||||
|
(f: () => Unit) =>
|
||||||
|
() => {
|
||||||
|
val term = ITerminal.get
|
||||||
|
if (!canonical) {
|
||||||
|
term.enterRawMode()
|
||||||
|
if (echo) term.setEchoEnabled(echo)
|
||||||
|
} else if (!echo) term.setEchoEnabled(false)
|
||||||
|
try f()
|
||||||
|
finally {
|
||||||
|
if (!canonical) term.exitRawMode()
|
||||||
|
if (!echo) term.setEchoEnabled(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def getMainClass(value: Option[String]): String =
|
||||||
|
value.getOrElse(sys.error("no main class detected"))
|
||||||
|
|
||||||
|
private def mkRunInfo(
|
||||||
|
args: Vector[String],
|
||||||
|
mainClass: String,
|
||||||
|
cp: Classpath,
|
||||||
|
fo: ForkOptions,
|
||||||
|
conv: FileConverter
|
||||||
|
): RunInfo =
|
||||||
|
val strategy = fo.outputStrategy.map(_.getClass().getSimpleName().filter(_ != '$'))
|
||||||
|
// sbtn doesn't set java.home, so we need to do the fallback here
|
||||||
|
val javaHome =
|
||||||
|
fo.javaHome.map(IO.toURI).orElse(sys.props.get("java.home").map(x => IO.toURI(new File(x))))
|
||||||
|
val jvmRunInfo = JvmRunInfo(
|
||||||
|
args = args,
|
||||||
|
classpath = cp.map(x => IO.toURI(conv.toPath(x.data).toFile)).map(FilePath(_, "")).toVector,
|
||||||
|
mainClass = mainClass,
|
||||||
|
connectInput = fo.connectInput,
|
||||||
|
javaHome = javaHome,
|
||||||
|
outputStrategy = strategy,
|
||||||
|
workingDirectory = fo.workingDirectory.map(IO.toURI),
|
||||||
|
jvmOptions = fo.runJVMOptions,
|
||||||
|
environmentVariables = fo.envVars.toMap,
|
||||||
|
)
|
||||||
|
RunInfo(
|
||||||
|
jvm = true,
|
||||||
|
jvmRunInfo = jvmRunInfo,
|
||||||
|
)
|
||||||
|
|
||||||
|
def defaultRunMainTask(
|
||||||
|
products: Initialize[Task[Classpath]],
|
||||||
|
classpath: Initialize[Task[Classpath]],
|
||||||
|
scalaRun: Initialize[Task[ScalaRun]],
|
||||||
|
clientRun: Initialize[Boolean],
|
||||||
|
): Initialize[InputTask[Unit | ClientJobParams]] =
|
||||||
|
val parser = Defaults.loadForParser(discoveredMainClasses)((s, names) =>
|
||||||
|
Defaults.runMainParser(s, names getOrElse Nil)
|
||||||
|
)
|
||||||
|
Def.inputTask {
|
||||||
|
val conv = fileConverter.value
|
||||||
|
given FileConverter = conv
|
||||||
|
val service = bgJobService.value
|
||||||
|
val (mainClass, args) = parser.parsed
|
||||||
|
val hashClasspath = (bgRunMain / bgHashClasspath).value
|
||||||
|
val fo = (run / forkOptions).value
|
||||||
|
val state = Keys.state.value
|
||||||
|
if clientRun.value && state.isNetworkCommand then
|
||||||
|
val workingDir = service.createWorkingDirectory
|
||||||
|
val cp = service.copyClasspath(
|
||||||
|
products.value,
|
||||||
|
classpath.value,
|
||||||
|
workingDir,
|
||||||
|
conv,
|
||||||
|
)
|
||||||
|
val info = mkRunInfo(args.toVector, mainClass, cp, fo, conv)
|
||||||
|
val result = ClientJobParams(
|
||||||
|
runInfo = info
|
||||||
|
)
|
||||||
|
import sbt.internal.worker.codec.JsonProtocol.*
|
||||||
|
state.notifyEvent(Serialization.clientJob, result)
|
||||||
|
result
|
||||||
|
else
|
||||||
|
val wrapper = termWrapper(canonicalInput.value, echoInput.value)
|
||||||
|
val handle = service.runInBackgroundWithLoader(Keys.resolvedScoped.value, state):
|
||||||
|
(logger, workingDir) =>
|
||||||
|
val cp = service.copyClasspath(
|
||||||
|
products.value,
|
||||||
|
classpath.value,
|
||||||
|
workingDir,
|
||||||
|
hashClasspath,
|
||||||
|
conv,
|
||||||
|
)
|
||||||
|
scalaRun.value match
|
||||||
|
case r: Run =>
|
||||||
|
val loader = r.newLoader(cp.files)
|
||||||
|
(
|
||||||
|
Some(loader),
|
||||||
|
wrapper(() => r.runWithLoader(loader, cp.files, mainClass, args, logger).get)
|
||||||
|
)
|
||||||
|
case sr =>
|
||||||
|
(None, wrapper(() => sr.run(mainClass, cp.files, args, logger).get))
|
||||||
|
service.waitForTry(handle).get
|
||||||
|
()
|
||||||
|
}
|
||||||
|
|
||||||
|
def defaultRunTask(
|
||||||
|
products: Initialize[Task[Classpath]],
|
||||||
|
classpath: Initialize[Task[Classpath]],
|
||||||
|
mainClassTask: Initialize[Task[Option[String]]],
|
||||||
|
scalaRun: Initialize[Task[ScalaRun]],
|
||||||
|
clientRun: Initialize[Boolean]
|
||||||
|
): Initialize[InputTask[Unit | ClientJobParams]] =
|
||||||
|
val parser = Def.spaceDelimited()
|
||||||
|
Def.inputTask {
|
||||||
|
val conv = fileConverter.value
|
||||||
|
given FileConverter = conv
|
||||||
|
val args = parser.parsed
|
||||||
|
val service = bgJobService.value
|
||||||
|
val mainClass = getMainClass(mainClassTask.value)
|
||||||
|
val hashClasspath = (bgRun / bgHashClasspath).value
|
||||||
|
val fo = (run / forkOptions).value
|
||||||
|
val state = Keys.state.value
|
||||||
|
if clientRun.value && state.isNetworkCommand then
|
||||||
|
val workingDir = service.createWorkingDirectory
|
||||||
|
val cp = service.copyClasspath(
|
||||||
|
products.value,
|
||||||
|
classpath.value,
|
||||||
|
workingDir,
|
||||||
|
conv,
|
||||||
|
)
|
||||||
|
val info = mkRunInfo(args.toVector, mainClass, cp, fo, conv)
|
||||||
|
val result = ClientJobParams(
|
||||||
|
runInfo = info
|
||||||
|
)
|
||||||
|
import sbt.internal.worker.codec.JsonProtocol.*
|
||||||
|
state.notifyEvent(Serialization.clientJob, result)
|
||||||
|
result
|
||||||
|
else
|
||||||
|
val wrapper = termWrapper(canonicalInput.value, echoInput.value)
|
||||||
|
val handle = service.runInBackgroundWithLoader(Keys.resolvedScoped.value, state):
|
||||||
|
(logger, workingDir) =>
|
||||||
|
val cp = service.copyClasspath(
|
||||||
|
products.value,
|
||||||
|
classpath.value,
|
||||||
|
workingDir,
|
||||||
|
hashClasspath,
|
||||||
|
conv
|
||||||
|
)
|
||||||
|
scalaRun.value match
|
||||||
|
case r: Run =>
|
||||||
|
val loader = r.newLoader(cp.files)
|
||||||
|
(
|
||||||
|
Some(loader),
|
||||||
|
wrapper(() => r.runWithLoader(loader, cp.files, mainClass, args, logger).get)
|
||||||
|
)
|
||||||
|
case sr =>
|
||||||
|
(None, wrapper(() => sr.run(mainClass, cp.files, args, logger).get))
|
||||||
|
service.waitForTry(handle).get
|
||||||
|
()
|
||||||
|
}
|
||||||
|
|
||||||
|
def bgRunMainTask(
|
||||||
|
products: Initialize[Task[Classpath]],
|
||||||
|
classpath: Initialize[Task[Classpath]],
|
||||||
|
copyClasspath: Initialize[Boolean],
|
||||||
|
scalaRun: Initialize[Task[ScalaRun]]
|
||||||
|
): Initialize[InputTask[JobHandle]] =
|
||||||
|
val parser = Defaults.loadForParser(discoveredMainClasses)((s, names) =>
|
||||||
|
Defaults.runMainParser(s, names getOrElse Nil)
|
||||||
|
)
|
||||||
|
Def.inputTask {
|
||||||
|
val service = bgJobService.value
|
||||||
|
val (mainClass, args) = parser.parsed
|
||||||
|
val hashClasspath = (bgRunMain / bgHashClasspath).value
|
||||||
|
val wrapper = termWrapper(canonicalInput.value, echoInput.value)
|
||||||
|
val converter = fileConverter.value
|
||||||
|
service.runInBackgroundWithLoader(Keys.resolvedScoped.value, state.value) {
|
||||||
|
(logger, workingDir) =>
|
||||||
|
val cp =
|
||||||
|
if copyClasspath.value then
|
||||||
|
service.copyClasspath(
|
||||||
|
products.value,
|
||||||
|
classpath.value,
|
||||||
|
workingDir,
|
||||||
|
hashClasspath,
|
||||||
|
converter,
|
||||||
|
)
|
||||||
|
else classpath.value
|
||||||
|
given FileConverter = fileConverter.value
|
||||||
|
scalaRun.value match
|
||||||
|
case r: Run =>
|
||||||
|
val loader = r.newLoader(cp.files)
|
||||||
|
(
|
||||||
|
Some(loader),
|
||||||
|
wrapper(() => r.runWithLoader(loader, cp.files, mainClass, args, logger).get)
|
||||||
|
)
|
||||||
|
case sr =>
|
||||||
|
(None, wrapper(() => sr.run(mainClass, cp.files, args, logger).get))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def bgRunTask(
|
||||||
|
products: Initialize[Task[Classpath]],
|
||||||
|
classpath: Initialize[Task[Classpath]],
|
||||||
|
mainClassTask: Initialize[Task[Option[String]]],
|
||||||
|
copyClasspath: Initialize[Boolean],
|
||||||
|
scalaRun: Initialize[Task[ScalaRun]]
|
||||||
|
): Initialize[InputTask[JobHandle]] =
|
||||||
|
val parser = Def.spaceDelimited()
|
||||||
|
Def.inputTask {
|
||||||
|
val args = parser.parsed
|
||||||
|
val service = bgJobService.value
|
||||||
|
val mainClass = getMainClass(mainClassTask.value)
|
||||||
|
val hashClasspath = (bgRun / bgHashClasspath).value
|
||||||
|
val wrapper = termWrapper(canonicalInput.value, echoInput.value)
|
||||||
|
val converter = fileConverter.value
|
||||||
|
service.runInBackgroundWithLoader(Keys.resolvedScoped.value, state.value) {
|
||||||
|
(logger, workingDir) =>
|
||||||
|
val cp =
|
||||||
|
if copyClasspath.value then
|
||||||
|
service.copyClasspath(
|
||||||
|
products.value,
|
||||||
|
classpath.value,
|
||||||
|
workingDir,
|
||||||
|
hashClasspath,
|
||||||
|
converter
|
||||||
|
)
|
||||||
|
else classpath.value
|
||||||
|
given FileConverter = converter
|
||||||
|
scalaRun.value match
|
||||||
|
case r: Run =>
|
||||||
|
val loader = r.newLoader(cp.files)
|
||||||
|
(
|
||||||
|
Some(loader),
|
||||||
|
wrapper(() => r.runWithLoader(loader, cp.files, mainClass, args, logger).get)
|
||||||
|
)
|
||||||
|
case sr =>
|
||||||
|
(None, wrapper(() => sr.run(mainClass, cp.files, args, logger).get))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end RunUtil
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
/*
|
|
||||||
* sbt
|
|
||||||
* Copyright 2023, Scala center
|
|
||||||
* Copyright 2011 - 2022, Lightbend, Inc.
|
|
||||||
* Copyright 2008 - 2010, Mark Harrah
|
|
||||||
* Licensed under Apache License 2.0 (see LICENSE)
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sbt
|
|
||||||
package internal
|
|
||||||
package server
|
|
||||||
|
|
||||||
import java.io.File
|
|
||||||
import sbt.Def.*
|
|
||||||
import sbt.Keys.*
|
|
||||||
import sbt.UpperStateOps.*
|
|
||||||
import sbt.internal.util.complete.Parser
|
|
||||||
import sbt.internal.worker.{ ClientJobParams, FilePath, JvmRunInfo, RunInfo }
|
|
||||||
import sbt.io.IO
|
|
||||||
import sbt.protocol.Serialization
|
|
||||||
import sbt.Keys.fileConverter
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A ClientJob represents a unit of work that sbt server process
|
|
||||||
* can outsourse back to the client. Initially intended for sbtn client-side run.
|
|
||||||
*/
|
|
||||||
object ClientJob {
|
|
||||||
lazy val globalSettings: Seq[Def.Setting[?]] = Seq(
|
|
||||||
clientJob := clientJobTask.evaluated,
|
|
||||||
clientJob / aggregate := false,
|
|
||||||
)
|
|
||||||
|
|
||||||
private def clientJobTask: Def.Initialize[InputTask[ClientJobParams]] = Def.inputTaskDyn {
|
|
||||||
val tokens = spaceDelimited().parsed
|
|
||||||
val state = Keys.state.value
|
|
||||||
val p = Act.aggregatedKeyParser(state)
|
|
||||||
if (tokens.isEmpty) {
|
|
||||||
sys.error("expected an argument, for example foo/run")
|
|
||||||
}
|
|
||||||
val scopedKey = Parser.parse(tokens.head, p) match {
|
|
||||||
case Right(x :: Nil) => x
|
|
||||||
case Right(xs) => sys.error("too many keys")
|
|
||||||
case Left(err) => sys.error(err)
|
|
||||||
}
|
|
||||||
if (scopedKey.key == run.key)
|
|
||||||
clientJobRunInfo.rescope(scopedKey.scope).toTask(" " + tokens.tail.mkString(" "))
|
|
||||||
else sys.error(s"unsupported task for clientJob $scopedKey")
|
|
||||||
}
|
|
||||||
|
|
||||||
// This will be scoped to Compile, Test, etc
|
|
||||||
lazy val configSettings: Seq[Def.Setting[?]] = Seq(
|
|
||||||
clientJobRunInfo := clientJobRunInfoTask.evaluated,
|
|
||||||
)
|
|
||||||
|
|
||||||
private def clientJobRunInfoTask: Def.Initialize[InputTask[ClientJobParams]] = Def.inputTask {
|
|
||||||
val state = Keys.state.value
|
|
||||||
val args = spaceDelimited().parsed
|
|
||||||
val mainClass = (Keys.run / Keys.mainClass).value
|
|
||||||
val service = bgJobService.value
|
|
||||||
val fo = (Keys.run / Keys.forkOptions).value
|
|
||||||
val workingDir = service.createWorkingDirectory
|
|
||||||
val conv = fileConverter.value
|
|
||||||
val cp = service.copyClasspath(
|
|
||||||
exportedProductJars.value,
|
|
||||||
fullClasspathAsJars.value,
|
|
||||||
workingDir,
|
|
||||||
conv,
|
|
||||||
)
|
|
||||||
val strategy = fo.outputStrategy.map(_.getClass().getSimpleName().filter(_ != '$'))
|
|
||||||
// sbtn doesn't set java.home, so we need to do the fallback here
|
|
||||||
val javaHome =
|
|
||||||
fo.javaHome.map(IO.toURI).orElse(sys.props.get("java.home").map(x => IO.toURI(new File(x))))
|
|
||||||
val jvmRunInfo = JvmRunInfo(
|
|
||||||
args = args.toVector,
|
|
||||||
classpath = cp.map(x => IO.toURI(conv.toPath(x.data).toFile)).map(FilePath(_, "")).toVector,
|
|
||||||
mainClass = mainClass.getOrElse(sys.error("no main class")),
|
|
||||||
connectInput = fo.connectInput,
|
|
||||||
javaHome = javaHome,
|
|
||||||
outputStrategy = strategy,
|
|
||||||
workingDirectory = fo.workingDirectory.map(IO.toURI),
|
|
||||||
jvmOptions = fo.runJVMOptions,
|
|
||||||
environmentVariables = fo.envVars.toMap,
|
|
||||||
)
|
|
||||||
val info = RunInfo(
|
|
||||||
jvm = true,
|
|
||||||
jvmRunInfo = jvmRunInfo,
|
|
||||||
)
|
|
||||||
val result = ClientJobParams(
|
|
||||||
runInfo = info
|
|
||||||
)
|
|
||||||
import sbt.internal.worker.codec.JsonProtocol.*
|
|
||||||
state.notifyEvent(Serialization.clientJob, result)
|
|
||||||
result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
addCommandAlias("demo-success", "runBlock true")
|
addCommandAlias("demo-success", "run true")
|
||||||
addCommandAlias("demo-failure", "runBlock false")
|
addCommandAlias("demo-failure", "run false")
|
||||||
addCommandAlias("z", "scalaVersion")
|
addCommandAlias("z", "scalaVersion")
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ lazy val root = (project in file(".")).
|
||||||
name := "run-test",
|
name := "run-test",
|
||||||
runFoo := Def.inputTaskDyn {
|
runFoo := Def.inputTaskDyn {
|
||||||
val args = Def.spaceDelimited().parsed
|
val args = Def.spaceDelimited().parsed
|
||||||
(Compile / runMainBlock).toTask(s" Foo " + args.mkString(" "))
|
(Compile / runMain).toTask(s" Foo " + args.mkString(" "))
|
||||||
}.evaluated,
|
}.evaluated,
|
||||||
check := {
|
check := {
|
||||||
val x = runFoo.toTask(" hi ho").value
|
val x = runFoo.toTask(" hi ho").value
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,6 @@ val root = (project in file(".")).settings(
|
||||||
val cp = System.getProperty("java.library.path", "").split(":").dropRight(1)
|
val cp = System.getProperty("java.library.path", "").split(":").dropRight(1)
|
||||||
System.setProperty("java.library.path", cp.mkString(":"))
|
System.setProperty("java.library.path", cp.mkString(":"))
|
||||||
},
|
},
|
||||||
wrappedRun := wrap(Runtime / runBlock).value,
|
wrappedRun := wrap(Runtime / run).value,
|
||||||
wrappedTest := wrap(Test / testOnly).value
|
wrappedTest := wrap(Test / testOnly).value
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
$ delete output
|
$ delete output
|
||||||
> runBlock
|
> run
|
||||||
$ exists output
|
$ exists output
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
$ delete output
|
$ delete output
|
||||||
> runBlock
|
> run
|
||||||
$ exists output
|
$ exists output
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
$ delete output
|
$ delete output
|
||||||
> runBlock
|
> run
|
||||||
$ exists output
|
$ exists output
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,6 @@
|
||||||
# macro expansion fails
|
# macro expansion fails
|
||||||
-> b/compile
|
-> b/compile
|
||||||
|
|
||||||
> c/runBlock
|
> c/run
|
||||||
$ exists s2.13.13.txt
|
$ exists s2.13.13.txt
|
||||||
$ delete s2.13.13.txt
|
$ delete s2.13.13.txt
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
> a/checkLibs
|
> a/checkLibs
|
||||||
> b/checkLibs
|
> b/checkLibs
|
||||||
|
|
||||||
> b/runBlock
|
> b/run
|
||||||
$ exists s2.13.12.txt
|
$ exists s2.13.12.txt
|
||||||
$ delete s2.13.12.txt
|
$ delete s2.13.12.txt
|
||||||
|
|
||||||
# don't crash when expanding the macro
|
# don't crash when expanding the macro
|
||||||
> b3/runBlock
|
> b3/run
|
||||||
$ exists s2.13.14.txt
|
$ exists s2.13.14.txt
|
||||||
$ delete s2.13.14.txt
|
$ delete s2.13.14.txt
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
$ delete output
|
$ delete output
|
||||||
> runBlock
|
> run
|
||||||
$ exists output
|
$ exists output
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
> runBlock
|
> run
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
$ delete output
|
$ delete output
|
||||||
> runBlock
|
> run
|
||||||
$ exists output
|
$ exists output
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
$ delete output
|
$ delete output
|
||||||
> runBlock
|
> run
|
||||||
$ exists output
|
$ exists output
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
$ delete output
|
$ delete output
|
||||||
> runBlock
|
> run
|
||||||
$ exists output
|
$ exists output
|
||||||
$ delete shapeless_2.11-2.3.0.jar
|
$ delete shapeless_2.11-2.3.0.jar
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
$ delete output
|
$ delete output
|
||||||
> runBlock
|
> run
|
||||||
$ exists output
|
$ exists output
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
> runBlock
|
> run
|
||||||
> Test/run
|
> Test/run
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
$ delete output
|
$ delete output
|
||||||
> runBlock
|
> run
|
||||||
$ exists output
|
$ exists output
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
$ delete output
|
$ delete output
|
||||||
> runBlock
|
> run
|
||||||
$ exists output
|
$ exists output
|
||||||
> updateClassifiersCheck
|
> updateClassifiersCheck
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
$ delete output
|
$ delete output
|
||||||
> runBlock
|
> run
|
||||||
$ exists output
|
$ exists output
|
||||||
> updateSbtClassifiers
|
> updateSbtClassifiers
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
$ delete output
|
$ delete output
|
||||||
> runBlock
|
> run
|
||||||
$ exists output
|
$ exists output
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
# This should fail because the Main object is in package jartest and the resource is directly
|
# This should fail because the Main object is in package jartest and the resource is directly
|
||||||
# in src/main/resources
|
# in src/main/resources
|
||||||
-> runBlock
|
-> run
|
||||||
|
|
||||||
> package
|
> package
|
||||||
|
|
||||||
|
|
@ -18,7 +18,7 @@ $ copy-file src/main/resources/main_resource_test src/main/resources/jartest/mai
|
||||||
$ delete src/main/resources/main_resource_test
|
$ delete src/main/resources/main_resource_test
|
||||||
|
|
||||||
# This should succeed because sbt should put the resource on the runClasspath
|
# This should succeed because sbt should put the resource on the runClasspath
|
||||||
> runBlock
|
> run
|
||||||
|
|
||||||
# This is necessary because package bases whether or not to run on last modified times, which don't have
|
# This is necessary because package bases whether or not to run on last modified times, which don't have
|
||||||
# high enough resolution to notice the above move of main_resource_test
|
# high enough resolution to notice the above move of main_resource_test
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,14 @@
|
||||||
$ copy-file changes/B.scala B.scala
|
$ copy-file changes/B.scala B.scala
|
||||||
|
|
||||||
$ copy-file changes/A1.scala A.scala
|
$ copy-file changes/A1.scala A.scala
|
||||||
> runBlock 1
|
> run 1
|
||||||
$ copy-file changes/A2.scala A.scala
|
$ copy-file changes/A2.scala A.scala
|
||||||
> runBlock 2
|
> run 2
|
||||||
|
|
||||||
> clean
|
> clean
|
||||||
> ++2.13.12!
|
> ++2.13.12!
|
||||||
|
|
||||||
$ copy-file changes/A1.scala A.scala
|
$ copy-file changes/A1.scala A.scala
|
||||||
> runBlock 1
|
> run 1
|
||||||
$ copy-file changes/A2.scala A.scala
|
$ copy-file changes/A2.scala A.scala
|
||||||
> runBlock 2
|
> run 2
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,10 @@ $ copy-file changes/A1.scala A.scala
|
||||||
$ copy-file changes/B.scala B.scala
|
$ copy-file changes/B.scala B.scala
|
||||||
$ copy-file changes/C.scala C.scala
|
$ copy-file changes/C.scala C.scala
|
||||||
> compile
|
> compile
|
||||||
-> runBlock
|
-> run
|
||||||
|
|
||||||
$ copy-file changes/A2.scala A.scala
|
$ copy-file changes/A2.scala A.scala
|
||||||
$ sleep 1000
|
$ sleep 1000
|
||||||
|
|
||||||
> compile
|
> compile
|
||||||
> runBlock
|
> run
|
||||||
|
|
|
||||||
|
|
@ -36,10 +36,10 @@ $ delete src/main/java/a/A.java
|
||||||
# It shouldn't run though, because it doesn't have a main method
|
# It shouldn't run though, because it doesn't have a main method
|
||||||
$ copy-file changes/B1.java src/main/java/a/b/B.java
|
$ copy-file changes/B1.java src/main/java/a/b/B.java
|
||||||
> compile
|
> compile
|
||||||
-> runBlock
|
-> run
|
||||||
|
|
||||||
|
|
||||||
# Replace B with a new B that has a main method and should therefore run
|
# Replace B with a new B that has a main method and should therefore run
|
||||||
# if the main method was properly detected
|
# if the main method was properly detected
|
||||||
$ copy-file changes/B3.java src/main/java/a/b/B.java
|
$ copy-file changes/B3.java src/main/java/a/b/B.java
|
||||||
> runBlock
|
> run
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
> compile
|
> compile
|
||||||
|
|
||||||
# the value of F.x should be 16
|
# the value of F.x should be 16
|
||||||
> runBlock 16
|
> run 16
|
||||||
|
|
||||||
# modify D.scala so that the linearization changes
|
# modify D.scala so that the linearization changes
|
||||||
$ copy-file changes/D.scala D.scala
|
$ copy-file changes/D.scala D.scala
|
||||||
|
|
@ -12,4 +12,4 @@ $ sleep 1000
|
||||||
|
|
||||||
# if F is recompiled, the value of x should be 11, otherwise it will still be 16
|
# if F is recompiled, the value of x should be 11, otherwise it will still be 16
|
||||||
# and this will fail
|
# and this will fail
|
||||||
> runBlock 11
|
> run 11
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
> compile
|
> compile
|
||||||
|
|
||||||
# result should be 1
|
# result should be 1
|
||||||
> runBlock 1
|
> run 1
|
||||||
|
|
||||||
# change order of arguments in A.x
|
# change order of arguments in A.x
|
||||||
$ copy-file changes/A.scala A.scala
|
$ copy-file changes/A.scala A.scala
|
||||||
|
|
@ -13,4 +13,4 @@ $ copy-file changes/A.scala A.scala
|
||||||
> compile
|
> compile
|
||||||
|
|
||||||
# Should still get 1 and not -1
|
# Should still get 1 and not -1
|
||||||
> runBlock 1
|
> run 1
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
> compile
|
> compile
|
||||||
|
|
||||||
# verify that erased A.x can be called normally and reflectively
|
# verify that erased A.x can be called normally and reflectively
|
||||||
> runBlock false
|
> run false
|
||||||
|
|
||||||
# make A.x specialized
|
# make A.x specialized
|
||||||
$ copy-file changes/A.scala A.scala
|
$ copy-file changes/A.scala A.scala
|
||||||
|
|
@ -12,4 +12,4 @@ $ copy-file changes/A.scala A.scala
|
||||||
|
|
||||||
# verify that specialized A.x can be called normally and reflectively
|
# verify that specialized A.x can be called normally and reflectively
|
||||||
# NOTE: this test doesn't actually work correctly: have to check the output to see that B.scala was recompiled
|
# NOTE: this test doesn't actually work correctly: have to check the output to see that B.scala was recompiled
|
||||||
> runBlock true
|
> run true
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,14 @@ class ClientTest extends AbstractServerTest {
|
||||||
test("three commands with middle failure") {
|
test("three commands with middle failure") {
|
||||||
assert(client("compile;willFail;willSucceed") == 1)
|
assert(client("compile;willFail;willSucceed") == 1)
|
||||||
}
|
}
|
||||||
|
test("run") {
|
||||||
|
val (exitCode, lines) = clientWithStdoutLines("run")
|
||||||
|
assert(exitCode == 0)
|
||||||
|
assert(
|
||||||
|
lines.toList.exists(_.contains("running (fork) hello")),
|
||||||
|
lines.toList.mkString(",")
|
||||||
|
)
|
||||||
|
}
|
||||||
test("compi completions") {
|
test("compi completions") {
|
||||||
val expected = Vector(
|
val expected = Vector(
|
||||||
"compile",
|
"compile",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue