diff --git a/main-command/src/main/scala/sbt/BasicKeys.scala b/main-command/src/main/scala/sbt/BasicKeys.scala index 86dd9343f..1570d392b 100644 --- a/main-command/src/main/scala/sbt/BasicKeys.scala +++ b/main-command/src/main/scala/sbt/BasicKeys.scala @@ -39,6 +39,12 @@ object BasicKeys { "The wire protocol for the server command.", 10000) + val autoStartServer = + AttributeKey[Boolean]( + "autoStartServer", + "If true, the sbt server will startup automatically during interactive sessions.", + 10000) + // Unlike other BasicKeys, this is not used directly as a setting key, // and severLog / logLevel is used instead. private[sbt] val serverLogLevel = diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 721e08914..b9ab939ef 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -268,6 +268,7 @@ object Defaults extends BuildCommon { .getOrElse(GCUtil.defaultForceGarbageCollection), minForcegcInterval :== GCUtil.defaultMinForcegcInterval, interactionService :== CommandLineUIService, + autoStartServer := true, serverHost := "127.0.0.1", serverPort := 5000 + (Hash .toHex(Hash(appConfiguration.value.baseDirectory.toString)) diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index bfcb3139e..8e26b54fb 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -131,6 +131,7 @@ object Keys { // Command keys val historyPath = SettingKey(BasicKeys.historyPath) val shellPrompt = SettingKey(BasicKeys.shellPrompt) + val autoStartServer = SettingKey(BasicKeys.autoStartServer) val serverPort = SettingKey(BasicKeys.serverPort) val serverHost = SettingKey(BasicKeys.serverHost) val serverAuthentication = SettingKey(BasicKeys.serverAuthentication) diff --git a/main/src/main/scala/sbt/Project.scala b/main/src/main/scala/sbt/Project.scala index fd175f6c6..f292ca0e1 100755 --- a/main/src/main/scala/sbt/Project.scala +++ b/main/src/main/scala/sbt/Project.scala @@ -21,6 +21,7 @@ import Keys.{ sessionSettings, shellPrompt, templateResolverInfos, + autoStartServer, serverHost, serverLog, serverPort, @@ -462,6 +463,7 @@ object Project extends ProjectExtra { val prompt = get(shellPrompt) val trs = (templateResolverInfos in Global get structure.data).toList.flatten val watched = get(watch) + val startSvr: Option[Boolean] = get(autoStartServer) val host: Option[String] = get(serverHost) val port: Option[Int] = get(serverPort) val authentication: Option[Set[ServerAuthentication]] = get(serverAuthentication) @@ -474,6 +476,7 @@ object Project extends ProjectExtra { s.attributes .setCond(Watched.Configuration, watched) .put(historyPath.key, history) + .setCond(autoStartServer.key, startSvr) .setCond(serverPort.key, port) .setCond(serverHost.key, host) .setCond(serverAuthentication.key, authentication) diff --git a/main/src/main/scala/sbt/internal/CommandExchange.scala b/main/src/main/scala/sbt/internal/CommandExchange.scala index d7b52280d..0b68cae24 100644 --- a/main/src/main/scala/sbt/internal/CommandExchange.scala +++ b/main/src/main/scala/sbt/internal/CommandExchange.scala @@ -14,6 +14,7 @@ import java.util.concurrent.atomic._ import scala.collection.mutable.ListBuffer import scala.annotation.tailrec import BasicKeys.{ + autoStartServer, serverHost, serverPort, serverAuthentication, @@ -43,7 +44,7 @@ import sbt.util.{ Level, Logger, LogExchange } * this exchange, which could serve command request from either of the channel. */ private[sbt] final class CommandExchange { - private val autoStartServer = sys.props.get("sbt.server.autostart") map { + private val autoStartServerSysProp = sys.props.get("sbt.server.autostart") map { _.toLowerCase == "true" } getOrElse true private val lock = new AnyRef {} @@ -87,7 +88,11 @@ private[sbt] final class CommandExchange { consoleChannel = Some(x) subscribe(x) } - if (autoStartServer) runServer(s) + val autoStartServerAttr = (s get autoStartServer) match { + case Some(bool) => bool + case None => true + } + if (autoStartServerSysProp && autoStartServerAttr) runServer(s) else s } diff --git a/notes/1.1.1/autoStartServer.md b/notes/1.1.1/autoStartServer.md new file mode 100644 index 000000000..cc00c3fbc --- /dev/null +++ b/notes/1.1.1/autoStartServer.md @@ -0,0 +1,31 @@ +### Improvements + +This pull request implements a Boolean setting called `autoStartServer`, whose default value is `true'. + +If a build or plugin explicitly sets it to `false`, the sbt-1.x server will not start up +(exactly as if the system property `sbt.server.autostart` were set to `false`). + +Users who set `autoStartServer` to `false` may manually execute `startServer` at the interactive prompt, +if they wish to use the server during a shell session. + +### Motivation + +Projects often encounter private information, such as deployment credentials, private keys, etc. +For such projects, it may be preferable to reduce the potential attack surface than to enjoy the +interoperability offered by sbt's server. Projects that wish to make this tradeoff can set `autoStartServer` +to `false` in their build. Security-sensitive plugins can disable `autoStartServer` as well, modifying the +default behavior in favor of security. + +(My own motivation is that I am working on a [plugin for developing Ethereum applications](https://github.com/swaldman/sbt-ethereum) +with scala and sbt. It must work with extremely sensitive private keys.) + +--- + +See also a [recent conversation on Stack Exchange](https://stackoverflow.com/questions/48591179/can-one-disable-the-sbt-1-x-server/48593906#48593906). + +--- + +##### History + +2018-02-06 Modified from negative `suppressServer` to positive `autoStartServer` at the (sensible) request of @eed3si9n +