Port commandProj

This commit is contained in:
Eugene Yokota 2022-01-30 13:23:36 -05:00
parent 7a5533e8fa
commit 4ff27d7ab7
16 changed files with 253 additions and 235 deletions

View File

@ -10,10 +10,10 @@ import scala.util.Try
// ThisBuild settings take lower precedence, // ThisBuild settings take lower precedence,
// but can be shared across the multi projects. // but can be shared across the multi projects.
ThisBuild / version := { ThisBuild / version := {
val v = "1.8.1-SNAPSHOT" val v = "2.0.0-alpha1-SNAPSHOT"
nightlyVersion.getOrElse(v) nightlyVersion.getOrElse(v)
} }
ThisBuild / version2_13 := "2.0.0-SNAPSHOT" ThisBuild / version2_13 := "2.0.0-alpha1-SNAPSHOT"
ThisBuild / versionScheme := Some("early-semver") ThisBuild / versionScheme := Some("early-semver")
ThisBuild / scalafmtOnCompile := !(Global / insideCI).value ThisBuild / scalafmtOnCompile := !(Global / insideCI).value
ThisBuild / Test / scalafmtOnCompile := !(Global / insideCI).value ThisBuild / Test / scalafmtOnCompile := !(Global / insideCI).value
@ -183,7 +183,6 @@ lazy val sbtRoot: Project = (project in file("."))
.aggregate( .aggregate(
(allProjects diff Seq( (allProjects diff Seq(
actionsProj, actionsProj,
commandProj,
mainSettingsProj, mainSettingsProj,
zincLmIntegrationProj, zincLmIntegrationProj,
scriptedSbtReduxProj, scriptedSbtReduxProj,

View File

@ -19,7 +19,7 @@ object BasicCommandStrings {
val TemplateCommand: String = "new" val TemplateCommand: String = "new"
val Cancel: String = "cancel" val Cancel: String = "cancel"
/** The command name to terminate the program.*/ /** The command name to terminate the program. */
val TerminateAction: String = Exit val TerminateAction: String = Exit
def helpBrief: (String, String) = def helpBrief: (String, String) =
@ -74,16 +74,16 @@ $HelpCommand <regular expression>
private[this] def logLevelDetail(level: Level.Value): String = private[this] def logLevelDetail(level: Level.Value): String =
s"""$level s"""$level
Sets the global logging level to $level. Sets the global logging level to $level.
This will be used as the default level for logging from commands, settings, and tasks. This will be used as the default level for logging from commands, settings, and tasks.
Any explicit `logLevel` configuration in a project overrides this setting. Any explicit `logLevel` configuration in a project overrides this setting.
-$level OR --$level -$level OR --$level
Sets the global logging level as described above, but does so before any other commands are executed on startup, including project loading. Sets the global logging level as described above, but does so before any other commands are executed on startup, including project loading.
This is useful as a startup option: This is useful as a startup option:
* it takes effect before any logging occurs * it takes effect before any logging occurs
* if no other commands are passed, interactive mode is still entered * if no other commands are passed, interactive mode is still entered
""" """
def runEarly(command: String): String = s"$EarlyCommand($command)" def runEarly(command: String): String = s"$EarlyCommand($command)"
@ -102,8 +102,8 @@ $HelpCommand <regular expression>
val EarlyCommandDetailed: String = val EarlyCommandDetailed: String =
s"""$EarlyCommand(<command>) s"""$EarlyCommand(<command>)
Schedules an early command, which will be run before other commands on the command line. Schedules an early command, which will be run before other commands on the command line.
The order is preserved between all early commands, so `sbt "early(a)" "early(b)"` executes `a` and `b` in order. The order is preserved between all early commands, so `sbt "early(a)" "early(b)"` executes `a` and `b` in order.
""" """
def addPluginSbtFileHelp(): Help = { def addPluginSbtFileHelp(): Help = {
@ -119,21 +119,21 @@ $HelpCommand <regular expression>
def ReadDetailed: String = def ReadDetailed: String =
ReadCommand + ReadFiles + """ ReadCommand + ReadFiles + """
Reads the lines from the given files and inserts them as commands. Reads the lines from the given files and inserts them as commands.
All empty lines and lines that start with '#' are ignored. All empty lines and lines that start with '#' are ignored.
If a file does not exist or is not readable, this command fails. If a file does not exist or is not readable, this command fails.
All the lines from all the files are read before any of the commands All the lines from all the files are read before any of the commands
are executed. Thus, if any file is not readable, none of commands are executed. Thus, if any file is not readable, none of commands
from any of the files (even the existing ones) will be run. from any of the files (even the existing ones) will be run.
You probably need to escape this command if entering it at your shell.""" You probably need to escape this command if entering it at your shell."""
def ApplyCommand: String = "apply" def ApplyCommand: String = "apply"
def ApplyDetailed: String = def ApplyDetailed: String =
ApplyCommand + """ [-cp|-classpath <classpath>] <module-name>* ApplyCommand + """ [-cp|-classpath <classpath>] <module-name>*
Transforms the current State by calling <module-name>.apply(currentState) for each listed module name. Transforms the current State by calling <module-name>.apply(currentState) for each listed module name.
Here, currentState is of type sbt.State. Here, currentState is of type sbt.State.
If a classpath is provided, modules are loaded from a new class loader for this classpath. If a classpath is provided, modules are loaded from a new class loader for this classpath.
""" """
@ -143,14 +143,14 @@ $HelpCommand <regular expression>
def RebootDetailed: String = def RebootDetailed: String =
RebootCommand + """ [dev | full] RebootCommand + """ [dev | full]
This command is equivalent to exiting sbt, restarting, and running the This command is equivalent to exiting sbt, restarting, and running the
remaining commands with the exception that the JVM is not shut down. remaining commands with the exception that the JVM is not shut down.
If 'dev' is specified, the current sbt artifacts from the boot directory If 'dev' is specified, the current sbt artifacts from the boot directory
(`~/.sbt/boot` by default) are deleted before restarting. (`~/.sbt/boot` by default) are deleted before restarting.
This forces an update of sbt and Scala, which is useful when working with development This forces an update of sbt and Scala, which is useful when working with development
versions of sbt. versions of sbt.
If 'full' is specified, the boot directory is wiped out before restarting. If 'full' is specified, the boot directory is wiped out before restarting.
""" """
def Multi: String = ";" def Multi: String = ";"
@ -197,8 +197,8 @@ $AliasCommand name=
def StartServer = "startServer" def StartServer = "startServer"
def StartServerDetailed: String = def StartServerDetailed: String =
s"""$StartServer s"""$StartServer
Starts the server if it has not been started. This is intended to be used with Starts the server if it has not been started. This is intended to be used with
-Dsbt.server.autostart=false.""" -Dsbt.server.autostart=false."""
def ServerDetailed: String = def ServerDetailed: String =
"--server always runs sbt in not-daemon mode." "--server always runs sbt in not-daemon mode."
@ -243,7 +243,7 @@ $AliasCommand name=
def IfLastDetailed = def IfLastDetailed =
s"""$IfLast <command> s"""$IfLast <command>
$IfLastCommon""" $IfLastCommon"""
val ContinuousExecutePrefix = "~" val ContinuousExecutePrefix = "~"
def continuousDetail: String = "Executes the specified command whenever source files change." def continuousDetail: String = "Executes the specified command whenever source files change."

View File

@ -74,7 +74,9 @@ object BasicCommands {
def early: Command = Command.arb(earlyParser, earlyHelp)((s, other) => other :: s) def early: Command = Command.arb(earlyParser, earlyHelp)((s, other) => other :: s)
private[this] def levelParser: Parser[String] = private[this] def levelParser: Parser[String] =
Iterator(Level.Debug, Level.Info, Level.Warn, Level.Error) map (l => token(l.toString)) reduce (_ | _) Iterator(Level.Debug, Level.Info, Level.Warn, Level.Error) map (l =>
token(l.toString)
) reduce (_ | _)
private[this] def addPluginSbtFileParser: Parser[File] = { private[this] def addPluginSbtFileParser: Parser[File] = {
token(AddPluginSbtFileCommand) ~> (":" | "=" | Space.map(_.toString)) ~> (StringBasic).examples( token(AddPluginSbtFileCommand) ~> (":" | "=" | Space.map(_.toString)) ~> (StringBasic).examples(
@ -87,8 +89,8 @@ object BasicCommands {
private[this] def addPluginSbtFileStringParser: Parser[String] = { private[this] def addPluginSbtFileStringParser: Parser[String] = {
token( token(
token(AddPluginSbtFileCommand) ~ (":" | "=" | Space.map(_.toString)) ~ (StringBasic) token(AddPluginSbtFileCommand) ~ (":" | "=" | Space.map(_.toString)) ~ (StringBasic)
.examples("/some/extra.sbt") map { .examples("/some/extra.sbt") map { case s1 ~ s2 ~ s3 =>
case s1 ~ s2 ~ s3 => s1 + s2 + s3 s1 + s2 + s3
} }
) )
} }
@ -106,7 +108,7 @@ object BasicCommands {
* Adds additional *.sbt to the plugin build. * Adds additional *.sbt to the plugin build.
* This must be combined with early command as: --addPluginSbtFile=/tmp/extra.sbt * This must be combined with early command as: --addPluginSbtFile=/tmp/extra.sbt
*/ */
def addPluginSbtFile: Command = Command.arb(_ => addPluginSbtFileParser, addPluginSbtFileHelp) { def addPluginSbtFile: Command = Command.arb(_ => addPluginSbtFileParser, addPluginSbtFileHelp()) {
(s, extraSbtFile) => (s, extraSbtFile) =>
val extraFiles = s.get(BasicKeys.extraMetaSbtFiles).toList.flatten val extraFiles = s.get(BasicKeys.extraMetaSbtFiles).toList.flatten
s.put(BasicKeys.extraMetaSbtFiles, (extraFiles: Seq[File]) :+ extraSbtFile) s.put(BasicKeys.extraMetaSbtFiles, (extraFiles: Seq[File]) :+ extraSbtFile)
@ -115,10 +117,9 @@ object BasicCommands {
def help: Command = Command.make(HelpCommand, helpBrief, helpDetailed)(helpParser) def help: Command = Command.make(HelpCommand, helpBrief, helpDetailed)(helpParser)
def helpParser(s: State): Parser[() => State] = { def helpParser(s: State): Parser[() => State] = {
val h = s.definedCommands.foldLeft(Help.empty)( val h = s.definedCommands.foldLeft(Help.empty)((a, b) =>
(a, b) => a ++ (try b.help(s)
a ++ (try b.help(s) catch { case NonFatal(_) => Help.empty })
catch { case NonFatal(_) => Help.empty })
) )
val helpCommands = h.detail.keySet val helpCommands = h.detail.keySet
val spacedArg = singleArgument(helpCommands).? val spacedArg = singleArgument(helpCommands).?
@ -136,8 +137,9 @@ object BasicCommands {
case Nil => none[String] case Nil => none[String]
case xs => xs.mkString(" ").some case xs => xs.mkString(" ").some
} }
val message = try Help.message(h, topic) val message =
catch { case NonFatal(ex) => ex.toString } try Help.message(h, topic)
catch { case NonFatal(ex) => ex.toString }
System.out.println(message) System.out.println(message)
s.copy(remainingCommands = remainingCommands) s.copy(remainingCommands = remainingCommands)
} }
@ -276,8 +278,8 @@ object BasicCommands {
matched((s.combinedParser: Parser[_]) | token(any, hide = const(true))) matched((s.combinedParser: Parser[_]) | token(any, hide = const(true)))
def ifLast: Command = def ifLast: Command =
Command(IfLast, Help.more(IfLast, IfLastDetailed))(otherCommandParser)( Command(IfLast, Help.more(IfLast, IfLastDetailed))(otherCommandParser)((s, arg) =>
(s, arg) => if (s.remainingCommands.isEmpty) arg :: s else s if (s.remainingCommands.isEmpty) arg :: s else s
) )
def append: Command = def append: Command =
@ -286,15 +288,15 @@ object BasicCommands {
) )
def setOnFailure: Command = def setOnFailure: Command =
Command(OnFailure, Help.more(OnFailure, OnFailureDetailed))(otherCommandParser)( Command(OnFailure, Help.more(OnFailure, OnFailureDetailed))(otherCommandParser)((s, arg) =>
(s, arg) => s.copy(onFailure = Some(Exec(arg, s.source))) s.copy(onFailure = Some(Exec(arg, s.source)))
) )
def clearOnFailure: Command = Command.command(ClearOnFailure)(s => s.copy(onFailure = None)) def clearOnFailure: Command = Command.command(ClearOnFailure)(s => s.copy(onFailure = None))
def stashOnFailure: Command = def stashOnFailure: Command =
Command.command(StashOnFailure)( Command.command(StashOnFailure)(s =>
s => s.copy(onFailure = None).update(OnFailureStack)(s.onFailure :: _.toList.flatten) s.copy(onFailure = None).update(OnFailureStack)(s.onFailure :: _.toList.flatten)
) )
def popOnFailure: Command = Command.command(PopOnFailure) { s => def popOnFailure: Command = Command.command(PopOnFailure) { s =>
@ -346,8 +348,8 @@ object BasicCommands {
private[this] def className: Parser[String] = { private[this] def className: Parser[String] = {
val base = StringBasic & not('-' ~> any.*, "Class name cannot start with '-'.") val base = StringBasic & not('-' ~> any.*, "Class name cannot start with '-'.")
def single(s: String) = Completions.single(Completion.displayOnly(s)) def single(s: String) = Completions.single(Completion.displayOnly(s))
val compl = TokenCompletions.fixed( val compl = TokenCompletions.fixed((seen, _) =>
(seen, _) => if (seen.startsWith("-")) Completions.nil else single("<class name>") if (seen.startsWith("-")) Completions.nil else single("<class name>")
) )
token(base, compl) token(base, compl)
} }
@ -391,7 +393,7 @@ object BasicCommands {
val lines = hp.toList.flatMap(p => IO.readLines(p)).toIndexedSeq val lines = hp.toList.flatMap(p => IO.readLines(p)).toIndexedSeq
histFun(CHistory(lines, hp)) match { histFun(CHistory(lines, hp)) match {
case Some(commands) => case Some(commands) =>
commands foreach println //printing is more appropriate than logging commands foreach println // printing is more appropriate than logging
(commands ::: s).continue (commands ::: s).continue
case None => s.fail case None => s.fail
} }

View File

@ -9,6 +9,7 @@ package sbt
import java.io.File import java.io.File
import java.util.regex.{ Pattern, PatternSyntaxException } import java.util.regex.{ Pattern, PatternSyntaxException }
import scala.collection.immutable.StringOps
import sbt.internal.util.AttributeKey import sbt.internal.util.AttributeKey
import sbt.internal.util.complete.Parser import sbt.internal.util.complete.Parser
@ -42,7 +43,7 @@ object CommandUtil {
for ((a, b) <- in) yield pre + fill(a, width) + sep + b for ((a, b) <- in) yield pre + fill(a, width) + sep + b
} }
def fill(s: String, size: Int): String = s + " " * math.max(size - s.length, 0) def fill(s: String, size: Int): String = s + StringOps(" ") * math.max(size - s.length, 0)
def withAttribute[T](s: State, key: AttributeKey[T], ifMissing: String)(f: T => State): State = def withAttribute[T](s: State, key: AttributeKey[T], ifMissing: String)(f: T => State): State =
s get key match { s get key match {
@ -74,16 +75,15 @@ object CommandUtil {
def searchHelp(selected: String, detailMap: Map[String, String]): Map[String, String] = { def searchHelp(selected: String, detailMap: Map[String, String]): Map[String, String] = {
val pattern = Pattern.compile(selected, HelpPatternFlags) val pattern = Pattern.compile(selected, HelpPatternFlags)
detailMap flatMap { detailMap flatMap { case (k, v) =>
case (k, v) => val contentMatches = Highlight.showMatches(pattern)(v)
val contentMatches = Highlight.showMatches(pattern)(v) val keyMatches = Highlight.showMatches(pattern)(k)
val keyMatches = Highlight.showMatches(pattern)(k) val keyString = Highlight.bold(keyMatches getOrElse k)
val keyString = Highlight.bold(keyMatches getOrElse k) val contentString = contentMatches getOrElse v
val contentString = contentMatches getOrElse v if (keyMatches.isDefined || contentMatches.isDefined)
if (keyMatches.isDefined || contentMatches.isDefined) Seq((keyString, contentString))
Seq((keyString, contentString)) else
else nilSeq
nilSeq
} }
} }

View File

@ -99,19 +99,19 @@ trait Identity {
trait StateOps extends Any { trait StateOps extends Any {
def process(f: (Exec, State) => State): State def process(f: (Exec, State) => State): State
/** Schedules `commands` to be run before any remaining commands.*/ /** Schedules `commands` to be run before any remaining commands. */
def :::(newCommands: List[String]): State def :::(newCommands: List[String]): State
/** Schedules `commands` to be run before any remaining commands.*/ /** Schedules `commands` to be run before any remaining commands. */
def ++:(newCommands: List[Exec]): State def ++:(newCommands: List[Exec]): State
/** Schedules `command` to be run before any remaining commands.*/ /** Schedules `command` to be run before any remaining commands. */
def ::(command: String): State def ::(command: String): State
/** Schedules `command` to be run before any remaining commands.*/ /** Schedules `command` to be run before any remaining commands. */
def +:(command: Exec): State def +:(command: Exec): State
/** Sets the next command processing action to be to continue processing the next command.*/ /** Sets the next command processing action to be to continue processing the next command. */
def continue: State def continue: State
/** /**
@ -135,7 +135,7 @@ trait StateOps extends Any {
*/ */
private[sbt] def reboot(full: Boolean, currentOnly: Boolean): State private[sbt] def reboot(full: Boolean, currentOnly: Boolean): State
/** Sets the next command processing action to do.*/ /** Sets the next command processing action to do. */
def setNext(n: State.Next): State def setNext(n: State.Next): State
/** /**
@ -145,16 +145,16 @@ trait StateOps extends Any {
*/ */
def reload: State def reload: State
/** Sets the next command processing action to be to rotate the global log and continue executing commands.*/ /** Sets the next command processing action to be to rotate the global log and continue executing commands. */
def clearGlobalLog: State def clearGlobalLog: State
/** Sets the next command processing action to be to keep the previous log and continue executing commands. */ /** Sets the next command processing action to be to keep the previous log and continue executing commands. */
def keepLastLog: State def keepLastLog: State
/** Sets the next command processing action to be to exit with a zero exit code if `ok` is true and a nonzero exit code if `ok` if false.*/ /** Sets the next command processing action to be to exit with a zero exit code if `ok` is true and a nonzero exit code if `ok` if false. */
def exit(ok: Boolean): State def exit(ok: Boolean): State
/** Marks the currently executing command as failing. This triggers failure handling by the command processor. See also `State.onFailure`*/ /** Marks the currently executing command as failing. This triggers failure handling by the command processor. See also `State.onFailure` */
def fail: State def fail: State
/** /**
@ -171,46 +171,46 @@ trait StateOps extends Any {
/** Registers `newCommand` as an available command. */ /** Registers `newCommand` as an available command. */
def +(newCommand: Command): State def +(newCommand: Command): State
/** Gets the value associated with `key` from the custom attributes map.*/ /** Gets the value associated with `key` from the custom attributes map. */
def get[T](key: AttributeKey[T]): Option[T] def get[T](key: AttributeKey[T]): Option[T]
/** Sets the value associated with `key` in the custom attributes map.*/ /** Sets the value associated with `key` in the custom attributes map. */
def put[T](key: AttributeKey[T], value: T): State def put[T](key: AttributeKey[T], value: T): State
/** Removes the `key` and any associated value from the custom attributes map.*/ /** Removes the `key` and any associated value from the custom attributes map. */
def remove(key: AttributeKey[_]): State def remove(key: AttributeKey[_]): State
/** Sets the value associated with `key` in the custom attributes map by transforming the current value.*/ /** Sets the value associated with `key` in the custom attributes map by transforming the current value. */
def update[T](key: AttributeKey[T])(f: Option[T] => T): State def update[T](key: AttributeKey[T])(f: Option[T] => T): State
/** Returns true if `key` exists in the custom attributes map, false if it does not exist.*/ /** Returns true if `key` exists in the custom attributes map, false if it does not exist. */
def has(key: AttributeKey[_]): Boolean def has(key: AttributeKey[_]): Boolean
/** The application base directory, which is not necessarily the current working directory.*/ /** The application base directory, which is not necessarily the current working directory. */
def baseDir: File def baseDir: File
/** The Logger used for general command logging.*/ /** The Logger used for general command logging. */
def log: Logger def log: Logger
/** Evaluates the provided expression with a JVM-wide and machine-wide lock on `file`.*/ /** Evaluates the provided expression with a JVM-wide and machine-wide lock on `file`. */
def locked[T](file: File)(t: => T): T def locked[T](file: File)(t: => T): T
/** Runs any defined exitHooks and then clears them.*/ /** Runs any defined exitHooks and then clears them. */
def runExitHooks(): State def runExitHooks(): State
/** Registers a new exit hook, which will run when sbt exits or restarts.*/ /** Registers a new exit hook, which will run when sbt exits or restarts. */
def addExitHook(f: => Unit): State def addExitHook(f: => Unit): State
/** An advisory flag that is `true` if this application will execute commands based on user input.*/ /** An advisory flag that is `true` if this application will execute commands based on user input. */
def interactive: Boolean def interactive: Boolean
/** Changes the advisory `interactive` flag. */ /** Changes the advisory `interactive` flag. */
def setInteractive(flag: Boolean): State def setInteractive(flag: Boolean): State
/** Get the class loader cache for the application.*/ /** Get the class loader cache for the application. */
def classLoaderCache: IncClassLoaderCache def classLoaderCache: IncClassLoaderCache
/** Create and register a class loader cache. This should be called once at the application entry-point.*/ /** Create and register a class loader cache. This should be called once at the application entry-point. */
def initializeClassLoaderCache: State def initializeClassLoaderCache: State
} }
@ -220,22 +220,22 @@ object State {
override def getURLs: Array[URL] = cp.map(_.toURI.toURL).toArray override def getURLs: Array[URL] = cp.map(_.toURI.toURL).toArray
} }
/** Indicates where command execution should resume after a failure.*/ /** Indicates where command execution should resume after a failure. */
val FailureWall = BasicCommandStrings.FailureWall val FailureWall = BasicCommandStrings.FailureWall
/** Represents the next action for the command processor.*/ /** Represents the next action for the command processor. */
sealed trait Next sealed trait Next
/** Indicates that the command processor should process the next command.*/ /** Indicates that the command processor should process the next command. */
object Continue extends Next object Continue extends Next
/** Indicates that the application should exit with the given result.*/ /** Indicates that the application should exit with the given result. */
final class Return(val result: xsbti.MainResult) extends Next final class Return(val result: xsbti.MainResult) extends Next
/** Indicates that global logging should be rotated.*/ /** Indicates that global logging should be rotated. */
final object ClearGlobalLog extends Next final object ClearGlobalLog extends Next
/** Indicates that the previous log file should be preserved instead of discarded.*/ /** Indicates that the previous log file should be preserved instead of discarded. */
final object KeepLastLog extends Next final object KeepLastLog extends Next
/** /**
@ -246,21 +246,21 @@ object State {
*/ */
final class History private[State] (val executed: Seq[Exec], val maxSize: Int) { final class History private[State] (val executed: Seq[Exec], val maxSize: Int) {
/** Adds `command` as the most recently executed command.*/ /** Adds `command` as the most recently executed command. */
def ::(command: Exec): History = { def ::(command: Exec): History = {
val prependTo = val prependTo =
if (maxSize > 0 && executed.size >= maxSize) executed.take(maxSize - 1) else executed if (maxSize > 0 && executed.size >= maxSize) executed.take(maxSize - 1) else executed
new History(command +: prependTo, maxSize) new History(command +: prependTo, maxSize)
} }
/** Changes the maximum number of commands kept, adjusting the current history if necessary.*/ /** Changes the maximum number of commands kept, adjusting the current history if necessary. */
def setMaxSize(size: Int): History = def setMaxSize(size: Int): History =
new History(if (size <= 0) executed else executed.take(size), size) new History(if (size <= 0) executed else executed.take(size), size)
def currentOption: Option[Exec] = executed.headOption def currentOption: Option[Exec] = executed.headOption
def previous: Option[Exec] = executed.drop(1).headOption def previous: Option[Exec] = executed.drop(1).headOption
} }
/** Constructs an empty command History with a default, finite command limit.*/ /** Constructs an empty command History with a default, finite command limit. */
def newHistory = new History(Vector.empty, HistoryCommands.MaxLines) def newHistory = new History(Vector.empty, HistoryCommands.MaxLines)
def defaultReload(state: State): Reboot = { def defaultReload(state: State): Reboot = {
@ -385,9 +385,12 @@ object State {
s.copy(exitHooks = Set.empty) s.copy(exitHooks = Set.empty)
} }
def locked[T](file: File)(t: => T): T = def locked[T](file: File)(t: => T): T =
s.configuration.provider.scalaProvider.launcher.globalLock.apply(file, new Callable[T] { s.configuration.provider.scalaProvider.launcher.globalLock.apply(
def call = t file,
}) new Callable[T] {
def call = t
}
)
def interactive = getBoolean(s, BasicKeys.interactive, false) def interactive = getBoolean(s, BasicKeys.interactive, false)
def setInteractive(i: Boolean) = s.put(BasicKeys.interactive, i) def setInteractive(i: Boolean) = s.put(BasicKeys.interactive, i)

View File

@ -39,7 +39,7 @@ trait Watched {
*/ */
def antiEntropy: FiniteDuration = Watched.AntiEntropy def antiEntropy: FiniteDuration = Watched.AntiEntropy
/** The message to show when triggered execution waits for sources to change.*/ /** The message to show when triggered execution waits for sources to change. */
private[sbt] def watchingMessage(s: WatchState): String = Watched.defaultWatchingMessage(s) private[sbt] def watchingMessage(s: WatchState): String = Watched.defaultWatchingMessage(s)
/** The message to show before an action is run. */ /** The message to show before an action is run. */

View File

@ -91,7 +91,7 @@ abstract class CommandChannel {
if (cmd.nonEmpty) append(Exec(cmd, Some(Exec.newExecId), Some(CommandSource(name)))) if (cmd.nonEmpty) append(Exec(cmd, Some(Exec.newExecId), Some(CommandSource(name))))
else false else false
} }
private[sbt] def onFastTrackTask: String => Boolean = { s: String => private[sbt] def onFastTrackTask: String => Boolean = { (s: String) =>
fastTrack.synchronized(fastTrack.forEach { q => fastTrack.synchronized(fastTrack.forEach { q =>
q.add(new FastTrackTask(this, s)) q.add(new FastTrackTask(this, s))
() ()

View File

@ -46,15 +46,16 @@ private[sbt] object LegacyWatched {
case Some(eventMonitor) => case Some(eventMonitor) =>
Watched.printIfDefined(watched watchingMessage eventMonitor.state()) Watched.printIfDefined(watched watchingMessage eventMonitor.state())
@tailrec def impl(): State = { @tailrec def impl(): State = {
val triggered = try eventMonitor.awaitEvent() val triggered =
catch { try eventMonitor.awaitEvent()
case NonFatal(e) => catch {
log.error( case NonFatal(e) =>
"Error occurred obtaining files to watch. Terminating continuous execution..." log.error(
) "Error occurred obtaining files to watch. Terminating continuous execution..."
s.handleError(e) )
false s.handleError(e)
} false
}
if (triggered) { if (triggered) {
Watched.printIfDefined(watched triggeredMessage eventMonitor.state()) Watched.printIfDefined(watched triggeredMessage eventMonitor.state())
ClearOnFailure :: next :: FailureWall :: repeat :: s ClearOnFailure :: next :: FailureWall :: repeat :: s

View File

@ -38,20 +38,21 @@ private[sbt] class ClassLoaderCache(
def setParent(parent: ClassLoader): Unit = parentHolder.set(parent) def setParent(parent: ClassLoader): Unit = parentHolder.set(parent)
def this(commonParent: ClassLoader) = this(commonParent, None) def this(commonParent: ClassLoader) = this(commonParent, None)
def this(scalaProvider: ScalaProvider) = def this(scalaProvider: ScalaProvider) =
this(scalaProvider.launcher.topLoader, { this(
scalaProvider.jars.find(_.getName == "scala-library.jar").flatMap { lib => scalaProvider.launcher.topLoader, {
val clazz = scalaProvider.getClass scalaProvider.jars.find(_.getName == "scala-library.jar").flatMap { lib =>
try { val clazz = scalaProvider.getClass
val loader = clazz.getDeclaredMethod("libraryLoaderOnly").invoke(scalaProvider) try {
Some(lib -> loader.asInstanceOf[ClassLoader]) val loader = clazz.getDeclaredMethod("libraryLoaderOnly").invoke(scalaProvider)
} catch { case NonFatal(_) => None } Some(lib -> loader.asInstanceOf[ClassLoader])
} } catch { case NonFatal(_) => None }
}) }
private val scalaProviderKey = miniProvider.map {
case (f, cl) =>
new Key((f -> IO.getModifiedTimeOrZero(f)) :: Nil, commonParent) {
override def toClassLoader: ClassLoader = cl
} }
)
private val scalaProviderKey = miniProvider.map { case (f, cl) =>
new Key((f -> IO.getModifiedTimeOrZero(f)) :: Nil, commonParent) {
override def toClassLoader: ClassLoader = cl
}
} }
private class Key(val fileStamps: Seq[(File, Long)], val parent: ClassLoader) { private class Key(val fileStamps: Seq[(File, Long)], val parent: ClassLoader) {
def this(files: List[File], parent: ClassLoader) = def this(files: List[File], parent: ClassLoader) =
@ -102,19 +103,20 @@ private[sbt] class ClassLoaderCache(
start() start()
@tailrec @tailrec
override final def run(): Unit = { override final def run(): Unit = {
val stop = try { val stop =
referenceQueue.remove(1000) match { try {
case ClassLoaderReference(key, classLoader) => referenceQueue.remove(1000) match {
close(classLoader) case ClassLoaderReference(key, classLoader) =>
delegate.remove(key) close(classLoader)
() delegate.remove(key)
case _ => ()
case _ =>
}
clearExpiredLoaders()
false
} catch {
case _: InterruptedException => true
} }
clearExpiredLoaders()
false
} catch {
case _: InterruptedException => true
}
if (!stop) run() if (!stop) run()
} }
} }

View File

@ -25,7 +25,8 @@ object BspClient {
while (!terminated.get) lock.wait() while (!terminated.get) lock.wait()
} }
0 0
} catch { case _: Throwable => 1 } finally sbtServer.close() } catch { case _: Throwable => 1 }
finally sbtServer.close()
} }
private[sbt] def transferTo( private[sbt] def transferTo(

View File

@ -190,18 +190,19 @@ class NetworkClient(
} }
} }
@tailrec def connect(attempt: Int): (Socket, Option[String]) = { @tailrec def connect(attempt: Int): (Socket, Option[String]) = {
val res = try Some(mkSocket(portfile)) val res =
catch { try Some(mkSocket(portfile))
// This catches a pipe busy exception which can happen if two windows clients catch {
// attempt to connect in rapid succession // This catches a pipe busy exception which can happen if two windows clients
case e: IOException if e.getMessage.contains("Couldn't open") && attempt < 10 => // attempt to connect in rapid succession
if (e.getMessage.contains("Access is denied") || e.getMessage.contains("(5)")) { case e: IOException if e.getMessage.contains("Couldn't open") && attempt < 10 =>
errorStream.println(s"Access denied for portfile $portfile") if (e.getMessage.contains("Access is denied") || e.getMessage.contains("(5)")) {
throw new NetworkClient.AccessDeniedException errorStream.println(s"Access denied for portfile $portfile")
} throw new NetworkClient.AccessDeniedException
None }
case e: IOException => throw new ConnectionRefusedException(e) None
} case e: IOException => throw new ConnectionRefusedException(e)
}
res match { res match {
case Some(r) => r case Some(r) => r
case None => case None =>
@ -431,36 +432,37 @@ class NetworkClient(
} }
@tailrec @tailrec
def blockUntilStart(): Unit = { def blockUntilStart(): Unit = {
val stop = try { val stop =
socket match { try {
case None => socket match {
process.foreach { p => case None =>
val output = p.getInputStream process.foreach { p =>
while (output.available > 0) { val output = p.getInputStream
printStream.write(output.read()) while (output.available > 0) {
printStream.write(output.read())
}
} }
} case Some(s) =>
case Some(s) => while (!gotInputBack && !stdinBytes.isEmpty && socket.isDefined) {
while (!gotInputBack && !stdinBytes.isEmpty && socket.isDefined) { val out = s.getOutputStream
val out = s.getOutputStream val b = stdinBytes.poll
val b = stdinBytes.poll if (b == -1) {
if (b == -1) { // server waits for user input but stinBytes has ended
// server waits for user input but stinBytes has ended shutdown.run()
shutdown.run() } else {
} else { out.write(b)
out.write(b) out.flush()
out.flush() }
} }
}
}
process.foreach { p =>
val error = p.getErrorStream
while (error.available > 0) {
errorStream.write(error.read())
} }
} process.foreach { p =>
false val error = p.getErrorStream
} catch { case e: IOException => true } while (error.available > 0) {
errorStream.write(error.read())
}
}
false
} catch { case e: IOException => true }
Thread.sleep(10) Thread.sleep(10)
printStream.flush() printStream.flush()
errorStream.flush() errorStream.flush()
@ -489,7 +491,8 @@ class NetworkClient(
} }
try blockUntilStart() try blockUntilStart()
catch { case t: Throwable => t.printStackTrace() } finally { catch { case t: Throwable => t.printStackTrace() }
finally {
sbtProcess.set(null) sbtProcess.set(null)
Util.ignoreResult(Runtime.getRuntime.removeShutdownHook(shutdown)) Util.ignoreResult(Runtime.getRuntime.removeShutdownHook(shutdown))
} }
@ -558,23 +561,22 @@ class NetworkClient(
completions(msg.result match { completions(msg.result match {
case Some(o: JObject) => case Some(o: JObject) =>
o.value o.value
.foldLeft(CompletionResponse(Vector.empty[String])) { .foldLeft(CompletionResponse(Vector.empty[String])) { case (resp, i) =>
case (resp, i) => if (i.field == "items")
if (i.field == "items") resp.withItems(
resp.withItems( Converter
Converter .fromJson[Vector[String]](i.value)
.fromJson[Vector[String]](i.value) .getOrElse(Vector.empty[String])
.getOrElse(Vector.empty[String]) )
) else if (i.field == "cachedTestNames")
else if (i.field == "cachedTestNames") resp.withCachedTestNames(
resp.withCachedTestNames( Converter.fromJson[Boolean](i.value).getOrElse(true)
Converter.fromJson[Boolean](i.value).getOrElse(true) )
) else if (i.field == "cachedMainClassNames")
else if (i.field == "cachedMainClassNames") resp.withCachedMainClassNames(
resp.withCachedMainClassNames( Converter.fromJson[Boolean](i.value).getOrElse(true)
Converter.fromJson[Boolean](i.value).getOrElse(true) )
) else resp
else resp
} }
case _ => CompletionResponse(Vector.empty[String]) case _ => CompletionResponse(Vector.empty[String])
}) })
@ -632,8 +634,8 @@ class NetworkClient(
) )
) )
} }
splitToMessage foreach { splitToMessage foreach { case (level, msg) =>
case (level, msg) => console.appendLog(level, msg) console.appendLog(level, msg)
} }
} }
@ -802,7 +804,7 @@ class NetworkClient(
} }
withSignalHandler(handler, Signals.INT) { withSignalHandler(handler, Signals.INT) {
def block(): Int = { def block(): Int = {
try this.synchronized(this.wait) try this.synchronized(this.wait())
catch { case _: InterruptedException => } catch { case _: InterruptedException => }
if (exitClean.get) 0 else 1 if (exitClean.get) 0 else 1
} }
@ -989,7 +991,8 @@ class NetworkClient(
if (attached.get()) drain() if (attached.get()) drain()
} }
try read() try read()
catch { case _: InterruptedException | NonFatal(_) => stopped.set(true) } finally { catch { case _: InterruptedException | NonFatal(_) => stopped.set(true) }
finally {
inputThread.set(null) inputThread.set(null)
} }
} }
@ -1023,6 +1026,7 @@ class NetworkClient(
val secs = f"${total % 60}%02d" val secs = f"${total % 60}%02d"
s" ($maybeHours$mins:$secs)" s" ($maybeHours$mins:$secs)"
}) })
s"Total time: $totalString, completed $nowString" s"Total time: $totalString, completed $nowString"
} }
} }
@ -1156,7 +1160,8 @@ object NetworkClient {
try { try {
if (client.connect(log = true, promptCompleteUsers = false)) client.run() if (client.connect(log = true, promptCompleteUsers = false)) client.run()
else 1 else 1
} catch { case _: Exception => 1 } finally client.close() } catch { case _: Exception => 1 }
finally client.close()
} }
def client( def client(
baseDirectory: File, baseDirectory: File,
@ -1187,7 +1192,8 @@ object NetworkClient {
if (client.connect(log = true, promptCompleteUsers = false)) client.run() if (client.connect(log = true, promptCompleteUsers = false)) client.run()
else 1 else 1
} }
} catch { case _: Exception => 1 } finally client.close() } catch { case _: Exception => 1 }
finally client.close()
} }
def client( def client(
baseDirectory: File, baseDirectory: File,
@ -1240,7 +1246,8 @@ object NetworkClient {
System.exit(Terminal.withStreams(isServer = false, isSubProcess = false) { System.exit(Terminal.withStreams(isServer = false, isSubProcess = false) {
val term = Terminal.console val term = Terminal.console
try client(base, parsed, term.inputStream, System.err, term, useJNI) try client(base, parsed, term.inputStream, System.err, term, useJNI)
catch { case _: AccessDeniedException => 1 } finally { catch { case _: AccessDeniedException => 1 }
finally {
Runtime.getRuntime.removeShutdownHook(hook) Runtime.getRuntime.removeShutdownHook(hook)
hook.run() hook.run()
} }
@ -1286,7 +1293,8 @@ object NetworkClient {
else Nil else Nil
out.println(results.sorted.distinct mkString "\n") out.println(results.sorted.distinct mkString "\n")
0 0
} catch { case _: Exception => 1 } finally client.close() } catch { case _: Exception => 1 }
finally client.close()
} catch { case _: AccessDeniedException => 1 } } catch { case _: AccessDeniedException => 1 }
} }
@ -1301,7 +1309,8 @@ object NetworkClient {
val err = new PrintStream(term.errorStream) val err = new PrintStream(term.errorStream)
val out = if (redirectOutput) err else new PrintStream(term.outputStream) val out = if (redirectOutput) err else new PrintStream(term.outputStream)
val args = parseArgs(arguments.toArray).withBaseDirectory(configuration.baseDirectory) val args = parseArgs(arguments.toArray).withBaseDirectory(configuration.baseDirectory)
val useJNI = BootServerSocket.requiresJNI || System.getProperty("sbt.ipcsocket.jni", "false") == "true" val useJNI =
BootServerSocket.requiresJNI || System.getProperty("sbt.ipcsocket.jni", "false") == "true"
val client = simpleClient(args, term.inputStream, out, err, useJNI = useJNI) val client = simpleClient(args, term.inputStream, out, err, useJNI = useJNI)
clientImpl(client, args.bsp) clientImpl(client, args.bsp)
} }

View File

@ -114,7 +114,7 @@ private[sbt] object Server {
} catch { } catch {
case e: IOException if e.getMessage.contains("connect") => case e: IOException if e.getMessage.contains("connect") =>
case _: SocketTimeoutException => // its ok case _: SocketTimeoutException => // its ok
case _: SocketException if !running.get => // the server is shutting down case _: SocketException if !running.get => // the server is shutting down
} }
} }
serverSocketHolder.get match { serverSocketHolder.get match {

View File

@ -29,10 +29,10 @@ object ServerHandler {
lazy val fallback: ServerHandler = ServerHandler({ handler => lazy val fallback: ServerHandler = ServerHandler({ handler =>
ServerIntent( ServerIntent(
onRequest = { case x => handler.log.debug(s"Unhandled request received: ${x.method}: $x") }, onRequest = { case x => handler.log.debug(s"Unhandled request received: ${x.method}: $x") },
onResponse = { case x => handler.log.debug(s"Unhandled responce received") }, onResponse = { case x => handler.log.debug(s"Unhandled responce received") },
onNotification = { onNotification = { case x =>
case x => handler.log.debug(s"Unhandled notification received: ${x.method}: $x") handler.log.debug(s"Unhandled notification received: ${x.method}: $x")
}, },
) )
}) })

View File

@ -23,7 +23,7 @@ import scala.annotation.tailrec
private[sbt] trait UITask extends Runnable with AutoCloseable { private[sbt] trait UITask extends Runnable with AutoCloseable {
private[sbt] val channel: CommandChannel private[sbt] val channel: CommandChannel
private[sbt] val reader: UITask.Reader private[sbt] def reader: UITask.Reader
private[this] final def handleInput(s: Either[String, String]): Boolean = s match { private[this] final def handleInput(s: Either[String, String]): Boolean = s match {
case Left(m) => channel.onFastTrackTask(m) case Left(m) => channel.onFastTrackTask(m)
case Right(cmd) => channel.onCommand(cmd) case Right(cmd) => channel.onCommand(cmd)

View File

@ -45,7 +45,8 @@ private[sbt] object ReadJsonFromInputStream {
var content: Seq[Byte] = Seq.empty[Byte] var content: Seq[Byte] = Seq.empty[Byte]
var consecutiveLineEndings = 0 var consecutiveLineEndings = 0
var onCarriageReturn = false var onCarriageReturn = false
do {
def run(): Unit =
val byte = inputStream.read val byte = inputStream.read
byte match { byte match {
case `newline` => case `newline` =>
@ -54,33 +55,34 @@ private[sbt] object ReadJsonFromInputStream {
onCarriageReturn = false onCarriageReturn = false
if (line.startsWith(contentLength)) { if (line.startsWith(contentLength)) {
Try(line.drop(contentLength.length).toInt) foreach { len => Try(line.drop(contentLength.length).toInt) foreach { len =>
def doDrainHeaders(): Unit =
inputStream.read match
case `newline` if onCarriageReturn =>
getLine()
onCarriageReturn = false
consecutiveLineEndings += 1
case `carriageReturn` => onCarriageReturn = true
case -1 => running.set(false)
case c =>
if (c == newline) getLine()
else {
if (index >= headerBuffer.length) expandHeaderBuffer()
headerBuffer(index) = c.toByte
index += 1
}
onCarriageReturn = false
consecutiveLineEndings = 0
def drainHeaders(): Unit = def drainHeaders(): Unit =
do { doDrainHeaders()
inputStream.read match { while consecutiveLineEndings < 2 && running.get do doDrainHeaders()
case `newline` if onCarriageReturn =>
getLine()
onCarriageReturn = false
consecutiveLineEndings += 1
case `carriageReturn` => onCarriageReturn = true
case -1 => running.set(false)
case c =>
if (c == newline) getLine()
else {
if (index >= headerBuffer.length) expandHeaderBuffer()
headerBuffer(index) = c.toByte
index += 1
}
onCarriageReturn = false
consecutiveLineEndings = 0
}
} while (consecutiveLineEndings < 2 && running.get)
drainHeaders() drainHeaders()
if (running.get) { if (running.get) {
val buf = new Array[Byte](len) val buf = new Array[Byte](len)
var offset = 0 var offset = 0
do { def run1(): Unit = offset += inputStream.read(buf, offset, len - offset)
offset += inputStream.read(buf, offset, len - offset) run1()
} while (offset < len && running.get) while offset < len && running.get do run1()
if (running.get) content = buf.toSeq if (running.get) content = buf.toSeq
} }
} }
@ -99,7 +101,9 @@ private[sbt] object ReadJsonFromInputStream {
index += 1 index += 1
} }
} while (content.isEmpty && running.get)
run()
while content.isEmpty && running.get do run()
content content
} }

View File

@ -15,8 +15,8 @@ object Dependencies {
// sbt modules // sbt modules
private val ioVersion = nightlyVersion.getOrElse("1.8.0") private val ioVersion = nightlyVersion.getOrElse("1.8.0")
private val lmVersion = private val lmVersion =
sys.props.get("sbt.build.lm.version").orElse(nightlyVersion).getOrElse("1.8.0") sys.props.get("sbt.build.lm.version").orElse(nightlyVersion).getOrElse("2.0.0-alpha1")
val zincVersion = nightlyVersion.getOrElse("1.8.0") val zincVersion = nightlyVersion.getOrElse("2.0.0-alpha1")
private val sbtIO = "org.scala-sbt" %% "io" % ioVersion private val sbtIO = "org.scala-sbt" %% "io" % ioVersion
@ -57,10 +57,7 @@ object Dependencies {
c: Option[Configuration] = None c: Option[Configuration] = None
) = (p: Project) => { ) = (p: Project) => {
val m0 = moduleId.withConfigurations(c.map(_.name)) val m0 = moduleId.withConfigurations(c.map(_.name))
val m = m0.name match { val m = m0
case "compiler-interface" => m0
case _ => m0.cross(CrossVersion.for3Use2_13)
}
path match { path match {
case Some(f) => case Some(f) =>
p.dependsOn(ClasspathDependency(ProjectRef(file(f), projectName), c.map(_.name))) p.dependsOn(ClasspathDependency(ProjectRef(file(f), projectName), c.map(_.name)))