diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 846327e2aa..dbc83f1d74 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,10 +32,10 @@ jobs: java: 21 distribution: temurin jobtype: 5 - - os: ubuntu-latest - java: 8 - distribution: adopt - jobtype: 6 + # - os: ubuntu-latest + # java: 8 + # distribution: adopt + # jobtype: 6 - os: ubuntu-latest java: 8 distribution: adopt diff --git a/build.sbt b/build.sbt index eebbfdd3cd..c2b7db293c 100644 --- a/build.sbt +++ b/build.sbt @@ -758,7 +758,7 @@ lazy val protocolProj = (project in file("protocol")) // General command support and core commands not specific to a build system lazy val commandProj = (project in file("main-command")) .enablePlugins(ContrabandPlugin, JsonCodecPlugin) - .dependsOn(protocolProj, completeProj, utilLogging) + .dependsOn(protocolProj, completeProj, utilLogging, runProj) .settings( testedBaseSettings, name := "Command", @@ -1072,6 +1072,7 @@ lazy val mainProj = (project in file("main")) exclude[IncompatibleTemplateDefProblem]("sbt.internal.server.BuildServerReporter"), exclude[MissingClassProblem]("sbt.internal.CustomHttp*"), exclude[ReversedMissingMethodProblem]("sbt.JobHandle.isAutoCancel"), + exclude[ReversedMissingMethodProblem]("sbt.BackgroundJobService.createWorkingDirectory"), ) ) .configure( diff --git a/internal/util-collection/src/main/scala/sbt/internal/util/Util.scala b/internal/util-collection/src/main/scala/sbt/internal/util/Util.scala index 324c62dfb2..4e4d3e8b7c 100644 --- a/internal/util-collection/src/main/scala/sbt/internal/util/Util.scala +++ b/internal/util-collection/src/main/scala/sbt/internal/util/Util.scala @@ -8,10 +8,13 @@ package sbt.internal.util +import java.nio.file.{ Path, Paths } import java.util.Locale import scala.reflect.macros.blackbox import scala.language.experimental.macros +import scala.language.reflectiveCalls +import scala.util.control.NonFatal object Util { def makeList[T](size: Int, value: T): List[T] = List.fill(size)(value) @@ -77,4 +80,50 @@ object Util { class Macro(val c: blackbox.Context) { def ignore(f: c.Tree): c.Expr[Unit] = c.universe.reify({ c.Expr[Any](f).splice; () }) } + + /** + * Given a list of event handlers expressed partial functions, combine them + * together using orElse from the left. + */ + def reduceIntents[A1, A2](intents: PartialFunction[A1, A2]*): PartialFunction[A1, A2] = + intents.toList.reduceLeft(_ orElse _) + + lazy val majorJavaVersion: Int = + try { + val javaVersion = sys.props.get("java.version").getOrElse("1.0") + if (javaVersion.startsWith("1.")) { + javaVersion.split("\\.")(1).toInt + } else { + javaVersion.split("\\.")(0).toInt + } + } catch { + case NonFatal(_) => 0 + } + + private type GetId = { + def getId: Long + } + private type ThreadId = { + def threadId: Long + } + + /** + * Returns current thread id. + * Thread.threadId was added in JDK 19, and deprecated Thread#getId + * https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Thread.html#threadId() + */ + def threadId: Long = + if (majorJavaVersion < 19) { + (Thread.currentThread(): AnyRef) match { + case g: GetId @unchecked => g.getId + } + } else { + (Thread.currentThread(): AnyRef) match { + case g: ThreadId @unchecked => g.threadId + } + } + + lazy val javaHome: Path = + if (sys.props("java.home").endsWith("jre")) Paths.get(sys.props("java.home")).getParent() + else Paths.get(sys.props("java.home")) } diff --git a/launcher-package/build.sbt b/launcher-package/build.sbt index 6ef8c07582..b58d8b2770 100755 --- a/launcher-package/build.sbt +++ b/launcher-package/build.sbt @@ -121,7 +121,7 @@ val root = (project in file(".")). file }, // update sbt.sh at root - sbtnVersion := "1.10.5", + sbtnVersion := "1.10.8", sbtnJarsBaseUrl := "https://github.com/sbt/sbtn-dist/releases/download", sbtnJarsMappings := { val baseUrl = sbtnJarsBaseUrl.value diff --git a/launcher-package/src/universal/bin/sbt.bat b/launcher-package/src/universal/bin/sbt.bat index 2bd4dd49e6..3de1fbd88b 100755 --- a/launcher-package/src/universal/bin/sbt.bat +++ b/launcher-package/src/universal/bin/sbt.bat @@ -476,6 +476,11 @@ if "%~0" == "new" ( set sbt_new=true ) ) +if "%~0" == "init" ( + if not defined SBT_ARGS ( + set sbt_new=true + ) +) if "%g:~0,2%" == "-D" ( rem special handling for -D since '=' gets parsed away diff --git a/main-command/src/main/scala/sbt/internal/CommandChannel.scala b/main-command/src/main/scala/sbt/internal/CommandChannel.scala index db53b95580..3e0d7a518d 100644 --- a/main-command/src/main/scala/sbt/internal/CommandChannel.scala +++ b/main-command/src/main/scala/sbt/internal/CommandChannel.scala @@ -63,6 +63,8 @@ abstract class CommandChannel { } } } + protected def appendExec(commandLine: String, execId: Option[String]): Boolean = + append(Exec(commandLine, execId.orElse(Some(Exec.newExecId)), Some(CommandSource(name)))) def poll: Option[Exec] = Option(commandQueue.poll) def prompt(e: ConsolePromptEvent): Unit = userThread.onConsolePromptEvent(e) @@ -81,20 +83,21 @@ abstract class CommandChannel { private[sbt] final def logLevel: Level.Value = level.get private[this] def setLevel(value: Level.Value, cmd: String): Boolean = { level.set(value) - append(Exec(cmd, Some(Exec.newExecId), Some(CommandSource(name)))) + appendExec(cmd, None) } - private[sbt] def onCommand: String => Boolean = { - case "error" => setLevel(Level.Error, "error") - case "debug" => setLevel(Level.Debug, "debug") - case "info" => setLevel(Level.Info, "info") - case "warn" => setLevel(Level.Warn, "warn") - case cmd => - if (cmd.nonEmpty) append(Exec(cmd, Some(Exec.newExecId), Some(CommandSource(name)))) - else false - } - private[sbt] def onFastTrackTask: String => Boolean = { s: String => + private[sbt] def onCommandLine(cmd: String): Boolean = + cmd match { + case "error" => setLevel(Level.Error, "error") + case "debug" => setLevel(Level.Debug, "debug") + case "info" => setLevel(Level.Info, "info") + case "warn" => setLevel(Level.Warn, "warn") + case cmd => + if (cmd.nonEmpty) appendExec(cmd, None) + else false + } + private[sbt] def onFastTrackTask(cmd: String): Boolean = { fastTrack.synchronized(fastTrack.forEach { q => - q.add(new FastTrackTask(this, s)) + q.add(new FastTrackTask(this, cmd)) () }) true diff --git a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala index 45ec9b52c7..3eea88c11a 100644 --- a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala +++ b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala @@ -23,8 +23,16 @@ import java.text.DateFormat import sbt.BasicCommandStrings.{ DashDashDetachStdio, DashDashServer, Shutdown, TerminateAction } import sbt.internal.client.NetworkClient.Arguments import sbt.internal.langserver.{ LogMessageParams, MessageType, PublishDiagnosticsParams } +import sbt.internal.worker.{ ClientJobParams, JvmRunInfo, NativeRunInfo, RunInfo } import sbt.internal.protocol._ -import sbt.internal.util.{ ConsoleAppender, ConsoleOut, Signals, Terminal, Util } +import sbt.internal.util.{ + ConsoleAppender, + ConsoleOut, + MessageOnlyException, + Signals, + Terminal, + Util +} import sbt.io.IO import sbt.io.syntax._ import sbt.protocol._ @@ -43,6 +51,7 @@ import Serialization.{ attach, cancelReadSystemIn, cancelRequest, + clientJob, promptChannel, readSystemIn, systemIn, @@ -63,6 +72,7 @@ import Serialization.{ } import NetworkClient.Arguments import java.util.concurrent.TimeoutException +import sbt.util.Logger trait ConsoleInterface { def appendLog(level: Level.Value, message: => String): Unit @@ -166,6 +176,11 @@ class NetworkClient( case null => inputThread.set(new RawInputThread) case _ => } + private lazy val log: Logger = new Logger { + def trace(t: => Throwable): Unit = () + def success(message: => String): Unit = () + def log(level: Level.Value, message: => String): Unit = console.appendLog(level, message) + } private[sbt] def connectOrStartServerAndConnect( promptCompleteUsers: Boolean, @@ -295,7 +310,18 @@ class NetworkClient( } // initiate handshake val execId = UUID.randomUUID.toString - val initCommand = InitCommand(tkn, Option(execId), Some(true)) + val skipAnalysis = true + val opts = InitializeOption( + token = tkn, + skipAnalysis = Some(skipAnalysis), + canWork = Some(true), + ) + val initCommand = InitCommand( + token = tkn, // duplicated with opts for compatibility + execId = Option(execId), + skipAnalysis = Some(skipAnalysis), // duplicated with opts for compatibility + initializationOptions = Some(opts), + ) conn.sendString(Serialization.serializeCommandAsJsonMessage(initCommand)) connectionHolder.set(conn) conn @@ -529,61 +555,77 @@ class NetworkClient( .getOrElse(1) case _ => 1 } - private def completeExec(execId: String, exitCode: => Int): Unit = + + private val onAttachResponse: PartialFunction[JsonRpcResponseMessage, Unit] = { + case msg if attachUUID.get == msg.id => + attachUUID.set(null) + attached.set(true) + Option(inputThread.get).foreach(_.drain()) + () + } + def completeExec(execId: String, exitCode: Int) = { pendingResults.remove(execId) match { - case null => + case null => () case (q, startTime, name) => val now = System.currentTimeMillis val message = NetworkClient.timing(startTime, now) - val ec = exitCode if (batchMode.get || !attached.get) { - if (ec == 0) console.success(message) + if (exitCode == 0) console.success(message) else console.appendLog(Level.Error, message) } - Util.ignoreResult(q.offer(ec)) - } - def onResponse(msg: JsonRpcResponseMessage): Unit = { - completeExec(msg.id, getExitCode(msg.result)) - pendingCancellations.remove(msg.id) match { - case null => - case q => q.offer(msg.toString.contains("Task cancelled")) - } - msg.id match { - case execId => - if (attachUUID.get == msg.id) { - attachUUID.set(null) - attached.set(true) - Option(inputThread.get).foreach(_.drain()) - } - pendingCompletions.remove(execId) match { - case null => - case completions => - completions(msg.result match { - case Some(o: JObject) => - o.value - .foldLeft(CompletionResponse(Vector.empty[String])) { - case (resp, i) => - if (i.field == "items") - resp.withItems( - Converter - .fromJson[Vector[String]](i.value) - .getOrElse(Vector.empty[String]) - ) - else if (i.field == "cachedTestNames") - resp.withCachedTestNames( - Converter.fromJson[Boolean](i.value).getOrElse(true) - ) - else if (i.field == "cachedMainClassNames") - resp.withCachedMainClassNames( - Converter.fromJson[Boolean](i.value).getOrElse(true) - ) - else resp - } - case _ => CompletionResponse(Vector.empty[String]) - }) - } + Util.ignoreResult(q.offer(exitCode)) } } + private val onExecResponse: PartialFunction[JsonRpcResponseMessage, Unit] = { + case msg if pendingResults.containsKey(msg.id) => + completeExec(msg.id, getExitCode(msg.result)) + } + private val onCancellationResponse: PartialFunction[JsonRpcResponseMessage, Unit] = { + case msg if pendingCancellations.containsKey(msg.id) => + pendingCancellations.remove(msg.id) match { + case null => () + case q => Util.ignoreResult(q.offer(msg.toString.contains("Task cancelled"))) + } + } + private val onCompletionResponse: PartialFunction[JsonRpcResponseMessage, Unit] = { + case msg if pendingCompletions.containsKey(msg.id) => + pendingCompletions.remove(msg.id) match { + case null => () + case completions => + completions(msg.result match { + case Some(o: JObject) => + o.value + .foldLeft(CompletionResponse(Vector.empty[String])) { + case (resp, i) => + if (i.field == "items") + resp.withItems( + Converter + .fromJson[Vector[String]](i.value) + .getOrElse(Vector.empty[String]) + ) + else if (i.field == "cachedTestNames") + resp.withCachedTestNames( + Converter.fromJson[Boolean](i.value).getOrElse(true) + ) + else if (i.field == "cachedMainClassNames") + resp.withCachedMainClassNames( + Converter.fromJson[Boolean](i.value).getOrElse(true) + ) + else resp + } + case _ => CompletionResponse(Vector.empty[String]) + }) + } + } + // cache the composed plan + private val responsePlan = Util.reduceIntents[JsonRpcResponseMessage, Unit]( + onExecResponse, + onCancellationResponse, + onAttachResponse, + onCompletionResponse, + { case _ => () }, + ) + def onResponse(msg: JsonRpcResponseMessage): Unit = responsePlan(msg) def onNotification(msg: JsonRpcNotificationMessage): Unit = { def splitToMessage: Vector[(Level.Value, String)] = @@ -625,6 +667,12 @@ class NetworkClient( case Success(params) => splitDiagnostics(params); Vector() case Failure(_) => Vector() } + case (`clientJob`, Some(json)) => + import sbt.internal.worker.codec.JsonProtocol._ + Converter.fromJson[ClientJobParams](json) match { + case Success(params) => clientSideRun(params).get; Vector.empty + case Failure(_) => Vector.empty + } case (`Shutdown`, Some(_)) => Vector.empty case (msg, _) if msg.startsWith("build/") => Vector.empty case _ => @@ -671,6 +719,58 @@ class NetworkClient( } } + private def clientSideRun(params: ClientJobParams): Try[Unit] = + params.runInfo match { + case Some(info) => clientSideRun(info) + case _ => Failure(new MessageOnlyException(s"runInfo is not specified in $params")) + } + + private def clientSideRun(runInfo: RunInfo): Try[Unit] = { + def jvmRun(info: JvmRunInfo): Try[Unit] = { + val option = ForkOptions( + javaHome = info.javaHome.map(new File(_)), + outputStrategy = None, // TODO: Handle buffered output etc + bootJars = Vector.empty, + workingDirectory = info.workingDirectory.map(new File(_)), + runJVMOptions = info.jvmOptions, + connectInput = info.connectInput, + envVars = info.environmentVariables, + ) + // ForkRun handles exit code handling and cancellation + val runner = new ForkRun(option) + runner + .run( + mainClass = info.mainClass, + classpath = info.classpath.map(_.path).map(new File(_)), + options = info.args, + log = log + ) + } + def nativeRun(info: NativeRunInfo): Try[Unit] = { + import java.lang.{ ProcessBuilder => JProcessBuilder } + val option = ForkOptions( + javaHome = None, + outputStrategy = None, // TODO: Handle buffered output etc + bootJars = Vector.empty, + workingDirectory = info.workingDirectory.map(new File(_)), + runJVMOptions = Vector.empty, + connectInput = info.connectInput, + envVars = info.environmentVariables, + ) + val command = info.cmd :: info.args.toList + val jpb = new JProcessBuilder(command: _*) + val exitCode = try Fork.blockForExitCode(Fork.forkInternal(option, Nil, jpb)) + catch { + case _: InterruptedException => + log.warn("run canceled") + 1 + } + Run.processExitCode(exitCode, "runner") + } + if (runInfo.jvm) jvmRun(runInfo.jvmRunInfo.getOrElse(sys.error("missing jvmRunInfo"))) + else nativeRun(runInfo.nativeRunInfo.getOrElse(sys.error("missing nativeRunInfo"))) + } + def onRequest(msg: JsonRpcRequestMessage): Unit = { import sbt.protocol.codec.JsonProtocol._ (msg.method, msg.params) match { @@ -1130,13 +1230,13 @@ object NetworkClient { val totalString = s"$total s" + (if (total <= 60) "" else { - val maybeHours = total / 3600 match { - case 0 => "" - case h => f"$h%02d:" + val hours = total / 3600 match { + case 0 => "0" + case h => f"$h%02d" } val mins = f"${total % 3600 / 60}%02d" val secs = f"${total % 60}%02d" - s" ($maybeHours$mins:$secs)" + s" ($hours:$mins:$secs.0)" }) s"Total time: $totalString, completed $nowString" } diff --git a/main-command/src/main/scala/sbt/internal/server/ServerHandler.scala b/main-command/src/main/scala/sbt/internal/server/ServerHandler.scala index b503e503c0..9a20f82532 100644 --- a/main-command/src/main/scala/sbt/internal/server/ServerHandler.scala +++ b/main-command/src/main/scala/sbt/internal/server/ServerHandler.scala @@ -79,6 +79,7 @@ trait ServerCallback { private[sbt] def authOptions: Set[ServerAuthentication] private[sbt] def authenticate(token: String): Boolean private[sbt] def setInitialized(value: Boolean): Unit + private[sbt] def setInitializeOption(opts: InitializeOption): Unit private[sbt] def onSettingQuery(execId: Option[String], req: Q): Unit private[sbt] def onCompletionRequest(execId: Option[String], cp: CP): Unit private[sbt] def onCancellationRequest(execId: Option[String], crp: CRP): Unit diff --git a/main-command/src/main/scala/sbt/internal/ui/UITask.scala b/main-command/src/main/scala/sbt/internal/ui/UITask.scala index 6e9fcd1bed..b4ceb047dd 100644 --- a/main-command/src/main/scala/sbt/internal/ui/UITask.scala +++ b/main-command/src/main/scala/sbt/internal/ui/UITask.scala @@ -28,7 +28,7 @@ private[sbt] trait UITask extends Runnable with AutoCloseable { private[sbt] val reader: UITask.Reader private[this] final def handleInput(s: Either[String, String]): Boolean = s match { case Left(m) => channel.onFastTrackTask(m) - case Right(cmd) => channel.onCommand(cmd) + case Right(cmd) => channel.onCommandLine(cmd) } private[this] val isStopped = new AtomicBoolean(false) override def run(): Unit = { @@ -56,6 +56,20 @@ private[sbt] object UITask { object Reader { // Avoid filling the stack trace since it isn't helpful here object interrupted extends InterruptedException + + /** + * Return Left for fast track commands, otherwise return Right(...). + */ + def splitCommand(cmd: String): Either[String, String] = + // We need to put the empty string on the fast track queue so that we can + // reprompt the user if another command is running on the server. + if (cmd.isEmpty()) Left("") + else + cmd match { + case Shutdown | TerminateAction | Cancel => Left(cmd) + case cmd => Right(cmd) + } + def terminalReader(parser: Parser[_])( terminal: Terminal, state: State @@ -78,15 +92,8 @@ private[sbt] object UITask { Right("") // should be unreachable // JLine returns null on ctrl+d when there is no other input. This interprets // ctrl+d with no imput as an exit - case None => Left(TerminateAction) - case Some(s: String) => - s.trim() match { - // We need to put the empty string on the fast track queue so that we can - // reprompt the user if another command is running on the server. - case "" => Left("") - case cmd @ (`Shutdown` | `TerminateAction` | `Cancel`) => Left(cmd) - case cmd => Right(cmd) - } + case None => Left(TerminateAction) + case Some(s: String) => splitCommand(s.trim()) } } terminal.setPrompt(Prompt.Pending) diff --git a/main/src/main/scala/sbt/BackgroundJobService.scala b/main/src/main/scala/sbt/BackgroundJobService.scala index 62aaec76d6..2b113c1c1f 100644 --- a/main/src/main/scala/sbt/BackgroundJobService.scala +++ b/main/src/main/scala/sbt/BackgroundJobService.scala @@ -70,6 +70,8 @@ abstract class BackgroundJobService extends Closeable { def waitFor(job: JobHandle): Unit + private[sbt] def createWorkingDirectory: File + /** Copies classpath to temporary directories. */ def copyClasspath(products: Classpath, full: Classpath, workingDirectory: File): Classpath diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 942fccd555..0b54ba2e5f 100644 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -9,8 +9,7 @@ package sbt import java.io.{ File, PrintWriter } -import java.net.{ URI, URL } -import java.nio.file.{ Paths, Path => NioPath } +import java.nio.file.{ Path => NioPath } import java.util.Optional import java.util.concurrent.TimeUnit import lmcoursier.CoursierDependencyResolution @@ -51,6 +50,7 @@ import sbt.internal.server.{ BspCompileTask, BuildServerProtocol, BuildServerReporter, + ClientJob, Definition, LanguageServerProtocol, ServerHandler, @@ -180,6 +180,7 @@ object Defaults extends BuildCommon { apiMappings := Map.empty, autoScalaLibrary :== true, managedScalaInstance :== true, + allowUnsafeScalaLibUpgrade :== false, classpathEntryDefinesClass := { (file: File) => sys.error("use classpathEntryDefinesClassVF instead") }, @@ -221,7 +222,7 @@ object Defaults extends BuildCommon { closeClassLoaders :== SysProp.closeClassLoaders, allowZombieClassLoaders :== true, packageTimestamp :== Package.defaultTimestamp, - ) ++ BuildServerProtocol.globalSettings + ) ++ BuildServerProtocol.globalSettings ++ ClientJob.globalSettings private[sbt] lazy val globalIvyCore: Seq[Setting[_]] = Seq( @@ -406,13 +407,12 @@ object Defaults extends BuildCommon { val boot = app.provider.scalaProvider.launcher.bootDirectory val ih = app.provider.scalaProvider.launcher.ivyHome val coursierCache = csrCacheDirectory.value - val javaHome = Paths.get(sys.props("java.home")) Map( "BASE" -> base.toPath, "SBT_BOOT" -> boot.toPath, "CSR_CACHE" -> coursierCache.toPath, "IVY_HOME" -> ih.toPath, - "JAVA_HOME" -> javaHome, + "JAVA_HOME" -> Util.javaHome, ) }, fileConverter := MappedFileConverter(rootPaths.value, allowMachinePath.value), @@ -928,7 +928,20 @@ object Defaults extends BuildCommon { tastyFiles.map(_.getAbsoluteFile) } else Nil }.value, - clean := (compileOutputs / clean).value, + clean := { + val _ = (compileOutputs / clean).value + val analysisFile = compileAnalysisFile.value + try { + val store = AnalysisUtil.staticCachedStore( + analysisFile = analysisFile.toPath, + useTextAnalysis = !enableBinaryCompileAnalysis.value, + useConsistent = enableConsistentCompileAnalysis.value, + ) + store.clearCache() + } catch { + case NonFatal(_) => () + } + }, earlyOutputPing := Def.promise[Boolean], compileProgress := { val s = streams.value @@ -1165,6 +1178,7 @@ object Defaults extends BuildCommon { def scalaInstanceFromUpdate: Initialize[Task[ScalaInstance]] = Def.task { val sv = scalaVersion.value val fullReport = update.value + val s = streams.value // For Scala 3, update scala-library.jar in `scala-tool` and `scala-doc-tool` in case a newer version // is present in the `compile` configuration. This is needed once forwards binary compatibility is dropped @@ -1191,24 +1205,38 @@ object Defaults extends BuildCommon { ) if (Classpaths.isScala213(sv)) { - for { - compileReport <- fullReport.configuration(Configurations.Compile) - libName <- ScalaArtifacts.Artifacts - } { - for (lib <- compileReport.modules.find(_.module.name == libName)) { - val libVer = lib.module.revision - val n = name.value - if (VersionNumber(sv).matchesSemVer(SemanticSelector(s"<$libVer"))) - sys.error( - s"""expected `$n/scalaVersion` to be "$libVer" or later, - |but found "$sv"; upgrade scalaVersion to fix the build. - | - |to support backwards-only binary compatibility (SIP-51), - |the Scala 2.13 compiler cannot be older than $libName on the - |dependency classpath. - |see `$n/evicted` to know why $libName $libVer is getting pulled in. - |""".stripMargin - ) + val scalaDeps = for { + compileReport <- fullReport.configuration(Configurations.Compile).iterator + libName <- ScalaArtifacts.Artifacts.iterator + lib <- compileReport.modules.find(_.module.name == libName) + } yield lib + for (lib <- scalaDeps.take(1)) { + val libVer = lib.module.revision + val libName = lib.module.name + val n = name.value + if (VersionNumber(sv).matchesSemVer(SemanticSelector(s"<$libVer"))) { + val err = !allowUnsafeScalaLibUpgrade.value + val fix = + if (err) + """Upgrade the `scalaVersion` to fix the build. If upgrading the Scala compiler version is + |not possible (for example due to a regression in the compiler or a missing dependency), + |this error can be demoted by setting `allowUnsafeScalaLibUpgrade := true`.""".stripMargin + else + s"""Note that the dependency classpath and the runtime classpath of your project + |contain the newer $libName $libVer, even if the scalaVersion is $sv. + |Compilation (macro expansion) or using the Scala REPL in sbt may fail with a LinkageError.""".stripMargin + + val msg = + s"""Expected `$n/scalaVersion` to be $libVer or later, but found $sv. + |To support backwards-only binary compatibility (SIP-51), the Scala 2.13 compiler + |should not be older than $libName on the dependency classpath. + | + |$fix + | + |See `$n/evicted` to know why $libName $libVer is getting pulled in. + |""".stripMargin + if (err) sys.error(msg) + else s.log.warn(msg) } } } @@ -2567,9 +2595,10 @@ object Defaults extends BuildCommon { private[sbt] def jnone[A]: Optional[A] = none[A].toOptional def compileAnalysisSettings: Seq[Setting[_]] = Seq( previousCompile := { - val setup = compileIncSetup.value + // Avoid compileIncSetup since it would trigger upstream compilation + val analysisFile = compileAnalysisFile.value val store = AnalysisUtil.staticCachedStore( - analysisFile = setup.cacheFile.toPath, + analysisFile = analysisFile.toPath, useTextAnalysis = !enableBinaryCompileAnalysis.value, useConsistent = enableConsistentCompileAnalysis.value, ) @@ -2687,7 +2716,7 @@ object Defaults extends BuildCommon { lazy val configSettings: Seq[Setting[_]] = Classpaths.configSettings ++ configTasks ++ configPaths ++ packageConfig ++ Classpaths.compilerPluginConfig ++ deprecationSettings ++ - BuildServerProtocol.configSettings + BuildServerProtocol.configSettings ++ ClientJob.configSettings lazy val compileSettings: Seq[Setting[_]] = configSettings ++ (mainBgRunMainTask +: mainBgRunTask) ++ Classpaths.addUnmanagedLibrary diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index 9349773d7f..dd290c3db7 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -29,6 +29,7 @@ import sbt.internal.remotecache.RemoteCacheArtifact import sbt.internal.server.BuildServerProtocol.BspFullWorkspace import sbt.internal.server.{ BuildServerReporter, ServerHandler } import sbt.internal.util.{ AttributeKey, ProgressState, SourcePosition } +import sbt.internal.worker.ClientJobParams import sbt.io._ import sbt.librarymanagement.Configurations.CompilerPlugin import sbt.librarymanagement.LibraryManagementCodec._ @@ -437,6 +438,8 @@ object Keys { val bspScalaMainClasses = inputKey[Unit]("Implementation of buildTarget/scalaMainClasses").withRank(DTask) val bspScalaMainClassesItem = taskKey[ScalaMainClassesItem]("").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 useCoursier = settingKey[Boolean]("Use Coursier for dependency resolution.").withRank(BSetting) val csrCacheDirectory = settingKey[File]("Coursier cache directory. Uses -Dsbt.coursier.home or Coursier's default.").withRank(CSetting) @@ -571,6 +574,7 @@ object Keys { val conflictManager = settingKey[ConflictManager]("Selects the conflict manager to use for dependency management.").withRank(CSetting) val autoScalaLibrary = settingKey[Boolean]("Adds a dependency on scala-library if true.").withRank(ASetting) val managedScalaInstance = settingKey[Boolean]("Automatically obtains Scala tools as managed dependencies if true.").withRank(BSetting) + val allowUnsafeScalaLibUpgrade = settingKey[Boolean]("Allow the Scala library on the compilation classpath to be newer than the scalaVersion (see Scala SIP-51).").withRank(CSetting) val sbtResolver = settingKey[Resolver]("Provides a resolver for obtaining sbt as a dependency.").withRank(BMinusSetting) val sbtResolvers = settingKey[Seq[Resolver]]("The external resolvers for sbt and plugin dependencies.").withRank(BMinusSetting) val sbtDependency = settingKey[ModuleID]("Provides a definition for declaring the current version of sbt.").withRank(BMinusSetting) diff --git a/main/src/main/scala/sbt/internal/AbstractTaskProgress.scala b/main/src/main/scala/sbt/internal/AbstractTaskProgress.scala index aabeb84014..bce7bb2baa 100644 --- a/main/src/main/scala/sbt/internal/AbstractTaskProgress.scala +++ b/main/src/main/scala/sbt/internal/AbstractTaskProgress.scala @@ -9,6 +9,7 @@ package sbt package internal +import sbt.internal.util.Util import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicLong import scala.collection.JavaConverters._ @@ -123,6 +124,7 @@ object AbstractTaskExecuteProgress { private[sbt] class Timer() { val startNanos: Long = System.nanoTime() val threadName: String = Thread.currentThread().getName + val threadId: Long = Util.threadId var endNanos: Long = 0L def stop(): Unit = { endNanos = System.nanoTime() diff --git a/main/src/main/scala/sbt/internal/CrossJava.scala b/main/src/main/scala/sbt/internal/CrossJava.scala index 38765232c0..6761b30f1a 100644 --- a/main/src/main/scala/sbt/internal/CrossJava.scala +++ b/main/src/main/scala/sbt/internal/CrossJava.scala @@ -412,13 +412,19 @@ private[sbt] object CrossJava { class MacOsDiscoverConfig extends JavaDiscoverConf { val base: File = file("/Library") / "Java" / "JavaVirtualMachines" + // User-specific JDKs are installed, for example, by IntelliJ IDEA + private val baseInUserHome: File = Path.userHome / "Library" / "Java" / "JavaVirtualMachines" def javaHomes: Vector[(String, File)] = - wrapNull(base.list()) - .collect { - case dir @ JavaHomeDir(version) => - version -> (base / dir / "Contents" / "Home") - } + findAllHomes(base) ++ + findAllHomes(baseInUserHome) + + private def findAllHomes(root: File): Vector[(String, File)] = { + wrapNull(root.list()).collect { + case dir @ JavaHomeDir(version) => + version -> (root / dir / "Contents" / "Home") + } + } } class JabbaDiscoverConfig extends JavaDiscoverConf { diff --git a/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala b/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala index 218cb32893..232204d467 100644 --- a/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala +++ b/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala @@ -144,6 +144,16 @@ private[sbt] abstract class AbstractBackgroundJobService extends BackgroundJobSe override val isAutoCancel = false } + private[sbt] def createWorkingDirectory: File = { + val id = nextId.getAndIncrement() + createWorkingDirectory(id) + } + private[sbt] def createWorkingDirectory(id: Long): File = { + val workingDir = serviceTempDir / s"job-$id" + IO.createDirectory(workingDir) + workingDir + } + def doRunInBackground( spawningTask: ScopedKey[_], state: State, @@ -153,8 +163,7 @@ private[sbt] abstract class AbstractBackgroundJobService extends BackgroundJobSe val extracted = Project.extract(state) val logger = LogManager.constructBackgroundLog(extracted.structure.data, state, context)(spawningTask) - val workingDir = serviceTempDir / s"job-$id" - IO.createDirectory(workingDir) + val workingDir = createWorkingDirectory(id) val job = try { new ThreadJobHandle(id, spawningTask, logger, workingDir, start(logger, workingDir)) } catch { diff --git a/main/src/main/scala/sbt/internal/LintUnused.scala b/main/src/main/scala/sbt/internal/LintUnused.scala index c2d2db0062..8df675c15b 100644 --- a/main/src/main/scala/sbt/internal/LintUnused.scala +++ b/main/src/main/scala/sbt/internal/LintUnused.scala @@ -34,6 +34,7 @@ object LintUnused { commands, crossScalaVersions, crossSbtVersions, + allowUnsafeScalaLibUpgrade, initialize, lintUnusedKeysOnLoad, onLoad, diff --git a/main/src/main/scala/sbt/internal/TaskTraceEvent.scala b/main/src/main/scala/sbt/internal/TaskTraceEvent.scala index 37f6ecdb6b..914ee84a19 100644 --- a/main/src/main/scala/sbt/internal/TaskTraceEvent.scala +++ b/main/src/main/scala/sbt/internal/TaskTraceEvent.scala @@ -61,7 +61,7 @@ private[sbt] final class TaskTraceEvent def durationEvent(name: String, cat: String, t: Timer): String = { val sb = new java.lang.StringBuilder(name.length + 2) CompactPrinter.print(new JString(name), sb) - s"""{"name": ${sb.toString}, "cat": "$cat", "ph": "X", "ts": ${(t.startMicros)}, "dur": ${(t.durationMicros)}, "pid": 0, "tname": "${t.threadName}"}""" + s"""{"name": ${sb.toString}, "cat": "$cat", "ph": "X", "ts": ${(t.startMicros)}, "dur": ${(t.durationMicros)}, "pid": 0, "tid": "${t.threadId}"}""" } val entryIterator = currentTimings while (entryIterator.hasNext) { diff --git a/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala b/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala index 88e07d77de..9bb371a78e 100644 --- a/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala +++ b/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala @@ -34,6 +34,7 @@ import sjsonnew.support.scalajson.unsafe.{ CompactPrinter, Converter, Parser => import xsbti.CompileFailed import java.io.File +import java.nio.file.Paths import java.util.concurrent.atomic.AtomicBoolean import scala.collection.mutable @@ -247,7 +248,7 @@ object BuildServerProtocol { state.respondEvent(result) } }.evaluated, - bspScalaMainClasses / aggregate := false + bspScalaMainClasses / aggregate := false, ) // This will be scoped to Compile, Test, IntegrationTest etc @@ -344,7 +345,7 @@ object BuildServerProtocol { } else { new BuildServerForwarder(meta, logger, underlying) } - } + }, ) private[sbt] object Method { final val Initialize = "build/initialize" @@ -614,12 +615,19 @@ object BuildServerProtocol { val thisProjectRef = Keys.thisProjectRef.value val thisConfig = Keys.configuration.value val scalaJars = Keys.scalaInstance.value.allJars.map(_.toURI.toString) + val (javaHomeForTarget, isForkedJava) = javaHome.value match { + case Some(forkedJava) => (Some(forkedJava.toURI), true) + case None => (sys.props.get("java.home").map(Paths.get(_)).map(_.toUri), false) + } + val javaVersionForTarget = extractJavaVersion(javacOptions.value, isForkedJava) + val jvmBuildTarget = JvmBuildTarget(javaHomeForTarget, javaVersionForTarget) val compileData = ScalaBuildTarget( scalaOrganization = scalaOrganization.value, scalaVersion = scalaVersion.value, scalaBinaryVersion = scalaBinaryVersion.value, platform = ScalaPlatform.JVM, - jars = scalaJars.toVector + jars = scalaJars.toVector, + jvmBuildTarget = jvmBuildTarget, ) val configuration = Keys.configuration.value val displayName = BuildTargetName.fromScope(thisProject.id, configuration.name) @@ -659,7 +667,11 @@ object BuildServerProtocol { scalaVersion = scalaProvider.version(), scalaBinaryVersion = binaryScalaVersion(scalaProvider.version()), platform = ScalaPlatform.JVM, - jars = scalaJars.toVector.map(_.toURI.toString) + jars = scalaJars.toVector.map(_.toURI.toString), + jvmBuildTarget = JvmBuildTarget( + sys.props.get("java.home").map(Paths.get(_)).map(_.toUri), + sys.props.get("java.version") + ), ) val sbtVersionValue = sbtVersion.value val sbtData = SbtBuildTarget( @@ -985,6 +997,26 @@ object BuildServerProtocol { ) } + private def extractJavaVersion( + javacOptions: Seq[String], + isForkedJava: Boolean + ): Option[String] = { + def getVersionAfterFlag(flag: String): Option[String] = { + val index = javacOptions.indexOf(flag) + if (index >= 0) javacOptions.lift(index + 1) + else None + } + + val versionFromJavacOption = getVersionAfterFlag("--release") + .orElse(getVersionAfterFlag("--target")) + .orElse(getVersionAfterFlag("-target")) + + versionFromJavacOption.orElse { + // TODO: extract java version from forked javac + if (isForkedJava) None else sys.props.get("java.version") + } + } + // naming convention still seems like the only reliable way to get IntelliJ to import this correctly // https://github.com/JetBrains/intellij-scala/blob/a54c2a7c157236f35957049cbfd8c10587c9e60c/scala/scala-impl/src/org/jetbrains/sbt/language/SbtFileImpl.scala#L82-L84 private def toSbtTargetIdName(ref: LoadedBuildUnit): String = { diff --git a/main/src/main/scala/sbt/internal/server/ClientJob.scala b/main/src/main/scala/sbt/internal/server/ClientJob.scala new file mode 100644 index 0000000000..e016b98f00 --- /dev/null +++ b/main/src/main/scala/sbt/internal/server/ClientJob.scala @@ -0,0 +1,94 @@ +/* + * 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.BuildSyntax._ +import sbt.Def._ +import sbt.Keys._ +import sbt.SlashSyntax0._ +import sbt.internal.util.complete.Parser +import sbt.internal.worker.{ ClientJobParams, FilePath, JvmRunInfo, RunInfo } +import sbt.io.IO +import sbt.protocol.Serialization + +/** + * 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.in(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 cp = service.copyClasspath( + exportedProductJars.value, + fullClasspathAsJars.value, + workingDir, + hashContents = true, + ) + 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(x.data)).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 + } +} diff --git a/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala b/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala index 2bf2878bda..5ea1918128 100644 --- a/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala +++ b/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala @@ -62,6 +62,7 @@ private[sbt] object LanguageServerProtocol { else throw LangServerError(ErrorCodes.InvalidRequest, "invalid token") } else () setInitialized(true) + setInitializeOption(opt) if (!opt.skipAnalysis.getOrElse(false)) appendExec("collectAnalyses", None) jsonRpcRespond(InitializeResult(serverCapabilities), Some(r.id)) diff --git a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala index 95c0e16c0d..36ace91f14 100644 --- a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala +++ b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala @@ -24,6 +24,7 @@ import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference } import sbt.BasicCommandStrings.{ Shutdown, TerminateAction } import sbt.internal.langserver.{ CancelRequestParams, ErrorCodes, LogMessageParams, MessageType } import sbt.internal.protocol.{ + InitializeOption, JsonRpcNotificationMessage, JsonRpcRequestMessage, JsonRpcResponseError, @@ -83,6 +84,10 @@ final class NetworkChannel( private val delimiter: Byte = '\n'.toByte private val out = connection.getOutputStream private var initialized = false + + /** Reference to the client-side custom options + */ + private val initializeOption = new AtomicReference[InitializeOption](null) private val pendingRequests: mutable.Map[String, JsonRpcRequestMessage] = mutable.Map() private[this] val inputBuffer = new LinkedBlockingQueue[Int]() @@ -124,7 +129,7 @@ final class NetworkChannel( self.jsonRpcNotify(method, params) def appendExec(commandLine: String, execId: Option[String]): Boolean = - self.append(Exec(commandLine, execId, Some(CommandSource(name)))) + self.appendExec(commandLine, execId) def appendExec(exec: Exec): Boolean = self.append(exec) @@ -133,6 +138,8 @@ final class NetworkChannel( private[sbt] def authOptions: Set[ServerAuthentication] = self.authOptions private[sbt] def authenticate(token: String): Boolean = self.authenticate(token) private[sbt] def setInitialized(value: Boolean): Unit = self.setInitialized(value) + private[sbt] def setInitializeOption(opts: InitializeOption): Unit = + self.setInitializeOption(opts) private[sbt] def onSettingQuery(execId: Option[String], req: SettingQuery): Unit = self.onSettingQuery(execId, req) private[sbt] def onCompletionRequest(execId: Option[String], cp: CompletionParams): Unit = @@ -141,6 +148,15 @@ final class NetworkChannel( self.onCancellationRequest(execId, crp) } + protected def setInitializeOption(opts: InitializeOption): Unit = initializeOption.set(opts) + + // Returns true if sbtn has declared with canWork: true + protected def clientCanWork: Boolean = + Option(initializeOption.get) match { + case Some(opts) => opts.canWork.getOrElse(false) + case _ => false + } + protected def authenticate(token: String): Boolean = instance.authenticate(token) protected def setInitialized(value: Boolean): Unit = initialized = value @@ -163,7 +179,7 @@ final class NetworkChannel( } } - val thread = new Thread(s"sbt-networkchannel-${connection.getPort}") { + private[this] val thread = new Thread(s"sbt-networkchannel-${connection.getPort}") { private val ct = "Content-Type: " private val x1 = "application/sbt-x1" override def run(): Unit = { @@ -241,7 +257,6 @@ final class NetworkChannel( } } } - thread.start() private[sbt] def isLanguageServerProtocol: Boolean = true @@ -365,46 +380,11 @@ final class NetworkChannel( impl() }, s"sbt-$name-write-thread") writeThread.setDaemon(true) - writeThread.start() def publishBytes(event: Array[Byte], delimit: Boolean): Unit = try pendingWrites.put(event -> delimit) catch { case _: InterruptedException => } - def onCommand(command: CommandMessage): Unit = command match { - case x: InitCommand => onInitCommand(x) - case x: ExecCommand => onExecCommand(x) - case x: SettingQuery => onSettingQuery(None, x) - } - - private def onInitCommand(cmd: InitCommand): Unit = { - if (auth(ServerAuthentication.Token)) { - cmd.token match { - case Some(x) => - authenticate(x) match { - case true => - initialized = true - notifyEvent(ChannelAcceptedEvent(name)) - case _ => sys.error("invalid token") - } - case None => sys.error("init command but without token.") - } - } else { - initialized = true - } - } - - private def onExecCommand(cmd: ExecCommand) = { - if (initialized) { - append( - Exec(cmd.commandLine, cmd.execId orElse Some(Exec.newExecId), Some(CommandSource(name))) - ) - () - } else { - log.warn(s"ignoring command $cmd before initialization") - } - } - protected def onSettingQuery(execId: Option[String], req: SettingQuery) = { if (initialized) { StandardMain.exchange.withState { s => @@ -914,6 +894,9 @@ final class NetworkChannel( } } private[sbt] def isAttached: Boolean = attached.get + + thread.start() + writeThread.start() } object NetworkChannel { diff --git a/main/src/test/scala/sbt/internal/AggregationSpec.scala b/main/src/test/scala/sbt/internal/AggregationSpec.scala index 8ac2d8f8a4..b6cbdb681e 100644 --- a/main/src/test/scala/sbt/internal/AggregationSpec.scala +++ b/main/src/test/scala/sbt/internal/AggregationSpec.scala @@ -12,15 +12,15 @@ object AggregationSpec extends verify.BasicTestSuite { val timing = Aggregation.timing(Aggregation.defaultFormat, 0, _: Long) test("timing should format total time properly") { - assert(timing(101).startsWith("Total time: 0 s,")) - assert(timing(1000).startsWith("Total time: 1 s,")) - assert(timing(3000).startsWith("Total time: 3 s,")) - assert(timing(30399).startsWith("Total time: 30 s,")) - assert(timing(60399).startsWith("Total time: 60 s,")) - assert(timing(60699).startsWith("Total time: 61 s (01:01),")) - assert(timing(303099).startsWith("Total time: 303 s (05:03),")) - assert(timing(6003099).startsWith("Total time: 6003 s (01:40:03),")) - assert(timing(96003099).startsWith("Total time: 96003 s (26:40:03),")) + assert(timing(101).startsWith("Total time: 0 s")) + assert(timing(1000).startsWith("Total time: 1 s")) + assert(timing(3000).startsWith("Total time: 3 s")) + assert(timing(30399).startsWith("Total time: 30 s")) + assert(timing(60399).startsWith("Total time: 60 s")) + assert(timing(60699).startsWith("Total time: 61 s (0:01:01.0)")) + assert(timing(303099).startsWith("Total time: 303 s (0:05:03.0)")) + assert(timing(6003099).startsWith("Total time: 6003 s (01:40:03.0)")) + assert(timing(96003099).startsWith("Total time: 96003 s (26:40:03.0)")) } test("timing should not emit special space characters") { diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 9e4b893be7..8e040b7e50 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -5,17 +5,17 @@ import sbt.contraband.ContrabandPlugin.autoImport._ object Dependencies { // WARNING: Please Scala update versions in PluginCross.scala too val scala212 = "2.12.20" - val scala213 = "2.13.15" + val scala213 = "2.13.16" val checkPluginCross = settingKey[Unit]("Make sure scalaVersion match up") val baseScalaVersion = scala212 def nightlyVersion: Option[String] = sys.env.get("BUILD_VERSION") orElse sys.props.get("sbt.build.version") // sbt modules - private val ioVersion = nightlyVersion.getOrElse("1.10.3") + private val ioVersion = nightlyVersion.getOrElse("1.10.4") private val lmVersion = - sys.props.get("sbt.build.lm.version").orElse(nightlyVersion).getOrElse("1.10.3") - val zincVersion = nightlyVersion.getOrElse("1.10.7") + sys.props.get("sbt.build.lm.version").orElse(nightlyVersion).getOrElse("1.10.4") + val zincVersion = nightlyVersion.getOrElse("1.10.8") private val sbtIO = "org.scala-sbt" %% "io" % ioVersion diff --git a/project/build.properties b/project/build.properties index e88a0d817d..73df629ac1 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.10.6 +sbt.version=1.10.7 diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/JvmBuildTarget.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/JvmBuildTarget.scala new file mode 100644 index 0000000000..488b8b8e68 --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/JvmBuildTarget.scala @@ -0,0 +1,48 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.bsp +/** + * Contains jvm-specific metadata, specifically JDK reference + * @param javaHome Uri representing absolute path to jdk + * @param javaVersion The java version this target is supposed to use (can be set using javac `-target` flag) + */ +final class JvmBuildTarget private ( + val javaHome: Option[java.net.URI], + val javaVersion: Option[String]) extends Serializable { + + + + override def equals(o: Any): Boolean = this.eq(o.asInstanceOf[AnyRef]) || (o match { + case x: JvmBuildTarget => (this.javaHome == x.javaHome) && (this.javaVersion == x.javaVersion) + case _ => false + }) + override def hashCode: Int = { + 37 * (37 * (37 * (17 + "sbt.internal.bsp.JvmBuildTarget".##) + javaHome.##) + javaVersion.##) + } + override def toString: String = { + "JvmBuildTarget(" + javaHome + ", " + javaVersion + ")" + } + private[this] def copy(javaHome: Option[java.net.URI] = javaHome, javaVersion: Option[String] = javaVersion): JvmBuildTarget = { + new JvmBuildTarget(javaHome, javaVersion) + } + def withJavaHome(javaHome: Option[java.net.URI]): JvmBuildTarget = { + copy(javaHome = javaHome) + } + def withJavaHome(javaHome: java.net.URI): JvmBuildTarget = { + copy(javaHome = Option(javaHome)) + } + def withJavaVersion(javaVersion: Option[String]): JvmBuildTarget = { + copy(javaVersion = javaVersion) + } + def withJavaVersion(javaVersion: String): JvmBuildTarget = { + copy(javaVersion = Option(javaVersion)) + } +} +object JvmBuildTarget { + + def apply(javaHome: Option[java.net.URI], javaVersion: Option[String]): JvmBuildTarget = new JvmBuildTarget(javaHome, javaVersion) + def apply(javaHome: java.net.URI, javaVersion: String): JvmBuildTarget = new JvmBuildTarget(Option(javaHome), Option(javaVersion)) +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/ScalaBuildTarget.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/ScalaBuildTarget.scala index 32e5885a91..e686d84dd5 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/bsp/ScalaBuildTarget.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/ScalaBuildTarget.scala @@ -14,28 +14,30 @@ package sbt.internal.bsp For example, 2.12 if scalaVersion is 2.12.4. * @param platform The target platform for this target * @param jars A sequence of Scala jars such as scala-library, scala-compiler and scala-reflect. + * @param jvmBuildTarget The jvm build target describing jdk to be used */ final class ScalaBuildTarget private ( val scalaOrganization: String, val scalaVersion: String, val scalaBinaryVersion: String, val platform: Int, - val jars: Vector[String]) extends Serializable { + val jars: Vector[String], + val jvmBuildTarget: Option[sbt.internal.bsp.JvmBuildTarget]) extends Serializable { override def equals(o: Any): Boolean = this.eq(o.asInstanceOf[AnyRef]) || (o match { - case x: ScalaBuildTarget => (this.scalaOrganization == x.scalaOrganization) && (this.scalaVersion == x.scalaVersion) && (this.scalaBinaryVersion == x.scalaBinaryVersion) && (this.platform == x.platform) && (this.jars == x.jars) + case x: ScalaBuildTarget => (this.scalaOrganization == x.scalaOrganization) && (this.scalaVersion == x.scalaVersion) && (this.scalaBinaryVersion == x.scalaBinaryVersion) && (this.platform == x.platform) && (this.jars == x.jars) && (this.jvmBuildTarget == x.jvmBuildTarget) case _ => false }) override def hashCode: Int = { - 37 * (37 * (37 * (37 * (37 * (37 * (17 + "sbt.internal.bsp.ScalaBuildTarget".##) + scalaOrganization.##) + scalaVersion.##) + scalaBinaryVersion.##) + platform.##) + jars.##) + 37 * (37 * (37 * (37 * (37 * (37 * (37 * (17 + "sbt.internal.bsp.ScalaBuildTarget".##) + scalaOrganization.##) + scalaVersion.##) + scalaBinaryVersion.##) + platform.##) + jars.##) + jvmBuildTarget.##) } override def toString: String = { - "ScalaBuildTarget(" + scalaOrganization + ", " + scalaVersion + ", " + scalaBinaryVersion + ", " + platform + ", " + jars + ")" + "ScalaBuildTarget(" + scalaOrganization + ", " + scalaVersion + ", " + scalaBinaryVersion + ", " + platform + ", " + jars + ", " + jvmBuildTarget + ")" } - private[this] def copy(scalaOrganization: String = scalaOrganization, scalaVersion: String = scalaVersion, scalaBinaryVersion: String = scalaBinaryVersion, platform: Int = platform, jars: Vector[String] = jars): ScalaBuildTarget = { - new ScalaBuildTarget(scalaOrganization, scalaVersion, scalaBinaryVersion, platform, jars) + private[this] def copy(scalaOrganization: String = scalaOrganization, scalaVersion: String = scalaVersion, scalaBinaryVersion: String = scalaBinaryVersion, platform: Int = platform, jars: Vector[String] = jars, jvmBuildTarget: Option[sbt.internal.bsp.JvmBuildTarget] = jvmBuildTarget): ScalaBuildTarget = { + new ScalaBuildTarget(scalaOrganization, scalaVersion, scalaBinaryVersion, platform, jars, jvmBuildTarget) } def withScalaOrganization(scalaOrganization: String): ScalaBuildTarget = { copy(scalaOrganization = scalaOrganization) @@ -52,8 +54,15 @@ final class ScalaBuildTarget private ( def withJars(jars: Vector[String]): ScalaBuildTarget = { copy(jars = jars) } + def withJvmBuildTarget(jvmBuildTarget: Option[sbt.internal.bsp.JvmBuildTarget]): ScalaBuildTarget = { + copy(jvmBuildTarget = jvmBuildTarget) + } + def withJvmBuildTarget(jvmBuildTarget: sbt.internal.bsp.JvmBuildTarget): ScalaBuildTarget = { + copy(jvmBuildTarget = Option(jvmBuildTarget)) + } } object ScalaBuildTarget { - def apply(scalaOrganization: String, scalaVersion: String, scalaBinaryVersion: String, platform: Int, jars: Vector[String]): ScalaBuildTarget = new ScalaBuildTarget(scalaOrganization, scalaVersion, scalaBinaryVersion, platform, jars) + def apply(scalaOrganization: String, scalaVersion: String, scalaBinaryVersion: String, platform: Int, jars: Vector[String], jvmBuildTarget: Option[sbt.internal.bsp.JvmBuildTarget]): ScalaBuildTarget = new ScalaBuildTarget(scalaOrganization, scalaVersion, scalaBinaryVersion, platform, jars, jvmBuildTarget) + def apply(scalaOrganization: String, scalaVersion: String, scalaBinaryVersion: String, platform: Int, jars: Vector[String], jvmBuildTarget: sbt.internal.bsp.JvmBuildTarget): ScalaBuildTarget = new ScalaBuildTarget(scalaOrganization, scalaVersion, scalaBinaryVersion, platform, jars, Option(jvmBuildTarget)) } diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/JsonProtocol.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/JsonProtocol.scala index f6cae48d80..c98c92a87b 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/JsonProtocol.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/JsonProtocol.scala @@ -55,6 +55,7 @@ trait JsonProtocol extends sjsonnew.BasicJsonProtocol with sbt.internal.bsp.codec.TestResultFormats with sbt.internal.bsp.codec.RunParamsFormats with sbt.internal.bsp.codec.RunResultFormats + with sbt.internal.bsp.codec.JvmBuildTargetFormats with sbt.internal.bsp.codec.ScalaBuildTargetFormats with sbt.internal.bsp.codec.ScalacOptionsParamsFormats with sbt.internal.bsp.codec.ScalacOptionsItemFormats diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/JvmBuildTargetFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/JvmBuildTargetFormats.scala new file mode 100644 index 0000000000..301e778ba8 --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/JvmBuildTargetFormats.scala @@ -0,0 +1,29 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.bsp.codec +import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError } +trait JvmBuildTargetFormats { self: sjsonnew.BasicJsonProtocol => +implicit lazy val JvmBuildTargetFormat: JsonFormat[sbt.internal.bsp.JvmBuildTarget] = new JsonFormat[sbt.internal.bsp.JvmBuildTarget] { + override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.bsp.JvmBuildTarget = { + __jsOpt match { + case Some(__js) => + unbuilder.beginObject(__js) + val javaHome = unbuilder.readField[Option[java.net.URI]]("javaHome") + val javaVersion = unbuilder.readField[Option[String]]("javaVersion") + unbuilder.endObject() + sbt.internal.bsp.JvmBuildTarget(javaHome, javaVersion) + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.internal.bsp.JvmBuildTarget, builder: Builder[J]): Unit = { + builder.beginObject() + builder.addField("javaHome", obj.javaHome) + builder.addField("javaVersion", obj.javaVersion) + builder.endObject() + } +} +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/SbtBuildTargetFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/SbtBuildTargetFormats.scala index ff96c4211e..4e057a969d 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/SbtBuildTargetFormats.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/SbtBuildTargetFormats.scala @@ -5,7 +5,7 @@ // DO NOT EDIT MANUALLY package sbt.internal.bsp.codec import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError } -trait SbtBuildTargetFormats { self: sbt.internal.bsp.codec.ScalaBuildTargetFormats with sjsonnew.BasicJsonProtocol with sbt.internal.bsp.codec.BuildTargetIdentifierFormats => +trait SbtBuildTargetFormats { self: sbt.internal.bsp.codec.ScalaBuildTargetFormats with sbt.internal.bsp.codec.JvmBuildTargetFormats with sjsonnew.BasicJsonProtocol with sbt.internal.bsp.codec.BuildTargetIdentifierFormats => implicit lazy val SbtBuildTargetFormat: JsonFormat[sbt.internal.bsp.SbtBuildTarget] = new JsonFormat[sbt.internal.bsp.SbtBuildTarget] { override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.bsp.SbtBuildTarget = { __jsOpt match { diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/ScalaBuildTargetFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/ScalaBuildTargetFormats.scala index 900994c4ed..67b1389a36 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/ScalaBuildTargetFormats.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/ScalaBuildTargetFormats.scala @@ -5,7 +5,7 @@ // DO NOT EDIT MANUALLY package sbt.internal.bsp.codec import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError } -trait ScalaBuildTargetFormats { self: sjsonnew.BasicJsonProtocol => +trait ScalaBuildTargetFormats { self: sbt.internal.bsp.codec.JvmBuildTargetFormats with sjsonnew.BasicJsonProtocol => implicit lazy val ScalaBuildTargetFormat: JsonFormat[sbt.internal.bsp.ScalaBuildTarget] = new JsonFormat[sbt.internal.bsp.ScalaBuildTarget] { override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.bsp.ScalaBuildTarget = { __jsOpt match { @@ -16,8 +16,9 @@ implicit lazy val ScalaBuildTargetFormat: JsonFormat[sbt.internal.bsp.ScalaBuild val scalaBinaryVersion = unbuilder.readField[String]("scalaBinaryVersion") val platform = unbuilder.readField[Int]("platform") val jars = unbuilder.readField[Vector[String]]("jars") + val jvmBuildTarget = unbuilder.readField[Option[sbt.internal.bsp.JvmBuildTarget]]("jvmBuildTarget") unbuilder.endObject() - sbt.internal.bsp.ScalaBuildTarget(scalaOrganization, scalaVersion, scalaBinaryVersion, platform, jars) + sbt.internal.bsp.ScalaBuildTarget(scalaOrganization, scalaVersion, scalaBinaryVersion, platform, jars, jvmBuildTarget) case None => deserializationError("Expected JsObject but found None") } @@ -29,6 +30,7 @@ implicit lazy val ScalaBuildTargetFormat: JsonFormat[sbt.internal.bsp.ScalaBuild builder.addField("scalaBinaryVersion", obj.scalaBinaryVersion) builder.addField("platform", obj.platform) builder.addField("jars", obj.jars) + builder.addField("jvmBuildTarget", obj.jvmBuildTarget) builder.endObject() } } diff --git a/protocol/src/main/contraband-scala/sbt/internal/protocol/InitializeOption.scala b/protocol/src/main/contraband-scala/sbt/internal/protocol/InitializeOption.scala index b900d641d6..0a5f72a079 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/protocol/InitializeOption.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/protocol/InitializeOption.scala @@ -4,24 +4,30 @@ // DO NOT EDIT MANUALLY package sbt.internal.protocol +/** + * Passed into InitializeParams as part of "initialize" request as the user-defined option. + * https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize + */ final class InitializeOption private ( val token: Option[String], - val skipAnalysis: Option[Boolean]) extends Serializable { + val skipAnalysis: Option[Boolean], + val canWork: Option[Boolean]) extends Serializable { - private def this(token: Option[String]) = this(token, None) + private def this(token: Option[String]) = this(token, None, None) + private def this(token: Option[String], skipAnalysis: Option[Boolean]) = this(token, skipAnalysis, None) override def equals(o: Any): Boolean = this.eq(o.asInstanceOf[AnyRef]) || (o match { - case x: InitializeOption => (this.token == x.token) && (this.skipAnalysis == x.skipAnalysis) + case x: InitializeOption => (this.token == x.token) && (this.skipAnalysis == x.skipAnalysis) && (this.canWork == x.canWork) case _ => false }) override def hashCode: Int = { - 37 * (37 * (37 * (17 + "sbt.internal.protocol.InitializeOption".##) + token.##) + skipAnalysis.##) + 37 * (37 * (37 * (37 * (17 + "sbt.internal.protocol.InitializeOption".##) + token.##) + skipAnalysis.##) + canWork.##) } override def toString: String = { - "InitializeOption(" + token + ", " + skipAnalysis + ")" + "InitializeOption(" + token + ", " + skipAnalysis + ", " + canWork + ")" } - private[this] def copy(token: Option[String] = token, skipAnalysis: Option[Boolean] = skipAnalysis): InitializeOption = { - new InitializeOption(token, skipAnalysis) + private[this] def copy(token: Option[String] = token, skipAnalysis: Option[Boolean] = skipAnalysis, canWork: Option[Boolean] = canWork): InitializeOption = { + new InitializeOption(token, skipAnalysis, canWork) } def withToken(token: Option[String]): InitializeOption = { copy(token = token) @@ -35,6 +41,12 @@ final class InitializeOption private ( def withSkipAnalysis(skipAnalysis: Boolean): InitializeOption = { copy(skipAnalysis = Option(skipAnalysis)) } + def withCanWork(canWork: Option[Boolean]): InitializeOption = { + copy(canWork = canWork) + } + def withCanWork(canWork: Boolean): InitializeOption = { + copy(canWork = Option(canWork)) + } } object InitializeOption { @@ -42,4 +54,6 @@ object InitializeOption { def apply(token: String): InitializeOption = new InitializeOption(Option(token)) def apply(token: Option[String], skipAnalysis: Option[Boolean]): InitializeOption = new InitializeOption(token, skipAnalysis) def apply(token: String, skipAnalysis: Boolean): InitializeOption = new InitializeOption(Option(token), Option(skipAnalysis)) + def apply(token: Option[String], skipAnalysis: Option[Boolean], canWork: Option[Boolean]): InitializeOption = new InitializeOption(token, skipAnalysis, canWork) + def apply(token: String, skipAnalysis: Boolean, canWork: Boolean): InitializeOption = new InitializeOption(Option(token), Option(skipAnalysis), Option(canWork)) } diff --git a/protocol/src/main/contraband-scala/sbt/internal/protocol/codec/InitializeOptionFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/protocol/codec/InitializeOptionFormats.scala index f7f3a09e79..a5b04d6a42 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/protocol/codec/InitializeOptionFormats.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/protocol/codec/InitializeOptionFormats.scala @@ -13,8 +13,9 @@ implicit lazy val InitializeOptionFormat: JsonFormat[sbt.internal.protocol.Initi unbuilder.beginObject(__js) val token = unbuilder.readField[Option[String]]("token") val skipAnalysis = unbuilder.readField[Option[Boolean]]("skipAnalysis") + val canWork = unbuilder.readField[Option[Boolean]]("canWork") unbuilder.endObject() - sbt.internal.protocol.InitializeOption(token, skipAnalysis) + sbt.internal.protocol.InitializeOption(token, skipAnalysis, canWork) case None => deserializationError("Expected JsObject but found None") } @@ -23,6 +24,7 @@ implicit lazy val InitializeOptionFormat: JsonFormat[sbt.internal.protocol.Initi builder.beginObject() builder.addField("token", obj.token) builder.addField("skipAnalysis", obj.skipAnalysis) + builder.addField("canWork", obj.canWork) builder.endObject() } } diff --git a/protocol/src/main/contraband-scala/sbt/internal/worker/ClientJobParams.scala b/protocol/src/main/contraband-scala/sbt/internal/worker/ClientJobParams.scala new file mode 100644 index 0000000000..d72bf2b9f2 --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/worker/ClientJobParams.scala @@ -0,0 +1,45 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.worker +/** + * Client-side job support. + * + * Notification: sbt/clientJob + * + * Parameter for the sbt/clientJob notification. + * A client-side job represents a unit of work that sbt server + * can outsourse back to the client, for example for run task. + */ +final class ClientJobParams private ( + val runInfo: Option[sbt.internal.worker.RunInfo]) extends Serializable { + + + + override def equals(o: Any): Boolean = this.eq(o.asInstanceOf[AnyRef]) || (o match { + case x: ClientJobParams => (this.runInfo == x.runInfo) + case _ => false + }) + override def hashCode: Int = { + 37 * (37 * (17 + "sbt.internal.worker.ClientJobParams".##) + runInfo.##) + } + override def toString: String = { + "ClientJobParams(" + runInfo + ")" + } + private[this] def copy(runInfo: Option[sbt.internal.worker.RunInfo] = runInfo): ClientJobParams = { + new ClientJobParams(runInfo) + } + def withRunInfo(runInfo: Option[sbt.internal.worker.RunInfo]): ClientJobParams = { + copy(runInfo = runInfo) + } + def withRunInfo(runInfo: sbt.internal.worker.RunInfo): ClientJobParams = { + copy(runInfo = Option(runInfo)) + } +} +object ClientJobParams { + + def apply(runInfo: Option[sbt.internal.worker.RunInfo]): ClientJobParams = new ClientJobParams(runInfo) + def apply(runInfo: sbt.internal.worker.RunInfo): ClientJobParams = new ClientJobParams(Option(runInfo)) +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/worker/FilePath.scala b/protocol/src/main/contraband-scala/sbt/internal/worker/FilePath.scala new file mode 100644 index 0000000000..24647f3c09 --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/worker/FilePath.scala @@ -0,0 +1,36 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.worker +final class FilePath private ( + val path: java.net.URI, + val digest: String) extends Serializable { + + + + override def equals(o: Any): Boolean = this.eq(o.asInstanceOf[AnyRef]) || (o match { + case x: FilePath => (this.path == x.path) && (this.digest == x.digest) + case _ => false + }) + override def hashCode: Int = { + 37 * (37 * (37 * (17 + "sbt.internal.worker.FilePath".##) + path.##) + digest.##) + } + override def toString: String = { + "FilePath(" + path + ", " + digest + ")" + } + private[this] def copy(path: java.net.URI = path, digest: String = digest): FilePath = { + new FilePath(path, digest) + } + def withPath(path: java.net.URI): FilePath = { + copy(path = path) + } + def withDigest(digest: String): FilePath = { + copy(digest = digest) + } +} +object FilePath { + + def apply(path: java.net.URI, digest: String): FilePath = new FilePath(path, digest) +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/worker/JvmRunInfo.scala b/protocol/src/main/contraband-scala/sbt/internal/worker/JvmRunInfo.scala new file mode 100644 index 0000000000..d0d6b5b73d --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/worker/JvmRunInfo.scala @@ -0,0 +1,84 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.worker +final class JvmRunInfo private ( + val args: Vector[String], + val classpath: Vector[sbt.internal.worker.FilePath], + val mainClass: String, + val connectInput: Boolean, + val javaHome: Option[java.net.URI], + val outputStrategy: Option[String], + val workingDirectory: Option[java.net.URI], + val jvmOptions: Vector[String], + val environmentVariables: scala.collection.immutable.Map[String, String], + val inputs: Vector[sbt.internal.worker.FilePath], + val outputs: Vector[sbt.internal.worker.FilePath]) extends Serializable { + + private def this(args: Vector[String], classpath: Vector[sbt.internal.worker.FilePath], mainClass: String, connectInput: Boolean, javaHome: Option[java.net.URI], outputStrategy: Option[String], workingDirectory: Option[java.net.URI], jvmOptions: Vector[String], environmentVariables: scala.collection.immutable.Map[String, String]) = this(args, classpath, mainClass, connectInput, javaHome, outputStrategy, workingDirectory, jvmOptions, environmentVariables, Vector(), Vector()) + + override def equals(o: Any): Boolean = this.eq(o.asInstanceOf[AnyRef]) || (o match { + case x: JvmRunInfo => (this.args == x.args) && (this.classpath == x.classpath) && (this.mainClass == x.mainClass) && (this.connectInput == x.connectInput) && (this.javaHome == x.javaHome) && (this.outputStrategy == x.outputStrategy) && (this.workingDirectory == x.workingDirectory) && (this.jvmOptions == x.jvmOptions) && (this.environmentVariables == x.environmentVariables) && (this.inputs == x.inputs) && (this.outputs == x.outputs) + case _ => false + }) + override def hashCode: Int = { + 37 * (37 * (37 * (37 * (37 * (37 * (37 * (37 * (37 * (37 * (37 * (37 * (17 + "sbt.internal.worker.JvmRunInfo".##) + args.##) + classpath.##) + mainClass.##) + connectInput.##) + javaHome.##) + outputStrategy.##) + workingDirectory.##) + jvmOptions.##) + environmentVariables.##) + inputs.##) + outputs.##) + } + override def toString: String = { + "JvmRunInfo(" + args + ", " + classpath + ", " + mainClass + ", " + connectInput + ", " + javaHome + ", " + outputStrategy + ", " + workingDirectory + ", " + jvmOptions + ", " + environmentVariables + ", " + inputs + ", " + outputs + ")" + } + private[this] def copy(args: Vector[String] = args, classpath: Vector[sbt.internal.worker.FilePath] = classpath, mainClass: String = mainClass, connectInput: Boolean = connectInput, javaHome: Option[java.net.URI] = javaHome, outputStrategy: Option[String] = outputStrategy, workingDirectory: Option[java.net.URI] = workingDirectory, jvmOptions: Vector[String] = jvmOptions, environmentVariables: scala.collection.immutable.Map[String, String] = environmentVariables, inputs: Vector[sbt.internal.worker.FilePath] = inputs, outputs: Vector[sbt.internal.worker.FilePath] = outputs): JvmRunInfo = { + new JvmRunInfo(args, classpath, mainClass, connectInput, javaHome, outputStrategy, workingDirectory, jvmOptions, environmentVariables, inputs, outputs) + } + def withArgs(args: Vector[String]): JvmRunInfo = { + copy(args = args) + } + def withClasspath(classpath: Vector[sbt.internal.worker.FilePath]): JvmRunInfo = { + copy(classpath = classpath) + } + def withMainClass(mainClass: String): JvmRunInfo = { + copy(mainClass = mainClass) + } + def withConnectInput(connectInput: Boolean): JvmRunInfo = { + copy(connectInput = connectInput) + } + def withJavaHome(javaHome: Option[java.net.URI]): JvmRunInfo = { + copy(javaHome = javaHome) + } + def withJavaHome(javaHome: java.net.URI): JvmRunInfo = { + copy(javaHome = Option(javaHome)) + } + def withOutputStrategy(outputStrategy: Option[String]): JvmRunInfo = { + copy(outputStrategy = outputStrategy) + } + def withOutputStrategy(outputStrategy: String): JvmRunInfo = { + copy(outputStrategy = Option(outputStrategy)) + } + def withWorkingDirectory(workingDirectory: Option[java.net.URI]): JvmRunInfo = { + copy(workingDirectory = workingDirectory) + } + def withWorkingDirectory(workingDirectory: java.net.URI): JvmRunInfo = { + copy(workingDirectory = Option(workingDirectory)) + } + def withJvmOptions(jvmOptions: Vector[String]): JvmRunInfo = { + copy(jvmOptions = jvmOptions) + } + def withEnvironmentVariables(environmentVariables: scala.collection.immutable.Map[String, String]): JvmRunInfo = { + copy(environmentVariables = environmentVariables) + } + def withInputs(inputs: Vector[sbt.internal.worker.FilePath]): JvmRunInfo = { + copy(inputs = inputs) + } + def withOutputs(outputs: Vector[sbt.internal.worker.FilePath]): JvmRunInfo = { + copy(outputs = outputs) + } +} +object JvmRunInfo { + + def apply(args: Vector[String], classpath: Vector[sbt.internal.worker.FilePath], mainClass: String, connectInput: Boolean, javaHome: Option[java.net.URI], outputStrategy: Option[String], workingDirectory: Option[java.net.URI], jvmOptions: Vector[String], environmentVariables: scala.collection.immutable.Map[String, String]): JvmRunInfo = new JvmRunInfo(args, classpath, mainClass, connectInput, javaHome, outputStrategy, workingDirectory, jvmOptions, environmentVariables) + def apply(args: Vector[String], classpath: Vector[sbt.internal.worker.FilePath], mainClass: String, connectInput: Boolean, javaHome: java.net.URI, outputStrategy: String, workingDirectory: java.net.URI, jvmOptions: Vector[String], environmentVariables: scala.collection.immutable.Map[String, String]): JvmRunInfo = new JvmRunInfo(args, classpath, mainClass, connectInput, Option(javaHome), Option(outputStrategy), Option(workingDirectory), jvmOptions, environmentVariables) + def apply(args: Vector[String], classpath: Vector[sbt.internal.worker.FilePath], mainClass: String, connectInput: Boolean, javaHome: Option[java.net.URI], outputStrategy: Option[String], workingDirectory: Option[java.net.URI], jvmOptions: Vector[String], environmentVariables: scala.collection.immutable.Map[String, String], inputs: Vector[sbt.internal.worker.FilePath], outputs: Vector[sbt.internal.worker.FilePath]): JvmRunInfo = new JvmRunInfo(args, classpath, mainClass, connectInput, javaHome, outputStrategy, workingDirectory, jvmOptions, environmentVariables, inputs, outputs) + def apply(args: Vector[String], classpath: Vector[sbt.internal.worker.FilePath], mainClass: String, connectInput: Boolean, javaHome: java.net.URI, outputStrategy: String, workingDirectory: java.net.URI, jvmOptions: Vector[String], environmentVariables: scala.collection.immutable.Map[String, String], inputs: Vector[sbt.internal.worker.FilePath], outputs: Vector[sbt.internal.worker.FilePath]): JvmRunInfo = new JvmRunInfo(args, classpath, mainClass, connectInput, Option(javaHome), Option(outputStrategy), Option(workingDirectory), jvmOptions, environmentVariables, inputs, outputs) +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/worker/NativeRunInfo.scala b/protocol/src/main/contraband-scala/sbt/internal/worker/NativeRunInfo.scala new file mode 100644 index 0000000000..5caffe8fd4 --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/worker/NativeRunInfo.scala @@ -0,0 +1,69 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.worker +final class NativeRunInfo private ( + val cmd: String, + val args: Vector[String], + val connectInput: Boolean, + val outputStrategy: Option[String], + val workingDirectory: Option[java.net.URI], + val environmentVariables: scala.collection.immutable.Map[String, String], + val inputs: Vector[sbt.internal.worker.FilePath], + val outputs: Vector[sbt.internal.worker.FilePath]) extends Serializable { + + private def this(cmd: String, args: Vector[String], connectInput: Boolean, outputStrategy: Option[String], workingDirectory: Option[java.net.URI], environmentVariables: scala.collection.immutable.Map[String, String]) = this(cmd, args, connectInput, outputStrategy, workingDirectory, environmentVariables, Vector(), Vector()) + + override def equals(o: Any): Boolean = this.eq(o.asInstanceOf[AnyRef]) || (o match { + case x: NativeRunInfo => (this.cmd == x.cmd) && (this.args == x.args) && (this.connectInput == x.connectInput) && (this.outputStrategy == x.outputStrategy) && (this.workingDirectory == x.workingDirectory) && (this.environmentVariables == x.environmentVariables) && (this.inputs == x.inputs) && (this.outputs == x.outputs) + case _ => false + }) + override def hashCode: Int = { + 37 * (37 * (37 * (37 * (37 * (37 * (37 * (37 * (37 * (17 + "sbt.internal.worker.NativeRunInfo".##) + cmd.##) + args.##) + connectInput.##) + outputStrategy.##) + workingDirectory.##) + environmentVariables.##) + inputs.##) + outputs.##) + } + override def toString: String = { + "NativeRunInfo(" + cmd + ", " + args + ", " + connectInput + ", " + outputStrategy + ", " + workingDirectory + ", " + environmentVariables + ", " + inputs + ", " + outputs + ")" + } + private[this] def copy(cmd: String = cmd, args: Vector[String] = args, connectInput: Boolean = connectInput, outputStrategy: Option[String] = outputStrategy, workingDirectory: Option[java.net.URI] = workingDirectory, environmentVariables: scala.collection.immutable.Map[String, String] = environmentVariables, inputs: Vector[sbt.internal.worker.FilePath] = inputs, outputs: Vector[sbt.internal.worker.FilePath] = outputs): NativeRunInfo = { + new NativeRunInfo(cmd, args, connectInput, outputStrategy, workingDirectory, environmentVariables, inputs, outputs) + } + def withCmd(cmd: String): NativeRunInfo = { + copy(cmd = cmd) + } + def withArgs(args: Vector[String]): NativeRunInfo = { + copy(args = args) + } + def withConnectInput(connectInput: Boolean): NativeRunInfo = { + copy(connectInput = connectInput) + } + def withOutputStrategy(outputStrategy: Option[String]): NativeRunInfo = { + copy(outputStrategy = outputStrategy) + } + def withOutputStrategy(outputStrategy: String): NativeRunInfo = { + copy(outputStrategy = Option(outputStrategy)) + } + def withWorkingDirectory(workingDirectory: Option[java.net.URI]): NativeRunInfo = { + copy(workingDirectory = workingDirectory) + } + def withWorkingDirectory(workingDirectory: java.net.URI): NativeRunInfo = { + copy(workingDirectory = Option(workingDirectory)) + } + def withEnvironmentVariables(environmentVariables: scala.collection.immutable.Map[String, String]): NativeRunInfo = { + copy(environmentVariables = environmentVariables) + } + def withInputs(inputs: Vector[sbt.internal.worker.FilePath]): NativeRunInfo = { + copy(inputs = inputs) + } + def withOutputs(outputs: Vector[sbt.internal.worker.FilePath]): NativeRunInfo = { + copy(outputs = outputs) + } +} +object NativeRunInfo { + + def apply(cmd: String, args: Vector[String], connectInput: Boolean, outputStrategy: Option[String], workingDirectory: Option[java.net.URI], environmentVariables: scala.collection.immutable.Map[String, String]): NativeRunInfo = new NativeRunInfo(cmd, args, connectInput, outputStrategy, workingDirectory, environmentVariables) + def apply(cmd: String, args: Vector[String], connectInput: Boolean, outputStrategy: String, workingDirectory: java.net.URI, environmentVariables: scala.collection.immutable.Map[String, String]): NativeRunInfo = new NativeRunInfo(cmd, args, connectInput, Option(outputStrategy), Option(workingDirectory), environmentVariables) + def apply(cmd: String, args: Vector[String], connectInput: Boolean, outputStrategy: Option[String], workingDirectory: Option[java.net.URI], environmentVariables: scala.collection.immutable.Map[String, String], inputs: Vector[sbt.internal.worker.FilePath], outputs: Vector[sbt.internal.worker.FilePath]): NativeRunInfo = new NativeRunInfo(cmd, args, connectInput, outputStrategy, workingDirectory, environmentVariables, inputs, outputs) + def apply(cmd: String, args: Vector[String], connectInput: Boolean, outputStrategy: String, workingDirectory: java.net.URI, environmentVariables: scala.collection.immutable.Map[String, String], inputs: Vector[sbt.internal.worker.FilePath], outputs: Vector[sbt.internal.worker.FilePath]): NativeRunInfo = new NativeRunInfo(cmd, args, connectInput, Option(outputStrategy), Option(workingDirectory), environmentVariables, inputs, outputs) +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/worker/RunInfo.scala b/protocol/src/main/contraband-scala/sbt/internal/worker/RunInfo.scala new file mode 100644 index 0000000000..855bd06d38 --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/worker/RunInfo.scala @@ -0,0 +1,49 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.worker +final class RunInfo private ( + val jvm: Boolean, + val jvmRunInfo: Option[sbt.internal.worker.JvmRunInfo], + val nativeRunInfo: Option[sbt.internal.worker.NativeRunInfo]) extends Serializable { + + private def this(jvm: Boolean, jvmRunInfo: Option[sbt.internal.worker.JvmRunInfo]) = this(jvm, jvmRunInfo, None) + + override def equals(o: Any): Boolean = this.eq(o.asInstanceOf[AnyRef]) || (o match { + case x: RunInfo => (this.jvm == x.jvm) && (this.jvmRunInfo == x.jvmRunInfo) && (this.nativeRunInfo == x.nativeRunInfo) + case _ => false + }) + override def hashCode: Int = { + 37 * (37 * (37 * (37 * (17 + "sbt.internal.worker.RunInfo".##) + jvm.##) + jvmRunInfo.##) + nativeRunInfo.##) + } + override def toString: String = { + "RunInfo(" + jvm + ", " + jvmRunInfo + ", " + nativeRunInfo + ")" + } + private[this] def copy(jvm: Boolean = jvm, jvmRunInfo: Option[sbt.internal.worker.JvmRunInfo] = jvmRunInfo, nativeRunInfo: Option[sbt.internal.worker.NativeRunInfo] = nativeRunInfo): RunInfo = { + new RunInfo(jvm, jvmRunInfo, nativeRunInfo) + } + def withJvm(jvm: Boolean): RunInfo = { + copy(jvm = jvm) + } + def withJvmRunInfo(jvmRunInfo: Option[sbt.internal.worker.JvmRunInfo]): RunInfo = { + copy(jvmRunInfo = jvmRunInfo) + } + def withJvmRunInfo(jvmRunInfo: sbt.internal.worker.JvmRunInfo): RunInfo = { + copy(jvmRunInfo = Option(jvmRunInfo)) + } + def withNativeRunInfo(nativeRunInfo: Option[sbt.internal.worker.NativeRunInfo]): RunInfo = { + copy(nativeRunInfo = nativeRunInfo) + } + def withNativeRunInfo(nativeRunInfo: sbt.internal.worker.NativeRunInfo): RunInfo = { + copy(nativeRunInfo = Option(nativeRunInfo)) + } +} +object RunInfo { + + def apply(jvm: Boolean, jvmRunInfo: Option[sbt.internal.worker.JvmRunInfo]): RunInfo = new RunInfo(jvm, jvmRunInfo) + def apply(jvm: Boolean, jvmRunInfo: sbt.internal.worker.JvmRunInfo): RunInfo = new RunInfo(jvm, Option(jvmRunInfo)) + def apply(jvm: Boolean, jvmRunInfo: Option[sbt.internal.worker.JvmRunInfo], nativeRunInfo: Option[sbt.internal.worker.NativeRunInfo]): RunInfo = new RunInfo(jvm, jvmRunInfo, nativeRunInfo) + def apply(jvm: Boolean, jvmRunInfo: sbt.internal.worker.JvmRunInfo, nativeRunInfo: sbt.internal.worker.NativeRunInfo): RunInfo = new RunInfo(jvm, Option(jvmRunInfo), Option(nativeRunInfo)) +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/worker/codec/ClientJobParamsFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/worker/codec/ClientJobParamsFormats.scala new file mode 100644 index 0000000000..e045d628ca --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/worker/codec/ClientJobParamsFormats.scala @@ -0,0 +1,27 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.worker.codec +import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError } +trait ClientJobParamsFormats { self: sbt.internal.worker.codec.RunInfoFormats with sbt.internal.worker.codec.JvmRunInfoFormats with sbt.internal.worker.codec.FilePathFormats with sjsonnew.BasicJsonProtocol with sbt.internal.worker.codec.NativeRunInfoFormats => +implicit lazy val ClientJobParamsFormat: JsonFormat[sbt.internal.worker.ClientJobParams] = new JsonFormat[sbt.internal.worker.ClientJobParams] { + override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.worker.ClientJobParams = { + __jsOpt match { + case Some(__js) => + unbuilder.beginObject(__js) + val runInfo = unbuilder.readField[Option[sbt.internal.worker.RunInfo]]("runInfo") + unbuilder.endObject() + sbt.internal.worker.ClientJobParams(runInfo) + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.internal.worker.ClientJobParams, builder: Builder[J]): Unit = { + builder.beginObject() + builder.addField("runInfo", obj.runInfo) + builder.endObject() + } +} +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/worker/codec/FilePathFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/worker/codec/FilePathFormats.scala new file mode 100644 index 0000000000..ebbac551ff --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/worker/codec/FilePathFormats.scala @@ -0,0 +1,29 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.worker.codec +import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError } +trait FilePathFormats { self: sjsonnew.BasicJsonProtocol => +implicit lazy val FilePathFormat: JsonFormat[sbt.internal.worker.FilePath] = new JsonFormat[sbt.internal.worker.FilePath] { + override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.worker.FilePath = { + __jsOpt match { + case Some(__js) => + unbuilder.beginObject(__js) + val path = unbuilder.readField[java.net.URI]("path") + val digest = unbuilder.readField[String]("digest") + unbuilder.endObject() + sbt.internal.worker.FilePath(path, digest) + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.internal.worker.FilePath, builder: Builder[J]): Unit = { + builder.beginObject() + builder.addField("path", obj.path) + builder.addField("digest", obj.digest) + builder.endObject() + } +} +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/worker/codec/JsonProtocol.scala b/protocol/src/main/contraband-scala/sbt/internal/worker/codec/JsonProtocol.scala new file mode 100644 index 0000000000..fa29c174cd --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/worker/codec/JsonProtocol.scala @@ -0,0 +1,13 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.worker.codec +trait JsonProtocol extends sjsonnew.BasicJsonProtocol + with sbt.internal.worker.codec.FilePathFormats + with sbt.internal.worker.codec.JvmRunInfoFormats + with sbt.internal.worker.codec.NativeRunInfoFormats + with sbt.internal.worker.codec.RunInfoFormats + with sbt.internal.worker.codec.ClientJobParamsFormats +object JsonProtocol extends JsonProtocol \ No newline at end of file diff --git a/protocol/src/main/contraband-scala/sbt/internal/worker/codec/JvmRunInfoFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/worker/codec/JvmRunInfoFormats.scala new file mode 100644 index 0000000000..793828b5ef --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/worker/codec/JvmRunInfoFormats.scala @@ -0,0 +1,47 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.worker.codec +import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError } +trait JvmRunInfoFormats { self: sbt.internal.worker.codec.FilePathFormats with sjsonnew.BasicJsonProtocol => +implicit lazy val JvmRunInfoFormat: JsonFormat[sbt.internal.worker.JvmRunInfo] = new JsonFormat[sbt.internal.worker.JvmRunInfo] { + override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.worker.JvmRunInfo = { + __jsOpt match { + case Some(__js) => + unbuilder.beginObject(__js) + val args = unbuilder.readField[Vector[String]]("args") + val classpath = unbuilder.readField[Vector[sbt.internal.worker.FilePath]]("classpath") + val mainClass = unbuilder.readField[String]("mainClass") + val connectInput = unbuilder.readField[Boolean]("connectInput") + val javaHome = unbuilder.readField[Option[java.net.URI]]("javaHome") + val outputStrategy = unbuilder.readField[Option[String]]("outputStrategy") + val workingDirectory = unbuilder.readField[Option[java.net.URI]]("workingDirectory") + val jvmOptions = unbuilder.readField[Vector[String]]("jvmOptions") + val environmentVariables = unbuilder.readField[scala.collection.immutable.Map[String, String]]("environmentVariables") + val inputs = unbuilder.readField[Vector[sbt.internal.worker.FilePath]]("inputs") + val outputs = unbuilder.readField[Vector[sbt.internal.worker.FilePath]]("outputs") + unbuilder.endObject() + sbt.internal.worker.JvmRunInfo(args, classpath, mainClass, connectInput, javaHome, outputStrategy, workingDirectory, jvmOptions, environmentVariables, inputs, outputs) + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.internal.worker.JvmRunInfo, builder: Builder[J]): Unit = { + builder.beginObject() + builder.addField("args", obj.args) + builder.addField("classpath", obj.classpath) + builder.addField("mainClass", obj.mainClass) + builder.addField("connectInput", obj.connectInput) + builder.addField("javaHome", obj.javaHome) + builder.addField("outputStrategy", obj.outputStrategy) + builder.addField("workingDirectory", obj.workingDirectory) + builder.addField("jvmOptions", obj.jvmOptions) + builder.addField("environmentVariables", obj.environmentVariables) + builder.addField("inputs", obj.inputs) + builder.addField("outputs", obj.outputs) + builder.endObject() + } +} +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/worker/codec/NativeRunInfoFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/worker/codec/NativeRunInfoFormats.scala new file mode 100644 index 0000000000..73588aa9f3 --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/worker/codec/NativeRunInfoFormats.scala @@ -0,0 +1,41 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.worker.codec +import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError } +trait NativeRunInfoFormats { self: sbt.internal.worker.codec.FilePathFormats with sjsonnew.BasicJsonProtocol => +implicit lazy val NativeRunInfoFormat: JsonFormat[sbt.internal.worker.NativeRunInfo] = new JsonFormat[sbt.internal.worker.NativeRunInfo] { + override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.worker.NativeRunInfo = { + __jsOpt match { + case Some(__js) => + unbuilder.beginObject(__js) + val cmd = unbuilder.readField[String]("cmd") + val args = unbuilder.readField[Vector[String]]("args") + val connectInput = unbuilder.readField[Boolean]("connectInput") + val outputStrategy = unbuilder.readField[Option[String]]("outputStrategy") + val workingDirectory = unbuilder.readField[Option[java.net.URI]]("workingDirectory") + val environmentVariables = unbuilder.readField[scala.collection.immutable.Map[String, String]]("environmentVariables") + val inputs = unbuilder.readField[Vector[sbt.internal.worker.FilePath]]("inputs") + val outputs = unbuilder.readField[Vector[sbt.internal.worker.FilePath]]("outputs") + unbuilder.endObject() + sbt.internal.worker.NativeRunInfo(cmd, args, connectInput, outputStrategy, workingDirectory, environmentVariables, inputs, outputs) + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.internal.worker.NativeRunInfo, builder: Builder[J]): Unit = { + builder.beginObject() + builder.addField("cmd", obj.cmd) + builder.addField("args", obj.args) + builder.addField("connectInput", obj.connectInput) + builder.addField("outputStrategy", obj.outputStrategy) + builder.addField("workingDirectory", obj.workingDirectory) + builder.addField("environmentVariables", obj.environmentVariables) + builder.addField("inputs", obj.inputs) + builder.addField("outputs", obj.outputs) + builder.endObject() + } +} +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/worker/codec/RunInfoFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/worker/codec/RunInfoFormats.scala new file mode 100644 index 0000000000..16e66747ea --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/worker/codec/RunInfoFormats.scala @@ -0,0 +1,31 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.worker.codec +import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError } +trait RunInfoFormats { self: sbt.internal.worker.codec.JvmRunInfoFormats with sbt.internal.worker.codec.FilePathFormats with sjsonnew.BasicJsonProtocol with sbt.internal.worker.codec.NativeRunInfoFormats => +implicit lazy val RunInfoFormat: JsonFormat[sbt.internal.worker.RunInfo] = new JsonFormat[sbt.internal.worker.RunInfo] { + override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.worker.RunInfo = { + __jsOpt match { + case Some(__js) => + unbuilder.beginObject(__js) + val jvm = unbuilder.readField[Boolean]("jvm") + val jvmRunInfo = unbuilder.readField[Option[sbt.internal.worker.JvmRunInfo]]("jvmRunInfo") + val nativeRunInfo = unbuilder.readField[Option[sbt.internal.worker.NativeRunInfo]]("nativeRunInfo") + unbuilder.endObject() + sbt.internal.worker.RunInfo(jvm, jvmRunInfo, nativeRunInfo) + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.internal.worker.RunInfo, builder: Builder[J]): Unit = { + builder.beginObject() + builder.addField("jvm", obj.jvm) + builder.addField("jvmRunInfo", obj.jvmRunInfo) + builder.addField("nativeRunInfo", obj.nativeRunInfo) + builder.endObject() + } +} +} diff --git a/protocol/src/main/contraband-scala/sbt/protocol/InitCommand.scala b/protocol/src/main/contraband-scala/sbt/protocol/InitCommand.scala index ddfe85f45f..26511bc1ee 100644 --- a/protocol/src/main/contraband-scala/sbt/protocol/InitCommand.scala +++ b/protocol/src/main/contraband-scala/sbt/protocol/InitCommand.scala @@ -7,22 +7,24 @@ package sbt.protocol final class InitCommand private ( val token: Option[String], val execId: Option[String], - val skipAnalysis: Option[Boolean]) extends sbt.protocol.CommandMessage() with Serializable { + val skipAnalysis: Option[Boolean], + val initializationOptions: Option[sbt.internal.protocol.InitializeOption]) extends sbt.protocol.CommandMessage() with Serializable { - private def this(token: Option[String], execId: Option[String]) = this(token, execId, None) + private def this(token: Option[String], execId: Option[String]) = this(token, execId, None, None) + private def this(token: Option[String], execId: Option[String], skipAnalysis: Option[Boolean]) = this(token, execId, skipAnalysis, None) override def equals(o: Any): Boolean = this.eq(o.asInstanceOf[AnyRef]) || (o match { - case x: InitCommand => (this.token == x.token) && (this.execId == x.execId) && (this.skipAnalysis == x.skipAnalysis) + case x: InitCommand => (this.token == x.token) && (this.execId == x.execId) && (this.skipAnalysis == x.skipAnalysis) && (this.initializationOptions == x.initializationOptions) case _ => false }) override def hashCode: Int = { - 37 * (37 * (37 * (37 * (17 + "sbt.protocol.InitCommand".##) + token.##) + execId.##) + skipAnalysis.##) + 37 * (37 * (37 * (37 * (37 * (17 + "sbt.protocol.InitCommand".##) + token.##) + execId.##) + skipAnalysis.##) + initializationOptions.##) } override def toString: String = { - "InitCommand(" + token + ", " + execId + ", " + skipAnalysis + ")" + "InitCommand(" + token + ", " + execId + ", " + skipAnalysis + ", " + initializationOptions + ")" } - private[this] def copy(token: Option[String] = token, execId: Option[String] = execId, skipAnalysis: Option[Boolean] = skipAnalysis): InitCommand = { - new InitCommand(token, execId, skipAnalysis) + private[this] def copy(token: Option[String] = token, execId: Option[String] = execId, skipAnalysis: Option[Boolean] = skipAnalysis, initializationOptions: Option[sbt.internal.protocol.InitializeOption] = initializationOptions): InitCommand = { + new InitCommand(token, execId, skipAnalysis, initializationOptions) } def withToken(token: Option[String]): InitCommand = { copy(token = token) @@ -42,6 +44,12 @@ final class InitCommand private ( def withSkipAnalysis(skipAnalysis: Boolean): InitCommand = { copy(skipAnalysis = Option(skipAnalysis)) } + def withInitializationOptions(initializationOptions: Option[sbt.internal.protocol.InitializeOption]): InitCommand = { + copy(initializationOptions = initializationOptions) + } + def withInitializationOptions(initializationOptions: sbt.internal.protocol.InitializeOption): InitCommand = { + copy(initializationOptions = Option(initializationOptions)) + } } object InitCommand { @@ -49,4 +57,6 @@ object InitCommand { def apply(token: String, execId: String): InitCommand = new InitCommand(Option(token), Option(execId)) def apply(token: Option[String], execId: Option[String], skipAnalysis: Option[Boolean]): InitCommand = new InitCommand(token, execId, skipAnalysis) def apply(token: String, execId: String, skipAnalysis: Boolean): InitCommand = new InitCommand(Option(token), Option(execId), Option(skipAnalysis)) + def apply(token: Option[String], execId: Option[String], skipAnalysis: Option[Boolean], initializationOptions: Option[sbt.internal.protocol.InitializeOption]): InitCommand = new InitCommand(token, execId, skipAnalysis, initializationOptions) + def apply(token: String, execId: String, skipAnalysis: Boolean, initializationOptions: sbt.internal.protocol.InitializeOption): InitCommand = new InitCommand(Option(token), Option(execId), Option(skipAnalysis), Option(initializationOptions)) } diff --git a/protocol/src/main/contraband-scala/sbt/protocol/codec/CommandMessageFormats.scala b/protocol/src/main/contraband-scala/sbt/protocol/codec/CommandMessageFormats.scala index 6f95b6f48a..a705d1b488 100644 --- a/protocol/src/main/contraband-scala/sbt/protocol/codec/CommandMessageFormats.scala +++ b/protocol/src/main/contraband-scala/sbt/protocol/codec/CommandMessageFormats.scala @@ -6,6 +6,6 @@ package sbt.protocol.codec import _root_.sjsonnew.JsonFormat -trait CommandMessageFormats { self: sjsonnew.BasicJsonProtocol with sbt.protocol.codec.InitCommandFormats with sbt.protocol.codec.ExecCommandFormats with sbt.protocol.codec.SettingQueryFormats with sbt.protocol.codec.AttachFormats with sbt.protocol.codec.TerminalCapabilitiesQueryFormats with sbt.protocol.codec.TerminalSetAttributesCommandFormats with sbt.protocol.codec.TerminalAttributesQueryFormats with sbt.protocol.codec.TerminalGetSizeQueryFormats with sbt.protocol.codec.TerminalSetSizeCommandFormats with sbt.protocol.codec.TerminalSetEchoCommandFormats with sbt.protocol.codec.TerminalSetRawModeCommandFormats => +trait CommandMessageFormats { self: sbt.internal.protocol.codec.InitializeOptionFormats with sjsonnew.BasicJsonProtocol with sbt.protocol.codec.InitCommandFormats with sbt.protocol.codec.ExecCommandFormats with sbt.protocol.codec.SettingQueryFormats with sbt.protocol.codec.AttachFormats with sbt.protocol.codec.TerminalCapabilitiesQueryFormats with sbt.protocol.codec.TerminalSetAttributesCommandFormats with sbt.protocol.codec.TerminalAttributesQueryFormats with sbt.protocol.codec.TerminalGetSizeQueryFormats with sbt.protocol.codec.TerminalSetSizeCommandFormats with sbt.protocol.codec.TerminalSetEchoCommandFormats with sbt.protocol.codec.TerminalSetRawModeCommandFormats => implicit lazy val CommandMessageFormat: JsonFormat[sbt.protocol.CommandMessage] = flatUnionFormat11[sbt.protocol.CommandMessage, sbt.protocol.InitCommand, sbt.protocol.ExecCommand, sbt.protocol.SettingQuery, sbt.protocol.Attach, sbt.protocol.TerminalCapabilitiesQuery, sbt.protocol.TerminalSetAttributesCommand, sbt.protocol.TerminalAttributesQuery, sbt.protocol.TerminalGetSizeQuery, sbt.protocol.TerminalSetSizeCommand, sbt.protocol.TerminalSetEchoCommand, sbt.protocol.TerminalSetRawModeCommand]("type") } diff --git a/protocol/src/main/contraband-scala/sbt/protocol/codec/InitCommandFormats.scala b/protocol/src/main/contraband-scala/sbt/protocol/codec/InitCommandFormats.scala index 827b6dc7c0..7d552b17b3 100644 --- a/protocol/src/main/contraband-scala/sbt/protocol/codec/InitCommandFormats.scala +++ b/protocol/src/main/contraband-scala/sbt/protocol/codec/InitCommandFormats.scala @@ -5,7 +5,7 @@ // DO NOT EDIT MANUALLY package sbt.protocol.codec import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError } -trait InitCommandFormats { self: sjsonnew.BasicJsonProtocol => +trait InitCommandFormats { self: sbt.internal.protocol.codec.InitializeOptionFormats with sjsonnew.BasicJsonProtocol => implicit lazy val InitCommandFormat: JsonFormat[sbt.protocol.InitCommand] = new JsonFormat[sbt.protocol.InitCommand] { override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.protocol.InitCommand = { __jsOpt match { @@ -14,8 +14,9 @@ implicit lazy val InitCommandFormat: JsonFormat[sbt.protocol.InitCommand] = new val token = unbuilder.readField[Option[String]]("token") val execId = unbuilder.readField[Option[String]]("execId") val skipAnalysis = unbuilder.readField[Option[Boolean]]("skipAnalysis") + val initializationOptions = unbuilder.readField[Option[sbt.internal.protocol.InitializeOption]]("initializationOptions") unbuilder.endObject() - sbt.protocol.InitCommand(token, execId, skipAnalysis) + sbt.protocol.InitCommand(token, execId, skipAnalysis, initializationOptions) case None => deserializationError("Expected JsObject but found None") } @@ -25,6 +26,7 @@ implicit lazy val InitCommandFormat: JsonFormat[sbt.protocol.InitCommand] = new builder.addField("token", obj.token) builder.addField("execId", obj.execId) builder.addField("skipAnalysis", obj.skipAnalysis) + builder.addField("initializationOptions", obj.initializationOptions) builder.endObject() } } diff --git a/protocol/src/main/contraband-scala/sbt/protocol/codec/JsonProtocol.scala b/protocol/src/main/contraband-scala/sbt/protocol/codec/JsonProtocol.scala index 2df56d1ad3..32852fe440 100644 --- a/protocol/src/main/contraband-scala/sbt/protocol/codec/JsonProtocol.scala +++ b/protocol/src/main/contraband-scala/sbt/protocol/codec/JsonProtocol.scala @@ -4,7 +4,8 @@ // DO NOT EDIT MANUALLY package sbt.protocol.codec -trait JsonProtocol extends sjsonnew.BasicJsonProtocol +trait JsonProtocol extends sbt.internal.protocol.codec.InitializeOptionFormats + with sjsonnew.BasicJsonProtocol with sbt.protocol.codec.InitCommandFormats with sbt.protocol.codec.ExecCommandFormats with sbt.protocol.codec.SettingQueryFormats diff --git a/protocol/src/main/contraband/bsp.contra b/protocol/src/main/contraband/bsp.contra index a931a8fb63..1956cf4404 100644 --- a/protocol/src/main/contraband/bsp.contra +++ b/protocol/src/main/contraband/bsp.contra @@ -605,6 +605,17 @@ type RunResult { } +# JVM extension + +## Contains jvm-specific metadata, specifically JDK reference +type JvmBuildTarget { + ## Uri representing absolute path to jdk + javaHome: java.net.URI + + ## The java version this target is supposed to use (can be set using javac `-target` flag) + javaVersion: String +} + # Scala Extension ## Contains scala-specific metadata for compiling a target containing Scala sources. @@ -626,6 +637,9 @@ type ScalaBuildTarget { ## A sequence of Scala jars such as scala-library, scala-compiler and scala-reflect. jars: [String]! + + ## The jvm build target describing jdk to be used + jvmBuildTarget: sbt.internal.bsp.JvmBuildTarget } ## Scalac options diff --git a/protocol/src/main/contraband/portfile.contra b/protocol/src/main/contraband/portfile.contra index 2e138c3159..ffd5dc6c9a 100644 --- a/protocol/src/main/contraband/portfile.contra +++ b/protocol/src/main/contraband/portfile.contra @@ -16,7 +16,10 @@ type TokenFile { token: String! } +## Passed into InitializeParams as part of "initialize" request as the user-defined option. +## https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize type InitializeOption { token: String skipAnalysis: Boolean @since("1.4.0") + canWork: Boolean @since("1.10.8") } diff --git a/protocol/src/main/contraband/server.contra b/protocol/src/main/contraband/server.contra index 18ec0a0d2f..176db450a7 100644 --- a/protocol/src/main/contraband/server.contra +++ b/protocol/src/main/contraband/server.contra @@ -11,6 +11,7 @@ type InitCommand implements CommandMessage { token: String execId: String skipAnalysis: Boolean @since("1.4.0") + initializationOptions: sbt.internal.protocol.InitializeOption @since("1.10.8") } ## Command to execute sbt command. diff --git a/protocol/src/main/contraband/worker.contra b/protocol/src/main/contraband/worker.contra new file mode 100644 index 0000000000..45a68eb741 --- /dev/null +++ b/protocol/src/main/contraband/worker.contra @@ -0,0 +1,51 @@ +package sbt.internal.worker +@target(Scala) +@codecPackage("sbt.internal.worker.codec") +@fullCodec("JsonProtocol") + +type FilePath { + path: java.net.URI! + digest: String! +} + +type JvmRunInfo { + args: [String], + classpath: [sbt.internal.worker.FilePath], + mainClass: String! + connectInput: Boolean! + javaHome: java.net.URI + outputStrategy: String + workingDirectory: java.net.URI + jvmOptions: [String] + environmentVariables: StringStringMap! + inputs: [sbt.internal.worker.FilePath] @since("0.1.0"), + outputs: [sbt.internal.worker.FilePath] @since("0.1.0"), +} + +type NativeRunInfo { + cmd: String!, + args: [String], + connectInput: Boolean! + outputStrategy: String + workingDirectory: java.net.URI + environmentVariables: StringStringMap! + inputs: [sbt.internal.worker.FilePath] @since("0.1.0"), + outputs: [sbt.internal.worker.FilePath] @since("0.1.0"), +} + +type RunInfo { + jvm: Boolean! + jvmRunInfo: sbt.internal.worker.JvmRunInfo, + nativeRunInfo: sbt.internal.worker.NativeRunInfo @since("0.1.0"), +} + +## Client-side job support. +## +## Notification: sbt/clientJob +## +## Parameter for the sbt/clientJob notification. +## A client-side job represents a unit of work that sbt server +## can outsourse back to the client, for example for run task. +type ClientJobParams { + runInfo: sbt.internal.worker.RunInfo +} diff --git a/protocol/src/main/scala/sbt/internal/bsp/BuildServerConnection.scala b/protocol/src/main/scala/sbt/internal/bsp/BuildServerConnection.scala index e03b67eae5..ba9042dde8 100644 --- a/protocol/src/main/scala/sbt/internal/bsp/BuildServerConnection.scala +++ b/protocol/src/main/scala/sbt/internal/bsp/BuildServerConnection.scala @@ -9,6 +9,7 @@ package sbt.internal.bsp import sbt.internal.bsp.codec.JsonProtocol.BspConnectionDetailsFormat +import sbt.internal.util.Util import sbt.io.IO import sjsonnew.support.scalajson.unsafe.{ CompactPrinter, Converter } @@ -25,7 +26,7 @@ object BuildServerConnection { private[sbt] def writeConnectionFile(sbtVersion: String, baseDir: File): Unit = { val bspConnectionFile = new File(baseDir, ".bsp/sbt.json") - val javaHome = System.getProperty("java.home") + val javaHome = Util.javaHome val classPath = System.getProperty("java.class.path") val sbtScript = Option(System.getProperty("sbt.script")) diff --git a/protocol/src/main/scala/sbt/protocol/Serialization.scala b/protocol/src/main/scala/sbt/protocol/Serialization.scala index 2dcd5ae981..1384fb4f24 100644 --- a/protocol/src/main/scala/sbt/protocol/Serialization.scala +++ b/protocol/src/main/scala/sbt/protocol/Serialization.scala @@ -27,6 +27,7 @@ object Serialization { private[sbt] val VsCode = "application/vscode-jsonrpc; charset=utf-8" val readSystemIn = "sbt/readSystemIn" val cancelReadSystemIn = "sbt/cancelReadSystemIn" + val clientJob = "sbt/clientJob" val systemIn = "sbt/systemIn" val systemOut = "sbt/systemOut" val systemErr = "sbt/systemErr" @@ -67,15 +68,10 @@ object Serialization { command match { case x: InitCommand => val execId = x.execId.getOrElse(UUID.randomUUID.toString) - val analysis = s""""skipAnalysis" : ${x.skipAnalysis.getOrElse(false)}""" - val opt = x.token match { - case Some(t) => - val json: JValue = Converter.toJson[String](t).get - val v = CompactPrinter(json) - s"""{ "token": $v, $analysis }""" - case None => s"{ $analysis }" - } - s"""{ "jsonrpc": "2.0", "id": "$execId", "method": "initialize", "params": { "initializationOptions": $opt } }""" + val opts = x.initializationOptions.getOrElse(sys.error("expected initializationOptions")) + import sbt.protocol.codec.JsonProtocol._ + val optsJson = CompactPrinter(Converter.toJson(opts).get) + s"""{ "jsonrpc": "2.0", "id": "$execId", "method": "initialize", "params": { "initializationOptions": $optsJson } }""" case x: ExecCommand => val execId = x.execId.getOrElse(UUID.randomUUID.toString) val json: JValue = Converter.toJson[String](x.commandLine).get diff --git a/run/src/main/scala/sbt/Fork.scala b/run/src/main/scala/sbt/Fork.scala index 3335f53f33..07fc33924b 100644 --- a/run/src/main/scala/sbt/Fork.scala +++ b/run/src/main/scala/sbt/Fork.scala @@ -33,15 +33,8 @@ final class Fork(val commandName: String, val runnerClass: Option[String]) { * It is configured according to `config`. * If `runnerClass` is defined for this Fork instance, it is prepended to `arguments` to define the arguments passed to the forked command. */ - def apply(config: ForkOptions, arguments: Seq[String]): Int = { - val p = fork(config, arguments) - RunningProcesses.add(p) - try p.exitValue() - finally { - if (p.isAlive()) p.destroy() - RunningProcesses.remove(p) - } - } + def apply(config: ForkOptions, arguments: Seq[String]): Int = + Fork.blockForExitCode(fork(config, arguments)) /** * Forks the configured process and returns a `Process` that can be used to wait for completion or to terminate the forked process. @@ -50,37 +43,22 @@ final class Fork(val commandName: String, val runnerClass: Option[String]) { * If `runnerClass` is defined for this Fork instance, it is prepended to `arguments` to define the arguments passed to the forked command. */ def fork(config: ForkOptions, arguments: Seq[String]): Process = { - import config.{ envVars => env, _ } + import config._ val executable = Fork.javaCommand(javaHome, commandName).getAbsolutePath val preOptions = makeOptions(runJVMOptions, bootJars, arguments) val (classpathEnv, options) = Fork.fitClasspath(preOptions) val command = executable +: options - - val environment: List[(String, String)] = env.toList ++ - (classpathEnv map { value => - Fork.ClasspathEnvKey -> value - }) val jpb = if (Fork.shouldUseArgumentsFile(options)) new JProcessBuilder(executable, Fork.createArgumentsFile(options)) else new JProcessBuilder(command.toArray: _*) - workingDirectory foreach (jpb directory _) - environment foreach { case (k, v) => jpb.environment.put(k, v) } - if (connectInput) { - jpb.redirectInput(Redirect.INHERIT) - () - } - val process = Process(jpb) - - outputStrategy.getOrElse(StdoutOutput: OutputStrategy) match { - case StdoutOutput => process.run(connectInput = false) - case out: BufferedOutput => - out.logger.buffer { process.run(out.logger, connectInput = false) } - case out: LoggedOutput => process.run(out.logger, connectInput = false) - case out: CustomOutput => (process #> out.output).run(connectInput = false) + val extraEnv = classpathEnv.toList.map { value => + Fork.ClasspathEnvKey -> value } + Fork.forkInternal(config, extraEnv, jpb) } + private[this] def makeOptions( jvmOptions: Seq[String], bootJars: Iterable[File], @@ -185,4 +163,36 @@ object Fork { pw.close() s"@${file.getAbsolutePath}" } + + private[sbt] def forkInternal( + config: ForkOptions, + extraEnv: List[(String, String)], + jpb: JProcessBuilder + ): Process = { + import config.{ envVars => env, _ } + val environment: List[(String, String)] = env.toList ++ extraEnv + workingDirectory.foreach(jpb directory _) + environment.foreach { case (k, v) => jpb.environment.put(k, v) } + if (connectInput) { + jpb.redirectInput(Redirect.INHERIT) + () + } + val process = Process(jpb) + outputStrategy.getOrElse(StdoutOutput: OutputStrategy) match { + case StdoutOutput => process.run(connectInput = false) + case out: BufferedOutput => + out.logger.buffer { process.run(out.logger, connectInput = false) } + case out: LoggedOutput => process.run(out.logger, connectInput = false) + case out: CustomOutput => (process #> out.output).run(connectInput = false) + } + } + + private[sbt] def blockForExitCode(p: Process): Int = { + RunningProcesses.add(p) + try p.exitValue() + finally { + if (p.isAlive()) p.destroy() + RunningProcesses.remove(p) + } + } } diff --git a/run/src/main/scala/sbt/Run.scala b/run/src/main/scala/sbt/Run.scala index 9e46f2b46c..cda9e58884 100644 --- a/run/src/main/scala/sbt/Run.scala +++ b/run/src/main/scala/sbt/Run.scala @@ -26,25 +26,16 @@ sealed trait ScalaRun { } class ForkRun(config: ForkOptions) extends ScalaRun { def run(mainClass: String, classpath: Seq[File], options: Seq[String], log: Logger): Try[Unit] = { - def processExitCode(exitCode: Int, label: String): Try[Unit] = - if (exitCode == 0) Success(()) - else - Failure( - new MessageOnlyException( - s"""Nonzero exit code returned from $label: $exitCode""".stripMargin - ) - ) - log.info(s"running (fork) $mainClass ${Run.runOptionsStr(options)}") val c = configLogged(log) val scalaOpts = scalaOptions(mainClass, classpath, options) val exitCode = try Fork.java(c, scalaOpts) catch { case _: InterruptedException => - log.warn("Run canceled.") + log.warn("run canceled") 1 } - processExitCode(exitCode, "runner") + Run.processExitCode(exitCode, "runner") } def fork(mainClass: String, classpath: Seq[File], options: Seq[String], log: Logger): Process = { @@ -58,7 +49,7 @@ class ForkRun(config: ForkOptions) extends ScalaRun { } private def configLogged(log: Logger): ForkOptions = { - if (config.outputStrategy.isDefined) config + if (config.outputStrategy.isDefined || config.connectInput) config else config.withOutputStrategy(OutputStrategy.LoggedOutput(log)) } @@ -195,4 +186,13 @@ object Run { case str if str.contains(" ") => "\"" + str + "\"" case str => str }).mkString(" ") + + private[sbt] def processExitCode(exitCode: Int, label: String): Try[Unit] = + if (exitCode == 0) Success(()) + else + Failure( + new MessageOnlyException( + s"""nonzero exit code returned from $label: $exitCode""".stripMargin + ) + ) } diff --git a/sbt b/sbt index d178f16c3f..3c7e9ab015 100755 --- a/sbt +++ b/sbt @@ -1,7 +1,7 @@ #!/usr/bin/env bash set +e -declare builtin_sbt_version="1.10.7" +declare builtin_sbt_version="1.10.9" declare -a residual_args declare -a java_args declare -a scalac_args @@ -24,7 +24,7 @@ declare build_props_sbt_version= declare use_sbtn= declare no_server= declare sbtn_command="$SBTN_CMD" -declare sbtn_version="1.10.5" +declare sbtn_version="1.10.8" declare use_colors=1 ### ------------------------------- ### @@ -200,6 +200,7 @@ acquire_sbtn () { exit 2 fi elif [[ "$OSTYPE" == "darwin"* ]]; then + arch="universal" archive_target="$p/sbtn-universal-apple-darwin-${sbtn_v}.tar.gz" url="https://github.com/sbt/sbtn-dist/releases/download/v${sbtn_v}/sbtn-universal-apple-darwin-${sbtn_v}.tar.gz" elif [[ "$OSTYPE" == "cygwin" ]] || [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "win32" ]]; then @@ -638,7 +639,7 @@ process_my_args () { -allow-empty|--allow-empty|-sbt-create|--sbt-create) allow_empty=true && shift ;; - new) sbt_new=true && addResidual "$1" && shift ;; + new|init) sbt_new=true && addResidual "$1" && shift ;; *) addResidual "$1" && shift ;; esac diff --git a/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-warn/a/A.scala b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-warn/a/A.scala new file mode 100644 index 0000000000..c66551e1bd --- /dev/null +++ b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-warn/a/A.scala @@ -0,0 +1,27 @@ +import scala.language.reflectiveCalls + + +package scala.collection.immutable { + object Exp { + // Access RedBlackTree.validate added in Scala 2.13.13 + def v = RedBlackTree.validate(null)(null) + } +} + + +object A extends App { + println(scala.util.Properties.versionString) +} + +object AMacro { + import scala.language.experimental.macros + import scala.reflect.macros.blackbox.Context + + def m(x: Int): Int = macro impl + + def impl(c: Context)(x: c.Expr[Int]): c.Expr[Int] = { + import c.universe._ + println(scala.collection.immutable.Exp.v) + c.Expr(q"2 + $x") + } +} diff --git a/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-warn/b/B.scala b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-warn/b/B.scala new file mode 100644 index 0000000000..f75b7905ef --- /dev/null +++ b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-warn/b/B.scala @@ -0,0 +1,7 @@ +import java.nio.file.{Paths, Files} +import java.nio.charset.StandardCharsets + +object B extends App { + println(AMacro.m(33)) // fails + Files.write(Paths.get(s"s${scala.util.Properties.versionNumberString}.txt"), "nix".getBytes) +} diff --git a/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-warn/build.sbt b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-warn/build.sbt new file mode 100644 index 0000000000..c757f26481 --- /dev/null +++ b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-warn/build.sbt @@ -0,0 +1,39 @@ +import sbt.librarymanagement.InclExclRule + +lazy val a = project.settings( + scalaVersion := "2.13.13", + libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value, + TaskKey[Unit]("checkLibs") := checkLibs("2.13.13", (Compile/dependencyClasspath).value, ".*scala-(library|reflect).*"), +) + +lazy val b = project.dependsOn(a).settings( + allowUnsafeScalaLibUpgrade := true, + scalaVersion := "2.13.12", + + // dependencies are upgraded to 2.13.13 + TaskKey[Unit]("checkLibs") := checkLibs("2.13.13", (Compile/dependencyClasspath).value, ".*scala-(library|reflect).*"), + + // check the compiler uses the 2.13.12 library on its runtime classpath + TaskKey[Unit]("checkScala") := { + val i = scalaInstance.value + i.libraryJars.filter(_.toString.contains("scala-library")).toList match { + case List(l) => assert(l.toString.contains("2.13.12"), i.toString) + } + assert(i.compilerJars.filter(_.toString.contains("scala-library")).isEmpty, i.toString) + assert(i.otherJars.filter(_.toString.contains("scala-library")).isEmpty, i.toString) + }, +) + +lazy val c = project.dependsOn(a).settings( + allowUnsafeScalaLibUpgrade := true, + scalaVersion := "2.13.12", + TaskKey[Unit]("checkLibs") := checkLibs("2.13.13", (Compile/dependencyClasspath).value, ".*scala-(library|reflect).*"), +) + +def checkLibs(v: String, cp: Classpath, filter: String): Unit = { + for (p <- cp) + if (p.toString.matches(filter)) { + println(s"$p -- $v") + assert(p.toString.contains(v), p) + } +} diff --git a/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-warn/c/C.scala b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-warn/c/C.scala new file mode 100644 index 0000000000..de0f7c0848 --- /dev/null +++ b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-warn/c/C.scala @@ -0,0 +1,7 @@ +import java.nio.file.{Paths, Files} +import java.nio.charset.StandardCharsets + +object C extends App { + assert(scala.collection.immutable.Exp.v == null) + Files.write(Paths.get(s"s${scala.util.Properties.versionNumberString}.txt"), "nix".getBytes) +} diff --git a/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-warn/test b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-warn/test new file mode 100644 index 0000000000..f347b35342 --- /dev/null +++ b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-warn/test @@ -0,0 +1,11 @@ +> a/checkLibs +> b/checkLibs +> b/checkScala +> c/checkLibs + +# macro expansion fails +-> b/compile + +> c/run +$ exists s2.13.13.txt +$ delete s2.13.13.txt diff --git a/sbt-app/src/sbt-test/project/scripted13/test b/sbt-app/src/sbt-test/project/scripted13/test index d40ea3d077..2fd6077fb1 100644 --- a/sbt-app/src/sbt-test/project/scripted13/test +++ b/sbt-app/src/sbt-test/project/scripted13/test @@ -1,6 +1,6 @@ # This tests that this sbt scripted plugin can launch the previous one -> ^^1.10.6 +> ^^1.10.7 $ copy-file changes/A.scala src/sbt-test/a/b/A.scala > scripted diff --git a/server-test/src/server-test/client/build.sbt b/server-test/src/server-test/client/build.sbt index 3225bd76da..686d2a7a8d 100644 --- a/server-test/src/server-test/client/build.sbt +++ b/server-test/src/server-test/client/build.sbt @@ -1,7 +1,9 @@ +scalaVersion := "3.6.3" + TaskKey[Unit]("willSucceed") := println("success") TaskKey[Unit]("willFail") := { throw new Exception("failed") } -libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.8" % "test" +libraryDependencies += "org.scalameta" %% "munit" % "1.0.4" % Test TaskKey[Unit]("fooBar") := { () } diff --git a/server-test/src/server-test/client/src/main/scala/A.scala b/server-test/src/server-test/client/src/main/scala/A.scala index 69c493db21..171b96e913 100644 --- a/server-test/src/server-test/client/src/main/scala/A.scala +++ b/server-test/src/server-test/client/src/main/scala/A.scala @@ -1 +1,3 @@ -object A +class A + +@main def hello() = println("Hello, World!") diff --git a/server-test/src/server-test/client/src/test/scala/FooSpec.scala b/server-test/src/server-test/client/src/test/scala/FooSpec.scala index 269be56244..fb5352fd9b 100644 --- a/server-test/src/server-test/client/src/test/scala/FooSpec.scala +++ b/server-test/src/server-test/client/src/test/scala/FooSpec.scala @@ -1,3 +1,3 @@ package test.pkg -class FooSpec extends org.scalatest.FlatSpec +class FooSpec extends munit.FunSuite diff --git a/server-test/src/test/scala/testpkg/BuildServerTest.scala b/server-test/src/test/scala/testpkg/BuildServerTest.scala index 15ec8a1da9..0c0306fe9d 100644 --- a/server-test/src/test/scala/testpkg/BuildServerTest.scala +++ b/server-test/src/test/scala/testpkg/BuildServerTest.scala @@ -53,6 +53,16 @@ object BuildServerTest extends AbstractServerTest { result.targets.find(_.displayName.contains("buildserver-build")).get assert(buildServerBuildTarget.id.uri.toString.endsWith("#buildserver-build")) assert(!result.targets.exists(_.displayName.contains("badBuildTarget"))) + // Check for JVM based Scala Project, built target should contain Java version information + val scalaBuildTarget = + Converter.fromJsonOptionUnsafe[ScalaBuildTarget](utilTarget.data) + val javaTarget = scalaBuildTarget.jvmBuildTarget + (javaTarget.flatMap(_.javaVersion), javaTarget.flatMap(_.javaHome)) match { + case (Some(javaVersion), Some(javaHome)) => + assert(javaVersion.equals(sys.props("java.version"))) + assert(javaHome.equals(Paths.get(sys.props("java.home")).toUri)) + case _ => fail("JVM build target should contain javaVersion and javaHome") + } } test("buildTarget/sources") { _ => @@ -218,7 +228,6 @@ object BuildServerTest extends AbstractServerTest { val buildTarget = buildTargetUri("javaProj", "Compile") compile(buildTarget) - assertMessage( "build/publishDiagnostics", "Hello.java", @@ -241,16 +250,17 @@ object BuildServerTest extends AbstractServerTest { val testFile = new File(svr.baseDirectory, s"java-proj/src/main/java/example/Hello.java") val otherBuildFile = new File(svr.baseDirectory, "force-java-out-of-process-compiler.sbt") - // Setting `javaHome` will force SBT to shell out to an external Java compiler instead + // Setting `javaHome` will force sbt to shell out to an external Java compiler instead // of using the local compilation service offered by the JVM running this SBT instance. IO.write( otherBuildFile, """ + |def jdk: File = sbt.internal.util.Util.javaHome.toFile() |lazy val javaProj = project | .in(file("java-proj")) | .settings( | javacOptions += "-Xlint:all", - | javaHome := Some(file(System.getProperty("java.home"))) + | javaHome := Some(jdk) | ) |""".stripMargin ) @@ -262,16 +272,17 @@ object BuildServerTest extends AbstractServerTest { "build/publishDiagnostics", "Hello.java", """"severity":2""", - """found raw type: List""" - )(message = "should send publishDiagnostics with severity 2 for Hello.java") + """found raw type""" + )(message = "should send publishDiagnostics with severity 2 for Hello.java", debug = false) assertMessage( "build/publishDiagnostics", "Hello.java", """"severity":1""", - """incompatible types: int cannot be converted to String""" + """incompatible types: int cannot be converted""" )( - message = "should send publishDiagnostics with severity 1 for Hello.java" + message = "should send publishDiagnostics with severity 1 for Hello.java", + debug = true ) // Note the messages changed slightly in both cases. That's interesting… @@ -294,6 +305,7 @@ object BuildServerTest extends AbstractServerTest { compile(buildTarget) + /* assertMessage( "build/publishDiagnostics", "Hello.java", @@ -302,6 +314,7 @@ object BuildServerTest extends AbstractServerTest { )( message = "should send publishDiagnostics with empty diagnostics" ) + */ IO.delete(otherBuildFile) reloadWorkspace() @@ -675,6 +688,11 @@ object BuildServerTest extends AbstractServerTest { def assertion = svr.waitForString(duration) { msg => if (debug) println(msg) + if (debug) + parts.foreach { p => + if (msg.contains(p)) println(s"> $msg contains $p") + else () + } parts.forall(msg.contains) } if (message.nonEmpty) assert.apply(assertion, message) else assert(assertion) diff --git a/server-test/src/test/scala/testpkg/ClientTest.scala b/server-test/src/test/scala/testpkg/ClientTest.scala index b4352062a0..628cbbda3b 100644 --- a/server-test/src/test/scala/testpkg/ClientTest.scala +++ b/server-test/src/test/scala/testpkg/ClientTest.scala @@ -57,7 +57,7 @@ object ClientTest extends AbstractServerTest { case r => r } } - private def client(args: String*): Int = { + private def client(args: String*): Int = background( NetworkClient.client( testPath.toFile, @@ -68,6 +68,19 @@ object ClientTest extends AbstractServerTest { false ) ) + def clientWithStdoutLines(args: String*): (Int, Seq[String]) = { + val out = new CachingPrintStream + val exitCode = background( + NetworkClient.client( + testPath.toFile, + args.toArray, + NullInputStream, + out, + NullPrintStream, + false + ) + ) + (exitCode, out.lines) } // This ensures that the completion command will send a tab that triggers // sbt to call definedTestNames or discoveredMainClasses if there hasn't diff --git a/server-test/src/test/scala/testpkg/TestServer.scala b/server-test/src/test/scala/testpkg/TestServer.scala index dac89ff79e..c3a78bc6ad 100644 --- a/server-test/src/test/scala/testpkg/TestServer.scala +++ b/server-test/src/test/scala/testpkg/TestServer.scala @@ -220,7 +220,7 @@ case class TestServer( // initiate handshake sendJsonRpc( - s"""{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "initializationOptions": { "skipAnalysis": true } } }""" + s"""{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "initializationOptions": { "skipAnalysis": true, "canWork": true } } }""" ) def test(f: TestServer => Future[Assertion]): Future[Assertion] = {