Adds "reboot dev"

This adds a new option `dev` to the `reboot` command, which deletes the only the current sbt artifacts from the boot directory. `reboot dev` reads actively from `build.properties` instead of using the current state since `reboot` can restart into another sbt version.

In general, `reboot dev` is intended for the local development of sbt.

Fixes #3590
This commit is contained in:
Eugene Yokota 2017-10-21 01:27:40 -05:00
parent bcd21ba9b6
commit 6ac4571197
6 changed files with 77 additions and 13 deletions

View File

@ -309,6 +309,8 @@ lazy val commandProj = (project in file("main-command"))
exclude[ReversedMissingMethodProblem]("sbt.internal.server.ServerInstance.*"), exclude[ReversedMissingMethodProblem]("sbt.internal.server.ServerInstance.*"),
// Added method to CommandChannel. internal. // Added method to CommandChannel. internal.
exclude[ReversedMissingMethodProblem]("sbt.internal.CommandChannel.*"), exclude[ReversedMissingMethodProblem]("sbt.internal.CommandChannel.*"),
// Added an overload to reboot. The overload is private[sbt].
exclude[ReversedMissingMethodProblem]("sbt.StateOps.reboot"),
) )
) )
.configure( .configure(

View File

@ -117,14 +117,17 @@ $HelpCommand <regular expression>
def RebootCommand = "reboot" def RebootCommand = "reboot"
def RebootDetailed = def RebootDetailed =
RebootCommand + """ [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 'full' is specified, the boot directory (`~/.sbt/boot` by default) If 'dev' is specified, the current sbt artifacts from the boot directory
is deleted before restarting. This forces an update of sbt and Scala (`~/.sbt/boot` by default) are deleted before restarting.
and is useful when working with development versions of sbt or Scala.""" This forces an update of sbt and Scala, which is useful when working with development
versions of sbt.
If 'full' is specified, the boot directory is wiped out before restarting.
"""
def Multi = ";" def Multi = ";"
def MultiBrief = def MultiBrief =

View File

@ -175,10 +175,19 @@ object BasicCommands {
} }
def reboot: Command = def reboot: Command =
Command(RebootCommand, Help.more(RebootCommand, RebootDetailed))(rebootParser)((s, full) => Command(RebootCommand, Help.more(RebootCommand, RebootDetailed))(rebootOptionParser) {
s reboot full) case (s, (full, currentOnly)) =>
s.reboot(full, currentOnly)
}
def rebootParser(s: State): Parser[Boolean] = token(Space ~> "full" ^^^ true) ?? false @deprecated("Use rebootOptionParser", "1.1.0")
def rebootParser(s: State): Parser[Boolean] =
rebootOptionParser(s) map { case (full, currentOnly) => full }
private[sbt] def rebootOptionParser(s: State): Parser[(Boolean, Boolean)] =
token(
Space ~> (("full" ^^^ ((true, false))) |
("dev" ^^^ ((false, true))))) ?? ((false, false))
def call: Command = def call: Command =
Command(ApplyCommand, Help.more(ApplyCommand, ApplyDetailed))(_ => callParser) { Command(ApplyCommand, Help.more(ApplyCommand, ApplyDetailed))(_ => callParser) {

View File

@ -89,6 +89,17 @@ trait StateOps {
*/ */
def reboot(full: Boolean): State def reboot(full: Boolean): State
/**
* Reboots sbt. A reboot restarts execution from the entry point of the launcher.
* A reboot is designed to be as close as possible to actually restarting the JVM without actually doing so.
* Because the JVM is not restarted, JVM exit hooks are not run.
* State.exitHooks should be used instead and those will be run before rebooting.
* If `full` is true, the boot directory is deleted before starting again.
* If `currentOnly` is true, the artifacts for the current sbt version is deleted.
* This command is currently implemented to not return, but may be implemented in the future to only reboot at the next command processing step.
*/
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
@ -248,12 +259,18 @@ object State {
def baseDir: File = s.configuration.baseDirectory def baseDir: File = s.configuration.baseDirectory
def setNext(n: Next) = s.copy(next = n) def setNext(n: Next) = s.copy(next = n)
def continue = setNext(Continue) def continue = setNext(Continue)
def reboot(full: Boolean) = {
runExitHooks(); /** Implementation of reboot. */
throw new xsbti.FullReload( def reboot(full: Boolean): State = reboot(full, false)
(s.remainingCommands map { case e: Exec => e.commandLine }).toArray,
full) /** Implementation of reboot. */
private[sbt] def reboot(full: Boolean, currentOnly: Boolean): State = {
runExitHooks()
val rs = s.remainingCommands map { case e: Exec => e.commandLine }
if (currentOnly) throw new RebootCurrent(rs)
else throw new xsbti.FullReload(rs.toArray, full)
} }
def reload = runExitHooks().setNext(new Return(defaultReload(s))) def reload = runExitHooks().setNext(new Return(defaultReload(s)))
def clearGlobalLog = setNext(ClearGlobalLog) def clearGlobalLog = setNext(ClearGlobalLog)
def keepLastLog = setNext(KeepLastLog) def keepLastLog = setNext(KeepLastLog)
@ -320,3 +337,5 @@ object State {
private[sbt] def getBoolean(s: State, key: AttributeKey[Boolean], default: Boolean): Boolean = private[sbt] def getBoolean(s: State, key: AttributeKey[Boolean], default: Boolean): Boolean =
s.get(key) getOrElse default s.get(key) getOrElse default
} }
private[sbt] final class RebootCurrent(val arguments: List[String]) extends RuntimeException

View File

@ -7,11 +7,12 @@
package sbt package sbt
import java.util.Properties
import scala.annotation.tailrec import scala.annotation.tailrec
import scala.util.control.NonFatal import scala.util.control.NonFatal
import jline.TerminalFactory import jline.TerminalFactory
import sbt.io.Using import sbt.io.{ IO, Using }
import sbt.internal.util.{ ErrorHandling, GlobalLogBacking } import sbt.internal.util.{ ErrorHandling, GlobalLogBacking }
import sbt.internal.util.complete.DefaultParsers import sbt.internal.util.complete.DefaultParsers
import sbt.util.Logger import sbt.util.Logger
@ -58,6 +59,10 @@ object MainLoop {
case e: xsbti.FullReload => case e: xsbti.FullReload =>
deleteLastLog(logBacking) deleteLastLog(logBacking)
throw e // pass along a reboot request throw e // pass along a reboot request
case e: RebootCurrent =>
deleteLastLog(logBacking)
deleteCurrentArtifacts(state)
throw new xsbti.FullReload(e.arguments.toArray, false)
case NonFatal(e) => case NonFatal(e) =>
System.err.println( System.err.println(
"sbt appears to be exiting abnormally.\n The log file for this session is at " + logBacking.file) "sbt appears to be exiting abnormally.\n The log file for this session is at " + logBacking.file)
@ -69,6 +74,28 @@ object MainLoop {
def deleteLastLog(logBacking: GlobalLogBacking): Unit = def deleteLastLog(logBacking: GlobalLogBacking): Unit =
logBacking.last.foreach(_.delete()) logBacking.last.foreach(_.delete())
/** Deletes the current sbt artifacts from boot. */
private[sbt] def deleteCurrentArtifacts(state: State): Unit = {
import sbt.io.syntax._
val provider = state.configuration.provider
val appId = provider.id
// If we can obtain boot directory more accurately it'd be better.
val defaultBoot = BuildPaths.defaultGlobalBase / "boot"
val buildProps = state.baseDir / "project" / "build.properties"
// First try reading the sbt version from build.properties file.
val sbtVersionOpt = if (buildProps.exists) {
val buildProperties = new Properties()
IO.load(buildProperties, buildProps)
Option(buildProperties.getProperty("sbt.version"))
} else None
val sbtVersion = sbtVersionOpt.getOrElse(appId.version)
val currentArtDirs = defaultBoot * "*" / appId.groupID / appId.name / sbtVersion
currentArtDirs.get foreach { dir =>
state.log.info(s"Deleting $dir")
IO.delete(dir)
}
}
/** Runs the next sequence of commands with global logging in place. */ /** Runs the next sequence of commands with global logging in place. */
def runWithNewLog(state: State, logBacking: GlobalLogBacking): RunNext = def runWithNewLog(state: State, logBacking: GlobalLogBacking): RunNext =
Using.fileWriter(append = true)(logBacking.file) { writer => Using.fileWriter(append = true)(logBacking.file) { writer =>
@ -109,6 +136,7 @@ object MainLoop {
ErrorHandling.wideConvert { state.process(processCommand) } match { ErrorHandling.wideConvert { state.process(processCommand) } match {
case Right(s) => s case Right(s) => s
case Left(t: xsbti.FullReload) => throw t case Left(t: xsbti.FullReload) => throw t
case Left(t: RebootCurrent) => throw t
case Left(t) => state.handleError(t) case Left(t) => state.handleError(t)
} }

3
notes/1.1.0/reboot.md Normal file
View File

@ -0,0 +1,3 @@
### Improvements
- Adds `reboot dev` command, which deletes the current artifact from the boot directory. This is useful when working with development versions of sbt.